diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 730a7336cb..66ae7e7bb1 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -71,6 +71,11 @@ std::string patch_engine::get_patch_config_path() #endif } +std::string patch_engine::get_imported_patch_path() +{ + return fs::get_config_dir() + "patches/imported_patch.yml"; +} + static void append_log_message(std::stringstream* log_messages, const std::string& message) { if (log_messages) @@ -461,7 +466,7 @@ void patch_engine::append_global_patches() load(m_map, fs::get_config_dir() + "patches/patch.yml"); // Imported patch.yml - load(m_map, fs::get_config_dir() + "patches/imported_patch.yml"); + load(m_map, get_imported_patch_path()); } void patch_engine::append_title_patches(const std::string& title_id) @@ -729,6 +734,11 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat for (const auto& [hash, container] : patches) { + if (container.patch_info_map.empty()) + { + continue; + } + out << YAML::Newline << YAML::Newline; out << hash << YAML::BeginMap; out << "Patches" << YAML::BeginMap; @@ -793,6 +803,27 @@ bool patch_engine::import_patches(const patch_engine::patch_map& patches, const return false; } +bool patch_engine::remove_patch(const patch_info& info) +{ + patch_engine::patch_map patches; + + if (load(patches, info.source_path)) + { + if (patches.find(info.hash) != patches.end()) + { + auto& container = patches[info.hash]; + + if (container.patch_info_map.find(info.description) != container.patch_info_map.end()) + { + container.patch_info_map.erase(info.description); + return save_patches(patches, info.source_path); + } + } + } + + return false; +} + patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_patches) { enable_legacy_patches = true; // Default to true diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index 3a2956be06..645962bc80 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -74,6 +74,9 @@ public: // Returns the directory in which patch_config.yml is located static std::string get_patch_config_path(); + // Returns the filepath for the imported_patch.yml + static std::string get_imported_patch_path(); + // Load from file and append to specified patches map // Example entry: // @@ -108,6 +111,9 @@ public: // Create or append patches to a file static bool import_patches(const patch_map& patches, const std::string& path, std::stringstream* log_messages = nullptr); + // Remove a patch from a file + static bool remove_patch(const patch_info& info); + // Load patch_config.yml static patch_config_map load_config(bool& enable_legacy_patches); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index 34a5b1ad93..74f56b1f44 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -101,7 +101,7 @@ void patch_manager_dialog::load_patches() } } -static QList find_children_by_data(QTreeWidgetItem* parent, const QList>& criteria) +static QList find_children_by_data(QTreeWidgetItem* parent, const QList>& criteria) { QList list; @@ -203,7 +203,7 @@ void patch_manager_dialog::populate_tree() const QString q_description = QString::fromStdString(description); QString visible_description = q_description; - const auto match_criteria = QList>() << QPair(description_role, q_description) << QPair(persistance_role, true); + const auto match_criteria = QList>() << QPair(description_role, q_description) << QPair(persistance_role, true); // Add counter to leafs if the name already exists due to different hashes of the same game (PPU, SPU, PRX, OVL) if (const auto matches = find_children_by_data(serial_level_item, match_criteria); matches.count() > 0) @@ -436,6 +436,37 @@ void patch_manager_dialog::on_custom_context_menu_requested(const QPoint &pos) { gui::utils::open_dir(info.source_path); }); + + if (info.source_path == patch_engine::get_imported_patch_path()) + { + menu->addSeparator(); + + QAction* remove_patch = new QAction("Remove Patch"); + menu->addAction(remove_patch); + connect(remove_patch, &QAction::triggered, this, [info, this](bool) + { + const auto answer = QMessageBox::question(this, tr("Remove Patch?"), + tr("Do you really want to remove the selected patch?\nThis action is immediate and irreversible!")); + + if (answer != QMessageBox::StandardButton::Yes) + { + return; + } + + if (patch_engine::remove_patch(info)) + { + patch_log.success("Successfully removed patch %s: %s from %s", info.hash, info.description, info.source_path); + refresh(); // Refresh before showing the dialog + QMessageBox::information(this, tr("Success"), tr("The patch was successfully removed!")); + } + else + { + patch_log.error("Could not remove patch %s: %s from %s", info.hash, info.description, info.source_path); + refresh(); // Refresh before showing the dialog + QMessageBox::warning(this, tr("Failure"), tr("The patch could not be removed!")); + } + }); + } } } } @@ -493,7 +524,7 @@ void patch_manager_dialog::dropEvent(QDropEvent* event) return; } - for (const auto drop_path : drop_paths) + for (const QString& drop_path : drop_paths) { const auto path = drop_path.toStdString(); patch_engine::patch_map patches; @@ -505,7 +536,7 @@ void patch_manager_dialog::dropEvent(QDropEvent* event) if (do_import) { - static const std::string imported_patch_yml_path = fs::get_config_dir() + "patches/imported_patch.yml"; + static const std::string imported_patch_yml_path = patch_engine::get_imported_patch_path(); log_message.clear();