mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 20:36:54 +01:00
addcont: add user add-ons with --addcont and a launcher list
This commit is contained in:
@@ -4,26 +4,39 @@
|
||||
#include <QDialog>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class Configuration;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
|
||||
class AdditionalContentDialog final: public QDialog {
|
||||
public:
|
||||
AdditionalContentDialog(const Configuration& game, const QString& folder,
|
||||
QWidget* parent = nullptr);
|
||||
// entries are the game's user add-ons, "LABEL" or "LABEL=DIR"; save receives every edit.
|
||||
AdditionalContentDialog(const Configuration& game, const QString& folder, QStringList entries,
|
||||
std::function<void(const QStringList&)> save,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
void Scan();
|
||||
void Add();
|
||||
void Remove();
|
||||
|
||||
QString m_title_id;
|
||||
QString m_folder;
|
||||
QIcon m_game_icon;
|
||||
QLabel* m_status = nullptr;
|
||||
QListWidget* m_list = nullptr;
|
||||
QPushButton* m_open = nullptr;
|
||||
QString m_title_id;
|
||||
QString m_folder;
|
||||
QStringList m_entries;
|
||||
std::function<void(const QStringList&)> m_save;
|
||||
QIcon m_game_icon;
|
||||
QLabel* m_status = nullptr;
|
||||
QListWidget* m_list = nullptr;
|
||||
QPushButton* m_open = nullptr;
|
||||
QPushButton* m_remove = nullptr;
|
||||
QLineEdit* m_label_edit = nullptr;
|
||||
QLineEdit* m_dir_edit = nullptr;
|
||||
};
|
||||
|
||||
#endif // ADDITIONAL_CONTENT_DIALOG_H
|
||||
|
||||
@@ -102,16 +102,17 @@ public:
|
||||
|
||||
Configuration() = default;
|
||||
|
||||
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 */
|
||||
QString addons_dir; /* Global setting; not saved per game. */
|
||||
bool custom_settings = false;
|
||||
GameStatus game_status = GameStatus::Unknown;
|
||||
QString game_comment;
|
||||
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 */
|
||||
QString addons_dir; /* Global setting; not saved per game. */
|
||||
QStringList addcont; /* User add-ons ("LABEL" or "LABEL=DIR"), saved by title ID. */
|
||||
bool custom_settings = false;
|
||||
GameStatus game_status = GameStatus::Unknown;
|
||||
QString game_comment;
|
||||
|
||||
// Controller preferences always come from the global configuration.
|
||||
ControllerSettings controller;
|
||||
|
||||
@@ -87,6 +87,7 @@ private:
|
||||
QString m_runtime_directory;
|
||||
QStringList m_game_dirs;
|
||||
QString m_addons_dir;
|
||||
QMap<QString, QStringList> m_addcont; // User add-ons by title ID.
|
||||
Configuration m_global_info;
|
||||
QMap<QString, Configuration*> m_custom_infos;
|
||||
CompatibilityDatabase* m_compatibility = nullptr;
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
@@ -16,8 +19,11 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
AdditionalContentDialog::AdditionalContentDialog(const Configuration& game, const QString& folder,
|
||||
QWidget* parent)
|
||||
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()), m_folder(folder) {
|
||||
QStringList entries,
|
||||
std::function<void(const QStringList&)> save,
|
||||
QWidget* parent)
|
||||
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()), m_folder(folder),
|
||||
m_entries(std::move(entries)), m_save(std::move(save)) {
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle(tr("Additional Content - %1").arg(game.name));
|
||||
resize(720, 480);
|
||||
@@ -39,6 +45,28 @@ AdditionalContentDialog::AdditionalContentDialog(const Configuration& game, cons
|
||||
m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
layout->addWidget(m_list, 1);
|
||||
|
||||
auto* add_layout = new QHBoxLayout;
|
||||
m_label_edit = new QLineEdit(this);
|
||||
m_label_edit->setPlaceholderText(tr("Label or content ID"));
|
||||
m_dir_edit = new QLineEdit(this);
|
||||
m_dir_edit->setPlaceholderText(tr("Add-on data folder (empty for a license only)"));
|
||||
auto* browse = new QPushButton(tr("Browse..."), this);
|
||||
auto* add = new QPushButton(tr("Add"), this);
|
||||
add->setDefault(true);
|
||||
add_layout->addWidget(m_label_edit, 1);
|
||||
add_layout->addWidget(m_dir_edit, 2);
|
||||
add_layout->addWidget(browse);
|
||||
add_layout->addWidget(add);
|
||||
layout->addLayout(add_layout);
|
||||
connect(browse, &QPushButton::clicked, this, [this]() {
|
||||
const auto dir =
|
||||
QFileDialog::getExistingDirectory(this, tr("Add-on data folder"), m_dir_edit->text());
|
||||
if (!dir.isEmpty()) {
|
||||
m_dir_edit->setText(QDir::toNativeSeparators(dir));
|
||||
}
|
||||
});
|
||||
connect(add, &QPushButton::clicked, this, &AdditionalContentDialog::Add);
|
||||
|
||||
m_status = new QLabel(this);
|
||||
m_status->setTextFormat(Qt::PlainText);
|
||||
m_status->setWordWrap(true);
|
||||
@@ -47,10 +75,15 @@ AdditionalContentDialog::AdditionalContentDialog(const Configuration& game, cons
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
||||
auto* refresh = buttons->addButton(tr("Refresh"), QDialogButtonBox::ActionRole);
|
||||
m_open = buttons->addButton(tr("Open folder"), QDialogButtonBox::ActionRole);
|
||||
m_remove = buttons->addButton(tr("Remove"), QDialogButtonBox::ActionRole);
|
||||
layout->addWidget(buttons);
|
||||
connect(refresh, &QPushButton::clicked, this, &AdditionalContentDialog::Scan);
|
||||
connect(m_open, &QPushButton::clicked, this,
|
||||
[this]() { QDesktopServices::openUrl(QUrl::fromLocalFile(m_folder)); });
|
||||
connect(m_remove, &QPushButton::clicked, this, &AdditionalContentDialog::Remove);
|
||||
connect(m_list, &QListWidget::currentItemChanged, this, [this](const QListWidgetItem* item) {
|
||||
m_remove->setEnabled(item != nullptr && item->data(Qt::UserRole).isValid());
|
||||
});
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||
|
||||
Scan();
|
||||
@@ -73,18 +106,54 @@ void AdditionalContentDialog::Scan() {
|
||||
tr("%1\nInstalled").arg(title), m_list);
|
||||
item->setToolTip(QString::fromStdString(entry.content_id));
|
||||
}
|
||||
for (const auto& entry: m_entries) {
|
||||
const auto label = entry.section('=', 0, 0);
|
||||
const auto dir = entry.section('=', 1);
|
||||
auto text = tr("%1\nLicense only").arg(label);
|
||||
if (!dir.isEmpty()) {
|
||||
text = (QDir(dir).exists() ? tr("%1\nData: %2") : tr("%1\nData folder not found: %2"))
|
||||
.arg(label, QDir::toNativeSeparators(dir));
|
||||
}
|
||||
auto* item = new QListWidgetItem(m_game_icon, text, m_list);
|
||||
item->setData(Qt::UserRole, entry);
|
||||
}
|
||||
m_remove->setEnabled(false);
|
||||
|
||||
if (m_folder.isEmpty()) {
|
||||
if (m_list->count() != 0) {
|
||||
m_status->setText(tr("%n add-on(s)", nullptr, m_list->count()));
|
||||
} else if (m_folder.isEmpty()) {
|
||||
m_status->setText(tr("No add-ons folder is set. Open Global settings and set the "
|
||||
"Add-ons folder to the location of your extracted add-on packages."));
|
||||
} else if (!folder_exists) {
|
||||
m_status->setText(tr("The add-ons folder does not exist:\n%1\nCheck the path in Global "
|
||||
"settings.")
|
||||
.arg(QDir::toNativeSeparators(m_folder)));
|
||||
} else if (m_list->count() == 0) {
|
||||
} else {
|
||||
m_status->setText(
|
||||
tr("No add-ons found for this game in:\n%1").arg(QDir::toNativeSeparators(m_folder)));
|
||||
} else {
|
||||
m_status->setText(tr("%n installed add-on(s)", nullptr, m_list->count()));
|
||||
}
|
||||
}
|
||||
|
||||
void AdditionalContentDialog::Add() {
|
||||
// A content ID ends with its entitlement label.
|
||||
const auto label = m_label_edit->text().trimmed().section('-', -1);
|
||||
const auto dir = m_dir_edit->text().trimmed();
|
||||
const auto entry = dir.isEmpty() ? label : label + '=' + QDir(dir).absolutePath();
|
||||
if (!Common::ParseAddContent(entry.toStdString())) {
|
||||
m_status->setText(tr("Enter a 16-character entitlement label (A-Z, 0-9) and an existing "
|
||||
"data folder, or no folder for a license only."));
|
||||
return;
|
||||
}
|
||||
m_entries.removeIf([&](const QString& e) { return e.section('=', 0, 0) == label; });
|
||||
m_entries.append(entry);
|
||||
m_save(m_entries);
|
||||
m_label_edit->clear();
|
||||
m_dir_edit->clear();
|
||||
Scan();
|
||||
}
|
||||
|
||||
void AdditionalContentDialog::Remove() {
|
||||
m_entries.removeOne(m_list->currentItem()->data(Qt::UserRole).toString());
|
||||
m_save(m_entries);
|
||||
Scan();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ constexpr char CONF_LAUNCHER[] = "Launcher";
|
||||
constexpr char CONF_GAME_DIR[] = "game_dir";
|
||||
constexpr char CONF_GAME_DIRS[] = "game_dirs";
|
||||
constexpr char CONF_ADDONS_DIR[] = "addons_dir";
|
||||
constexpr char CONF_ADDCONT[] = "AdditionalContent";
|
||||
constexpr char CONF_GLOBAL[] = "GlobalConfiguration";
|
||||
constexpr char SAVE_DATA_DIR[] = "_SaveData";
|
||||
|
||||
@@ -348,6 +349,13 @@ void ConfigurationListWidget::WriteSettings() {
|
||||
s->remove(CONF_GAME_DIR);
|
||||
s->endGroup();
|
||||
|
||||
s->remove(CONF_ADDCONT);
|
||||
s->beginGroup(CONF_ADDCONT);
|
||||
for (auto it = m_addcont.constBegin(); it != m_addcont.constEnd(); ++it) {
|
||||
s->setValue(it.key(), it.value());
|
||||
}
|
||||
s->endGroup();
|
||||
|
||||
s->remove(CONF_GLOBAL);
|
||||
s->beginGroup(CONF_GLOBAL);
|
||||
m_global_info.WriteSettings(s.get());
|
||||
@@ -395,6 +403,13 @@ void ConfigurationListWidget::ReadSettings() {
|
||||
m_addons_dir = NormalizeGameDirectory(s->value(CONF_ADDONS_DIR).toString());
|
||||
s->endGroup();
|
||||
|
||||
m_addcont.clear();
|
||||
s->beginGroup(CONF_ADDCONT);
|
||||
for (const auto& title_id: s->childKeys()) {
|
||||
m_addcont.insert(title_id, SettingsStringList(s->value(title_id)));
|
||||
}
|
||||
s->endGroup();
|
||||
|
||||
s->beginGroup(CONF_GLOBAL);
|
||||
if (!s->childKeys().isEmpty()) {
|
||||
m_global_info.ReadSettings(s.get());
|
||||
@@ -458,6 +473,7 @@ ConfigurationListWidget::CreateConfiguration(const ConfigurationItem& item) cons
|
||||
}
|
||||
info->host_input_mapping = m_global_info.host_input_mapping;
|
||||
info->addons_dir = m_addons_dir;
|
||||
info->addcont = m_addcont.value(item.GetInfo().title_id.trimmed().toUpper());
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -1273,11 +1289,22 @@ void ConfigurationListWidget::ShowContextMenu(QTreeWidgetItem* witem) {
|
||||
connect(action_addcont, &QAction::triggered, this,
|
||||
[this, item = QPointer<ConfigurationItem>(item)]() {
|
||||
if (item != nullptr) {
|
||||
auto* dialog = new AdditionalContentDialog(item->GetInfo(), m_addons_dir, this);
|
||||
const auto title_id = item->GetInfo().title_id.trimmed().toUpper();
|
||||
auto* dialog = new AdditionalContentDialog(
|
||||
item->GetInfo(), m_addons_dir, m_addcont.value(title_id),
|
||||
[this, title_id](const QStringList& entries) {
|
||||
if (entries.isEmpty()) {
|
||||
m_addcont.remove(title_id);
|
||||
} else {
|
||||
m_addcont.insert(title_id, entries);
|
||||
}
|
||||
WriteSettings();
|
||||
},
|
||||
this);
|
||||
dialog->show();
|
||||
}
|
||||
});
|
||||
action_addcont->setEnabled(item != nullptr);
|
||||
action_addcont->setEnabled(item != nullptr && !item->GetInfo().title_id.trimmed().isEmpty());
|
||||
QAction* action_compress =
|
||||
menu.addAction(tr("Compress to ZAR..."), this, SLOT(compress_game()));
|
||||
QAction* action_remove_save_data =
|
||||
|
||||
@@ -305,6 +305,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
if (!info.addons_dir.isEmpty()) {
|
||||
args << "--addcont-dir" << info.addons_dir;
|
||||
}
|
||||
for (const auto& entry: info.addcont) {
|
||||
args << "--addcont" << entry;
|
||||
}
|
||||
|
||||
const auto patch_plan = Cheats::PlanPath(info.title_id);
|
||||
if (QFileInfo::exists(patch_plan)) {
|
||||
|
||||
Reference in New Issue
Block a user