diff --git a/Makefile b/Makefile index e791012a0..d940b1592 100644 --- a/Makefile +++ b/Makefile @@ -116,9 +116,16 @@ pokecrystal_au_opt = -Cjv -t PM_CRYSTAL -i BYTU -n 0 -k 01 -l 0x33 -m 0x10 pokecrystal_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10 -r 3 -p 0 pokecrystal11_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 1 -k 01 -l 0x33 -m 0x10 -r 3 -p 0 +pokecrystal_base = us +pokecrystal11_base = us +pokecrystal_au_base = us +pokecrystal_debug_base = dbg +pokecrystal11_debug_base = dbg + %.gbc: $$(%_obj) layout.link $(RGBLINK) -n $*.sym -m $*.map -l layout.link -o $@ $(filter %.o,$^) $(RGBFIX) $($*_opt) $@ + tools/stadium --base $($*_base) $@ ### LZ compression rules diff --git a/layout.link b/layout.link index 4201de74a..1c248e8c1 100644 --- a/layout.link +++ b/layout.link @@ -306,7 +306,7 @@ ROMX $7e "Crystal Events" ROMX $7f org $7de0 - "Mobile Stadium 2" + "Stadium 2 Checksums" WRAM0 "Stack" "Audio RAM" diff --git a/main.asm b/main.asm index e535651d7..3a83ccbbe 100644 --- a/main.asm +++ b/main.asm @@ -724,20 +724,12 @@ INCLUDE "engine/events/battle_tower/load_trainer.asm" INCLUDE "engine/events/odd_egg.asm" -SECTION "Mobile Stadium 2", ROMX +SECTION "Stadium 2 Checksums", ROMX[$7DE0], BANK[$7F] -if DEF(_CRYSTAL_AU) -INCBIN "mobile/stadium/stadium2_au.bin" -elif DEF(_CRYSTAL11) -if DEF(_DEBUG) -INCBIN "mobile/stadium/stadium2_11_debug.bin" -else -INCBIN "mobile/stadium/stadium2_11.bin" -endc -else -if DEF(_DEBUG) -INCBIN "mobile/stadium/stadium2_debug.bin" -else -INCBIN "mobile/stadium/stadium2.bin" -endc -endc +; The end of the ROM is taken up by checksums of the content, apparently used +; by Pokémon Stadium 2 due to the checksums' "N64PS3" header. (In Japan, +; Pokémon Stadium Gold and Silver was the third Stadium release for N64.) +; This SECTION reserves space for those checksums. +; If it is removed, also remove the "tools/stadium" command in the Makefile. + + ds $220 diff --git a/mobile/stadium/stadium2.bin b/mobile/stadium/stadium2.bin deleted file mode 100644 index 155e8bab8..000000000 Binary files a/mobile/stadium/stadium2.bin and /dev/null differ diff --git a/mobile/stadium/stadium2_11.bin b/mobile/stadium/stadium2_11.bin deleted file mode 100644 index 2273d94f3..000000000 Binary files a/mobile/stadium/stadium2_11.bin and /dev/null differ diff --git a/mobile/stadium/stadium2_11_debug.bin b/mobile/stadium/stadium2_11_debug.bin deleted file mode 100644 index 6d838e845..000000000 Binary files a/mobile/stadium/stadium2_11_debug.bin and /dev/null differ diff --git a/mobile/stadium/stadium2_au.bin b/mobile/stadium/stadium2_au.bin deleted file mode 100644 index 79cb83ac0..000000000 Binary files a/mobile/stadium/stadium2_au.bin and /dev/null differ diff --git a/mobile/stadium/stadium2_debug.bin b/mobile/stadium/stadium2_debug.bin deleted file mode 100644 index 9a63e4623..000000000 Binary files a/mobile/stadium/stadium2_debug.bin and /dev/null differ diff --git a/tools/Makefile b/tools/Makefile index 5dd3139fa..f8a93429f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -10,7 +10,8 @@ tools := \ palette \ pokemon_animation \ pokemon_animation_graphics \ - gfx + gfx \ + stadium all: $(tools) @: diff --git a/tools/stadium.c b/tools/stadium.c new file mode 100644 index 000000000..796b7fc22 --- /dev/null +++ b/tools/stadium.c @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +// The Game Boy cartridge header stores a global checksum at 0x014E-0x014F +#define GLOBALOFF 0x014E +// "base" data; Crystal-only +#define BASESIZE 24 +// ASCII "N64PS3" header +#define N64PS3SIZE 6 +// N64PS3 + CRC +#define HEADERSIZE (N64PS3SIZE + 2) +// Checksum every half-bank +#define CHECKSIZE 0x2000 +// The CRC initial value (also used for checksums) +#define CRC_INIT 0xFEFE +// The CRC polynomial value +#define CRC_POLY 0xC387 + +typedef enum Base { BASE_NONE, BASE_US, BASE_EU, BASE_DEBUG } Base; + +uint8_t us_base[BASESIZE] = {'b', 'a', 's', 'e', + 0x01, 0x00, 0xBF, 0x6B, 0x40, 0x11, 0x00, 0x22, 0x00, 0x3A, + 0xF3, 0x38, 0x18, 0xFF, 0xFF, 0x0F, 0x07, 0x10, 0x68, 0x07}; + +uint8_t eu_base[BASESIZE] = {'b', 'a', 's', 'e', + 0x01, 0x01, 0x1E, 0xCF, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0C, + 0xA3, 0x38, 0x00, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x14}; + +uint8_t dbg_base[BASESIZE] = {'b', 'a', 's', 'e', + 0x01, 0x00, 0x07, 0x82, 0x40, 0x10, 0x00, 0x22, 0x00, 0x3A, + 0xE3, 0x38, 0x00, 0xFF, 0xFF, 0x07, 0x07, 0x10, 0x68, 0x06}; + +uint8_t n64ps3[N64PS3SIZE] = {'N', '6', '4', 'P', 'S', '3'}; + +static void usage(void) { + fprintf(stderr, "Usage: stadium [-h|--help] [-b|--base us|eu|dbg] romfile\n"); +} + +void parse_args(int argc, char *argv[], Base *b) { + struct option long_options[] = { + {"base", required_argument, 0, 'b'}, + {"help", no_argument, 0, 'h'}, + {0} + }; + for (int opt = 0; opt != -1;) { + switch (opt = getopt_long(argc, argv, "hb:", long_options)) { + case 'h': + usage(); + exit(0); + break; + case 'b': + *b = !strcmp(optarg, "us") ? BASE_US : + !strcmp(optarg, "eu") ? BASE_EU : + !strcmp(optarg, "dbg") ? BASE_DEBUG : + BASE_NONE; + break; + case 0: + case -1: + break; + default: + usage(); + exit(1); + break; + } + } +} + +#define SET_U16BE(file, off, v) do { \ + file[(off) + 0] = (uint8_t)(((v) & 0xFF00) >> 8); \ + file[(off) + 1] = (uint8_t)(((v) & 0x00FF) >> 0); \ +} while (0) + +void calculate_checksums(uint8_t *file, int filesize, Base base) { + int NUMCHECKS = filesize / CHECKSIZE; + int DATASIZE = HEADERSIZE + NUMCHECKS * 2; // 2 bytes per checksum + int ORIGIN = filesize - DATASIZE; // Stadium data goes at the end of the file + + // Clear the global checksum + SET_U16BE(file, GLOBALOFF, 0); + + // Write the appropriate base data, or none + int BASEOFF = ORIGIN - BASESIZE; + if (base == BASE_US) { + memcpy(file + BASEOFF, us_base, BASESIZE); + } else if (base == BASE_EU) { + memcpy(file + BASEOFF, eu_base, BASESIZE); + } else if (base == BASE_DEBUG) { + memcpy(file + BASEOFF, dbg_base, BASESIZE); + } + + // Initialize the Stadium data (this should be free space anyway) + memset(file + ORIGIN, 0, DATASIZE); + memcpy(file + ORIGIN, n64ps3, N64PS3SIZE); + + // Calculate the half-bank checksums + for (int i = 0; i < NUMCHECKS; i++) { + uint16_t checksum = CRC_INIT; + for (int j = 0; j < CHECKSIZE; j++) { + checksum += file[i * CHECKSIZE + j]; + } + SET_U16BE(file, ORIGIN + HEADERSIZE + i * 2, checksum); + } + + // Initialize the CRC table + uint16_t crc_table[256]; + for (int i = 0; i < 256; i++) { + uint16_t c = i; + uint16_t rem = 0; + for (int y = 0; y < 8; y++) { + rem = (rem >> 1) ^ ((rem ^ c) & 1 ? CRC_POLY : 0); + c >>= 1; + } + crc_table[i] = rem; + } + + // Calculate the CRC of the half-bank checksums + uint16_t crc = CRC_INIT; + for (int i = ORIGIN + HEADERSIZE; i < ORIGIN + DATASIZE; i++) { + crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ file[i]]; + } + SET_U16BE(file, ORIGIN + HEADERSIZE - 2, crc); + + // Calculate the global checksum + uint16_t globalsum = 0; + for (int i = 0; i < filesize; i++) { + globalsum += file[i]; + } + SET_U16BE(file, GLOBALOFF, globalsum); +} + +int main(int argc, char *argv[]) { + Base base = BASE_NONE; + parse_args(argc, argv, &base); + + argc -= optind; + argv += optind; + if (argc < 1) { + usage(); + exit(1); + } + + char *filename = argv[0]; + int filesize; + uint8_t *file = read_u8(filename, &filesize); + calculate_checksums(file, filesize, base); + write_u8(filename, file, filesize); + + return 0; +}