mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #3730 from FearlessTobi/game-compat-fixes
citra_qt: Refactor game list compatibility system
This commit is contained in:
		
						commit
						80bfd87270
					
				
					 6 changed files with 68 additions and 19 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue