all repos — mgba @ 181c05c7ac8a739cd6d9dea393ac9a30e1898d96

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			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, const 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	if (inSize > SSIZE_MAX || outSize > SSIZE_MAX) {
133		return false;
134	}
135	size_t metadataLength = _decodeLength(patch->vf);
136	patch->vf->seek(patch->vf, metadataLength, SEEK_CUR); // Skip metadata
137	size_t writeLocation = 0;
138	ssize_t readSourceLocation = 0;
139	ssize_t readTargetLocation = 0;
140	size_t readOffset;
141	uint8_t* writeBuffer = out;
142	const uint8_t* readBuffer = in;
143	while (patch->vf->seek(patch->vf, 0, SEEK_CUR) < filesize + IN_CHECKSUM) {
144		size_t command = _decodeLength(patch->vf);
145		size_t length = (command >> 2) + 1;
146		if (writeLocation + length > outSize) {
147			return false;
148		}
149		size_t i;
150		switch (command & 0x3) {
151		case 0x0:
152			// SourceRead
153			memmove(&writeBuffer[writeLocation], &readBuffer[writeLocation], length);
154			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
155			writeLocation += length;
156			break;
157		case 0x1:
158			// TargetRead
159			if (patch->vf->read(patch->vf, &writeBuffer[writeLocation], length) != (ssize_t) length) {
160				return false;
161			}
162			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
163			writeLocation += length;
164			break;
165		case 0x2:
166			// SourceCopy
167			readOffset = _decodeLength(patch->vf);
168			if (readOffset & 1) {
169				readSourceLocation -= readOffset >> 1;
170			} else {
171				readSourceLocation += readOffset >> 1;
172			}
173			if (readSourceLocation < 0 || readSourceLocation > (ssize_t) inSize) {
174				return false;
175			}
176			memmove(&writeBuffer[writeLocation], &readBuffer[readSourceLocation], length);
177			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation], length);
178			writeLocation += length;
179			readSourceLocation += length;
180			break;
181		case 0x3:
182			// TargetCopy
183			readOffset = _decodeLength(patch->vf);
184			if (readOffset & 1) {
185				readTargetLocation -= readOffset >> 1;
186			} else {
187				readTargetLocation += readOffset >> 1;
188			}
189			if (readTargetLocation < 0 || readTargetLocation > (ssize_t) outSize) {
190				return false;
191			}
192			for (i = 0; i < length; ++i) {
193				// This needs to be bytewise as it can overlap
194				writeBuffer[writeLocation] = writeBuffer[readTargetLocation];
195				++writeLocation;
196				++readTargetLocation;
197			}
198			outputChecksum = updateCrc32(outputChecksum, &writeBuffer[writeLocation - length], length);
199			break;
200		}
201	}
202	if (expectedOutChecksum != outputChecksum) {
203		return false;
204	}
205	return true;
206}
207
208size_t _decodeLength(struct VFile* vf) {
209	size_t shift = 1;
210	size_t value = 0;
211	uint8_t byte;
212	while (true) {
213		if (vf->read(vf, &byte, 1) != 1) {
214			break;
215		}
216		value += (byte & 0x7f) * shift;
217		if (byte & 0x80) {
218			break;
219		}
220		shift <<= 7;
221		value += shift;
222	}
223	return value;
224}