RetroArch/ups.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;
}