mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
@@ -183,6 +183,11 @@
|
||||
<string>Serial</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Game Version</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Firmware Version</string>
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
|
||||
QString name;
|
||||
QString title_id; /* Serial / title id from sce_sys/param.json */
|
||||
QString gameVersion; /* appVersion / contentVersion from sce_sys/param.json */
|
||||
QString firmwareVer; /* requiredSystemSoftwareVersion from sce_sys/param.json */
|
||||
QString basedir; /* Game base directory */
|
||||
QString game_path; /* Launcher-unique game path */
|
||||
@@ -117,6 +118,7 @@ public:
|
||||
void CopyFrom(const Configuration& other) {
|
||||
name = other.name;
|
||||
title_id = other.title_id;
|
||||
gameVersion = other.gameVersion;
|
||||
firmwareVer = other.firmwareVer;
|
||||
basedir = other.basedir;
|
||||
game_path = other.game_path;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef PATCHES_DIALOG_H
|
||||
#define PATCHES_DIALOG_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
class Configuration;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
|
||||
class PatchesDialog final: public QDialog {
|
||||
public:
|
||||
explicit PatchesDialog(const Configuration& game, QWidget* parent = nullptr);
|
||||
~PatchesDialog() override = default;
|
||||
|
||||
KYTY_QT_CLASS_NO_COPY(PatchesDialog);
|
||||
|
||||
[[nodiscard]] static bool IsSupportedTitleId(const QString& title_id);
|
||||
[[nodiscard]] static QString PatchPlanPath(const QString& title_id);
|
||||
|
||||
private:
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
QString m_title_id;
|
||||
QListWidget* m_patches = nullptr;
|
||||
QLabel* m_status = nullptr;
|
||||
QPushButton* m_apply = nullptr;
|
||||
};
|
||||
|
||||
#endif // PATCHES_DIALOG_H
|
||||
@@ -24,6 +24,7 @@ namespace {
|
||||
enum Column {
|
||||
NameColumn,
|
||||
SerialColumn,
|
||||
GameVersionColumn,
|
||||
FirmwareVersionColumn,
|
||||
PathColumn,
|
||||
StatusColumn,
|
||||
@@ -85,6 +86,9 @@ QString GetDisplayText(const Configuration& info) {
|
||||
if (!info.title_id.isEmpty()) {
|
||||
lines.append(QStringLiteral("Serial ID: %1").arg(info.title_id));
|
||||
}
|
||||
if (!info.gameVersion.isEmpty()) {
|
||||
lines.append(QStringLiteral("Game version: %1").arg(info.gameVersion));
|
||||
}
|
||||
if (!info.firmwareVer.isEmpty()) {
|
||||
lines.append(QStringLiteral("Firmware version: %1").arg(info.firmwareVer));
|
||||
}
|
||||
@@ -169,6 +173,8 @@ void ConfigurationItem::Update() {
|
||||
|
||||
setText(NameColumn, m_info->name);
|
||||
setText(SerialColumn, m_info->title_id);
|
||||
setText(GameVersionColumn,
|
||||
m_info->gameVersion.isEmpty() ? QStringLiteral("\u2014") : m_info->gameVersion);
|
||||
setText(FirmwareVersionColumn,
|
||||
m_info->firmwareVer.isEmpty() ? QStringLiteral("\u2014") : m_info->firmwareVer);
|
||||
setText(PathColumn, path);
|
||||
@@ -198,9 +204,13 @@ bool ConfigurationItem::operator<(const QTreeWidgetItem& other) const {
|
||||
case StatusColumn:
|
||||
return GetStatusText(m_info->game_status) <
|
||||
GetStatusText(other_item->m_info->game_status);
|
||||
case GameVersionColumn:
|
||||
case FirmwareVersionColumn: {
|
||||
const auto& version = m_info->firmwareVer;
|
||||
const auto& other_version = other_item->m_info->firmwareVer;
|
||||
const auto& version = column == GameVersionColumn ? m_info->gameVersion
|
||||
: m_info->firmwareVer;
|
||||
const auto& other_version = column == GameVersionColumn
|
||||
? other_item->m_info->gameVersion
|
||||
: other_item->m_info->firmwareVer;
|
||||
if (version.isEmpty() || other_version.isEmpty()) {
|
||||
return version.isEmpty() && !other_version.isEmpty();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "configurationListWidget.h"
|
||||
|
||||
#include "patchesDialog.h"
|
||||
#include "common.h"
|
||||
#include "compatibilityDatabase.h"
|
||||
#include "configuration.h"
|
||||
@@ -55,10 +56,11 @@ constexpr char SAVE_DATA_DIR[] = "_SaveData";
|
||||
|
||||
constexpr int GAME_NAME_COLUMN = 0;
|
||||
constexpr int GAME_SERIAL_COLUMN = 1;
|
||||
constexpr int GAME_FIRMWARE_VERSION_COLUMN = 2;
|
||||
constexpr int GAME_PATH_COLUMN = 3;
|
||||
constexpr int GAME_STATUS_COLUMN = 4;
|
||||
constexpr int GAME_COMMENT_COLUMN = 5;
|
||||
constexpr int GAME_VERSION_COLUMN = 2;
|
||||
constexpr int GAME_FIRMWARE_VERSION_COLUMN = 3;
|
||||
constexpr int GAME_PATH_COLUMN = 4;
|
||||
constexpr int GAME_STATUS_COLUMN = 5;
|
||||
constexpr int GAME_COMMENT_COLUMN = 6;
|
||||
|
||||
static QString NormalizeGameDirectory(const QString& dir) {
|
||||
const auto trimmed = dir.trimmed();
|
||||
@@ -239,6 +241,7 @@ ConfigurationListWidget::ConfigurationListWidget(QWidget* parent)
|
||||
m_ui->cfgs_list->setSortingEnabled(true);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_NAME_COLUMN, 320);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_SERIAL_COLUMN, 110);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_VERSION_COLUMN, 120);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_FIRMWARE_VERSION_COLUMN, 150);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_PATH_COLUMN, 320);
|
||||
m_ui->cfgs_list->setColumnWidth(GAME_STATUS_COLUMN, 150);
|
||||
@@ -403,6 +406,7 @@ static Configuration* CloneConfiguration(const Configuration& source) {
|
||||
struct GameMetadata {
|
||||
QString title_name;
|
||||
QString title_id;
|
||||
QString gameVersion;
|
||||
QString firmwareVer;
|
||||
};
|
||||
|
||||
@@ -488,6 +492,10 @@ static GameMetadata GetGameMetadata(const QString& param_file, const QString& fa
|
||||
}
|
||||
|
||||
ret.title_id = GetJsonString(root, QStringLiteral("titleId"));
|
||||
ret.gameVersion = GetJsonString(root, QStringLiteral("appVersion"));
|
||||
if (ret.gameVersion.isEmpty()) {
|
||||
ret.gameVersion = GetJsonString(root, QStringLiteral("contentVersion"));
|
||||
}
|
||||
ret.firmwareVer = GetFirmwareVersion(root);
|
||||
|
||||
return ret;
|
||||
@@ -501,6 +509,7 @@ static void SetGameFiles(Configuration& info, const QString& game_dir, const QSt
|
||||
info.basedir = game.absolutePath();
|
||||
info.name = metadata.title_name;
|
||||
info.title_id = metadata.title_id;
|
||||
info.gameVersion = metadata.gameVersion;
|
||||
info.firmwareVer = metadata.firmwareVer;
|
||||
|
||||
if (info.name.isEmpty()) {
|
||||
@@ -866,6 +875,15 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
|
||||
style()->standardIcon(QStyle::SP_FileDialogContentsView), tr("View trophies..."));
|
||||
connect(action_view_trophies, &QAction::triggered, this,
|
||||
&ConfigurationListWidget::ViewTrophies);
|
||||
QAction* action_patches = menu.addAction(tr("Patches (experimental)..."));
|
||||
connect(action_patches, &QAction::triggered, this, [this, item]() {
|
||||
if (item != nullptr) {
|
||||
auto* dialog = new PatchesDialog(item->GetInfo(), this);
|
||||
dialog->show();
|
||||
}
|
||||
});
|
||||
action_patches->setVisible(item != nullptr &&
|
||||
PatchesDialog::IsSupportedTitleId(item->GetInfo().title_id));
|
||||
QAction* action_remove_save_data =
|
||||
menu.addAction(style()->standardIcon(QStyle::SP_DialogDiscardButton),
|
||||
tr("Remove save data..."), this, SLOT(remove_save_data()));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "configuration.h"
|
||||
#include "configurationItem.h"
|
||||
#include "configurationListWidget.h"
|
||||
#include "patchesDialog.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
@@ -228,6 +229,11 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
}
|
||||
args << "--game" << game;
|
||||
|
||||
const auto patch_plan = PatchesDialog::PatchPlanPath(info.title_id);
|
||||
if (QFileInfo::exists(patch_plan)) {
|
||||
args << "--game-patch" << patch_plan;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "patchesDialog.h"
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QSaveFile>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
PatchesDialog::PatchesDialog(const Configuration& game, QWidget* parent)
|
||||
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()) {
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle(tr("Patches (Experimental) - %1").arg(game.name));
|
||||
resize(640, 480);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
m_patches = new QListWidget(this);
|
||||
m_status = new QLabel(this);
|
||||
m_apply = new QPushButton(tr("Apply selection"), this);
|
||||
auto* close = new QPushButton(tr("Close"), this);
|
||||
|
||||
m_status->setWordWrap(true);
|
||||
layout->addWidget(m_patches);
|
||||
layout->addWidget(m_status);
|
||||
|
||||
auto* buttons = new QDialogButtonBox(this);
|
||||
buttons->addButton(m_apply, QDialogButtonBox::ActionRole);
|
||||
buttons->addButton(close, QDialogButtonBox::RejectRole);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
connect(m_apply, &QPushButton::clicked, this, &PatchesDialog::Save);
|
||||
connect(close, &QPushButton::clicked, this, &QDialog::close);
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
bool PatchesDialog::IsSupportedTitleId(const QString& title_id) {
|
||||
return title_id.trimmed().toUpper().startsWith(QStringLiteral("PPSA"));
|
||||
}
|
||||
|
||||
QString PatchesDialog::PatchPlanPath(const QString& title_id) {
|
||||
return QDir(QCoreApplication::applicationDirPath())
|
||||
.filePath(QStringLiteral("_Patches/%1.json").arg(title_id.trimmed().toUpper()));
|
||||
}
|
||||
|
||||
void PatchesDialog::Load() {
|
||||
QFile file(PatchPlanPath(m_title_id));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
m_status->setText(tr("No local patch file: %1").arg(file.fileName()));
|
||||
m_apply->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto patches = QJsonDocument::fromJson(file.readAll())
|
||||
.object()
|
||||
.value(QStringLiteral("patches"))
|
||||
.toArray();
|
||||
for (const auto& value: patches) {
|
||||
const auto patch = value.toObject();
|
||||
auto* item = new QListWidgetItem(patch.value(QStringLiteral("name")).toString(), m_patches);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(patch.value(QStringLiteral("enabled")).toBool(true) ? Qt::Checked
|
||||
: Qt::Unchecked);
|
||||
}
|
||||
|
||||
m_apply->setEnabled(!patches.isEmpty());
|
||||
m_status->setText(tr("Loaded %1 patch(es) from %2.").arg(patches.size()).arg(file.fileName()));
|
||||
}
|
||||
|
||||
void PatchesDialog::Save() {
|
||||
const auto path = PatchPlanPath(m_title_id);
|
||||
QFile input(path);
|
||||
if (!input.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto document = QJsonDocument::fromJson(input.readAll());
|
||||
input.close();
|
||||
auto root = document.object();
|
||||
auto patches = root.value(QStringLiteral("patches")).toArray();
|
||||
for (int index = 0; index < patches.size(); index++) {
|
||||
auto patch = patches[index].toObject();
|
||||
patch.insert(QStringLiteral("enabled"),
|
||||
m_patches->item(index)->checkState() == Qt::Checked);
|
||||
patches[index] = patch;
|
||||
}
|
||||
root.insert(QStringLiteral("patches"), patches);
|
||||
document.setObject(root);
|
||||
|
||||
QSaveFile output(path);
|
||||
if (output.open(QIODevice::WriteOnly) && output.write(document.toJson()) >= 0 &&
|
||||
output.commit()) {
|
||||
m_status->setText(tr("Patch selection saved."));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user