GCC Code Coverage Report


Directory: src/
File: src/mpq-patch-reader.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 1050 1237 84.9%
Functions: 45 45 100.0%
Branches: 682 938 72.7%

Line Branch Exec Source
1 /*
2 * mpq-patch-reader.c -- private MPQ incremental patch decoding.
3 *
4 * Copyright (c) 2003-2026 Maik Broemme <mbroemme@libmpq.org>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this file; if not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "mpq-patch-reader.h"
21 #include "mpq-archive.h"
22 #include "mpq-attributes.h"
23 #include "mpq-block.h"
24 #include "mpq-crypto.h"
25 #include "mpq-endian.h"
26 #include "mpq-md5.h"
27 #include "mpq-reader.h"
28 #include "mpq-signature.h"
29 #include "mpq-source.h"
30
31 #include <ctype.h>
32 #include <limits.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <zlib.h>
36
37 #ifdef _WIN32
38 #include <windows.h>
39 #endif
40
41 #define LIBMPQ_PATCH_HEADER_SIZE 68u
42 #define LIBMPQ_PATCH_BSDIFF_HEADER_SIZE 32u
43 #define LIBMPQ_PATCH_COPY_SIZE 65536u
44
45 struct mpq_patch_view
46 {
47 mpq_directory_s *directory;
48 char *temporary;
49 char *path;
50 mpq_archive_s *archive;
51 libmpq__off_t archive_offset;
52 };
53
54 /*
55 * A live patch hash entry is authoritative; a listfile may add its plaintext
56 * name for decryption or insertion, but never determines whether it exists.
57 */
58 typedef struct
59 {
60 uint32_t hash_a;
61 uint32_t hash_b;
62 uint32_t physical_hash_a;
63 uint32_t physical_hash_b;
64 uint16_t locale;
65 uint16_t platform;
66 uint32_t block_index;
67 char *name;
68 char *physical_name;
69 uint8_t live;
70 uint8_t internal;
71 } mpq_patch_entry_s;
72
73 /* Compare one buffer with a serialized MD5 digest. */
74 static int32_t
75 246 patch_check_md5(const uint8_t *data, size_t size, const uint8_t expected[16])
76 {
77 mpq_md5_s context;
78 uint8_t actual[16];
79
80 246 libmpq__md5_init(&context);
81 246 libmpq__md5_update(&context, data, size);
82 246 libmpq__md5_final(&context, actual);
83
2/2
✓ Branch 0 taken 239 times.
✓ Branch 1 taken 7 times.
246 return memcmp(actual, expected, sizeof(actual)) == 0 ? 0 : LIBMPQ_ERROR_FORMAT;
84 }
85
86 /* Only the fixed header has fields; extension bytes need not be allocated. */
87 static int32_t
88 132 patch_info_parse_header(
89 const uint8_t *data, size_t header_size, size_t available_size, mpq_patch_info_s *info
90 )
91 {
92
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 131 times.
132 if (info == NULL)
93 1 return LIBMPQ_ERROR_EXIST;
94 131 memset(info, 0, sizeof(*info));
95
4/4
✓ Branch 0 taken 130 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 102 times.
131 if (data == NULL || header_size < LIBMPQ_PATCH_INFO_SIZE)
96 29 return LIBMPQ_ERROR_FORMAT;
97 102 info->length = libmpq__load_le32(data);
98 102 info->flags = libmpq__load_le32(data + 4);
99 102 info->data_size = libmpq__load_le32(data + 8);
100 102 memcpy(info->md5, data + 12, sizeof(info->md5));
101
4/4
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 91 times.
✓ Branch 3 taken 6 times.
102 if (info->length < LIBMPQ_PATCH_INFO_SIZE || info->length > available_size ||
102
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 90 times.
91 (info->flags & 0x80000000u) == 0)
103 12 return LIBMPQ_ERROR_FORMAT;
104 90 return 0;
105 }
106
107 int32_t
108 50 libmpq__patch_info_parse(const uint8_t *data, size_t size, mpq_patch_info_s *info)
109 {
110 50 return patch_info_parse_header(data, size, size, info);
111 }
112
113 /* Expand Blizzard's zero-skipping RLE wrapper around a BSD0 transform. */
114 static int32_t
115 11 patch_expand_rle(const uint8_t *encoded, size_t encoded_size, uint8_t *decoded, size_t decoded_size)
116 {
117 11 size_t input = 4;
118 11 size_t output = 0;
119
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (encoded_size < 4)
121 ✗ return LIBMPQ_ERROR_FORMAT;
122 11 memset(decoded, 0, decoded_size);
123
3/4
✓ Branch 0 taken 622 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 622 times.
✗ Branch 3 not taken.
630 while (input < encoded_size && output < decoded_size) {
124 622 uint8_t command = encoded[input++];
125 622 size_t count = (size_t)(command & 0x7fu) + 1;
126
127
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 621 times.
622 if (count > decoded_size - output)
128 1 return LIBMPQ_ERROR_FORMAT;
129
2/2
✓ Branch 0 taken 275 times.
✓ Branch 1 taken 346 times.
621 if ((command & 0x80u) != 0) {
130
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 273 times.
275 if (count > encoded_size - input)
131 2 return LIBMPQ_ERROR_FORMAT;
132 273 memcpy(decoded + output, encoded + input, count);
133 273 input += count;
134 }
135 619 output += count;
136 }
137
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 return input == encoded_size && output == decoded_size ? 0 : LIBMPQ_ERROR_FORMAT;
138 }
139
140 /* Apply the BSD0 control, delta, and extra blocks using bounded offsets. */
141 static int32_t
142 23 patch_apply_bsd0(
143 const uint8_t *base, size_t base_size, const uint8_t *data, size_t data_size, uint8_t *output,
144 size_t output_size
145 )
146 {
147 size_t control_size;
148 size_t delta_size;
149 size_t extra_size;
150 23 size_t control = 0;
151 23 size_t delta = 0;
152 23 size_t extra = 0;
153 23 size_t target = 0;
154 23 int64_t source = 0;
155 const uint8_t *controls;
156 const uint8_t *deltas;
157 const uint8_t *extras;
158
159
4/6
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
45 if (data_size < LIBMPQ_PATCH_BSDIFF_HEADER_SIZE || memcmp(data, "BSDIFF40", 8) != 0 ||
160 44 libmpq__load_le64(data + 24) != output_size || libmpq__load_le64(data + 8) > SIZE_MAX ||
161 22 libmpq__load_le64(data + 16) > SIZE_MAX)
162 1 return LIBMPQ_ERROR_FORMAT;
163 22 control_size = (size_t)libmpq__load_le64(data + 8);
164 22 delta_size = (size_t)libmpq__load_le64(data + 16);
165
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 1 times.
22 if (control_size > data_size - LIBMPQ_PATCH_BSDIFF_HEADER_SIZE ||
166
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 20 times.
21 delta_size > data_size - LIBMPQ_PATCH_BSDIFF_HEADER_SIZE - control_size)
167 2 return LIBMPQ_ERROR_FORMAT;
168 20 controls = data + LIBMPQ_PATCH_BSDIFF_HEADER_SIZE;
169 20 deltas = controls + control_size;
170 20 extras = deltas + delta_size;
171 20 extra_size = data_size - LIBMPQ_PATCH_BSDIFF_HEADER_SIZE - control_size - delta_size;
172
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 16 times.
41 while (target < output_size) {
173 size_t add;
174 size_t copy;
175 uint32_t raw_move;
176 int64_t move;
177 size_t i;
178
179
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
25 if (control_size - control < 12)
180 1 return LIBMPQ_ERROR_FORMAT;
181 24 add = libmpq__load_le32(controls + control);
182 24 copy = libmpq__load_le32(controls + control + 4);
183 24 raw_move = libmpq__load_le32(controls + control + 8);
184 24 move =
185
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 22 times.
24 (raw_move & 0x80000000u) != 0 ? -(int64_t)(raw_move & 0x7fffffffu) : (int64_t)raw_move;
186 24 control += 12;
187
5/6
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 1 times.
24 if (add > output_size - target || add > delta_size - delta || source < 0 ||
188
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 source > INT64_MAX - (int64_t)add)
189 2 return LIBMPQ_ERROR_FORMAT;
190
2/2
✓ Branch 0 taken 49274 times.
✓ Branch 1 taken 22 times.
49296 for (i = 0; i < add; i++) {
191 49274 uint64_t old_offset = (uint64_t)source + i;
192
2/2
✓ Branch 0 taken 49273 times.
✓ Branch 1 taken 1 times.
49274 uint8_t old_byte = old_offset < base_size ? base[old_offset] : 0;
193
194 49274 output[target + i] = (uint8_t)(deltas[delta + i] + old_byte);
195 }
196 22 target += add;
197 22 delta += add;
198 22 source += (int64_t)add;
199
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if (copy > output_size - target || copy > extra_size - extra)
200 ✗ return LIBMPQ_ERROR_FORMAT;
201 22 memcpy(output + target, extras + extra, copy);
202 22 target += copy;
203 22 extra += copy;
204
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
22 if ((move > 0 && source > INT64_MAX - move) || (move < 0 && source < INT64_MIN - move))
205 ✗ return LIBMPQ_ERROR_FORMAT;
206 22 source += move;
207
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
22 if (add == 0 && copy == 0)
208 1 return LIBMPQ_ERROR_FORMAT;
209 }
210
6/6
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 1 times.
16 return target == output_size && control == control_size && delta == delta_size &&
211 extra == extra_size
212 ? 0
213
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
32 : LIBMPQ_ERROR_FORMAT;
214 }
215
216 /* Apply and verify a PTCH payload, optionally borrowing COPY output. */
217 static int32_t
218 176 patch_apply_internal(
219 const uint8_t *base, size_t base_size, const uint8_t *patch, size_t patch_size,
220 uint8_t **output, size_t *output_size, uint8_t borrow_copy
221 )
222 {
223 uint32_t complete_size;
224 uint32_t before_size;
225 uint32_t after_size;
226 uint32_t md5_block_size;
227 uint32_t transform_size;
228 const uint8_t *encoded;
229 size_t encoded_size;
230 size_t decoded_size;
231 176 uint8_t *decoded = NULL;
232 176 uint8_t *result = NULL;
233 int32_t status;
234
235
2/4
✓ Branch 0 taken 176 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 176 times.
176 if (output == NULL || output_size == NULL)
236 ✗ return LIBMPQ_ERROR_EXIST;
237 176 *output = NULL;
238 176 *output_size = 0;
239
8/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 174 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 174 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 106 times.
✓ Branch 7 taken 68 times.
176 if ((base == NULL && base_size != 0) || patch == NULL ||
240
2/2
✓ Branch 0 taken 105 times.
✓ Branch 1 taken 1 times.
106 patch_size < LIBMPQ_PATCH_HEADER_SIZE || memcmp(patch, "PTCH", 4) != 0 ||
241
4/4
✓ Branch 0 taken 104 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 103 times.
105 memcmp(patch + 16, "MD5_", 4) != 0 || memcmp(patch + 56, "XFRM", 4) != 0)
242 73 return LIBMPQ_ERROR_FORMAT;
243 103 complete_size = libmpq__load_le32(patch + 4);
244 103 before_size = libmpq__load_le32(patch + 8);
245 103 after_size = libmpq__load_le32(patch + 12);
246 103 md5_block_size = libmpq__load_le32(patch + 20);
247 103 transform_size = libmpq__load_le32(patch + 60);
248
5/6
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 101 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 101 times.
✗ Branch 5 not taken.
103 if (before_size != base_size || md5_block_size != 40 ||
249
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 1 times.
101 complete_size < LIBMPQ_PATCH_HEADER_SIZE || transform_size < 12 ||
250
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 98 times.
100 transform_size - 12 != patch_size - LIBMPQ_PATCH_HEADER_SIZE)
251 5 return LIBMPQ_ERROR_FORMAT;
252 98 status = patch_check_md5(base, base_size, patch + 24);
253
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 97 times.
98 if (status != 0)
254 1 return status;
255 97 encoded = patch + LIBMPQ_PATCH_HEADER_SIZE;
256 97 encoded_size = patch_size - LIBMPQ_PATCH_HEADER_SIZE;
257 97 decoded_size = complete_size - LIBMPQ_PATCH_HEADER_SIZE;
258
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 28 times.
97 if (memcmp(patch + 64, "COPY", 4) == 0) {
259
4/4
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 67 times.
69 if (decoded_size != encoded_size || decoded_size != after_size)
260 2 return LIBMPQ_ERROR_FORMAT;
261 67 decoded = (uint8_t *)encoded;
262
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 times.
28 } else if (memcmp(patch + 64, "BSD0", 4) == 0) {
263
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
27 if (decoded_size < LIBMPQ_PATCH_BSDIFF_HEADER_SIZE)
264 1 return LIBMPQ_ERROR_FORMAT;
265
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 15 times.
26 if (decoded_size != encoded_size) {
266
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 decoded = malloc(decoded_size == 0 ? 1 : decoded_size);
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (decoded == NULL)
268 ✗ return LIBMPQ_ERROR_MALLOC;
269 11 status = patch_expand_rle(encoded, encoded_size, decoded, decoded_size);
270
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 8 times.
11 if (status != 0)
271 3 goto done;
272 } else {
273 15 decoded = (uint8_t *)encoded;
274 }
275 } else {
276 1 return LIBMPQ_ERROR_FORMAT;
277 }
278
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 23 times.
90 if (memcmp(patch + 64, "COPY", 4) == 0) {
279
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 3 times.
67 if (borrow_copy)
280 64 result = decoded;
281 else {
282
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 result = malloc(after_size == 0 ? 1 : after_size);
283
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (result == NULL) {
284 ✗ status = LIBMPQ_ERROR_MALLOC;
285 ✗ goto done;
286 }
287 3 memcpy(result, decoded, after_size);
288 }
289 67 status = 0;
290 } else {
291
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 result = malloc(after_size == 0 ? 1 : after_size);
292
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 if (result == NULL) {
293 ✗ status = LIBMPQ_ERROR_MALLOC;
294 ✗ goto done;
295 }
296 23 status = patch_apply_bsd0(base, base_size, decoded, decoded_size, result, after_size);
297 }
298
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 10 times.
90 if (status == 0)
299 80 status = patch_check_md5(result, after_size, patch + 40);
300
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 76 times.
90 if (status == 0) {
301 76 *output = result;
302 76 *output_size = after_size;
303 76 result = NULL;
304 }
305
306 14 done:
307
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 82 times.
93 if (decoded != encoded)
308 11 free(decoded);
309
1/2
✓ Branch 0 taken 93 times.
✗ Branch 1 not taken.
93 if (result != encoded)
310 93 free(result);
311 93 return status;
312 }
313
314 /* Preserve the owning output contract for tests, fuzzers, and other callers. */
315 int32_t
316 110 libmpq__patch_apply(
317 const uint8_t *base, size_t base_size, const uint8_t *patch, size_t patch_size,
318 uint8_t **output, size_t *output_size
319 )
320 {
321 110 return patch_apply_internal(base, base_size, patch, patch_size, output, output_size, 0);
322 }
323
324 /* Construct the stable path of a sibling temporary file. */
325 static char *
326 197 patch_temporary_path(const char *absolute, const char *temporary)
327 {
328 197 const char *slash = strrchr(absolute, '/');
329 size_t directory_size;
330 197 size_t name_size = strlen(temporary);
331 char *path;
332
333 #ifdef _WIN32
334 {
335 const char *backslash = strrchr(absolute, '\\');
336
337 if (backslash != NULL && (slash == NULL || backslash > slash))
338 slash = backslash;
339 }
340 #endif
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 197 times.
197 if (slash == NULL)
342 ✗ return NULL;
343 197 directory_size = (size_t)(slash - absolute) + 1;
344
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 197 times.
197 if (directory_size > SIZE_MAX - name_size - 1)
345 ✗ return NULL;
346 197 path = malloc(directory_size + name_size + 1);
347
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 197 times.
197 if (path == NULL)
348 ✗ return NULL;
349 197 memcpy(path, absolute, directory_size);
350 197 memcpy(path + directory_size, temporary, name_size + 1);
351 197 return path;
352 }
353
354 /* Select a process temporary directory, separate from the read-only base. */
355 static char *
356 92 patch_temporary_anchor(void)
357 {
358 #ifdef _WIN32
359 DWORD capacity = GetTempPathW(0, NULL);
360 wchar_t *wide;
361 char *directory;
362 DWORD length;
363 int bytes;
364
365 if (capacity == 0)
366 return NULL;
367 wide = malloc((size_t)capacity * sizeof(*wide));
368 if (wide == NULL)
369 return NULL;
370 length = GetTempPathW(capacity, wide);
371 if (length == 0 || length >= capacity) {
372 free(wide);
373 return NULL;
374 }
375 bytes = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL);
376 directory = bytes > 0 ? malloc((size_t)bytes + sizeof("libmpq-patch-view-anchor")) : NULL;
377 if (directory != NULL) {
378 if (WideCharToMultiByte(CP_UTF8, 0, wide, -1, directory, bytes, NULL, NULL) == 0) {
379 free(directory);
380 directory = NULL;
381 } else {
382 strcat(directory, "libmpq-patch-view-anchor");
383 }
384 }
385 free(wide);
386 return directory;
387 #else
388 92 const char *directory = getenv("TMPDIR");
389 size_t length;
390 char *anchor;
391
392
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
92 if (directory == NULL || directory[0] != '/')
393 92 directory = "/tmp";
394 92 length = strlen(directory);
395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (length > SIZE_MAX - sizeof("/libmpq-patch-view-anchor"))
396 ✗ return NULL;
397 92 anchor = malloc(length + sizeof("/libmpq-patch-view-anchor"));
398
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if (anchor != NULL) {
399 92 memcpy(anchor, directory, length);
400 92 memcpy(anchor + length, "/libmpq-patch-view-anchor", sizeof("/libmpq-patch-view-anchor"));
401 }
402 92 return anchor;
403 #endif
404 }
405
406 /* Copy a bounded physical container range without interpreting its contents. */
407 static int32_t
408 282 patch_copy_range(mpq_archive_s *base, FILE *output, uint64_t offset, uint64_t remaining)
409 {
410 uint8_t buffer[LIBMPQ_PATCH_COPY_SIZE];
411
412
2/4
✓ Branch 0 taken 282 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 282 times.
282 if (offset > base->file_size || remaining > base->file_size - offset)
413 ✗ return LIBMPQ_ERROR_SIZE;
414
415
2/2
✓ Branch 0 taken 199 times.
✓ Branch 1 taken 282 times.
481 while (remaining != 0) {
416 199 size_t count = remaining < sizeof(buffer) ? (size_t)remaining : sizeof(buffer);
417 199 int32_t status = libmpq__source_read_at(base->source, offset, buffer, count);
418
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
199 if (status != 0)
420 ✗ return status;
421
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 199 times.
199 if (fwrite(buffer, 1, count, output) != count)
422 ✗ return LIBMPQ_ERROR_WRITE;
423 199 offset += count;
424 199 remaining -= count;
425 }
426 282 return 0;
427 }
428
429 /* Read a member through current archive metadata, including adjusted patch blocks. */
430 static int32_t
431 374 patch_read_adjusted(
432 mpq_archive_s *archive, const char *name, uint32_t number, uint8_t **data, size_t *size
433 )
434 {
435 374 libmpq__off_t transferred = 0;
436 374 libmpq__off_t logical_size = 0;
437 374 uint8_t *bytes = NULL;
438 int32_t status;
439
440 374 status = libmpq__reader_offsets_acquire(archive, number, name);
441
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 372 times.
374 if (status != 0)
442 2 return status;
443 372 status = libmpq__file_size_unpacked(archive, number, &logical_size);
444
2/4
✓ Branch 0 taken 372 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 372 times.
372 if (status != 0 || logical_size < 0 || (uint64_t)logical_size > SIZE_MAX) {
445 ✗ if (status == 0)
446 ✗ status = LIBMPQ_ERROR_SIZE;
447 ✗ goto done;
448 }
449
1/2
✓ Branch 0 taken 372 times.
✗ Branch 1 not taken.
372 bytes = malloc(logical_size == 0 ? 1 : (size_t)logical_size);
450
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 372 times.
372 if (bytes == NULL) {
451 ✗ status = LIBMPQ_ERROR_MALLOC;
452 ✗ goto done;
453 }
454 372 status = libmpq__file_read(archive, number, bytes, logical_size, &transferred);
455
3/4
✓ Branch 0 taken 368 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 368 times.
372 if (status == 0 && transferred != logical_size)
456 ✗ status = LIBMPQ_ERROR_READ;
457
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 368 times.
372 if (status == 0) {
458 368 *data = bytes;
459 368 *size = (size_t)logical_size;
460 368 bytes = NULL;
461 }
462
463 4 done:
464 372 free(bytes);
465 {
466 372 int32_t release_status = libmpq__reader_offsets_release(archive, number);
467
468
2/2
✓ Branch 0 taken 368 times.
✓ Branch 1 taken 4 times.
372 if (status == 0)
469 368 status = release_status;
470 }
471 372 return status;
472 }
473
474 /* Use complete-file verification when reading an ordinary named member. */
475 static int32_t
476 187 patch_read_named(mpq_archive_s *archive, const char *name, uint8_t **data, size_t *size)
477 {
478 uint32_t number;
479 187 int32_t status = libmpq__file_number(archive, name, &number);
480
481
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 182 times.
187 if (status != 0)
482 5 return status;
483 182 return patch_read_adjusted(archive, name, number, data, size);
484 }
485
486 /*
487 * Parse the plaintext patch prefix before using its body size, then
488 * decode the stored body through normal MPQ reader semantics. Patch-file
489 * blocks describe the resulting file size rather than the decoded body size.
490 */
491 static int32_t
492 82 patch_read_incremental(
493 mpq_archive_s *archive, const char *name, uint32_t number, uint8_t **data, size_t *size
494 )
495 {
496 82 mpq_archive_s *clone = NULL;
497 mpq_block_s *block;
498 uint8_t fixed[LIBMPQ_PATCH_INFO_SIZE];
499 82 uint8_t *body = NULL;
500 mpq_patch_info_s info;
501 uint64_t offset;
502 uint64_t shifted;
503 uint32_t prefix_size;
504 uint32_t physical;
505 82 size_t body_size = 0;
506 int32_t status;
507
508 82 *data = NULL;
509 82 *size = 0;
510 82 physical = archive->mpq_map[number].block_table_indices;
511 82 block = &archive->mpq_block[physical];
512
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
82 if (block->packed_size < LIBMPQ_PATCH_INFO_SIZE)
513 ✗ return LIBMPQ_ERROR_FORMAT;
514 82 offset = (uint64_t)archive->archive_offset + block->offset +
515 82 ((uint64_t)archive->mpq_block_ex[physical].offset_high << 32);
516 82 status = libmpq__source_read_at(archive->source, offset, fixed, sizeof(fixed));
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
82 if (status != 0)
518 ✗ return status;
519 82 status = patch_info_parse_header(fixed, sizeof(fixed), block->packed_size, &info);
520
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 74 times.
82 if (status != 0)
521 8 return status;
522 74 prefix_size = info.length;
523 74 status = libmpq__archive_clone(&clone, archive);
524
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (status != 0)
525 ✗ goto done;
526 74 block = &clone->mpq_block[physical];
527 74 shifted = (uint64_t)block->offset +
528 74 ((uint64_t)clone->mpq_block_ex[physical].offset_high << 32) + prefix_size;
529
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
74 if (shifted > UINT32_MAX && clone->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_ONE) {
530 ✗ status = LIBMPQ_ERROR_FORMAT;
531 ✗ goto done;
532 }
533 74 block->offset = (uint32_t)shifted;
534 74 clone->mpq_block_ex[physical].offset_high = (uint16_t)(shifted >> 32);
535 74 block->packed_size -= prefix_size;
536 74 block->unpacked_size = info.data_size;
537 74 block->flags &= ~LIBMPQ_FILE_FLAG_PATCH_FILE;
538 74 clone->attributes_error = LIBMPQ_ERROR_EXIST;
539 74 status = patch_read_adjusted(clone, name, number, &body, &body_size);
540
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 68 times.
74 if (status != 0)
541 6 goto done;
542
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (body_size != info.data_size) {
543 ✗ status = LIBMPQ_ERROR_FORMAT;
544 ✗ goto done;
545 }
546 68 status = patch_check_md5(body, body_size, info.md5);
547
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 66 times.
68 if (status == 0) {
548 66 *data = body;
549 66 *size = body_size;
550 66 body = NULL;
551 }
552
553 2 done:
554
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 if (clone != NULL) {
555 74 int32_t close_status = libmpq__archive_close(clone);
556
557
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 8 times.
74 if (status == 0)
558 66 status = close_status;
559 }
560
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 66 times.
74 if (status != 0) {
561 8 free(*data);
562 8 *data = NULL;
563 8 *size = 0;
564 }
565 74 free(body);
566 74 return status;
567 }
568
569 /* Live hash identity is enough to match an existing member without its name. */
570 static uint8_t
571 252572 patch_live_hash(const mpq_archive_s *archive, const mpq_hash_s *hash)
572 {
573
2/2
✓ Branch 0 taken 14906 times.
✓ Branch 1 taken 237666 times.
267478 return hash->block_table_index < archive->mpq_header.block_table_count &&
574
1/2
✓ Branch 0 taken 14906 times.
✗ Branch 1 not taken.
14906 (archive->mpq_block[hash->block_table_index].flags & LIBMPQ_FLAG_EXISTS) != 0;
575 }
576
577 static uint32_t
578 791 patch_hash_slot(
579 const mpq_archive_s *archive, uint32_t hash_a, uint32_t hash_b, uint16_t locale,
580 uint16_t platform
581 )
582 {
583
2/2
✓ Branch 0 taken 55449 times.
✓ Branch 1 taken 59 times.
55508 for (uint32_t i = 0; i < archive->mpq_header.hash_table_count; i++) {
584 55449 const mpq_hash_s *candidate = &archive->mpq_hash[i];
585
586
4/4
✓ Branch 1 taken 5913 times.
✓ Branch 2 taken 49536 times.
✓ Branch 3 taken 732 times.
✓ Branch 4 taken 5181 times.
55449 if (patch_live_hash(archive, candidate) && candidate->hash_a == hash_a &&
587
2/4
✓ Branch 0 taken 732 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 732 times.
✗ Branch 3 not taken.
732 candidate->hash_b == hash_b && candidate->locale == locale &&
588
1/2
✓ Branch 0 taken 732 times.
✗ Branch 1 not taken.
732 candidate->platform == platform)
589 732 return i;
590 }
591 59 return UINT32_MAX;
592 }
593
594 static uint32_t
595 246 patch_matching_hash(const mpq_archive_s *archive, const mpq_patch_entry_s *needle)
596 {
597 492 return patch_hash_slot(
598 246 archive, needle->hash_a, needle->hash_b, needle->locale, needle->platform
599 );
600 }
601
602 static uint32_t
603 411 patch_number_for_block(const mpq_archive_s *archive, uint32_t block)
604 {
605
1/2
✓ Branch 0 taken 1283 times.
✗ Branch 1 not taken.
1283 for (uint32_t i = 0; i < archive->files; i++)
606
2/2
✓ Branch 0 taken 411 times.
✓ Branch 1 taken 872 times.
1283 if (archive->mpq_map[i].block_table_indices == block)
607 411 return i;
608 ✗ return UINT32_MAX;
609 }
610
611 /* Resolve identity first; the source's block index only decodes its wire row. */
612 static int32_t
613 545 patch_attribute_for_identity(
614 const mpq_archive_s *archive, uint32_t hash_a, uint32_t hash_b, uint16_t locale,
615 uint16_t platform, mpq_file_attributes_s *attributes
616 )
617 {
618 uint32_t slot;
619
620 545 memset(attributes, 0, sizeof(*attributes));
621
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 545 times.
545 if (archive->attributes == NULL)
622 ✗ return LIBMPQ_ERROR_EXIST;
623 545 slot = patch_hash_slot(archive, hash_a, hash_b, locale, platform);
624
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 524 times.
545 if (slot == UINT32_MAX)
625 21 return LIBMPQ_ERROR_EXIST;
626 524 libmpq__attributes_get(
627 524 archive->attributes, archive->mpq_hash[slot].block_table_index, attributes
628 );
629 524 return 0;
630 }
631
632 static uint8_t
633 590 patch_known_internal(uint32_t hash_a, uint32_t hash_b)
634 {
635 static const char *const names[] = { LIBMPQ_LISTFILE_NAME, LIBMPQ_ATTRIBUTES_NAME,
636 LIBMPQ_SIGNATURE_NAME, "(patch_metadata)" };
637
638
2/2
✓ Branch 0 taken 1951 times.
✓ Branch 1 taken 381 times.
2332 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++)
639
3/4
✓ Branch 1 taken 209 times.
✓ Branch 2 taken 1742 times.
✓ Branch 3 taken 209 times.
✗ Branch 4 not taken.
2160 if (hash_a == libmpq__crypto_hash_string(names[i], 0x100) &&
640 209 hash_b == libmpq__crypto_hash_string(names[i], 0x200))
641 209 return 1;
642 381 return 0;
643 }
644
645 static uint32_t
646 300 patch_internal_slot(const mpq_archive_s *archive, const char *name)
647 {
648 300 uint32_t hash_a = libmpq__crypto_hash_string(name, 0x100);
649 300 uint32_t hash_b = libmpq__crypto_hash_string(name, 0x200);
650
651
2/2
✓ Branch 0 taken 6922 times.
✓ Branch 1 taken 41 times.
6963 for (uint32_t i = 0; i < archive->mpq_header.hash_table_count; i++)
652
2/2
✓ Branch 1 taken 1054 times.
✓ Branch 2 taken 5868 times.
6922 if (patch_live_hash(archive, &archive->mpq_hash[i]) &&
653
3/4
✓ Branch 0 taken 259 times.
✓ Branch 1 taken 795 times.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
1054 archive->mpq_hash[i].hash_a == hash_a && archive->mpq_hash[i].hash_b == hash_b)
654 259 return i;
655 41 return UINT32_MAX;
656 }
657
658 static uint8_t
659 786 patch_hash_present(const mpq_archive_s *archive, uint32_t hash_a, uint32_t hash_b)
660 {
661
2/2
✓ Branch 0 taken 29589 times.
✓ Branch 1 taken 106 times.
29695 for (uint32_t i = 0; i < archive->mpq_header.hash_table_count; i++)
662
2/2
✓ Branch 1 taken 5698 times.
✓ Branch 2 taken 23891 times.
29589 if (patch_live_hash(archive, &archive->mpq_hash[i]) &&
663
3/4
✓ Branch 0 taken 680 times.
✓ Branch 1 taken 5018 times.
✓ Branch 2 taken 680 times.
✗ Branch 3 not taken.
5698 archive->mpq_hash[i].hash_a == hash_a && archive->mpq_hash[i].hash_b == hash_b)
664 680 return 1;
665 106 return 0;
666 }
667
668 static void
669 76 patch_delete_hash(mpq_hash_s *hash)
670 {
671 76 memset(hash, 0, sizeof(*hash));
672 76 hash->block_table_index = LIBMPQ_HASH_DELETED;
673 76 }
674
675 /* Build authoritative entries from live hashes and optionally enrich their names. */
676 static int32_t
677 109 patch_entries(mpq_archive_s *patch, mpq_patch_entry_s **entries, uint32_t *entry_count)
678 {
679 109 uint8_t *list = NULL;
680 109 size_t size = 0;
681 109 size_t position = 0;
682 int32_t status;
683
684 109 *entries = NULL;
685 109 *entry_count = patch->mpq_header.hash_table_count;
686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 109 times.
109 if (*entry_count == 0)
687 ✗ return LIBMPQ_ERROR_FORMAT;
688 109 *entries = calloc(*entry_count, sizeof(**entries));
689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 109 times.
109 if (*entries == NULL)
690 ✗ return LIBMPQ_ERROR_MALLOC;
691
2/2
✓ Branch 0 taken 76600 times.
✓ Branch 1 taken 109 times.
76709 for (uint32_t i = 0; i < *entry_count; i++) {
692 76600 const mpq_hash_s *hash = &patch->mpq_hash[i];
693 76600 mpq_patch_entry_s *entry = &(*entries)[i];
694
695
2/2
✓ Branch 1 taken 76160 times.
✓ Branch 2 taken 440 times.
76600 if (!patch_live_hash(patch, hash))
696 76160 continue;
697 440 entry->hash_a = hash->hash_a;
698 440 entry->hash_b = hash->hash_b;
699 440 entry->physical_hash_a = hash->hash_a;
700 440 entry->physical_hash_b = hash->hash_b;
701 440 entry->locale = hash->locale;
702 440 entry->platform = hash->platform;
703 440 entry->block_index = hash->block_table_index;
704 440 entry->live = 1;
705 }
706 109 status = patch_read_named(patch, LIBMPQ_LISTFILE_NAME, &list, &size);
707
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 104 times.
109 if (status != 0) {
708 5 free(list);
709
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 return status == LIBMPQ_ERROR_MALLOC ? status : 0;
710 }
711
2/2
✓ Branch 0 taken 327 times.
✓ Branch 1 taken 104 times.
431 while (position < size) {
712 327 size_t start = position;
713 size_t length;
714 uint32_t first;
715 uint32_t second;
716
717
3/4
✓ Branch 0 taken 4680 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4353 times.
✓ Branch 3 taken 327 times.
4680 while (position < size && list[position] != '\n')
718 4353 position++;
719 327 length = position - start;
720
1/2
✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
327 if (position < size)
721 327 position++;
722
3/4
✓ Branch 0 taken 325 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 325 times.
327 if (length != 0 && list[start + length - 1] == '\r')
723 ✗ length--;
724
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 325 times.
327 if (length == 0)
725 2 continue;
726
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 325 times.
325 if (memchr(list + start, 0, length) != NULL)
727 ✗ continue;
728 {
729 325 char *name = malloc(length + 1);
730
731
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 325 times.
325 if (name == NULL) {
732 ✗ status = LIBMPQ_ERROR_MALLOC;
733 ✗ break;
734 }
735 325 memcpy(name, list + start, length);
736 325 name[length] = '\0';
737 325 first = libmpq__crypto_hash_string(name, 0x100);
738 325 second = libmpq__crypto_hash_string(name, 0x200);
739
2/2
✓ Branch 0 taken 177928 times.
✓ Branch 1 taken 325 times.
178253 for (uint32_t i = 0; i < *entry_count; i++) {
740 177928 mpq_patch_entry_s *entry = &(*entries)[i];
741
742
5/6
✓ Branch 0 taken 1609 times.
✓ Branch 1 taken 176319 times.
✓ Branch 2 taken 325 times.
✓ Branch 3 taken 1284 times.
✓ Branch 4 taken 325 times.
✗ Branch 5 not taken.
177928 if (!entry->live || entry->hash_a != first || entry->hash_b != second ||
743
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 325 times.
325 entry->name != NULL)
744 177603 continue;
745 325 entry->name = malloc(length + 1);
746
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 325 times.
325 if (entry->name == NULL) {
747 ✗ status = LIBMPQ_ERROR_MALLOC;
748 ✗ break;
749 }
750 325 memcpy(entry->name, name, length + 1);
751 }
752 325 free(name);
753
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 325 times.
325 if (status != 0)
754 ✗ break;
755 }
756 }
757 104 free(list);
758 104 return status;
759 }
760
761 /* Compare names using the same byte-wise uppercase folding as MPQ hashing. */
762 static uint8_t
763 111 patch_name_equal(const char *left, const char *right, size_t length)
764 {
765
2/2
✓ Branch 0 taken 1295 times.
✓ Branch 1 taken 108 times.
1403 for (size_t i = 0; i < length; i++)
766
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1292 times.
1295 if (toupper((unsigned char)left[i]) != toupper((unsigned char)right[i]))
767 3 return 0;
768 108 return 1;
769 }
770
771 /* Identify only a complete metadata path component, not a similar basename. */
772 static uint8_t
773 526 patch_metadata_path(const char *name, size_t *prefix_length)
774 {
775 526 const char *separator = strrchr(name, '\\');
776
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 443 times.
526 const char *basename = separator == NULL ? name : separator + 1;
777 static const char marker[] = "(patch_metadata)";
778
779
4/4
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 456 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 68 times.
596 if (strlen(basename) != sizeof(marker) - 1 ||
780 70 !patch_name_equal(basename, marker, sizeof(marker) - 1))
781 458 return 0;
782
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 50 times.
68 *prefix_length = separator == NULL ? 0 : (size_t)(separator - name) + 1;
783 68 return 1;
784 }
785
786 /* Normalize an explicit prefix to either empty or one trailing backslash. */
787 static int32_t
788 12 patch_normalize_prefix(const char *input, char **result)
789 {
790 12 size_t length = strlen(input);
791
792
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 12 times.
16 while (length != 0 && input[length - 1] == '\\')
793 4 length--;
794
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (length > SIZE_MAX - 2)
795 ✗ return LIBMPQ_ERROR_SIZE;
796 12 *result = malloc(length + (length != 0) + 1);
797
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (*result == NULL)
798 ✗ return LIBMPQ_ERROR_MALLOC;
799 12 memcpy(*result, input, length);
800
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (length != 0)
801 12 (*result)[length++] = '\\';
802 12 (*result)[length] = '\0';
803 12 return 0;
804 }
805
806 /* Keep physical member lookup separate from logical lower-layer identity. */
807 static int32_t
808 109 patch_prepare_entries(
809 mpq_archive_s *patch, mpq_patch_entry_s *entries, uint32_t entry_count,
810 const mpq_patch_source_s *source, uint8_t *detected
811 )
812 {
813 109 char *discovered = NULL;
814 109 char *explicit_prefix = NULL;
815 109 char *marker_name = NULL;
816 const char *prefix;
817 size_t prefix_length;
818 109 int32_t status = 0;
819
820
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 97 times.
109 if (source->prefix != NULL) {
821 12 status = patch_normalize_prefix(source->prefix, &explicit_prefix);
822
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (status != 0)
823 ✗ goto done;
824 }
825
2/2
✓ Branch 0 taken 76553 times.
✓ Branch 1 taken 108 times.
76661 for (uint32_t i = 0; i < entry_count; i++) {
826 76553 mpq_patch_entry_s *entry = &entries[i];
827 size_t length;
828
829
6/6
✓ Branch 0 taken 439 times.
✓ Branch 1 taken 76114 times.
✓ Branch 2 taken 325 times.
✓ Branch 3 taken 114 times.
✓ Branch 5 taken 257 times.
✓ Branch 6 taken 68 times.
76553 if (!entry->live || entry->name == NULL || !patch_metadata_path(entry->name, &length))
830 76485 continue;
831 68 *detected = 1;
832
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (length > SIZE_MAX - 1) {
833 ✗ status = LIBMPQ_ERROR_SIZE;
834 1 goto done;
835 }
836
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 1 times.
68 if (discovered == NULL) {
837 67 discovered = malloc(length + 1);
838
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 if (discovered == NULL) {
839 ✗ status = LIBMPQ_ERROR_MALLOC;
840 ✗ goto done;
841 }
842 67 memcpy(discovered, entry->name, length);
843 67 discovered[length] = '\0';
844
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 } else if (strlen(discovered) != length ||
845 ✗ !patch_name_equal(discovered, entry->name, length)) {
846 1 status = LIBMPQ_ERROR_FORMAT;
847 1 goto done;
848 }
849 {
850 67 uint32_t number = patch_number_for_block(patch, entry->block_index);
851 libmpq__off_t size;
852
853
2/4
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
67 if (number == UINT32_MAX || libmpq__file_size_unpacked(patch, number, &size) != 0 ||
854
2/4
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
67 size <= 0 || size >= 64) {
855 ✗ status = LIBMPQ_ERROR_FORMAT;
856 ✗ goto done;
857 }
858 }
859 }
860
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 96 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 times.
108 if (explicit_prefix != NULL && discovered != NULL &&
861
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
21 (strlen(explicit_prefix) != strlen(discovered) ||
862 10 !patch_name_equal(explicit_prefix, discovered, strlen(discovered)))) {
863 1 status = LIBMPQ_ERROR_FORMAT;
864 1 goto done;
865 }
866
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 96 times.
107 prefix = explicit_prefix != NULL ? explicit_prefix : discovered;
867
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 66 times.
107 if (prefix == NULL)
868 41 prefix = "";
869 107 prefix_length = strlen(prefix);
870
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 91 times.
107 if (prefix_length != 0) {
871 static const char marker[] = "(patch_metadata)";
872
873
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (prefix_length > SIZE_MAX - sizeof(marker)) {
874 ✗ status = LIBMPQ_ERROR_SIZE;
875 ✗ goto done;
876 }
877 16 marker_name = malloc(prefix_length + sizeof(marker));
878
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (marker_name == NULL) {
879 ✗ status = LIBMPQ_ERROR_MALLOC;
880 ✗ goto done;
881 }
882 16 memcpy(marker_name, prefix, prefix_length);
883 16 memcpy(marker_name + prefix_length, marker, sizeof(marker));
884 }
885
2/2
✓ Branch 0 taken 76462 times.
✓ Branch 1 taken 106 times.
76568 for (uint32_t i = 0; i < entry_count; i++) {
886 76462 mpq_patch_entry_s *entry = &entries[i];
887 size_t ignored;
888
889
2/2
✓ Branch 0 taken 76030 times.
✓ Branch 1 taken 432 times.
76462 if (!entry->live)
890 76431 continue;
891
4/4
✓ Branch 1 taken 223 times.
✓ Branch 2 taken 209 times.
✓ Branch 3 taken 47 times.
✓ Branch 4 taken 176 times.
432 if (patch_known_internal(entry->physical_hash_a, entry->physical_hash_b) ||
892
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 32 times.
47 (marker_name != NULL &&
893
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
62 entry->physical_hash_a == libmpq__crypto_hash_string(marker_name, 0x100) &&
894 15 entry->physical_hash_b == libmpq__crypto_hash_string(marker_name, 0x200)) ||
895
3/4
✓ Branch 0 taken 201 times.
✓ Branch 1 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 201 times.
208 (entry->name != NULL && patch_metadata_path(entry->name, &ignored))) {
896 224 entry->internal = 1;
897 224 continue;
898 }
899
2/2
✓ Branch 0 taken 176 times.
✓ Branch 1 taken 32 times.
208 if (prefix_length == 0)
900 176 continue;
901
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 31 times.
32 if (entry->name == NULL) {
902 1 status = LIBMPQ_ERROR_FORMAT;
903 1 goto done;
904 }
905
3/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 30 times.
62 if (strlen(entry->name) < prefix_length ||
906 31 !patch_name_equal(entry->name, prefix, prefix_length)) {
907 1 entry->live = 0;
908 1 continue;
909 }
910
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (entry->name[prefix_length] == '\0') {
911 ✗ status = LIBMPQ_ERROR_FORMAT;
912 ✗ goto done;
913 }
914 30 entry->physical_name = entry->name;
915 {
916 30 const char *logical = entry->physical_name + prefix_length;
917 30 size_t length = strlen(logical);
918
919 30 entry->name = malloc(length + 1);
920
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 if (entry->name != NULL)
921 30 memcpy(entry->name, logical, length + 1);
922 }
923
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (entry->name == NULL) {
924 ✗ status = LIBMPQ_ERROR_MALLOC;
925 ✗ goto done;
926 }
927 30 entry->hash_a = libmpq__crypto_hash_string(entry->name, 0x100);
928 30 entry->hash_b = libmpq__crypto_hash_string(entry->name, 0x200);
929 }
930
931 106 done:
932 109 free(marker_name);
933 109 free(explicit_prefix);
934 109 free(discovered);
935 109 return status;
936 }
937
938 /* Grow every reader-owned block array before changing the serialized count. */
939 static int32_t
940 261 patch_new_block(mpq_archive_s *archive, uint32_t *index)
941 {
942 261 uint32_t block = archive->mpq_header.block_table_count;
943 261 size_t count = (size_t)block + 1;
944 void *grown;
945
946
2/4
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 261 times.
✗ Branch 3 not taken.
261 if (block == UINT32_MAX || count > SIZE_MAX / sizeof(*archive->mpq_block) ||
947
2/4
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 261 times.
✗ Branch 3 not taken.
261 count > SIZE_MAX / sizeof(*archive->mpq_block_ex) ||
948
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 count > SIZE_MAX / sizeof(*archive->mpq_file) ||
949 count > SIZE_MAX / sizeof(*archive->mpq_map))
950 ✗ return LIBMPQ_ERROR_SIZE;
951 261 grown = realloc(archive->mpq_block, count * sizeof(*archive->mpq_block));
952
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (grown == NULL)
953 ✗ return LIBMPQ_ERROR_MALLOC;
954 261 archive->mpq_block = grown;
955 261 grown = realloc(archive->mpq_block_ex, count * sizeof(*archive->mpq_block_ex));
956
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (grown == NULL)
957 ✗ return LIBMPQ_ERROR_MALLOC;
958 261 archive->mpq_block_ex = grown;
959 261 grown = realloc(archive->mpq_file, count * sizeof(*archive->mpq_file));
960
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (grown == NULL)
961 ✗ return LIBMPQ_ERROR_MALLOC;
962 261 archive->mpq_file = grown;
963 261 grown = realloc(archive->mpq_map, count * sizeof(*archive->mpq_map));
964
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (grown == NULL)
965 ✗ return LIBMPQ_ERROR_MALLOC;
966 261 archive->mpq_map = grown;
967 261 memset(&archive->mpq_block[block], 0, sizeof(*archive->mpq_block));
968 261 memset(&archive->mpq_block_ex[block], 0, sizeof(*archive->mpq_block_ex));
969 261 archive->mpq_file[block] = NULL;
970 261 memset(&archive->mpq_map[block], 0, sizeof(*archive->mpq_map));
971 261 archive->mpq_header.block_table_count++;
972 261 *index = block;
973 261 return 0;
974 }
975
976 static int32_t
977 39 patch_insert_hash(mpq_archive_s *archive, const char *name, const mpq_hash_s *entry)
978 {
979 39 uint32_t count = archive->mpq_header.hash_table_count;
980 uint32_t start;
981 39 uint32_t candidate = UINT32_MAX;
982
983
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 if (count == 0)
984 ✗ return LIBMPQ_ERROR_FORMAT;
985 39 start = libmpq__crypto_hash_string(name, 0) % count;
986
987
1/2
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
43 for (uint32_t i = 0; i < count; i++) {
988 43 uint32_t slot = (start + i) % count;
989 43 uint32_t index = archive->mpq_hash[slot].block_table_index;
990
991
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 41 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
43 if (index == UINT32_MAX - 1u && candidate == UINT32_MAX)
992 2 candidate = slot;
993
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 4 times.
43 if (index == UINT32_MAX) {
994
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 2 times.
39 if (candidate == UINT32_MAX)
995 37 candidate = slot;
996 39 break;
997 }
998 }
999
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 if (candidate == UINT32_MAX)
1000 ✗ return LIBMPQ_ERROR_SIZE;
1001 39 archive->mpq_hash[candidate] = *entry;
1002 39 return 0;
1003 }
1004
1005 /* Changed files use a private raw single-unit payload; old physical data stays intact. */
1006 static int32_t
1007 261 patch_append_member(
1008 mpq_archive_s *archive, FILE *output, const uint8_t *data, size_t size, uint32_t *block
1009 )
1010 {
1011 261 libmpq__off_t absolute = libmpq__file_tell(output);
1012 uint64_t offset;
1013 int32_t status;
1014
1015
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (absolute < archive->archive_offset)
1016 ✗ return LIBMPQ_ERROR_SEEK;
1017 261 offset = (uint64_t)(absolute - archive->archive_offset);
1018
3/6
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 261 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 261 times.
261 if (offset > UINT32_MAX || size > UINT32_MAX || size > UINT32_MAX - offset)
1019 ✗ return LIBMPQ_ERROR_SIZE;
1020 261 status = patch_new_block(archive, block);
1021
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 261 times.
261 if (status != 0)
1022 ✗ return status;
1023
2/4
✓ Branch 0 taken 261 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 261 times.
261 if (size != 0 && fwrite(data, 1, size, output) != size)
1024 ✗ return LIBMPQ_ERROR_WRITE;
1025 261 archive->mpq_block[*block].offset = (uint32_t)offset;
1026 261 archive->mpq_block[*block].packed_size = (uint32_t)size;
1027 261 archive->mpq_block[*block].unpacked_size = (uint32_t)size;
1028 261 archive->mpq_block[*block].flags = LIBMPQ_FLAG_EXISTS | LIBMPQ_FLAG_SINGLE;
1029 261 return 0;
1030 }
1031
1032 static uint8_t
1033 106 patch_list_contains(const uint8_t *list, size_t size, const char *name)
1034 {
1035 106 size_t length = strlen(name);
1036 106 size_t at = 0;
1037
1038
2/2
✓ Branch 0 taken 488 times.
✓ Branch 1 taken 32 times.
520 while (at < size) {
1039 488 size_t start = at;
1040
1041
3/4
✓ Branch 0 taken 6546 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6058 times.
✓ Branch 3 taken 488 times.
6546 while (at < size && list[at] != '\n')
1042 6058 at++;
1043
4/4
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 413 times.
✓ Branch 2 taken 74 times.
✓ Branch 3 taken 1 times.
488 if (at - start == length && memcmp(list + start, name, length) == 0)
1044 74 return 1;
1045
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 if (at < size)
1046 414 at++;
1047 }
1048 32 return 0;
1049 }
1050
1051 static void
1052 150 patch_set_attributes(
1053 mpq_file_attributes_s *attributes, uint32_t flags, const uint8_t *data, size_t size,
1054 int32_t patch_bit
1055 )
1056 {
1057 mpq_md5_s md5;
1058 150 size_t at = 0;
1059 150 uint32_t crc = (uint32_t)crc32(0, Z_NULL, 0);
1060
1061 150 memset(attributes, 0, sizeof(*attributes));
1062 150 attributes->flags = flags;
1063 150 attributes->patch_bit = patch_bit;
1064
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 150 times.
300 while (at < size) {
1065
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 uInt count = size - at > UINT_MAX ? UINT_MAX : (uInt)(size - at);
1066
1067 150 crc = (uint32_t)crc32(crc, data + at, count);
1068 150 at += count;
1069 }
1070 150 attributes->crc32 = crc;
1071 150 libmpq__md5_init(&md5);
1072 150 libmpq__md5_update(&md5, data, size);
1073 150 libmpq__md5_final(&md5, attributes->md5);
1074 150 }
1075
1076 /* Keep known live names, remove deleted names, and enrich new named entries. */
1077 static int32_t
1078 85 patch_rewrite_listfile(
1079 mpq_archive_s *lower, mpq_archive_s *target, const mpq_patch_entry_s *entries,
1080 uint32_t entry_count, FILE *output, mpq_file_attributes_s *list_row, uint32_t attribute_flags,
1081 uint8_t add_attributes_name
1082 )
1083 {
1084 85 uint8_t *original = NULL;
1085 85 uint8_t *updated = NULL;
1086 85 size_t original_size = 0;
1087 size_t capacity;
1088 85 size_t used = 0;
1089 85 size_t at = 0;
1090 85 uint32_t slot = patch_internal_slot(target, LIBMPQ_LISTFILE_NAME);
1091 int32_t status;
1092
1093
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 78 times.
85 if (slot == UINT32_MAX)
1094 7 return 0;
1095 78 status = patch_read_named(lower, LIBMPQ_LISTFILE_NAME, &original, &original_size);
1096
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 if (status != 0)
1097 ✗ return status;
1098
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 if (original_size == SIZE_MAX) {
1099 ✗ status = LIBMPQ_ERROR_SIZE;
1100 ✗ goto done;
1101 }
1102
1103 /* A final line without a newline gains one byte when rewritten. */
1104 78 capacity = original_size + 1;
1105
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 77 times.
78 if (add_attributes_name) {
1106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (capacity > SIZE_MAX - sizeof(LIBMPQ_ATTRIBUTES_NAME)) {
1107 ✗ status = LIBMPQ_ERROR_SIZE;
1108 ✗ goto done;
1109 }
1110 1 capacity += sizeof(LIBMPQ_ATTRIBUTES_NAME);
1111 }
1112
2/2
✓ Branch 0 taken 35200 times.
✓ Branch 1 taken 78 times.
35278 for (uint32_t i = 0; i < entry_count; i++) {
1113 size_t length;
1114
1115
2/2
✓ Branch 0 taken 34951 times.
✓ Branch 1 taken 249 times.
35200 if (entries[i].name == NULL)
1116 34951 continue;
1117 249 length = strlen(entries[i].name);
1118
2/4
✓ Branch 0 taken 249 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 249 times.
249 if (capacity == SIZE_MAX || length > SIZE_MAX - capacity - 1) {
1119 ✗ status = LIBMPQ_ERROR_SIZE;
1120 ✗ goto done;
1121 }
1122 249 capacity += length + 1;
1123 }
1124
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 updated = malloc(capacity == 0 ? 1 : capacity);
1125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 if (updated == NULL) {
1126 ✗ status = LIBMPQ_ERROR_MALLOC;
1127 ✗ goto done;
1128 }
1129
2/2
✓ Branch 0 taken 650 times.
✓ Branch 1 taken 78 times.
728 while (at < original_size) {
1130 650 size_t start = at;
1131 size_t length;
1132 char *name;
1133
1134
5/6
✓ Branch 0 taken 8446 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 7797 times.
✓ Branch 3 taken 649 times.
✓ Branch 4 taken 7797 times.
✗ Branch 5 not taken.
8447 while (at < original_size && original[at] != '\n' && original[at] != '\r')
1135 7797 at++;
1136 650 length = at - start;
1137
5/6
✓ Branch 0 taken 1221 times.
✓ Branch 1 taken 78 times.
✓ Branch 2 taken 649 times.
✓ Branch 3 taken 572 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 572 times.
1299 while (at < original_size && (original[at] == '\n' || original[at] == '\r'))
1138 649 at++;
1139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 650 times.
650 if (length == 0)
1140 ✗ continue;
1141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 650 times.
650 if (memchr(original + start, 0, length) != NULL) {
1142 ✗ status = LIBMPQ_ERROR_FORMAT;
1143 ✗ goto done;
1144 }
1145 650 name = malloc(length + 1);
1146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 650 times.
650 if (name == NULL) {
1147 ✗ status = LIBMPQ_ERROR_MALLOC;
1148 ✗ goto done;
1149 }
1150 650 memcpy(name, original + start, length);
1151 650 name[length] = '\0';
1152
5/6
✓ Branch 0 taken 628 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 628 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 575 times.
✓ Branch 5 taken 53 times.
1278 if (strcmp(name, LIBMPQ_SIGNATURE_NAME) != 0 && strcmp(name, "(patch_metadata)") != 0 &&
1153 628 patch_hash_present(
1154 target, libmpq__crypto_hash_string(name, 0x100),
1155 libmpq__crypto_hash_string(name, 0x200)
1156 )) {
1157
2/4
✓ Branch 0 taken 575 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 575 times.
575 if (used > capacity || length >= capacity - used) {
1158 ✗ free(name);
1159 ✗ status = LIBMPQ_ERROR_SIZE;
1160 ✗ goto done;
1161 }
1162 575 memcpy(updated + used, name, length);
1163 575 used += length;
1164 575 updated[used++] = '\n';
1165 }
1166 650 free(name);
1167 }
1168
2/2
✓ Branch 0 taken 35200 times.
✓ Branch 1 taken 78 times.
35278 for (uint32_t i = 0; i < entry_count; i++) {
1169 size_t length;
1170
1171
7/8
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 34869 times.
✓ Branch 2 taken 162 times.
✓ Branch 3 taken 169 times.
✓ Branch 4 taken 158 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 158 times.
✗ Branch 7 not taken.
35358 if (!entries[i].live || entries[i].internal || entries[i].name == NULL ||
1172
2/2
✓ Branch 1 taken 105 times.
✓ Branch 2 taken 53 times.
316 patch_known_internal(entries[i].hash_a, entries[i].hash_b) ||
1173
2/2
✓ Branch 1 taken 74 times.
✓ Branch 2 taken 31 times.
263 !patch_hash_present(target, entries[i].hash_a, entries[i].hash_b) ||
1174 105 patch_list_contains(updated, used, entries[i].name))
1175 35169 continue;
1176 31 length = strlen(entries[i].name);
1177
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
31 if (used > capacity || length >= capacity - used) {
1178 ✗ status = LIBMPQ_ERROR_SIZE;
1179 ✗ goto done;
1180 }
1181 31 memcpy(updated + used, entries[i].name, length);
1182 31 used += length;
1183 31 updated[used++] = '\n';
1184 }
1185
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 77 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
78 if (add_attributes_name && !patch_list_contains(updated, used, LIBMPQ_ATTRIBUTES_NAME)) {
1186 static const uint8_t attributes_line[] = LIBMPQ_ATTRIBUTES_NAME "\n";
1187 1 size_t length = sizeof(attributes_line) - 1;
1188
1189
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (used > capacity || length > capacity - used) {
1190 ✗ status = LIBMPQ_ERROR_SIZE;
1191 ✗ goto done;
1192 }
1193 1 memcpy(updated + used, attributes_line, length);
1194 1 used += length;
1195 }
1196 {
1197 uint32_t block;
1198
1199 78 status = patch_append_member(target, output, updated, used, &block);
1200
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 if (status == 0) {
1201 78 target->mpq_hash[slot].block_table_index = block;
1202
2/2
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 17 times.
78 if (list_row != NULL)
1203 61 patch_set_attributes(list_row, attribute_flags, updated, used, 0);
1204 }
1205 }
1206
1207 78 done:
1208 78 free(original);
1209 78 free(updated);
1210 78 return status;
1211 }
1212
1213 /* Map source and changed hash identities onto the result's serialized rows. */
1214 static int32_t
1215 65 patch_remap_attributes(
1216 const mpq_archive_s *lower, const mpq_archive_s *target, const mpq_patch_entry_s *entries,
1217 const mpq_file_attributes_s *changed_rows, const uint8_t *changed, uint32_t entry_count,
1218 const mpq_file_attributes_s *list_row, mpq_file_attributes_s *result_rows
1219 )
1220 {
1221 65 uint32_t list_a = libmpq__crypto_hash_string(LIBMPQ_LISTFILE_NAME, 0x100);
1222 65 uint32_t list_b = libmpq__crypto_hash_string(LIBMPQ_LISTFILE_NAME, 0x200);
1223 65 uint32_t attributes_a = libmpq__crypto_hash_string(LIBMPQ_ATTRIBUTES_NAME, 0x100);
1224 65 uint32_t attributes_b = libmpq__crypto_hash_string(LIBMPQ_ATTRIBUTES_NAME, 0x200);
1225 uint8_t *assigned;
1226 65 int32_t status = 0;
1227
1228 65 assigned = calloc(target->mpq_header.block_table_count, 1);
1229
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (assigned == NULL)
1230 ✗ return LIBMPQ_ERROR_MALLOC;
1231
2/2
✓ Branch 0 taken 3168 times.
✓ Branch 1 taken 65 times.
3233 for (uint32_t i = 0; i < target->mpq_header.hash_table_count; i++) {
1232 3168 const mpq_hash_s *hash = &target->mpq_hash[i];
1233 3168 mpq_file_attributes_s row = { 0 };
1234 uint32_t block;
1235 3168 uint8_t resolved = 0;
1236
1237
2/2
✓ Branch 1 taken 641 times.
✓ Branch 2 taken 2527 times.
3168 if (!patch_live_hash(target, hash) ||
1238
3/4
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 581 times.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
641 (hash->hash_a == attributes_a && hash->hash_b == attributes_b))
1239 2587 continue;
1240 581 block = hash->block_table_index;
1241
4/6
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 520 times.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 61 times.
✗ Branch 5 not taken.
581 if (hash->hash_a == list_a && hash->hash_b == list_b && list_row != NULL) {
1242 61 row = *list_row;
1243 61 resolved = 1;
1244 }
1245
4/4
✓ Branch 0 taken 115967 times.
✓ Branch 1 taken 431 times.
✓ Branch 2 taken 115817 times.
✓ Branch 3 taken 150 times.
116398 for (uint32_t j = entry_count; j > 0 && !resolved; j--) {
1246 115817 const mpq_patch_entry_s *entry = &entries[j - 1];
1247
1248
5/6
✓ Branch 0 taken 830 times.
✓ Branch 1 taken 114987 times.
✓ Branch 2 taken 89 times.
✓ Branch 3 taken 741 times.
✓ Branch 4 taken 89 times.
✗ Branch 5 not taken.
115817 if (changed[j - 1] && entry->hash_a == hash->hash_a && entry->hash_b == hash->hash_b &&
1249
2/4
✓ Branch 0 taken 89 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
89 entry->locale == hash->locale && entry->platform == hash->platform) {
1250 89 row = changed_rows[j - 1];
1251 89 resolved = 1;
1252 }
1253 }
1254
4/4
✓ Branch 0 taken 431 times.
✓ Branch 1 taken 150 times.
✓ Branch 2 taken 430 times.
✓ Branch 3 taken 1 times.
581 if (!resolved && lower->attributes != NULL) {
1255 430 status = patch_attribute_for_identity(
1256 430 lower, hash->hash_a, hash->hash_b, hash->locale, hash->platform, &row
1257 );
1258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 430 times.
430 if (status != 0)
1259 ✗ goto done;
1260 }
1261
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 581 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
581 if (assigned[block] && memcmp(&result_rows[block], &row, sizeof(row)) != 0) {
1262 ✗ status = LIBMPQ_ERROR_FORMAT;
1263 ✗ goto done;
1264 }
1265 581 result_rows[block] = row;
1266 581 assigned[block] = 1;
1267 }
1268
1269 65 done:
1270 65 free(assigned);
1271 65 return status;
1272 }
1273
1274 static int32_t
1275 85 patch_rewrite_attributes(
1276 mpq_archive_s *target, FILE *output, mpq_file_attributes_s *attributes, uint32_t flags
1277 )
1278 {
1279 85 uint32_t slot = patch_internal_slot(target, LIBMPQ_ATTRIBUTES_NAME);
1280 uint32_t self;
1281 uint32_t block;
1282 85 uint8_t *data = NULL;
1283 85 size_t size = 0;
1284 int32_t status;
1285
1286
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 65 times.
85 if (attributes == NULL)
1287 20 return 0;
1288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (target->mpq_header.block_table_count == UINT32_MAX)
1289 ✗ return LIBMPQ_ERROR_FORMAT;
1290 65 self = target->mpq_header.block_table_count;
1291 status =
1292 65 libmpq__attributes_serialize_patch_bits(attributes, self + 1, self, flags, &data, &size);
1293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (status != 0)
1294 ✗ return status;
1295 65 status = patch_append_member(target, output, data, size, &block);
1296
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65 if (status == 0) {
1297
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (block != self)
1298 ✗ status = LIBMPQ_ERROR_FORMAT;
1299
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 5 times.
65 else if (slot != UINT32_MAX)
1300 60 target->mpq_hash[slot].block_table_index = block;
1301 else {
1302 5 mpq_hash_s inserted = { libmpq__crypto_hash_string(LIBMPQ_ATTRIBUTES_NAME, 0x100),
1303 5 libmpq__crypto_hash_string(LIBMPQ_ATTRIBUTES_NAME, 0x200), 0, 0,
1304 block };
1305
1306 5 status = patch_insert_hash(target, LIBMPQ_ATTRIBUTES_NAME, &inserted);
1307 }
1308 }
1309 65 free(data);
1310 65 return status;
1311 }
1312
1313 /* Serialize the private view's updated tables without using update semantics. */
1314 static int32_t
1315 85 patch_write_tables(mpq_archive_s *archive, FILE *output)
1316 {
1317 85 uint8_t header[LIBMPQ_HEADER_WIRE_SIZE + LIBMPQ_HEADER_EX_WIRE_SIZE] = { 0 };
1318 mpq_header_s wire_header;
1319 mpq_header_ex_s wire_ex;
1320 int32_t result;
1321 uint8_t *raw;
1322 uint64_t hash_offset;
1323 uint64_t block_offset;
1324 85 uint64_t extended_offset = 0;
1325 uint64_t end;
1326 size_t bytes;
1327
1328
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (libmpq__file_tell(output) < 0)
1329 ✗ return LIBMPQ_ERROR_SEEK;
1330
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (libmpq__file_tell(output) < archive->archive_offset)
1331 ✗ return LIBMPQ_ERROR_SEEK;
1332 85 hash_offset = (uint64_t)(libmpq__file_tell(output) - archive->archive_offset);
1333
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 if (hash_offset > UINT32_MAX ||
1334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 archive->mpq_header.hash_table_count > UINT32_MAX / LIBMPQ_HASH_ENTRY_WIRE_SIZE)
1335 ✗ return LIBMPQ_ERROR_SIZE;
1336 85 bytes = (size_t)archive->mpq_header.hash_table_count * LIBMPQ_HASH_ENTRY_WIRE_SIZE;
1337
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 raw = malloc(bytes == 0 ? 1 : bytes);
1338
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (raw == NULL)
1339 ✗ return LIBMPQ_ERROR_MALLOC;
1340 85 result = libmpq__hash_table_encode(
1341 85 archive->mpq_hash, archive->mpq_header.hash_table_count, raw, bytes
1342 );
1343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (result < 0) {
1344 ✗ free(raw);
1345 ✗ return result;
1346 }
1347 85 libmpq__crypto_encrypt_block(
1348 raw, (uint32_t)bytes, libmpq__crypto_hash_string("(hash table)", 0x300)
1349 );
1350
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (fwrite(raw, 1, bytes, output) != bytes) {
1351 ✗ free(raw);
1352 ✗ return LIBMPQ_ERROR_WRITE;
1353 }
1354 85 free(raw);
1355 85 block_offset = (uint64_t)(libmpq__file_tell(output) - archive->archive_offset);
1356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (archive->mpq_header.block_table_count > UINT32_MAX / LIBMPQ_BLOCK_ENTRY_WIRE_SIZE)
1357 ✗ return LIBMPQ_ERROR_SIZE;
1358 85 bytes = (size_t)archive->mpq_header.block_table_count * LIBMPQ_BLOCK_ENTRY_WIRE_SIZE;
1359
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 raw = malloc(bytes == 0 ? 1 : bytes);
1360
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (raw == NULL)
1361 ✗ return LIBMPQ_ERROR_MALLOC;
1362 85 result = libmpq__block_table_encode(
1363 85 archive->mpq_block, archive->mpq_header.block_table_count, raw, bytes
1364 );
1365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (result < 0) {
1366 ✗ free(raw);
1367 ✗ return result;
1368 }
1369 85 libmpq__crypto_encrypt_block(
1370 raw, (uint32_t)bytes, libmpq__crypto_hash_string("(block table)", 0x300)
1371 );
1372
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (fwrite(raw, 1, bytes, output) != bytes) {
1373 ✗ free(raw);
1374 ✗ return LIBMPQ_ERROR_WRITE;
1375 }
1376 85 free(raw);
1377
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 62 times.
85 if (archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) {
1378 23 extended_offset = (uint64_t)(libmpq__file_tell(output) - archive->archive_offset);
1379
2/2
✓ Branch 0 taken 546 times.
✓ Branch 1 taken 23 times.
569 for (uint32_t i = 0; i < archive->mpq_header.block_table_count; i++) {
1380 uint8_t word[2];
1381
1382 result =
1383 546 libmpq__block_ex_table_encode(&archive->mpq_block_ex[i], 1, word, sizeof(word));
1384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 546 times.
546 if (result < 0)
1385 ✗ return result;
1386
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 546 times.
546 if (fwrite(word, 1, sizeof(word), output) != sizeof(word))
1387 ✗ return LIBMPQ_ERROR_WRITE;
1388 }
1389 }
1390 85 end = (uint64_t)(libmpq__file_tell(output) - archive->archive_offset);
1391
2/4
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 85 times.
85 if (end > UINT32_MAX || archive->mpq_header.header_size > sizeof(header))
1392 ✗ return LIBMPQ_ERROR_SIZE;
1393 85 wire_header = archive->mpq_header;
1394 85 wire_header.mpq_magic = LIBMPQ_HEADER;
1395 85 wire_header.archive_size = (uint32_t)end;
1396 85 wire_header.hash_table_offset = (uint32_t)hash_offset;
1397 85 wire_header.block_table_offset = (uint32_t)block_offset;
1398 85 result = libmpq__header_encode(&wire_header, header, sizeof(header));
1399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (result < 0)
1400 ✗ return result;
1401
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 62 times.
85 if (archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) {
1402 23 wire_ex.extended_offset = extended_offset;
1403 23 wire_ex.hash_table_offset_high = 0;
1404 23 wire_ex.block_table_offset_high = 0;
1405 23 result = libmpq__header_ex_encode(
1406 &wire_ex, header + LIBMPQ_HEADER_WIRE_SIZE, LIBMPQ_HEADER_EX_WIRE_SIZE
1407 );
1408
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 if (result < 0)
1409 ✗ return result;
1410 }
1411
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 if (libmpq__file_seek(output, (uint64_t)archive->archive_offset, SEEK_SET) != 0 ||
1412 85 fwrite(header, 1, archive->mpq_header.header_size, output) !=
1413
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 archive->mpq_header.header_size)
1414 ✗ return LIBMPQ_ERROR_WRITE;
1415
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 return fflush(output) == 0 ? 0 : LIBMPQ_ERROR_WRITE;
1416 }
1417
1418 /* Apply one hash entry, using its name only when needed for decoding or insertion. */
1419 static int32_t
1420 190 patch_stage_hash(
1421 mpq_archive_s *lower, mpq_archive_s *target, mpq_archive_s *patch,
1422 const mpq_patch_entry_s *entry, FILE *output, mpq_file_attributes_s *changed_row,
1423 uint32_t attribute_flags
1424 )
1425 {
1426 190 uint32_t slot = patch_matching_hash(lower, entry);
1427 190 uint32_t patch_number = patch_number_for_block(patch, entry->block_index);
1428 190 uint32_t lower_number = UINT32_MAX;
1429 190 uint32_t flags = patch->mpq_block[entry->block_index].flags;
1430 190 uint8_t *payload = NULL;
1431 190 uint8_t *base = NULL;
1432 190 uint8_t *result = NULL;
1433 190 uint8_t borrowed_result = 0;
1434 190 size_t payload_size = 0;
1435 190 size_t base_size = 0;
1436 190 size_t result_size = 0;
1437 uint32_t block;
1438 int32_t status;
1439
1440
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 190 times.
190 if (patch_number == UINT32_MAX)
1441 ✗ return LIBMPQ_ERROR_FORMAT;
1442
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 36 times.
190 if (slot != UINT32_MAX)
1443 154 lower_number = patch_number_for_block(lower, lower->mpq_hash[slot].block_table_index);
1444
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 136 times.
190 if ((flags & LIBMPQ_FILE_FLAG_DELETE_MARKER) != 0) {
1445
1/2
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
54 if (slot != UINT32_MAX)
1446 54 patch_delete_hash(&target->mpq_hash[slot]);
1447 54 return 0;
1448 }
1449
4/4
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 100 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 34 times.
136 if (slot == UINT32_MAX && entry->name == NULL)
1450 2 return LIBMPQ_ERROR_FORMAT;
1451
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 52 times.
134 if ((flags & LIBMPQ_FILE_FLAG_PATCH_FILE) != 0)
1452 82 status = patch_read_incremental(
1453
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 10 times.
82 patch, entry->physical_name == NULL ? entry->name : entry->physical_name, patch_number,
1454 &payload, &payload_size
1455 );
1456 else
1457 52 status = patch_read_adjusted(
1458
2/2
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 10 times.
52 patch, entry->physical_name == NULL ? entry->name : entry->physical_name, patch_number,
1459 &payload, &payload_size
1460 );
1461
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 118 times.
134 if (status != 0)
1462 16 goto done;
1463
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 52 times.
118 if ((flags & LIBMPQ_FILE_FLAG_PATCH_FILE) != 0) {
1464
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 if (lower_number == UINT32_MAX) {
1465 ✗ status = LIBMPQ_ERROR_FORMAT;
1466 ✗ goto done;
1467 }
1468 66 status = patch_read_adjusted(lower, entry->name, lower_number, &base, &base_size);
1469
1/2
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
66 if (status == 0) {
1470 66 borrowed_result =
1471
3/4
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 2 times.
66 payload_size > LIBMPQ_PATCH_HEADER_SIZE && memcmp(payload + 64, "COPY", 4) == 0;
1472 66 status = patch_apply_internal(
1473 base, base_size, payload, payload_size, &result, &result_size, borrowed_result
1474 );
1475 }
1476 } else {
1477 52 result = payload;
1478 52 result_size = payload_size;
1479 52 payload = NULL;
1480 }
1481
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118 times.
118 if (status != 0)
1482 ✗ goto done;
1483 118 status = patch_append_member(target, output, result, result_size, &block);
1484
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 118 times.
118 if (status != 0)
1485 ✗ goto done;
1486
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 29 times.
118 if (changed_row != NULL) {
1487 89 mpq_file_attributes_s inherited = { 0 };
1488 89 mpq_file_attributes_s patch_row = { 0 };
1489
1490
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 6 times.
89 if (lower->attributes != NULL) {
1491 83 status = patch_attribute_for_identity(
1492 83 lower, entry->hash_a, entry->hash_b, entry->locale, entry->platform, &inherited
1493 );
1494
3/4
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
83 if (status != 0 && status != LIBMPQ_ERROR_EXIST)
1495 ✗ goto done;
1496 }
1497
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 57 times.
89 if (patch->attributes != NULL) {
1498 32 status = patch_attribute_for_identity(
1499 32 patch, entry->physical_hash_a, entry->physical_hash_b, entry->locale,
1500 32 entry->platform, &patch_row
1501 );
1502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
32 if (status != 0)
1503 ✗ goto done;
1504
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 25 times.
32 if ((patch_row.flags & LIBMPQ_ATTRIBUTE_FILETIME) != 0)
1505 7 inherited.filetime = patch_row.filetime;
1506 }
1507 89 patch_set_attributes(
1508 changed_row, attribute_flags, result, result_size, inherited.patch_bit
1509 );
1510 89 changed_row->filetime = inherited.filetime;
1511 }
1512
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 34 times.
118 if (slot != UINT32_MAX)
1513 84 target->mpq_hash[slot].block_table_index = block;
1514 else {
1515 34 mpq_hash_s inserted = { entry->hash_a, entry->hash_b, entry->locale, entry->platform,
1516 block };
1517
1518 34 status = patch_insert_hash(target, entry->name, &inserted);
1519 }
1520
1521 134 done:
1522 134 free(base);
1523 134 free(payload);
1524
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 64 times.
134 if (!borrowed_result)
1525 70 free(result);
1526 134 return status;
1527 }
1528
1529 /* Open an ordinary MPQ or authenticate an MPQE source through the existing reader. */
1530 static int32_t
1531 212 patch_source_open(mpq_archive_s **archive, const mpq_patch_source_s *source)
1532 {
1533
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 212 times.
212 if (archive == NULL)
1534 ✗ return LIBMPQ_ERROR_EXIST;
1535 212 *archive = NULL;
1536
2/4
✓ Branch 0 taken 212 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 212 times.
212 if (source == NULL || source->path == NULL)
1537 ✗ return LIBMPQ_ERROR_EXIST;
1538 212 return source->auth_code == NULL
1539 180 ? libmpq__archive_open(archive, source->path, -1)
1540
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 32 times.
424 : libmpq__archive_open_mpqe(
1541 32 archive, source->path, -1, source->auth_code, source->auth_code_size
1542 );
1543 }
1544
1545 /* Apply a complete layer in a separate private materialization, never via updates. */
1546 static int32_t
1547 116 patch_apply_layer(mpq_patch_view_s *view, const mpq_patch_source_s *source)
1548 {
1549 116 mpq_archive_s *patch = NULL;
1550 116 mpq_archive_s *lower = NULL;
1551 116 mpq_archive_s *target = NULL;
1552 116 mpq_archive_s *check = NULL;
1553 116 FILE *output = NULL;
1554 116 char *temporary = NULL;
1555 116 char *candidate = NULL;
1556 116 mpq_patch_entry_s *entries = NULL;
1557 116 mpq_file_attributes_s *attributes = NULL;
1558 116 mpq_file_attributes_s *changed_rows = NULL;
1559 116 mpq_file_attributes_s list_row = { 0 };
1560 116 uint8_t *changed = NULL;
1561 116 uint32_t attribute_flags = 0;
1562 uint32_t metadata_number;
1563 116 uint32_t entry_count = 0;
1564 116 uint8_t detected = 0;
1565 116 uint8_t mutated = 0;
1566 116 uint8_t has_attributes = 0;
1567 int32_t status;
1568
1569 116 status = patch_source_open(&patch, source);
1570
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 109 times.
116 if (status != 0)
1571 7 return status;
1572
2/2
✓ Branch 1 taken 54 times.
✓ Branch 2 taken 55 times.
109 if (libmpq__file_number(patch, "(patch_metadata)", &metadata_number) == 0) {
1573 54 libmpq__off_t metadata_size = 0;
1574
1575 54 status = libmpq__file_size_unpacked(patch, metadata_number, &metadata_size);
1576
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
54 if (status != 0)
1577 ✗ goto done;
1578
2/4
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 54 times.
54 if (metadata_size <= 0 || metadata_size >= 64) {
1579 ✗ status = LIBMPQ_ERROR_FORMAT;
1580 ✗ goto done;
1581 }
1582 54 detected = 1;
1583 }
1584
2/2
✓ Branch 0 taken 76600 times.
✓ Branch 1 taken 109 times.
76709 for (uint32_t i = 0; i < patch->mpq_header.hash_table_count; i++)
1585
2/2
✓ Branch 1 taken 440 times.
✓ Branch 2 taken 76160 times.
76600 if (patch_live_hash(patch, &patch->mpq_hash[i]) &&
1586
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 298 times.
440 (patch->mpq_block[patch->mpq_hash[i].block_table_index].flags &
1587 (LIBMPQ_FILE_FLAG_PATCH_FILE | LIBMPQ_FILE_FLAG_DELETE_MARKER)) != 0)
1588 142 detected = 1;
1589 109 status = patch_entries(patch, &entries, &entry_count);
1590
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 109 times.
109 if (status != 0)
1591 ✗ goto done;
1592 109 status = patch_prepare_entries(patch, entries, entry_count, source, &detected);
1593
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 106 times.
109 if (status != 0)
1594 3 goto done;
1595
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 105 times.
106 if (!detected) {
1596 1 status = LIBMPQ_ERROR_FORMAT;
1597 1 goto done;
1598 }
1599 105 status = libmpq__archive_open(&lower, view->path, view->archive_offset);
1600
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (status != 0)
1601 ✗ goto done;
1602 105 status = libmpq__archive_open(&target, view->path, view->archive_offset);
1603
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (status != 0)
1604 ✗ goto done;
1605 105 status = libmpq__attributes_load(lower);
1606
2/2
✓ Branch 0 taken 79 times.
✓ Branch 1 taken 26 times.
105 if (status == 0) {
1607 79 has_attributes = 1;
1608 79 attribute_flags = lower->attributes->flags;
1609
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 } else if (status != LIBMPQ_ERROR_EXIST) {
1610 ✗ goto done;
1611 }
1612 105 status = libmpq__attributes_load(patch);
1613
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 56 times.
105 if (status == 0) {
1614
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 44 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
49 if (!has_attributes && (patch->attributes->flags & LIBMPQ_ATTRIBUTE_PATCH_BIT) != 0) {
1615 5 has_attributes = 1;
1616 5 attribute_flags = LIBMPQ_ATTRIBUTE_PATCH_BIT;
1617
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 } else if (has_attributes) {
1618 44 attribute_flags |= patch->attributes->flags & LIBMPQ_ATTRIBUTE_PATCH_BIT;
1619 }
1620
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 } else if (status != LIBMPQ_ERROR_EXIST) {
1621 ✗ goto done;
1622 }
1623
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 21 times.
105 if (has_attributes) {
1624 84 changed_rows = calloc(entry_count, sizeof(*changed_rows));
1625 84 changed = calloc(entry_count, 1);
1626
2/4
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 84 times.
84 if (changed_rows == NULL || changed == NULL) {
1627 ✗ status = LIBMPQ_ERROR_MALLOC;
1628 ✗ goto done;
1629 }
1630 }
1631 105 status = libmpq__directory_temporary_reopenable(
1632 view->directory, ".libmpq-patch-view-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, &temporary,
1633 &output
1634 );
1635
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (status != 0)
1636 ✗ goto done;
1637 105 candidate = patch_temporary_path(view->path, temporary);
1638
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (candidate == NULL) {
1639 ✗ status = LIBMPQ_ERROR_MALLOC;
1640 ✗ goto done;
1641 }
1642 {
1643 uint64_t extent;
1644
1645 105 status = libmpq__archive_signature_extent(lower, &extent);
1646
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (status != 0)
1647 ✗ goto done;
1648 105 status = patch_copy_range(lower, output, 0, (uint64_t)view->archive_offset + extent);
1649 }
1650
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 105 times.
105 if (status != 0)
1651 ✗ goto done;
1652
2/2
✓ Branch 0 taken 70792 times.
✓ Branch 1 taken 87 times.
70879 for (uint32_t i = 0; i < entry_count; i++) {
1653 70792 const mpq_patch_entry_s *entry = &entries[i];
1654
1655
4/4
✓ Branch 0 taken 413 times.
✓ Branch 1 taken 70379 times.
✓ Branch 2 taken 221 times.
✓ Branch 3 taken 192 times.
70792 if (!entry->live || entry->internal)
1656 70600 continue;
1657
4/4
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 136 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 54 times.
248 if ((patch->mpq_block[entry->block_index].flags & LIBMPQ_FILE_FLAG_DELETE_MARKER) != 0 &&
1658 56 patch_matching_hash(target, entry) == UINT32_MAX)
1659 2 continue;
1660
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 40 times.
340 status = patch_stage_hash(
1661 150 lower, target, patch, entry, output, has_attributes ? &changed_rows[i] : NULL,
1662 attribute_flags
1663 );
1664
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 172 times.
190 if (status != 0)
1665 18 goto done;
1666
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 40 times.
172 if (has_attributes &&
1667
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 43 times.
132 (patch->mpq_block[entry->block_index].flags & LIBMPQ_FILE_FLAG_DELETE_MARKER) == 0)
1668 89 changed[i] = 1;
1669 172 mutated = 1;
1670 }
1671
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 85 times.
87 if (!mutated)
1672 2 goto done;
1673
2/2
✓ Branch 0 taken 4244 times.
✓ Branch 1 taken 85 times.
4329 for (uint32_t i = 0; i < target->mpq_header.hash_table_count; i++) {
1674 4244 mpq_hash_s *hash = &target->mpq_hash[i];
1675
1676
4/4
✓ Branch 1 taken 720 times.
✓ Branch 2 taken 3524 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 698 times.
4964 if (patch_live_hash(target, hash) &&
1677
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
742 ((hash->hash_a == libmpq__crypto_hash_string(LIBMPQ_SIGNATURE_NAME, 0x100) &&
1678
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 698 times.
720 hash->hash_b == libmpq__crypto_hash_string(LIBMPQ_SIGNATURE_NAME, 0x200)) ||
1679
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
698 (hash->hash_a == libmpq__crypto_hash_string("(patch_metadata)", 0x100) &&
1680 ✗ hash->hash_b == libmpq__crypto_hash_string("(patch_metadata)", 0x200))))
1681 22 patch_delete_hash(hash);
1682 }
1683 85 status = patch_rewrite_listfile(
1684 lower, target, entries, entry_count, output, has_attributes ? &list_row : NULL,
1685 attribute_flags,
1686
6/6
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 60 times.
✓ Branch 4 taken 65 times.
✓ Branch 5 taken 20 times.
150 (uint8_t)(has_attributes &&
1687 65 patch_internal_slot(target, LIBMPQ_ATTRIBUTES_NAME) == UINT32_MAX)
1688 );
1689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1690 ✗ goto done;
1691
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 20 times.
85 if (has_attributes) {
1692 65 uint32_t count = target->mpq_header.block_table_count;
1693
1694
2/4
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 65 times.
65 if (count == UINT32_MAX || (size_t)count + 1 > SIZE_MAX / sizeof(*attributes)) {
1695 ✗ status = LIBMPQ_ERROR_SIZE;
1696 ✗ goto done;
1697 }
1698 65 attributes = calloc((size_t)count + 1, sizeof(*attributes));
1699
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (attributes == NULL) {
1700 ✗ status = LIBMPQ_ERROR_MALLOC;
1701 ✗ goto done;
1702 }
1703
2/2
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 4 times.
65 status = patch_remap_attributes(
1704 lower, target, entries, changed_rows, changed, entry_count,
1705 65 patch_internal_slot(target, LIBMPQ_LISTFILE_NAME) == UINT32_MAX ? NULL : &list_row,
1706 attributes
1707 );
1708
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (status != 0)
1709 ✗ goto done;
1710 }
1711 85 status = patch_rewrite_attributes(target, output, attributes, attribute_flags);
1712
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1713 ✗ goto done;
1714 85 status = patch_write_tables(target, output);
1715
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1716 ✗ goto done;
1717 {
1718 uint64_t extent;
1719 uint64_t suffix;
1720 uint8_t marker[4];
1721
1722 85 status = libmpq__archive_signature_extent(lower, &extent);
1723
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1724 ✗ goto done;
1725 85 suffix = (uint64_t)view->archive_offset + extent;
1726
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 72 times.
85 if (lower->file_size - suffix >= LIBMPQ_STRONG_TRAILER_SIZE) {
1727 13 status = libmpq__source_read_at(lower->source, suffix, marker, sizeof(marker));
1728
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (status != 0)
1729 ✗ goto done;
1730
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (memcmp(marker, "NGIS", sizeof(marker)) == 0)
1731 13 suffix += LIBMPQ_STRONG_TRAILER_SIZE;
1732 }
1733
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (libmpq__file_seek(output, 0, SEEK_END) != 0) {
1734 ✗ status = LIBMPQ_ERROR_SEEK;
1735 ✗ goto done;
1736 }
1737 85 status = patch_copy_range(lower, output, suffix, lower->file_size - suffix);
1738
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1739 ✗ goto done;
1740 }
1741
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
85 if (fclose(output) != 0) {
1742 ✗ output = NULL;
1743 ✗ status = LIBMPQ_ERROR_CLOSE;
1744 ✗ goto done;
1745 }
1746 85 output = NULL;
1747 85 status = libmpq__archive_open(&check, candidate, view->archive_offset);
1748
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1749 ✗ goto done;
1750 85 status = libmpq__archive_close(check);
1751 85 check = NULL;
1752
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1753 ✗ goto done;
1754 85 status = libmpq__archive_close(target);
1755 85 target = NULL;
1756
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1757 ✗ goto done;
1758 85 status = libmpq__archive_close(lower);
1759 85 lower = NULL;
1760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1761 ✗ goto done;
1762 85 status = libmpq__directory_remove(view->directory, view->temporary);
1763
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (status != 0)
1764 ✗ goto done;
1765 85 free(view->temporary);
1766 85 free(view->path);
1767 85 view->temporary = temporary;
1768 85 view->path = candidate;
1769 85 temporary = NULL;
1770 85 candidate = NULL;
1771
1772 109 done:
1773
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 89 times.
109 if (output != NULL)
1774 20 (void)fclose(output);
1775
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 109 times.
109 if (check != NULL)
1776 ✗ (void)libmpq__archive_close(check);
1777
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 89 times.
109 if (target != NULL)
1778 20 (void)libmpq__archive_close(target);
1779
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 89 times.
109 if (lower != NULL)
1780 20 (void)libmpq__archive_close(lower);
1781
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 89 times.
109 if (temporary != NULL)
1782 20 (void)libmpq__directory_remove(view->directory, temporary);
1783
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 if (entries != NULL) {
1784
2/2
✓ Branch 0 taken 76600 times.
✓ Branch 1 taken 109 times.
76709 for (uint32_t i = 0; i < entry_count; i++) {
1785 76600 free(entries[i].name);
1786 76600 free(entries[i].physical_name);
1787 }
1788 }
1789 109 free(entries);
1790 109 free(attributes);
1791 109 free(changed_rows);
1792 109 free(changed);
1793
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 if (patch != NULL)
1794 109 (void)libmpq__archive_close(patch);
1795 109 free(temporary);
1796 109 free(candidate);
1797 109 return status;
1798 }
1799
1800 /* Consume the private view and remove its temporary materialization. */
1801 int32_t
1802 96 libmpq__patch_view_close(mpq_patch_view_s *view)
1803 {
1804 96 int32_t status = 0;
1805
1806
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (view == NULL)
1807 ✗ return LIBMPQ_ERROR_EXIST;
1808
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 33 times.
96 if (view->archive != NULL)
1809 63 status = libmpq__archive_close(view->archive);
1810
3/4
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
96 if (view->directory != NULL && view->temporary != NULL) {
1811 92 int32_t remove_status = libmpq__directory_remove(view->directory, view->temporary);
1812
1813
1/2
✓ Branch 0 taken 92 times.
✗ Branch 1 not taken.
92 if (status == 0)
1814 92 status = remove_status;
1815 }
1816
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 4 times.
96 if (view->directory != NULL)
1817 92 libmpq__directory_close(view->directory);
1818 96 free(view->temporary);
1819 96 free(view->path);
1820 96 free(view);
1821 96 return status;
1822 }
1823
1824 /* Expose the private materialized view to the existing read-only archive APIs. */
1825 mpq_archive_s *
1826 136 libmpq__patch_view_archive(mpq_patch_view_s *view)
1827 {
1828
1/2
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
136 return view == NULL ? NULL : view->archive;
1829 }
1830
1831 /* Materialize independently opened sources without changing any input archive. */
1832 int32_t
1833 96 libmpq__patch_view_open_sources(
1834 mpq_patch_view_s **view, const mpq_patch_source_s *base_source,
1835 const mpq_patch_source_s *patch_sources, size_t patch_count
1836 )
1837 {
1838 96 mpq_patch_view_s *state = NULL;
1839 96 mpq_archive_s *base = NULL;
1840 96 FILE *temporary = NULL;
1841 96 char *absolute = NULL;
1842 96 char *destination = NULL;
1843 int32_t status;
1844
1845
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (view == NULL)
1846 ✗ return LIBMPQ_ERROR_EXIST;
1847 96 *view = NULL;
1848
4/8
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 96 times.
96 if (base_source == NULL || base_source->path == NULL || patch_sources == NULL ||
1849 patch_count == 0)
1850 ✗ return LIBMPQ_ERROR_EXIST;
1851 96 state = calloc(1, sizeof(*state));
1852
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (state == NULL)
1853 ✗ return LIBMPQ_ERROR_MALLOC;
1854 96 status = patch_source_open(&base, base_source);
1855
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 92 times.
96 if (status != 0)
1856 4 goto error;
1857 92 absolute = patch_temporary_anchor();
1858
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (absolute == NULL) {
1859 ✗ status = LIBMPQ_ERROR_MALLOC;
1860 ✗ goto error;
1861 }
1862 92 status = libmpq__directory_open(absolute, &state->directory, &destination);
1863
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (status != 0)
1864 ✗ goto error;
1865 92 status = libmpq__directory_temporary_reopenable(
1866 state->directory, ".libmpq-patch-view-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1,
1867 &state->temporary, &temporary
1868 );
1869
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (status != 0)
1870 ✗ goto error;
1871 92 state->path = patch_temporary_path(absolute, state->temporary);
1872
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (state->path == NULL) {
1873 ✗ status = LIBMPQ_ERROR_MALLOC;
1874 ✗ goto error;
1875 }
1876 92 state->archive_offset = base->archive_offset;
1877 92 status = patch_copy_range(base, temporary, 0, base->file_size);
1878
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (status != 0)
1879 ✗ goto error;
1880
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
92 if (fclose(temporary) != 0) {
1881 ✗ temporary = NULL;
1882 ✗ status = LIBMPQ_ERROR_CLOSE;
1883 ✗ goto error;
1884 }
1885 92 temporary = NULL;
1886 92 status = libmpq__archive_close(base);
1887 92 base = NULL;
1888
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if (status != 0)
1889 ✗ goto error;
1890
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 63 times.
179 for (size_t i = 0; i < patch_count; i++) {
1891
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
116 if (patch_sources[i].path == NULL) {
1892 ✗ status = LIBMPQ_ERROR_EXIST;
1893 ✗ goto error;
1894 }
1895 116 status = patch_apply_layer(state, &patch_sources[i]);
1896
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 87 times.
116 if (status != 0)
1897 29 goto error;
1898 }
1899 63 status = libmpq__archive_open(&state->archive, state->path, state->archive_offset);
1900
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
63 if (status != 0)
1901 ✗ goto error;
1902 63 free(destination);
1903 63 free(absolute);
1904 63 *view = state;
1905 63 return 0;
1906
1907 33 error:
1908
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if (temporary != NULL)
1909 ✗ (void)fclose(temporary);
1910
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if (base != NULL)
1911 ✗ (void)libmpq__archive_close(base);
1912 33 free(destination);
1913 33 free(absolute);
1914 33 (void)libmpq__patch_view_close(state);
1915 33 return status;
1916 }
1917
1918 /* Adapt path-only patch callers to independent ordinary source descriptors. */
1919 static int32_t
1920 66 patch_view_open_paths(
1921 mpq_patch_view_s **view, const mpq_patch_source_s *base_source, const char *const *patch_paths,
1922 size_t patch_count
1923 )
1924 {
1925 mpq_patch_source_s *sources;
1926 int32_t status;
1927
1928
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 if (view == NULL)
1929 ✗ return LIBMPQ_ERROR_EXIST;
1930 66 *view = NULL;
1931
2/4
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
66 if (patch_paths == NULL || patch_count == 0)
1932 ✗ return LIBMPQ_ERROR_EXIST;
1933
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 if (patch_count > SIZE_MAX / sizeof(*sources))
1934 ✗ return LIBMPQ_ERROR_SIZE;
1935 66 sources = calloc(patch_count, sizeof(*sources));
1936
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 66 times.
66 if (sources == NULL)
1937 ✗ return LIBMPQ_ERROR_MALLOC;
1938
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 66 times.
142 for (size_t i = 0; i < patch_count; i++)
1939 76 sources[i].path = patch_paths[i];
1940 66 status = libmpq__patch_view_open_sources(view, base_source, sources, patch_count);
1941 66 free(sources);
1942 66 return status;
1943 }
1944
1945 /* Build a private view from an ordinary MPQ base. */
1946 int32_t
1947 60 libmpq__patch_view_open(
1948 mpq_patch_view_s **view, const char *base_path, const char *const *patch_paths,
1949 size_t patch_count
1950 )
1951 {
1952 60 mpq_patch_source_s base_source = { base_path, NULL, 0, NULL };
1953
1954 60 return patch_view_open_paths(view, &base_source, patch_paths, patch_count);
1955 }
1956
1957 /* Decode an authenticated MPQE base before composing ordinary MPQ patches. */
1958 int32_t
1959 7 libmpq__patch_view_open_mpqe_base(
1960 mpq_patch_view_s **view, const char *base_path, const uint8_t *auth_code, size_t auth_code_size,
1961 const char *const *patch_paths, size_t patch_count
1962 )
1963 {
1964 7 mpq_patch_source_s base_source = { base_path, auth_code, auth_code_size, NULL };
1965
1966
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 if (auth_code == NULL) {
1967
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (view != NULL)
1968 1 *view = NULL;
1969
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 return view == NULL ? LIBMPQ_ERROR_EXIST : LIBMPQ_ERROR_DECRYPT;
1970 }
1971 6 return patch_view_open_paths(view, &base_source, patch_paths, patch_count);
1972 }
1973