all repos — mgba @ 542662ca682ce0df57849bde1636d5d45d764450

mGBA Game Boy Advance Emulator

src/util/patch-ups.c (view raw)

  1/* Copyright (c) 2013-2014 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "util/patch-ips.h"
  7
  8#include "util/crc32.h"
  9#include "util/patch.h"
 10#include "util/vfs.h"
 11
 12enum {
 13	IN_CHECKSUM = -12,
 14	OUT_CHECKSUM = -8,
 15	PATCH_CHECKSUM = -4,
 16};
 17
 18static size_t _UPSOutputSize(struct Patch* patch, size_t inSize);
 19
 20static bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize);
 21static bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize);
 22
 23static size_t _decodeLength(struct VFile* vf);
 24
 25bool loadPatchUPS(struct Patch* patch) {
 26	patch->vf->seek(patch->vf, 0, SEEK_SET);
 27
 28	char buffer[4];
 29	if (patch->vf->read(patch->vf, buffer, 4) != 4) {
 30		return false;
 31	}
 32
 33	if (memcmp(buffer, "UPS1", 4) == 0) {
 34		patch->applyPatch = _UPSApplyPatch;
 35	} else if (memcmp(buffer, "BPS1", 4) == 0) {
 36		patch->applyPatch = _BPSApplyPatch;
 37	} else {
 38		return false;
 39	}
 40
 41	size_t filesize = patch->vf->size(patch->vf);
 42
 43	uint32_t goodCrc32;
 44	patch->vf->seek(patch->vf, PATCH_CHECKSUM, SEEK_END);
 45	if (patch->vf->read(patch->vf, &goodCrc32, 4) != 4) {
 46		return false;
 47	}
 48
 49	uint32_t crc = fileCrc32(patch->vf, filesize + PATCH_CHECKSUM);
 50	if (crc != goodCrc32) {
 51		return false;
 52	}
 53
 54	patch->outputSize = _UPSOutputSize;
 55	return true;
 56}
 57
 58size_t _UPSOutputSize(struct Patch* patch, size_t inSize) {
 59	UNUSED(inSize);
 60	patch->vf->seek(patch->vf, 4, SEEK_SET);
 61	if (_decodeLength(patch->vf) != inSize) {
 62		return 0;
 63	}
 64	return _decodeLength(patch->vf);
 65}
 66
 67bool _UPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) {
 68	// TODO: Input checksum
 69
 70	size_t filesize = patch->vf->size(patch->vf);
 71	patch->vf->seek(patch->vf, 4, SEEK_SET);
 72	_decodeLength(patch->vf); // Discard input size
 73	if (_decodeLength(patch->vf) != outSize) {
 74		return false;
 75	}
 76
 77	memcpy(out, in, inSize > outSize ? outSize : inSize);
 78
 79	size_t offset = 0;
 80	size_t alreadyRead = 0;
 81	uint8_t* buf = out;
 82	while (alreadyRead < filesize + IN_CHECKSUM) {
 83		offset += _decodeLength(patch->vf);
 84		uint8_t byte;
 85
 86		while (true) {
 87			if (patch->vf->read(patch->vf, &byte, 1) != 1) {
 88				return false;
 89			}
 90			buf[offset] ^= byte;
 91			++offset;
 92			if (!byte) {
 93				break;
 94			}
 95		}
 96		alreadyRead = patch->vf->seek(patch->vf, 0, SEEK_CUR);
 97	}
 98
 99	uint32_t goodCrc32;
100	patch->vf->seek(patch->vf, OUT_CHECKSUM, SEEK_END);
101	if (patch->vf->read(patch->vf, &goodCrc32, 4) != 4) {
102		return false;
103	}
104
105	patch->vf->seek(patch->vf, 0, SEEK_SET);
106	if (doCrc32(out, outSize) != goodCrc32) {
107		return false;
108	}
109	return true;
110}
111
112bool _BPSApplyPatch(struct Patch* patch, void* in, size_t inSize, void* out, size_t outSize) {
113	patch->vf->seek(patch->vf, IN_CHECKSUM, SEEK_END);
114	uint32_t expectedInChecksum;
115	uint32_t expectedOutChecksum;
116	patch->vf->read(patch->vf, &expectedInChecksum, sizeof(expectedInChecksum));
117	patch->vf->read(patch->vf, &expectedOutChecksum, sizeof(expectedOutChecksum));
118
119	uint32_t inputChecksum = doCrc32(in, inSize);
120	uint32_t outputChecksum = 0;
121
122	if (inputChecksum != expectedInChecksum) {
123		return false;
124	}
125
126	ssize_t filesize = patch->vf->size(patch->vf);
127	patch->vf->seek(patch->vf, 4, SEEK_SET);
128	_decodeLength(patch->vf); // Discard input size
129	if (_decodeLength(patch->vf) != outSize) {
130		return false;
131	}
132	size_t metadataLength = _decodeLength(patch->vf);
133	patch->vf->seek(patch->vf, metadataLength, SEEK_CUR); // Skip metadata
134	size_t writeLocation = 0;
135	ssize_t readSourceLocation = 0;
136	ssize_t readTargetLocation = 0;
137	size_t readOffset;
138	uint8_t* writeBuffer = out;
139	uint8_t* readBuffer = in;
140	while (patch->vf->seek(patch->vf, 0, SEEK_CUR) < filesize + IN_CHECKSUM) {
141		size_t command = _decodeLength(patch->vf);
142		size_t length = (command >> 2) + 1;
143		if (writeLocation + length > outSize) {
144			return false;
145		}
146		size_t i;
147		switch (command & 0x3) {
148		case 0x0:
149			// SourceRead
150			memmove(&writeBuffer[writeLocation], &readBuffer[writeLocation], length);
151			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
152			writeLocation += length;
153			break;
154		case 0x1:
155			// TargetRead
156			if (patch->vf->read(patch->vf, &writeBuffer[writeLocation], length) != length) {
157				return false;
158			}
159			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
160			writeLocation += length;
161			break;
162		case 0x2:
163			// SourceCopy
164			readOffset = _decodeLength(patch->vf);
165			if (readOffset & 1) {
166				readSourceLocation -= readOffset >> 1;
167			} else {
168				readSourceLocation += readOffset >> 1;
169			}
170			if (readSourceLocation < 0 || readSourceLocation > inSize) {
171				return false;
172			}
173			memmove(&writeBuffer[writeLocation], &readBuffer[readSourceLocation], length);
174			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
175			writeLocation += length;
176			readSourceLocation += length;
177			break;
178		case 0x3:
179			// TargetCopy
180			readOffset = _decodeLength(patch->vf);
181			if (readOffset & 1) {
182				readTargetLocation -= readOffset >> 1;
183			} else {
184				readTargetLocation += readOffset >> 1;
185			}
186			if (readTargetLocation < 0 || readTargetLocation > outSize) {
187				return false;
188			}
189			for (i = 0; i < length; ++i) {
190				// This needs to be bytewise as it can overlap
191				writeBuffer[writeLocation] = writeBuffer[readTargetLocation];
192				++writeLocation;
193				++readTargetLocation;
194			}
195			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation - length], length);
196			break;
197		}
198	}
199	if (expectedOutChecksum != outputChecksum) {
200		return false;
201	}
202	return true;
203}
204
205size_t _decodeLength(struct VFile* vf) {
206	size_t shift = 1;
207	size_t value = 0;
208	uint8_t byte;
209	while (true) {
210		if (vf->read(vf, &byte, 1) != 1) {
211			break;
212		}
213		value += (byte & 0x7f) * shift;
214		if (byte & 0x80) {
215			break;
216		}
217		shift <<= 7;
218		value += shift;
219	}
220	return value;
221}