Add BPS soft-patching support (thanks qwertymodo!)

This commit is contained in:
Etienne Haarsma 2018-01-17 16:16:47 +01:00
parent 97e637fed8
commit a6e77b92c5

View File

@ -956,7 +956,7 @@ static bool8 ReadUPSPatch (Reader *, long, int32 &);
static long ReadInt (Reader *, unsigned); static long ReadInt (Reader *, unsigned);
static bool8 ReadIPSPatch (Reader *, long, int32 &); static bool8 ReadIPSPatch (Reader *, long, int32 &);
#ifdef UNZIP_SUPPORT #ifdef UNZIP_SUPPORT
static int unzFindExtension (unzFile &, const char *, bool restart = TRUE, bool print = TRUE); static int unzFindExtension (unzFile &, const char *, bool restart = TRUE, bool print = TRUE, bool allowExact = FALSE);
#endif #endif
// deinterleave // deinterleave
@ -1395,7 +1395,7 @@ uint32 CMemory::FileLoader (uint8 *buffer, const char *filename, int32 maxsize)
_makepath(fname, drive, dir, name, exts); _makepath(fname, drive, dir, name, exts);
int nFormat = FILE_DEFAULT; int nFormat = FILE_DEFAULT;
if (strcasecmp(ext, "zip") == 0) if (strcasecmp(ext, "zip") == 0 || strcasecmp(ext, "msu1") == 0)
nFormat = FILE_ZIP; nFormat = FILE_ZIP;
else else
if (strcasecmp(ext, "jma") == 0) if (strcasecmp(ext, "jma") == 0)
@ -2270,6 +2270,7 @@ void CMemory::InitROM (void)
Settings.SETA = 0; Settings.SETA = 0;
Settings.SRTC = FALSE; Settings.SRTC = FALSE;
Settings.BS = FALSE; Settings.BS = FALSE;
Settings.MSU1 = FALSE;
SuperFX.nRomBanks = CalculatedSize >> 15; SuperFX.nRomBanks = CalculatedSize >> 15;
@ -2446,6 +2447,9 @@ void CMemory::InitROM (void)
break; break;
} }
// MSU1
Settings.MSU1 = S9xMSU1ROMExists();
//// Map memory and calculate checksum //// Map memory and calculate checksum
Map_Initialize(); Map_Initialize();
@ -3348,7 +3352,7 @@ const char * CMemory::KartContents (void)
static char str[64]; static char str[64];
static const char *contents[3] = { "ROM", "ROM+RAM", "ROM+RAM+BAT" }; static const char *contents[3] = { "ROM", "ROM+RAM", "ROM+RAM+BAT" };
char chip[16]; char chip[20];
if (ROMType == 0 && !Settings.BS) if (ROMType == 0 && !Settings.BS)
return ("ROM"); return ("ROM");
@ -3394,6 +3398,9 @@ const char * CMemory::KartContents (void)
else else
strcpy(chip, ""); strcpy(chip, "");
if (Settings.MSU1)
sprintf(chip + strlen(chip), "+MSU-1");
sprintf(str, "%s%s", contents[(ROMType & 0xf) % 3], chip); sprintf(str, "%s%s", contents[(ROMType & 0xf) % 3], chip);
return (str); return (str);
@ -3904,9 +3911,10 @@ void CMemory::ApplyROMFixes (void)
} }
} }
// UPS % IPS // BPS % UPS % IPS
static uint32 ReadUPSPointer (const uint8 *data, unsigned &addr, unsigned size) // number decoding used for both BPS and UPS
static uint32 XPSdecode (const uint8 *data, unsigned &addr, unsigned size)
{ {
uint32 offset = 0, shift = 1; uint32 offset = 0, shift = 1;
while(addr < size) { while(addr < size) {
@ -3961,8 +3969,8 @@ static bool8 ReadUPSPatch (Reader *r, long, int32 &rom_size)
if(patch_crc32 != pp_crc32) { delete[] data; return false; } //patch is corrupted if(patch_crc32 != pp_crc32) { delete[] data; return false; } //patch is corrupted
if((rom_crc32 != px_crc32) && (rom_crc32 != py_crc32)) { delete[] data; return false; } //patch is for a different ROM if((rom_crc32 != px_crc32) && (rom_crc32 != py_crc32)) { delete[] data; return false; } //patch is for a different ROM
uint32 px_size = ReadUPSPointer(data, addr, size); uint32 px_size = XPSdecode(data, addr, size);
uint32 py_size = ReadUPSPointer(data, addr, size); uint32 py_size = XPSdecode(data, addr, size);
uint32 out_size = ((uint32) rom_size == px_size) ? py_size : px_size; uint32 out_size = ((uint32) rom_size == px_size) ? py_size : px_size;
if(out_size > CMemory::MAX_ROM_SIZE) { delete[] data; return false; } //applying this patch will overflow Memory.ROM buffer if(out_size > CMemory::MAX_ROM_SIZE) { delete[] data; return false; } //applying this patch will overflow Memory.ROM buffer
@ -3974,7 +3982,7 @@ static bool8 ReadUPSPatch (Reader *r, long, int32 &rom_size)
uint32 relative = 0; uint32 relative = 0;
while(addr < size - 12) { while(addr < size - 12) {
relative += ReadUPSPointer(data, addr, size); relative += XPSdecode(data, addr, size);
while(addr < size - 12) { while(addr < size - 12) {
uint8 x = data[addr++]; uint8 x = data[addr++];
Memory.ROM[relative++] ^= x; Memory.ROM[relative++] ^= x;
@ -4006,6 +4014,101 @@ static bool8 ReadUPSPatch (Reader *r, long, int32 &rom_size)
} }
} }
// header notes for UPS patches also apply to BPS
//
// logic taken from http://byuu.org/programming/bps and the accompanying source
//
static bool8 ReadBPSPatch (Reader *r, long, int32 &rom_size)
{
uint8 *data = new uint8[8 * 1024 * 1024]; //allocate a lot of memory, better safe than sorry ...
uint32 size = 0;
while(true) {
int value = r->get_char();
if(value == EOF) break;
data[size++] = value;
if(size >= 8 * 1024 * 1024) {
//prevent buffer overflow: SNES-made BPS patches should never be this big anyway ...
delete[] data;
return false;
}
}
/* 4-byte header + 1-byte input size + 1-byte output size + 1-byte metadata size
+ 4-byte unpatched CRC32 + 4-byte patched CRC32 + 4-byte patch CRC32 */
if(size < 19) { delete[] data; return false; } //patch is too small
uint32 addr = 0;
if(data[addr++] != 'B') { delete[] data; return false; } //patch has an invalid header
if(data[addr++] != 'P') { delete[] data; return false; } //...
if(data[addr++] != 'S') { delete[] data; return false; } //...
if(data[addr++] != '1') { delete[] data; return false; } //...
uint32 patch_crc32 = caCRC32(data, size - 4); //don't include patch CRC32 itself in CRC32 calculation
uint32 rom_crc32 = caCRC32(Memory.ROM, rom_size);
uint32 source_crc32 = (data[size - 12] << 0) + (data[size - 11] << 8) + (data[size - 10] << 16) + (data[size - 9] << 24);
uint32 target_crc32 = (data[size - 8] << 0) + (data[size - 7] << 8) + (data[size - 6] << 16) + (data[size - 5] << 24);
uint32 pp_crc32 = (data[size - 4] << 0) + (data[size - 3] << 8) + (data[size - 2] << 16) + (data[size - 1] << 24);
if(patch_crc32 != pp_crc32) { delete[] data; return false; } //patch is corrupted
if(rom_crc32 != source_crc32) { delete[] data; return false; } //patch is for a different ROM
uint32 source_size = XPSdecode(data, addr, size);
uint32 target_size = XPSdecode(data, addr, size);
uint32 metadata_size = XPSdecode(data, addr, size);
addr += metadata_size;
if(target_size > CMemory::MAX_ROM_SIZE) { delete[] data; return false; } //applying this patch will overflow Memory.ROM buffer
enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
uint32 outputOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0;
uint8 *patched_rom = new uint8[target_size];
memset(patched_rom,0,target_size);
while(addr < size - 12) {
uint32 length = XPSdecode(data, addr, size);
uint32 mode = length & 3;
length = (length >> 2) + 1;
switch((int)mode) {
case SourceRead:
while(length--) patched_rom[outputOffset++] = Memory.ROM[outputOffset];
break;
case TargetRead:
while(length--) patched_rom[outputOffset++] = data[addr++];
break;
case SourceCopy:
case TargetCopy:
int32 offset = XPSdecode(data, addr, size);
bool negative = offset & 1;
offset >>= 1;
if(negative) offset = -offset;
if(mode == SourceCopy) {
sourceRelativeOffset += offset;
while(length--) patched_rom[outputOffset++] = Memory.ROM[sourceRelativeOffset++];
} else {
targetRelativeOffset += offset;
while(length--) patched_rom[outputOffset++] = patched_rom[targetRelativeOffset++];
}
break;
}
}
delete[] data;
uint32 out_crc32 = caCRC32(patched_rom, target_size);
if(out_crc32 == target_crc32) {
memcpy(Memory.ROM, patched_rom, target_size);
rom_size = target_size;
delete[] patched_rom;
return true;
} else {
delete[] patched_rom;
fprintf(stderr, "WARNING: BPS patching failed.\nROM has not been altered.\n");
return false;
}
}
static long ReadInt (Reader *r, unsigned nbytes) static long ReadInt (Reader *r, unsigned nbytes)
{ {
long v = 0; long v = 0;
@ -4102,10 +4205,10 @@ static bool8 ReadIPSPatch (Reader *r, long offset, int32 &rom_size)
} }
#ifdef UNZIP_SUPPORT #ifdef UNZIP_SUPPORT
static int unzFindExtension (unzFile &file, const char *ext, bool restart, bool print) static int unzFindExtension (unzFile &file, const char *ext, bool restart, bool print, bool allowExact)
{ {
unz_file_info info; unz_file_info info;
int port, l = strlen(ext); int port, l = strlen(ext), e = allowExact ? 0 : 1;
if (restart) if (restart)
port = unzGoToFirstFile(file); port = unzGoToFirstFile(file);
@ -4120,10 +4223,10 @@ static int unzFindExtension (unzFile &file, const char *ext, bool restart, bool
unzGetCurrentFileInfo(file, &info, name, 128, NULL, 0, NULL, 0); unzGetCurrentFileInfo(file, &info, name, 128, NULL, 0, NULL, 0);
len = strlen(name); len = strlen(name);
if (len >= l + 1 && name[len - l - 1] == '.' && strcasecmp(name + len - l, ext) == 0 && unzOpenCurrentFile(file) == UNZ_OK) if (len >= l + e && name[len - l - 1] == '.' && strcasecmp(name + len - l, ext) == 0 && unzOpenCurrentFile(file) == UNZ_OK)
{ {
if (print) if (print)
printf("Using IPS or UPS patch %s", name); printf("Using patch %s", name);
return (port); return (port);
} }
@ -4142,21 +4245,31 @@ void CMemory::CheckForAnyPatch (const char *rom_filename, bool8 header, int32 &r
#ifdef GEKKO #ifdef GEKKO
int patchtype; int patchtype;
char patchpath[2][512]; char patchpath[3][512];
STREAM patchfile = NULL; STREAM patchfile = NULL;
long offset = header ? 512 : 0; long offset = header ? 512 : 0;
sprintf(patchpath[0], "%s%s.ips", browser.dir, Memory.ROMFilename); sprintf(patchpath[0], "%s%s.bps", browser.dir, Memory.ROMFilename);
sprintf(patchpath[1], "%s%s.ups", browser.dir, Memory.ROMFilename); sprintf(patchpath[1], "%s%s.ips", browser.dir, Memory.ROMFilename);
sprintf(patchpath[2], "%s%s.ups", browser.dir, Memory.ROMFilename);
for(patchtype=0; patchtype<2; patchtype++) for(patchtype=0; patchtype<3; patchtype++)
{ {
if ((patchfile = OPEN_STREAM(patchpath[patchtype], "rb")) != NULL) if ((patchfile = OPEN_STREAM(patchpath[patchtype], "rb")) != NULL)
{ {
if(patchtype == 0) switch(patchtype) {
case 0:
ReadBPSPatch(new fReader(patchfile), offset, rom_size);
break;
case 1:
ReadIPSPatch(new fReader(patchfile), offset, rom_size); ReadIPSPatch(new fReader(patchfile), offset, rom_size);
else break;
case 2:
ReadUPSPatch(new fReader(patchfile), 0, rom_size); ReadUPSPatch(new fReader(patchfile), 0, rom_size);
break;
default:
break;
}
CLOSE_STREAM(patchfile); CLOSE_STREAM(patchfile);
break; break;
} }
@ -4170,9 +4283,76 @@ void CMemory::CheckForAnyPatch (const char *rom_filename, bool8 header, int32 &r
char dir[_MAX_DIR + 1], drive[_MAX_DRIVE + 1], name[_MAX_FNAME + 1], ext[_MAX_EXT + 1], ips[_MAX_EXT + 3], fname[PATH_MAX + 1]; char dir[_MAX_DIR + 1], drive[_MAX_DRIVE + 1], name[_MAX_FNAME + 1], ext[_MAX_EXT + 1], ips[_MAX_EXT + 3], fname[PATH_MAX + 1];
const char *n; const char *n;
_splitpath(rom_filename, drive, dir, name, ext);
// BPS
_makepath(fname, drive, dir, name, "bps");
if ((patch_file = OPEN_FSTREAM(fname, "rb")) != NULL)
{
printf("Using BPS patch %s", fname);
ret = ReadBPSPatch(new fReader(patch_file), 0, rom_size);
CLOSE_STREAM(patch_file);
if (ret)
{
printf("!\n");
return;
}
else
printf(" failed!\n");
}
#ifdef UNZIP_SUPPORT
if (!strcasecmp(ext, "zip") || !strcasecmp(ext, ".zip"))
{
unzFile file = unzOpen(rom_filename);
if (file)
{
int port = unzFindExtension(file, "bps");
if (port == UNZ_OK)
{
printf(" in %s", rom_filename);
ret = ReadBPSPatch(new unzReader(file), offset, rom_size);
unzCloseCurrentFile(file);
if (ret)
printf("!\n");
else
printf(" failed!\n");
}
}
}
// Mercurial Magic (MSU-1 distribution pack)
if (strcasecmp(ext, "msu1") && strcasecmp(ext, ".msu1"))
{
_makepath(fname, drive, dir, name, "msu1");
unzFile msu1file = unzOpen(fname);
if (msu1file)
{
int port = unzFindExtension(msu1file, "bps");
if (port == UNZ_OK)
{
printf(" in %s", fname);
ret = ReadBPSPatch(new unzReader(file), offset, rom_size);
unzCloseCurrentFile(file);
if (ret)
printf("!\n");
else
printf(" failed!\n");
}
}
}
#endif
// UPS // UPS
_splitpath(rom_filename, drive, dir, name, ext);
_makepath(fname, drive, dir, name, "ups"); _makepath(fname, drive, dir, name, "ups");
if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL) if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL)
@ -4234,7 +4414,6 @@ void CMemory::CheckForAnyPatch (const char *rom_filename, bool8 header, int32 &r
// IPS // IPS
_splitpath(rom_filename, drive, dir, name, ext);
_makepath(fname, drive, dir, name, "ips"); _makepath(fname, drive, dir, name, "ips");
if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL) if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL)