GCC Code Coverage Report


Directory: src/
File: src/mpq-attributes.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 223 244 91.4%
Functions: 11 11 100.0%
Branches: 162 204 79.4%

Line Branch Exec Source
1 /*
2 * mpq-attributes.c -- optional MPQ attributes parsing and serialization.
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-attributes.h"
21 #include "mpq-archive.h"
22 #include "mpq-endian.h"
23 #include "mpq-md5.h"
24 #include "mpq-reader.h"
25
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <zlib.h>
30
31 /*
32 * Calculate numeric-array offsets without narrowing unchecked file sizes.
33 * The same helper handles full tables and the known one-entry-short layout.
34 */
35 static uint64_t
36 2226 array_layout(uint32_t count, uint32_t flags, mpq_attributes_s *view)
37 {
38 static const uint32_t widths[] = { 4, 8, 16 };
39 2226 uint64_t size = 8;
40 uint32_t i;
41
42
2/2
✓ Branch 0 taken 6678 times.
✓ Branch 1 taken 2226 times.
8904 for (i = 0; i < 3; ++i) {
43 if (size <= SIZE_MAX)
44 6678 view->offsets[i] = (size_t)size;
45
2/2
✓ Branch 0 taken 4027 times.
✓ Branch 1 taken 2651 times.
6678 if (flags & (1u << i))
46 4027 size += (uint64_t)count * widths[i];
47 }
48 if (size <= SIZE_MAX)
49 2226 view->offsets[3] = (size_t)size;
50 2226 return size;
51 }
52
53 /*
54 * Recognize an exact layout, including documented Storm-style exceptions.
55 * Unknown DWORD patch contents are not interpreted as a bit array.
56 */
57 static int
58 1247 match_layout(
59 const uint8_t *data, size_t size, uint32_t count, uint32_t entries, mpq_attributes_s *view,
60 int legacy
61 )
62 {
63 1247 uint64_t base = array_layout(entries, view->flags, view);
64 1247 uint64_t bits = ((uint64_t)entries + 7) / 8;
65 1247 uint64_t short_bits = ((uint64_t)count + 6) / 8;
66 size_t i;
67
68
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1242 times.
1247 if (base > size)
69 5 return 0;
70 1242 view->entries = entries;
71
2/2
✓ Branch 0 taken 683 times.
✓ Branch 1 taken 559 times.
1242 if (!(view->flags & LIBMPQ_ATTRIBUTE_PATCH_BIT))
72 683 return base == size;
73
2/2
✓ Branch 0 taken 437 times.
✓ Branch 1 taken 122 times.
559 if (base + bits == size) {
74 437 view->patch_bits = (uint64_t)entries;
75 437 return 1;
76 }
77
2/2
✓ Branch 0 taken 89 times.
✓ Branch 1 taken 33 times.
122 if (base + short_bits == size) {
78 89 view->patch_bits = short_bits * 8;
79 89 view->patch_self_omitted = 1;
80 89 return 1;
81 }
82
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 11 times.
33 if (!legacy)
83 22 return 0;
84
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (base == size)
85 1 return 1;
86
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if (base + (uint64_t)count * 4 != size)
87 8 return 0;
88
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 1 times.
40 for (i = (size_t)base; i < size; ++i) {
89
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 38 times.
39 if (data[i] != 0)
90 1 return 0;
91 }
92 1 return 1;
93 }
94
95 /*
96 * Parse an immutable payload against its physical block-table count.
97 * Only recognized exact layouts are accepted; no partial result escapes on error.
98 */
99 int32_t
100 1216 libmpq__attributes_parse(
101 const uint8_t *data, size_t size, uint32_t count, uint32_t self, mpq_attributes_s *view
102 )
103 {
104 1216 mpq_attributes_s candidate = { 0 };
105
106 1216 memset(view, 0, sizeof(*view));
107
7/10
✓ Branch 0 taken 1216 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1208 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 1208 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1208 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 1206 times.
2424 if (data == NULL || size < 8 || count == 0 || self >= count ||
108 1208 libmpq__load_le32(data) != LIBMPQ_ATTRIBUTES_VERSION)
109 10 return LIBMPQ_ERROR_FORMAT;
110 1206 candidate.flags = libmpq__load_le32(data + 4);
111
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1205 times.
1206 if (candidate.flags & ~LIBMPQ_ATTRIBUTES_ALL)
112 1 return LIBMPQ_ERROR_FORMAT;
113 1205 candidate.data = data;
114 1205 candidate.self = self;
115
4/4
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 1183 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 2 times.
1227 if (!match_layout(data, size, count, count, &candidate, 0) &&
116
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 2 times.
42 !match_layout(data, size, count, count - 1, &candidate, 0) &&
117 20 !match_layout(data, size, count, count, &candidate, 1))
118 18 return LIBMPQ_ERROR_FORMAT;
119 1187 *view = candidate;
120 1187 return 0;
121 }
122
123 /*
124 * Decode one physical block-table entry without changing its availability.
125 * Omitted legacy rows and opaque patch arrays remain explicitly unavailable.
126 */
127 void
128 12057 libmpq__attributes_get(const mpq_attributes_s *view, uint32_t index, mpq_file_attributes_s *result)
129 {
130 12057 memset(result, 0, sizeof(*result));
131
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 12054 times.
12057 if (index >= view->entries)
132 3 return;
133 12054 result->flags = view->flags & ~LIBMPQ_ATTRIBUTE_PATCH_BIT;
134
2/2
✓ Branch 0 taken 7372 times.
✓ Branch 1 taken 4682 times.
12054 if (result->flags & LIBMPQ_ATTRIBUTE_CRC32)
135 7372 result->crc32 = libmpq__load_le32(view->data + view->offsets[0] + (size_t)index * 4);
136
2/2
✓ Branch 0 taken 5895 times.
✓ Branch 1 taken 6159 times.
12054 if (result->flags & LIBMPQ_ATTRIBUTE_FILETIME)
137 5895 result->filetime = libmpq__load_le64(view->data + view->offsets[1] + (size_t)index * 8);
138
2/2
✓ Branch 0 taken 7359 times.
✓ Branch 1 taken 4695 times.
12054 if (result->flags & LIBMPQ_ATTRIBUTE_MD5) {
139 7359 uint8_t nonzero = 0;
140 size_t i;
141
142 7359 memcpy(result->md5, view->data + view->offsets[2] + (size_t)index * 16, 16);
143
2/2
✓ Branch 0 taken 117744 times.
✓ Branch 1 taken 7359 times.
125103 for (i = 0; i < sizeof(result->md5); ++i)
144 117744 nonzero |= result->md5[i];
145
146 /* An all-zero row is an absent MD5 value, not a digest to compare. */
147
2/2
✓ Branch 0 taken 846 times.
✓ Branch 1 taken 6513 times.
7359 if (nonzero == 0)
148 846 result->flags &= ~LIBMPQ_ATTRIBUTE_MD5;
149 }
150
2/2
✓ Branch 0 taken 5224 times.
✓ Branch 1 taken 6830 times.
12054 if (view->flags & LIBMPQ_ATTRIBUTE_PATCH_BIT) {
151
2/2
✓ Branch 0 taken 5165 times.
✓ Branch 1 taken 59 times.
5224 if (index < view->patch_bits) {
152 5165 result->flags |= LIBMPQ_ATTRIBUTE_PATCH_BIT;
153 5165 result->patch_bit =
154 5165 (view->data[view->offsets[3] + index / 8] & (0x80u >> (index % 8))) != 0;
155
3/4
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 41 times.
✗ Branch 3 not taken.
59 } else if (index == view->self && view->patch_self_omitted) {
156 41 result->flags |= LIBMPQ_ATTRIBUTE_PATCH_BIT;
157 }
158 }
159 }
160
161 /*
162 * Load optional metadata only when requested, using normal MPQ file I/O.
163 * Structural errors and absence are cached; transient resource errors can be retried.
164 */
165 int32_t
166 4054 libmpq__attributes_load(mpq_archive_s *archive)
167 {
168 uint32_t number;
169 4054 libmpq__off_t size = 0;
170 4054 libmpq__off_t transferred = 0;
171 mpq_attributes_s *view;
172 uint8_t *data;
173 int32_t result;
174
175
2/2
✓ Branch 0 taken 1332 times.
✓ Branch 1 taken 2722 times.
4054 if (archive->attributes != NULL)
176 1332 return 0;
177
2/2
✓ Branch 0 taken 1907 times.
✓ Branch 1 taken 815 times.
2722 if (archive->attributes_error != 0)
178 1907 return archive->attributes_error;
179 815 result = libmpq__file_number(archive, LIBMPQ_ATTRIBUTES_NAME, &number);
180
2/2
✓ Branch 0 taken 207 times.
✓ Branch 1 taken 608 times.
815 if (result != 0) {
181
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 207 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
207 if (result == LIBMPQ_ERROR_EXIST || result == LIBMPQ_ERROR_FORMAT)
182 207 archive->attributes_error = result;
183 207 return result;
184 }
185 608 result = libmpq__file_size_unpacked(archive, number, &size);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 608 times.
608 if (result != 0)
187 ✗ return result;
188
1/2
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
608 if (size < 8 || (uint64_t)size > SIZE_MAX ||
189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 608 times.
608 (uint64_t)size > 8 + (uint64_t)archive->mpq_header.block_table_count * 32) {
190 ✗ archive->attributes_error = LIBMPQ_ERROR_FORMAT;
191 ✗ return LIBMPQ_ERROR_FORMAT;
192 }
193 608 data = malloc((size_t)size);
194 608 view = calloc(1, sizeof(*view));
195
2/4
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 608 times.
608 if (data == NULL || view == NULL) {
196 ✗ free(data);
197 ✗ free(view);
198 ✗ return LIBMPQ_ERROR_MALLOC;
199 }
200 608 result = libmpq__reader_offsets_acquire(archive, number, LIBMPQ_ATTRIBUTES_NAME);
201
1/2
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
608 if (result == 0) {
202 608 result = libmpq__file_read(archive, number, data, size, &transferred);
203 608 (void)libmpq__reader_offsets_release(archive, number);
204
2/4
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 608 times.
608 if (result == 0 && transferred != size)
205 ✗ result = LIBMPQ_ERROR_READ;
206 }
207
1/2
✓ Branch 0 taken 608 times.
✗ Branch 1 not taken.
608 if (result == 0)
208 608 result = libmpq__attributes_parse(
209 data, (size_t)size, archive->mpq_header.block_table_count,
210 608 archive->mpq_map[number].block_table_indices, view
211 );
212
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 607 times.
608 if (result != 0) {
213 1 free(data);
214 1 free(view);
215
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (result == LIBMPQ_ERROR_FORMAT)
216 1 archive->attributes_error = result;
217 1 return result;
218 }
219 607 archive->attributes = view;
220 607 return 0;
221 }
222
223 /*
224 * Compare only requested and available file checksum attributes. Explicit
225 * verification and automatic complete-read verification share this path.
226 */
227 void
228 1644 libmpq__attributes_compare_file(
229 const mpq_file_attributes_s *attributes, uint32_t verify_flags, uint32_t crc,
230 const uint8_t digest[LIBMPQ_MD5_SIZE], uint32_t *mismatches
231 )
232 {
233
2/2
✓ Branch 0 taken 1266 times.
✓ Branch 1 taken 378 times.
1644 if ((verify_flags & LIBMPQ_VERIFY_FILE_CRC32) != 0 &&
234
3/4
✓ Branch 0 taken 1266 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 255 times.
✓ Branch 3 taken 1011 times.
1266 (attributes->flags & LIBMPQ_ATTRIBUTE_CRC32) != 0 && crc != attributes->crc32)
235 255 *mismatches |= LIBMPQ_VERIFY_FILE_CRC32;
236
2/2
✓ Branch 0 taken 1254 times.
✓ Branch 1 taken 390 times.
1644 if ((verify_flags & LIBMPQ_VERIFY_FILE_MD5) != 0 &&
237
1/2
✓ Branch 0 taken 1254 times.
✗ Branch 1 not taken.
1254 (attributes->flags & LIBMPQ_ATTRIBUTE_MD5) != 0 &&
238
2/2
✓ Branch 0 taken 245 times.
✓ Branch 1 taken 1009 times.
1254 memcmp(digest, attributes->md5, LIBMPQ_MD5_SIZE) != 0)
239 245 *mismatches |= LIBMPQ_VERIFY_FILE_MD5;
240 1644 }
241
242 /*
243 * Compare a complete decoded lossless file without rereading it. The optional
244 * attributes member is skipped itself to avoid recursive metadata loading;
245 * unusable optional metadata is left to explicit attribute APIs to report.
246 */
247 int32_t
248 3390 libmpq__attributes_verify_data(
249 mpq_archive_s *archive, uint32_t file_number, const uint8_t *data, size_t size,
250 uint32_t *mismatches
251 )
252 {
253 mpq_file_attributes_s attributes;
254 mpq_md5_s md5;
255 3390 uint8_t digest[LIBMPQ_MD5_SIZE] = { 0 };
256 libmpq__off_t expected;
257 uint32_t metadata_number;
258 3390 uint32_t crc = 0;
259 3390 uint32_t verify_flags = 0;
260 int32_t status;
261
262
1/2
✓ Branch 0 taken 3390 times.
✗ Branch 1 not taken.
3390 if (mismatches != NULL)
263 3390 *mismatches = 0;
264
3/8
✓ Branch 0 taken 3390 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3390 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3390 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3390 if (archive == NULL || mismatches == NULL || (data == NULL && size != 0))
265 ✗ return LIBMPQ_ERROR_EXIST;
266
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3390 times.
3390 if (archive->write_mode)
267 ✗ return LIBMPQ_ERROR_NOT_INITIALIZED;
268
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3390 times.
3390 if (libmpq__reader_validate_file_number(archive, file_number) < 0)
269 ✗ return LIBMPQ_ERROR_EXIST;
270 3390 status = libmpq__file_size_unpacked(archive, file_number, &expected);
271
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3390 times.
3390 if (status < 0)
272 ✗ return status;
273
2/4
✓ Branch 0 taken 3390 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3390 times.
3390 if (expected < 0 || (uint64_t)expected != size)
274 ✗ return LIBMPQ_ERROR_SIZE;
275
2/2
✓ Branch 1 taken 1425 times.
✓ Branch 2 taken 1965 times.
3390 if (libmpq__file_number(archive, LIBMPQ_ATTRIBUTES_NAME, &metadata_number) == 0 &&
276
2/2
✓ Branch 0 taken 644 times.
✓ Branch 1 taken 781 times.
1425 metadata_number == file_number)
277 644 return 0;
278
2/2
✓ Branch 1 taken 168 times.
✓ Branch 2 taken 2578 times.
2746 if (libmpq__file_number(archive, LIBMPQ_SIGNATURE_NAME, &metadata_number) == 0 &&
279
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 162 times.
168 metadata_number == file_number)
280 6 return 0;
281
282 2740 memset(&attributes, 0, sizeof(attributes));
283 2740 status = libmpq__file_attributes(archive, file_number, &attributes);
284
2/2
✓ Branch 0 taken 1990 times.
✓ Branch 1 taken 750 times.
2740 if (status < 0)
285 1990 return 0;
286
2/2
✓ Branch 0 taken 712 times.
✓ Branch 1 taken 38 times.
750 if ((attributes.flags & LIBMPQ_ATTRIBUTE_CRC32) != 0)
287 712 verify_flags |= LIBMPQ_VERIFY_FILE_CRC32;
288
2/2
✓ Branch 0 taken 706 times.
✓ Branch 1 taken 44 times.
750 if ((attributes.flags & LIBMPQ_ATTRIBUTE_MD5) != 0)
289 706 verify_flags |= LIBMPQ_VERIFY_FILE_MD5;
290
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 728 times.
750 if (verify_flags == 0)
291 22 return 0;
292
2/2
✓ Branch 0 taken 712 times.
✓ Branch 1 taken 16 times.
728 if ((verify_flags & LIBMPQ_VERIFY_FILE_CRC32) != 0) {
293 712 size_t remaining = size;
294 712 const uint8_t *cursor = data;
295
296
2/2
✓ Branch 0 taken 688 times.
✓ Branch 1 taken 712 times.
1400 while (remaining != 0) {
297
1/2
✓ Branch 0 taken 688 times.
✗ Branch 1 not taken.
688 uInt chunk = remaining > UINT_MAX ? UINT_MAX : (uInt)remaining;
298 688 crc = (uint32_t)crc32(crc, cursor, chunk);
299 688 cursor += chunk;
300 688 remaining -= chunk;
301 }
302 }
303
2/2
✓ Branch 0 taken 706 times.
✓ Branch 1 taken 22 times.
728 if ((verify_flags & LIBMPQ_VERIFY_FILE_MD5) != 0) {
304 706 libmpq__md5_init(&md5);
305
2/2
✓ Branch 0 taken 682 times.
✓ Branch 1 taken 24 times.
706 if (size != 0)
306 682 libmpq__md5_update(&md5, data, size);
307 706 libmpq__md5_final(&md5, digest);
308 }
309 728 libmpq__attributes_compare_file(&attributes, verify_flags, crc, digest, mismatches);
310 728 return 0;
311 }
312
313 /*
314 * Release both reader-owned payloads and writer-owned metadata records.
315 * Reader callers invoke this only after a successful public close.
316 */
317 void
318 2238 libmpq__attributes_free(mpq_archive_s *archive)
319 {
320
2/2
✓ Branch 0 taken 607 times.
✓ Branch 1 taken 1631 times.
2238 if (archive->attributes != NULL) {
321 607 free((void *)archive->attributes->data);
322 607 free(archive->attributes);
323 607 archive->attributes = NULL;
324 }
325 2238 free(archive->write_attributes);
326 2238 archive->write_attributes = NULL;
327 2238 }
328
329 /* Return the dedicated attribute mask, independent of creation flags and policy. */
330 uint32_t
331 8334 libmpq__attributes_write_flags(const mpq_archive_s *archive)
332 {
333 8334 return archive->write_attributes_flags;
334 }
335
336 /*
337 * Serialize complete numeric arrays and Storm-compatible zero patch bits.
338 * Self and unused records are zero, and true patch bits are never silently discarded.
339 */
340 int32_t
341 979 libmpq__attributes_serialize(
342 const mpq_file_attributes_s *entries, uint32_t count, uint32_t self, uint32_t flags,
343 uint8_t **data, size_t *size
344 )
345 {
346 979 mpq_attributes_s layout = { 0 };
347 uint64_t length;
348 uint8_t *raw;
349 uint32_t i;
350
351 979 *data = NULL;
352 979 *size = 0;
353
3/6
✓ Branch 0 taken 979 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 979 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 979 times.
979 if (count == 0 || self >= count || (flags & ~LIBMPQ_ATTRIBUTES_ALL))
354 ✗ return LIBMPQ_ERROR_FORMAT;
355 979 length = array_layout(count, flags, &layout);
356
2/2
✓ Branch 0 taken 404 times.
✓ Branch 1 taken 575 times.
979 if (flags & LIBMPQ_ATTRIBUTE_PATCH_BIT)
357 404 length += ((uint64_t)count + 6) / 8;
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 979 times.
979 if (length > SIZE_MAX || length > UINT32_MAX)
359 ✗ return LIBMPQ_ERROR_SIZE;
360
2/2
✓ Branch 0 taken 38768 times.
✓ Branch 1 taken 978 times.
39746 for (i = 0; i < count; ++i) {
361
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 38767 times.
38768 if (entries[i].patch_bit != 0)
362 1 return LIBMPQ_ERROR_FORMAT;
363 }
364 978 raw = calloc(1, (size_t)length);
365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 978 times.
978 if (raw == NULL)
366 ✗ return LIBMPQ_ERROR_MALLOC;
367 978 libmpq__store_le32(raw, LIBMPQ_ATTRIBUTES_VERSION);
368 978 libmpq__store_le32(raw + 4, flags);
369
2/2
✓ Branch 0 taken 38767 times.
✓ Branch 1 taken 978 times.
39745 for (i = 0; i < count; ++i) {
370
2/2
✓ Branch 0 taken 978 times.
✓ Branch 1 taken 37789 times.
38767 if (i == self)
371 978 continue;
372
2/2
✓ Branch 0 taken 33370 times.
✓ Branch 1 taken 4419 times.
37789 if (flags & LIBMPQ_ATTRIBUTE_CRC32)
373 33370 libmpq__store_le32(raw + layout.offsets[0] + (size_t)i * 4, entries[i].crc32);
374
2/2
✓ Branch 0 taken 6733 times.
✓ Branch 1 taken 31056 times.
37789 if (flags & LIBMPQ_ATTRIBUTE_FILETIME)
375 6733 libmpq__store_le64(raw + layout.offsets[1] + (size_t)i * 8, entries[i].filetime);
376
2/2
✓ Branch 0 taken 33370 times.
✓ Branch 1 taken 4419 times.
37789 if (flags & LIBMPQ_ATTRIBUTE_MD5)
377 33370 memcpy(raw + layout.offsets[2] + (size_t)i * 16, entries[i].md5, 16);
378 }
379 978 *data = raw;
380 978 *size = (size_t)length;
381 978 return 0;
382 }
383
384 /* Only private patch creation/materialization may serialize true patch bits. */
385 int32_t
386 90 libmpq__attributes_serialize_patch_bits(
387 const mpq_file_attributes_s *entries, uint32_t count, uint32_t self, uint32_t flags,
388 uint8_t **data, size_t *size
389 )
390 {
391 mpq_file_attributes_s *copy;
392 90 size_t bits_offset = 8;
393 int32_t status;
394
395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
90 if (count == 0 || (uint64_t)count * sizeof(*copy) > SIZE_MAX)
396 ✗ return LIBMPQ_ERROR_SIZE;
397 90 copy = malloc((size_t)count * sizeof(*copy));
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
90 if (copy == NULL)
399 ✗ return LIBMPQ_ERROR_MALLOC;
400 90 memcpy(copy, entries, (size_t)count * sizeof(*copy));
401
2/2
✓ Branch 0 taken 27471 times.
✓ Branch 1 taken 90 times.
27561 for (uint32_t i = 0; i < count; i++)
402 27471 copy[i].patch_bit = 0;
403 90 status = libmpq__attributes_serialize(copy, count, self, flags, data, size);
404 90 free(copy);
405
3/4
✓ Branch 0 taken 90 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 62 times.
90 if (status != 0 || (flags & LIBMPQ_ATTRIBUTE_PATCH_BIT) == 0)
406 28 return status;
407
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 5 times.
62 if (flags & LIBMPQ_ATTRIBUTE_CRC32)
408 57 bits_offset += (size_t)count * 4;
409
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 30 times.
62 if (flags & LIBMPQ_ATTRIBUTE_FILETIME)
410 32 bits_offset += (size_t)count * 8;
411
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 5 times.
62 if (flags & LIBMPQ_ATTRIBUTE_MD5)
412 57 bits_offset += (size_t)count * 16;
413
2/2
✓ Branch 0 taken 26427 times.
✓ Branch 1 taken 62 times.
26489 for (uint32_t i = 0; i < count; i++) {
414
2/2
✓ Branch 0 taken 26402 times.
✓ Branch 1 taken 25 times.
26427 if (entries[i].patch_bit == 0)
415 26402 continue;
416
3/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
25 if (i == self || bits_offset > *size || i / 8 >= *size - bits_offset) {
417 ✗ free(*data);
418 ✗ *data = NULL;
419 ✗ *size = 0;
420 ✗ return LIBMPQ_ERROR_FORMAT;
421 }
422 25 (*data)[bits_offset + i / 8] |= (uint8_t)(0x80u >> (i % 8));
423 }
424 62 return 0;
425 }
426