diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index c149850ecd..fb522da699 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -6,17 +6,17 @@ set(CMAKE_CXX_STANDARD 14) include(CheckCXXCompilerFlag) # Qt section -find_package(Qt5 5.7 COMPONENTS Widgets) +find_package(Qt5 5.7 COMPONENTS Widgets Network) if(WIN32) find_package(Qt5 5.7 COMPONENTS WinExtras REQUIRED) - set(RPCS3_QT_LIBS Qt5::Widgets Qt5::WinExtras) + set(RPCS3_QT_LIBS Qt5::Widgets Qt5::WinExtras Qt5::Network) else() find_package(Qt5 5.7 COMPONENTS DBus) if(Qt5DBus_FOUND) - set(RPCS3_QT_LIBS Qt5::Widgets Qt5::DBus) + set(RPCS3_QT_LIBS Qt5::Widgets Qt5::DBus Qt5::Network) add_definitions(-DHAVE_QTDBUS) else() - set(RPCS3_QT_LIBS Qt5::Widgets) + set(RPCS3_QT_LIBS Qt5::Widgets Qt5::Network) endif() endif() diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 60c435f72c..d6e572dec9 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -376,6 +376,11 @@ true true + + true + true + true + true true @@ -511,6 +516,11 @@ true true + + true + true + true + true true @@ -656,6 +666,11 @@ true true + + true + true + true + true true @@ -791,6 +806,11 @@ true true + + true + true + true + true true @@ -893,6 +913,7 @@ + @@ -1266,6 +1287,24 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing game_compatibility.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing game_compatibility.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing game_compatibility.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing game_compatibility.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index f630997167..ab4e7c5f49 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -569,6 +569,21 @@ Gui\misc dialogs + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + + + Gui\game list + @@ -762,6 +777,9 @@ Gui\misc dialogs + + Gui\game list + diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp new file mode 100644 index 0000000000..30725d9f71 --- /dev/null +++ b/rpcs3/rpcs3qt/game_compatibility.cpp @@ -0,0 +1,240 @@ +#include "game_compatibility.h" + +#include + +constexpr auto qstr = QString::fromStdString; +inline std::string sstr(const QString& _in) { return _in.toStdString(); } + +game_compatibility::game_compatibility(std::shared_ptr settings) : m_xgui_settings(settings) +{ + m_filepath = m_xgui_settings->GetSettingsDir() + "/compat_database.dat"; + m_url = "https://rpcs3.net/compatibility?api=v1&export"; + m_network_request = QNetworkRequest(QUrl(m_url)); + + RequestCompatibility(); +} + +void game_compatibility::RequestCompatibility(bool online) +{ + // Creates new map from database + auto ReadJSON = [=](const QJsonObject& json_data, bool after_download) + { + int return_code = json_data["return_code"].toInt(); + + if (return_code < 0) + { + if (after_download) + { + std::string error_message; + switch (return_code) + { + case -1: + error_message = "Server Error - Internal Error"; + break; + case -2: + error_message = "Server Error - Maintenance Mode"; + break; + default: + error_message = "Server Error - Unknown Error"; + break; + } + LOG_ERROR(GENERAL, "Compatibility error: { %s: return code %d }", error_message, return_code); + Q_EMIT DownloadError(qstr(error_message) + " " + QString::number(return_code)); + } + else + { + LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Invalid: return code %d }", return_code); + } + return false; + } + + if (!json_data["results"].isObject()) + { + LOG_ERROR(GENERAL, "Compatibility error: { Database Error - No Results found }"); + return false; + } + + m_compat_database.clear(); + + QJsonObject json_results = json_data["results"].toObject(); + + // Retrieve status data for every valid entry + for (const auto& key : json_results.keys()) + { + if (!json_results[key].isObject()) + { + LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Unusable object %s }", sstr(key)); + continue; + } + + QJsonObject json_result = json_results[key].toObject(); + + // Retrieve compatibility information from json + Compat_Status compat_status = Status_Data.at(json_result.value("status").toString("NoResult")); + + // Add date if possible + compat_status.date = json_result.value("date").toString(); + + // Add status to map + m_compat_database.emplace(std::pair(sstr(key), compat_status)); + } + + return true; + }; + + if (!online) + { + // Retrieve database from file + QFile file(m_filepath); + + if (!file.exists()) + { + LOG_NOTICE(GENERAL, "Compatibility notice: { Database file not found: %s }", sstr(m_filepath)); + return; + } + + if (!file.open(QIODevice::ReadOnly)) + { + LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Could not read database from file: %s }", sstr(m_filepath)); + return; + } + + QByteArray data = file.readAll(); + file.close(); + + LOG_NOTICE(GENERAL, "Compatibility notice: { Finished reading database from file: %s }", sstr(m_filepath)); + + // Create new map from database + ReadJSON(QJsonDocument::fromJson(data).object(), online); + + return; + } + + if (QSslSocket::supportsSsl() == false) + { + LOG_ERROR(GENERAL, "Can not retrieve the online database! Please make sure your system supports SSL."); + QMessageBox::warning(nullptr, tr("Warning!"), tr("Can not retrieve the online database! Please make sure your system supports SSL.")); + return; + } + + LOG_NOTICE(GENERAL, "SSL supported! Beginning compatibility database download from: %s", sstr(m_url)); + + // Send request and wait for response + m_network_access_manager.reset(new QNetworkAccessManager()); + QNetworkReply* network_reply = m_network_access_manager->get(m_network_request); + + // Show Progress + m_progress_dialog.reset(new QProgressDialog(tr(".Please wait."), tr("Abort"), 0, 100)); + m_progress_dialog->setWindowTitle(tr("Downloading Database")); + m_progress_dialog->setFixedWidth(QLabel("This is the very length of the progressbar due to hidpi reasons.").sizeHint().width()); + m_progress_dialog->setValue(0); + m_progress_dialog->show(); + + // Animate progress dialog a bit more + m_progress_timer.reset(new QTimer(this)); + connect(m_progress_timer.get(), &QTimer::timeout, [&]() + { + switch (++m_timer_count % 3) + { + case 0: + m_timer_count = 0; + m_progress_dialog->setLabelText(tr(".Please wait.")); + break; + case 1: + m_progress_dialog->setLabelText(tr("..Please wait..")); + break; + default: + m_progress_dialog->setLabelText(tr("...Please wait...")); + break; + } + }); + m_progress_timer->start(500); + + // Handle abort + connect(m_progress_dialog.get(), &QProgressDialog::rejected, network_reply, &QNetworkReply::abort); + + // Handle progress + connect(network_reply, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal) + { + m_progress_dialog->setMaximum(bytesTotal); + m_progress_dialog->setValue(bytesReceived); + }); + + // Handle response according to its contents + connect(network_reply, &QNetworkReply::finished, [=]() + { + // Clean up Progress Dialog + if (m_progress_dialog) + { + m_progress_dialog->close(); + } + if (m_progress_timer) + { + m_progress_timer->stop(); + } + + // Handle Errors + if (network_reply->error() != QNetworkReply::NoError) + { + // We failed to retrieve a new database, therefore refresh gamelist to old state + QString error = network_reply->errorString(); + Q_EMIT DownloadError(error); + LOG_ERROR(GENERAL, "Compatibility error: { Network Error - %s }", sstr(error)); + return; + } + + LOG_NOTICE(GENERAL, "Compatibility notice: { Database download finished }"); + + // Read data from network reply + QByteArray data = network_reply->readAll(); + network_reply->deleteLater(); + + // Create new map from database and write database to file if database was valid + if (ReadJSON(QJsonDocument::fromJson(data).object(), online)) + { + // We have a new database in map, therefore refresh gamelist to new state + Q_EMIT DownloadFinished(); + + // Write database to file + QFile file(m_filepath); + + if (file.exists()) + { + LOG_NOTICE(GENERAL, "Compatibility notice: { Database file found: %s }", sstr(m_filepath)); + } + + if (!file.open(QIODevice::WriteOnly)) + { + LOG_ERROR(GENERAL, "Compatibility error: { Database Error - Could not write database to file: %s }", sstr(m_filepath)); + return; + } + + file.write(data); + file.close(); + + LOG_SUCCESS(GENERAL, "Compatibility success: { Write database to file: %s }", sstr(m_filepath)); + } + }); + + // We want to retrieve a new database, therefore refresh gamelist and indicate that + Q_EMIT DownloadStarted(); +} + +Compat_Status game_compatibility::GetCompatibility(const std::string& title_id) +{ + if (m_compat_database.empty()) + { + return Status_Data.at("NoData"); + } + else if (m_compat_database.count(title_id) > 0) + { + return m_compat_database[title_id]; + } + + return Status_Data.at("NoResult"); +} + +Compat_Status game_compatibility::GetStatusData(const QString& status) +{ + return Status_Data.at(status); +} diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h new file mode 100644 index 0000000000..12f9dea357 --- /dev/null +++ b/rpcs3/rpcs3qt/game_compatibility.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui_settings.h" + +class game_compatibility : public QObject +{ + Q_OBJECT + + const std::map Status_Data = + { + { "Playable", { "", "#2ecc71", QObject::tr("Playable"), QObject::tr("Games that can be properly played from start to finish") } }, + { "Ingame", { "", "#f1c40f", QObject::tr("Ingame"), QObject::tr("Games that go somewhere but not far enough to be considered playable") } }, + { "Intro", { "", "#f39c12", QObject::tr("Intro"), QObject::tr("Games that only display some screens") } }, + { "Loadable", { "", "#e74c3c", QObject::tr("Loadable"), QObject::tr("Games that display a black screen with an active framerate") } }, + { "Nothing", { "", "#2c3e50", QObject::tr("Nothing"), QObject::tr("Games that show nothing") } }, + { "NoResult", { "", "", QObject::tr("No results found"), QObject::tr("There is no entry for this game or application in the compatibility database yet.") } }, + { "NoData", { "", "", QObject::tr("Database missing"), QObject::tr("Right click here and download the current database.\nMake sure you are connected to the internet.") } }, + { "Download", { "", "", QObject::tr("Retrieving..."), QObject::tr("Downloading the compatibility database. Please wait...") } } + }; + int m_timer_count = 0; + QString m_filepath; + QString m_url; + QNetworkRequest m_network_request; + std::shared_ptr m_xgui_settings; + std::unique_ptr m_progress_timer; + std::unique_ptr m_progress_dialog; + std::unique_ptr m_network_access_manager; + std::map m_compat_database; + +public: + /** Handles reads, writes and downloads for the compatibility database */ + game_compatibility(std::shared_ptr settings); + + /** Reads database. If online set to true: Downloads and writes the database to file */ + void RequestCompatibility(bool online = false); + + /** Returns the compatibility status for the requested title */ + Compat_Status GetCompatibility(const std::string& title_id); + + /** Returns the data for the requested status */ + Compat_Status GetStatusData(const QString& status); + +Q_SIGNALS: + void DownloadStarted(); + void DownloadFinished(); + void DownloadError(const QString& error); +}; + +class compat_pixmap : public QPixmap +{ +public: + compat_pixmap(const QColor& color) : QPixmap(16, 16) + { + fill(Qt::transparent); + + QPainter painter(this); + painter.setPen(color); + painter.setBrush(color); + painter.drawEllipse(0, 0, 15, 15); + } +}; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index f2f80ad43a..a8b440ecd5 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -172,6 +172,7 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: m_gameList->setHorizontalHeaderItem(gui::column_resolution, new QTableWidgetItem(tr("Supported Resolutions"))); m_gameList->setHorizontalHeaderItem(gui::column_sound, new QTableWidgetItem(tr("Sound Formats"))); m_gameList->setHorizontalHeaderItem(gui::column_parental, new QTableWidgetItem(tr("Parental Level"))); + m_gameList->setHorizontalHeaderItem(gui::column_compat, new QTableWidgetItem(tr("Compatibility"))); // since this won't work somehow: gameList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); for (int i = 0; i < m_gameList->horizontalHeader()->count(); i++) @@ -179,6 +180,8 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: m_gameList->horizontalHeaderItem(i)->setTextAlignment(Qt::AlignLeft); } + m_game_compat = std::make_unique(xgui_settings); + m_Central_Widget = new QStackedWidget(this); m_Central_Widget->addWidget(m_gameList); m_Central_Widget->addWidget(m_xgrid); @@ -197,9 +200,10 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: QAction* showResolutionColAct = new QAction(tr("Show Supported Resolutions"), this); QAction* showSoundFormatColAct = new QAction(tr("Show Sound Formats"), this); QAction* showParentalLevelColAct = new QAction(tr("Show Parental Levels"), this); + QAction* showCompatibilityAct = new QAction(tr("Show Compatibilities"), this); m_columnActs = { showIconColAct, showNameColAct, showSerialColAct, showFWColAct, showAppVersionColAct, showCategoryColAct, showPathColAct, - showResolutionColAct, showSoundFormatColAct, showParentalLevelColAct }; + showResolutionColAct, showSoundFormatColAct, showParentalLevelColAct, showCompatibilityAct }; // Events connect(m_gameList, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); @@ -216,10 +220,39 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: connect(m_xgrid, &QTableWidget::doubleClicked, this, &game_list_frame::doubleClickedSlot); connect(m_xgrid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); + connect(m_game_compat.get(), &game_compatibility::DownloadStarted, [=]() + { + for (auto& game : m_game_data) + { + game.compat = m_game_compat->GetStatusData("Download"); + } + Refresh(); + }); + connect(m_game_compat.get(), &game_compatibility::DownloadFinished, [=]() + { + for (auto& game : m_game_data) + { + game.compat = m_game_compat->GetCompatibility(game.info.serial); + } + Refresh(); + }); + connect(m_game_compat.get(), &game_compatibility::DownloadError, [=](const QString& error) + { + for (auto& game : m_game_data) + { + game.compat = m_game_compat->GetCompatibility(game.info.serial); + } + Refresh(); + QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n") + tr(qPrintable(error))); + }); + connect(m_Search_Bar, &QLineEdit::textChanged, this, &game_list_frame::SetSearchText); connect(m_Slider_Size, &QSlider::valueChanged, this, &game_list_frame::RequestIconSizeActSet); - connect(m_Slider_Size, &QSlider::sliderReleased, this, [&]{ xgui_settings->SetValue(gui::gl_iconSize, m_Slider_Size->value()); }); + connect(m_Slider_Size, &QSlider::sliderReleased, this, [&] + { + xgui_settings->SetValue(gui::gl_iconSize, m_Slider_Size->value()); + }); connect(m_Slider_Size, &QSlider::actionTriggered, [&](int action) { if (action != QAbstractSlider::SliderNoAction && action != QAbstractSlider::SliderMove) @@ -457,7 +490,7 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter) QPixmap pxmap = PaintedPixmap(img, hasCustomConfig); - m_game_data.push_back({ game, img, pxmap, true, bootable, hasCustomConfig }); + m_game_data.push_back({ game, m_game_compat->GetCompatibility(game.serial), img, pxmap, true, bootable, hasCustomConfig }); } auto op = [](const GUI_GameInfo& game1, const GUI_GameInfo& game2) @@ -638,6 +671,7 @@ void game_list_frame::ShowSpecifiedContextMenu(const QPoint &pos, int row) QAction* openConfig = myMenu.addAction(tr("&Open Config Folder")); myMenu.addSeparator(); QAction* checkCompat = myMenu.addAction(tr("&Check Game Compatibility")); + QAction* downloadCompat = myMenu.addAction(tr("&Download Compatibility Database")); connect(boot, &QAction::triggered, [=] { @@ -711,6 +745,10 @@ void game_list_frame::ShowSpecifiedContextMenu(const QPoint &pos, int row) QString link = "https://rpcs3.net/compatibility?g=" + qstr(currGame.serial); QDesktopServices::openUrl(QUrl(link)); }); + connect(downloadCompat, &QAction::triggered, [=] + { + m_game_compat->RequestCompatibility(true); + }); //Disable options depending on software category QString category = qstr(currGame.category); @@ -1054,6 +1092,15 @@ int game_list_frame::PopulateGameList() title_item->setIcon(QIcon(":/Icons/cog_black.png")); } + // Compatibility + QTableWidgetItem* compat_item = new QTableWidgetItem; + compat_item->setFlags(compat_item->flags() & ~Qt::ItemIsEditable); + compat_item->setText(game.compat.text + (game.compat.date.isEmpty() ? "" : " (" + game.compat.date + ")")); + compat_item->setToolTip(game.compat.tooltip); + if (!game.compat.color.isEmpty()) + { + compat_item->setData(Qt::DecorationRole, compat_pixmap(game.compat.color)); + } m_gameList->setItem(row, gui::column_icon, icon_item); m_gameList->setItem(row, gui::column_name, title_item); m_gameList->setItem(row, gui::column_serial, l_GetItem(game.info.serial)); @@ -1064,8 +1111,12 @@ int game_list_frame::PopulateGameList() m_gameList->setItem(row, gui::column_resolution, l_GetItem(GetStringFromU32(game.info.resolution, resolution::mode, true))); m_gameList->setItem(row, gui::column_sound, l_GetItem(GetStringFromU32(game.info.sound_format, sound::format, true))); m_gameList->setItem(row, gui::column_parental, l_GetItem(GetStringFromU32(game.info.parental_lvl, parental::level))); + m_gameList->setItem(row, gui::column_compat, compat_item); - if (selected_item == game.info.icon_path) result = row; + if (selected_item == game.info.icon_path) + { + result = row; + } row++; } diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index bb46d737f8..97c9fb278e 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -5,8 +5,8 @@ #include "game_list.h" #include "game_list_grid.h" -#include "gui_settings.h" #include "emu_settings.h" +#include "game_compatibility.h" #include #include @@ -163,6 +163,7 @@ namespace sound struct GUI_GameInfo { GameInfo info; + Compat_Status compat; QImage icon; QPixmap pxmap; bool isVisible; @@ -259,6 +260,7 @@ private: // Game List game_list* m_gameList; + std::unique_ptr m_game_compat; QList m_columnActs; Qt::SortOrder m_colSortOrder; int m_sortColumn; diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 2fd81dc837..364a3b0e6b 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -10,6 +10,14 @@ #include #include +struct Compat_Status +{ + QString date; + QString color; + QString text; + QString tooltip; +}; + struct gui_save { QString key; @@ -53,6 +61,7 @@ namespace gui column_resolution, column_sound, column_parental, + column_compat, column_count };