From b4f07bc9be7eeef9aa0baafb6ba82e9aad4f6369 Mon Sep 17 00:00:00 2001 From: OV2 Date: Sat, 10 Sep 2011 16:20:00 +0200 Subject: [PATCH] Add bps soft-patching support --- memmap.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 8 deletions(-) diff --git a/memmap.cpp b/memmap.cpp index 5d27f355..3b7ebce1 100644 --- a/memmap.cpp +++ b/memmap.cpp @@ -3750,9 +3750,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; while(addr < size) { @@ -3807,8 +3808,8 @@ static bool8 ReadUPSPatch (Reader *r, long, int32 &rom_size) 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 - uint32 px_size = ReadUPSPointer(data, addr, size); - uint32 py_size = ReadUPSPointer(data, addr, size); + uint32 px_size = XPSdecode(data, addr, size); + uint32 py_size = XPSdecode(data, addr, 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 @@ -3820,7 +3821,7 @@ static bool8 ReadUPSPatch (Reader *r, long, int32 &rom_size) uint32 relative = 0; while(addr < size - 12) { - relative += ReadUPSPointer(data, addr, size); + relative += XPSdecode(data, addr, size); while(addr < size - 12) { uint8 x = data[addr++]; Memory.ROM[relative++] ^= x; @@ -3852,6 +3853,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 : uint32 { 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(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) { long v = 0; @@ -3969,7 +4065,7 @@ static int unzFindExtension (unzFile &file, const char *ext, bool restart, bool if (len >= l + 1 && name[len - l - 1] == '.' && strcasecmp(name + len - l, ext) == 0 && unzOpenCurrentFile(file) == UNZ_OK) { if (print) - printf("Using IPS or UPS patch %s", name); + printf("Using patch %s", name); return (port); } @@ -3994,9 +4090,70 @@ 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]; const char *n; + _splitpath(rom_filename, drive, dir, name, ext); + + // BPS + _makepath(fname, drive, dir, name, "bps"); + + if ((patch_file = OPEN_STREAM(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"); + } + } + } +#endif + + n = S9xGetFilename(".bps", IPS_DIR); + + if ((patch_file = OPEN_STREAM(n, "rb")) != NULL) + { + printf("Using BPS patch %s", n); + + ret = ReadBPSPatch(new fReader(patch_file), 0, rom_size); + CLOSE_STREAM(patch_file); + + if (ret) + { + printf("!\n"); + return; + } + else + printf(" failed!\n"); + } + // UPS - _splitpath(rom_filename, drive, dir, name, ext); _makepath(fname, drive, dir, name, "ups"); if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL) @@ -4058,7 +4215,6 @@ void CMemory::CheckForAnyPatch (const char *rom_filename, bool8 header, int32 &r // IPS - _splitpath(rom_filename, drive, dir, name, ext); _makepath(fname, drive, dir, name, "ips"); if ((patch_file = OPEN_STREAM(fname, "rb")) != NULL)