mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #3463 from FearlessTobi/game-list-compat
citra-qt: Show Game Compatibility within Citra
This commit is contained in:
		
						commit
						a2ab91fa31
					
				
					 11 changed files with 153 additions and 11 deletions
				
			
		|  | @ -20,7 +20,7 @@ echo y | sh cmake-3.10.1-Linux-x86_64.sh --prefix=cmake | |||
| export PATH=/citra/cmake/cmake-3.10.1-Linux-x86_64/bin:$PATH | ||||
| 
 | ||||
| mkdir build && cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON | ||||
| make -j4 | ||||
| 
 | ||||
| ctest -VV -C Release | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ echo y | sh cmake-3.10.1-Linux-x86_64.sh --prefix=cmake | |||
| export PATH=/citra/cmake/cmake-3.10.1-Linux-x86_64/bin:$PATH | ||||
| 
 | ||||
| mkdir build && cd build | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} | ||||
| cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON | ||||
| make -j4 | ||||
| 
 | ||||
| ctest -VV -C Release | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5 | |||
| export PATH="/usr/local/opt/ccache/libexec:$PATH" | ||||
| 
 | ||||
| mkdir build && cd build | ||||
| cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} | ||||
| cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON | ||||
| make -j4 | ||||
| 
 | ||||
| ctest -VV -C Release | ||||
|  |  | |||
|  | @ -40,6 +40,22 @@ function(check_submodules_present) | |||
| endfunction() | ||||
| check_submodules_present() | ||||
| 
 | ||||
| 
 | ||||
| configure_file(${CMAKE_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|                ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|                COPYONLY) | ||||
| 
 | ||||
| if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
|     message(STATUS "Downloading compatibility list for citra...") | ||||
|     file(DOWNLOAD | ||||
|         https://api.citra-emu.org/gamedb/titleid/ | ||||
|         "${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS) | ||||
| endif() | ||||
| 
 | ||||
| if (NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
|     file(WRITE ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json "") | ||||
| endif() | ||||
| 
 | ||||
| # Detect current compilation architecture and create standard definitions | ||||
| # ======================================================================= | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,9 +43,9 @@ before_build: | |||
|         $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING} | ||||
|         if ($env:BUILD_TYPE -eq 'msvc') { | ||||
|           # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning | ||||
|           cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1 && exit 0' | ||||
|           cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0' | ||||
|         } else { | ||||
|           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1" | ||||
|           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1" | ||||
|         } | ||||
|   - cd .. | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										5
									
								
								dist/compatibility_list/compatibility_list.qrc
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								dist/compatibility_list/compatibility_list.qrc
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| <RCC> | ||||
|   <qresource prefix="compatibility_list"> | ||||
|       <file>compatibility_list.json</file> | ||||
|   </qresource> | ||||
| </RCC> | ||||
|  | @ -85,6 +85,9 @@ set(UIS | |||
|     compatdb.ui | ||||
| ) | ||||
| 
 | ||||
| file(GLOB COMPAT_LIST | ||||
|             ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|             ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
| file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*) | ||||
| file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) | ||||
| 
 | ||||
|  | @ -125,6 +128,7 @@ endif() | |||
| 
 | ||||
| target_sources(citra-qt | ||||
|     PRIVATE | ||||
|         ${COMPAT_LIST} | ||||
|         ${ICONS} | ||||
|         ${THEMES} | ||||
|         ${UI_HDRS} | ||||
|  |  | |||
|  | @ -2,11 +2,14 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cinttypes> | ||||
| #include <QApplication> | ||||
| #include <QFileInfo> | ||||
| #include <QFileSystemWatcher> | ||||
| #include <QHBoxLayout> | ||||
| #include <QHeaderView> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonObject> | ||||
| #include <QKeyEvent> | ||||
| #include <QLabel> | ||||
| #include <QLineEdit> | ||||
|  | @ -227,6 +230,7 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} { | |||
| 
 | ||||
|     item_model->insertColumns(0, COLUMN_COUNT); | ||||
|     item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); | ||||
|     item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility"); | ||||
|     item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); | ||||
|     item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); | ||||
| 
 | ||||
|  | @ -337,6 +341,39 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
|     context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); | ||||
| } | ||||
| 
 | ||||
| void GameList::LoadCompatibilityList() { | ||||
|     QFile compat_list{":compatibility_list/compatibility_list.json"}; | ||||
| 
 | ||||
|     if (!compat_list.open(QFile::ReadOnly | QFile::Text)) { | ||||
|         NGLOG_ERROR(Frontend, "Unable to open game compatibility list"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (compat_list.size() == 0) { | ||||
|         NGLOG_ERROR(Frontend, "Game compatibility list is empty"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const QByteArray content = compat_list.readAll(); | ||||
|     if (content.isEmpty()) { | ||||
|         NGLOG_ERROR(Frontend, "Unable to completely read game compatibility list"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const QString string_content = content; | ||||
|     QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); | ||||
|     QJsonObject list = json.object(); | ||||
|     QStringList game_ids = list.keys(); | ||||
|     for (QString id : game_ids) { | ||||
|         QJsonObject game = list[id].toObject(); | ||||
| 
 | ||||
|         if (game.contains("compatibility") && game["compatibility"].isString()) { | ||||
|             QString compatibility = game["compatibility"].toString(); | ||||
|             compatibility_list.insert(std::make_pair(id.toUpper().toStdString(), compatibility)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { | ||||
|     if (!FileUtil::Exists(dir_path.toStdString()) || | ||||
|         !FileUtil::IsDirectory(dir_path.toStdString())) { | ||||
|  | @ -351,7 +388,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { | |||
| 
 | ||||
|     emit ShouldCancelWorker(); | ||||
| 
 | ||||
|     GameListWorker* worker = new GameListWorker(dir_path, deep_scan); | ||||
|     GameListWorker* worker = new GameListWorker(dir_path, deep_scan, compatibility_list); | ||||
| 
 | ||||
|     connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | ||||
|     connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, | ||||
|  | @ -436,8 +473,21 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign | |||
|                 return update_smdh; | ||||
|             }(); | ||||
| 
 | ||||
|             auto it = std::find_if(compatibility_list.begin(), compatibility_list.end(), | ||||
|                                    [program_id](const std::pair<std::string, QString>& element) { | ||||
|                                        std::string pid = | ||||
|                                            Common::StringFromFormat("%016" PRIX64, program_id); | ||||
|                                        return element.first == pid; | ||||
|                                    }); | ||||
| 
 | ||||
|             // The game list uses this as compatibility number for untested games
 | ||||
|             QString compatibility("99"); | ||||
|             if (it != compatibility_list.end()) | ||||
|                 compatibility = it->second; | ||||
| 
 | ||||
|             emit EntryReady({ | ||||
|                 new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id), | ||||
|                 new GameListItemCompat(compatibility), | ||||
|                 new GameListItem( | ||||
|                     QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), | ||||
|                 new GameListItemSize(FileUtil::GetSize(physical_name)), | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <QString> | ||||
| #include <QWidget> | ||||
| #include "common/common_types.h" | ||||
|  | @ -29,6 +30,7 @@ class GameList : public QWidget { | |||
| public: | ||||
|     enum { | ||||
|         COLUMN_NAME, | ||||
|         COLUMN_COMPATIBILITY, | ||||
|         COLUMN_FILE_TYPE, | ||||
|         COLUMN_SIZE, | ||||
|         COLUMN_COUNT, // Number of columns
 | ||||
|  | @ -68,6 +70,7 @@ public: | |||
|     void setFilterFocus(); | ||||
|     void setFilterVisible(bool visibility); | ||||
| 
 | ||||
|     void LoadCompatibilityList(); | ||||
|     void PopulateAsync(const QString& dir_path, bool deep_scan); | ||||
| 
 | ||||
|     void SaveInterfaceLayout(); | ||||
|  | @ -100,6 +103,7 @@ private: | |||
|     QStandardItemModel* item_model = nullptr; | ||||
|     GameListWorker* current_worker = nullptr; | ||||
|     QFileSystemWatcher* watcher = nullptr; | ||||
|     std::unordered_map<std::string, QString> compatibility_list; | ||||
| }; | ||||
| 
 | ||||
| Q_DECLARE_METATYPE(GameListOpenTarget); | ||||
|  |  | |||
|  | @ -5,11 +5,16 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <atomic> | ||||
| #include <map> | ||||
| #include <unordered_map> | ||||
| #include <QImage> | ||||
| #include <QObject> | ||||
| #include <QPainter> | ||||
| #include <QRunnable> | ||||
| #include <QStandardItem> | ||||
| #include <QString> | ||||
| #include "citra_qt/util/util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/loader/smdh.h" | ||||
| 
 | ||||
|  | @ -39,6 +44,23 @@ static QPixmap GetDefaultIcon(bool large) { | |||
|     return icon; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates a circle pixmap from a specified color | ||||
|  * @param color The color the pixmap shall have | ||||
|  * @return QPixmap circle pixmap | ||||
|  */ | ||||
| static QPixmap CreateCirclePixmapFromColor(const QColor& color) { | ||||
|     QPixmap circle_pixmap(16, 16); | ||||
|     circle_pixmap.fill(Qt::transparent); | ||||
| 
 | ||||
|     QPainter painter(&circle_pixmap); | ||||
|     painter.setPen(color); | ||||
|     painter.setBrush(color); | ||||
|     painter.drawEllipse(0, 0, 15, 15); | ||||
| 
 | ||||
|     return circle_pixmap; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the short game title from SMDH data. | ||||
|  * @param smdh SMDH data | ||||
|  | @ -50,8 +72,25 @@ static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, | |||
|     return QString::fromUtf16(smdh.GetShortTitle(language).data()); | ||||
| } | ||||
| 
 | ||||
| class GameListItem : public QStandardItem { | ||||
| struct CompatStatus { | ||||
|     QString color; | ||||
|     QString text; | ||||
|     QString tooltip; | ||||
| }; | ||||
| 
 | ||||
| // When this is put in a class, MSVS builds crash when closing Citra
 | ||||
| // clang-format off
 | ||||
| const static inline std::map<QString, CompatStatus> status_data = { | ||||
| { "0", { "#5c93ed", GameList::tr("Perfect"),    GameList::tr("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.") } }, | ||||
| { "1", { "#47d35c", GameList::tr("Great"),      GameList::tr("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.") } }, | ||||
| { "2", { "#94b242", GameList::tr("Okay"),       GameList::tr("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.") } }, | ||||
| { "3", { "#f2d624", GameList::tr("Bad"),        GameList::tr("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.") } }, | ||||
| { "4", { "#FF0000", GameList::tr("Intro/Menu"), GameList::tr("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.") } }, | ||||
| { "5", { "#828282", GameList::tr("Won't Boot"), GameList::tr("The game crashes when attempting to startup.") } }, | ||||
| { "99",{ "#000000", GameList::tr("Not Tested"), GameList::tr("The game has not yet been tested.") } }, }; | ||||
| // clang-format on
 | ||||
| 
 | ||||
| class GameListItem : public QStandardItem { | ||||
| public: | ||||
|     GameListItem() : QStandardItem() {} | ||||
|     GameListItem(const QString& string) : QStandardItem(string) {} | ||||
|  | @ -65,7 +104,6 @@ public: | |||
|  * If this class receives valid SMDH data, it will also display game icons and titles. | ||||
|  */ | ||||
| class GameListItemPath : public GameListItem { | ||||
| 
 | ||||
| public: | ||||
|     static const int FullPathRole = Qt::UserRole + 1; | ||||
|     static const int TitleRole = Qt::UserRole + 2; | ||||
|  | @ -107,13 +145,34 @@ public: | |||
|     } | ||||
| }; | ||||
| 
 | ||||
| class GameListItemCompat : public GameListItem { | ||||
| public: | ||||
|     static const int CompatNumberRole = Qt::UserRole + 1; | ||||
|     GameListItemCompat() = default; | ||||
|     explicit GameListItemCompat(const QString compatiblity) { | ||||
|         auto iterator = status_data.find(compatiblity); | ||||
|         if (iterator == status_data.end()) { | ||||
|             NGLOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString()); | ||||
|             return; | ||||
|         } | ||||
|         CompatStatus status = iterator->second; | ||||
|         setData(compatiblity, CompatNumberRole); | ||||
|         setText(status.text); | ||||
|         setToolTip(status.tooltip); | ||||
|         setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); | ||||
|     } | ||||
| 
 | ||||
|     bool operator<(const QStandardItem& other) const override { | ||||
|         return data(CompatNumberRole) < other.data(CompatNumberRole); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * A specialization of GameListItem for size values. | ||||
|  * This class ensures that for every numerical size value it holds (in bytes), a correct | ||||
|  * human-readable string representation will be displayed to the user. | ||||
|  */ | ||||
| class GameListItemSize : public GameListItem { | ||||
| 
 | ||||
| public: | ||||
|     static const int SizeRole = Qt::UserRole + 1; | ||||
| 
 | ||||
|  | @ -152,8 +211,10 @@ class GameListWorker : public QObject, public QRunnable { | |||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GameListWorker(QString dir_path, bool deep_scan) | ||||
|         : QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {} | ||||
|     GameListWorker(QString dir_path, bool deep_scan, | ||||
|                    const std::unordered_map<std::string, QString>& compatibility_list) | ||||
|         : QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan), | ||||
|           compatibility_list(compatibility_list) {} | ||||
| 
 | ||||
| public slots: | ||||
|     /// Starts the processing of directory tree information.
 | ||||
|  | @ -179,6 +240,7 @@ private: | |||
|     QStringList watch_list; | ||||
|     QString dir_path; | ||||
|     bool deep_scan; | ||||
|     const std::unordered_map<std::string, QString>& compatibility_list; | ||||
|     std::atomic_bool stop_processing; | ||||
| 
 | ||||
|     void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); | ||||
|  |  | |||
|  | @ -131,6 +131,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { | |||
| 
 | ||||
|     show(); | ||||
| 
 | ||||
|     game_list->LoadCompatibilityList(); | ||||
|     game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||||
| 
 | ||||
|     // Show one-time "callout" messages to the user
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue