mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 12:12:50 +01:00
patch_manager: add more dynamic to dynamic patches
This commit is contained in:
parent
1040757556
commit
080737fd1f
@ -31,6 +31,23 @@ void fmt_class_string<YAML::NodeType::value>::format(std::string& out, u64 arg)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void fmt_class_string<patch_dynamic_type>::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 <>
|
template <>
|
||||||
void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
|
void fmt_class_string<patch_type>::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)
|
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();
|
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);
|
||||||
info.default_dynamic_values[value_key] = dynamic_value_node.second.as<f64>(0.0);
|
is_valid = false;
|
||||||
}
|
}
|
||||||
else
|
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);
|
patch_dynamic_value& dynamic_value = info.default_dynamic_values[value_key];
|
||||||
is_valid = false;
|
|
||||||
|
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<f64>(node, err);
|
||||||
|
break;
|
||||||
|
case patch_dynamic_type::long_range:
|
||||||
|
case patch_dynamic_type::long_enum:
|
||||||
|
val = get_yaml_node_value<s64>(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();
|
p_data.original_value = value_node.Scalar();
|
||||||
|
|
||||||
const bool is_dynamic_value = info.default_dynamic_values.contains(p_data.original_value);
|
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;
|
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::bef64:
|
||||||
case patch_type::lef64:
|
case patch_type::lef64:
|
||||||
{
|
{
|
||||||
p_data.value.double_value = is_dynamic_value ? dynamic_value : get_yaml_node_value<f64>(value_node, error_message);
|
p_data.value.double_value = is_dynamic_value ? dynamic_value.value : get_yaml_node_value<f64>(value_node, error_message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
p_data.value.long_value = is_dynamic_value ? static_cast<u64>(dynamic_value) : get_yaml_node_value<u64>(value_node, error_message);
|
p_data.value.long_value = is_dynamic_value ? static_cast<u64>(dynamic_value.value) : get_yaml_node_value<u64>(value_node, error_message);
|
||||||
|
|
||||||
if (error_message.find("bad conversion") != std::string::npos)
|
if (error_message.find("bad conversion") != std::string::npos)
|
||||||
{
|
{
|
||||||
@ -601,11 +763,44 @@ void unmap_vm_area(std::shared_ptr<vm::block_t>& ptr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns old 'applied' size
|
// Returns old 'applied' size
|
||||||
static usz apply_modification(std::basic_string<u32>& applied, const patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr)
|
static usz apply_modification(std::basic_string<u32>& applied, patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr)
|
||||||
{
|
{
|
||||||
const usz old_applied_size = applied.size();
|
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;
|
if (p.type != patch_type::alloc) continue;
|
||||||
|
|
||||||
@ -679,7 +874,7 @@ static usz apply_modification(std::basic_string<u32>& applied, const patch_engin
|
|||||||
|
|
||||||
u32 relocate_instructions_at = 0;
|
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;
|
u32 offset = p.offset;
|
||||||
|
|
||||||
@ -982,10 +1177,10 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
|||||||
const auto& app_version = Emu.GetAppVersion();
|
const auto& app_version = Emu.GetAppVersion();
|
||||||
|
|
||||||
// Different containers in order to seperate the patches
|
// Different containers in order to seperate the patches
|
||||||
std::vector<const patch_info*> patches_for_this_serial_and_this_version;
|
std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_this_version;
|
||||||
std::vector<const patch_info*> patches_for_this_serial_and_all_versions;
|
std::vector<std::shared_ptr<patch_info>> patches_for_this_serial_and_all_versions;
|
||||||
std::vector<const patch_info*> patches_for_all_serials_and_this_version;
|
std::vector<std::shared_ptr<patch_info>> patches_for_all_serials_and_this_version;
|
||||||
std::vector<const patch_info*> patches_for_all_serials_and_all_versions;
|
std::vector<std::shared_ptr<patch_info>> patches_for_all_serials_and_all_versions;
|
||||||
|
|
||||||
// Sort patches into different vectors based on their serial and version
|
// Sort patches into different vectors based on their serial and version
|
||||||
for (const auto& [description, patch] : container.patch_info_map)
|
for (const auto& [description, patch] : container.patch_info_map)
|
||||||
@ -1033,27 +1228,43 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
|||||||
|
|
||||||
const patch_config_values& config_values = ::at32(app_versions, found_app_version);
|
const patch_config_values& config_values = ::at32(app_versions, found_app_version);
|
||||||
|
|
||||||
|
// Check if this patch is enabled
|
||||||
if (config_values.enabled)
|
if (config_values.enabled)
|
||||||
{
|
{
|
||||||
// This patch is enabled
|
// Make copy of this patch
|
||||||
|
std::shared_ptr<patch_info> p_ptr = std::make_shared<patch_info>(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_serials)
|
||||||
{
|
{
|
||||||
if (is_all_versions)
|
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
|
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)
|
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
|
else
|
||||||
{
|
{
|
||||||
patches_for_this_serial_and_this_version.emplace_back(&patch);
|
patches_for_this_serial_and_this_version.emplace_back(p_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -1061,17 +1272,6 @@ std::basic_string<u32> 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
|
// Sort specific patches after global patches
|
||||||
// So they will determine the end results
|
// So they will determine the end results
|
||||||
const auto patch_super_list =
|
const auto patch_super_list =
|
||||||
@ -1091,19 +1291,26 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
|||||||
{
|
{
|
||||||
if (!m_applied_groups.insert(patch->patch_group).second)
|
if (!m_applied_groups.insert(patch->patch_group).second)
|
||||||
{
|
{
|
||||||
patch = nullptr;
|
patch.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply modifications sequentially
|
||||||
for (auto patch_list : patch_super_list)
|
for (auto patch_list : patch_super_list)
|
||||||
{
|
{
|
||||||
for (const patch_info* patch : *patch_list)
|
for (const std::shared_ptr<patch_info>& patch : *patch_list)
|
||||||
{
|
{
|
||||||
if (patch)
|
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;
|
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;
|
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;
|
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;
|
out << YAML::EndMap;
|
||||||
@ -1505,7 +1736,8 @@ patch_engine::patch_map patch_engine::load_config()
|
|||||||
{
|
{
|
||||||
for (const auto dynamic_value_node : dynamic_values_node)
|
for (const auto dynamic_value_node : dynamic_values_node)
|
||||||
{
|
{
|
||||||
config_values.dynamic_values[dynamic_value_node.first.Scalar()] = dynamic_value_node.second.as<f64>(0.0);
|
patch_dynamic_value& dynamic_value = config_values.dynamic_values[dynamic_value_node.first.Scalar()];
|
||||||
|
dynamic_value.value = dynamic_value_node.second.as<f64>(0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,11 @@ namespace patch_key
|
|||||||
static const std::string version = "Version";
|
static const std::string version = "Version";
|
||||||
static const std::string enabled = "Enabled";
|
static const std::string enabled = "Enabled";
|
||||||
static const std::string dynamic_values = "Dynamic Values";
|
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";
|
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)
|
utf8, // Text of string (not null-terminated automatically)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class patch_dynamic_type
|
||||||
|
{
|
||||||
|
double_range,
|
||||||
|
double_enum,
|
||||||
|
long_range,
|
||||||
|
long_enum
|
||||||
|
};
|
||||||
|
|
||||||
class patch_engine
|
class patch_engine
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -65,11 +78,26 @@ public:
|
|||||||
mutable u32 alloc_addr = 0; // Used to save optional allocation address (if occured)
|
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<f64> 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
|
struct patch_config_values
|
||||||
{
|
{
|
||||||
bool enabled{};
|
bool enabled{};
|
||||||
std::map<std::string, f64> dynamic_values;
|
std::map<std::string, patch_dynamic_value> dynamic_values;
|
||||||
};
|
};
|
||||||
|
|
||||||
using patch_app_versions = std::unordered_map<std::string /*app_version*/, patch_config_values>;
|
using patch_app_versions = std::unordered_map<std::string /*app_version*/, patch_config_values>;
|
||||||
using patch_serials = std::unordered_map<std::string /*serial*/, patch_app_versions>;
|
using patch_serials = std::unordered_map<std::string /*serial*/, patch_app_versions>;
|
||||||
using patch_titles = std::unordered_map<std::string /*serial*/, patch_serials>;
|
using patch_titles = std::unordered_map<std::string /*serial*/, patch_serials>;
|
||||||
@ -85,11 +113,12 @@ public:
|
|||||||
std::string author{};
|
std::string author{};
|
||||||
std::string notes{};
|
std::string notes{};
|
||||||
std::string source_path{};
|
std::string source_path{};
|
||||||
std::map<std::string, f64> default_dynamic_values;
|
std::map<std::string, patch_dynamic_value> default_dynamic_values;
|
||||||
|
|
||||||
// Redundant information for accessibility (see patch_container)
|
// Redundant information for accessibility (see patch_container)
|
||||||
std::string hash{};
|
std::string hash{};
|
||||||
std::string version{};
|
std::string version{};
|
||||||
|
std::map<std::string, patch_dynamic_value> actual_dynamic_values;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct patch_container
|
struct patch_container
|
||||||
|
@ -55,6 +55,8 @@ enum node_level : int
|
|||||||
patch_level
|
patch_level
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(patch_engine::patch_dynamic_value);
|
||||||
|
|
||||||
patch_manager_dialog::patch_manager_dialog(std::shared_ptr<gui_settings> gui_settings, std::unordered_map<std::string, std::set<std::string>> games, const std::string& title_id, const std::string& version, QWidget* parent)
|
patch_manager_dialog::patch_manager_dialog(std::shared_ptr<gui_settings> gui_settings, std::unordered_map<std::string, std::set<std::string>> games, const std::string& title_id, const std::string& version, QWidget* parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, m_gui_settings(std::move(gui_settings))
|
, m_gui_settings(std::move(gui_settings))
|
||||||
@ -77,13 +79,28 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr<gui_settings> gui_set
|
|||||||
|
|
||||||
m_downloader = new downloader(this);
|
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
|
// Create connects
|
||||||
connect(ui->patch_filter, &QLineEdit::textChanged, this, &patch_manager_dialog::filter_patches);
|
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::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::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->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->cb_owned_games_only, &QCheckBox::stateChanged, this, &patch_manager_dialog::handle_show_owned_games_only);
|
||||||
connect(ui->dynamic_value_box, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed);
|
connect(ui->dynamic_combo_box, QOverload<int>::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<int>::of(&QSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed);
|
||||||
|
connect(ui->dynamic_double_spin_box, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &patch_manager_dialog::handle_dynamic_value_changed);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
|
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
||||||
{
|
{
|
||||||
@ -307,9 +324,16 @@ void patch_manager_dialog::populate_tree()
|
|||||||
|
|
||||||
QMap<QString, QVariant> q_dynamic_values;
|
QMap<QString, QVariant> 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();
|
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_title->setText(info.title);
|
||||||
ui->label_app_version->setText(info.app_version);
|
ui->label_app_version->setText(info.app_version);
|
||||||
|
|
||||||
// TODO: support more than one value in the future
|
// TODO: support more than one dynamic 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());
|
if (info.dynamic_values.empty())
|
||||||
ui->dynamic_value_box->setEnabled(!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<patch_engine::patch_dynamic_value>());
|
||||||
|
|
||||||
|
const patch_engine::patch_dynamic_value dynamic_value = variant.value<patch_engine::patch_dynamic_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*/)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
q_dynamic_values[key] = value;
|
QVariant& variant = q_dynamic_values[key];
|
||||||
|
ensure(variant.canConvert<patch_engine::patch_dynamic_value>());
|
||||||
|
|
||||||
|
patch_engine::patch_dynamic_value dynamic_value = variant.value<patch_engine::patch_dynamic_value>();
|
||||||
|
dynamic_value.value = value;
|
||||||
|
variant = QVariant::fromValue(dynamic_value);
|
||||||
|
|
||||||
item->setData(0, dynamic_values_role, q_dynamic_values);
|
item->setData(0, dynamic_values_role, q_dynamic_values);
|
||||||
|
|
||||||
// Update the dynamic value of the patch for this item
|
// 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())
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,13 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDoubleSpinBox" name="dynamic_value_box">
|
<widget class="QComboBox" name="dynamic_combo_box"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="dynamic_spin_box"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDoubleSpinBox" name="dynamic_double_spin_box">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<double>-1000000.000000000000000</double>
|
<double>-1000000.000000000000000</double>
|
||||||
</property>
|
</property>
|
||||||
|
Loading…
Reference in New Issue
Block a user