Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pret
GitHub Repository: pret/pokered
Path: blob/master/tools/make_patch.c
1270 views
1
#define PROGRAM_NAME "make_patch"
2
#define USAGE_OPTS "values.sym patched.gbc original.gbc vc.patch.template vc.patch"
3
4
#include "common.h"
5
6
#include <ctype.h>
7
8
struct Buffer {
9
size_t item_size;
10
size_t size;
11
size_t capacity;
12
void *data;
13
};
14
15
struct Symbol {
16
struct Symbol *next;
17
unsigned int address;
18
unsigned int offset;
19
char name[]; // C99 FAM
20
};
21
22
struct Patch {
23
unsigned int offset;
24
unsigned int size;
25
};
26
27
struct Buffer *buffer_create(size_t item_size) {
28
struct Buffer *buffer = xmalloc(sizeof(*buffer));
29
buffer->item_size = item_size;
30
buffer->size = 0;
31
buffer->capacity = 0x10;
32
buffer->data = xmalloc(buffer->capacity * item_size);
33
return buffer;
34
}
35
36
void buffer_append(struct Buffer *buffer, const void *item) {
37
if (buffer->size >= buffer->capacity) {
38
buffer->capacity = (buffer->capacity + 1) * 2;
39
buffer->data = xrealloc(buffer->data, buffer->capacity * buffer->item_size);
40
}
41
memcpy((char *)buffer->data + (buffer->size++ * buffer->item_size), item, buffer->item_size);
42
}
43
44
void buffer_free(struct Buffer *buffer) {
45
free(buffer->data);
46
free(buffer);
47
}
48
49
void symbol_append(struct Symbol **symbols, const char *name, int bank, int address) {
50
size_t name_len = strlen(name) + 1;
51
struct Symbol *symbol = xmalloc(sizeof(*symbol) + name_len);
52
symbol->address = address;
53
symbol->offset = address < 0x8000
54
? (bank > 0 ? address + (bank - 1) * 0x4000 : address) // ROM addresses are relative to their bank
55
: address - 0x8000; // RAM addresses are relative to the start of all RAM
56
memcpy(symbol->name, name, name_len);
57
symbol->next = *symbols;
58
*symbols = symbol;
59
}
60
61
void symbol_free(struct Symbol *symbols) {
62
for (struct Symbol *next; symbols; symbols = next) {
63
next = symbols->next;
64
free(symbols);
65
}
66
}
67
68
const struct Symbol *symbol_find(const struct Symbol *symbols, const char *name) {
69
size_t name_len = strlen(name);
70
for (const struct Symbol *symbol = symbols; symbol; symbol = symbol->next) {
71
size_t sym_name_len = strlen(symbol->name);
72
if (name_len > sym_name_len) {
73
continue;
74
}
75
const char *sym_name = symbol->name;
76
if (name[0] == '.') {
77
// If `name` is a local label, compare it to the local part of `symbol->name`
78
sym_name += sym_name_len - name_len;
79
}
80
if (!strcmp(sym_name, name)) {
81
return symbol;
82
}
83
}
84
error_exit("Error: Unknown symbol: \"%s\"\n", name);
85
}
86
87
const struct Symbol *symbol_find_cat(const struct Symbol *symbols, const char *prefix, const char *suffix) {
88
char *sym_name = xmalloc(strlen(prefix) + strlen(suffix) + 1);
89
sprintf(sym_name, "%s%s", prefix, suffix);
90
const struct Symbol *symbol = symbol_find(symbols, sym_name);
91
free(sym_name);
92
return symbol;
93
}
94
95
int parse_number(const char *input, int base) {
96
char *endptr;
97
int n = (int)strtol(input, &endptr, base);
98
if (endptr == input || *endptr || n < 0) {
99
error_exit("Error: Cannot parse number: \"%s\"\n", input);
100
}
101
return n;
102
}
103
104
void parse_symbol_value(char *input, int *restrict bank, int *restrict address) {
105
char *colon = strchr(input, ':');
106
if (colon) {
107
*colon++ = '\0';
108
*bank = parse_number(input, 16);
109
*address = parse_number(colon, 16);
110
} else {
111
*bank = 0;
112
*address = parse_number(input, 16);
113
}
114
}
115
116
struct Symbol *parse_symbols(const char *filename) {
117
FILE *file = xfopen(filename, 'r');
118
struct Buffer *buffer = buffer_create(1);
119
120
enum { SYM_PRE, SYM_VALUE, SYM_SPACE, SYM_NAME } state = SYM_PRE;
121
int bank = 0;
122
int address = 0;
123
struct Symbol *symbols = NULL;
124
125
for (;;) {
126
int c = getc(file);
127
if (c == EOF || c == '\n' || c == '\r' || c == ';' || (state == SYM_NAME && (c == ' ' || c == '\t'))) {
128
if (state == SYM_NAME) {
129
// The symbol name has ended; append the buffered symbol
130
buffer_append(buffer, &(char []){'\0'});
131
symbol_append(&symbols, buffer->data, bank, address);
132
}
133
// Skip to the next line, ignoring anything after the symbol value and name
134
state = SYM_PRE;
135
while (c != EOF && c != '\n' && c != '\r') {
136
c = getc(file);
137
}
138
if (c == EOF) {
139
break;
140
}
141
} else if (c != ' ' && c != '\t') {
142
if (state == SYM_PRE || state == SYM_SPACE) {
143
// The symbol value or name has started; buffer its contents
144
if (++state == SYM_NAME) {
145
// The symbol name has started; parse the buffered value
146
buffer_append(buffer, &(char []){'\0'});
147
parse_symbol_value(buffer->data, &bank, &address);
148
}
149
buffer->size = 0;
150
}
151
buffer_append(buffer, &c);
152
} else if (state == SYM_VALUE) {
153
// The symbol value has ended; wait to see if a name comes after it
154
state = SYM_SPACE;
155
}
156
}
157
158
fclose(file);
159
buffer_free(buffer);
160
return symbols;
161
}
162
163
int strfind(const char *s, const char *list[], int count) {
164
for (int i = 0; i < count; i++) {
165
if (!strcmp(s, list[i])) {
166
return i;
167
}
168
}
169
return -1;
170
}
171
172
#define vstrfind(s, ...) strfind(s, (const char *[]){__VA_ARGS__}, COUNTOF((const char *[]){__VA_ARGS__}))
173
174
int parse_arg_value(const char *arg, bool absolute, const struct Symbol *symbols, const char *patch_name) {
175
// Comparison operators for "ConditionValueB" evaluate to their particular values
176
int op = vstrfind(arg, "==", ">", "<", ">=", "<=", "!=", "||");
177
if (op >= 0) {
178
return op == 6 ? 0x11 : op; // "||" is 0x11
179
}
180
181
// Literal numbers evaluate to themselves
182
if (isdigit((unsigned)arg[0]) || arg[0] == '+') {
183
return parse_number(arg, 0);
184
}
185
186
// Symbols may take the low or high part
187
enum { SYM_WHOLE, SYM_LOW, SYM_HIGH } part = SYM_WHOLE;
188
if (arg[0] == '<') {
189
part = SYM_LOW;
190
arg++;
191
} else if (arg[0] == '>') {
192
part = SYM_HIGH;
193
arg++;
194
}
195
196
// Symbols evaluate to their offset or address, plus an optional offset mod
197
int offset_mod = 0;
198
char *plus = strchr(arg, '+');
199
if (plus) {
200
offset_mod = parse_number(plus, 0);
201
*plus = '\0';
202
}
203
204
// Symbols evaluate to their offset or address
205
const char *sym_name = !strcmp(arg, "@") ? patch_name : arg; // "@" is the current patch label
206
const struct Symbol *symbol = symbol_find(symbols, sym_name);
207
208
int value = (absolute ? symbol->offset : symbol->address) + offset_mod;
209
return part == SYM_LOW ? value & 0xff : part == SYM_HIGH ? value >> 8 : value;
210
}
211
212
void interpret_command(char *command, const struct Symbol *current_hook, const struct Symbol *symbols, struct Buffer *patches, FILE *restrict new_rom, FILE *restrict orig_rom, FILE *restrict output) {
213
// Strip all leading spaces and all but one trailing space
214
int x = 0;
215
for (int i = 0; command[i]; i++) {
216
if (!isspace((unsigned)command[i]) || (i > 0 && !isspace((unsigned)command[i - 1]))) {
217
command[x++] = command[i];
218
}
219
}
220
command[x - (x > 0 && isspace((unsigned)command[x - 1]))] = '\0';
221
222
// Count the arguments
223
int argc = 0;
224
for (const char *c = command; *c; c++) {
225
if (isspace((unsigned)*c)) {
226
argc++;
227
}
228
}
229
230
// Get the arguments
231
char *argv[argc]; // VLA
232
char *arg = command;
233
for (int i = 0; i < argc; i++) {
234
while (*arg && !isspace((unsigned)*arg)) {
235
arg++;
236
}
237
if (!*arg) {
238
break;
239
}
240
*arg++ = '\0';
241
argv[i] = arg;
242
}
243
244
// Use the arguments
245
if (vstrfind(command, "patch", "PATCH", "patch_", "PATCH_", "patch/", "PATCH/") >= 0) {
246
if (argc > 2) {
247
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
248
}
249
if (!current_hook) {
250
error_exit("Error: No current patch for command: \"%s\"\n", command);
251
}
252
int current_offset = current_hook->offset + (argc > 0 ? parse_number(argv[0], 0) : 0);
253
if (fseek(orig_rom, current_offset, SEEK_SET)) {
254
error_exit("Error: Cannot seek to \"vc_patch %s\" in the original ROM\n", current_hook->name);
255
}
256
if (fseek(new_rom, current_offset, SEEK_SET)) {
257
error_exit("Error: Cannot seek to \"vc_patch %s\" in the new ROM\n", current_hook->name);
258
}
259
int length;
260
if (argc == 2) {
261
length = parse_number(argv[1], 0);
262
} else {
263
const struct Symbol *current_hook_end = symbol_find_cat(symbols, current_hook->name, "_End");
264
length = current_hook_end->offset - current_offset;
265
}
266
buffer_append(patches, &(struct Patch){current_offset, length});
267
bool modified = false;
268
if (length == 1) {
269
int c = getc(new_rom);
270
modified = c != getc(orig_rom);
271
fprintf(output, isupper((unsigned)command[0]) ? "0x%02X" : "0x%02x", c);
272
} else {
273
if (command[strlen(command) - 1] != '/') {
274
fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", length);
275
}
276
for (int i = 0; i < length; i++) {
277
if (i) {
278
putc(' ', output);
279
}
280
int c = getc(new_rom);
281
modified |= c != getc(orig_rom);
282
fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", c);
283
}
284
}
285
if (!modified) {
286
fprintf(stderr, PROGRAM_NAME ": Warning: \"vc_patch %s\" doesn't alter the ROM\n", current_hook->name);
287
}
288
289
} else if (vstrfind(command, "dws", "DWS", "dws_", "DWS_", "dws/", "DWS/") >= 0) {
290
if (argc < 1) {
291
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
292
}
293
if (command[strlen(command) - 1] != '/') {
294
fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", argc * 2);
295
}
296
for (int i = 0; i < argc; i++) {
297
int value = parse_arg_value(argv[i], false, symbols, current_hook->name);
298
if (value > 0xffff) {
299
error_exit("Error: Invalid value for \"%s\" argument: 0x%x\n", command, value);
300
}
301
if (i) {
302
putc(' ', output);
303
}
304
fprintf(output, isupper((unsigned)command[0]) ? "%02X %02X": "%02x %02x", value & 0xff, value >> 8);
305
}
306
307
} else if (vstrfind(command, "db", "DB", "db_", "DB_", "db/", "DB/") >= 0) {
308
if (argc != 1) {
309
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
310
}
311
int value = parse_arg_value(argv[0], false, symbols, current_hook->name);
312
if (value > 0xff) {
313
error_exit("Error: Invalid value for \"%s\" argument: 0x%x\n", command, value);
314
}
315
if (command[strlen(command) - 1] != '/') {
316
fputs(command[strlen(command) - 1] == '_' ? "a1: " : "a1:", output);
317
}
318
fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", value);
319
320
} else if (vstrfind(command, "hex", "HEX", "HEx", "Hex", "heX", "hEX", "hex~", "HEX~", "HEx~", "Hex~", "heX~", "hEX~") >= 0) {
321
if (argc != 1 && argc != 2) {
322
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
323
}
324
int value = parse_arg_value(argv[0], command[strlen(command) - 1] != '~', symbols, current_hook->name);
325
int padding = argc > 1 ? parse_number(argv[1], 0) : 2;
326
if (vstrfind(command, "HEx", "HEx~") >= 0) {
327
fprintf(output, "0x%0*X%02x", padding - 2, value >> 8, value & 0xff);
328
} else if (vstrfind(command, "Hex", "Hex~") >= 0) {
329
fprintf(output, "0x%0*X%03x", padding - 3, value >> 12, value & 0xfff);
330
} else if (vstrfind(command, "heX", "heX~") >= 0) {
331
fprintf(output, "0x%0*x%02X", padding - 2, value >> 8, value & 0xff);
332
} else if (vstrfind(command, "hEX", "hEX~") >= 0) {
333
fprintf(output, "0x%0*x%03X", padding - 3, value >> 12, value & 0xfff);
334
} else {
335
fprintf(output, isupper((unsigned)command[0]) ? "0x%0*X" : "0x%0*x", padding, value);
336
}
337
338
} else {
339
error_exit("Error: Unknown command: \"%s\"\n", command);
340
}
341
}
342
343
void skip_to_next_line(FILE *restrict input, FILE *restrict output) {
344
for (int c = getc(input); c != EOF; c = getc(input)) {
345
putc(c, output);
346
if (c == '\n' || c == '\r') {
347
break;
348
}
349
}
350
}
351
352
struct Buffer *process_template(const char *template_filename, const char *patch_filename, FILE *restrict new_rom, FILE *restrict orig_rom, const struct Symbol *symbols) {
353
FILE *input = xfopen(template_filename, 'r');
354
FILE *output = xfopen(patch_filename, 'w');
355
356
struct Buffer *patches = buffer_create(sizeof(struct Patch));
357
struct Buffer *buffer = buffer_create(1);
358
359
// The ROM checksum will always differ
360
buffer_append(patches, &(struct Patch){0x14e, 2});
361
362
// Fill in the template
363
const struct Symbol *current_hook = NULL;
364
for (int c = getc(input); c != EOF; c = getc(input)) {
365
switch (c) {
366
case ';':
367
// ";" comments until the end of the line
368
putc(c, output);
369
skip_to_next_line(input, output);
370
break;
371
372
case '{':
373
// "{...}" is a template command; buffer its contents
374
buffer->size = 0;
375
for (c = getc(input); c != EOF && c != '}'; c = getc(input)) {
376
buffer_append(buffer, &c);
377
}
378
buffer_append(buffer, &(char []){'\0'});
379
// Interpret the command in the context of the current patch
380
interpret_command(buffer->data, current_hook, symbols, patches, new_rom, orig_rom, output);
381
break;
382
383
case '[':
384
// "[...]" is a patch label; buffer its contents
385
putc(c, output);
386
bool alternate = false;
387
buffer->size = 0;
388
for (c = getc(input); c != EOF; c = getc(input)) {
389
if (!alternate && c == '@') {
390
// "@" designates an alternate name for the ".VC_" label
391
alternate = true;
392
buffer->size = 0;
393
} else if (c == ']') {
394
putc(c, output);
395
break;
396
} else {
397
if (!alternate) {
398
putc(c, output);
399
if (!isalnum(c) && c != '_') {
400
// Convert non-identifier characters to underscores
401
c = '_';
402
}
403
}
404
buffer_append(buffer, &c);
405
}
406
}
407
buffer_append(buffer, &(char []){'\0'});
408
// The current patch should have a corresponding ".VC_" label
409
current_hook = symbol_find_cat(symbols, ".VC_", buffer->data);
410
skip_to_next_line(input, output);
411
break;
412
413
default:
414
putc(c, output);
415
}
416
}
417
418
rewind(orig_rom);
419
rewind(new_rom);
420
421
fclose(input);
422
fclose(output);
423
buffer_free(buffer);
424
return patches;
425
}
426
427
int compare_patch(const void *patch1, const void *patch2) {
428
unsigned int offset1 = ((const struct Patch *)patch1)->offset;
429
unsigned int offset2 = ((const struct Patch *)patch2)->offset;
430
return (offset1 > offset2) - (offset1 < offset2);
431
}
432
433
bool verify_completeness(FILE *restrict orig_rom, FILE *restrict new_rom, struct Buffer *patches) {
434
qsort(patches->data, patches->size, patches->item_size, compare_patch);
435
for (unsigned int offset = 0, index = 0; ; offset++) {
436
int orig_byte = getc(orig_rom);
437
int new_byte = getc(new_rom);
438
if (orig_byte == EOF || new_byte == EOF) {
439
return orig_byte == new_byte;
440
}
441
struct Patch *patch = &((struct Patch *)patches->data)[index];
442
if (index < patches->size && patch->offset == offset) {
443
if (fseek(orig_rom, patch->size, SEEK_CUR)) {
444
return false;
445
}
446
if (fseek(new_rom, patch->size, SEEK_CUR)) {
447
return false;
448
}
449
offset += patch->size;
450
index++;
451
} else if (orig_byte != new_byte) {
452
fprintf(stderr, PROGRAM_NAME ": Warning: Unpatched difference at offset: 0x%x\n", offset);
453
fprintf(stderr, " Original ROM value: 0x%02x\n", orig_byte);
454
fprintf(stderr, " Patched ROM value: 0x%02x\n", new_byte);
455
fprintf(stderr, " Current patch offset: 0x%06x\n", patch->offset);
456
return false;
457
}
458
}
459
}
460
461
int main(int argc, char *argv[]) {
462
if (argc != 6) {
463
usage_exit(1);
464
}
465
466
struct Symbol *symbols = parse_symbols(argv[1]);
467
468
FILE *new_rom = xfopen(argv[2], 'r');
469
FILE *orig_rom = xfopen(argv[3], 'r');
470
struct Buffer *patches = process_template(argv[4], argv[5], new_rom, orig_rom, symbols);
471
472
if (!verify_completeness(orig_rom, new_rom, patches)) {
473
fprintf(stderr, PROGRAM_NAME ": Warning: Not all ROM differences are defined by \"%s\"\n", argv[5]);
474
}
475
476
symbol_free(symbols);
477
fclose(new_rom);
478
fclose(orig_rom);
479
buffer_free(patches);
480
return 0;
481
}
482
483