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 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 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}