Files
spice2x.github.io/src/spice2x/patcher/patch_manager.h
T
bicarus be4bb00390 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?*
2026-07-19 02:10:13 -07:00

143 lines
4.0 KiB
C++

#pragma once
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace patcher {
enum class PatchType {
Unknown,
Memory,
Signature,
Union,
Integer,
};
enum class PatchStatus {
Error,
Disabled,
Enabled,
};
enum class PatchUrlStatus {
Valid,
Invalid,
Unapplied,
ValidButNoData,
Partial,
};
struct MemoryPatch {
std::string dll_name = "";
std::shared_ptr<uint8_t[]> data_disabled = nullptr;
size_t data_disabled_len = 0;
std::shared_ptr<uint8_t[]> data_enabled = nullptr;
size_t data_enabled_len = 0;
uint64_t data_offset = 0;
uint8_t *data_offset_ptr = nullptr;
bool fatal_error = false;
};
struct PatchData;
struct SignaturePatch {
std::string dll_name = "";
std::string signature = "", replacement = "";
uint64_t offset = 0;
int64_t usage = 0;
MemoryPatch to_memory(PatchData *patch);
};
struct UnionPatch {
std::string name = "";
std::string dll_name = "";
std::shared_ptr<uint8_t[]> data = nullptr;
size_t data_len = 0;
uint64_t offset = 0;
uint8_t *data_offset_ptr = nullptr;
bool fatal_error = false;
};
struct NumberPatch {
std::string dll_name = "";
uint64_t data_offset = 0;
uint8_t *data_offset_ptr = nullptr;
int32_t min;
int32_t max;
int32_t value;
size_t size_in_bytes;
bool fatal_error = false;
};
struct PatchGroup {
std::string name;
std::string description;
std::string caution;
std::string name_in_lower_case;
};
struct PatchData {
bool enabled;
std::string game_code;
int datecode_min = 0;
int datecode_max = 0;
std::string name, description, caution;
std::string name_in_lower_case = "";
PatchType type;
bool preset;
std::vector<MemoryPatch> patches_memory;
std::vector<UnionPatch> patches_union;
NumberPatch patch_number;
std::string group_id;
PatchStatus last_status;
std::string hash;
bool unverified = false;
std::string peIdentifier;
std::string error_reason = "";
// for union patch only
std::string selected_union_name = "";
};
extern std::optional<std::string> PATCH_MANAGER_CFG_PATH_OVERRIDE;
std::string get_game_identifier(const std::filesystem::path& dll_path, bool print_info = false);
// lifecycle: init() sets up the config path and registers for DLL-load
// notifications; apply_patches_on_start() applies auto-apply patches once the
// game DLL is loaded. both are safe to call repeatedly.
void init();
void apply_patches_on_start();
// patch load / apply operations (also driven by the overlay window)
void reload_local_patches(bool apply_patches = false);
bool import_remote_patches_to_disk();
void hard_apply_patches();
void config_load();
void config_save();
std::vector<std::string> getExtraDlls(const std::string& firstDll);
// process-wide patch engine state, rendered and edited by the overlay window
extern std::filesystem::path config_path;
extern bool config_dirty;
extern bool setting_auto_apply;
extern std::string patch_url;
extern std::string ACTIVE_JSON_FILE;
extern std::vector<PatchData> patches;
extern bool local_patches_initialized;
extern std::vector<size_t> patches_sorted;
extern std::string url_fetch_errors;
extern size_t url_recent_idx;
extern std::vector<std::string> url_recents;
PatchStatus is_patch_active(PatchData& patch);
bool apply_patch(PatchData& patch, bool active);
const PatchGroup* find_patch_group(const PatchData& patch);
std::string displayPath(const std::filesystem::path& path);
}