ref: 5a7ddb327200295ce32be389fab3524b23eec55b
parent: 5206ac6836fa3f4d80b9c7046ec682b4a4c0072a
author: Rangi <[email protected]>
date: Sat Mar 26 13:13:50 EDT 2022
Port and document make_patch.c features needed for pokered
--- a/docs/vc_patch.md
+++ b/docs/vc_patch.md
@@ -75,15 +75,15 @@
Commands are interpreted with a series of arguments, separated by whitespace (spaces, tabs, or newlines). Leading and trailing whitespace is ignored; for example, "`{ hex @ 4 }`" is interpreted the same as "`{hex @ 4}`".
-Some commands may output a **value series**, which is a series of two-digit hexadecimal bytes separated by spaces, preceded by a decimal count: "<code>a*N*: <i>v1</i> <i>v2</i> [...] <i>vN</i></code>".
+Command names have variants to allow reproducing the exact formatting in a `.patch` file. If the command name is all lowercase, the output byte values use lowercase for hexadecimal digits A-F; if it is all uppercase, they use uppercase.
-Some command names have variants to allow reproducing the exact formatting in a `.patch` file. If the command name is all lowercase, the output byte values use lowercase for hexadecimal digits A-F; if it is all uppercase, they use uppercase. For commands which output a value series, if the command name ends in an underscore, a space is output after the colon preceding the values; if not, then it is not.
+Some commands may output a **value series**, which is a series of two-digit hexadecimal bytes separated by spaces, preceded by a decimal count and a colon "`:`": "<code>a*N*: <i>v1</i> <i>v2</i> [...] <i>vN</i></code>". These commands have additional variants: if the command name ends in a slash "`/`", the count and colon are not output; or else, if it ends in an underscore "`_`", a space is output after the colon; otherwise, the count and colon are output without a space.
**Arguments** evaluate to numeric values. They may be any of the following:
- Literal numbers in decimal (base 10, e.g. "`42`"), hexadecimal (base 16, e.g. "`0x2a`"), or octal (base 8, e.g. "`052`"). They may start with a plus sign "`+`". Numbers should not be negative.
- Comparison operators: "`==`" is 0, "`>`" is 1, "`<`" is 2, "`>=`" is 3, "`<=`" is 4, "`!=`" is 5, and "`||`" is 0x11.
-- Symbol names from the two `.sym` files provided to `make_patch` may evaluate as their bank-relative address, or their absolute offset in the ROM, depending on the command. They may also be followed by a plus sign and a literal number that gets added to the value.
+- Symbol names from the two `.sym` files provided to `make_patch` may evaluate as their relative address or their absolute offset, depending on the command. (Addresses are relative to the symbol's bank for ROM addresses, or to 0x8000, the start of all RAM, for RAM addresses.) They may also be followed by a plus sign and a literal number that gets added to the value.
- "`@`" evaluates as the address or absolute offset of the current patch/hook label, depending on the command.
Any other characters are output as-is.
@@ -117,8 +117,8 @@
### <code>{hex <i>arg</i>[ <i>padding</i>]}</code>
-Outputs its first argument as a hexadecimal number. An optional second argument is the minimum length in digits; values shorter than it will be padded with leading zeros. Symbol names or "`@`" are evaluated as their absolute offset.
+Outputs its first argument as a hexadecimal number. An optional second argument is the minimum length in digits; values shorter than it will be padded with leading zeros. Symbol names or "`@`" are evaluated as their absolute offset, or as their relative address if the command name ends in a tilde "`~`".
-For example, "`{hex 0xabcd 5}`" outputs "`0x0abcd`".
+For example, if "`{hex @}`" outputs "`0x6789`", then "`{hex @+1 5}`" outputs "`0x0678a`".
This command has extra variants to reproduce inconsistent output casing: "`Hex`" prints the last three digits in lowercase and the rest uppercase; "`HEx`" prints the last two digits in lowercase and the rest uppercase; "`hEX`" prints the last three digits in uppercase and the rest lowercase; and "`heX`" prints the last two digits in uppercase and the rest lowercase.
--- a/tools/make_patch.c
+++ b/tools/make_patch.c
@@ -50,7 +50,9 @@
size_t name_len = strlen(name) + 1;
struct Symbol *symbol = xmalloc(sizeof(*symbol) + name_len);
symbol->address = address;
- symbol->offset = bank > 0 && address < 0x8000 ? address + (bank - 1) * 0x4000 : address;
+ symbol->offset = address < 0x8000
+ ? (bank > 0 ? address + (bank - 1) * 0x4000 : address) // ROM addresses are relative to their bank
+ : address - 0x8000; // RAM addresses are relative to the start of all RAM
memcpy(symbol->name, name, name_len);
symbol->next = *symbols;
*symbols = symbol;
@@ -154,13 +156,22 @@
buffer_free(buffer);
}
+int strfind(const char *s, const char *list[], int count) {
+ for (int i = 0; i < count; i++) {
+ if (!strcmp(s, list[i])) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+#define vstrfind(s, ...) strfind(s, (const char *[]){__VA_ARGS__}, sizeof (const char *[]){__VA_ARGS__} / sizeof(const char *))
+
int parse_arg_value(const char *arg, bool absolute, const struct Symbol *symbols, const char *patch_name) {
// Comparison operators for "ConditionValueB" evaluate to their particular values
- static const char *comparisons[] = {"==", ">", "<", ">=", "<=", "!=", "||"};
- for (unsigned int i = 0; i < sizeof(comparisons) / sizeof(*comparisons); i++) {
- if (!strcmp(arg, comparisons[i])) {
- return i == 6 ? 0x11 : i; // "||" is 0x11
- }
+ int op = vstrfind(arg, "==", ">", "<", ">=", "<=", "!=", "||");
+ if (op >= 0) {
+ return op == 6 ? 0x11 : op; // "||" is 0x11
}
// Literal numbers evaluate to themselves
@@ -213,7 +224,7 @@
}
// Use the arguments
- if (!strcmp(command, "patch") || !strcmp(command, "PATCH") || !strcmp(command, "patch_") || !strcmp(command, "PATCH_")) {
+ if (vstrfind(command, "patch", "PATCH", "patch_", "PATCH_", "patch/", "PATCH/") >= 0) {
if (argc > 2) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
@@ -241,7 +252,9 @@
modified = c != getc(orig_rom);
fprintf(output, isupper((unsigned)command[0]) ? "0x%02X" : "0x%02x", c);
} else {
- fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", length);
+ if (command[strlen(command) - 1] != '/') {
+ fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", length);
+ }
for (int i = 0; i < length; i++) {
if (i) {
putc(' ', output);
@@ -255,11 +268,13 @@
fprintf(stderr, PROGRAM_NAME ": Warning: \"vc_patch %s\" doesn't alter the ROM\n", current_hook->name);
}
- } else if (!strcmp(command, "dws") || !strcmp(command, "DWS") || !strcmp(command, "dws_") || !strcmp(command, "DWS_")) {
+ } else if (vstrfind(command, "dws", "DWS", "dws_", "DWS_", "dws/", "DWS/") >= 0) {
if (argc < 1) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
- fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", argc * 2);
+ if (command[strlen(command) - 1] != '/') {
+ fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", argc * 2);
+ }
for (int i = 0; i < argc; i++) {
int value = parse_arg_value(argv[i], false, symbols, current_hook->name);
if (value > 0xffff) {
@@ -271,7 +286,7 @@
fprintf(output, isupper((unsigned)command[0]) ? "%02X %02X": "%02x %02x", value & 0xff, value >> 8);
}
- } else if (!strcmp(command, "db") || !strcmp(command, "DB") || !strcmp(command, "db_") || !strcmp(command, "DB_")) {
+ } else if (vstrfind(command, "db", "DB", "db_", "DB_", "db/", "DB/") >= 0) {
if (argc != 1) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
@@ -279,22 +294,24 @@
if (value > 0xff) {
error_exit("Error: Invalid value for \"%s\" argument: 0x%x\n", command, value);
}
- fputs(command[strlen(command) - 1] == '_' ? "a1: " : "a1:", output);
+ if (command[strlen(command) - 1] != '/') {
+ fputs(command[strlen(command) - 1] == '_' ? "a1: " : "a1:", output);
+ }
fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", value);
- } else if (!strcmp(command, "hex") || !strcmp(command, "HEX") || !strcmp(command, "HEx") || !strcmp(command, "Hex") || !strcmp(command, "heX") || !strcmp(command, "hEX")) {
+ } else if (vstrfind(command, "hex", "HEX", "HEx", "Hex", "heX", "hEX", "hex~", "HEX~", "HEx~", "Hex~", "heX~", "hEX~") >= 0) {
if (argc != 1 && argc != 2) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
- int value = parse_arg_value(argv[0], true, symbols, current_hook->name);
+ int value = parse_arg_value(argv[0], command[strlen(command) - 1] != '~', symbols, current_hook->name);
int padding = argc > 1 ? parse_number(argv[1], 0) : 2;
- if (!strcmp(command, "HEx")) {
+ if (vstrfind(command, "HEx", "HEx~") >= 0) {
fprintf(output, "0x%0*X%02x", padding - 2, value >> 8, value & 0xff);
- } else if (!strcmp(command, "Hex")) {
+ } else if (vstrfind(command, "Hex", "Hex~") >= 0) {
fprintf(output, "0x%0*X%03x", padding - 3, value >> 12, value & 0xfff);
- } else if (!strcmp(command, "heX")) {
+ } else if (vstrfind(command, "heX", "heX~") >= 0) {
fprintf(output, "0x%0*x%02X", padding - 2, value >> 8, value & 0xff);
- } else if (!strcmp(command, "hEX")) {
+ } else if (vstrfind(command, "hEX", "hEX~") >= 0) {
fprintf(output, "0x%0*x%03X", padding - 3, value >> 12, value & 0xfff);
} else {
fprintf(output, isupper((unsigned)command[0]) ? "0x%0*X" : "0x%0*x", padding, value);
--- a/vc/pokecrystal11.patch.template
+++ b/vc/pokecrystal11.patch.template
@@ -263,7 +263,7 @@
Mode = 6
Type = 0
Address = {hex @}
-MemAddress={hex hJoyPressed}
+MemAddress={hex~ hJoyPressed}
Fixcode={db NO_INPUT}
ConditionType = 0
ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wMenuSelection wMenuCursorY hJoyPressed hJoyPressed hJoyPressed hJoyPressed}
@@ -278,7 +278,7 @@
Mode = 6
Type = 0
Address = {hex @}
-MemAddress={hex hJoyPressed}
+MemAddress={hex~ hJoyPressed}
Fixcode={db NO_INPUT}
ConditionType = 0
ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wMenuCursorY wMapGroup wMapNumber wYCoord wXCoord hJoyPressed hJoyPressed hJoyPressed hJoyPressed}
@@ -321,7 +321,7 @@
Mode = 6
Type = 0
Address = {hex @}
-MemAddress={hex hJoyPressed}
+MemAddress={hex~ hJoyPressed}
Fixcode={db NO_INPUT}
ConditionType = 0
ConditionValueA = {dws_ wWindowStackPointer wWindowStackPointer+1 wMenuJoypad wMenuSelection wDexArrowCursorPosIndex hJoyPressed hJoyPressed hJoyPressed hJoyPressed}