diff --git a/docs/vc_patch.md b/docs/vc_patch.md index b3e6fc137..cc0e91635 100644 --- a/docs/vc_patch.md +++ b/docs/vc_patch.md @@ -75,15 +75,15 @@ Patch names may designate an alternate for the label with an at-sign "`@`". This 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: "a*N*: v1 v2 [...] vN". +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 "`:`": "a*N*: v1 v2 [...] vN". 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 @@ For example, "`{db 0xEF}`" outputs "`a1:ef`". ### {hex arg[ padding]} -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. diff --git a/tools/make_patch.c b/tools/make_patch.c index 136de0d54..29d798f61 100644 --- a/tools/make_patch.c +++ b/tools/make_patch.c @@ -50,7 +50,9 @@ void symbol_append(struct Symbol **symbols, const char *name, int bank, int addr 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 @@ void parse_symbols(const char *filename, struct Symbol **symbols) { 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 @@ void interpret_command(char *command, const struct Symbol *current_hook, const s } // 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 @@ void interpret_command(char *command, const struct Symbol *current_hook, const s 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 @@ void interpret_command(char *command, const struct Symbol *current_hook, const s 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 @@ void interpret_command(char *command, const struct Symbol *current_hook, const s 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 @@ void interpret_command(char *command, const struct Symbol *current_hook, const s 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); diff --git a/vc/pokecrystal11.patch.template b/vc/pokecrystal11.patch.template index 134a51520..0a871e1cf 100644 --- a/vc/pokecrystal11.patch.template +++ b/vc/pokecrystal11.patch.template @@ -263,7 +263,7 @@ Fixcode={patch} 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 @@ ConditionValueC = {dws_ 0xdd 0xd3 A_BUTTON 0x 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 @@ Fixcode={patch} 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}