mirror of
https://github.com/dborth/fceugx.git
synced 2024-12-04 22:34:14 +01:00
265 lines
5.0 KiB
C++
265 lines
5.0 KiB
C++
/****************************************************************************
|
|
* FCE Ultra
|
|
* Nintendo Wii/Gamecube Port
|
|
*
|
|
* Tantric 2008-2023
|
|
*
|
|
* patch.cpp
|
|
*
|
|
* IPS/UPS patch support
|
|
***************************************************************************/
|
|
|
|
#include <zlib.h>
|
|
#include <gctypes.h>
|
|
|
|
#include "fceugx.h"
|
|
#include "fceusupport.h"
|
|
#include "menu.h"
|
|
#include "memfile.h"
|
|
#include "fileop.h"
|
|
#include "filebrowser.h"
|
|
|
|
static int readInt2(MFILE *f) {
|
|
int res = 0;
|
|
int c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
res = c;
|
|
c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
return c + (res << 8);
|
|
}
|
|
|
|
static int readInt3(MFILE *f) {
|
|
int res = 0;
|
|
int c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
res = c;
|
|
c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
res = c + (res << 8);
|
|
c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
return c + (res << 8);
|
|
}
|
|
|
|
static s64 readInt4(MFILE *f) {
|
|
s64 tmp, res = 0;
|
|
int c;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return -1;
|
|
tmp = c;
|
|
res = res + (tmp << (i * 8));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static s64 readVarPtr(MFILE *f) {
|
|
s64 offset = 0, shift = 1;
|
|
for (;;) {
|
|
int c = memfgetc(f);
|
|
if (c == MEOF)
|
|
return 0;
|
|
offset += (c & 0x7F) * shift;
|
|
if (c & 0x80)
|
|
break;
|
|
shift <<= 7;
|
|
offset += shift;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
#ifndef MIN
|
|
#define MIN(a,b) (((a)<(b))?(a):(b))
|
|
#endif
|
|
|
|
static uLong computePatchCRC(MFILE *f, unsigned int size) {
|
|
Bytef buf[4096];
|
|
long readed;
|
|
|
|
uLong crc = crc32(0L, Z_NULL, 0);
|
|
do {
|
|
readed = memfread(buf, 1, MIN(size, sizeof(buf)), f);
|
|
crc = crc32(crc, buf, readed);
|
|
size -= readed;
|
|
} while (readed > 0);
|
|
|
|
return crc;
|
|
}
|
|
|
|
bool patchApplyIPS(MFILE * f, u8 **r, int *s) {
|
|
// from the IPS spec at http://zerosoft.zophar.net/ips.htm
|
|
|
|
bool result = false;
|
|
|
|
u8 *rom = *r;
|
|
int size = *s;
|
|
if (memfgetc(f) == 'P' && memfgetc(f) == 'A' && memfgetc(f) == 'T'
|
|
&& memfgetc(f) == 'C' && memfgetc(f) == 'H') {
|
|
int b;
|
|
int offset;
|
|
int len;
|
|
|
|
result = true;
|
|
|
|
for (;;) {
|
|
// read offset
|
|
offset = readInt3(f);
|
|
// if offset == MEOF, end of patch
|
|
if (offset == 0x454f46 || offset == -1)
|
|
break;
|
|
// read length
|
|
len = readInt2(f);
|
|
if (!len) {
|
|
// len == 0, RLE block
|
|
len = readInt2(f);
|
|
// byte to fill
|
|
int c = memfgetc(f);
|
|
if (c == -1)
|
|
break;
|
|
b = (u8) c;
|
|
} else
|
|
b = -1;
|
|
// check if we need to reallocate our ROM
|
|
if ((offset + len) >= size) {
|
|
#ifdef GEKKO
|
|
size = offset + len;
|
|
#else
|
|
size *= 2;
|
|
rom = (u8 *) realloc(rom, size);
|
|
#endif
|
|
*r = rom;
|
|
*s = size;
|
|
}
|
|
if (b == -1) {
|
|
// normal block, just read the data
|
|
if (memfread(&rom[offset], 1, len, f) != (size_t) len)
|
|
break;
|
|
} else {
|
|
// fill the region with the given byte
|
|
while (len--) {
|
|
rom[offset++] = b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool patchApplyUPS(MFILE * f, u8 **rom, int *size) {
|
|
|
|
s64 srcCRC, dstCRC, patchCRC;
|
|
|
|
memfseek(f, 0, MSEEK_END);
|
|
long int patchSize = memftell(f);
|
|
if (patchSize < 20) {
|
|
return false;
|
|
}
|
|
|
|
memfseek(f, 0, MSEEK_SET);
|
|
|
|
if (memfgetc(f) != 'U' || memfgetc(f) != 'P' || memfgetc(f) != 'S'
|
|
|| memfgetc(f) != '1') {
|
|
return false;
|
|
}
|
|
|
|
memfseek(f, -12, MSEEK_END);
|
|
srcCRC = readInt4(f);
|
|
dstCRC = readInt4(f);
|
|
patchCRC = readInt4(f);
|
|
if (srcCRC == -1 || dstCRC == -1 || patchCRC == -1) {
|
|
return false;
|
|
}
|
|
|
|
memfseek(f, 0, MSEEK_SET);
|
|
u32 crc = computePatchCRC(f, patchSize - 4);
|
|
|
|
if (crc != patchCRC) {
|
|
return false;
|
|
}
|
|
|
|
crc = crc32(0L, Z_NULL, 0);
|
|
crc = crc32(crc, *rom, *size);
|
|
|
|
memfseek(f, 4, MSEEK_SET);
|
|
s64 dataSize;
|
|
s64 srcSize = readVarPtr(f);
|
|
s64 dstSize = readVarPtr(f);
|
|
|
|
if (crc == srcCRC) {
|
|
dataSize = srcSize;
|
|
} else if (crc == dstCRC) {
|
|
dataSize = dstSize;
|
|
} else {
|
|
return false;
|
|
}
|
|
if (dataSize != *size) {
|
|
return false;
|
|
}
|
|
|
|
s64 relative = 0;
|
|
u8 *mem;
|
|
while (memftell(f) < patchSize - 12) {
|
|
relative += readVarPtr(f);
|
|
if (relative > dataSize)
|
|
continue;
|
|
mem = *rom + relative;
|
|
for (s64 i = relative; i < dataSize; i++) {
|
|
int x = memfgetc(f);
|
|
relative++;
|
|
if (!x)
|
|
break;
|
|
if (i < dataSize) {
|
|
*mem++ ^= x;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int LoadPatch(int size)
|
|
{
|
|
int newsize = size;
|
|
int patchsize = 0;
|
|
int patchtype;
|
|
char patchpath[2][512];
|
|
|
|
AllocSaveBuffer ();
|
|
|
|
memset(patchpath, 0, sizeof(patchpath));
|
|
sprintf(patchpath[0], "%s%s.ips", browser.dir, romFilename);
|
|
sprintf(patchpath[1], "%s%s.ups", browser.dir, romFilename);
|
|
|
|
for(patchtype=0; patchtype<2; patchtype++)
|
|
{
|
|
patchsize = LoadFile(patchpath[patchtype], SILENT);
|
|
|
|
if(patchsize)
|
|
break;
|
|
}
|
|
|
|
if(patchsize > 0)
|
|
{
|
|
// create memory file
|
|
MFILE * mf = memfopen((char *)savebuffer, patchsize);
|
|
|
|
if(patchtype == 0)
|
|
patchApplyIPS(mf, &nesrom, &newsize);
|
|
else
|
|
patchApplyUPS(mf, &nesrom, &newsize);
|
|
|
|
memfclose(mf); // close memory file
|
|
}
|
|
|
|
FreeSaveBuffer ();
|
|
return newsize;
|
|
}
|