diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 7404618f1c..7058d5f5d2 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -96,10 +96,17 @@ std::string patch_engine::get_imported_patch_path() return get_patches_path() + "imported_patch.yml"; } -static void append_log_message(std::stringstream* log_messages, const std::string& message) +static void append_log_message(std::stringstream* log_messages, const std::string& message, const logs::message* channel = nullptr) { - if (log_messages) + if (channel) + { + channel->operator()("%s", message); + } + + if (log_messages && !message.empty()) + { *log_messages << message << std::endl; + } }; bool patch_engine::load(patch_map& patches_map, const std::string& path, std::string content, bool importing, std::stringstream* log_messages) @@ -144,8 +151,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (version != patch_engine_version) { - append_log_message(log_messages, fmt::format("Error: File version %s does not match patch engine target version %s (location: %s, file: %s)", version, patch_engine_version, get_yaml_node_location(version_node), path)); - patch_log.error("File version %s does not match patch engine target version %s (location: %s, file: %s)", version, patch_engine_version, get_yaml_node_location(version_node), path); + append_log_message(log_messages, fmt::format("Error: File version %s does not match patch engine target version %s (location: %s, file: %s)", version, patch_engine_version, get_yaml_node_location(version_node), path), &patch_log.error); return false; } @@ -154,8 +160,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st } else { - append_log_message(log_messages, fmt::format("Error: No '%s' entry found. Patch engine version = %s (file: %s)", patch_key::version, patch_engine_version, path)); - patch_log.error("No '%s' entry found. Patch engine version = %s (file: %s)", patch_key::version, patch_engine_version, path); + append_log_message(log_messages, fmt::format("Error: No '%s' entry found. Patch engine version = %s (file: %s)", patch_key::version, patch_engine_version, path), &patch_log.error); return false; } @@ -168,16 +173,14 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (const auto yml_type = pair.second.Type(); yml_type != YAML::NodeType::Map) { - append_log_message(log_messages, fmt::format("Error: Skipping key %s: expected Map, found %s (location: %s)", main_key, yml_type, get_yaml_node_location(pair.second))); - patch_log.error("Skipping key %s: expected Map, found %s (location: %s, file: %s)", main_key, yml_type, get_yaml_node_location(pair.second), path); + append_log_message(log_messages, fmt::format("Error: Skipping key %s: expected Map, found %s (location: %s, file: %s)", main_key, yml_type, get_yaml_node_location(pair.second), path), &patch_log.error); is_valid = false; continue; } if (main_key.empty()) { - append_log_message(log_messages, fmt::format("Error: Skipping empty key (location: %s)", get_yaml_node_location(pair.second))); - patch_log.error("Skipping empty key (location: %s, file: %s)", get_yaml_node_location(pair.second), path); + append_log_message(log_messages, fmt::format("Error: Skipping empty key (location: %s, file: %s)", get_yaml_node_location(pair.second), path), &patch_log.error); is_valid = false; continue; } @@ -203,8 +206,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (const auto yml_type = patches_entry.second.Type(); yml_type != YAML::NodeType::Map) { - append_log_message(log_messages, fmt::format("Error: Skipping Patch key %s: expected Map, found %s (key: %s, location: %s)", description, yml_type, main_key, get_yaml_node_location(patches_entry.second))); - patch_log.error("Skipping Patch key %s: expected Map, found %s (key: %s, location: %s, file: %s)", description, yml_type, main_key, get_yaml_node_location(patches_entry.second), path); + append_log_message(log_messages, fmt::format("Error: Skipping Patch key %s: expected Map, found %s (key: %s, location: %s, file: %s)", description, yml_type, main_key, get_yaml_node_location(patches_entry.second), path), &patch_log.error); is_valid = false; continue; } @@ -219,8 +221,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st { if (const auto yml_type = games_node.Type(); yml_type != YAML::NodeType::Map) { - append_log_message(log_messages, fmt::format("Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s, location: %s)", yml_type, description, main_key, get_yaml_node_location(games_node))); - patch_log.error("Skipping Games key: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", yml_type, description, main_key, get_yaml_node_location(games_node), path); + append_log_message(log_messages, fmt::format("Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", yml_type, description, main_key, get_yaml_node_location(games_node), path), &patch_log.error); is_valid = false; continue; } @@ -231,16 +232,14 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (title.empty()) { - append_log_message(log_messages, fmt::format("Error: Empty game title (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(game_node), path)); - patch_log.error("Empty game title (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(game_node), path); + append_log_message(log_messages, fmt::format("Error: Empty game title (key: %s, location: %s, file: %s)", main_key, get_yaml_node_location(game_node), path), &patch_log.error); is_valid = false; continue; } if (const auto yml_type = game_node.second.Type(); yml_type != YAML::NodeType::Map) { - append_log_message(log_messages, fmt::format("Error: Skipping game %s: expected Map, found %s (patch: %s, key: %s, location: %s)", title, yml_type, description, main_key, get_yaml_node_location(game_node))); - patch_log.error("Skipping game %s: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", title, yml_type, description, main_key, get_yaml_node_location(game_node), path); + append_log_message(log_messages, fmt::format("Error: Skipping game %s: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", title, yml_type, description, main_key, get_yaml_node_location(game_node), path), &patch_log.error); is_valid = false; continue; } @@ -253,33 +252,30 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (serial.empty()) { - append_log_message(log_messages, fmt::format("Error: Using empty serial (title: %s, patch: %s, key: %s, location: %s)", title, description, main_key, get_yaml_node_location(serial_node))); - patch_log.error("Using empty serial (title: %s, patch: %s, key: %s, location: %s, file: %s)", title, description, main_key, get_yaml_node_location(serial_node), path); + append_log_message(log_messages, fmt::format("Error: Using empty serial (title: %s, patch: %s, key: %s, location: %s, file: %s)", title, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error); is_valid = false; continue; } - else if (serial == patch_key::all) + + if (serial == patch_key::all) { if (!title_is_all_key) { - append_log_message(log_messages, fmt::format("Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s, location: %s)", patch_key::all, patch_key::all, title, description, main_key, get_yaml_node_location(serial_node))); - patch_log.error("Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s, location: %s, file: %s)", patch_key::all, patch_key::all, title, description, main_key, get_yaml_node_location(serial_node), path); + append_log_message(log_messages, fmt::format("Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s, location: %s, file: %s)", patch_key::all, patch_key::all, title, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error); is_valid = false; continue; } } else if (title_is_all_key) { - append_log_message(log_messages, fmt::format("Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s, location: %s)", patch_key::all, patch_key::all, serial, description, main_key, get_yaml_node_location(serial_node))); - patch_log.error("Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s, location: %s, file: %s)", patch_key::all, patch_key::all, serial, description, main_key, get_yaml_node_location(serial_node), path); + append_log_message(log_messages, fmt::format("Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s, location: %s, file: %s)", patch_key::all, patch_key::all, serial, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error); is_valid = false; continue; } if (const auto yml_type = serial_node.second.Type(); yml_type != YAML::NodeType::Sequence) { - append_log_message(log_messages, fmt::format("Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, location: %s)", serial, title, yml_type, description, main_key, get_yaml_node_location(serial_node))); - patch_log.error("Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, location: %s, file: %s)", serial, title, yml_type, description, main_key, get_yaml_node_location(serial_node), path); + append_log_message(log_messages, fmt::format("Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, location: %s, file: %s)", serial, title, yml_type, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error); is_valid = false; continue; } @@ -288,18 +284,17 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st for (const auto version : serial_node.second) { - const auto& app_version = version.Scalar(); + const std::string& app_version = version.Scalar(); - // Find out if this patch was enabled in the patch config - const bool enabled = patch_config[main_key].patch_info_map[description].titles[title][serial][app_version]; + // Get this patch's config values + const patch_config_values& config_values = patch_config[main_key].patch_info_map[description].titles[title][serial][app_version]; - app_versions.emplace(version.Scalar(), enabled); + app_versions[version.Scalar()] = config_values; } if (app_versions.empty()) { - append_log_message(log_messages, fmt::format("Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, location: %s)", serial, title, description, main_key, get_yaml_node_location(serial_node))); - patch_log.error("Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, location: %s, file: %s)", serial, title, description, main_key, get_yaml_node_location(serial_node), path); + append_log_message(log_messages, fmt::format("Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, location: %s, file: %s)", serial, title, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error); is_valid = false; } else @@ -332,8 +327,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st } else { - append_log_message(log_messages, fmt::format("Error: Skipping sequenced Note (patch: %s, key: %s, location: %s)", description, main_key, get_yaml_node_location(note))); - patch_log.error("Skipping sequenced Note (patch: %s, key: %s, location: %s, file: %s)", description, main_key, get_yaml_node_location(note), path); + append_log_message(log_messages, fmt::format("Error: Skipping sequenced Note (patch: %s, key: %s, location: %s, file: %s)", description, main_key, get_yaml_node_location(note), path), &patch_log.error); is_valid = false; } } @@ -349,6 +343,36 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st info.patch_group = patch_group_node.Scalar(); } + if (const auto dynamic_values_node = patches_entry.second[patch_key::dynamic_values]) + { + if (const auto yml_type = dynamic_values_node.Type(); yml_type != YAML::NodeType::Map || dynamic_values_node.size() == 0) + { + append_log_message(log_messages, fmt::format("Error: Skipping dynamic values: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)", yml_type, description, main_key, get_yaml_node_location(dynamic_values_node), path), &patch_log.error); + is_valid = false; + } + else if (dynamic_values_node.size() > 1) + { + append_log_message(log_messages, fmt::format("Error: Skipping dynamic values: Currently only one value is allowed, found %d (patch: %s, key: %s, location: %s, file: %s)", dynamic_values_node.size(), description, main_key, get_yaml_node_location(dynamic_values_node), path), &patch_log.error); + is_valid = false; + } + else + { + 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(); + info.default_dynamic_values[value_key] = dynamic_value_node.second.as(0.0); + } + 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; + } + } + } + } + if (const auto patch_node = patches_entry.second[patch_key::patch]) { if (!read_patch_node(info, patch_node, root, log_messages)) @@ -358,7 +382,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st } // Skip this patch if a higher patch version already exists - if (container.patch_info_map.find(description) != container.patch_info_map.end()) + if (container.patch_info_map.contains(description)) { bool ok; const std::string& existing_version = container.patch_info_map[description].patch_version; @@ -366,11 +390,11 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st if (!ok || !version_is_bigger) { - patch_log.warning("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s)", info.patch_version, existing_version, main_key, description, path); - append_log_message(log_messages, fmt::format("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s)", info.patch_version, existing_version, main_key, description, path)); + append_log_message(log_messages, fmt::format("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s)", info.patch_version, existing_version, main_key, description, path), &patch_log.warning); continue; } - else if (!importing) + + if (!importing) { patch_log.warning("A lower patch version was found ('%s' vs '%s') for %s: %s (in file %s)", existing_version, info.patch_version, main_key, description, container.patch_info_map[description].source_path); } @@ -410,8 +434,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie { if (!node || !node.IsSequence()) { - append_log_message(log_messages, fmt::format("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } @@ -424,8 +447,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie if (type == patch_type::invalid) { const auto type_str = type_node && type_node.IsScalar() ? type_node.Scalar() : ""; - append_log_message(log_messages, fmt::format("Skipping patch node %s: type '%s' is invalid. (key: %s, location: %s)", info.description, type_str, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping patch node %s: type '%s' is invalid. (key: %s, location: %s)", info.description, type_str, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping patch node %s: type '%s' is invalid. (key: %s, location: %s)", info.description, type_str, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } @@ -436,8 +458,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie // Check if the anchor was resolved. if (const auto yml_type = addr_node.Type(); yml_type != YAML::NodeType::Sequence) { - append_log_message(log_messages, fmt::format("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } @@ -459,15 +480,13 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie if (const auto yml_type = value_node.Type(); yml_type != YAML::NodeType::Scalar) { - append_log_message(log_messages, fmt::format("Skipping patch node %s. Value element has wrong type %s. (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping patch node %s. Value element has wrong type %s. (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping patch node %s. Value element has wrong type %s. (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } if (!addr_node.Scalar().starts_with("0x")) { - append_log_message(log_messages, fmt::format("Skipping patch node %s. Address element has wrong format %s. (key: %s, location: %s)", info.description, addr_node.Scalar(), info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping patch node %s. Address element has wrong format %s. (key: %s, location: %s)", info.description, addr_node.Scalar(), info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping patch node %s. Address element has wrong format %s. (key: %s, location: %s)", info.description, addr_node.Scalar(), info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } @@ -476,6 +495,9 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie p_data.offset = addr_node.as(0) + modifier; 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; + std::string error_message; switch (p_data.type) @@ -490,15 +512,16 @@ 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 = get_yaml_node_value(value_node, error_message); + p_data.value.double_value = is_dynamic_value ? dynamic_value : get_yaml_node_value(value_node, error_message); break; } default: { - p_data.value.long_value = get_yaml_node_value(value_node, error_message); + p_data.value.long_value = is_dynamic_value ? static_cast(dynamic_value) : get_yaml_node_value(value_node, error_message); + if (error_message.find("bad conversion") != std::string::npos) { - error_message = ""; + error_message.clear(); p_data.value.long_value = get_yaml_node_value(value_node, error_message); } break; @@ -509,8 +532,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie { error_message = fmt::format("Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s) %s", p_data.type, p_data.offset, p_data.original_value.empty() ? "?" : p_data.original_value, info.hash, get_yaml_node_location(node), error_message); - append_log_message(log_messages, error_message); - patch_log.error("%s", error_message); + append_log_message(log_messages, error_message, &patch_log.error); return false; } @@ -523,15 +545,13 @@ bool patch_engine::read_patch_node(patch_info& info, YAML::Node node, const YAML { if (!node) { - append_log_message(log_messages, fmt::format("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping invalid patch node %s. (key: %s, location: %s)", info.description, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } if (const auto yml_type = node.Type(); yml_type != YAML::NodeType::Sequence) { - append_log_message(log_messages, fmt::format("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node))); - patch_log.error("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)); + append_log_message(log_messages, fmt::format("Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)", info.description, yml_type, info.hash, get_yaml_node_location(node)), &patch_log.error); return false; } @@ -951,7 +971,7 @@ static usz apply_modification(std::basic_string& applied, const patch_engin std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 filesz, u32 min_addr) { - if (m_map.find(name) == m_map.cend()) + if (!m_map.contains(name)) { return {}; } @@ -978,11 +998,11 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 std::string found_serial; - if (serials.find(serial) != serials.end()) + if (serials.contains(serial)) { found_serial = serial; } - else if (serials.find(patch_key::all) != serials.end()) + else if (serials.contains(patch_key::all)) { found_serial = patch_key::all; is_all_serials = true; @@ -996,17 +1016,24 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 const auto& app_versions = ::at32(serials, found_serial); std::string found_app_version; - if (app_versions.find(app_version) != app_versions.end()) + if (app_versions.contains(app_version)) { found_app_version = app_version; } - else if (app_versions.find(patch_key::all) != app_versions.end()) + else if (app_versions.contains(patch_key::all)) { found_app_version = patch_key::all; is_all_versions = true; } - if (!found_app_version.empty() && ::at32(app_versions, found_app_version)) + if (found_app_version.empty()) + { + continue; + } + + const patch_config_values& config_values = ::at32(app_versions, found_app_version); + + if (config_values.enabled) { // This patch is enabled if (is_all_serials) @@ -1086,7 +1113,7 @@ std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 void patch_engine::unload(const std::string& name) { - if (m_map.find(name) == m_map.cend()) + if (!m_map.contains(name)) { return; } @@ -1117,7 +1144,7 @@ void patch_engine::save_config(const patch_map& patches_map) YAML::Emitter out; out << YAML::BeginMap; - // Save 'enabled' state per hash, description, serial and app_version + // Save values per hash, description, serial and app_version patch_map config_map; for (const auto& [hash, container] : patches_map) @@ -1128,28 +1155,28 @@ void patch_engine::save_config(const patch_map& patches_map) { for (const auto& [serial, app_versions] : serials) { - for (const auto& [app_version, enabled] : app_versions) + for (const auto& [app_version, config_values] : app_versions) { - if (enabled) + const bool dynamic_values_dirty = !patch.default_dynamic_values.empty() && !config_values.dynamic_values.empty() && patch.default_dynamic_values != config_values.dynamic_values; + + if (config_values.enabled || dynamic_values_dirty) { - config_map[hash].patch_info_map[description].titles[title][serial][app_version] = true; + config_map[hash].patch_info_map[description].titles[title][serial][app_version] = config_values; } } } } } - if (const auto& enabled_patches = config_map[hash].patch_info_map; !enabled_patches.empty()) + if (const auto& patches_to_save = config_map[hash].patch_info_map; !patches_to_save.empty()) { out << hash << YAML::BeginMap; - for (const auto& [description, patch] : enabled_patches) + for (const auto& [description, patch] : patches_to_save) { - const auto& titles = patch.titles; - out << description << YAML::BeginMap; - for (const auto& [title, serials] : titles) + for (const auto& [title, serials] : patch.titles) { out << title << YAML::BeginMap; @@ -1157,9 +1184,34 @@ void patch_engine::save_config(const patch_map& patches_map) { out << serial << YAML::BeginMap; - for (const auto& [app_version, enabled] : app_versions) + for (const auto& [app_version, config_values] : app_versions) { - out << app_version << enabled; + const auto& default_dynamic_values = ::at32(container.patch_info_map, description).default_dynamic_values; + const bool dynamic_values_dirty = !default_dynamic_values.empty() && !config_values.dynamic_values.empty() && default_dynamic_values != config_values.dynamic_values; + + if (config_values.enabled || dynamic_values_dirty) + { + out << app_version << YAML::BeginMap; + + if (config_values.enabled) + { + out << patch_key::enabled << config_values.enabled; + } + + if (dynamic_values_dirty) + { + out << patch_key::dynamic_values << YAML::BeginMap; + + for (const auto& [name, value] : config_values.dynamic_values) + { + out << name << value; + } + + out << YAML::EndMap; + } + + out << YAML::EndMap; + } } out << YAML::EndMap; @@ -1191,7 +1243,7 @@ static void append_patches(patch_engine::patch_map& existing_patches, const patc { total += new_container.patch_info_map.size(); - if (existing_patches.find(hash) == existing_patches.end()) + if (!existing_patches.contains(hash)) { existing_patches[hash] = new_container; count += new_container.patch_info_map.size(); @@ -1202,7 +1254,7 @@ static void append_patches(patch_engine::patch_map& existing_patches, const patc for (const auto& [description, new_info] : new_container.patch_info_map) { - if (container.patch_info_map.find(description) == container.patch_info_map.end()) + if (!container.patch_info_map.contains(description)) { container.patch_info_map[description] = new_info; count++; @@ -1216,15 +1268,13 @@ static void append_patches(patch_engine::patch_map& existing_patches, const patc if (!ok) { - patch_log.error("Failed to compare patch versions ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description); - append_log_message(log_messages, fmt::format("Failed to compare patch versions ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description)); + append_log_message(log_messages, fmt::format("Failed to compare patch versions ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description), &patch_log.error); continue; } if (!version_is_bigger) { - patch_log.error("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description); - append_log_message(log_messages, fmt::format("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description)); + append_log_message(log_messages, fmt::format("A higher or equal patch version already exists ('%s' vs '%s') for %s: %s", new_info.patch_version, info.patch_version, hash, description), &patch_log.error); continue; } @@ -1255,8 +1305,7 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat fs::file file(path, fs::rewrite); if (!file) { - patch_log.fatal("save_patches: Failed to open patch file %s (%s)", path, fs::g_tls_error); - append_log_message(log_messages, fmt::format("Failed to open patch file %s (%s)", path, fs::g_tls_error)); + append_log_message(log_messages, fmt::format("Failed to open patch file %s (%s)", path, fs::g_tls_error), &patch_log.fatal); return false; } @@ -1305,6 +1354,18 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat if (!info.patch_group.empty()) out << patch_key::group << info.patch_group; if (!info.notes.empty()) out << patch_key::notes << info.notes; + if (!info.default_dynamic_values.empty()) + { + out << patch_key::dynamic_values << YAML::BeginMap; + + for (const auto& [key, value] : info.default_dynamic_values) + { + out << key << value; + } + + out << YAML::EndMap; + } + out << patch_key::patch << YAML::BeginSeq; for (const auto& data : info.data_list) @@ -1356,11 +1417,11 @@ bool patch_engine::remove_patch(const patch_info& info) if (load(patches, info.source_path)) { - if (patches.find(info.hash) != patches.end()) + if (patches.contains(info.hash)) { auto& container = patches[info.hash]; - if (container.patch_info_map.find(info.description) != container.patch_info_map.end()) + if (container.patch_info_map.contains(info.description)) { container.patch_info_map.erase(info.description); return save_patches(patches, info.source_path); @@ -1373,7 +1434,7 @@ bool patch_engine::remove_patch(const patch_info& info) patch_engine::patch_map patch_engine::load_config() { - patch_map config_map; + patch_map config_map{}; const std::string path = get_patch_config_path(); patch_log.notice("Loading patch config file %s", path); @@ -1431,8 +1492,28 @@ patch_engine::patch_map patch_engine::load_config() for (const auto app_version_node : serial_node.second) { const auto& app_version = app_version_node.first.Scalar(); - const bool enabled = app_version_node.second.as(false); - config_map[hash].patch_info_map[description].titles[title][serial][app_version] = enabled; + auto& config_values = config_map[hash].patch_info_map[description].titles[title][serial][app_version]; + + if (app_version_node.second.IsMap()) + { + if (const auto enable_node = app_version_node.second[patch_key::enabled]) + { + config_values.enabled = enable_node.as(false); + } + + if (const auto dynamic_values_node = app_version_node.second[patch_key::dynamic_values]) + { + 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); + } + } + } + else + { + // Legacy + config_values.enabled = app_version_node.second.as(false); + } } } } diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index 83eb77d83f..f47afd11cc 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -18,6 +18,8 @@ namespace patch_key static const std::string patch = "Patch"; static const std::string patch_version = "Patch Version"; static const std::string version = "Version"; + static const std::string enabled = "Enabled"; + static const std::string dynamic_values = "Dynamic Values"; } inline static const std::string patch_engine_version = "1.2"; @@ -63,7 +65,12 @@ public: mutable u32 alloc_addr = 0; // Used to save optional allocation address (if occured) }; - using patch_app_versions = std::unordered_map; + struct patch_config_values + { + bool enabled{}; + std::map dynamic_values; + }; + using patch_app_versions = std::unordered_map; using patch_serials = std::unordered_map; using patch_titles = std::unordered_map; @@ -78,6 +85,7 @@ public: std::string author{}; std::string notes{}; std::string source_path{}; + std::map default_dynamic_values; // Redundant information for accessibility (see patch_container) std::string hash{}; diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp index 305a440431..5db7267928 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp @@ -913,7 +913,8 @@ error_code sys_rsx_device_map(cpu_thread& cpu, vm::ptr dev_addr, vm::ptr #include #include +#include #include "ui_patch_manager_dialog.h" #include "patch_manager_dialog.h" @@ -43,7 +44,8 @@ enum patch_role : int description_role, patch_group_role, persistance_role, - node_level_role + node_level_role, + dynamic_values_role }; enum node_level : int @@ -64,9 +66,6 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set ui->setupUi(this); setModal(true); - // Load config for special settings - patch_engine::load_config(); - // Load gui settings m_show_owned_games_only = m_gui_settings->GetValue(gui::pm_show_owned).toBool(); @@ -84,6 +83,7 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set 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->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { @@ -259,7 +259,7 @@ void patch_manager_dialog::populate_tree() const QString q_serial = QString::fromStdString(serial); const QString visible_serial = serial == patch_key::all ? tr_all_serials : q_serial; - for (const auto& [app_version, enabled] : app_versions) + for (const auto& [app_version, config_values] : app_versions) { const QString q_app_version = QString::fromStdString(app_version); const QString q_version_suffix = app_version == patch_key::all ? (QStringLiteral(" - ") + tr_all_versions) : (QStringLiteral(" v.") + q_app_version); @@ -305,9 +305,16 @@ void patch_manager_dialog::populate_tree() visible_description += QString::number(counter) + ')'; } + QMap q_dynamic_values; + + for (const auto& [key, value] : patch.default_dynamic_values) + { + q_dynamic_values[QString::fromStdString(key)] = config_values.dynamic_values.contains(key) ? config_values.dynamic_values.at(key) : value; + } + QTreeWidgetItem* patch_level_item = new QTreeWidgetItem(); patch_level_item->setText(0, visible_description); - patch_level_item->setCheckState(0, enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + patch_level_item->setCheckState(0, config_values.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); patch_level_item->setData(0, hash_role, q_hash); patch_level_item->setData(0, title_role, q_title); patch_level_item->setData(0, serial_role, q_serial); @@ -316,6 +323,7 @@ void patch_manager_dialog::populate_tree() patch_level_item->setData(0, patch_group_role, q_patch_group); patch_level_item->setData(0, node_level_role, node_level::patch_level); patch_level_item->setData(0, persistance_role, true); + patch_level_item->setData(0, dynamic_values_role, q_dynamic_values); serial_level_item->addChild(patch_level_item); } @@ -395,7 +403,7 @@ void patch_manager_dialog::filter_patches(const QString& term) const std::string app_version = item->data(0, app_version_role).toString().toStdString(); if (serial != patch_key::all && - (m_owned_games.find(serial) == m_owned_games.end() || (app_version != patch_key::all && !::at32(m_owned_games, serial).contains(app_version)))) + (!m_owned_games.contains(serial) || (app_version != patch_key::all && !::at32(m_owned_games, serial).contains(app_version)))) { item->setHidden(true); return 0; @@ -494,6 +502,11 @@ void patch_manager_dialog::update_patch_info(const patch_manager_dialog::gui_pat ui->label_serial->setText(info.serial); 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()); } void patch_manager_dialog::handle_item_selected(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/) @@ -519,17 +532,18 @@ void patch_manager_dialog::handle_item_selected(QTreeWidgetItem *current, QTreeW const std::string description = current->data(0, description_role).toString().toStdString(); // Find the patch for this item and get its metadata - if (m_map.find(hash) != m_map.end()) + if (m_map.contains(hash)) { const auto& container = ::at32(m_map, hash); - if (container.patch_info_map.find(description) != container.patch_info_map.end()) + if (container.patch_info_map.contains(description)) { const auto& found_info = ::at32(container.patch_info_map, description); info.author = QString::fromStdString(found_info.author); info.notes = QString::fromStdString(found_info.notes); info.description = QString::fromStdString(found_info.description); info.patch_version = QString::fromStdString(found_info.patch_version); + info.dynamic_values = current->data(0, dynamic_values_role).toMap(); } } [[fallthrough]]; @@ -593,15 +607,70 @@ void patch_manager_dialog::handle_item_changed(QTreeWidgetItem *item, int /*colu } // Enable/disable the patch for this item and show its metadata - if (m_map.find(hash) != m_map.end()) + if (m_map.contains(hash)) { - auto& container = m_map[hash]; + auto& info = m_map[hash].patch_info_map; - if (container.patch_info_map.find(description) != container.patch_info_map.end()) + if (info.contains(description)) { - m_map[hash].patch_info_map[description].titles[title][serial][app_version] = enabled; + info[description].titles[title][serial][app_version].enabled = enabled; handle_item_selected(item, nullptr); - return; + } + } +} + +void patch_manager_dialog::handle_dynamic_value_changed(double value) +{ + QList list = ui->patch_tree->selectedItems(); + QTreeWidgetItem* item = list.size() == 1 ? list.first() : nullptr; + + if (!item) + { + return; + } + + const node_level level = static_cast(item->data(0, node_level_role).toInt()); + + if (level != node_level::patch_level) + { + return; + } + + const QString key = ui->dynamic_label->text(); + const QVariant data = item->data(0, dynamic_values_role); + QVariantMap q_dynamic_values = data.canConvert() ? data.toMap() : QVariantMap{}; + + if (q_dynamic_values.isEmpty() || !q_dynamic_values.contains(key)) + { + return; + } + + q_dynamic_values[key] = value; + item->setData(0, dynamic_values_role, q_dynamic_values); + + // Update the dynamic value of the patch for this item + const std::string hash = item->data(0, hash_role).toString().toStdString(); + const std::string title = item->data(0, title_role).toString().toStdString(); + const std::string serial = item->data(0, serial_role).toString().toStdString(); + const std::string app_version = item->data(0, app_version_role).toString().toStdString(); + const std::string description = item->data(0, description_role).toString().toStdString(); + + if (m_map.contains(hash)) + { + auto& info = m_map[hash].patch_info_map; + + if (info.contains(description)) + { + auto& patch = info[description]; + auto& dynamic_values = patch.titles[title][serial][app_version].dynamic_values; + + 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)) + { + dynamic_values[s_key] = q_dynamic_values[q_key].toDouble(); + } + } } } } @@ -625,11 +694,11 @@ void patch_manager_dialog::handle_custom_context_menu_requested(const QPoint &po const std::string hash = item->data(0, hash_role).toString().toStdString(); const std::string description = item->data(0, description_role).toString().toStdString(); - if (m_map.find(hash) != m_map.end()) + if (m_map.contains(hash)) { const auto& container = ::at32(m_map, hash); - if (container.patch_info_map.find(description) != container.patch_info_map.end()) + if (container.patch_info_map.contains(description)) { const auto& info = ::at32(container.patch_info_map, description); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.h b/rpcs3/rpcs3qt/patch_manager_dialog.h index cead123cc1..b8269d060d 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.h +++ b/rpcs3/rpcs3qt/patch_manager_dialog.h @@ -30,6 +30,7 @@ class patch_manager_dialog : public QDialog QString notes; QString description; QString patch_version; + QMap dynamic_values; }; const QString tr_all_titles = tr("All titles - Warning: These patches apply to all games!"); @@ -46,6 +47,7 @@ private Q_SLOTS: void filter_patches(const QString& term); void handle_item_selected(QTreeWidgetItem *current, QTreeWidgetItem *previous); void handle_item_changed(QTreeWidgetItem *item, int column); + void handle_dynamic_value_changed(double value); void handle_custom_context_menu_requested(const QPoint& pos); void handle_show_owned_games_only(int state); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.ui b/rpcs3/rpcs3qt/patch_manager_dialog.ui index 1478e1b89f..a8d9013df1 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.ui +++ b/rpcs3/rpcs3qt/patch_manager_dialog.ui @@ -83,8 +83,8 @@ 0 0 - 106 - 580 + 247 + 637 @@ -105,7 +105,7 @@ Patch Information - + @@ -258,6 +258,32 @@ + + + + Dynamic Values + + + + + + Dynamic label + + + + + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + + + +