From cc9c0f9b12300a9a8ef9a5361e78f2b9dbba0dc1 Mon Sep 17 00:00:00 2001 From: Ty Lamontagne Date: Tue, 10 Sep 2024 20:36:12 -0400 Subject: [PATCH] Patches: Implement dynamic patching support in pnaches --- pcsx2/Patch.cpp | 160 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 137 insertions(+), 23 deletions(-) diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index 2d0ff8a92c..1dd6ad17f4 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -114,6 +114,7 @@ namespace Patch std::optional override_aspect_ratio; std::optional override_interlace_mode; std::vector patches; + std::vector dpatches; }; struct PatchTextTable @@ -132,6 +133,7 @@ namespace Patch static void patch(PatchGroup* group, const std::string_view cmd, const std::string_view param); static void gsaspectratio(PatchGroup* group, const std::string_view cmd, const std::string_view param); static void gsinterlacemode(PatchGroup* group, const std::string_view cmd, const std::string_view param); + static void dpatch(PatchGroup* group, const std::string_view cmd, const std::string_view param); } // namespace PatchFunc static void TrimPatchLine(std::string& buffer); @@ -187,6 +189,7 @@ namespace Patch {0, "patch", &Patch::PatchFunc::patch}, {0, "gsaspectratio", &Patch::PatchFunc::gsaspectratio}, {0, "gsinterlacemode", &Patch::PatchFunc::gsinterlacemode}, + {0, "dpatch", &Patch::PatchFunc::dpatch}, {0, nullptr, nullptr}, }; } // namespace Patch @@ -247,32 +250,35 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch PatchGroup current_patch_group; const auto add_current_patch = [patch_list, ¤t_patch_group]() { - if (current_patch_group.patches.empty()) - return; - - // Ungrouped/legacy patches should merge with other ungrouped patches. - if (current_patch_group.name.empty()) + if (!current_patch_group.patches.empty()) { - const PatchList::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(), - [](const PatchGroup& pg) { return pg.name.empty(); }); - if (ungrouped_patch != patch_list->end()) + // Ungrouped/legacy patches should merge with other ungrouped patches. + if (current_patch_group.name.empty()) { - Console.WriteLn(Color_Gray, fmt::format( - "Patch: Merging {} new patch commands into ungrouped list.", current_patch_group.patches.size())); + const PatchList::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(), + [](const PatchGroup& pg) { return pg.name.empty(); }); + if (ungrouped_patch != patch_list->end()) + { + Console.WriteLn(Color_Gray, fmt::format( + "Patch: Merging {} new patch commands into ungrouped list.", current_patch_group.patches.size())); - ungrouped_patch->patches.reserve(ungrouped_patch->patches.size() + current_patch_group.patches.size()); - for (PatchCommand& cmd : current_patch_group.patches) - ungrouped_patch->patches.push_back(std::move(cmd)); - } - else - { - // Always add ungrouped patches, no sense to compare empty names. - patch_list->push_back(std::move(current_patch_group)); - } + ungrouped_patch->patches.reserve(ungrouped_patch->patches.size() + current_patch_group.patches.size()); + for (PatchCommand& cmd : current_patch_group.patches) + ungrouped_patch->patches.push_back(std::move(cmd)); + } + else + { + // Always add ungrouped patches, no sense to compare empty names. + patch_list->push_back(std::move(current_patch_group)); + } - return; + return; + } } + if (current_patch_group.patches.empty() && current_patch_group.dpatches.empty()) + return; + // Don't show patches with duplicate names, prefer the first loaded. if (!ContainsPatchName(*patch_list, current_patch_group.name)) { @@ -302,7 +308,7 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch continue; } - if (!current_patch_group.name.empty() || !current_patch_group.patches.empty()) + if (!current_patch_group.name.empty() || !current_patch_group.patches.empty() || !current_patch_group.dpatches.empty()) { add_current_patch(); current_patch_group = {}; @@ -318,7 +324,7 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch LoadPatchLine(¤t_patch_group, line); } - if (!current_patch_group.name.empty() || !current_patch_group.patches.empty()) + if (!current_patch_group.name.empty() || !current_patch_group.patches.empty() || !current_patch_group.dpatches.empty()) add_current_patch(); return static_cast(patch_list->size() - before); @@ -623,13 +629,18 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable s_active_patches.push_back(&ip); } + for (const DynamicPatch& dp : p.dpatches) + { + s_active_dynamic_patches.push_back(dp); + } + if (p.override_aspect_ratio.has_value()) s_override_aspect_ratio = p.override_aspect_ratio; if (p.override_interlace_mode.has_value()) s_override_interlace_mode = p.override_interlace_mode; // Count unlabelled patches once per command, or one patch per group. - count += p.name.empty() ? static_cast(p.patches.size()) : 1; + count += p.name.empty() ? (static_cast(p.patches.size()) + static_cast(p.dpatches.size())) : 1; } return count; @@ -688,6 +699,7 @@ void Patch::UpdateActivePatches(bool reload_enabled_list, bool verbose, bool ver s_active_patches.clear(); s_override_aspect_ratio.reset(); s_override_interlace_mode.reset(); + s_active_dynamic_patches.clear(); SmallString message; u32 gp_count = 0; @@ -901,6 +913,108 @@ void Patch::PatchFunc::gsinterlacemode(PatchGroup* group, const std::string_view group->override_interlace_mode = static_cast(interlace_mode.value()); } +void Patch::PatchFunc::dpatch(PatchGroup* group, const std::string_view cmd, const std::string_view param) +{ +#define PATCH_ERROR(fstring, ...) \ + Console.Error(fmt::format("(dPatch) Error Parsing: {}={}: " fstring, cmd, param, __VA_ARGS__)) + + // [0]=version/type,[1]=number of patterns,[2]=number of replacements + // Each pattern or replacement is [3]=offset,[4]=hex + + const std::vector pieces(StringUtil::SplitString(param, ',', false)); + if (pieces.size() < 3) + { + PATCH_ERROR("Expected at least 3 data parameters; only found {}", pieces.size()); + return; + } + + + std::string_view patterns_end, replacements_end; + + // Implemented for possible future use so we don't have to break backcompat + std::optional dpatch_type = StringUtil::FromChars(pieces[0]); + + std::optional num_patterns = StringUtil::FromChars(pieces[1], 16, &patterns_end); + std::optional num_replacements = StringUtil::FromChars(pieces[2], 16, &replacements_end); + + if (!dpatch_type.has_value()) + { + PATCH_ERROR("Malformed version/type '{}', a decimal number(e.g. 0,1,2) is expected", pieces[0]); + return; + } + + if (dpatch_type.value() != 0) + { + PATCH_ERROR("Unsupported version/type '{}', only 0 is currently supported", pieces[0]); + return; + } + + if (!num_patterns.has_value()) + { + PATCH_ERROR("Malformed number of patterns '{}', a decimal number is expected", pieces[1]); + return; + } + + if (!num_replacements.has_value()) + { + PATCH_ERROR("Malformed number of replacements '{}', a decimal number is expected", pieces[2]); + return; + } + + if (pieces.size() != ((num_patterns.value() * 2) + (num_replacements.value() * 2) + 3)) + { + PATCH_ERROR("Expected 2 fields for each {} patterns and {} replacements; found {}", num_patterns.value(), num_replacements.value(), pieces.size() - 2); + return; + } + + DynamicPatch dpatch; + for (u32 i = 0; i < num_patterns.value(); i++) + { + std::optional offset = StringUtil::FromChars(pieces[3 + (i * 2)], 16); + std::optional value = StringUtil::FromChars(pieces[4 + (i * 2)], 16); + if (!offset.has_value()) + { + PATCH_ERROR("Malformed offset '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[3 + (i * 2)]); + return; + } + if (!value.has_value()) + { + PATCH_ERROR("Malformed value '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[4 + (i * 2)]); + return; + } + + DynamicPatchEntry pattern; + pattern.offset = offset.value(); + pattern.value = value.value(); + + dpatch.pattern.push_back(pattern); + } + + for (u32 i = 0; i < num_replacements.value(); i++) + { + std::optional offset = StringUtil::FromChars(pieces[3 + (num_patterns.value() * 2) + (i * 2)], 16); + std::optional value = StringUtil::FromChars(pieces[4 + (num_patterns.value() * 2) + (i * 2)], 16); + if (!offset.has_value()) + { + PATCH_ERROR("Malformed offset '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[3 + (num_patterns.value() * 2) + (i * 2)]); + return; + } + if (!value.has_value()) + { + PATCH_ERROR("Malformed value '{}', a hex number without prefix (e.g. 0123ABCD) is expected", pieces[4 + (num_patterns.value() * 2) + (i * 2)]); + return; + } + + DynamicPatchEntry replacement; + replacement.offset = offset.value(); + replacement.value = value.value(); + + dpatch.replacement.push_back(replacement); + } + + group->dpatches.push_back(dpatch); +} + // This is for applying patches directly to memory void Patch::ApplyLoadedPatches(patch_place_type place) {