diff --git a/movie.c b/movie.c index 062b490a33..8a43ff3bff 100644 --- a/movie.c +++ b/movie.c @@ -70,8 +70,7 @@ static const uint32_t crc32_table[256] = { 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; - -static inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) +uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; } diff --git a/movie.h b/movie.h index ae0f73e3be..794d02675b 100644 --- a/movie.h +++ b/movie.h @@ -45,6 +45,7 @@ void bsv_movie_frame_rewind(bsv_movie_t *handle); void bsv_movie_free(bsv_movie_t *handle); uint32_t crc32_calculate(const uint8_t *data, unsigned length); +uint32_t crc32_adjust(uint32_t crc32, uint8_t input); #endif diff --git a/ups.c b/ups.c new file mode 100644 index 0000000000..734ace36a6 --- /dev/null +++ b/ups.c @@ -0,0 +1,160 @@ +/* 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 "ups.h" +#include "movie.h" + +struct ups_data +{ + const uint8_t *patch_data, *source_data; + uint8_t *target_data; + unsigned patch_length, source_length, target_length; + unsigned patch_offset, source_offset, target_offset; + unsigned patch_checksum, source_checksum, target_checksum; +}; + +static uint8_t patch_read(struct ups_data *data) +{ + if (data->patch_offset < data->patch_length) + { + uint8_t n = data->patch_data[data->patch_offset++]; + data->patch_checksum = crc32_adjust(data->patch_checksum, n); + return n; + } + return 0x00; +} + +static uint8_t source_read(struct ups_data *data) +{ + if (data->source_offset < data->source_length) + { + uint8_t n = data->source_data[data->source_offset++]; + data->source_checksum = crc32_adjust(data->source_checksum, n); + return n; + } + return 0x00; +} + +static void target_write(struct ups_data *data, uint8_t n) +{ + if (data->target_offset < data->target_length) + { + data->target_data[data->target_offset] = n; + data->target_checksum = crc32_adjust(data->target_checksum, n); + } +} + +static uint64_t decode(struct ups_data *data) +{ + uint64_t offset = 0, shift = 1; + while(true) + { + uint8_t x = patch_read(data); + offset += (x & 0x7f) * shift; + if(x & 0x80) + break; + shift <<= 7; + offset += shift; + } + return offset; +} + +ups_error_t ups_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) +{ + struct ups_data data = { + .patch_data = patch_data, + .source_data = source_data, + .target_data = target_data, + .patch_length = patch_length, + .source_length = source_length, + .target_length = *target_length, + .patch_checksum = ~0, + .source_checksum = ~0, + .target_checksum = ~0 + }; + + if (patch_length < 18) + return UPS_PATCH_INVALID; + if (patch_read(&data) != 'U') + return UPS_PATCH_INVALID; + if (patch_read(&data) != 'P') + return UPS_PATCH_INVALID; + if (patch_read(&data) != 'S') + return UPS_PATCH_INVALID; + if (patch_read(&data) != '1') + return UPS_PATCH_INVALID; + + unsigned source_read_length = decode(&data); + unsigned target_read_length = decode(&data); + + if (source_length != source_read_length && source_length != target_read_length) + return UPS_SOURCE_INVALID; + *target_length = (data.source_length == source_read_length ? target_read_length : source_read_length); + if (data.target_length < *target_length) + return UPS_TARGET_TOO_SMALL; + data.target_length = *target_length; + + while (data.patch_offset < data.patch_length - 12) + { + unsigned length = decode(&data); + while (length--) + target_write(&data, source_read(&data)); + while (true) + { + uint8_t patch_xor = patch_read(&data); + target_write(&data, patch_xor ^ source_read(&data)); + if (patch_xor == 0) break; + } + } + while (data.source_offset < data.source_length) + target_write(&data, source_read(&data)); + while (data.target_offset < data.target_length) + target_write(&data, source_read(&data)); + + uint32_t patch_read_checksum = 0, source_read_checksum = 0, target_read_checksum = 0; + for(unsigned i = 0; i < 4; i++) + source_read_checksum |= patch_read(&data) << (i * 8); + for(unsigned i = 0; i < 4; i++) + target_read_checksum |= patch_read(&data) << (i * 8); + uint32_t patch_result_checksum = ~data.patch_checksum; + data.source_checksum = ~data.source_checksum; + data.target_checksum = ~data.target_checksum; + for(unsigned i = 0; i < 4; i++) + patch_read_checksum |= patch_read(&data) << (i * 8); + + if (patch_result_checksum != patch_read_checksum) + return UPS_PATCH_INVALID; + + if (data.source_checksum == source_read_checksum && data.source_length == source_read_length) + { + if (data.target_checksum == target_read_checksum && data.target_length == target_read_length) + return UPS_SUCCESS; + return UPS_TARGET_INVALID; + } + else if (data.source_checksum == target_read_checksum && data.source_length == target_read_length) + { + if (data.target_checksum == source_read_checksum && data.target_length == source_read_length) + return UPS_SUCCESS; + return UPS_TARGET_INVALID; + } + else + return UPS_SOURCE_INVALID; +} + diff --git a/ups.h b/ups.h new file mode 100644 index 0000000000..56441569e3 --- /dev/null +++ b/ups.h @@ -0,0 +1,44 @@ +/* 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 __UPS_H +#define __UPS_H + +#include +#include + +// UPS implementation from bSNES. + +typedef enum ups_error +{ + UPS_UNKNOWN, + UPS_SUCCESS, + UPS_PATCH_INVALID, + UPS_SOURCE_INVALID, + UPS_TARGET_INVALID, + UPS_TARGET_TOO_SMALL, + UPS_PATCH_CHECKSUM_INVALID, + UPS_SOURCE_CHECKSUM_INVALID, + UPS_TARGET_CHECKSUM_INVALID +} ups_error_t; + +ups_error_t ups_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