diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 3a61f9aa0..aa96a2e0d 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -76,6 +76,8 @@ add_executable(citra-qt game_list.cpp game_list.h game_list_p.h + game_list_worker.cpp + game_list_worker.h hotkeys.cpp hotkeys.h main.cpp diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index f736c21df..c16b63b86 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -23,6 +23,7 @@ #include #include "citra_qt/game_list.h" #include "citra_qt/game_list_p.h" +#include "citra_qt/game_list_worker.h" #include "citra_qt/main.h" #include "citra_qt/ui_settings.h" #include "common/common_paths.h" @@ -30,7 +31,6 @@ #include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/archive_source_sd_savedata.h" #include "core/hle/service/fs/archive.h" -#include "core/loader/loader.h" GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} @@ -648,11 +648,6 @@ void GameList::LoadInterfaceLayout() { const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", "cci", "cxi", "app"}; -static bool HasSupportedFileExtension(const std::string& file_name) { - QFileInfo file = QFileInfo(QString::fromStdString(file_name)); - return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); -} - void GameList::RefreshGameDirectory() { if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); @@ -678,123 +673,6 @@ QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_i return ""; } -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, - GameListDir* parent_dir) { - const auto callback = [this, recursion, parent_dir](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)) { - std::unique_ptr loader = Loader::GetLoader(physical_name); - if (!loader) - return true; - - u64 program_id = 0; - loader->ReadProgramId(program_id); - - u64 extdata_id = 0; - loader->ReadExtdataId(extdata_id); - - std::vector smdh = [program_id, &loader]() -> std::vector { - std::vector original_smdh; - loader->ReadIcon(original_smdh); - - if (program_id < 0x0004000000000000 || program_id > 0x00040000FFFFFFFF) - return original_smdh; - - std::string update_path = Service::AM::GetTitleContentPath( - Service::FS::MediaType::SDMC, program_id + 0x0000000E00000000); - - if (!FileUtil::Exists(update_path)) - return original_smdh; - - std::unique_ptr update_loader = Loader::GetLoader(update_path); - - if (!update_loader) - return original_smdh; - - std::vector update_smdh; - update_loader->ReadIcon(update_smdh); - return update_smdh; - }(); - - if (!Loader::IsValidSMDH(smdh) && UISettings::values.game_list_hide_no_icon) { - // Skip this invalid entry - return true; - } - - 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(QString::fromStdString(physical_name), smdh, program_id, - extdata_id), - new GameListItemCompat(compatibility), - new GameListItemRegion(smdh), - new GameListItem( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), - new GameListItemSize(FileUtil::GetSize(physical_name)), - }, - parent_dir); - - } else if (is_dir && recursion > 0) { - watch_list.append(QString::fromStdString(physical_name)); - AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir); - } - - return true; - }; - - FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); -} - -void GameListWorker::run() { - stop_processing = false; - for (UISettings::GameDir& game_dir : game_dirs) { - if (game_dir.path == "INSTALLED") { - QString path = - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) + - "Nintendo " - "3DS/00000000000000000000000000000000/" - "00000000000000000000000000000000/title/00040000"; - watch_list.append(path); - GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir); - emit DirEntryReady({game_list_dir}); - AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); - } else if (game_dir.path == "SYSTEM") { - QString path = - QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) + - "00000000000000000000000000000000/title/00040010"; - watch_list.append(path); - GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir); - emit DirEntryReady({game_list_dir}); - AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); - } else { - watch_list.append(game_dir.path); - GameListDir* game_list_dir = new GameListDir(game_dir); - emit DirEntryReady({game_list_dir}); - AddFstEntriesToGameList(game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0, - game_list_dir); - } - }; - emit Finished(watch_list); -} - -void GameListWorker::Cancel() { - this->disconnect(); - stop_processing = true; -} - GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { this->main_window = parent; diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index c355ecc1e..cfcf23615 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include "common/common_types.h" diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index cc8b92851..46f4714bc 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include @@ -59,17 +58,6 @@ static QPixmap GetDefaultIcon(bool large) { return icon; } -static auto FindMatchingCompatibilityEntry( - const std::unordered_map>& compatibility_list, - u64 program_id) { - return std::find_if( - compatibility_list.begin(), compatibility_list.end(), - [program_id](const std::pair>& element) { - std::string pid = fmt::format("{:016X}", program_id); - return element.first == pid; - }); -} - /** * Gets the short game title from SMDH data. * @param smdh SMDH data @@ -373,50 +361,16 @@ 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: - explicit GameListWorker( - QList& game_dirs, - const std::unordered_map>& compatibility_list) - : game_dirs(game_dirs), compatibility_list(compatibility_list) {} - -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 DirEntryReady(GameListDir* entry_items); - void EntryReady(QList entry_items, GameListDir* parent_dir); - - /** - * 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: - QStringList watch_list; - const std::unordered_map>& compatibility_list; - QList& game_dirs; - std::atomic_bool stop_processing; - - void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, - GameListDir* parent_dir); -}; +inline auto FindMatchingCompatibilityEntry( + const std::unordered_map>& compatibility_list, + u64 program_id) { + return std::find_if( + compatibility_list.begin(), compatibility_list.end(), + [program_id](const std::pair>& element) { + std::string pid = fmt::format("{:016X}", program_id); + return element.first == pid; + }); +} class GameList; class QHBoxLayout; diff --git a/src/citra_qt/game_list_worker.cpp b/src/citra_qt/game_list_worker.cpp new file mode 100644 index 000000000..b29f4246b --- /dev/null +++ b/src/citra_qt/game_list_worker.cpp @@ -0,0 +1,152 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include + +#include +#include + +#include "citra_qt/game_list.h" +#include "citra_qt/game_list_p.h" +#include "citra_qt/game_list_worker.h" +#include "citra_qt/ui_settings.h" +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/fs/archive.h" +#include "core/loader/loader.h" + +namespace { +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); +} +} // Anonymous namespace + +GameListWorker::GameListWorker( + QList& game_dirs, + const std::unordered_map>& compatibility_list) + : game_dirs(game_dirs), compatibility_list(compatibility_list) {} + +GameListWorker::~GameListWorker() = default; + +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, + GameListDir* parent_dir) { + const auto callback = [this, recursion, parent_dir](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)) { + std::unique_ptr loader = Loader::GetLoader(physical_name); + if (!loader) + return true; + + u64 program_id = 0; + loader->ReadProgramId(program_id); + + u64 extdata_id = 0; + loader->ReadExtdataId(extdata_id); + + std::vector smdh = [program_id, &loader]() -> std::vector { + std::vector original_smdh; + loader->ReadIcon(original_smdh); + + if (program_id < 0x0004000000000000 || program_id > 0x00040000FFFFFFFF) + return original_smdh; + + std::string update_path = Service::AM::GetTitleContentPath( + Service::FS::MediaType::SDMC, program_id + 0x0000000E00000000); + + if (!FileUtil::Exists(update_path)) + return original_smdh; + + std::unique_ptr update_loader = Loader::GetLoader(update_path); + + if (!update_loader) + return original_smdh; + + std::vector update_smdh; + update_loader->ReadIcon(update_smdh); + return update_smdh; + }(); + + if (!Loader::IsValidSMDH(smdh) && UISettings::values.game_list_hide_no_icon) { + // Skip this invalid entry + return true; + } + + 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(QString::fromStdString(physical_name), smdh, program_id, + extdata_id), + new GameListItemCompat(compatibility), + new GameListItemRegion(smdh), + new GameListItem( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), + new GameListItemSize(FileUtil::GetSize(physical_name)), + }, + parent_dir); + + } else if (is_dir && recursion > 0) { + watch_list.append(QString::fromStdString(physical_name)); + AddFstEntriesToGameList(physical_name, recursion - 1, parent_dir); + } + + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); +} + +void GameListWorker::run() { + stop_processing = false; + for (UISettings::GameDir& game_dir : game_dirs) { + if (game_dir.path == "INSTALLED") { + QString path = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)) + + "Nintendo " + "3DS/00000000000000000000000000000000/" + "00000000000000000000000000000000/title/00040000"; + watch_list.append(path); + GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::InstalledDir); + emit DirEntryReady({game_list_dir}); + AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); + } else if (game_dir.path == "SYSTEM") { + QString path = + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)) + + "00000000000000000000000000000000/title/00040010"; + watch_list.append(path); + GameListDir* game_list_dir = new GameListDir(game_dir, GameListItemType::SystemDir); + emit DirEntryReady({game_list_dir}); + AddFstEntriesToGameList(path.toStdString(), 2, game_list_dir); + } else { + watch_list.append(game_dir.path); + GameListDir* game_list_dir = new GameListDir(game_dir); + emit DirEntryReady({game_list_dir}); + AddFstEntriesToGameList(game_dir.path.toStdString(), game_dir.deep_scan ? 256 : 0, + game_list_dir); + } + }; + emit Finished(watch_list); +} + +void GameListWorker::Cancel() { + this->disconnect(); + stop_processing = true; +} diff --git a/src/citra_qt/game_list_worker.h b/src/citra_qt/game_list_worker.h new file mode 100644 index 000000000..b35e890ca --- /dev/null +++ b/src/citra_qt/game_list_worker.h @@ -0,0 +1,64 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/common_types.h" + +class QStandardItem; + +/** + * 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( + QList& game_dirs, + const std::unordered_map>& 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 DirEntryReady(GameListDir* entry_items); + void EntryReady(QList entry_items, GameListDir* parent_dir); + + /** + * 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 AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion, + GameListDir* parent_dir); + + QStringList watch_list; + const std::unordered_map>& compatibility_list; + QList& game_dirs; + std::atomic_bool stop_processing; +};