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}