patcher: patch groups (#812)

## Link to GitHub Issue or related Pull Request, if one exists
Fixes #245

## Description of change

Introduce patch groups. JSON schema now allows for a `"type": "group"`:

```json
    {
        "type": "group",
        "gameCode": "LDJ",
        "id": "timer-freeze",
        "name": "Timer Freeze Patches",
        "description": "Freezes various timers in the game."
    },
    {
        "name": "Standard/Menu Timer Freeze",
        "description": "Freezes all non-premium area timers.",
        "caution": "",
        "gameCode": "LDJ",
        "type": "memory",
        "group": "timer-freeze",
        "patches": [
            {
                "offset": 9962743,
                "dllName": "bm2dx.dll",
                "dataDisabled": "0F84",
                "dataEnabled": "90E9"
            }
        ]
    },
    {
        "name": "Premium Free Timer Freeze",
        "description": "Freezes all premium area timers.",
        "caution": "",
        "gameCode": "LDJ",
        "type": "memory",
        "group": "timer-freeze",
        "patches": [
            {
                "offset": 9111965,
                "dllName": "bm2dx.dll",
                "dataDisabled": "7E",
                "dataEnabled": "EB"
            }
        ]
    },
```

In the UI, these will show up as tree nodes. The parent is not actually
treated as a patch; e.g., its state is not saved to the patch manager
config file; only the child patches states are managed, the parent's
state only bubble up only in the UI.

In downlevel versions of spice, group parents will show up as a patch of
invalid type. Child patches will still show up as individual patches.

## Testing
*how was the code tested?*
This commit is contained in:
bicarus
2026-07-19 02:10:13 -07:00
committed by GitHub
parent a8cc85531c
commit be4bb00390
7 changed files with 741 additions and 50 deletions
+15
View File
@@ -1,5 +1,8 @@
#pragma once
#include <map>
#include <utility>
#include "patch_manager.h"
#include "external/rapidjson/document.h"
#include "util/nt_loader.h"
@@ -11,6 +14,7 @@ namespace patcher {
extern std::vector<std::string> setting_patches_enabled;
extern std::map<std::string, std::string> setting_union_patches_enabled;
extern std::map<std::string, int64_t> setting_int_patches_enabled;
extern std::map<std::pair<std::string, std::string>, PatchGroup> patch_groups;
extern std::filesystem::path LOCAL_PATCHES_PATH;
extern std::map<std::string, std::vector<std::string>> EXTRA_DLLS;
extern bool ldr_registered;
@@ -28,6 +32,17 @@ namespace patcher {
bool load_from_patches_json(bool apply_patches);
void load_embedded_patches(bool apply_patches);
bool import_remote_patches_for_dll(const std::string& url, const std::string& dll_name);
bool is_patch_group_definition(const rapidjson::Value& patch);
std::map<std::pair<std::string, std::string>, PatchGroup> parse_patch_group_definitions(
const rapidjson::Document& doc);
std::string resolve_patch_group_id(
const rapidjson::Value& patch,
const std::map<std::pair<std::string, std::string>, PatchGroup>& groups,
const std::string& game_code,
const char *patch_name);
void register_patch_group(
PatchData& patch,
const std::map<std::pair<std::string, std::string>, PatchGroup>& definitions);
VOID CALLBACK loader_notification(ULONG reason, PCLDR_DLL_NOTIFICATION_DATA data, PVOID context);
std::string patch_hash(PatchData& patch);