From 080737fd1f9795a54087d4e275c56c984f970451 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Feb 2023 23:57:23 +0100 Subject: [PATCH] patch_manager: add more dynamic to dynamic patches --- Utilities/bin_patch.cpp | 310 +++++++++++++++++++++---- Utilities/bin_patch.h | 33 ++- rpcs3/rpcs3qt/patch_manager_dialog.cpp | 103 +++++++- rpcs3/rpcs3qt/patch_manager_dialog.ui | 8 +- 4 files changed, 402 insertions(+), 52 deletions(-) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 7058d5f5d2..299b5333c9 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -31,6 +31,23 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](patch_dynamic_type value) + { + switch (value) + { + case patch_dynamic_type::double_range: return "double_range"; + case patch_dynamic_type::double_enum: return "double_enum"; + case patch_dynamic_type::long_range: return "long_range"; + case patch_dynamic_type::long_enum: return "long_enum"; + } + + return unknown; + }); +} + template <> void fmt_class_string::format(std::string& out, u64 arg) { @@ -359,15 +376,160 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st { for (const auto dynamic_value_node : dynamic_values_node) { - if (dynamic_value_node.second.IsScalar()) + const std::string& value_key = dynamic_value_node.first.Scalar(); + + if (const auto yml_type = dynamic_value_node.second.Type(); yml_type != YAML::NodeType::Map) { - const std::string& value_key = dynamic_value_node.first.Scalar(); - info.default_dynamic_values[value_key] = dynamic_value_node.second.as(0.0); + append_log_message(log_messages, fmt::format("Error: Skipping dynamic value %s: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", value_key, yml_type, description, main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; } else { - append_log_message(log_messages, fmt::format("Error: Invalid dynamic value (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); - is_valid = false; + patch_dynamic_value& dynamic_value = info.default_dynamic_values[value_key]; + + if (const auto dynamic_value_type_node = dynamic_value_node.second[patch_key::type]; dynamic_value_type_node && dynamic_value_type_node.IsScalar()) + { + const std::string& str_type = dynamic_value_type_node.Scalar(); + bool is_valid_type = false; + + for (patch_dynamic_type type : { patch_dynamic_type::double_range, patch_dynamic_type::double_enum, patch_dynamic_type::long_range, patch_dynamic_type::long_enum }) + { + if (str_type == fmt::format("%s", type)) + { + dynamic_value.type = type; + is_valid_type = true; + break; + } + } + + if (is_valid_type) + { + const auto get_and_check_dynamic_value = [&](const YAML::Node& node) + { + std::string err; + f64 val{}; + + switch (dynamic_value.type) + { + case patch_dynamic_type::double_range: + case patch_dynamic_type::double_enum: + val = get_yaml_node_value(node, err); + break; + case patch_dynamic_type::long_range: + case patch_dynamic_type::long_enum: + val = get_yaml_node_value(node, err); + break; + } + + if (!err.empty()) + { + append_log_message(log_messages, fmt::format("Error: Invalid data type found in dynamic value: %s (key: %s, location: %s, file: %s)", err, main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + + return val; + }; + + if (const auto dynamic_value_value_node = dynamic_value_node.second[patch_key::value]; dynamic_value_value_node && dynamic_value_value_node.IsScalar()) + { + dynamic_value.value = get_and_check_dynamic_value(dynamic_value_value_node); + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid or missing dynamic value (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + + switch (dynamic_value.type) + { + case patch_dynamic_type::double_range: + case patch_dynamic_type::long_range: + { + if (const auto dynamic_value_min_node = dynamic_value_node.second[patch_key::min]; dynamic_value_min_node && dynamic_value_min_node.IsScalar()) + { + dynamic_value.min = get_and_check_dynamic_value(dynamic_value_min_node); + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid or missing dynamic min (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + + if (const auto dynamic_value_max_node = dynamic_value_node.second[patch_key::max]; dynamic_value_max_node && dynamic_value_max_node.IsScalar()) + { + dynamic_value.max = get_and_check_dynamic_value(dynamic_value_max_node); + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid or missing dynamic max (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + + if (dynamic_value.min >= dynamic_value.max) + { + append_log_message(log_messages, fmt::format("Error: dynamic max has to be larger than dynamic min (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + + if (dynamic_value.value < dynamic_value.min || dynamic_value.value > dynamic_value.max) + { + append_log_message(log_messages, fmt::format("Error: dynamic value out of range (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + break; + } + case patch_dynamic_type::double_enum: + case patch_dynamic_type::long_enum: + { + if (const auto dynamic_value_allowed_values_node = dynamic_value_node.second[patch_key::allowed_values]; dynamic_value_allowed_values_node && dynamic_value_allowed_values_node.IsSequence()) + { + dynamic_value.allowed_values.clear(); + + for (const auto allowed_value : dynamic_value_allowed_values_node) + { + if (allowed_value && allowed_value.IsScalar()) + { + dynamic_value.allowed_values.push_back(get_and_check_dynamic_value(allowed_value)); + } + else + { + append_log_message(log_messages, fmt::format("Error: Skipping dynamic allowed value (patch: %s, key: %s, location: %s, file: %s)", description, main_key, get_yaml_node_location(allowed_value), path), &patch_log.error); + is_valid = false; + } + } + + if (dynamic_value.allowed_values.size() < 2) + { + append_log_message(log_messages, fmt::format("Error: Dynamic allowed values need at least 2 entries (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_allowed_values_node), path), &patch_log.error); + is_valid = false; + } + + if (std::none_of(dynamic_value.allowed_values.begin(), dynamic_value.allowed_values.end(), [&dynamic_value](const f64& val){ return val == dynamic_value.value; })) + { + append_log_message(log_messages, fmt::format("Error: Dynamic value was not found in allowed values (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_allowed_values_node), path), &patch_log.error); + is_valid = false; + } + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid or missing dynamic allowed values (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } + break; + } + } + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid dynamic type (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_type_node), path), &patch_log.error); + is_valid = false; + } + } + else + { + append_log_message(log_messages, fmt::format("Error: Invalid or missing dynamic type (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(dynamic_value_node), path), &patch_log.error); + is_valid = false; + } } } } @@ -496,7 +658,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie p_data.original_value = value_node.Scalar(); const bool is_dynamic_value = info.default_dynamic_values.contains(p_data.original_value); - const f64 dynamic_value = is_dynamic_value ? ::at32(info.default_dynamic_values, p_data.original_value) : 0.0; + const patch_dynamic_value dynamic_value = is_dynamic_value ? ::at32(info.default_dynamic_values, p_data.original_value) : patch_dynamic_value{}; std::string error_message; @@ -512,12 +674,12 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie case patch_type::bef64: case patch_type::lef64: { - p_data.value.double_value = is_dynamic_value ? dynamic_value : get_yaml_node_value(value_node, error_message); + p_data.value.double_value = is_dynamic_value ? dynamic_value.value : get_yaml_node_value(value_node, error_message); break; } default: { - p_data.value.long_value = is_dynamic_value ? static_cast(dynamic_value) : get_yaml_node_value(value_node, error_message); + p_data.value.long_value = is_dynamic_value ? static_cast(dynamic_value.value) : get_yaml_node_value(value_node, error_message); if (error_message.find("bad conversion") != std::string::npos) { @@ -601,11 +763,44 @@ void unmap_vm_area(std::shared_ptr& ptr) } // Returns old 'applied' size -static usz apply_modification(std::basic_string& applied, const patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr) +static usz apply_modification(std::basic_string& applied, patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr) { const usz old_applied_size = applied.size(); - for (const auto& p : patch.data_list) + // Update dynamic values + for (const auto& [key, dynamic_value] : patch.actual_dynamic_values) + { + for (usz i = 0; i < patch.data_list.size(); i++) + { + patch_engine::patch_data& p = ::at32(patch.data_list, i); + + if (p.original_value == key) + { + switch (p.type) + { + case patch_type::bef32: + case patch_type::lef32: + case patch_type::bef64: + case patch_type::lef64: + { + p.value.double_value = dynamic_value.value; + patch_log.notice("Using dynamic value (key='%s', value=%f, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s')", + key, p.value.double_value, i, patch.hash, patch.description, patch.author, patch.patch_version, patch.version); + break; + } + default: + { + p.value.long_value = dynamic_value.value; + patch_log.notice("Using dynamic value (key='%s', value=0x%x=%d, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s')", + key, p.value.long_value, p.value.long_value, i, patch.hash, patch.description, patch.author, patch.patch_version, patch.version); + break; + } + } + } + } + } + + for (const patch_engine::patch_data& p : patch.data_list) { if (p.type != patch_type::alloc) continue; @@ -679,7 +874,7 @@ static usz apply_modification(std::basic_string& applied, const patch_engin u32 relocate_instructions_at = 0; - for (const auto& p : patch.data_list) + for (const patch_engine::patch_data& p : patch.data_list) { u32 offset = p.offset; @@ -982,10 +1177,10 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 const auto& app_version = Emu.GetAppVersion(); // Different containers in order to seperate the patches - std::vector patches_for_this_serial_and_this_version; - std::vector patches_for_this_serial_and_all_versions; - std::vector patches_for_all_serials_and_this_version; - std::vector patches_for_all_serials_and_all_versions; + std::vector> patches_for_this_serial_and_this_version; + std::vector> patches_for_this_serial_and_all_versions; + std::vector> patches_for_all_serials_and_this_version; + std::vector> patches_for_all_serials_and_all_versions; // Sort patches into different vectors based on their serial and version for (const auto& [description, patch] : container.patch_info_map) @@ -1033,27 +1228,43 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 const patch_config_values& config_values = ::at32(app_versions, found_app_version); + // Check if this patch is enabled if (config_values.enabled) { - // This patch is enabled + // Make copy of this patch + std::shared_ptr p_ptr = std::make_shared(patch); + + // Move dynamic values to special container for readability + p_ptr->actual_dynamic_values = p_ptr->default_dynamic_values; + + // Update dynamic values + for (auto& [key, dynamic_value] : config_values.dynamic_values) + { + if (p_ptr->actual_dynamic_values.contains(key)) + { + ::at32(p_ptr->actual_dynamic_values, key).value = dynamic_value.value; + } + } + + // Sort the patch by priority if (is_all_serials) { if (is_all_versions) { - patches_for_all_serials_and_all_versions.emplace_back(&patch); + patches_for_all_serials_and_all_versions.emplace_back(p_ptr); } else { - patches_for_all_serials_and_this_version.emplace_back(&patch); + patches_for_all_serials_and_this_version.emplace_back(p_ptr); } } else if (is_all_versions) { - patches_for_this_serial_and_all_versions.emplace_back(&patch); + patches_for_this_serial_and_all_versions.emplace_back(p_ptr); } else { - patches_for_this_serial_and_this_version.emplace_back(&patch); + patches_for_this_serial_and_this_version.emplace_back(p_ptr); } break; @@ -1061,17 +1272,6 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 } } - // Apply modifications sequentially - auto apply_func = [&](const patch_info& patch) - { - const usz old_size = apply_modification(applied_total, patch, dst, filesz, min_addr); - - if (applied_total.size() != old_size) - { - patch_log.success("Applied patch (hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') (<- %u)", patch.hash, patch.description, patch.author, patch.patch_version, patch.version, applied_total.size() - old_size); - } - }; - // Sort specific patches after global patches // So they will determine the end results const auto patch_super_list = @@ -1091,19 +1291,26 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 { if (!m_applied_groups.insert(patch->patch_group).second) { - patch = nullptr; + patch.reset(); } } } } + // Apply modifications sequentially for (auto patch_list : patch_super_list) { - for (const patch_info* patch : *patch_list) + for (const std::shared_ptr& patch : *patch_list) { if (patch) { - apply_func(*patch); + const usz old_size = apply_modification(applied_total, *patch, dst, filesz, min_addr); + + if (applied_total.size() != old_size) + { + patch_log.success("Applied patch (hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') (<- %u)", + patch->hash, patch->description, patch->author, patch->patch_version, patch->version, applied_total.size() - old_size); + } } } } @@ -1202,9 +1409,9 @@ void patch_engine::save_config(const patch_map& patches_map) { out << patch_key::dynamic_values << YAML::BeginMap; - for (const auto& [name, value] : config_values.dynamic_values) + for (const auto& [name, dynamic_value] : config_values.dynamic_values) { - out << name << value; + out << name << dynamic_value.value; } out << YAML::EndMap; @@ -1358,9 +1565,33 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat { out << patch_key::dynamic_values << YAML::BeginMap; - for (const auto& [key, value] : info.default_dynamic_values) + for (const auto& [key, dynamic_value] : info.default_dynamic_values) { - out << key << value; + out << key << YAML::BeginMap; + out << patch_key::type << fmt::format("%s", dynamic_value.type); + out << patch_key::value << dynamic_value.value; + + switch (dynamic_value.type) + { + case patch_dynamic_type::double_range: + case patch_dynamic_type::long_range: + out << patch_key::min << dynamic_value.min; + out << patch_key::max << dynamic_value.max; + break; + case patch_dynamic_type::double_enum: + case patch_dynamic_type::long_enum: + out << patch_key::allowed_values << YAML::BeginSeq; + + for (const auto& allowed_value : dynamic_value.allowed_values) + { + out << allowed_value; + } + + out << YAML::EndSeq; + break; + } + + out << YAML::EndMap; } out << YAML::EndMap; @@ -1505,7 +1736,8 @@ patch_engine::patch_map patch_engine::load_config() { for (const auto dynamic_value_node : dynamic_values_node) { - config_values.dynamic_values[dynamic_value_node.first.Scalar()] = dynamic_value_node.second.as(0.0); + patch_dynamic_value& dynamic_value = config_values.dynamic_values[dynamic_value_node.first.Scalar()]; + dynamic_value.value = dynamic_value_node.second.as(0.0); } } } diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index f47afd11cc..1834621590 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -20,6 +20,11 @@ namespace patch_key static const std::string version = "Version"; static const std::string enabled = "Enabled"; static const std::string dynamic_values = "Dynamic Values"; + static const std::string value = "Value"; + static const std::string type = "Type"; + static const std::string min = "Min"; + static const std::string max = "Max"; + static const std::string allowed_values = "Allowed Values"; } inline static const std::string patch_engine_version = "1.2"; @@ -49,6 +54,14 @@ enum class patch_type utf8, // Text of string (not null-terminated automatically) }; +enum class patch_dynamic_type +{ + double_range, + double_enum, + long_range, + long_enum +}; + class patch_engine { public: @@ -65,11 +78,26 @@ public: mutable u32 alloc_addr = 0; // Used to save optional allocation address (if occured) }; + struct patch_dynamic_value + { + f64 value{}; + f64 min{}; + f64 max{}; + patch_dynamic_type type{}; + std::vector allowed_values; + + bool operator==(const patch_dynamic_value& other) const + { + return value == other.value && min == other.min && max == other.max && type == other.type && allowed_values == other.allowed_values; + } + }; + struct patch_config_values { bool enabled{}; - std::map dynamic_values; + std::map dynamic_values; }; + using patch_app_versions = std::unordered_map; using patch_serials = std::unordered_map; using patch_titles = std::unordered_map; @@ -85,11 +113,12 @@ public: std::string author{}; std::string notes{}; std::string source_path{}; - std::map default_dynamic_values; + std::map default_dynamic_values; // Redundant information for accessibility (see patch_container) std::string hash{}; std::string version{}; + std::map actual_dynamic_values; }; struct patch_container diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index d878852880..ff24f97e2d 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -55,6 +55,8 @@ enum node_level : int patch_level }; +Q_DECLARE_METATYPE(patch_engine::patch_dynamic_value); + patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_settings, std::unordered_map> games, const std::string& title_id, const std::string& version, QWidget* parent) : QDialog(parent) , m_gui_settings(std::move(gui_settings)) @@ -77,13 +79,28 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set m_downloader = new downloader(this); + ui->dynamic_combo_box->setEnabled(false); + ui->dynamic_combo_box->setVisible(false); + ui->dynamic_spin_box->setEnabled(false); + ui->dynamic_spin_box->setVisible(false); + ui->dynamic_double_spin_box->setEnabled(false); + ui->dynamic_double_spin_box->setVisible(false); + // Create connects connect(ui->patch_filter, &QLineEdit::textChanged, this, &patch_manager_dialog::filter_patches); connect(ui->patch_tree, &QTreeWidget::currentItemChanged, this, &patch_manager_dialog::handle_item_selected); connect(ui->patch_tree, &QTreeWidget::itemChanged, this, &patch_manager_dialog::handle_item_changed); connect(ui->patch_tree, &QTreeWidget::customContextMenuRequested, this, &patch_manager_dialog::handle_custom_context_menu_requested); connect(ui->cb_owned_games_only, &QCheckBox::stateChanged, this, &patch_manager_dialog::handle_show_owned_games_only); - connect(ui->dynamic_value_box, QOverload::of(&QDoubleSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed); + connect(ui->dynamic_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + { + if (index >= 0) + { + handle_dynamic_value_changed(ui->dynamic_combo_box->itemData(index).toDouble()); + } + }); + connect(ui->dynamic_spin_box, QOverload::of(&QSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed); + connect(ui->dynamic_double_spin_box, QOverload::of(&QDoubleSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { @@ -307,9 +324,16 @@ void patch_manager_dialog::populate_tree() QMap q_dynamic_values; - for (const auto& [key, value] : patch.default_dynamic_values) + for (const auto& [key, default_dynamic_value] : patch.default_dynamic_values) { - q_dynamic_values[QString::fromStdString(key)] = config_values.dynamic_values.contains(key) ? config_values.dynamic_values.at(key) : value; + patch_engine::patch_dynamic_value dynamic_value = default_dynamic_value; + + if (config_values.dynamic_values.contains(key)) + { + dynamic_value.value = config_values.dynamic_values.at(key).value; + } + + q_dynamic_values[QString::fromStdString(key)] = QVariant::fromValue(dynamic_value); } QTreeWidgetItem* patch_level_item = new QTreeWidgetItem(); @@ -503,10 +527,63 @@ void patch_manager_dialog::update_patch_info(const patch_manager_dialog::gui_pat ui->label_title->setText(info.title); ui->label_app_version->setText(info.app_version); - // TODO: support more than one value in the future - ui->dynamic_label->setText(info.dynamic_values.empty() ? tr("N/A") : info.dynamic_values.firstKey()); - ui->dynamic_value_box->setValue(info.dynamic_values.empty() ? 0.0 : info.dynamic_values.first().toDouble()); - ui->dynamic_value_box->setEnabled(!info.dynamic_values.empty()); + // TODO: support more than one dynamic value in the future + + if (info.dynamic_values.empty()) + { + ui->dynamic_label->setText(tr("N/A")); + ui->dynamic_combo_box->setEnabled(false); + ui->dynamic_combo_box->setVisible(false); + ui->dynamic_spin_box->setEnabled(false); + ui->dynamic_spin_box->setVisible(false); + ui->dynamic_double_spin_box->setEnabled(false); + ui->dynamic_double_spin_box->setVisible(false); + return; + } + + ui->dynamic_label->setText(info.dynamic_values.firstKey()); + + const QVariant& variant = info.dynamic_values.first(); + ensure(variant.canConvert()); + + const patch_engine::patch_dynamic_value dynamic_value = variant.value(); + + switch (dynamic_value.type) + { + case patch_dynamic_type::double_range: + ui->dynamic_double_spin_box->blockSignals(true); + ui->dynamic_double_spin_box->setRange(dynamic_value.min, dynamic_value.max); + ui->dynamic_double_spin_box->setValue(dynamic_value.value); + ui->dynamic_double_spin_box->setEnabled(true); + ui->dynamic_double_spin_box->setVisible(true); + ui->dynamic_double_spin_box->blockSignals(false); + break; + case patch_dynamic_type::long_range: + ui->dynamic_spin_box->blockSignals(true); + ui->dynamic_spin_box->setRange(dynamic_value.min, dynamic_value.max); + ui->dynamic_spin_box->setValue(dynamic_value.value); + ui->dynamic_spin_box->setEnabled(true); + ui->dynamic_spin_box->setVisible(true); + ui->dynamic_spin_box->blockSignals(false); + break; + case patch_dynamic_type::double_enum: + case patch_dynamic_type::long_enum: + ui->dynamic_combo_box->blockSignals(true); + ui->dynamic_combo_box->clear(); + for (const f64& allowed_value : dynamic_value.allowed_values) + { + ui->dynamic_combo_box->addItem(QString::number(allowed_value), allowed_value); + + if (allowed_value == dynamic_value.value) + { + ui->dynamic_combo_box->setCurrentIndex(ui->dynamic_combo_box->findText(QString::number(allowed_value))); + } + } + ui->dynamic_combo_box->setEnabled(true); + ui->dynamic_combo_box->setVisible(true); + ui->dynamic_combo_box->blockSignals(false); + break; + } } void patch_manager_dialog::handle_item_selected(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/) @@ -645,7 +722,13 @@ void patch_manager_dialog::handle_dynamic_value_changed(double value) return; } - q_dynamic_values[key] = value; + QVariant& variant = q_dynamic_values[key]; + ensure(variant.canConvert()); + + patch_engine::patch_dynamic_value dynamic_value = variant.value(); + dynamic_value.value = value; + variant = QVariant::fromValue(dynamic_value); + item->setData(0, dynamic_values_role, q_dynamic_values); // Update the dynamic value of the patch for this item @@ -666,9 +749,9 @@ void patch_manager_dialog::handle_dynamic_value_changed(double value) for (const QString& q_key : q_dynamic_values.keys()) { - if (const std::string s_key = q_key.toStdString(); patch.default_dynamic_values.contains(s_key)) + if (const std::string s_key = q_key.toStdString(); key == q_key && patch.default_dynamic_values.contains(s_key)) { - dynamic_values[s_key] = q_dynamic_values[q_key].toDouble(); + dynamic_values[s_key].value = value; } } } diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.ui b/rpcs3/rpcs3qt/patch_manager_dialog.ui index a8d9013df1..ccbcc4df8b 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.ui +++ b/rpcs3/rpcs3qt/patch_manager_dialog.ui @@ -272,7 +272,13 @@ - + + + + + + + -1000000.000000000000000