Repository navigation
Expand file tree
/
Copy pathgenerate_data.py
More file actions
123 lines (109 loc) · 4.45 KB
/
Copy pathgenerate_data.py
File metadata and controls
123 lines (109 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""Generate the fixed patch tables from patches.json. No game files are needed."""
import json
from pathlib import Path
SOURCE = Path(__file__).resolve().parent
SUPPORTED_GROUPS = {
"DLC",
"VIP",
"INTRO",
"MARKETPLACE",
"LIVERY",
"MSAA",
"PAUSE_GARAGE",
"TEXT_INIT",
"TEXT_LOCK",
"AUDIO_LANGUAGE",
"ISLAND_CARS",
"PARENTAL",
"PHOTO_CAMERA",
"TREASURE_MAP",
"SUN_SHADOWS",
}
def byte_initializer(hex_bytes):
return ", ".join(f"0x{value:02x}" for value in bytes.fromhex(hex_bytes))
def patch_states(patch):
standard = patch.get("patched") or patch["targets"]["standard"]
supersampled = patch.get("targets", {}).get("supersampled", standard)
variants = list(dict.fromkeys([patch["original"], *patch["accepted"], standard, supersampled]))
return standard, supersampled, variants
def validate_patches(patches):
previous_raw_end = previous_rva_end = 0
for patch in patches:
size = len(bytes.fromhex(patch["original"]))
assert size and patch["offset"] >= previous_raw_end, patch["id"]
assert patch["rva"] >= previous_rva_end, patch["id"]
assert patch["runtime_group"] in SUPPORTED_GROUPS, patch["id"]
_, _, variants = patch_states(patch)
assert all(len(bytes.fromhex(value)) == size for value in variants), patch["id"]
previous_raw_end = patch["offset"] + size
previous_rva_end = patch["rva"] + size
def generate():
manifest = json.loads((SOURCE / "patches.json").read_text(encoding="utf-8"))
patches = sorted(manifest["patches"], key=lambda patch: patch["offset"])
validate_patches(patches)
declarations = []
definitions = [
"/* Generated by generate_data.py; edit patches.json instead. */",
'#include "patches_generated.h"',
"",
]
patch_rows = []
for patch in patches:
name = patch["id"]
standard, supersampled, variants = patch_states(patch)
symbols = {value: f"{name}_bytes_{index}" for index, value in enumerate(variants)}
for value, symbol in symbols.items():
definitions.append(
f"static const unsigned char {symbol}[] = {{{byte_initializer(value)}}};"
)
accepted = ", ".join(symbols[value] for value in variants)
definitions.append(f"static const unsigned char *const {name}_accepted[] = {{{accepted}}};")
if "language_targets" in patch:
targets = ", ".join(symbols[value] for value in patch["language_targets"])
definitions.append(
f"const unsigned char *const {name}_targets[LANGUAGE_COUNT] = {{{targets}}};"
)
declarations.append(
f"extern const unsigned char *const {name}_targets[LANGUAGE_COUNT];"
)
patch_rows.append(
" {"
f"0x{patch['rva']:x}, 0x{patch['offset']:x}, {len(bytes.fromhex(patch['original']))}, "
f"GROUP_{patch['runtime_group']}, \"{name}\", "
f"{symbols[patch['original']]}, {symbols[standard]}, {symbols[supersampled]}, "
f"{name}_accepted, {len(variants)}"
"},"
)
definitions.extend(
[
"",
"const PatchDefinition fixed_patches[FIXED_PATCH_COUNT] = {",
*patch_rows,
"};",
"",
"const unsigned char game_file_sha256[32] = {",
f" {byte_initializer(manifest['original_sha256'])}",
"};",
"",
]
)
maximum_size = max(len(bytes.fromhex(patch["original"])) for patch in patches)
header = [
"/* Generated by generate_data.py; edit patches.json instead. */",
"#ifndef FH2_PATCHES_GENERATED_H",
"#define FH2_PATCHES_GENERATED_H",
'#include "extra_options.h"',
"",
f"#define FIXED_PATCH_COUNT {len(patches)}",
f"#define MAX_PATCH_SIZE {maximum_size}",
"extern const PatchDefinition fixed_patches[FIXED_PATCH_COUNT];",
"extern const unsigned char game_file_sha256[32];",
*declarations,
"#endif",
"",
]
(SOURCE / "patches_generated.c").write_text("\n".join(definitions), encoding="utf-8")
(SOURCE / "patches_generated.h").write_text("\n".join(header), encoding="utf-8")
print(f"Generated {len(patches)} fixed patch definitions.")
if __name__ == "__main__":
generate()