mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-26 01:30:51 +00:00
166 lines
5.2 KiB
C
166 lines
5.2 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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);
|
|
}
|
|
|
|
data->target_offset++;
|
|
}
|
|
|
|
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_apply_patch(
|
|
const uint8_t *patchdata, size_t patchlength,
|
|
const uint8_t *sourcedata, size_t sourcelength,
|
|
uint8_t *targetdata, size_t *targetlength)
|
|
{
|
|
struct ups_data data = {
|
|
.patch_data = patchdata,
|
|
.source_data = sourcedata,
|
|
.target_data = targetdata,
|
|
.patch_length = patchlength,
|
|
.source_length = sourcelength,
|
|
.target_length = *targetlength,
|
|
.patch_checksum = ~0,
|
|
.source_checksum = ~0,
|
|
.target_checksum = ~0
|
|
};
|
|
|
|
if (data.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 (data.source_length != source_read_length && data.source_length != target_read_length)
|
|
return UPS_SOURCE_INVALID;
|
|
*targetlength = (data.source_length == source_read_length ? target_read_length : source_read_length);
|
|
if (data.target_length < *targetlength)
|
|
return UPS_TARGET_TOO_SMALL;
|
|
data.target_length = *targetlength;
|
|
|
|
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;
|
|
}
|
|
|