Merge pull request #3730 from FearlessTobi/game-compat-fixes
citra_qt: Refactor game list compatibility system
This commit is contained in:
commit
80bfd87270
|
@ -48,7 +48,7 @@ configure_file(${CMAKE_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qr
|
||||||
if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||||
message(STATUS "Downloading compatibility list for citra...")
|
message(STATUS "Downloading compatibility list for citra...")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD
|
||||||
https://api.citra-emu.org/gamedb/titleid/
|
https://api.citra-emu.org/gamedb/
|
||||||
"${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS)
|
"${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
@ -325,12 +326,20 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||||
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
|
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
|
||||||
QAction* open_application_location = context_menu.addAction(tr("Open Application Location"));
|
QAction* open_application_location = context_menu.addAction(tr("Open Application Location"));
|
||||||
QAction* open_update_location = context_menu.addAction(tr("Open Update Data Location"));
|
QAction* open_update_location = context_menu.addAction(tr("Open Update Data Location"));
|
||||||
|
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||||
|
|
||||||
open_save_location->setEnabled(program_id != 0);
|
open_save_location->setEnabled(program_id != 0);
|
||||||
open_application_location->setVisible(FileUtil::Exists(
|
open_application_location->setVisible(FileUtil::Exists(
|
||||||
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id)));
|
Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, program_id)));
|
||||||
open_update_location->setEnabled(0x00040000'00000000 <= program_id &&
|
open_update_location->setEnabled(0x00040000'00000000 <= program_id &&
|
||||||
program_id <= 0x00040000'FFFFFFFF);
|
program_id <= 0x00040000'FFFFFFFF);
|
||||||
|
auto it = std::find_if(
|
||||||
|
compatibility_list.begin(), compatibility_list.end(),
|
||||||
|
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||||
|
std::string pid = Common::StringFromFormat("%016" PRIX64, program_id);
|
||||||
|
return element.first == pid;
|
||||||
|
});
|
||||||
|
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
|
||||||
|
|
||||||
connect(open_save_location, &QAction::triggered,
|
connect(open_save_location, &QAction::triggered,
|
||||||
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA); });
|
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SAVE_DATA); });
|
||||||
|
@ -338,6 +347,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||||
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION); });
|
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION); });
|
||||||
connect(open_update_location, &QAction::triggered,
|
connect(open_update_location, &QAction::triggered,
|
||||||
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA); });
|
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::UPDATE_DATA); });
|
||||||
|
connect(navigate_to_gamedb_entry, &QAction::triggered,
|
||||||
|
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
|
||||||
|
|
||||||
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
||||||
}
|
}
|
||||||
|
@ -363,14 +374,23 @@ void GameList::LoadCompatibilityList() {
|
||||||
|
|
||||||
const QString string_content = content;
|
const QString string_content = content;
|
||||||
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
|
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
|
||||||
QJsonObject list = json.object();
|
QJsonArray arr = json.array();
|
||||||
QStringList game_ids = list.keys();
|
|
||||||
for (QString id : game_ids) {
|
|
||||||
QJsonObject game = list[id].toObject();
|
|
||||||
|
|
||||||
if (game.contains("compatibility") && game["compatibility"].isString()) {
|
for (const QJsonValue& value : arr) {
|
||||||
QString compatibility = game["compatibility"].toString();
|
QJsonObject game = value.toObject();
|
||||||
compatibility_list.insert(std::make_pair(id.toUpper().toStdString(), compatibility));
|
|
||||||
|
if (game.contains("compatibility") && game["compatibility"].isDouble()) {
|
||||||
|
int compatibility = game["compatibility"].toInt();
|
||||||
|
QString directory = game["directory"].toString();
|
||||||
|
QJsonArray ids = game["releases"].toArray();
|
||||||
|
|
||||||
|
for (const QJsonValue& value : ids) {
|
||||||
|
QJsonObject object = value.toObject();
|
||||||
|
QString id = object["id"].toString();
|
||||||
|
compatibility_list.insert(
|
||||||
|
std::make_pair(id.toUpper().toStdString(),
|
||||||
|
std::make_pair(QString::number(compatibility), directory)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -478,17 +498,17 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||||
return update_smdh;
|
return update_smdh;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
auto it = std::find_if(compatibility_list.begin(), compatibility_list.end(),
|
auto it = std::find_if(
|
||||||
[program_id](const std::pair<std::string, QString>& element) {
|
compatibility_list.begin(), compatibility_list.end(),
|
||||||
std::string pid =
|
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||||
Common::StringFromFormat("%016" PRIX64, program_id);
|
std::string pid = Common::StringFromFormat("%016" PRIX64, program_id);
|
||||||
return element.first == pid;
|
return element.first == pid;
|
||||||
});
|
});
|
||||||
|
|
||||||
// The game list uses this as compatibility number for untested games
|
// The game list uses this as compatibility number for untested games
|
||||||
QString compatibility("99");
|
QString compatibility("99");
|
||||||
if (it != compatibility_list.end())
|
if (it != compatibility_list.end())
|
||||||
compatibility = it->second;
|
compatibility = it->second.first;
|
||||||
|
|
||||||
emit EntryReady({
|
emit EntryReady({
|
||||||
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id),
|
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id),
|
||||||
|
|
|
@ -85,6 +85,9 @@ 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(
|
||||||
|
u64 program_id,
|
||||||
|
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onTextChanged(const QString& newText);
|
void onTextChanged(const QString& newText);
|
||||||
|
@ -106,7 +109,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, QString> compatibility_list;
|
std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(GameListOpenTarget);
|
Q_DECLARE_METATYPE(GameListOpenTarget);
|
||||||
|
|
|
@ -260,8 +260,9 @@ class GameListWorker : public QObject, public QRunnable {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameListWorker(QString dir_path, bool deep_scan,
|
GameListWorker(
|
||||||
const std::unordered_map<std::string, QString>& compatibility_list)
|
QString dir_path, bool deep_scan,
|
||||||
|
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
|
||||||
: QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan),
|
: QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan),
|
||||||
compatibility_list(compatibility_list) {}
|
compatibility_list(compatibility_list) {}
|
||||||
|
|
||||||
|
@ -289,7 +290,7 @@ private:
|
||||||
QStringList watch_list;
|
QStringList watch_list;
|
||||||
QString dir_path;
|
QString dir_path;
|
||||||
bool deep_scan;
|
bool deep_scan;
|
||||||
const std::unordered_map<std::string, QString>& compatibility_list;
|
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
|
||||||
std::atomic_bool stop_processing;
|
std::atomic_bool stop_processing;
|
||||||
|
|
||||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#define QT_NO_OPENGL
|
#define QT_NO_OPENGL
|
||||||
|
#include <cinttypes>
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
|
@ -399,6 +400,8 @@ void GMainWindow::RestoreUIState() {
|
||||||
void GMainWindow::ConnectWidgetEvents() {
|
void GMainWindow::ConnectWidgetEvents() {
|
||||||
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
|
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
|
||||||
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
|
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
|
||||||
|
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
|
||||||
|
&GMainWindow::OnGameListNavigateToGamedbEntry);
|
||||||
|
|
||||||
connect(this, &GMainWindow::EmulationStarting, render_window,
|
connect(this, &GMainWindow::EmulationStarting, render_window,
|
||||||
&GRenderWindow::OnEmulationStarting);
|
&GRenderWindow::OnEmulationStarting);
|
||||||
|
@ -806,6 +809,25 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnGameListNavigateToGamedbEntry(
|
||||||
|
u64 program_id,
|
||||||
|
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) {
|
||||||
|
|
||||||
|
auto it = std::find_if(
|
||||||
|
compatibility_list.begin(), compatibility_list.end(),
|
||||||
|
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||||
|
std::string pid = Common::StringFromFormat("%016" PRIX64, program_id);
|
||||||
|
return element.first == pid;
|
||||||
|
});
|
||||||
|
|
||||||
|
QString directory = "";
|
||||||
|
|
||||||
|
if (it != compatibility_list.end())
|
||||||
|
directory = it->second.second;
|
||||||
|
|
||||||
|
QDesktopServices::openUrl(QUrl("https://citra-emu.org/game/" + directory));
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuLoadFile() {
|
void GMainWindow::OnMenuLoadFile() {
|
||||||
QString extensions;
|
QString extensions;
|
||||||
for (const auto& piece : game_list->supported_file_extensions)
|
for (const auto& piece : game_list->supported_file_extensions)
|
||||||
|
|
|
@ -145,6 +145,9 @@ 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(
|
||||||
|
u64 program_id,
|
||||||
|
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||||
void OnMenuLoadFile();
|
void OnMenuLoadFile();
|
||||||
void OnMenuInstallCIA();
|
void OnMenuInstallCIA();
|
||||||
void OnUpdateProgress(size_t written, size_t total);
|
void OnUpdateProgress(size_t written, size_t total);
|
||||||
|
|
Loading…
Reference in New Issue