mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-27 01:02:48 +01:00
yuzu: Move GameListWorker to its own source files
This has gotten sufficiently large enough to warrant moving it to its own source files. Especially given it dumps the file_sys headers around code that doesn't use it for the most part. This'll also make it easier to introduce a type alias for the compatibility list, so a large unordered_map type declaration doesn't need to be specified all the time (we don't want to propagate the game_list_p.h include via the main game_list.h header).
This commit is contained in:
parent
c08c5d346a
commit
564b7fdc9c
5 changed files with 324 additions and 286 deletions
|
@ -43,6 +43,8 @@ add_executable(yuzu
|
||||||
game_list.cpp
|
game_list.cpp
|
||||||
game_list.h
|
game_list.h
|
||||||
game_list_p.h
|
game_list_p.h
|
||||||
|
game_list_worker.cpp
|
||||||
|
game_list_worker.h
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
hotkeys.h
|
hotkeys.h
|
||||||
main.cpp
|
main.cpp
|
||||||
|
|
|
@ -18,17 +18,10 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.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/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.h"
|
||||||
#include "yuzu/game_list_p.h"
|
#include "yuzu/game_list_p.h"
|
||||||
|
#include "yuzu/game_list_worker.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
#include "yuzu/ui_settings.h"
|
#include "yuzu/ui_settings.h"
|
||||||
|
|
||||||
|
@ -436,45 +429,6 @@ void GameList::LoadInterfaceLayout() {
|
||||||
|
|
||||||
const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
|
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() {
|
void GameList::RefreshGameDirectory() {
|
||||||
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
|
if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
|
||||||
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
|
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);
|
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 <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -16,7 +14,6 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QRunnable>
|
|
||||||
#include <QStandardItem>
|
#include <QStandardItem>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
@ -26,12 +23,6 @@
|
||||||
#include "yuzu/ui_settings.h"
|
#include "yuzu/ui_settings.h"
|
||||||
#include "yuzu/util/util.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)
|
* Gets the default icon (for games without valid SMDH)
|
||||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||||
|
@ -43,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) {
|
||||||
return icon;
|
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 {
|
class GameListItem : public QStandardItem {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -197,49 +177,13 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
inline auto FindMatchingCompatibilityEntry(
|
||||||
* Asynchronous worker object for populating the game list.
|
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
|
||||||
* Communicates with other threads through Qt's signal/slot system.
|
u64 program_id) {
|
||||||
*/
|
return std::find_if(
|
||||||
class GameListWorker : public QObject, public QRunnable {
|
compatibility_list.begin(), compatibility_list.end(),
|
||||||
Q_OBJECT
|
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||||
|
std::string pid = fmt::format("{:016X}", program_id);
|
||||||
public:
|
return element.first == pid;
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
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 a new issue