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:

committed by
Crimson Hawk

parent
feb3b6ece3
commit
e5a954617b
@@ -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")));
|
||||
|
||||
|
@@ -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),
|
||||
|
@@ -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)},
|
||||
|
@@ -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);
|
||||
|
@@ -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));
|
||||
|
@@ -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())));
|
||||
}
|
||||
|
@@ -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);
|
||||
};
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user