From 72e9bebf6178079d5766615402f4ebd5feee8e51 Mon Sep 17 00:00:00 2001 From: crueter Date: Thu, 5 Feb 2026 13:59:37 -0500 Subject: [PATCH] Add to per game addons tab Signed-off-by: crueter --- src/frontend_common/mod_manager.cpp | 7 ++-- src/qt_common/util/mod.cpp | 41 +++++++++++++++++-- src/qt_common/util/mod.h | 4 +- .../configure_per_game_addons.cpp | 35 +++++++++++++++- .../configuration/configure_per_game_addons.h | 4 ++ .../configure_per_game_addons.ui | 18 +++++++- 6 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/frontend_common/mod_manager.cpp b/src/frontend_common/mod_manager.cpp index 89ce4dfabe..316d10a0b4 100644 --- a/src/frontend_common/mod_manager.cpp +++ b/src/frontend_common/mod_manager.cpp @@ -49,10 +49,9 @@ bool InstallMod(const std::filesystem::path& path, const u64 program_id, const b // now copy try { - if (copy) - std::filesystem::copy(path, mod_dir, std::filesystem::copy_options::recursive); - else - std::filesystem::rename(path, mod_dir); + std::filesystem::copy(path, mod_dir, std::filesystem::copy_options::recursive); + if (!copy) + std::filesystem::remove_all(path); } catch (std::exception& e) { LOG_ERROR(Frontend, "Mod install failed with message {}", e.what()); return false; diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index d1e9f874e5..18c30c01fd 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -1,4 +1,5 @@ #include +#include #include "frontend_common/mod_manager.h" #include "mod.h" #include "qt_common/abstract/frontend.h" @@ -11,16 +12,18 @@ QString GetModFolder(const QString& root, const QString& fallbackName) { auto std_path = FrontendCommon::GetModFolder(std_root); QString default_name; - if (std_path) + if (!fallbackName.isEmpty()) + default_name = fallbackName; + else if (std_path) default_name = QString::fromStdString(std_path->filename()); - else if (fallbackName.isEmpty()) - default_name = root.split(QLatin1Char('/')).last(); else - default_name = fallbackName; + default_name = root.split(QLatin1Char('/')).last(); QString name = QtCommon::Frontend::GetTextInput( tr("Mod Name"), tr("What should this mod be called?"), default_name); + qDebug() << "Naming mod:" << name; + // if std_path is empty, frontend_common could not determine mod type and/or name. // so we have to prompt the user and set up the structure ourselves if (!std_path) { @@ -63,6 +66,7 @@ QString GetModFolder(const QString& root, const QString& fallbackName) { std_path = mod_dir; + // ... and copy everything from the root to the temp dir for (const auto& entry : fs::directory_iterator(root.toStdString())) { const auto target = tmp / entry.path().filename(); @@ -70,8 +74,15 @@ QString GetModFolder(const QString& root, const QString& fallbackName) { fs::copy(entry.path(), target, fs::copy_options::recursive | fs::copy_options::overwrite_existing); } + } else { + // Rename the existing mod folder. + const auto new_path = std_path->parent_path() / name.toStdString(); + fs::rename(std_path.value(), new_path); + std_path = new_path; } + qDebug() << "Mod path" << std_path->string(); + return QString::fromStdString(std_path->string()); } @@ -81,4 +92,26 @@ bool InstallMod(const QString& path, const QString& fallbackName, const u64 prog return FrontendCommon::InstallMod(target.toStdString(), program_id, copy); } +bool InstallModFromZip(const QString& path, const u64 program_id) { + namespace fs = std::filesystem; + fs::path tmp{fs::temp_directory_path() / "eden" / "unzip_mod"}; + + fs::remove_all(tmp); + if (!fs::create_directories(tmp)) + return false; + + QString qCacheDir = QString::fromStdString(tmp.string()); + + QFile zip{path}; + + // TODO(crueter): use QtCompress + QStringList result = JlCompress::extractDir(&zip, qCacheDir); + if (result.isEmpty()) + return false; + + const auto fallback = fs::path{path.toStdString()}.stem(); + + return InstallMod(qCacheDir, QString::fromStdString(fallback.string()), program_id, false); +} + } // namespace QtCommon::Mod diff --git a/src/qt_common/util/mod.h b/src/qt_common/util/mod.h index dc5c6a57dc..8039331417 100644 --- a/src/qt_common/util/mod.h +++ b/src/qt_common/util/mod.h @@ -7,6 +7,8 @@ namespace QtCommon::Mod { QString GetModFolder(const QString &root, const QString &fallbackName); -bool InstallMod(const QString &path, const QString &fallbackName, const u64 program_id, const bool copy); +bool InstallMod(const QString &path, const QString &fallbackName, const u64 program_id, const bool copy = true); + +bool InstallModFromZip(const QString &path, const u64 program_id); } diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index ee2db55a5d..68e5b4e7f2 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -19,8 +19,9 @@ #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 "qt_common/abstract/frontend.h" +#include "qt_common/util/mod.h" #include "ui_configure_per_game_addons.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_per_game_addons.h" @@ -66,6 +67,9 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p connect(item_model, &QStandardItemModel::itemChanged, [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + + connect(ui->folder, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModFolder); + connect(ui->zip, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModZip); } ConfigurePerGameAddons::~ConfigurePerGameAddons() = default; @@ -99,6 +103,35 @@ void ConfigurePerGameAddons::SetTitleId(u64 id) { this->title_id = id; } +void ConfigurePerGameAddons::InstallModFolder() { + const auto path = QtCommon::Frontend::GetExistingDirectory(tr("Mod Folder")); + if (path.isEmpty()) { + return; + } + + // TODO: Pending refresh game list + if (QtCommon::Mod::InstallMod(path, {}, title_id)) { + QtCommon::Frontend::Information(tr("Mod Installed"), tr("Mod was successfully installed.")); + LoadConfiguration(); + } else { + QtCommon::Frontend::Critical(tr("Mod Install Failed"), tr("Mod install was unsuccessful. Check the log for details.")); + } +} + +void ConfigurePerGameAddons::InstallModZip() { + const auto path = QtCommon::Frontend::GetOpenFileName(tr("Zipped Mod Location"), {}, tr("Zipped Archives (*.zip)")); + if (path.isEmpty()) { + return; + } + + if (QtCommon::Mod::InstallModFromZip(path, title_id)) { + QtCommon::Frontend::Information(tr("Mod Installed"), tr("Mod was successfully installed.")); + LoadConfiguration(); + } else { + QtCommon::Frontend::Critical(tr("Mod Install Failed"), tr("Mod install was unsuccessful. Check the log for details.")); + } +} + void ConfigurePerGameAddons::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 32dc5dde62..1813d1660e 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -7,6 +7,7 @@ #include #include +#include #include "core/file_sys/vfs/vfs_types.h" @@ -38,6 +39,9 @@ public: void SetTitleId(u64 id); +public slots: + void InstallModFolder(); + void InstallModZip(); private: void changeEvent(QEvent* event) override; void RetranslateUI(); diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui index f9cf6f2c31..632f9d422b 100644 --- a/src/yuzu/configuration/configure_per_game_addons.ui +++ b/src/yuzu/configuration/configure_per_game_addons.ui @@ -17,7 +17,21 @@ Add-Ons - + + + + Import Mod from ZIP + + + + + + + Import Mod from Folder + + + + true @@ -28,7 +42,7 @@ 0 0 380 - 280 + 249