diff --git a/docs/ssnes.1 b/docs/ssnes.1 index fc3917cc85..e99771fd55 100644 --- a/docs/ssnes.1 +++ b/docs/ssnes.1 @@ -192,6 +192,12 @@ If this flag is not specified, SSNES will look for a .ups file with same basenam Attempts to apply a BPS patch to the current ROM image. No files are altered. If this flag is not specified, SSNES will look for a .bps file with same basename as ROM specified. +.TP +\fB--ips PATCH\fR +Attempts to apply a IPS patch to the current ROM image. No files are altered. +If this flag is not specified, SSNES will look for a .ips file with same basename as ROM specified. +Note that SSNES cannot perform any error checking if patching was successful due to how IPS works. + .TP \fB--xml MAP, -X MAP\fR Specifies path to XML memory map for the given ROM. diff --git a/file.c b/file.c index 095748a766..d5272cb652 100644 --- a/file.c +++ b/file.c @@ -150,24 +150,34 @@ static void patch_rom(uint8_t **buf, ssize_t *size) void *patch_data = NULL; bool success = false; - if (g_extern.ups_pref && g_extern.bps_pref) + if (g_extern.ups_pref + g_extern.bps_pref + g_extern.ips_pref > 1) { - SSNES_WARN("Both UPS and BPS patch explicitly defined, ignoring both ...\n"); + SSNES_WARN("Several patches are explicitly defined, ignoring all ...\n"); return; } - if (!g_extern.bps_pref && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0) + bool allow_bps = !g_extern.ups_pref && !g_extern.ips_pref; + bool allow_ups = !g_extern.bps_pref && !g_extern.ips_pref; + bool allow_ips = !g_extern.ups_pref && !g_extern.bps_pref; + + if (allow_ups && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0) { patch_desc = "UPS"; patch_path = g_extern.ups_name; func = ups_apply_patch; } - else if (!g_extern.ups_pref && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0) + else if (allow_bps && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0) { patch_desc = "BPS"; patch_path = g_extern.bps_name; func = bps_apply_patch; } + else if (allow_ips && *g_extern.ips_name && (patch_size = read_file(g_extern.ips_name, &patch_data)) >= 0) + { + patch_desc = "IPS"; + patch_path = g_extern.ips_name; + func = ips_apply_patch; + } else { SSNES_LOG("Did not find a valid ROM patch.\n"); @@ -279,6 +289,9 @@ static ssize_t read_rom_file(FILE* file, void** buf) ret_buf = (uint8_t*)rom_buf; } + // Attempt to apply a patch. + patch_rom(&ret_buf, &ret); + // Remove copier header if present (512 first bytes). if ((ret & 0x7fff) == 512) { @@ -286,9 +299,6 @@ static ssize_t read_rom_file(FILE* file, void** buf) ret -= 512; } - // Attempt to apply a patch :) - patch_rom(&ret_buf, &ret); - g_extern.cart_crc = crc32_calculate(ret_buf, ret); #ifdef HAVE_XML sha256_hash(g_extern.sha256, ret_buf, ret); diff --git a/general.h b/general.h index 225f8e79bc..5ab385fe02 100644 --- a/general.h +++ b/general.h @@ -288,8 +288,10 @@ struct global bool ups_pref; bool bps_pref; + bool ips_pref; char ups_name[PATH_MAX]; char bps_name[PATH_MAX]; + char ips_name[PATH_MAX]; unsigned state_slot; diff --git a/patch.c b/patch.c index af8b1e43b2..78d639ff2e 100644 --- a/patch.c +++ b/patch.c @@ -15,11 +15,15 @@ * If not, see . */ +// BPS/UPS/IPS implementation from bSNES (nall::). +// Modified for SSNES. + #include "patch.h" #include "movie.h" #include "boolean.h" #include "msvc/msvc_compat.h" #include +#include enum bps_mode { @@ -316,3 +320,82 @@ patch_error_t ups_apply_patch( return PATCH_SOURCE_INVALID; } +patch_error_t ips_apply_patch( + const uint8_t *patchdata, size_t patchlen, + const uint8_t *sourcedata, size_t sourcelength, + uint8_t *targetdata, size_t *targetlength) +{ + if (patchlen < 8 || + patchdata[0] != 'P' || + patchdata[1] != 'A' || + patchdata[2] != 'T' || + patchdata[3] != 'C' || + patchdata[4] != 'H') + return PATCH_PATCH_INVALID; + + memcpy(targetdata, sourcedata, sourcelength); + + uint32_t offset = 5; + *targetlength = sourcelength; + + for (;;) + { + if (offset > patchlen - 3) + break; + + uint32_t address = patchdata[offset++] << 16; + address |= patchdata[offset++] << 8; + address |= patchdata[offset++] << 0; + + if (address == 0x454f46) // EOF + { + if (offset == patchlen) + return PATCH_SUCCESS; + else if (offset == patchlen - 3) + { + uint32_t size = patchdata[offset++] << 16; + size |= patchdata[offset++] << 8; + size |= patchdata[offset++] << 0; + *targetlength = size; + return PATCH_SUCCESS; + } + } + + if (offset > patchlen - 2) + break; + + unsigned length = patchdata[offset++] << 8; + length |= patchdata[offset++] << 0; + + if (length) // Copy + { + if (offset > patchlen - length) + break; + + while (length--) + targetdata[address++] = patchdata[offset++]; + } + else // RLE + { + if (offset > patchlen - 3) + break; + + length = patchdata[offset++] << 8; + length |= patchdata[offset++] << 0; + + if (length == 0) // Illegal + break; + + while (length--) + targetdata[address++] = patchdata[offset]; + + offset++; + } + + if (address > *targetlength) + *targetlength = address; + } + + return PATCH_PATCH_INVALID; +} + diff --git a/patch.h b/patch.h index a32c672d02..4edadfeeec 100644 --- a/patch.h +++ b/patch.h @@ -21,7 +21,8 @@ #include #include -// BPS/UPS implementation from bSNES (nall::). +// BPS/UPS/IPS implementation from bSNES (nall::). +// Modified for SSNES. typedef enum { @@ -51,4 +52,10 @@ patch_error_t ups_apply_patch( const uint8_t *source_data, size_t source_length, uint8_t *target_data, size_t *target_length); + +patch_error_t ips_apply_patch( + const uint8_t *patch_data, size_t patch_length, + const uint8_t *source_data, size_t source_length, + uint8_t *target_data, size_t *target_length); + #endif diff --git a/ssnes.c b/ssnes.c index 64183133b4..45bc587807 100644 --- a/ssnes.c +++ b/ssnes.c @@ -536,6 +536,7 @@ static void print_help(void) puts("\t-v/--verbose: Verbose logging."); puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM."); puts("\t--bps: Specifies path for BPS patch that will be applied to ROM."); + puts("\t--ips: Specifies path for IPS patch that will be applied to ROM."); puts("\t-X/--xml: Specifies path to XML memory map."); puts("\t-D/--detach: Detach SSNES from the running console. Not relevant for all platforms.\n"); } @@ -683,6 +684,7 @@ static void parse_input(int argc, char *argv[]) #endif { "ups", 1, NULL, 'U' }, { "bps", 1, &val, 'B' }, + { "ips", 1, &val, 'I' }, { "xml", 1, NULL, 'X' }, { "detach", 0, NULL, 'D' }, { "features", 0, &val, 'f' }, @@ -909,6 +911,11 @@ static void parse_input(int argc, char *argv[]) g_extern.bps_pref = true; break; + case 'I': + strlcpy(g_extern.ips_name, optarg, sizeof(g_extern.ips_name)); + g_extern.ips_pref = true; + break; + #ifdef HAVE_FFMPEG case 's': { @@ -1511,6 +1518,9 @@ static void fill_pathnames(void) if (!(*g_extern.bps_name)) fill_pathname_noext(g_extern.bps_name, g_extern.basename, ".bps", sizeof(g_extern.bps_name)); + if (!(*g_extern.ips_name)) + fill_pathname_noext(g_extern.ips_name, g_extern.basename, ".ips", sizeof(g_extern.ips_name)); + if (!(*g_extern.xml_name)) fill_pathname_noext(g_extern.xml_name, g_extern.basename, ".xml", sizeof(g_extern.xml_name));