1
0
mirror of https://git.suyu.dev/suyu/suyu synced 2025-09-19 12:37:59 -05:00

Improved Addons Manager

This commit is contained in:
Levi Akatsuki
2024-03-12 04:30:44 +00:00
committed by Crimson Hawk
parent feb3b6ece3
commit e5a954617b
103 changed files with 1588 additions and 1351 deletions

View File

@@ -54,8 +54,8 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
: QWebEngineView(parent), input_subsystem{input_subsystem_},
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
input_interpreter(std::make_unique<InputInterpreter>(system)),
default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
default_profile->settings()} {
default_profile{QWebEngineProfile::defaultProfile()},
global_settings{default_profile->settings()} {
default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
Common::FS::GetSuyuPath(Common::FS::SuyuPath::SuyuDir) / "qtwebengine")));

View File

@@ -284,8 +284,8 @@ struct NullRenderWidget : public RenderWidget {
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_)
: QWidget(parent),
emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, system{system_} {
: QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)},
system{system_} {
setWindowTitle(QStringLiteral("suyu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),

View File

@@ -32,9 +32,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
InputCommon::InputSubsystem* input_subsystem,
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_, bool enable_web_config)
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
this, !system_.IsPoweredOn())},
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry(registry_),
system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
applets_tab{std::make_unique<ConfigureApplets>(system_, nullptr, *builder, this)},
audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},

View File

@@ -293,11 +293,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
InputCommon::InputSubsystem* input_subsystem_,
InputProfiles* profiles_, Core::HID::HIDCore& hid_core_,
bool is_powered_on_, bool debug_)
: QWidget(parent),
ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index{player_index_}, debug{debug_},
is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_),
timeout_timer(std::make_unique<QTimer>()),
poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} {
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()),
player_index{player_index_}, debug{debug_}, is_powered_on{is_powered_on_},
input_subsystem{input_subsystem_}, profiles(profiles_),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
bottom_row{bottom_row_}, hid_core{hid_core_} {
if (player_index == 0) {
auto* emulated_controller_p1 =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);

View File

@@ -44,8 +44,8 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_)
: QDialog(parent),
ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <memory>
#include <utility>
@@ -11,12 +13,19 @@
#include <QString>
#include <QTimer>
#include <QTreeView>
#include <qdesktopservices.h>
#include <qdialog.h>
#include <qdialogbuttonbox.h>
#include <qformlayout.h>
#include <qlabel.h>
#include <qlineedit.h>
#include <qmessagebox.h>
#include <qtreewidget.h>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "suyu/configuration/configure_input.h"
#include "suyu/configuration/configure_per_game_addons.h"
@@ -63,6 +72,16 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
connect(tree_view, &QTreeView::clicked, this, &ConfigurePerGameAddons::OnPatchSelected);
connect(ui->new_btn, &QPushButton::clicked, this, &ConfigurePerGameAddons::OnPatchCreateClick);
connect(ui->edit_btn, &QPushButton::clicked, this, &ConfigurePerGameAddons::OnPatchEditClick);
connect(ui->remove_btn, &QPushButton::clicked, this,
&ConfigurePerGameAddons::OnPatchRemoveClick);
connect(ui->folder_btn, &QPushButton::clicked, this,
&ConfigurePerGameAddons::OnPatchOpenFolder);
}
ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
@@ -119,10 +138,11 @@ void ConfigurePerGameAddons::LoadConfiguration() {
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
patches = pm.GetPatches(update_raw);
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatches(update_raw)) {
for (const auto& patch : patches) {
const auto name = QString::fromStdString(patch.name);
auto* const first_item = new QStandardItem;
@@ -141,3 +161,148 @@ void ConfigurePerGameAddons::LoadConfiguration() {
tree_view->resizeColumnToContents(1);
}
void ConfigurePerGameAddons::ReloadList() {
// Clear all items and selection
item_model->setRowCount(0);
list_items.clear();
selected_patch = std::nullopt;
// Remove the cache to ensure we'll recreate it
Common::FS::RemoveFile(Common::FS::GetSuyuPath(Common::FS::SuyuPath::CacheDir) / "game_list" /
fmt::format("{:016X}.pv.txt", title_id));
// Reload stuff
UISettings::values.is_game_list_reload_pending.exchange(true);
UISettings::values.is_game_list_reload_pending.notify_all();
LoadConfiguration();
ApplyConfiguration();
}
void ConfigurePerGameAddons::OnPatchSelected(const QModelIndex& selectedIndex) {
QModelIndexList indexes = tree_view->selectionModel()->selectedIndexes();
if (indexes.size() == 0) {
// Nothing selected
ui->edit_btn->setEnabled(false);
ui->remove_btn->setEnabled(false);
return;
}
QStandardItemModel* model = (QStandardItemModel*)tree_view->model();
QStandardItem* item = model->itemFromIndex(selectedIndex.siblingAtColumn(0));
std::string patch_name = item->text().toStdString();
selected_patch = std::nullopt;
for (const auto& patch : patches) {
if (patch.name != patch_name)
continue;
if (patch.version != "IPSwitch")
continue;
selected_patch = patch;
}
if (!selected_patch || !selected_patch->file_path) {
// Either patch not found or selected isn't a patch
ui->edit_btn->setEnabled(false);
ui->remove_btn->setEnabled(false);
return;
}
ui->edit_btn->setEnabled(true);
ui->remove_btn->setEnabled(true);
}
void ConfigurePerGameAddons::OnPatchCreateClick(bool checked) {
std::filesystem::path addon_path =
Common::FS::GetSuyuPath(Common::FS::SuyuPath::LoadDir) / fmt::format("{:016X}", title_id);
QDialog dialog(this);
dialog.setWindowTitle(QString::fromStdString("New Patch"));
QFormLayout form(&dialog);
form.addRow(
new QLabel(QString::fromStdString("Enter the name of the patch that will be created")));
QLineEdit* lineEdit = new QLineEdit(&dialog);
form.addRow(QString::fromStdString("Patch Name"), lineEdit);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal,
&dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
if (dialog.exec() == QDialog::Accepted) {
std::filesystem::path addon_root_path = addon_path / lineEdit->text().toStdString();
std::filesystem::path addon_exefs_path = addon_root_path / "exefs";
std::filesystem::path addon_file_path = addon_exefs_path / "patch.pchtxt";
// Create the folders
if (!Common::FS::CreateDir(addon_root_path)) {
LOG_ERROR(Core, "Could not create new addon root path at {}",
addon_root_path.generic_string());
return;
}
if (!Common::FS::CreateDir(addon_exefs_path)) {
LOG_ERROR(Core, "Could not create new addon root path at {}",
addon_root_path.generic_string());
return;
}
// Create the patch file
std::ofstream patch_file(addon_file_path);
patch_file << "# Place your patches below" << std::endl;
patch_file.close();
// Reload everything
ReloadList();
}
}
void ConfigurePerGameAddons::OnPatchEditClick(bool checked) {
if (!selected_patch || !selected_patch->file_path) {
// Either no patch selected or selected patch somehow doesn't have a file?
return;
}
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(selected_patch->file_path.value())));
}
void ConfigurePerGameAddons::OnPatchRemoveClick(bool checked) {
if (!selected_patch || !selected_patch->file_path || !selected_patch->root_path) {
// Either no patch selected or selected patch somehow doesn't have a file?
return;
}
QMessageBox::StandardButton reply;
reply = QMessageBox::question(
this, QString::fromStdString("Remove patch confirmation"),
QString::fromStdString(
"Are you sure you want to remove the patch '%1'? This action is permanent!")
.arg(QString::fromStdString(selected_patch->name)),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) {
return;
}
// Remove the patch then reload
if (!Common::FS::RemoveDirRecursively(selected_patch->root_path.value_or("Invalid Path"))) {
LOG_ERROR(Core, "Could not create new addon root path at {}",
selected_patch->root_path.value_or("Invalid Path"));
}
ReloadList();
}
void ConfigurePerGameAddons::OnPatchOpenFolder(bool checked) {
std::filesystem::path path =
Common::FS::GetSuyuPath(Common::FS::SuyuPath::LoadDir) / fmt::format("{:016X}", title_id);
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path.generic_string())));
}

View File

@@ -7,7 +7,9 @@
#include <vector>
#include <QList>
#include <qtreewidget.h>
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs/vfs_types.h"
namespace Core {
@@ -43,6 +45,7 @@ private:
void RetranslateUI();
void LoadConfiguration();
void ReloadList();
std::unique_ptr<Ui::ConfigurePerGameAddons> ui;
FileSys::VirtualFile file;
@@ -54,5 +57,15 @@ private:
std::vector<QList<QStandardItem*>> list_items;
std::optional<FileSys::Patch> selected_patch;
std::vector<FileSys::Patch> patches;
Core::System& system;
private slots:
void OnPatchSelected(const QModelIndex& selectedIndex);
void OnPatchCreateClick(bool checked = false);
void OnPatchEditClick(bool checked = false);
void OnPatchRemoveClick(bool checked = false);
void OnPatchOpenFolder(bool checked = false);
};

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>482</width>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
@@ -18,21 +18,63 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>280</height>
</rect>
</property>
</widget>
</widget>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>262</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="action_buttons">
<item>
<widget class="QPushButton" name="new_btn">
<property name="text">
<string>New Patch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit_btn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Edit Patch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_btn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove Patch</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="folder_btn">
<property name="text">
<string>Open Folder</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>

View File

@@ -766,8 +766,8 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
Builder::Builder(QWidget* parent_, bool runtime_lock_)
: translations{InitializeTranslations(parent_)},
combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_}, runtime_lock{
runtime_lock_} {}
combobox_translations{ComboboxEnumeration(parent_)}, parent{parent_},
runtime_lock{runtime_lock_} {}
Builder::~Builder() = default;

View File

@@ -234,8 +234,8 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{
system_} {
compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_},
system{system_} {
// We want the game list to manage our lifetime.
setAutoDelete(false);
}

View File

@@ -24,8 +24,8 @@ enum class ConnectionType : u8 { TraversalServer, IP };
DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{
system.GetRoomNetwork()} {
ui(std::make_unique<Ui::DirectConnect>()), system{system_},
room_network{system.GetRoomNetwork()} {
ui->setupUi(this);

View File

@@ -31,9 +31,8 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
std::shared_ptr<Core::AnnounceMultiplayerSession> session,
Core::System& system_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::HostRoom>()),
announce_multiplayer_session(session), system{system_}, room_network{
system.GetRoomNetwork()} {
ui(std::make_unique<Ui::HostRoom>()), announce_multiplayer_session(session), system{system_},
room_network{system.GetRoomNetwork()} {
ui->setupUi(this);
// set up validation for all of the fields

View File

@@ -27,9 +27,8 @@
Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui(std::make_unique<Ui::Lobby>()),
announce_multiplayer_session(session), system{system_}, room_network{
system.GetRoomNetwork()} {
ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session), system{system_},
room_network{system.GetRoomNetwork()} {
ui->setupUi(this);
// setup the watcher for background connections