From 1ff9b6a3756efe721dd82600c923f12322f81f9e Mon Sep 17 00:00:00 2001 From: Themaister Date: Thu, 18 Aug 2011 00:05:56 +0200 Subject: [PATCH] Start adding support for BPS patches. --- Makefile | 2 +- bps.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ bps.h | 45 ++++++++++++++ file.c | 131 ++++++++++++++++++++++++++++++---------- general.h | 1 + ssnes.c | 14 +++-- 6 files changed, 331 insertions(+), 36 deletions(-) create mode 100644 bps.c create mode 100644 bps.h 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.