mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	Merge pull request #1259 from lioncash/relocate
yuzu: Move GameListWorker to its own source files
This commit is contained in:
		
						commit
						2515d2433b
					
				| @ -43,6 +43,8 @@ add_executable(yuzu | ||||
|     game_list.cpp | ||||
|     game_list.h | ||||
|     game_list_p.h | ||||
|     game_list_worker.cpp | ||||
|     game_list_worker.h | ||||
|     hotkeys.cpp | ||||
|     hotkeys.h | ||||
|     main.cpp | ||||
|  | ||||
| @ -18,17 +18,10 @@ | ||||
| #include "common/common_types.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/control_metadata.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/vfs_real.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "yuzu/game_list.h" | ||||
| #include "yuzu/game_list_p.h" | ||||
| #include "yuzu/game_list_worker.h" | ||||
| #include "yuzu/main.h" | ||||
| #include "yuzu/ui_settings.h" | ||||
| 
 | ||||
| @ -436,45 +429,6 @@ void GameList::LoadInterfaceLayout() { | ||||
| 
 | ||||
| const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; | ||||
| 
 | ||||
| static 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); | ||||
| } | ||||
| 
 | ||||
| static bool IsExtractedNCAMain(const std::string& file_name) { | ||||
|     return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; | ||||
| } | ||||
| 
 | ||||
| static QString FormatGameName(const std::string& physical_name) { | ||||
|     const QString physical_name_as_qstring = QString::fromStdString(physical_name); | ||||
|     const QFileInfo file_info(physical_name_as_qstring); | ||||
| 
 | ||||
|     if (IsExtractedNCAMain(physical_name)) { | ||||
|         return file_info.dir().path(); | ||||
|     } | ||||
| 
 | ||||
|     return physical_name_as_qstring; | ||||
| } | ||||
| 
 | ||||
| static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, | ||||
|                                        bool updatable = true) { | ||||
|     QString out; | ||||
|     for (const auto& kv : patch_manager.GetPatchVersionNames()) { | ||||
|         if (!updatable && kv.first == FileSys::PatchType::Update) | ||||
|             continue; | ||||
| 
 | ||||
|         if (kv.second.empty()) { | ||||
|             out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str()); | ||||
|         } else { | ||||
|             out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second) | ||||
|                            .c_str()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     out.chop(1); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| void GameList::RefreshGameDirectory() { | ||||
|     if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { | ||||
|         LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); | ||||
| @ -482,176 +436,3 @@ void GameList::RefreshGameDirectory() { | ||||
|         PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, | ||||
|                                       const std::shared_ptr<FileSys::NCA>& nca, | ||||
|                                       std::vector<u8>& icon, std::string& name) { | ||||
|     auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); | ||||
|     if (icon_file != nullptr) | ||||
|         icon = icon_file->ReadAllBytes(); | ||||
|     if (nacp != nullptr) | ||||
|         name = nacp->GetApplicationName(); | ||||
| } | ||||
| 
 | ||||
| GameListWorker::GameListWorker( | ||||
|     FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) | ||||
|     : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), | ||||
|       compatibility_list(compatibility_list) {} | ||||
| 
 | ||||
| GameListWorker::~GameListWorker() = default; | ||||
| 
 | ||||
| void GameListWorker::AddInstalledTitlesToGameList() { | ||||
|     const auto cache = Service::FileSystem::GetUnionContents(); | ||||
|     const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, | ||||
|                                                           FileSys::ContentRecordType::Program); | ||||
| 
 | ||||
|     for (const auto& game : installed_games) { | ||||
|         const auto& file = cache->GetEntryUnparsed(game); | ||||
|         std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); | ||||
|         if (!loader) | ||||
|             continue; | ||||
| 
 | ||||
|         std::vector<u8> icon; | ||||
|         std::string name; | ||||
|         u64 program_id = 0; | ||||
|         loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|         const FileSys::PatchManager patch{program_id}; | ||||
|         const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); | ||||
|         if (control != nullptr) | ||||
|             GetMetadataFromControlNCA(patch, control, icon, name); | ||||
| 
 | ||||
|         auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|         // The game list uses this as compatibility number for untested games
 | ||||
|         QString compatibility("99"); | ||||
|         if (it != compatibility_list.end()) | ||||
|             compatibility = it->second.first; | ||||
| 
 | ||||
|         emit EntryReady({ | ||||
|             new GameListItemPath( | ||||
|                 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), | ||||
|                 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), | ||||
|                 program_id), | ||||
|             new GameListItemCompat(compatibility), | ||||
|             new GameListItem(FormatPatchNameVersions(patch)), | ||||
|             new GameListItem( | ||||
|                 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|             new GameListItemSize(file->GetSize()), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, | ||||
|                                                        FileSys::ContentRecordType::Control); | ||||
| 
 | ||||
|     for (const auto& entry : control_data) { | ||||
|         const auto nca = cache->GetEntry(entry); | ||||
|         if (nca != nullptr) | ||||
|             nca_control_map.insert_or_assign(entry.title_id, nca); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::FillControlMap(const std::string& dir_path) { | ||||
|     const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, | ||||
|                                              const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         QFileInfo file_info(physical_name.c_str()); | ||||
|         if (!is_dir && file_info.suffix().toStdString() == "nca") { | ||||
|             auto nca = | ||||
|                 std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); | ||||
|             if (nca->GetType() == FileSys::NCAContentType::Control) | ||||
|                 nca_control_map.insert_or_assign(nca->GetTitleId(), nca); | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { | ||||
|     const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, | ||||
|                                             const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         if (!is_dir && | ||||
|             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { | ||||
|             std::unique_ptr<Loader::AppLoader> loader = | ||||
|                 Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); | ||||
|             if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || | ||||
|                              loader->GetFileType() == Loader::FileType::Error) && | ||||
|                             !UISettings::values.show_unknown)) | ||||
|                 return true; | ||||
| 
 | ||||
|             std::vector<u8> icon; | ||||
|             const auto res1 = loader->ReadIcon(icon); | ||||
| 
 | ||||
|             u64 program_id = 0; | ||||
|             const auto res2 = loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|             std::string name = " "; | ||||
|             const auto res3 = loader->ReadTitle(name); | ||||
| 
 | ||||
|             const FileSys::PatchManager patch{program_id}; | ||||
| 
 | ||||
|             if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && | ||||
|                 res2 == Loader::ResultStatus::Success) { | ||||
|                 // Use from metadata pool.
 | ||||
|                 if (nca_control_map.find(program_id) != nca_control_map.end()) { | ||||
|                     const auto nca = nca_control_map[program_id]; | ||||
|                     GetMetadataFromControlNCA(patch, nca, icon, name); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|             // The game list uses this as compatibility number for untested games
 | ||||
|             QString compatibility("99"); | ||||
|             if (it != compatibility_list.end()) | ||||
|                 compatibility = it->second.first; | ||||
| 
 | ||||
|             emit EntryReady({ | ||||
|                 new GameListItemPath( | ||||
|                     FormatGameName(physical_name), icon, QString::fromStdString(name), | ||||
|                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), | ||||
|                     program_id), | ||||
|                 new GameListItemCompat(compatibility), | ||||
|                 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())), | ||||
|                 new GameListItem( | ||||
|                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|                 new GameListItemSize(FileUtil::GetSize(physical_name)), | ||||
|             }); | ||||
|         } else if (is_dir && recursion > 0) { | ||||
|             watch_list.append(QString::fromStdString(physical_name)); | ||||
|             AddFstEntriesToGameList(physical_name, recursion - 1); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::run() { | ||||
|     stop_processing = false; | ||||
|     watch_list.append(dir_path); | ||||
|     FillControlMap(dir_path.toStdString()); | ||||
|     AddInstalledTitlesToGameList(); | ||||
|     AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); | ||||
|     nca_control_map.clear(); | ||||
|     emit Finished(watch_list); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::Cancel() { | ||||
|     this->disconnect(); | ||||
|     stop_processing = true; | ||||
| } | ||||
|  | ||||
| @ -6,9 +6,7 @@ | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| #include <utility> | ||||
| @ -16,7 +14,6 @@ | ||||
| #include <QCoreApplication> | ||||
| #include <QImage> | ||||
| #include <QObject> | ||||
| #include <QRunnable> | ||||
| #include <QStandardItem> | ||||
| #include <QString> | ||||
| 
 | ||||
| @ -26,12 +23,6 @@ | ||||
| #include "yuzu/ui_settings.h" | ||||
| #include "yuzu/util/util.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class NCA; | ||||
| class RegisteredCache; | ||||
| class VfsFilesystem; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the default icon (for games without valid SMDH) | ||||
|  * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) | ||||
| @ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) { | ||||
|     return icon; | ||||
| } | ||||
| 
 | ||||
| static auto FindMatchingCompatibilityEntry( | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list, | ||||
|     u64 program_id) { | ||||
|     return std::find_if( | ||||
|         compatibility_list.begin(), compatibility_list.end(), | ||||
|         [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) { | ||||
|             std::string pid = fmt::format("{:016X}", program_id); | ||||
|             return element.first == pid; | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| class GameListItem : public QStandardItem { | ||||
| 
 | ||||
| public: | ||||
| @ -197,49 +177,13 @@ public: | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Asynchronous worker object for populating the game list. | ||||
|  * Communicates with other threads through Qt's signal/slot system. | ||||
|  */ | ||||
| class GameListWorker : public QObject, public QRunnable { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GameListWorker( | ||||
|         std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, | ||||
|         const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); | ||||
|     ~GameListWorker() override; | ||||
| 
 | ||||
| public slots: | ||||
|     /// Starts the processing of directory tree information.
 | ||||
|     void run() override; | ||||
|     /// Tells the worker that it should no longer continue processing. Thread-safe.
 | ||||
|     void Cancel(); | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|      * The `EntryReady` signal is emitted once an entry has been prepared and is ready | ||||
|      * to be added to the game list. | ||||
|      * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. | ||||
|      */ | ||||
|     void EntryReady(QList<QStandardItem*> entry_items); | ||||
| 
 | ||||
|     /**
 | ||||
|      * After the worker has traversed the game directory looking for entries, this signal is emmited | ||||
|      * with a list of folders that should be watched for changes as well. | ||||
|      */ | ||||
|     void Finished(QStringList watch_list); | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<FileSys::VfsFilesystem> vfs; | ||||
|     std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; | ||||
|     QStringList watch_list; | ||||
|     QString dir_path; | ||||
|     bool deep_scan; | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; | ||||
|     std::atomic_bool stop_processing; | ||||
| 
 | ||||
|     void AddInstalledTitlesToGameList(); | ||||
|     void FillControlMap(const std::string& dir_path); | ||||
|     void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); | ||||
| }; | ||||
| inline auto FindMatchingCompatibilityEntry( | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list, | ||||
|     u64 program_id) { | ||||
|     return std::find_if( | ||||
|         compatibility_list.begin(), compatibility_list.end(), | ||||
|         [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) { | ||||
|             std::string pid = fmt::format("{:016X}", program_id); | ||||
|             return element.first == pid; | ||||
|         }); | ||||
| } | ||||
|  | ||||
							
								
								
									
										239
									
								
								src/yuzu/game_list_worker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								src/yuzu/game_list_worker.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,239 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include <QDir> | ||||
| #include <QFileInfo> | ||||
| 
 | ||||
| #include "common/common_paths.h" | ||||
| #include "common/file_util.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/control_metadata.h" | ||||
| #include "core/file_sys/mode.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/patch_manager.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "yuzu/game_list.h" | ||||
| #include "yuzu/game_list_p.h" | ||||
| #include "yuzu/game_list_worker.h" | ||||
| #include "yuzu/ui_settings.h" | ||||
| 
 | ||||
| namespace { | ||||
| void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, | ||||
|                                const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon, | ||||
|                                std::string& name) { | ||||
|     auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); | ||||
|     if (icon_file != nullptr) | ||||
|         icon = icon_file->ReadAllBytes(); | ||||
|     if (nacp != nullptr) | ||||
|         name = nacp->GetApplicationName(); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| bool IsExtractedNCAMain(const std::string& file_name) { | ||||
|     return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; | ||||
| } | ||||
| 
 | ||||
| QString FormatGameName(const std::string& physical_name) { | ||||
|     const QString physical_name_as_qstring = QString::fromStdString(physical_name); | ||||
|     const QFileInfo file_info(physical_name_as_qstring); | ||||
| 
 | ||||
|     if (IsExtractedNCAMain(physical_name)) { | ||||
|         return file_info.dir().path(); | ||||
|     } | ||||
| 
 | ||||
|     return physical_name_as_qstring; | ||||
| } | ||||
| 
 | ||||
| QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { | ||||
|     QString out; | ||||
|     for (const auto& kv : patch_manager.GetPatchVersionNames()) { | ||||
|         if (!updatable && kv.first == FileSys::PatchType::Update) | ||||
|             continue; | ||||
| 
 | ||||
|         if (kv.second.empty()) { | ||||
|             out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str()); | ||||
|         } else { | ||||
|             out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second) | ||||
|                            .c_str()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     out.chop(1); | ||||
|     return out; | ||||
| } | ||||
| } // Anonymous namespace
 | ||||
| 
 | ||||
| GameListWorker::GameListWorker( | ||||
|     FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) | ||||
|     : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), | ||||
|       compatibility_list(compatibility_list) {} | ||||
| 
 | ||||
| GameListWorker::~GameListWorker() = default; | ||||
| 
 | ||||
| void GameListWorker::AddInstalledTitlesToGameList() { | ||||
|     const auto cache = Service::FileSystem::GetUnionContents(); | ||||
|     const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, | ||||
|                                                           FileSys::ContentRecordType::Program); | ||||
| 
 | ||||
|     for (const auto& game : installed_games) { | ||||
|         const auto& file = cache->GetEntryUnparsed(game); | ||||
|         std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); | ||||
|         if (!loader) | ||||
|             continue; | ||||
| 
 | ||||
|         std::vector<u8> icon; | ||||
|         std::string name; | ||||
|         u64 program_id = 0; | ||||
|         loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|         const FileSys::PatchManager patch{program_id}; | ||||
|         const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); | ||||
|         if (control != nullptr) | ||||
|             GetMetadataFromControlNCA(patch, control, icon, name); | ||||
| 
 | ||||
|         auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|         // The game list uses this as compatibility number for untested games
 | ||||
|         QString compatibility("99"); | ||||
|         if (it != compatibility_list.end()) | ||||
|             compatibility = it->second.first; | ||||
| 
 | ||||
|         emit EntryReady({ | ||||
|             new GameListItemPath( | ||||
|                 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), | ||||
|                 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), | ||||
|                 program_id), | ||||
|             new GameListItemCompat(compatibility), | ||||
|             new GameListItem(FormatPatchNameVersions(patch)), | ||||
|             new GameListItem( | ||||
|                 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|             new GameListItemSize(file->GetSize()), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, | ||||
|                                                        FileSys::ContentRecordType::Control); | ||||
| 
 | ||||
|     for (const auto& entry : control_data) { | ||||
|         const auto nca = cache->GetEntry(entry); | ||||
|         if (nca != nullptr) | ||||
|             nca_control_map.insert_or_assign(entry.title_id, nca); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::FillControlMap(const std::string& dir_path) { | ||||
|     const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, | ||||
|                                              const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         QFileInfo file_info(physical_name.c_str()); | ||||
|         if (!is_dir && file_info.suffix().toStdString() == "nca") { | ||||
|             auto nca = | ||||
|                 std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); | ||||
|             if (nca->GetType() == FileSys::NCAContentType::Control) | ||||
|                 nca_control_map.insert_or_assign(nca->GetTitleId(), nca); | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { | ||||
|     const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, | ||||
|                                             const std::string& virtual_name) -> bool { | ||||
|         std::string physical_name = directory + DIR_SEP + virtual_name; | ||||
| 
 | ||||
|         if (stop_processing) | ||||
|             return false; // Breaks the callback loop.
 | ||||
| 
 | ||||
|         bool is_dir = FileUtil::IsDirectory(physical_name); | ||||
|         if (!is_dir && | ||||
|             (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { | ||||
|             std::unique_ptr<Loader::AppLoader> loader = | ||||
|                 Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); | ||||
|             if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || | ||||
|                              loader->GetFileType() == Loader::FileType::Error) && | ||||
|                             !UISettings::values.show_unknown)) | ||||
|                 return true; | ||||
| 
 | ||||
|             std::vector<u8> icon; | ||||
|             const auto res1 = loader->ReadIcon(icon); | ||||
| 
 | ||||
|             u64 program_id = 0; | ||||
|             const auto res2 = loader->ReadProgramId(program_id); | ||||
| 
 | ||||
|             std::string name = " "; | ||||
|             const auto res3 = loader->ReadTitle(name); | ||||
| 
 | ||||
|             const FileSys::PatchManager patch{program_id}; | ||||
| 
 | ||||
|             if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && | ||||
|                 res2 == Loader::ResultStatus::Success) { | ||||
|                 // Use from metadata pool.
 | ||||
|                 if (nca_control_map.find(program_id) != nca_control_map.end()) { | ||||
|                     const auto nca = nca_control_map[program_id]; | ||||
|                     GetMetadataFromControlNCA(patch, nca, icon, name); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| 
 | ||||
|             // The game list uses this as compatibility number for untested games
 | ||||
|             QString compatibility("99"); | ||||
|             if (it != compatibility_list.end()) | ||||
|                 compatibility = it->second.first; | ||||
| 
 | ||||
|             emit EntryReady({ | ||||
|                 new GameListItemPath( | ||||
|                     FormatGameName(physical_name), icon, QString::fromStdString(name), | ||||
|                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), | ||||
|                     program_id), | ||||
|                 new GameListItemCompat(compatibility), | ||||
|                 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())), | ||||
|                 new GameListItem( | ||||
|                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|                 new GameListItemSize(FileUtil::GetSize(physical_name)), | ||||
|             }); | ||||
|         } else if (is_dir && recursion > 0) { | ||||
|             watch_list.append(QString::fromStdString(physical_name)); | ||||
|             AddFstEntriesToGameList(physical_name, recursion - 1); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     }; | ||||
| 
 | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::run() { | ||||
|     stop_processing = false; | ||||
|     watch_list.append(dir_path); | ||||
|     FillControlMap(dir_path.toStdString()); | ||||
|     AddInstalledTitlesToGameList(); | ||||
|     AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); | ||||
|     nca_control_map.clear(); | ||||
|     emit Finished(watch_list); | ||||
| } | ||||
| 
 | ||||
| void GameListWorker::Cancel() { | ||||
|     this->disconnect(); | ||||
|     stop_processing = true; | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/yuzu/game_list_worker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/yuzu/game_list_worker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| #include <QList> | ||||
| #include <QObject> | ||||
| #include <QRunnable> | ||||
| #include <QString> | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| class QStandardItem; | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class NCA; | ||||
| class VfsFilesystem; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| /**
 | ||||
|  * Asynchronous worker object for populating the game list. | ||||
|  * Communicates with other threads through Qt's signal/slot system. | ||||
|  */ | ||||
| class GameListWorker : public QObject, public QRunnable { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GameListWorker( | ||||
|         std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, | ||||
|         const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); | ||||
|     ~GameListWorker() override; | ||||
| 
 | ||||
|     /// Starts the processing of directory tree information.
 | ||||
|     void run() override; | ||||
| 
 | ||||
|     /// Tells the worker that it should no longer continue processing. Thread-safe.
 | ||||
|     void Cancel(); | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|      * The `EntryReady` signal is emitted once an entry has been prepared and is ready | ||||
|      * to be added to the game list. | ||||
|      * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. | ||||
|      */ | ||||
|     void EntryReady(QList<QStandardItem*> entry_items); | ||||
| 
 | ||||
|     /**
 | ||||
|      * After the worker has traversed the game directory looking for entries, this signal is emitted | ||||
|      * with a list of folders that should be watched for changes as well. | ||||
|      */ | ||||
|     void Finished(QStringList watch_list); | ||||
| 
 | ||||
| private: | ||||
|     void AddInstalledTitlesToGameList(); | ||||
|     void FillControlMap(const std::string& dir_path); | ||||
|     void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); | ||||
| 
 | ||||
|     std::shared_ptr<FileSys::VfsFilesystem> vfs; | ||||
|     std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; | ||||
|     QStringList watch_list; | ||||
|     QString dir_path; | ||||
|     bool deep_scan; | ||||
|     const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; | ||||
|     std::atomic_bool stop_processing; | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei