Merge pull request #4235 from FearlessTobi/port-1259

Port various game_list changes from yuzu
This commit is contained in:
Weiyi Wang 2018-10-24 20:57:58 -04:00 committed by GitHub
commit 6742472133
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 265 additions and 198 deletions

View file

@ -14,6 +14,8 @@ add_executable(citra-qt
applets/swkbd.h applets/swkbd.h
bootmanager.cpp bootmanager.cpp
bootmanager.h bootmanager.h
compatibility_list.cpp
compatibility_list.h
camera/camera_util.cpp camera/camera_util.cpp
camera/camera_util.h camera/camera_util.h
camera/still_image_camera.cpp camera/still_image_camera.cpp
@ -76,6 +78,8 @@ add_executable(citra-qt
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

View file

@ -0,0 +1,16 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <fmt/format.h>
#include "citra_qt/compatibility_list.h"
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
const CompatibilityList& compatibility_list, u64 program_id) {
return std::find_if(compatibility_list.begin(), compatibility_list.end(),
[program_id](const auto& element) {
std::string pid = fmt::format("{:016X}", program_id);
return element.first == pid;
});
}

View file

@ -0,0 +1,15 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <unordered_map>
#include <QString>
#include "common/common_types.h"
using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>;
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
const CompatibilityList& compatibility_list, u64 program_id);

View file

@ -21,8 +21,10 @@
#include <QToolButton> #include <QToolButton>
#include <QTreeView> #include <QTreeView>
#include <fmt/format.h> #include <fmt/format.h>
#include "citra_qt/compatibility_list.h"
#include "citra_qt/game_list.h" #include "citra_qt/game_list.h"
#include "citra_qt/game_list_p.h" #include "citra_qt/game_list_p.h"
#include "citra_qt/game_list_worker.h"
#include "citra_qt/main.h" #include "citra_qt/main.h"
#include "citra_qt/ui_settings.h" #include "citra_qt/ui_settings.h"
#include "common/common_paths.h" #include "common/common_paths.h"
@ -30,7 +32,6 @@
#include "core/file_sys/archive_extsavedata.h" #include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h" #include "core/file_sys/archive_source_sd_savedata.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/loader/loader.h"
GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
@ -648,11 +649,6 @@ void GameList::LoadInterfaceLayout() {
const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf", const QStringList GameList::supported_file_extensions = {"3ds", "3dsx", "elf", "axf",
"cci", "cxi", "app"}; "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() { void GameList::RefreshGameDirectory() {
if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { if (!UISettings::values.game_dirs.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.");
@ -678,123 +674,6 @@ QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_i
return ""; 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::AppLoader> 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<u8> smdh = [program_id, &loader]() -> std::vector<u8> {
std::vector<u8> 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<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);
if (!update_loader)
return original_smdh;
std::vector<u8> 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} { GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
this->main_window = parent; this->main_window = parent;

View file

@ -4,9 +4,10 @@
#pragma once #pragma once
#include <unordered_map> #include <QMenu>
#include <QString> #include <QString>
#include <QWidget> #include <QWidget>
#include "citra_qt/compatibility_list.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "ui_settings.h" #include "ui_settings.h"
@ -70,9 +71,8 @@ signals:
void GameChosen(QString game_path); void GameChosen(QString game_path);
void ShouldCancelWorker(); void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target); void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
void NavigateToGamedbEntryRequested( void NavigateToGamedbEntryRequested(u64 program_id,
u64 program_id, const CompatibilityList& compatibility_list);
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
void OpenDirectory(QString directory); void OpenDirectory(QString directory);
void AddDirectory(); void AddDirectory();
void ShowList(bool show); void ShowList(bool show);
@ -103,7 +103,7 @@ private:
QStandardItemModel* item_model = nullptr; QStandardItemModel* item_model = nullptr;
GameListWorker* current_worker = nullptr; GameListWorker* current_worker = nullptr;
QFileSystemWatcher* watcher = nullptr; QFileSystemWatcher* watcher = nullptr;
std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list; CompatibilityList compatibility_list;
friend class GameListSearchField; friend class GameListSearchField;
}; };

View file

@ -4,7 +4,6 @@
#pragma once #pragma once
#include <atomic>
#include <map> #include <map>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@ -59,17 +58,6 @@ static QPixmap GetDefaultIcon(bool large) {
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;
});
}
/** /**
* Gets the short game title from SMDH data. * Gets the short game title from SMDH data.
* @param smdh SMDH data * @param smdh SMDH data
@ -216,7 +204,7 @@ class GameListItemCompat : public GameListItem {
public: public:
static const int CompatNumberRole = SortRole; static const int CompatNumberRole = SortRole;
GameListItemCompat() = default; GameListItemCompat() = default;
explicit GameListItemCompat(const QString& compatiblity) { explicit GameListItemCompat(const QString& compatibility) {
setData(type(), TypeRole); setData(type(), TypeRole);
struct CompatStatus { struct CompatStatus {
@ -235,13 +223,13 @@ public:
{"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}};
// clang-format on // clang-format on
auto iterator = status_data.find(compatiblity); auto iterator = status_data.find(compatibility);
if (iterator == status_data.end()) { if (iterator == status_data.end()) {
LOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString()); LOG_WARNING(Frontend, "Invalid compatibility number {}", compatibility.toStdString());
return; return;
} }
CompatStatus status = iterator->second; const CompatStatus& status = iterator->second;
setData(compatiblity, CompatNumberRole); setData(compatibility, CompatNumberRole);
setText(QObject::tr(status.text)); setText(QObject::tr(status.text));
setToolTip(QObject::tr(status.tooltip)); setToolTip(QObject::tr(status.tooltip));
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
@ -373,51 +361,6 @@ 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<UISettings::GameDir>& game_dirs,
const std::unordered_map<std::string, std::pair<QString, QString>>& 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<QStandardItem*> 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<std::string, std::pair<QString, QString>>& compatibility_list;
QList<UISettings::GameDir>& game_dirs;
std::atomic_bool stop_processing;
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion,
GameListDir* parent_dir);
};
class GameList; class GameList;
class QHBoxLayout; class QHBoxLayout;
class QTreeView; class QTreeView;

View file

@ -0,0 +1,150 @@
// 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 "citra_qt/compatibility_list.h"
#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<UISettings::GameDir>& game_dirs,
const CompatibilityList& 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::AppLoader> 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<u8> smdh = [program_id, &loader]() -> std::vector<u8> {
std::vector<u8> 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<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);
if (!update_loader)
return original_smdh;
std::vector<u8> 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;
}

View file

@ -0,0 +1,62 @@
// 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 "citra_qt/compatibility_list.h"
#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<UISettings::GameDir>& game_dirs,
const CompatibilityList& 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<QStandardItem*> 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 CompatibilityList& compatibility_list;
QList<UISettings::GameDir>& game_dirs;
std::atomic_bool stop_processing;
};

View file

@ -21,6 +21,7 @@
#include "citra_qt/camera/qt_multimedia_camera.h" #include "citra_qt/camera/qt_multimedia_camera.h"
#include "citra_qt/camera/still_image_camera.h" #include "citra_qt/camera/still_image_camera.h"
#include "citra_qt/compatdb.h" #include "citra_qt/compatdb.h"
#include "citra_qt/compatibility_list.h"
#include "citra_qt/configuration/config.h" #include "citra_qt/configuration/config.h"
#include "citra_qt/configuration/configure_dialog.h" #include "citra_qt/configuration/configure_dialog.h"
#include "citra_qt/debugger/console.h" #include "citra_qt/debugger/console.h"
@ -960,14 +961,11 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
} }
void GMainWindow::OnGameListNavigateToGamedbEntry( void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
u64 program_id, const CompatibilityList& compatibility_list) {
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) {
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
QString directory; QString directory;
if (it != compatibility_list.end()) if (it != compatibility_list.end())
directory = it->second.second; directory = it->second.second;

View file

@ -9,6 +9,7 @@
#include <QMainWindow> #include <QMainWindow>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include "citra_qt/compatibility_list.h"
#include "citra_qt/hotkeys.h" #include "citra_qt/hotkeys.h"
#include "common/announce_multiplayer_room.h" #include "common/announce_multiplayer_room.h"
#include "core/core.h" #include "core/core.h"
@ -153,9 +154,8 @@ private slots:
/// Called whenever a user selects a game in the game list widget. /// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path); void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
void OnGameListNavigateToGamedbEntry( void OnGameListNavigateToGamedbEntry(u64 program_id,
u64 program_id, const CompatibilityList& compatibility_list);
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
void OnGameListOpenDirectory(QString path); void OnGameListOpenDirectory(QString path);
void OnGameListAddDirectory(); void OnGameListAddDirectory();
void OnGameListShowList(bool show); void OnGameListShowList(bool show);