all repos — mgba @ af77e5ab62a920000b8e33544ba2f2efa0cb42e9

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 <mgba-util/patch/ips.h>
  7
  8#include <mgba-util/crc32.h>
  9#include <mgba-util/patch.h>
 10#include <mgba-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, const void* in, size_t inSize, void* out, size_t outSize);
 21static bool _BPSApplyPatch(struct Patch* patch, const 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, const 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			if (offset >= outSize) {
 91				return false;
 92			}
 93			buf[offset] ^= byte;
 94			++offset;
 95			if (!byte) {
 96				break;
 97			}
 98		}
 99		alreadyRead = patch->vf->seek(patch->vf, 0, SEEK_CUR);
100	}
101
102	uint32_t goodCrc32;
103	patch->vf->seek(patch->vf, OUT_CHECKSUM, SEEK_END);
104	if (patch->vf->read(patch->vf, &goodCrc32, 4) != 4) {
105		return false;
106	}
107
108	patch->vf->seek(patch->vf, 0, SEEK_SET);
109	if (doCrc32(out, outSize) != goodCrc32) {
110		return false;
111	}
112	return true;
113}
114
115bool _BPSApplyPatch(struct Patch* patch, const void* in, size_t inSize, void* out, size_t outSize) {
116	patch->vf->seek(patch->vf, IN_CHECKSUM, SEEK_END);
117	uint32_t expectedInChecksum;
118	uint32_t expectedOutChecksum;
119	patch->vf->read(patch->vf, &expectedInChecksum, sizeof(expectedInChecksum));
120	patch->vf->read(patch->vf, &expectedOutChecksum, sizeof(expectedOutChecksum));
121
122	uint32_t inputChecksum = doCrc32(in, inSize);
123	uint32_t outputChecksum = 0;
124
125	if (inputChecksum != expectedInChecksum) {
126		return false;
127	}
128
129	ssize_t filesize = patch->vf->size(patch->vf);
130	patch->vf->seek(patch->vf, 4, SEEK_SET);
131	_decodeLength(patch->vf); // Discard input size
132	if (_decodeLength(patch->vf) != outSize) {
133		return false;
134	}
135	if (inSize > SSIZE_MAX || outSize > SSIZE_MAX) {
136		return false;
137	}
138	size_t metadataLength = _decodeLength(patch->vf);
139	patch->vf->seek(patch->vf, metadataLength, SEEK_CUR); // Skip metadata
140	size_t writeLocation = 0;
141	ssize_t readSourceLocation = 0;
142	ssize_t readTargetLocation = 0;
143	size_t readOffset;
144	uint8_t* writeBuffer = out;
145	const uint8_t* readBuffer = in;
146	while (patch->vf->seek(patch->vf, 0, SEEK_CUR) < filesize + IN_CHECKSUM) {
147		size_t command = _decodeLength(patch->vf);
148		size_t length = (command >> 2) + 1;
149		if (writeLocation + length > outSize) {
150			return false;
151		}
152		size_t i;
153		switch (command & 0x3) {
154		case 0x0:
155			// SourceRead
156			memmove(&writeBuffer[writeLocation], &readBuffer[writeLocation], length);
157			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
158			writeLocation += length;
159			break;
160		case 0x1:
161			// TargetRead
162			if (patch->vf->read(patch->vf, &writeBuffer[writeLocation], length) != (ssize_t) length) {
163				return false;
164			}
165			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
166			writeLocation += length;
167			break;
168		case 0x2:
169			// SourceCopy
170			readOffset = _decodeLength(patch->vf);
171			if (readOffset & 1) {
172				readSourceLocation -= readOffset >> 1;
173			} else {
174				readSourceLocation += readOffset >> 1;
175			}
176			if (readSourceLocation < 0 || readSourceLocation > (ssize_t) inSize) {
177				return false;
178			}
179			memmove(&writeBuffer[writeLocation], &readBuffer[readSourceLocation], length);
180			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
181			writeLocation += length;
182			readSourceLocation += length;
183			break;
184		case 0x3:
185			// TargetCopy
186			readOffset = _decodeLength(patch->vf);
187			if (readOffset & 1) {
188				readTargetLocation -= readOffset >> 1;
189			} else {
190				readTargetLocation += readOffset >> 1;
191			}
192			if (readTargetLocation < 0 || readTargetLocation > (ssize_t) outSize) {
193				return false;
194			}
195			for (i = 0; i < length; ++i) {
196				// This needs to be bytewise as it can overlap
197				writeBuffer[writeLocation] = writeBuffer[readTargetLocation];
198				++writeLocation;
199				++readTargetLocation;
200			}
201			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation - length], length);
202			break;
203		}
204	}
205	if (expectedOutChecksum != outputChecksum) {
206		return false;
207	}
208	return true;
209}
210
211size_t _decodeLength(struct VFile* vf) {
212	size_t shift = 1;
213	size_t value = 0;
214	uint8_t byte;
215	while (true) {
216		if (vf->read(vf, &byte, 1) != 1) {
217			break;
218		}
219		value += (byte & 0x7f) * shift;
220		if (byte & 0x80) {
221			break;
222		}
223		shift <<= 7;
224		value += shift;
225	}
226	return value;
227}