diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp index a10f226a41..77356818cd 100644 --- a/rpcs3/rpcs3qt/game_compatibility.cpp +++ b/rpcs3/rpcs3qt/game_compatibility.cpp @@ -2,6 +2,9 @@ #include "gui_settings.h" #include "downloader.h" +#include "Crypto/unpkg.h" +#include "Loader/PSF.h" + #include #include #include @@ -240,3 +243,53 @@ compat::status game_compatibility::GetStatusData(const QString& status) { return Status_Data.at(status); } + +compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, game_compatibility* compat) +{ + package_reader reader(pkg_path.toStdString()); + psf::registry psf = reader.get_psf(); + + // TODO: localization of title and changelog + std::string title_key = "TITLE"; + std::string changelog_key = "paramhip"; + + compat::package_info info; + info.path = pkg_path; + info.title_id = qstr(std::string(psf::get_string(psf, "TITLE_ID", "Unknown"))); + info.version = qstr(std::string(psf::get_string(psf, "APP_VER"))); + info.title = qstr(std::string(psf::get_string(psf, title_key))); // Let's read this from the psf first + + if (compat) + { + compat::status stat = compat->GetCompatibility(sstr(info.title_id)); + if (!stat.patch_sets.empty()) + { + // We currently only handle the first patch set + for (const auto& package : stat.patch_sets.front().packages) + { + if (sstr(info.version) == package.version) + { + if (const std::string localized_title = package.get_title(title_key); !localized_title.empty()) + { + info.title= qstr(localized_title); + } + + if (const std::string localized_changelog = package.get_changelog(changelog_key); !localized_changelog.empty()) + { + info.changelog = qstr(localized_changelog); + } + + break; + } + } + } + } + + if (info.title.isEmpty()) + { + const QFileInfo file_info(pkg_path); + info.title = file_info.fileName(); + } + + return info; +} diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h index 67e0eebd11..d9f2d3e91c 100644 --- a/rpcs3/rpcs3qt/game_compatibility.h +++ b/rpcs3/rpcs3qt/game_compatibility.h @@ -10,18 +10,21 @@ class gui_settings; namespace compat { + /** Represents the "title" json object */ struct pkg_title { std::string type; // TITLE or TITLE_08 etc. (system languages) std::string title; // The Last of Arse }; + /** Represents the "changelog" json object */ struct pkg_changelog { std::string type; // paramhip or paramhip_08 etc. (system languages) std::string content; // "This system software update improves system performance." }; + /** Represents the "package" json object */ struct pkg_package { std::string version; // 01.04 @@ -63,6 +66,7 @@ namespace compat } }; + /** Represents the "patchset" json object */ struct pkg_patchset { std::string tag_id; // BLES01269_T7 @@ -73,6 +77,7 @@ namespace compat std::vector packages; }; + /** Represents the json object that contains an app's information and some additional info that is used in the GUI */ struct status { int index; @@ -83,6 +88,16 @@ namespace compat QString latest_version; std::vector patch_sets; }; + + /** Concicely represents a specific pkg's localized information for use in the GUI */ + struct package_info + { + QString path; // File path + QString title_id; // TEST12345 + QString title; // Localized + QString changelog; // Localized, may be empty + QString version; // May be empty + }; } class game_compatibility : public QObject @@ -122,6 +137,9 @@ public: /** Returns the data for the requested status */ compat::status GetStatusData(const QString& status); + /** Returns package information like title, version, changelog etc. */ + static compat::package_info GetPkgInfo(const QString& pkg_path, game_compatibility* compat); + Q_SIGNALS: void DownloadStarted(); void DownloadFinished(); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 250950e416..84df9eb698 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -512,62 +512,26 @@ void main_window::InstallPackages(QStringList file_paths) { // This can currently only happen by drag and drop. const QString file_path = file_paths.front(); - package_reader reader(file_path.toStdString()); - psf::registry psf = reader.get_psf(); - const std::string title_id(psf::get_string(psf, "TITLE_ID")); - // TODO: localization of title and changelog - std::string title_key = "TITLE"; - std::string changelog_key = "paramhip"; - //std::string cat(psf::get_string(psf, "CATEGORY")); - QString version = qstr(std::string(psf::get_string(psf, "APP_VER"))); - QString title = qstr(std::string(psf::get_string(psf, title_key))); // Let's read this from the psf first - QString changelog; + compat::package_info info = game_compatibility::GetPkgInfo(file_path, m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr); - if (game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr) + if (!info.title_id.isEmpty()) { - compat::status info = compat->GetCompatibility(title_id); - if (!info.patch_sets.empty()) - { - // We currently only handle the first patch set - for (const auto& package : info.patch_sets.front().packages) - { - if (sstr(version) == package.version) - { - if (const std::string localized_title = package.get_title(title_key); !localized_title.empty()) - { - title = qstr(localized_title); - } - - if (const std::string localized_changelog = package.get_changelog(changelog_key); !localized_changelog.empty()) - { - changelog = qstr(localized_changelog); - } - - break; - } - } - } + info.title_id = tr("\n%0").arg(info.title_id); } - if (!changelog.isEmpty()) + if (!info.changelog.isEmpty()) { - changelog = tr("\n\nChangelog:\n%0").arg(changelog); + info.changelog = tr("\n\nChangelog:\n%0").arg(info.changelog); } - if (!version.isEmpty()) + if (!info.version.isEmpty()) { - version = tr("\nVersion %0").arg(version); + info.version = tr("\nVersion %0").arg(info.version); } - if (title.isEmpty()) - { - QFileInfo file_info(file_path); - title = file_info.fileName(); - } - - if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Do you want to install this package?\n\n%0%1%2") - .arg(title).arg(version).arg(changelog), + if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Do you want to install this package?\n\n%0%1%2%3") + .arg(info.title).arg(info.title_id).arg(info.version).arg(info.changelog), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { gui_log.notice("PKG: Cancelled installation from drop. File: %s", sstr(file_paths.front())); @@ -599,32 +563,34 @@ void main_window::InstallPackages(QStringList file_paths) return; } + std::vector infos; + // Let the user choose the packages to install and select the order in which they shall be installed. if (file_paths.size() > 1) { - pkg_install_dialog dlg(file_paths, this); - connect(&dlg, &QDialog::accepted, [&file_paths, &dlg]() + pkg_install_dialog dlg(file_paths, m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr, this); + connect(&dlg, &QDialog::accepted, [&infos, &dlg]() { - file_paths = dlg.GetPathsToInstall(); + infos = dlg.GetPathsToInstall(); }); dlg.exec(); } - if (file_paths.empty()) + if (infos.empty()) { return; } // Handle the actual installations with a timeout. Otherwise the source explorer instance is not usable during the following file processing. - QTimer::singleShot(0, [this, file_paths]() + QTimer::singleShot(0, [this, packages = std::move(infos)]() { - HandlePackageInstallation(file_paths); + HandlePackageInstallation(packages); }); } -void main_window::HandlePackageInstallation(QStringList file_paths) +void main_window::HandlePackageInstallation(const std::vector& packages) { - if (file_paths.isEmpty()) + if (packages.empty()) { return; } @@ -644,7 +610,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) bool cancelled = false; - for (int i = 0, count = file_paths.count(); i < count; i++) + for (size_t i = 0, count = packages.size(); i < count; i++) { progress = 0.; @@ -655,7 +621,7 @@ void main_window::HandlePackageInstallation(QStringList file_paths) Emu.SetForceBoot(true); Emu.Stop(); - const QString file_path = file_paths.at(i); + const QString file_path = packages.at(i).path; const QFileInfo file_info(file_path); const std::string path = sstr(file_path); const std::string file_name = sstr(file_info.fileName()); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index e5638e1ecb..2c3882c3ee 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -27,6 +27,11 @@ struct gui_game_info; enum class game_boot_result : u32; +namespace compat +{ + struct package_info; +} + namespace Ui { class main_window; @@ -135,7 +140,7 @@ private: static bool InstallRapFile(const QString& path, const std::string& filename); void InstallPackages(QStringList file_paths = QStringList()); - void HandlePackageInstallation(QStringList file_paths = QStringList()); + void HandlePackageInstallation(const std::vector& packages); void InstallPup(QString filePath = ""); void HandlePupInstallation(QString file_path = ""); diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index f13f62d70e..375b3192de 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -1,4 +1,5 @@ #include "pkg_install_dialog.h" +#include "game_compatibility.h" #include #include @@ -7,10 +8,17 @@ #include #include -constexpr int FullPathRole = Qt::UserRole + 0; -constexpr int BaseDisplayRole = Qt::UserRole + 1; +enum Roles +{ + FullPathRole = Qt::UserRole + 0, + BaseDisplayRole = Qt::UserRole + 1, + ChangelogRole = Qt::UserRole + 2, + TitleRole = Qt::UserRole + 3, + TitleIdRole = Qt::UserRole + 4, + VersionRole = Qt::UserRole + 5, +}; -pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent) +pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent) : QDialog(parent) { m_dir_list = new QListWidget(this); @@ -29,9 +37,9 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent switch (role) { case Qt::DisplayRole: - result = QStringLiteral("%1. %2").arg(listWidget()->row(this) + 1).arg(data(BaseDisplayRole).toString()); + result = QStringLiteral("%1. %2").arg(listWidget()->row(this) + 1).arg(data(Roles::BaseDisplayRole).toString()); break; - case BaseDisplayRole: + case Roles::BaseDisplayRole: result = QListWidgetItem::data(Qt::DisplayRole); break; default: @@ -43,15 +51,40 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent bool operator<(const QListWidgetItem& other) const override { - return data(BaseDisplayRole).toString() < other.data(BaseDisplayRole).toString(); + return data(Roles::BaseDisplayRole).toString() < other.data(Roles::BaseDisplayRole).toString(); } }; for (const QString& path : paths) { - QListWidgetItem* item = new numbered_widget_item(QFileInfo(path).fileName(), m_dir_list); - // Save full path in a custom data role - item->setData(FullPathRole, path); + const compat::package_info info = game_compatibility::GetPkgInfo(path, compat); + + QString tooltip; + QString version = info.version; + + if (info.changelog.isEmpty()) + { + tooltip = tr("No info"); + } + else + { + tooltip = tr("Changelog:\n\n%0").arg(info.changelog); + } + + if (!version.isEmpty()) + { + version = tr("v.%0").arg(info.version); + } + + const QString text = tr("%0 (%1 %2)").arg(info.title).arg(info.title_id).arg(version); + + QListWidgetItem* item = new numbered_widget_item(text, m_dir_list); + item->setData(Roles::FullPathRole, info.path); + item->setData(Roles::ChangelogRole, info.changelog); + item->setData(Roles::TitleRole, info.title); + item->setData(Roles::TitleIdRole, info.title_id); + item->setData(Roles::VersionRole, info.version); + item->setToolTip(tooltip); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Checked); } @@ -65,7 +98,7 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent buttons->button(QDialogButtonBox::Ok)->setText(tr("Install")); buttons->button(QDialogButtonBox::Ok)->setDefault(true); - connect(buttons, &QDialogButtonBox::clicked, [this, buttons](QAbstractButton* button) + connect(buttons, &QDialogButtonBox::clicked, this, [this, buttons](QAbstractButton* button) { if (button == buttons->button(QDialogButtonBox::Ok)) { @@ -77,7 +110,7 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent } }); - connect(m_dir_list, &QListWidget::itemChanged, [this, buttons](QListWidgetItem*) + connect(m_dir_list, &QListWidget::itemChanged, this, [this, buttons](QListWidgetItem*) { bool any_checked = false; for (int i = 0; i < m_dir_list->count(); i++) @@ -95,12 +128,12 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent QToolButton* move_up = new QToolButton; move_up->setArrowType(Qt::UpArrow); move_up->setToolTip(tr("Move selected item up")); - connect(move_up, &QToolButton::clicked, [this]() { MoveItem(-1); }); + connect(move_up, &QToolButton::clicked, this, [this]() { MoveItem(-1); }); QToolButton* move_down = new QToolButton; move_down->setArrowType(Qt::DownArrow); move_down->setToolTip(tr("Move selected item down")); - connect(move_down, &QToolButton::clicked, [this]() { MoveItem(1); }); + connect(move_down, &QToolButton::clicked, this, [this]() { MoveItem(1); }); QHBoxLayout* hbox = new QHBoxLayout; hbox->addStretch(); @@ -134,16 +167,22 @@ void pkg_install_dialog::MoveItem(int offset) } } -QStringList pkg_install_dialog::GetPathsToInstall() const +std::vector pkg_install_dialog::GetPathsToInstall() const { - QStringList result; + std::vector result; for (int i = 0; i < m_dir_list->count(); i++) { const QListWidgetItem* item = m_dir_list->item(i); - if (item->checkState() == Qt::Checked) + if (item && item->checkState() == Qt::Checked) { - result.append(item->data(FullPathRole).toString()); + compat::package_info info; + info.path = item->data(Roles::FullPathRole).toString(); + info.title = item->data(Roles::TitleRole).toString(); + info.title_id = item->data(Roles::TitleIdRole).toString(); + info.changelog = item->data(Roles::ChangelogRole).toString(); + info.version = item->data(Roles::VersionRole).toString(); + result.push_back(info); } } diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h index 1cefb1fec3..f8a4c3d0b6 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.h +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -3,13 +3,20 @@ #include #include +namespace compat +{ + struct package_info; +} + +class game_compatibility; + class pkg_install_dialog : public QDialog { Q_OBJECT public: - explicit pkg_install_dialog(const QStringList& paths, QWidget* parent = nullptr); - QStringList GetPathsToInstall() const; + explicit pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent = nullptr); + std::vector GetPathsToInstall() const; private: void MoveItem(int offset);