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