diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 778fcce6ce..be391bea11 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1847,7 +1847,7 @@ void MainWindow::ShowRiivolutionBootWidget(const UICommon::GameFile& game) auto& disc = std::get(boot_params->parameters); RiivolutionBootWidget w(disc.volume->GetGameID(), disc.volume->GetRevision(), - disc.volume->GetDiscNumber(), this); + disc.volume->GetDiscNumber(), game.GetFilePath(), this); w.exec(); if (!w.ShouldBoot()) return; diff --git a/Source/Core/DolphinQt/RiivolutionBootWidget.cpp b/Source/Core/DolphinQt/RiivolutionBootWidget.cpp index ecb4ac078a..c70df8e770 100644 --- a/Source/Core/DolphinQt/RiivolutionBootWidget.cpp +++ b/Source/Core/DolphinQt/RiivolutionBootWidget.cpp @@ -23,6 +23,7 @@ #include "Common/FileSearch.h" #include "Common/FileUtil.h" +#include "DiscIO/GameModDescriptor.h" #include "DiscIO/RiivolutionParser.h" #include "DiscIO/RiivolutionPatcher.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" @@ -38,8 +39,10 @@ struct GuiRiivolutionPatchIndex Q_DECLARE_METATYPE(GuiRiivolutionPatchIndex); RiivolutionBootWidget::RiivolutionBootWidget(std::string game_id, std::optional revision, - std::optional disc, QWidget* parent) - : QDialog(parent), m_game_id(std::move(game_id)), m_revision(revision), m_disc_number(disc) + std::optional disc, std::string base_game_path, + QWidget* parent) + : QDialog(parent), m_game_id(std::move(game_id)), m_revision(revision), m_disc_number(disc), + m_base_game_path(std::move(base_game_path)) { setWindowTitle(tr("Start with Riivolution Patches")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -57,6 +60,7 @@ void RiivolutionBootWidget::CreateWidgets() auto* open_xml_button = new QPushButton(tr("Open Riivolution XML...")); auto* boot_game_button = new QPushButton(tr("Start")); boot_game_button->setDefault(true); + auto* save_preset_button = new QPushButton(tr("Save as Preset...")); auto* group_box = new QGroupBox(); auto* scroll_area = new QScrollArea(); @@ -71,6 +75,7 @@ void RiivolutionBootWidget::CreateWidgets() auto* button_layout = new QHBoxLayout(); button_layout->addStretch(); button_layout->addWidget(open_xml_button, 0, Qt::AlignRight); + button_layout->addWidget(save_preset_button, 0, Qt::AlignRight); button_layout->addWidget(boot_game_button, 0, Qt::AlignRight); auto* layout = new QVBoxLayout(); @@ -80,6 +85,7 @@ void RiivolutionBootWidget::CreateWidgets() connect(open_xml_button, &QPushButton::clicked, this, &RiivolutionBootWidget::OpenXML); connect(boot_game_button, &QPushButton::clicked, this, &RiivolutionBootWidget::BootGame); + connect(save_preset_button, &QPushButton::clicked, this, &RiivolutionBootWidget::SaveAsPreset); } void RiivolutionBootWidget::LoadMatchingXMLs() @@ -144,13 +150,14 @@ void RiivolutionBootWidget::OpenXML() } } -void RiivolutionBootWidget::MakeGUIForParsedFile(const std::string& path, std::string root, +void RiivolutionBootWidget::MakeGUIForParsedFile(std::string path, std::string root, DiscIO::Riivolution::Disc input_disc) { const size_t disc_index = m_discs.size(); - const auto& disc = m_discs.emplace_back(DiscWithRoot{std::move(input_disc), std::move(root)}); + const auto& disc = + m_discs.emplace_back(DiscWithRoot{std::move(input_disc), std::move(root), std::move(path)}); - auto* disc_box = new QGroupBox(QFileInfo(QString::fromStdString(path)).fileName()); + auto* disc_box = new QGroupBox(QFileInfo(QString::fromStdString(disc.path)).fileName()); auto* disc_layout = new QVBoxLayout(); disc_box->setLayout(disc_layout); @@ -279,3 +286,52 @@ void RiivolutionBootWidget::BootGame() m_should_boot = true; close(); } + +void RiivolutionBootWidget::SaveAsPreset() +{ + DiscIO::GameModDescriptor descriptor; + descriptor.base_file = m_base_game_path; + + DiscIO::GameModDescriptorRiivolution riivolution_descriptor; + for (const auto& disc : m_discs) + { + // filter out XMLs that don't actually contribute to the preset + auto patches = disc.disc.GeneratePatches(m_game_id); + if (patches.empty()) + continue; + + auto& descriptor_patch = riivolution_descriptor.patches.emplace_back(); + descriptor_patch.xml = disc.path; + descriptor_patch.root = disc.root; + for (const auto& section : disc.disc.m_sections) + { + for (const auto& option : section.m_options) + { + auto& descriptor_option = descriptor_patch.options.emplace_back(); + descriptor_option.section_name = section.m_name; + if (!option.m_id.empty()) + descriptor_option.option_id = option.m_id; + else + descriptor_option.option_name = option.m_name; + descriptor_option.choice = option.m_selected_choice; + } + } + } + + if (!riivolution_descriptor.patches.empty()) + descriptor.riivolution = std::move(riivolution_descriptor); + + QDir dir = QFileInfo(QString::fromStdString(m_base_game_path)).dir(); + QString target_path = QFileDialog::getSaveFileName(this, tr("Save Preset"), dir.absolutePath(), + QStringLiteral("%1 (*.json);;%2 (*)") + .arg(tr("Dolphin Game Mod Preset")) + .arg(tr("All Files"))); + if (target_path.isEmpty()) + return; + + descriptor.display_name = QFileInfo(target_path).fileName().toStdString(); + auto dot = descriptor.display_name.rfind('.'); + if (dot != std::string::npos) + descriptor.display_name = descriptor.display_name.substr(0, dot); + DiscIO::WriteGameModDescriptorFile(target_path.toStdString(), descriptor, true); +} diff --git a/Source/Core/DolphinQt/RiivolutionBootWidget.h b/Source/Core/DolphinQt/RiivolutionBootWidget.h index 5200ca2151..1318241a11 100644 --- a/Source/Core/DolphinQt/RiivolutionBootWidget.h +++ b/Source/Core/DolphinQt/RiivolutionBootWidget.h @@ -19,7 +19,8 @@ class RiivolutionBootWidget : public QDialog Q_OBJECT public: explicit RiivolutionBootWidget(std::string game_id, std::optional revision, - std::optional disc, QWidget* parent = nullptr); + std::optional disc, std::string base_game_path, + QWidget* parent = nullptr); ~RiivolutionBootWidget(); bool ShouldBoot() const { return m_should_boot; } @@ -30,21 +31,24 @@ private: void LoadMatchingXMLs(); void OpenXML(); - void MakeGUIForParsedFile(const std::string& path, std::string root, + void MakeGUIForParsedFile(std::string path, std::string root, DiscIO::Riivolution::Disc input_disc); std::optional LoadConfigXML(const std::string& root_directory); void SaveConfigXMLs(); void BootGame(); + void SaveAsPreset(); std::string m_game_id; std::optional m_revision; std::optional m_disc_number; + std::string m_base_game_path; bool m_should_boot = false; struct DiscWithRoot { DiscIO::Riivolution::Disc disc; std::string root; + std::string path; }; std::vector m_discs; std::vector m_patches;