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}