RetroArch/patch.c

634 lines
17 KiB
C
Raw Normal View History

2012-04-21 21:13:50 +00:00
/* RetroArch - A frontend for libretro.
2014-01-01 00:50:59 +00:00
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
2016-01-10 03:06:50 +00:00
* Copyright (C) 2011-2016 - Daniel De Matteis
2011-08-17 22:05:56 +00:00
*
2012-04-21 21:13:50 +00:00
* RetroArch is free software: you can redistribute it and/or modify it under the terms
2011-08-17 22:05:56 +00:00
* 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.
*
2012-04-21 21:13:50 +00:00
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
2011-08-17 22:05:56 +00:00
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
2012-04-21 21:31:57 +00:00
* You should have received a copy of the GNU General Public License along with RetroArch.
2011-08-17 22:05:56 +00:00
* If not, see <http://www.gnu.org/licenses/>.
*/
2014-09-02 03:10:54 +00:00
/* BPS/UPS/IPS implementation from bSNES (nall::).
* Modified for RetroArch. */
2012-03-20 22:08:34 +00:00
2012-03-16 22:42:00 +00:00
#include <stdint.h>
2012-03-20 22:08:34 +00:00
#include <string.h>
#include <boolean.h>
#include <compat/msvc.h>
#include <file/file_path.h>
2016-01-24 06:29:11 +00:00
#include <file/file_archive.h>
2016-01-30 02:58:33 +00:00
#include <retro_file.h>
#include <retro_stat.h>
#include <string/stdstring.h>
2015-02-19 03:30:53 +00:00
#include "patch.h"
2016-02-07 12:18:48 +00:00
#include "runloop.h"
2015-11-23 11:03:38 +00:00
#include "verbosity.h"
2011-08-17 22:05:56 +00:00
enum bps_mode
{
SOURCE_READ = 0,
TARGET_READ,
SOURCE_COPY,
TARGET_COPY
};
2016-02-25 18:33:46 +00:00
enum patch_error
{
PATCH_UNKNOWN = 0,
PATCH_SUCCESS,
PATCH_PATCH_TOO_SMALL,
PATCH_PATCH_INVALID_HEADER,
PATCH_PATCH_INVALID,
PATCH_SOURCE_TOO_SMALL,
PATCH_TARGET_TOO_SMALL,
PATCH_SOURCE_INVALID,
PATCH_TARGET_INVALID,
PATCH_SOURCE_CHECKSUM_INVALID,
PATCH_TARGET_CHECKSUM_INVALID,
PATCH_PATCH_CHECKSUM_INVALID
};
2011-08-17 22:05:56 +00:00
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;
};
2016-02-25 18:33:46 +00:00
typedef enum patch_error (*patch_func_t)(const uint8_t*, size_t,
2016-02-07 00:46:20 +00:00
const uint8_t*, size_t, uint8_t*, size_t*);
2011-08-17 22:05:56 +00:00
static uint8_t bps_read(struct bps_data *bps)
{
uint8_t data = bps->modify_data[bps->modify_offset++];
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
bps->modify_checksum = ~stream_backend->stream_crc_calculate(
~bps->modify_checksum, &data, 1);
#endif
2011-08-17 22:05:56 +00:00
return data;
}
static uint64_t bps_decode(struct bps_data *bps)
{
uint64_t data = 0, shift = 1;
for (;;)
{
2016-01-21 02:35:06 +00:00
uint8_t x = bps_read(bps);
data += (x & 0x7f) * shift;
2011-08-17 22:05:56 +00:00
if (x & 0x80)
break;
2016-01-21 02:35:06 +00:00
shift <<= 7;
data += shift;
2011-08-17 22:05:56 +00:00
}
return data;
}
static void bps_write(struct bps_data *bps, uint8_t data)
{
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
#endif
2015-03-12 03:11:12 +00:00
if (!bps)
return;
2011-08-17 22:05:56 +00:00
bps->target_data[bps->output_offset++] = data;
#ifdef HAVE_ZLIB
bps->target_checksum = ~stream_backend->stream_crc_calculate(~bps->target_checksum, &data, 1);
#endif
2011-08-17 22:05:56 +00:00
}
2016-02-25 18:33:46 +00:00
static enum patch_error bps_apply_patch(
2011-08-17 22:05:56 +00:00
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)
{
size_t i;
size_t modify_source_size, modify_target_size,
modify_markup_size;
struct bps_data bps = {0};
uint32_t modify_source_checksum = 0, modify_target_checksum = 0,
modify_modify_checksum = 0, checksum;
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
#endif
2011-08-17 22:05:56 +00:00
if (modify_length < 19)
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_TOO_SMALL;
2011-08-17 22:05:56 +00:00
2011-12-24 12:46:12 +00:00
bps.modify_data = modify_data;
bps.modify_length = modify_length;
bps.target_data = target_data;
bps.target_length = *target_length;
bps.source_data = source_data;
bps.source_length = source_length;
bps.modify_checksum = ~0;
bps.target_checksum = ~0;
2011-08-17 22:05:56 +00:00
2014-09-02 03:10:54 +00:00
if ((bps_read(&bps) != 'B') || (bps_read(&bps) != 'P') ||
(bps_read(&bps) != 'S') || (bps_read(&bps) != '1'))
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID_HEADER;
2011-08-17 22:05:56 +00:00
modify_source_size = bps_decode(&bps);
modify_target_size = bps_decode(&bps);
modify_markup_size = bps_decode(&bps);
for (i = 0; i < modify_markup_size; i++)
2011-08-17 22:05:56 +00:00
bps_read(&bps);
if (modify_source_size > bps.source_length)
2012-03-17 00:10:57 +00:00
return PATCH_SOURCE_TOO_SMALL;
2011-08-17 22:05:56 +00:00
if (modify_target_size > bps.target_length)
2012-03-17 00:10:57 +00:00
return PATCH_TARGET_TOO_SMALL;
2011-08-17 22:05:56 +00:00
while (bps.modify_offset < bps.modify_length - 12)
{
size_t length = bps_decode(&bps);
unsigned mode = length & 3;
2011-08-17 22:05:56 +00:00
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:
{
2015-07-01 22:56:51 +00:00
int offset = bps_decode(&bps);
2011-08-17 22:05:56 +00:00
bool negative = offset & 1;
2015-07-01 22:56:51 +00:00
2011-08-17 22:05:56 +00:00
offset >>= 1;
2015-07-01 22:56:51 +00:00
2011-08-17 22:05:56 +00:00
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;
}
}
}
2014-09-02 03:10:54 +00:00
for (i = 0; i < 32; i += 8)
2011-08-17 22:05:56 +00:00
modify_source_checksum |= bps_read(&bps) << i;
for (i = 0; i < 32; i += 8)
2011-08-17 22:05:56 +00:00
modify_target_checksum |= bps_read(&bps) << i;
checksum = ~bps.modify_checksum;
for (i = 0; i < 32; i += 8)
2011-08-17 22:05:56 +00:00
modify_modify_checksum |= bps_read(&bps) << i;
#ifdef HAVE_ZLIB
2016-02-07 00:44:54 +00:00
bps.source_checksum = stream_backend->stream_crc_calculate(0,
bps.source_data, bps.source_length);
#else
return PATCH_PATCH_CHECKSUM_INVALID;
#endif
2011-08-17 22:05:56 +00:00
bps.target_checksum = ~bps.target_checksum;
if (bps.source_checksum != modify_source_checksum)
2012-03-17 00:10:57 +00:00
return PATCH_SOURCE_CHECKSUM_INVALID;
2011-08-17 22:05:56 +00:00
if (bps.target_checksum != modify_target_checksum)
2012-03-17 00:10:57 +00:00
return PATCH_TARGET_CHECKSUM_INVALID;
2011-08-17 22:05:56 +00:00
if (checksum != modify_modify_checksum)
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_CHECKSUM_INVALID;
2011-08-17 22:05:56 +00:00
*target_length = modify_target_size;
2012-03-17 00:10:57 +00:00
return PATCH_SUCCESS;
2011-08-17 22:05:56 +00:00
}
2012-03-16 22:42:00 +00:00
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 ups_patch_read(struct ups_data *data)
{
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
#endif
2015-03-12 03:11:12 +00:00
if (data && data->patch_offset < data->patch_length)
2012-03-16 22:42:00 +00:00
{
uint8_t n = data->patch_data[data->patch_offset++];
#ifdef HAVE_ZLIB
2016-02-07 00:44:54 +00:00
data->patch_checksum =
~stream_backend->stream_crc_calculate(~data->patch_checksum, &n, 1);
#endif
2012-03-16 22:42:00 +00:00
return n;
}
return 0x00;
}
static uint8_t ups_source_read(struct ups_data *data)
{
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
#endif
2015-03-12 03:11:12 +00:00
if (data && data->source_offset < data->source_length)
2012-03-16 22:42:00 +00:00
{
uint8_t n = data->source_data[data->source_offset++];
#ifdef HAVE_ZLIB
2016-02-07 00:44:54 +00:00
data->source_checksum =
~stream_backend->stream_crc_calculate(~data->source_checksum, &n, 1);
#endif
2012-03-16 22:42:00 +00:00
return n;
}
return 0x00;
}
static void ups_target_write(struct ups_data *data, uint8_t n)
{
#ifdef HAVE_ZLIB
2016-01-24 21:17:11 +00:00
const struct file_archive_file_backend *stream_backend =
file_archive_get_default_file_backend();
#endif
2015-03-12 03:11:12 +00:00
if (data && data->target_offset < data->target_length)
2012-03-16 22:42:00 +00:00
{
data->target_data[data->target_offset] = n;
#ifdef HAVE_ZLIB
2016-02-07 00:44:54 +00:00
data->target_checksum =
~stream_backend->stream_crc_calculate(~data->target_checksum, &n, 1);
#endif
2012-03-16 22:42:00 +00:00
}
2015-03-12 03:11:12 +00:00
if (data)
data->target_offset++;
2012-03-16 22:42:00 +00:00
}
static uint64_t ups_decode(struct ups_data *data)
{
uint64_t offset = 0, shift = 1;
while (true)
{
uint8_t x = ups_patch_read(data);
2015-07-01 22:56:51 +00:00
offset += (x & 0x7f) * shift;
2012-03-16 22:42:00 +00:00
if (x & 0x80)
break;
shift <<= 7;
offset += shift;
}
return offset;
}
2016-02-25 18:33:46 +00:00
static enum patch_error ups_apply_patch(
2012-03-16 22:42:00 +00:00
const uint8_t *patchdata, size_t patchlength,
const uint8_t *sourcedata, size_t sourcelength,
uint8_t *targetdata, size_t *targetlength)
{
size_t i;
unsigned source_read_length, target_read_length;
uint32_t patch_read_checksum = 0, source_read_checksum = 0,
target_read_checksum = 0, patch_result_checksum;
2012-03-16 22:42:00 +00:00
struct ups_data data = {0};
2015-03-12 03:11:12 +00:00
data.patch_data = patchdata;
data.source_data = sourcedata;
data.target_data = targetdata;
data.patch_length = patchlength;
data.source_length = sourcelength;
data.target_length = *targetlength;
data.patch_checksum = ~0;
2012-03-16 22:42:00 +00:00
data.source_checksum = ~0;
data.target_checksum = ~0;
if (data.patch_length < 18)
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
if (ups_patch_read(&data) != 'U')
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
if (ups_patch_read(&data) != 'P')
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
if (ups_patch_read(&data) != 'S')
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
if (ups_patch_read(&data) != '1')
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
source_read_length = ups_decode(&data);
target_read_length = ups_decode(&data);
2012-03-16 22:42:00 +00:00
2014-09-02 03:10:54 +00:00
if (data.source_length != source_read_length
&& data.source_length != target_read_length)
2012-03-17 00:10:57 +00:00
return PATCH_SOURCE_INVALID;
2014-09-02 03:10:54 +00:00
*targetlength = (data.source_length == source_read_length ?
target_read_length : source_read_length);
2012-03-16 22:42:00 +00:00
if (data.target_length < *targetlength)
2012-03-17 00:10:57 +00:00
return PATCH_TARGET_TOO_SMALL;
2012-03-16 22:42:00 +00:00
data.target_length = *targetlength;
while (data.patch_offset < data.patch_length - 12)
{
unsigned length = ups_decode(&data);
while (length--)
ups_target_write(&data, ups_source_read(&data));
while (true)
{
uint8_t patch_xor = ups_patch_read(&data);
ups_target_write(&data, patch_xor ^ ups_source_read(&data));
if (patch_xor == 0)
break;
2012-03-16 22:42:00 +00:00
}
}
while (data.source_offset < data.source_length)
ups_target_write(&data, ups_source_read(&data));
while (data.target_offset < data.target_length)
ups_target_write(&data, ups_source_read(&data));
2014-09-02 03:10:54 +00:00
for (i = 0; i < 4; i++)
2012-03-16 22:42:00 +00:00
source_read_checksum |= ups_patch_read(&data) << (i * 8);
for (i = 0; i < 4; i++)
2012-03-16 22:42:00 +00:00
target_read_checksum |= ups_patch_read(&data) << (i * 8);
patch_result_checksum = ~data.patch_checksum;
data.source_checksum = ~data.source_checksum;
data.target_checksum = ~data.target_checksum;
2012-03-16 22:42:00 +00:00
for (i = 0; i < 4; i++)
2012-03-16 22:42:00 +00:00
patch_read_checksum |= ups_patch_read(&data) << (i * 8);
if (patch_result_checksum != patch_read_checksum)
2012-03-17 00:10:57 +00:00
return PATCH_PATCH_INVALID;
2012-03-16 22:42:00 +00:00
2014-09-02 03:10:54 +00:00
if (data.source_checksum == source_read_checksum
&& data.source_length == source_read_length)
2012-03-16 22:42:00 +00:00
{
2014-09-02 03:10:54 +00:00
if (data.target_checksum == target_read_checksum
&& data.target_length == target_read_length)
2012-03-17 00:10:57 +00:00
return PATCH_SUCCESS;
return PATCH_TARGET_INVALID;
2012-03-16 22:42:00 +00:00
}
2014-09-02 03:10:54 +00:00
else if (data.source_checksum == target_read_checksum
&& data.source_length == target_read_length)
2012-03-16 22:42:00 +00:00
{
2014-09-02 03:10:54 +00:00
if (data.target_checksum == source_read_checksum
&& data.target_length == source_read_length)
2012-03-17 00:10:57 +00:00
return PATCH_SUCCESS;
return PATCH_TARGET_INVALID;
2012-03-16 22:42:00 +00:00
}
return PATCH_SOURCE_INVALID;
2012-03-16 22:42:00 +00:00
}
2016-02-25 18:33:46 +00:00
static enum patch_error ips_apply_patch(
2012-03-20 22:08:34 +00:00
const uint8_t *patchdata, size_t patchlen,
const uint8_t *sourcedata, size_t sourcelength,
uint8_t *targetdata, size_t *targetlength)
{
uint32_t offset = 5;
2012-03-20 22:08:34 +00:00
if (patchlen < 8 ||
patchdata[0] != 'P' ||
patchdata[1] != 'A' ||
patchdata[2] != 'T' ||
patchdata[3] != 'C' ||
patchdata[4] != 'H')
return PATCH_PATCH_INVALID;
memcpy(targetdata, sourcedata, sourcelength);
*targetlength = sourcelength;
for (;;)
{
uint32_t address;
unsigned length;
2012-03-20 22:08:34 +00:00
if (offset > patchlen - 3)
break;
address = patchdata[offset++] << 16;
2012-03-20 22:08:34 +00:00
address |= patchdata[offset++] << 8;
address |= patchdata[offset++] << 0;
2014-09-02 03:10:54 +00:00
if (address == 0x454f46) /* EOF */
2012-03-20 22:08:34 +00:00
{
if (offset == patchlen)
return PATCH_SUCCESS;
else if (offset == patchlen - 3)
{
uint32_t size = patchdata[offset++] << 16;
size |= patchdata[offset++] << 8;
size |= patchdata[offset++] << 0;
*targetlength = size;
return PATCH_SUCCESS;
}
}
if (offset > patchlen - 2)
break;
length = patchdata[offset++] << 8;
2012-03-20 22:08:34 +00:00
length |= patchdata[offset++] << 0;
2014-09-02 03:10:54 +00:00
if (length) /* Copy */
2012-03-20 22:08:34 +00:00
{
if (offset > patchlen - length)
break;
while (length--)
targetdata[address++] = patchdata[offset++];
}
2014-09-02 03:10:54 +00:00
else /* RLE */
2012-03-20 22:08:34 +00:00
{
if (offset > patchlen - 3)
break;
length = patchdata[offset++] << 8;
length |= patchdata[offset++] << 0;
2014-09-02 03:10:54 +00:00
if (length == 0) /* Illegal */
2012-03-20 22:08:34 +00:00
break;
while (length--)
targetdata[address++] = patchdata[offset];
offset++;
}
if (address > *targetlength)
*targetlength = address;
}
return PATCH_PATCH_INVALID;
}
2015-02-19 03:30:53 +00:00
static bool apply_patch_content(uint8_t **buf,
ssize_t *size, const char *patch_desc, const char *patch_path,
patch_func_t func)
{
size_t target_size;
ssize_t patch_size;
2015-03-12 03:11:12 +00:00
void *patch_data = NULL;
2016-02-25 18:33:46 +00:00
enum patch_error err = PATCH_UNKNOWN;
2015-03-12 03:11:12 +00:00
bool success = false;
uint8_t *patched_content = NULL;
ssize_t ret_size = *size;
uint8_t *ret_buf = *buf;
2015-02-19 03:30:53 +00:00
if (!path_is_valid(patch_path))
return false;
2016-01-30 02:58:33 +00:00
if (!retro_read_file(patch_path, &patch_data, &patch_size))
2015-02-19 03:30:53 +00:00
return false;
if (patch_size < 0)
return false;
if (!path_file_exists(patch_path))
return false;
RARCH_LOG("Found %s file in \"%s\", attempting to patch ...\n",
patch_desc, patch_path);
target_size = ret_size * 4; /* Just to be sure. */
patched_content = (uint8_t*)malloc(target_size);
if (!patched_content)
{
RARCH_ERR("Failed to allocate memory for patched content ...\n");
goto error;
}
err = func((const uint8_t*)patch_data, patch_size, ret_buf,
ret_size, patched_content, &target_size);
if (err == PATCH_SUCCESS)
{
RARCH_LOG("Content patched successfully (%s).\n", patch_desc);
success = true;
}
else
RARCH_ERR("Failed to patch %s: Error #%u\n", patch_desc,
(unsigned)err);
if (success)
{
free(ret_buf);
*buf = patched_content;
*size = target_size;
}
free(patch_data);
return true;
error:
*buf = ret_buf;
*size = ret_size;
free(patch_data);
return false;
}
static bool try_bps_patch(uint8_t **buf, ssize_t *size)
{
2015-03-21 03:43:18 +00:00
global_t *global = global_get_ptr();
2015-07-27 15:18:10 +00:00
bool allow_bps = !global->patch.ups_pref && !global->patch.ips_pref;
2015-02-19 03:30:53 +00:00
2016-01-23 23:02:06 +00:00
if (!allow_bps || string_is_empty(global->name.bps))
2015-02-19 03:30:53 +00:00
return false;
2015-07-27 15:18:10 +00:00
return apply_patch_content(buf, size, "BPS", global->name.bps,
2015-02-19 03:30:53 +00:00
bps_apply_patch);
}
static bool try_ups_patch(uint8_t **buf, ssize_t *size)
{
2015-03-21 03:43:18 +00:00
global_t *global = global_get_ptr();
2015-07-27 15:18:10 +00:00
bool allow_ups = !global->patch.bps_pref && !global->patch.ips_pref;
2015-02-19 03:30:53 +00:00
2016-01-23 23:02:06 +00:00
if (!allow_ups || string_is_empty(global->name.ups))
2015-02-19 03:30:53 +00:00
return false;
2015-07-27 15:18:10 +00:00
return apply_patch_content(buf, size, "UPS", global->name.ups,
2015-02-19 03:30:53 +00:00
ups_apply_patch);
}
static bool try_ips_patch(uint8_t **buf, ssize_t *size)
{
2015-03-21 03:43:18 +00:00
global_t *global = global_get_ptr();
2015-07-27 15:18:10 +00:00
bool allow_ips = !global->patch.ups_pref && !global->patch.bps_pref;
2015-02-19 03:30:53 +00:00
2016-01-23 23:02:06 +00:00
if (!allow_ips || string_is_empty(global->name.ips))
2015-02-19 03:30:53 +00:00
return false;
2015-07-27 15:18:10 +00:00
return apply_patch_content(buf, size, "IPS", global->name.ips,
2015-02-19 03:30:53 +00:00
ips_apply_patch);
}
/**
* patch_content:
* @buf : buffer of the content file.
* @size : size of the content file.
*
* Apply patch to the content file in-memory.
*
**/
void patch_content(uint8_t **buf, ssize_t *size)
{
2015-03-21 03:43:18 +00:00
global_t *global = global_get_ptr();
2016-02-07 00:44:54 +00:00
if ( global->patch.ips_pref
+ global->patch.bps_pref
+ global->patch.ups_pref > 1)
2015-02-19 03:30:53 +00:00
{
RARCH_WARN("Several patches are explicitly defined, ignoring all ...\n");
return;
}
2016-02-07 00:44:54 +00:00
if ( !try_ips_patch(buf, size)
&& !try_bps_patch(buf, size)
&& !try_ups_patch(buf, size))
2015-02-19 03:30:53 +00:00
{
RARCH_LOG("Did not find a valid content patch.\n");
2015-02-19 03:30:53 +00:00
}
}