mirror of https://github.com/mgba-emu/mgba.git
225 lines
5.9 KiB
C
225 lines
5.9 KiB
C
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include "util/patch-ips.h"
|
|
|
|
#include "util/crc32.h"
|
|
#include "util/patch.h"
|
|
#include "util/vfs.h"
|
|
|
|
enum {
|
|
IN_CHECKSUM = -12,
|
|
OUT_CHECKSUM = -8,
|
|
PATCH_CHECKSUM = -4,
|
|
};
|
|
|
|
static size_t _UPSOutputSize(struct Patch* patch, size_t inSize);
|
|
|
|
static bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize);
|
|
static bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize);
|
|
|
|
static size_t _decodeLength(struct VFile* vf);
|
|
|
|
bool loadPatchUPS(struct Patch* patch) {
|
|
patch->vf->seek(patch->vf, 0, SEEK_SET);
|
|
|
|
char buffer[4];
|
|
if (patch->vf->read(patch->vf, buffer, 4) != 4) {
|
|
return false;
|
|
}
|
|
|
|
if (memcmp(buffer, "UPS1", 4) == 0) {
|
|
patch->applyPatch = _UPSApplyPatch;
|
|
} else if (memcmp(buffer, "BPS1", 4) == 0) {
|
|
patch->applyPatch = _BPSApplyPatch;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
size_t filesize = patch->vf->size(patch->vf);
|
|
|
|
uint32_t goodCrc32;
|
|
patch->vf->seek(patch->vf, PATCH_CHECKSUM, SEEK_END);
|
|
if (patch->vf->read(patch->vf, &goodCrc32, 4) != 4) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t crc = fileCrc32(patch->vf, filesize + PATCH_CHECKSUM);
|
|
if (crc != goodCrc32) {
|
|
return false;
|
|
}
|
|
|
|
patch->outputSize = _UPSOutputSize;
|
|
return true;
|
|
}
|
|
|
|
size_t _UPSOutputSize(struct Patch* patch, size_t inSize) {
|
|
UNUSED(inSize);
|
|
patch->vf->seek(patch->vf, 4, SEEK_SET);
|
|
if (_decodeLength(patch->vf) != inSize) {
|
|
return 0;
|
|
}
|
|
return _decodeLength(patch->vf);
|
|
}
|
|
|
|
bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) {
|
|
// TODO: Input checksum
|
|
|
|
size_t filesize = patch->vf->size(patch->vf);
|
|
patch->vf->seek(patch->vf, 4, SEEK_SET);
|
|
_decodeLength(patch->vf); // Discard input size
|
|
if (_decodeLength(patch->vf) != outSize) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(out, in, inSize > outSize ? outSize : inSize);
|
|
|
|
size_t offset = 0;
|
|
size_t alreadyRead = 0;
|
|
uint8_t* buf = out;
|
|
while (alreadyRead < filesize + IN_CHECKSUM) {
|
|
offset += _decodeLength(patch->vf);
|
|
uint8_t byte;
|
|
|
|
while (true) {
|
|
if (patch->vf->read(patch->vf, &byte, 1) != 1) {
|
|
return false;
|
|
}
|
|
buf[offset] ^= byte;
|
|
++offset;
|
|
if (!byte) {
|
|
break;
|
|
}
|
|
}
|
|
alreadyRead = patch->vf->seek(patch->vf, 0, SEEK_CUR);
|
|
}
|
|
|
|
uint32_t goodCrc32;
|
|
patch->vf->seek(patch->vf, OUT_CHECKSUM, SEEK_END);
|
|
if (patch->vf->read(patch->vf, &goodCrc32, 4) != 4) {
|
|
return false;
|
|
}
|
|
|
|
patch->vf->seek(patch->vf, 0, SEEK_SET);
|
|
if (doCrc32(out, outSize) != goodCrc32) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) {
|
|
patch->vf->seek(patch->vf, IN_CHECKSUM, SEEK_END);
|
|
uint32_t expectedInChecksum;
|
|
uint32_t expectedOutChecksum;
|
|
patch->vf->read(patch->vf, &expectedInChecksum, sizeof(expectedInChecksum));
|
|
patch->vf->read(patch->vf, &expectedOutChecksum, sizeof(expectedOutChecksum));
|
|
|
|
uint32_t inputChecksum = doCrc32(in, inSize);
|
|
uint32_t outputChecksum = 0;
|
|
|
|
if (inputChecksum != expectedInChecksum) {
|
|
return false;
|
|
}
|
|
|
|
ssize_t filesize = patch->vf->size(patch->vf);
|
|
patch->vf->seek(patch->vf, 4, SEEK_SET);
|
|
_decodeLength(patch->vf); // Discard input size
|
|
if (_decodeLength(patch->vf) != outSize) {
|
|
return false;
|
|
}
|
|
if (inSize > SSIZE_MAX || outSize > SSIZE_MAX) {
|
|
return false;
|
|
}
|
|
size_t metadataLength = _decodeLength(patch->vf);
|
|
patch->vf->seek(patch->vf, metadataLength, SEEK_CUR); // Skip metadata
|
|
size_t writeLocation = 0;
|
|
ssize_t readSourceLocation = 0;
|
|
ssize_t readTargetLocation = 0;
|
|
size_t readOffset;
|
|
uint8_t* writeBuffer = out;
|
|
uint8_t* readBuffer = in;
|
|
while (patch->vf->seek(patch->vf, 0, SEEK_CUR) < filesize + IN_CHECKSUM) {
|
|
size_t command = _decodeLength(patch->vf);
|
|
size_t length = (command >> 2) + 1;
|
|
if (writeLocation + length > outSize) {
|
|
return false;
|
|
}
|
|
size_t i;
|
|
switch (command & 0x3) {
|
|
case 0x0:
|
|
// SourceRead
|
|
memmove(&writeBuffer[writeLocation], &readBuffer[writeLocation], length);
|
|
outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
|
|
writeLocation += length;
|
|
break;
|
|
case 0x1:
|
|
// TargetRead
|
|
if (patch->vf->read(patch->vf, &writeBuffer[writeLocation], length) != (ssize_t) length) {
|
|
return false;
|
|
}
|
|
outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
|
|
writeLocation += length;
|
|
break;
|
|
case 0x2:
|
|
// SourceCopy
|
|
readOffset = _decodeLength(patch->vf);
|
|
if (readOffset & 1) {
|
|
readSourceLocation -= readOffset >> 1;
|
|
} else {
|
|
readSourceLocation += readOffset >> 1;
|
|
}
|
|
if (readSourceLocation < 0 || readSourceLocation > (ssize_t) inSize) {
|
|
return false;
|
|
}
|
|
memmove(&writeBuffer[writeLocation], &readBuffer[readSourceLocation], length);
|
|
outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
|
|
writeLocation += length;
|
|
readSourceLocation += length;
|
|
break;
|
|
case 0x3:
|
|
// TargetCopy
|
|
readOffset = _decodeLength(patch->vf);
|
|
if (readOffset & 1) {
|
|
readTargetLocation -= readOffset >> 1;
|
|
} else {
|
|
readTargetLocation += readOffset >> 1;
|
|
}
|
|
if (readTargetLocation < 0 || readTargetLocation > (ssize_t) outSize) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < length; ++i) {
|
|
// This needs to be bytewise as it can overlap
|
|
writeBuffer[writeLocation] = writeBuffer[readTargetLocation];
|
|
++writeLocation;
|
|
++readTargetLocation;
|
|
}
|
|
outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation - length], length);
|
|
break;
|
|
}
|
|
}
|
|
if (expectedOutChecksum != outputChecksum) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
size_t _decodeLength(struct VFile* vf) {
|
|
size_t shift = 1;
|
|
size_t value = 0;
|
|
uint8_t byte;
|
|
while (true) {
|
|
if (vf->read(vf, &byte, 1) != 1) {
|
|
break;
|
|
}
|
|
value += (byte & 0x7f) * shift;
|
|
if (byte & 0x80) {
|
|
break;
|
|
}
|
|
shift <<= 7;
|
|
value += shift;
|
|
}
|
|
return value;
|
|
}
|