mirror of
https://github.com/PabloMK7/citra.git
synced 2024-11-19 22:16:36 +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…
Reference in a new issue