Generate the correct Stadium base data for every ROM (#1010)

This commit is contained in:
Rangi 2022-09-30 16:42:29 -04:00 committed by GitHub
parent a9869f1896
commit 38578cc34d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 86 deletions

View File

@ -159,17 +159,10 @@ pokecrystal_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 0 -k 01 -l 0x33 -m 0x10
pokecrystal11_debug_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 1 -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
pokecrystal11_vc_opt = -Cjv -t PM_CRYSTAL -i BYTE -n 1 -k 01 -l 0x33 -m 0x10 -r 3 -p 0 pokecrystal11_vc_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
pokecrystal11_vc_base = us
pokecrystal_debug_base = dbg
pokecrystal11_debug_base = dbg
%.gbc: $$(%_obj) layout.link %.gbc: $$(%_obj) layout.link
$(RGBLINK) -n $*.sym -m $*.map -l layout.link -o $@ $(filter %.o,$^) $(RGBLINK) -n $*.sym -m $*.map -l layout.link -o $@ $(filter %.o,$^)
$(RGBFIX) $($*_opt) $@ $(RGBFIX) $($*_opt) $@
tools/stadium --base $($*_base) $@ tools/stadium $@
### LZ compression rules ### LZ compression rules

View File

@ -1,23 +1,18 @@
#define PROGRAM_NAME "stadium" #define PROGRAM_NAME "stadium"
#define USAGE_OPTS "[-h|--help] [-b|--base us|eu|dbg] pokecrystal.gbc" #define USAGE_OPTS "[-h|--help] [-e|--european] pokecrystal.gbc"
#include "common.h" #include "common.h"
enum Base { BASE_NONE, BASE_US, BASE_EU, BASE_DEBUG }; void parse_args(int argc, char *argv[], bool *european) {
void parse_args(int argc, char *argv[], enum Base *base) {
struct option long_options[] = { struct option long_options[] = {
{"base", required_argument, 0, 'b'}, {"european", no_argument, 0, 'e'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{0} {0}
}; };
for (int opt; (opt = getopt_long(argc, argv, "b:h", long_options)) != -1;) { for (int opt; (opt = getopt_long(argc, argv, "eh", long_options)) != -1;) {
switch (opt) { switch (opt) {
case 'b': case 'e':
*base = !strcmp(optarg, "us") ? BASE_US : *european = true;
!strcmp(optarg, "eu") ? BASE_EU :
!strcmp(optarg, "dbg") ? BASE_DEBUG :
BASE_NONE;
break; break;
case 'h': case 'h':
usage_exit(0); usage_exit(0);
@ -28,58 +23,110 @@ void parse_args(int argc, char *argv[], enum Base *base) {
} }
} }
// A matching ROM has 128 banks
#define NUMBANKS 128
// ROM banks are 0x4000 bytes
#define BANKSIZE 0x4000
// A matching ROM is 2 MB
#define ROMSIZE (NUMBANKS * BANKSIZE)
// The Game Boy cartridge header stores a global checksum at 0x014E-0x014F // The Game Boy cartridge header stores a global checksum at 0x014E-0x014F
#define GLOBALOFF 0x014E #define GLOBALOFF 0x014E
// "base" data; Crystal-only
#define BASESIZE 24 // ASCII "base" header (Crystal only)
// ASCII "N64PS3" header #define BASE10SIZE 6
uint8_t base10[BASE10SIZE] = {'b', 'a', 's', 'e', 1, 0};
// "base" + 2-byte version + 2-byte CRC
#define BASEHEADERSIZE (BASE10SIZE + 2)
// "base" + 2-byte version + 2-byte CRC + per-bank bit flags
#define BASEDATASIZE (BASEHEADERSIZE + NUMBANKS / 8)
// The base data is stored before the Stadium data
#define BASEDATAOFF (N64PS3DATAOFF - BASEDATASIZE)
// ASCII "N64PS3" header (Stadium G/S was the third Japanese Stadium release for N64)
#define N64PS3SIZE 6 #define N64PS3SIZE 6
// N64PS3 + CRC uint8_t n64ps3[N64PS3SIZE] = {'N', '6', '4', 'P', 'S', '3'};
#define HEADERSIZE (N64PS3SIZE + 2) // "N64PS3" + 2-byte CRC
// Checksum every half-bank #define N64PS3HEADERSIZE (N64PS3SIZE + 2)
#define CHECKSIZE 0x2000 // "N64PS3" + 2-byte CRC + per-half-bank 2-byte checksums
#define N64PS3DATASIZE (N64PS3HEADERSIZE + NUMBANKS * 2 * 2)
// The Stadium data is stored at the end of the ROM
#define N64PS3DATAOFF (ROMSIZE - N64PS3DATASIZE)
// The CRC polynomial value // The CRC polynomial value
#define CRC_POLY 0xC387 #define CRC_POLY 0xC387
// The CRC initial value (also used for checksums) // The CRC initial value (also used for checksums)
#define CRC_INIT 0xFEFE #define CRC_INIT 0xFEFE
// The CRC initial value for Crystal base data // The CRC initial value for Crystal base data
#define CRC_INIT_BASE 0xACDE #define CRC_INIT_BASE 0xACDE
// The CRC lookup table
// Base data format: "base", 1, version byte, CRC (big-endian), uint16_t crc_table[256];
// 16 bytes = a 128-bit mask of which banks Stadium can skip comparing
uint8_t us_base[BASESIZE] = {'b', 'a', 's', 'e', 1, 0, 0, 0,
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', 1, 1, 0, 0,
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', 1, 0, 0, 0,
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'};
#define SET_U16BE(file, off, v) do { \ #define SET_U16BE(file, off, v) do { \
file[(off) + 0] = (uint8_t)(((v) & 0xFF00) >> 8); \ file[(off) + 0] = (uint8_t)(((v) & 0xFF00) >> 8); \
file[(off) + 1] = (uint8_t)(((v) & 0x00FF) >> 0); \ file[(off) + 1] = (uint8_t)(((v) & 0x00FF) >> 0); \
} while (0) } while (0)
void calculate_checksums(uint8_t *file, int filesize, enum Base base) { // CRCs of every bank in the base ROM, crystal_base0.bin
int NUMCHECKS = filesize / CHECKSIZE; uint16_t base0_crcs[NUMBANKS] = {
int DATASIZE = HEADERSIZE + NUMCHECKS * 2; // 2 bytes per checksum 0x9650, 0x8039, 0x2D8F, 0xD75A, 0xAC50, 0x5D55, 0xE94B, 0x9886,
int ORIGIN = filesize - DATASIZE; // Stadium data goes at the end of the file 0x2A46, 0xB5AC, 0xC3D3, 0x79C4, 0xCE55, 0xA95E, 0xEF78, 0x9B50,
0x82BA, 0x8716, 0x5895, 0xAD33, 0x4EF0, 0xE434, 0xC521, 0xBFB1,
0xB352, 0xA497, 0xCA84, 0xD3F5, 0x3C79, 0xB61A, 0xAE1B, 0xF314,
0x00B3, 0x7C0A, 0x1018, 0x7F6B, 0x1CFF, 0x15AF, 0x4078, 0xE473,
0x081C, 0x4B9D, 0x2FFC, 0xD9D0, 0x2CBA, 0xCD8C, 0x004C, 0x773C,
0xF040, 0x3585, 0xF924, 0x6FD5, 0xC5E4, 0xD918, 0x1228, 0x1C86,
0x21C0, 0x77F3, 0x6206, 0x0110, 0x152F, 0x0F74, 0xCEDF, 0xBBFE,
0xE382, 0x5C15, 0xFD4D, 0x954C, 0xD2D9, 0xCA2F, 0x14B1, 0x9D2F,
0x172C, 0xEA0C, 0x4EAD, 0x604B, 0x0659, 0xF4C5, 0x4168, 0xD151,
0x58C7, 0x99BF, 0x77D3, 0xCDEC, 0x61B5, 0x1A48, 0xD614, 0x7FB0,
0x91D5, 0x812A, 0x812A, 0x82B2, 0xDCE2, 0x9067, 0x6DB3, 0x3DC7,
0xDCB8, 0xA1CE, 0x9C21, 0x4A23, 0xB50F, 0x63E6, 0xE78A, 0x9238,
0x644D, 0x1BD6, 0xB5B6, 0x1AB9, 0x9D07, 0xC849, 0x6992, 0x10CA,
0x4453, 0xA3A1, 0x5A18, 0xAFE0, 0x7F2B, 0xFC38, 0xFC38, 0xBA98,
0x5AEB, 0xFC38, 0xFC38, 0xFC38, 0xFC38, 0xEFAD, 0x6D83, 0xFC38
};
// CRCs of every bank in the European base ROM, crystal_base1.bin
uint16_t base1_crcs[NUMBANKS] = {
0x5416, 0xFD37, 0xC4A4, 0xBC37, 0x9458, 0xB489, 0xE94B, 0x9906,
0x2A46, 0xDEA9, 0x17F4, 0xF447, 0xCE55, 0xD843, 0xC5B2, 0xAE13,
0x4E91, 0x3984, 0xD984, 0xD02F, 0x77B8, 0x4D8D, 0x1F8C, 0x7185,
0xBA34, 0xA497, 0xE813, 0xFF97, 0x245E, 0xB61A, 0xCEF0, 0x8BF4,
0xA786, 0x4CE5, 0xA9B8, 0x1988, 0xEF53, 0x2A24, 0x4588, 0x6084,
0x2609, 0x4B9D, 0x8C33, 0xD9D0, 0x2CBA, 0xCD8C, 0xDA4F, 0xE020,
0xF040, 0x3585, 0x2B21, 0xAEEA, 0xC5E4, 0xD918, 0x1228, 0x1C86,
0x78B3, 0xF4B1, 0x7577, 0x0110, 0x152F, 0x0F74, 0xCCDD, 0x3444,
0x58A8, 0x1FB0, 0xDACE, 0x954C, 0xD2D9, 0xF7CB, 0xEE99, 0xA5F0,
0x172C, 0xEA0C, 0x4EAD, 0x604B, 0x0659, 0xF4C5, 0x4168, 0xD151,
0x58C7, 0x99BF, 0x77D3, 0xCDEC, 0x61B5, 0x1A48, 0xD614, 0x7FB0,
0x91D5, 0x812A, 0x812A, 0x82B2, 0x5C2C, 0x91E6, 0x79C5, 0xF2BF,
0xDCB8, 0xA1CE, 0x9C21, 0x579B, 0x4B59, 0x21F5, 0xB2B6, 0x58AD,
0xC91D, 0xB96F, 0x4DCE, 0xBA03, 0x9D07, 0x7A7E, 0xC77E, 0xB8AA,
0xF7E4, 0xA7A4, 0x22E8, 0xAFE0, 0xE0C8, 0xFC38, 0xFC38, 0x2277,
0x5AEB, 0xFC38, 0xFC38, 0x4314, 0x25B0, 0xCE7B, 0x12FA, 0xDD05
};
uint16_t calculate_checksum(uint16_t checksum, uint8_t *file, size_t size) {
for (size_t i = 0; i < size; i++) {
checksum += file[i];
}
return checksum;
}
uint16_t calculate_crc(uint16_t crc, uint8_t *file, size_t size) {
for (size_t i = 0; i < size; i++) {
crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ file[i]];
}
return crc;
}
void calculate_checksums(uint8_t *file, bool european) {
// Initialize the CRC table // Initialize the CRC table
uint16_t crc_table[256]; for (uint16_t i = 0; i < sizeof(crc_table); i++) {
for (int i = 0; i < 256; i++) {
uint16_t c = i;
uint16_t rem = 0; uint16_t rem = 0;
for (int y = 0; y < 8; y++) { for (uint16_t y = 0, c = i; y < 8; y++, c >>= 1) {
rem = (rem >> 1) ^ ((rem ^ c) & 1 ? CRC_POLY : 0); rem = (rem >> 1) ^ ((rem ^ c) & 1 ? CRC_POLY : 0);
c >>= 1;
} }
crc_table[i] = rem; crc_table[i] = rem;
} }
@ -87,56 +134,55 @@ void calculate_checksums(uint8_t *file, int filesize, enum Base base) {
// Clear the global checksum // Clear the global checksum
SET_U16BE(file, GLOBALOFF, 0); SET_U16BE(file, GLOBALOFF, 0);
// Write the appropriate base data, or none // Initialize the base data (this should be free space anyway)
int BASEOFF = ORIGIN - BASESIZE; memset(file + BASEDATAOFF, 0, BASEDATASIZE);
if (base == BASE_US) { memcpy(file + BASEDATAOFF, base10, BASE10SIZE);
memcpy(file + BASEOFF, us_base, BASESIZE);
} else if (base == BASE_EU) { // Use the appropriate base CRCs
memcpy(file + BASEOFF, eu_base, BASESIZE); uint16_t *base_crcs = base0_crcs;
} else if (base == BASE_DEBUG) { if (european) {
memcpy(file + BASEOFF, dbg_base, BASESIZE); base_crcs = base1_crcs;
file[BASEDATAOFF + BASE10SIZE - 1] = 1;
} }
// Calculate the CRC of the base data, or none // Calculate the base data bits using bank CRCs
if (base) { // Bits indicate if the bank CRC matches the base one
uint16_t crc = CRC_INIT_BASE; for (size_t i = 0; i < BASEDATASIZE - BASEHEADERSIZE; i++) {
for (int i = BASEOFF; i < BASEOFF + BASESIZE; i++) { uint8_t bits = 0;
crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ file[i]]; for (size_t j = 0; j < 8; j++) {
size_t bank = i * 8 + j;
uint16_t crc = calculate_crc(CRC_INIT, file + bank * BANKSIZE, BANKSIZE);
bits |= (crc == base_crcs[bank]) << j;
} }
SET_U16BE(file, BASEOFF + 6, crc); file[BASEDATAOFF + BASEHEADERSIZE + i] = bits;
} }
// Calculate the CRC of the base data
uint16_t crc = calculate_crc(CRC_INIT_BASE, file + BASEDATAOFF, BASEDATASIZE);
SET_U16BE(file, BASEDATAOFF + BASEHEADERSIZE - 2, crc);
// Initialize the Stadium data (this should be free space anyway) // Initialize the Stadium data (this should be free space anyway)
memset(file + ORIGIN, 0, DATASIZE); memset(file + N64PS3DATAOFF, 0, N64PS3DATASIZE);
memcpy(file + ORIGIN, n64ps3, N64PS3SIZE); memcpy(file + N64PS3DATAOFF, n64ps3, N64PS3SIZE);
// Calculate the half-bank checksums // Calculate the half-bank checksums
for (int i = 0; i < NUMCHECKS; i++) { for (size_t i = 0; i < NUMBANKS * 2; i++) {
uint16_t checksum = CRC_INIT; uint16_t checksum = calculate_checksum(CRC_INIT, file + i * BANKSIZE / 2, BANKSIZE / 2);
for (int j = 0; j < CHECKSIZE; j++) { SET_U16BE(file, N64PS3DATAOFF + N64PS3HEADERSIZE + i * 2, checksum);
checksum += file[i * CHECKSIZE + j];
}
SET_U16BE(file, ORIGIN + HEADERSIZE + i * 2, checksum);
} }
// Calculate the CRC of the half-bank checksums // Calculate the CRC of the half-bank checksums
uint16_t crc = CRC_INIT; crc = calculate_crc(CRC_INIT, file + N64PS3DATAOFF + N64PS3HEADERSIZE, N64PS3DATASIZE - N64PS3HEADERSIZE);
for (int i = ORIGIN + HEADERSIZE; i < ORIGIN + DATASIZE; i++) { SET_U16BE(file, N64PS3DATAOFF + N64PS3HEADERSIZE - 2, crc);
crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ file[i]];
}
SET_U16BE(file, ORIGIN + HEADERSIZE - 2, crc);
// Calculate the global checksum // Calculate the global checksum
uint16_t globalsum = 0; uint16_t globalsum = calculate_checksum(0, file, ROMSIZE);
for (int i = 0; i < filesize; i++) {
globalsum += file[i];
}
SET_U16BE(file, GLOBALOFF, globalsum); SET_U16BE(file, GLOBALOFF, globalsum);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
enum Base base = BASE_NONE; bool european = false;
parse_args(argc, argv, &base); parse_args(argc, argv, &european);
argc -= optind; argc -= optind;
argv += optind; argv += optind;
@ -147,7 +193,9 @@ int main(int argc, char *argv[]) {
char *filename = argv[0]; char *filename = argv[0];
long filesize; long filesize;
uint8_t *file = read_u8(filename, &filesize); uint8_t *file = read_u8(filename, &filesize);
calculate_checksums(file, filesize, base); if (filesize == ROMSIZE) {
calculate_checksums(file, european);
}
write_u8(filename, file, filesize); write_u8(filename, file, filesize);
return 0; return 0;
} }