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));