diff --git a/Makefile b/Makefile
index 26cc1530f7..b6510501e5 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ include config.mk
TARGET = ssnes tools/ssnes-joyconfig
-OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
+OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o bps.o strl.o screenshot.o
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
HEADERS = $(wildcard */*.h) $(wildcard *.h)
diff --git a/bps.c b/bps.c
new file mode 100644
index 0000000000..55f1c10178
--- /dev/null
+++ b/bps.c
@@ -0,0 +1,174 @@
+/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
+ * Copyright (C) 2010-2011 - Hans-Kristian Arntzen
+ *
+ * Some code herein may be based on code found in BSNES.
+ *
+ * SSNES is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with SSNES.
+ * If not, see .
+ */
+
+#include "bps.h"
+#include "movie.h"
+#include
+#include
+
+enum bps_mode
+{
+ SOURCE_READ = 0,
+ TARGET_READ,
+ SOURCE_COPY,
+ TARGET_COPY
+};
+
+struct bps_data
+{
+ const uint8_t *modify_data, *source_data;
+ uint8_t *target_data;
+ size_t modify_length, source_length, target_length;
+ size_t modify_offset, source_offset, target_offset;
+ uint32_t modify_checksum, source_checksum, target_checksum;
+
+ size_t source_relative_offset, target_relative_offset, output_offset;
+};
+
+static uint8_t bps_read(struct bps_data *bps)
+{
+ uint8_t data = bps->modify_data[bps->modify_offset++];
+ bps->modify_checksum = crc32_adjust(bps->modify_checksum, data);
+ return data;
+}
+
+static uint64_t bps_decode(struct bps_data *bps)
+{
+ uint64_t data = 0, shift = 1;
+
+ for (;;)
+ {
+ uint8_t x = bps_read(bps);
+ data += (x & 0x7f) * shift;
+ if (x & 0x80)
+ break;
+ shift <<= 7;
+ data += shift;
+ }
+
+ return data;
+}
+
+static void bps_write(struct bps_data *bps, uint8_t data)
+{
+ bps->target_data[bps->output_offset++] = data;
+ bps->target_checksum = crc32_adjust(bps->target_checksum, data);
+}
+
+bps_error_t bps_apply_patch(
+ const uint8_t *modify_data, size_t modify_length,
+ const uint8_t *source_data, size_t source_length,
+ uint8_t *target_data, size_t *target_length)
+{
+ if (modify_length < 19)
+ return BPS_PATCH_TOO_SMALL;
+
+ struct bps_data bps = {
+ .modify_data = modify_data,
+ .target_data = target_data,
+ .modify_length = modify_length,
+ .source_data = source_data,
+ .source_length = source_length,
+
+ .modify_checksum = ~0,
+ .target_checksum = ~0,
+ };
+
+ if ((bps_read(&bps) != 'B') || (bps_read(&bps) != 'P') || (bps_read(&bps) != 'S') || (bps_read(&bps) != '1'))
+ return BPS_PATCH_INVALID_HEADER;
+
+ size_t modify_source_size = bps_decode(&bps);
+ size_t modify_target_size = bps_decode(&bps);
+ size_t modify_markup_size = bps_decode(&bps);
+ for (size_t i = 0; i < modify_markup_size; i++)
+ bps_read(&bps);
+
+ if (modify_source_size > bps.source_length)
+ return BPS_SOURCE_TOO_SMALL;
+ if (modify_target_size > bps.target_length)
+ return BPS_TARGET_TOO_SMALL;
+
+ while (bps.modify_offset < bps.modify_length - 12)
+ {
+ size_t length = bps_decode(&bps);
+ unsigned mode = length & 3;
+ length = (length >> 2) + 1;
+
+ switch (mode)
+ {
+ case SOURCE_READ:
+ while (length--)
+ bps_write(&bps, bps.source_data[bps.output_offset]);
+ break;
+
+ case TARGET_READ:
+ while (length--)
+ bps_write(&bps, bps_read(&bps));
+ break;
+
+ case SOURCE_COPY:
+ case TARGET_COPY:
+ {
+ int offset = bps_decode(&bps);
+ bool negative = offset & 1;
+ offset >>= 1;
+ if (negative)
+ offset = -offset;
+
+ if (mode == SOURCE_COPY)
+ {
+ bps.source_offset += offset;
+ while (length--)
+ bps_write(&bps, bps.source_data[bps.source_offset++]);
+ }
+ else
+ {
+ bps.target_offset += offset;
+ while (length--)
+ bps_write(&bps, bps.target_data[bps.target_offset++]);
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ uint32_t modify_source_checksum = 0, modify_target_checksum = 0, modify_modify_checksum = 0;
+ for (unsigned i = 0; i < 32; i += 8)
+ modify_source_checksum |= bps_read(&bps) << i;
+ for (unsigned i = 0; i < 32; i += 8)
+ modify_target_checksum |= bps_read(&bps) << i;
+
+ uint32_t checksum = ~bps.modify_checksum;
+ for (unsigned i = 0; i < 32; i += 8)
+ modify_modify_checksum |= bps_read(&bps) << i;
+
+ bps.source_checksum = crc32_calculate(bps.source_data, bps.source_length);
+ bps.target_checksum = ~bps.target_checksum;
+
+ if (bps.source_checksum != modify_source_checksum)
+ return BPS_SOURCE_CHECKSUM_INVALID;
+ if (bps.target_checksum != modify_target_checksum)
+ return BPS_TARGET_CHECKSUM_INVALID;
+ if (checksum != modify_modify_checksum)
+ return BPS_PATCH_CHECKSUM_INVALID;
+
+ *target_length = modify_target_size;
+
+ return BPS_SUCCESS;
+}
+
diff --git a/bps.h b/bps.h
new file mode 100644
index 0000000000..7044a7ccbf
--- /dev/null
+++ b/bps.h
@@ -0,0 +1,45 @@
+/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
+ * Copyright (C) 2010-2011 - Hans-Kristian Arntzen
+ *
+ * Some code herein may be based on code found in BSNES.
+ *
+ * SSNES is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with SSNES.
+ * If not, see .
+ */
+
+#ifndef __BPS_H
+#define __BPS_H
+
+#include
+#include
+
+// BPS implementation from bSNES (nall::).
+
+typedef enum bps_error
+{
+ BPS_UNKNOWN,
+ BPS_SUCCESS,
+ BPS_PATCH_TOO_SMALL,
+ BPS_PATCH_INVALID_HEADER,
+ BPS_SOURCE_TOO_SMALL,
+ BPS_TARGET_TOO_SMALL,
+ BPS_SOURCE_CHECKSUM_INVALID,
+ BPS_TARGET_CHECKSUM_INVALID,
+ BPS_PATCH_CHECKSUM_INVALID
+} bps_error_t;
+
+bps_error_t bps_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/file.c b/file.c
index 5607813922..ce80943d57 100644
--- a/file.c
+++ b/file.c
@@ -24,6 +24,7 @@
#include "dynamic.h"
#include "movie.h"
#include "ups.h"
+#include "bps.h"
#include "strl.h"
#ifdef HAVE_XML
@@ -116,6 +117,102 @@ error:
return false;
}
+enum patch_type
+{
+ PATCH_NONE,
+ PATCH_UPS,
+ PATCH_BPS
+};
+
+static void patch_rom(uint8_t **buf, ssize_t *size)
+{
+ uint8_t *ret_buf = *buf;
+ ssize_t ret_size = *size;
+
+ ssize_t patch_size = 0;
+ void *patch_data = NULL;
+ enum patch_type type = PATCH_NONE;
+
+ if (*g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0)
+ type = PATCH_UPS;
+ else if (*g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0)
+ type = PATCH_BPS;
+
+ if (type == PATCH_NONE)
+ {
+ SSNES_LOG("Could not find any ROM patch.\n");
+ return;
+ }
+
+ switch (type)
+ {
+ case PATCH_UPS:
+ SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
+ break;
+ case PATCH_BPS:
+ SSNES_LOG("Found BPS file in \"%s\", attempting to patch ...\n", g_extern.bps_name);
+ break;
+
+ default:
+ return; // Should not happen, but.
+ }
+
+ size_t target_size = ret_size * 4; // Just to be sure ...
+ uint8_t *patched_rom = malloc(target_size);
+ if (!patched_rom)
+ {
+ SSNES_ERR("Failed to allocate memory for patched ROM ...\n");
+ goto error;
+ }
+
+ bool success = false;
+ switch (type)
+ {
+ case PATCH_UPS:
+ {
+ ups_error_t err = ups_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
+ if (err == UPS_SUCCESS)
+ {
+ SSNES_LOG("ROM patched successfully (UPS)!\n");
+ success = true;
+ }
+ break;
+ }
+
+ case PATCH_BPS:
+ {
+ bps_error_t err = bps_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
+ if (err == BPS_SUCCESS)
+ {
+ SSNES_LOG("ROM patched successfully (BPS)!\n");
+ success = true;
+ }
+ break;
+ }
+
+ default:
+ return;
+ }
+
+ if (success)
+ {
+ free(ret_buf);
+ *buf = patched_rom;
+ *size = target_size;
+ }
+
+ if (patch_data)
+ free(patch_data);
+
+ return;
+
+error:
+ *buf = ret_buf;
+ *size = ret_size;
+ if (patch_data)
+ free(patch_data);
+}
+
// Load SNES rom only. Applies a hack for headered ROMs.
static ssize_t read_rom_file(FILE* file, void** buf)
{
@@ -183,37 +280,6 @@ static ssize_t read_rom_file(FILE* file, void** buf)
ret_buf = rom_buf;
}
- // Patch with UPS.
- ssize_t ups_patch_size;
- void *ups_patch = NULL;
- if (*g_extern.ups_name && (ups_patch_size = read_file(g_extern.ups_name, &ups_patch)) >= 0)
- {
- SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
-
- size_t target_size = ret * 4; // Just to be sure ...
- uint8_t *patched_rom = malloc(target_size);
- if (patched_rom)
- {
- ups_error_t err = ups_apply_patch(ups_patch, ups_patch_size, ret_buf, ret, patched_rom, &target_size);
- if (err == UPS_SUCCESS)
- {
- free(ret_buf);
- ret_buf = patched_rom;
- ret = target_size;
- SSNES_LOG("ROM patched successfully (UPS)!\n");
- }
- else
- {
- free(patched_rom);
- SSNES_LOG("ROM failed to patch (UPS).\n");
- }
-
- free(ups_patch);
- }
- }
- else if (*g_extern.ups_name)
- SSNES_LOG("Could not find UPS patch in: \"%s\".\n", g_extern.ups_name);
-
// Remove copier header if present (512 first bytes).
if ((ret & 0x7fff) == 512)
{
@@ -221,6 +287,9 @@ 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 4e4040aa6f..91277548de 100644
--- a/general.h
+++ b/general.h
@@ -182,6 +182,7 @@ struct global
char savefile_name_bsrm[512];
char savestate_name[256];
char ups_name[512];
+ char bps_name[512];
char xml_name[512];
unsigned state_slot;
diff --git a/ssnes.c b/ssnes.c
index fc5d2185a5..0b9586d3bc 100644
--- a/ssnes.c
+++ b/ssnes.c
@@ -1077,11 +1077,17 @@ static void fill_pathnames(void)
if (!g_extern.bsv_movie_playback)
fill_pathname(g_extern.bsv_movie_path, g_extern.savefile_name_srm, "", sizeof(g_extern.bsv_movie_path));
- if (!(*g_extern.ups_name) && *g_extern.basename)
- fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
+ if (*g_extern.basename)
+ {
+ if (!(*g_extern.ups_name))
+ fill_pathname_noext(g_extern.ups_name, g_extern.basename, ".ups", sizeof(g_extern.ups_name));
- if (!(*g_extern.xml_name) && *g_extern.basename)
- fill_pathname_noext(g_extern.xml_name, g_extern.basename, ".xml", sizeof(g_extern.xml_name));
+ if (!(*g_extern.bps_name))
+ fill_pathname_noext(g_extern.bps_name, g_extern.basename, ".bps", sizeof(g_extern.bps_name));
+
+ if (!(*g_extern.xml_name))
+ fill_pathname_noext(g_extern.xml_name, g_extern.basename, ".xml", sizeof(g_extern.xml_name));
+ }
}
// Save or load state here.