GCC Code Coverage Report


Directory: src/
File: src/mpq-api.c
Date: 2026-09-02 16:40:44
Exec Total Coverage
Lines: 370 449 82.4%
Functions: 32 32 100.0%
Branches: 178 264 67.4%

Line Branch Exec Source
1 /*
2 * mpq-api.c -- public archive, file and block operations.
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 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "mpq-compression.h"
25 #include "mpq-crypto.h"
26 #include "mpq-endian.h"
27 #include "mpq-internal.h"
28 #include "mpq-platform.h"
29 #include "mpq-reader.h"
30 #include "mpq-writer.h"
31 #include <libmpq/mpq.h>
32
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
38
39 /* Error strings indexed by the negated libmpq error code. */
40 static const char *libmpq_error_strings[] = { "success",
41 "open error on file",
42 "close error on file",
43 "lseek error on file",
44 "read error on file",
45 "write error on file",
46 "memory allocation error",
47 "format error",
48 "init() wasn't called",
49 "buffer size is too small",
50 "archive, file, or block does not exist",
51 "we don't know the decryption seed",
52 "error on unpacking file" };
53
54 /* Verify that a file payload subrange is both internally consistent and
55 * contained in the physical backing file captured when the archive opened.
56 * Sector offsets and block-table sizes are archive-controlled, so this check
57 * must happen before using either value for allocation or stream reads. */
58 static int32_t
59 1365 libmpq__validate_payload_range(
60 const mpq_archive_s *mpq_archive, uint32_t block_table_index, uint64_t relative_offset,
61 uint64_t size
62 )
63 {
64 uint64_t payload_offset;
65 uint64_t absolute_offset;
66
67
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1365 times.
1365 if (mpq_archive->archive_offset < 0) {
68 return LIBMPQ_ERROR_FORMAT;
69 }
70
71 1365 payload_offset = ((uint64_t)mpq_archive->mpq_block_ex[block_table_index].offset_high << 32) |
72 1365 mpq_archive->mpq_block[block_table_index].offset;
73 1365 absolute_offset = (uint64_t)mpq_archive->archive_offset;
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1365 times.
1365 if (payload_offset > UINT64_MAX - absolute_offset) {
75 return LIBMPQ_ERROR_FORMAT;
76 }
77 1365 absolute_offset += payload_offset;
78
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1365 times.
1365 if (relative_offset > UINT64_MAX - absolute_offset) {
79 return LIBMPQ_ERROR_FORMAT;
80 }
81 1365 absolute_offset += relative_offset;
82
1/2
✓ Branch 0 taken 1365 times.
✗ Branch 1 not taken.
1365 if (absolute_offset > mpq_archive->file_size ||
83
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1365 times.
1365 size > mpq_archive->file_size - absolute_offset) {
84 return LIBMPQ_ERROR_READ;
85 }
86
87 1365 return LIBMPQ_SUCCESS;
88 }
89
90 /* Return the configured libmpq package version.
91 * The returned pointer refers to immutable library storage and remains valid
92 * for the lifetime of the process. */
93 const char *
94 2 libmpq__version(void)
95 {
96 2 return VERSION;
97 }
98
99 /* Translate a libmpq return code into a static diagnostic string.
100 * Valid codes index an internal immutable table; invalid positive or out-of-
101 * range negative values return NULL instead of reading outside that table. */
102 const char *
103 15 libmpq__strerror(int32_t return_code)
104 {
105
106 /* Only negative libmpq error codes and zero are valid table indexes. */
107
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1 times.
15 if (-return_code < 0 ||
108
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 (size_t)-return_code >= sizeof(libmpq_error_strings) / sizeof(libmpq_error_strings[0]))
109 2 return NULL;
110
111 /* Return the static string owned by the library. */
112 13 return libmpq_error_strings[-return_code];
113 }
114
115 /* Create an MPQ archive through the internal writer implementation.
116 * This public facade preserves the stable API while keeping archive layout
117 * and file-table construction in the writer module. */
118 int32_t
119 70 libmpq__archive_create(
120 mpq_archive_s **out, const char *path, const mpq_archive_create_options_s *options
121 )
122 {
123 70 return libmpq__writer_archive_create(out, path, options);
124 }
125
126 /* Begin a streamed file through the internal writer implementation.
127 * The returned opaque writer owns the in-progress file state until finish or
128 * an error closes the stream. */
129 int32_t
130 13 libmpq__file_begin(
131 mpq_archive_s *archive, const char *name, libmpq__off_t size, const mpq_file_options_s *options,
132 mpq_writer_s **out
133 )
134 {
135 13 return libmpq__writer_file_begin(archive, name, size, options, out);
136 }
137
138 /* Write one input range through the internal writer implementation.
139 * The writer validates the declared file size and buffers or flushes sectors
140 * according to the selected storage and compression options. */
141 int32_t
142 15 libmpq__file_write(mpq_writer_s *writer, const uint8_t *buffer, libmpq__off_t size)
143 {
144 15 return libmpq__writer_file_write(writer, buffer, size);
145 }
146
147 /* Finish a streamed file through the internal writer implementation.
148 * Finalization verifies that all declared bytes were supplied and publishes
149 * the completed file entry in the archive tables. */
150 int32_t
151 8 libmpq__file_finish(mpq_writer_s *writer)
152 {
153 8 return libmpq__writer_file_finish(writer);
154 }
155
156 /* Add an in-memory file through the internal writer implementation.
157 * The convenience call performs begin, write, and finish operations while
158 * retaining the same validation and compression behavior as streaming. */
159 int32_t
160 565 libmpq__file_add(
161 mpq_archive_s *archive, const char *name, const uint8_t *data, libmpq__off_t size,
162 const mpq_file_options_s *options
163 )
164 {
165 565 return libmpq__writer_file_add(archive, name, data, size, options);
166 }
167
168 /* Add a filesystem file through the internal writer implementation.
169 * The source is read in bounded chunks, so callers need not load the complete
170 * file into memory before archive creation begins. */
171 int32_t
172 5 libmpq__file_add_path(
173 mpq_archive_s *archive, const char *name, const char *source, const mpq_file_options_s *options
174 )
175 {
176 5 return libmpq__writer_file_add_path(archive, name, source, options);
177 }
178
179 /* Open an MPQ archive from a path and optional embedded archive offset.
180 * A sentinel offset enables embedded-header scanning, while an explicit offset
181 * restricts parsing to the requested archive location. */
182 int32_t
183 74 libmpq__archive_open(
184 mpq_archive_s **mpq_archive, const char *mpq_filename, libmpq__off_t archive_offset
185 )
186 {
187 74 return libmpq__reader_archive_open_path(mpq_archive, mpq_filename, archive_offset);
188 }
189
190 /* Reopen an archive with independent file I/O, metadata, and lazy caches.
191 * Cloning rejects writer handles and verifies the source path still identifies
192 * the same file before reparsing it into a separate archive object. */
193 int32_t
194 3 libmpq__archive_clone(mpq_archive_s **clone, mpq_archive_s *source)
195 {
196 struct stat file_status;
197
198
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (clone == NULL)
199 1 return LIBMPQ_ERROR_EXIST;
200 2 *clone = NULL;
201
202
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (source == NULL || source->filename == NULL || source->write_mode)
203 1 return LIBMPQ_ERROR_EXIST;
204
205 #if !defined(_WIN32) && !defined(_WIN64)
206
207 /* Reject cloning after the backing path has been replaced or removed. */
208
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (source->file_identity_valid) {
209
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (stat(source->filename, &file_status) < 0)
210 return errno == ENOENT ? LIBMPQ_ERROR_EXIST : LIBMPQ_ERROR_OPEN;
211
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if ((uint64_t)file_status.st_dev != source->file_device ||
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 (uint64_t)file_status.st_ino != source->file_inode)
213 return LIBMPQ_ERROR_EXIST;
214 }
215 #endif
216
217 1 return libmpq__reader_archive_open_path(clone, source->filename, source->archive_offset);
218 }
219
220 /* Close the archive file and release all metadata tables allocated during archive open.
221 * Writer handles are finalized before their archive storage is freed, while
222 * reader-side cached block offsets are released entry by entry. */
223 int32_t
224 129 libmpq__archive_close(mpq_archive_s *mpq_archive)
225 {
226 uint32_t i;
227 129 int32_t result = LIBMPQ_SUCCESS;
228
229
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 128 times.
129 if (mpq_archive == NULL)
230 1 return LIBMPQ_ERROR_EXIST;
231
232
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 63 times.
128 if (mpq_archive->write_mode) {
233
234 /* Writer closure must serialize tables before releasing writer storage. */
235 65 result = libmpq__writer_finalize(mpq_archive);
236
2/6
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 65 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
65 if (mpq_archive->fp != NULL && fclose(mpq_archive->fp) < 0 && result == LIBMPQ_SUCCESS)
237 result = LIBMPQ_ERROR_CLOSE;
238
2/2
✓ Branch 0 taken 3010 times.
✓ Branch 1 taken 65 times.
3075 for (i = 0; i < mpq_archive->write_capacity; i++)
239
1/2
✓ Branch 0 taken 3010 times.
✗ Branch 1 not taken.
3010 free(mpq_archive->write_names ? mpq_archive->write_names[i] : NULL);
240 65 free(mpq_archive->write_names);
241 65 free(mpq_archive->write_locales);
242 65 free(mpq_archive->write_platforms);
243 65 free(mpq_archive->mpq_hash);
244 65 free(mpq_archive->mpq_block);
245 65 free(mpq_archive->mpq_block_ex);
246 65 free(mpq_archive->filename);
247 65 free(mpq_archive);
248 65 return result;
249 }
250
251
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
63 if ((fclose(mpq_archive->fp)) < 0) {
252
253 /* Keep the handle intact so the caller may retry closing it. */
254 return LIBMPQ_ERROR_CLOSE;
255 }
256
257
2/2
✓ Branch 0 taken 2016 times.
✓ Branch 1 taken 63 times.
2079 for (i = 0; i < mpq_archive->mpq_header.block_table_count; i++) {
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2016 times.
2016 if (mpq_archive->mpq_file[i] != NULL) {
259 free(mpq_archive->mpq_file[i]->packed_offset);
260 free(mpq_archive->mpq_file[i]);
261 }
262 }
263
264 63 free(mpq_archive->mpq_map);
265 63 free(mpq_archive->mpq_file);
266 63 free(mpq_archive->mpq_hash);
267 63 free(mpq_archive->mpq_block);
268 63 free(mpq_archive->mpq_block_ex);
269 63 free(mpq_archive->filename);
270 63 free(mpq_archive);
271
272 63 return LIBMPQ_SUCCESS;
273 }
274
275 /* Return the sum of packed sizes for all files in the archive block table.
276 * The caller supplies an accumulator, allowing this query to preserve the
277 * library's existing additive API behavior. */
278 int32_t
279 1 libmpq__archive_size_packed(mpq_archive_s *mpq_archive, libmpq__off_t *packed_size)
280 {
281
282 /* Running total across all block-table entries. */
283 uint32_t i;
284
285
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 for (i = 0; i < mpq_archive->files; i++) {
286 6 *packed_size +=
287 6 mpq_archive->mpq_block[mpq_archive->mpq_map[i].block_table_indices].packed_size;
288 }
289
290 1 return LIBMPQ_SUCCESS;
291 }
292
293 /* Return the sum of unpacked sizes for all files in the archive block table.
294 * Only live public file-map entries are counted; unused block-table capacity
295 * does not contribute to the reported total. */
296 int32_t
297 1 libmpq__archive_size_unpacked(mpq_archive_s *mpq_archive, libmpq__off_t *unpacked_size)
298 {
299
300 /* Running total across all block-table entries. */
301 uint32_t i;
302
303
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 for (i = 0; i < mpq_archive->files; i++) {
304 6 *unpacked_size +=
305 6 mpq_archive->mpq_block[mpq_archive->mpq_map[i].block_table_indices].unpacked_size;
306 }
307
308 1 return LIBMPQ_SUCCESS;
309 }
310
311 /* Return the byte offset where the MPQ archive starts in the backing file.
312 * Embedded archives therefore report their discovered start rather than zero. */
313 int32_t
314 1 libmpq__archive_offset(mpq_archive_s *mpq_archive, libmpq__off_t *offset)
315 {
316 1 *offset = mpq_archive->archive_offset;
317
318 1 return LIBMPQ_SUCCESS;
319 }
320
321 /* Return the MPQ archive format version stored in the header.
322 * The internal zero-based version is converted to the public one-based API
323 * value before being written to the caller's output. */
324 int32_t
325 3 libmpq__archive_version(mpq_archive_s *mpq_archive, uint32_t *version)
326 {
327 3 *version = mpq_archive->mpq_header.version + 1;
328
329 3 return LIBMPQ_SUCCESS;
330 }
331
332 /* Return the number of valid file entries discovered while opening the archive.
333 * This is the compact public count, not the reserved block-table capacity. */
334 int32_t
335 3 libmpq__archive_files(mpq_archive_s *mpq_archive, uint32_t *files)
336 {
337 3 *files = mpq_archive->files;
338
339 3 return LIBMPQ_SUCCESS;
340 }
341
342 /* Return the packed size of a file entry by block-table number.
343 * The public file number is validated and translated through the compact map
344 * before reading the corresponding block-table entry. */
345 int32_t
346 3 libmpq__file_size_packed(
347 mpq_archive_s *mpq_archive, uint32_t file_number, libmpq__off_t *packed_size
348 )
349 {
350
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
3 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
351 1 return LIBMPQ_ERROR_EXIST;
352 }
353
354 2 *packed_size =
355 2 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].packed_size;
356
357 2 return LIBMPQ_SUCCESS;
358 }
359
360 /* Return the unpacked size of a file entry by block-table number.
361 * Invalid compact file numbers are rejected before any archive metadata is
362 * accessed. */
363 int32_t
364 606 libmpq__file_size_unpacked(
365 mpq_archive_s *mpq_archive, uint32_t file_number, libmpq__off_t *unpacked_size
366 )
367 {
368
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 605 times.
606 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
369 1 return LIBMPQ_ERROR_EXIST;
370 }
371
372 605 *unpacked_size =
373 605 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].unpacked_size;
374
375 605 return LIBMPQ_SUCCESS;
376 }
377
378 /* Return the file data offset relative to the start of the archive.
379 * MPQ v2 high offset words are combined with the legacy low word to produce
380 * the complete offset visible through the public API. */
381 int32_t
382 576 libmpq__file_offset(mpq_archive_s *mpq_archive, uint32_t file_number, libmpq__off_t *offset)
383 {
384
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 575 times.
576 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
385 1 return LIBMPQ_ERROR_EXIST;
386 }
387
388 575 *offset = mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].offset +
389 575 (((long long)mpq_archive
390 575 ->mpq_block_ex[mpq_archive->mpq_map[file_number].block_table_indices]
391 575 .offset_high)
392 575 << 32);
393
394 575 return LIBMPQ_SUCCESS;
395 }
396
397 /* Return the number of blocks needed to store the selected file.
398 * The reader distinguishes single-unit files from sectorized entries and
399 * applies the archive sector size for the latter. */
400 int32_t
401 578 libmpq__file_blocks(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t *blocks)
402 {
403
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 577 times.
578 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
404 1 return LIBMPQ_ERROR_EXIST;
405 }
406
407 577 *blocks = libmpq__reader_count_file_blocks(mpq_archive, file_number);
408
409 577 return LIBMPQ_SUCCESS;
410 }
411
412 /* Report whether the selected file entry has the MPQ encrypted flag set.
413 * The result is normalized to the library's boolean convention after lookup. */
414 int32_t
415 946 libmpq__file_encrypted(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t *encrypted)
416 {
417
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 945 times.
946 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
418 1 return LIBMPQ_ERROR_EXIST;
419 }
420
421 945 *encrypted =
422 945 (mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
423 LIBMPQ_FLAG_ENCRYPTED) != 0
424 ? TRUE
425 945 : FALSE;
426
427 945 return LIBMPQ_SUCCESS;
428 }
429
430 /* Report whether the selected file entry has any MPQ compression flags set.
431 * This reports Blizzard multi-compression metadata, including its per-sector
432 * codec mask, rather than treating standalone PKWARE as multi-compressed. */
433 int32_t
434 948 libmpq__file_compressed(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t *compressed)
435 {
436
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 947 times.
948 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
437 1 return LIBMPQ_ERROR_EXIST;
438 }
439
440 947 *compressed =
441 947 (mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
442 LIBMPQ_FLAG_COMPRESS_MULTI) != 0
443 ? TRUE
444 947 : FALSE;
445
446 947 return LIBMPQ_SUCCESS;
447 }
448
449 /* Report whether the selected file entry uses PKWARE implosion.
450 * The flag query covers standalone implode storage as represented in the MPQ
451 * block entry and returns a normalized boolean result. */
452 int32_t
453 945 libmpq__file_imploded(mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t *imploded)
454 {
455
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 944 times.
945 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
456 1 return LIBMPQ_ERROR_EXIST;
457 }
458
459 944 *imploded =
460 944 (mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
461 LIBMPQ_FLAG_COMPRESS_PKZIP) != 0
462 ? TRUE
463 944 : FALSE;
464
465 944 return LIBMPQ_SUCCESS;
466 }
467
468 /* Calculate the three Storm hashes used to identify an MPQ file name.
469 * Each output corresponds to a distinct hash-table phase used during MPQ
470 * name lookup and collision probing. */
471 void
472 4899 libmpq__file_hash(const char *filename, uint32_t *hash1, uint32_t *hash2, uint32_t *hash3)
473 {
474 4899 *hash1 = libmpq__crypto_hash_string(filename, 0x0);
475 4899 *hash2 = libmpq__crypto_hash_string(filename, 0x100);
476 4899 *hash3 = libmpq__crypto_hash_string(filename, 0x200);
477 4899 }
478
479 /* Resolve a precomputed MPQ file-name hash to a public file number.
480 * The first hash selects a slot and linear probing continues until the stored
481 * pair matches or the table wraps without finding the file. */
482 int32_t
483 577 libmpq__file_number_from_hash(
484 mpq_archive_s *mpq_archive, uint32_t hash1, uint32_t hash2, uint32_t hash3, uint32_t *number
485 )
486 {
487
488 /* Hash table probe state and archive hash-table size. */
489 uint32_t i;
490 uint32_t ht_count;
491 uint32_t block_table_index;
492
493 577 ht_count = mpq_archive->mpq_header.hash_table_count;
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 577 times.
577 if (ht_count == 0) {
495 return LIBMPQ_ERROR_EXIST;
496 }
497
498 577 hash1 %= ht_count;
499
500 /* The first hash selects the initial probe slot; collisions use linear probing. */
501
2/2
✓ Branch 0 taken 697 times.
✓ Branch 1 taken 2 times.
699 for (i = hash1; mpq_archive->mpq_hash[i].block_table_index != LIBMPQ_HASH_FREE;
502 122 i = (i + 1) % ht_count) {
503
3/4
✓ Branch 0 taken 575 times.
✓ Branch 1 taken 122 times.
✓ Branch 2 taken 575 times.
✗ Branch 3 not taken.
697 if (mpq_archive->mpq_hash[i].hash_a == hash2 && mpq_archive->mpq_hash[i].hash_b == hash3) {
504 575 block_table_index = mpq_archive->mpq_hash[i].block_table_index;
505
1/2
✓ Branch 0 taken 575 times.
✗ Branch 1 not taken.
575 if (block_table_index >= mpq_archive->mpq_header.block_table_count ||
506
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 575 times.
575 (mpq_archive->mpq_block[block_table_index].flags & LIBMPQ_FLAG_EXISTS) == 0) {
507 return LIBMPQ_ERROR_FORMAT;
508 }
509 575 *number = block_table_index - mpq_archive->mpq_map[block_table_index].block_table_diff;
510
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 575 times.
575 if (*number >= mpq_archive->files) {
511 return LIBMPQ_ERROR_FORMAT;
512 }
513
514 575 return LIBMPQ_SUCCESS;
515 }
516
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 122 times.
122 if ((i + 1) % ht_count == hash1) {
518 break;
519 }
520 }
521
522 2 return LIBMPQ_ERROR_EXIST;
523 }
524
525 /* Resolve an MPQ file name to its block-table number through the hash table.
526 * The name is hashed with all three Storm phases before the collision-aware
527 * lookup is delegated to the precomputed-hash helper. */
528 int32_t
529 576 libmpq__file_number(mpq_archive_s *mpq_archive, const char *filename, uint32_t *number)
530 {
531 uint32_t hash1;
532 uint32_t hash2;
533 uint32_t hash3;
534
535 576 libmpq__file_hash(filename, &hash1, &hash2, &hash3);
536 576 return libmpq__file_number_from_hash(mpq_archive, hash1, hash2, hash3, number);
537 }
538
539 /* Read a complete file by opening its block offset table and copying each block.
540 * The output buffer must hold the complete unpacked file, and cached offset
541 * state is closed on both successful and failed block reads. */
542 int32_t
543 574 libmpq__file_read(
544 mpq_archive_s *mpq_archive, uint32_t file_number, uint8_t *out_buf, libmpq__off_t out_size,
545 libmpq__off_t *transferred
546 )
547 {
548
549 /* Block loop state and total bytes transferred to the caller. */
550 uint32_t i;
551 574 uint32_t blocks = 0;
552 574 int32_t result = 0;
553 574 libmpq__off_t file_offset = 0;
554 574 libmpq__off_t unpacked_size = 0;
555 574 libmpq__off_t transferred_block = 0;
556 574 libmpq__off_t transferred_total = 0;
557
558
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 573 times.
574 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
559 1 return LIBMPQ_ERROR_EXIST;
560 }
561
562 573 libmpq__file_size_unpacked(mpq_archive, file_number, &unpacked_size);
563
564
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 572 times.
573 if (unpacked_size > out_size) {
565 1 return LIBMPQ_ERROR_SIZE;
566 }
567
568 572 libmpq__file_offset(mpq_archive, file_number, &file_offset);
569 572 libmpq__file_blocks(mpq_archive, file_number, &blocks);
570
571
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 571 times.
572 if ((result = libmpq__block_open_offset(mpq_archive, file_number)) < 0) {
572 1 return result;
573 }
574
575 /* Read each block into its exact destination slice and maintain one total. */
576
2/2
✓ Branch 0 taken 942 times.
✓ Branch 1 taken 571 times.
1513 for (i = 0; i < blocks; i++) {
577 942 unpacked_size = 0;
578
579 942 libmpq__block_size_unpacked(mpq_archive, file_number, i, &unpacked_size);
580
581
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 942 times.
942 if ((result = libmpq__block_read(
582 mpq_archive, file_number, i, out_buf + transferred_total, unpacked_size,
583 &transferred_block
584 )) < 0) {
585 libmpq__block_close_offset(mpq_archive, file_number);
586 return result;
587 }
588
589 942 transferred_total += transferred_block;
590 }
591
592 571 libmpq__block_close_offset(mpq_archive, file_number);
593
594
1/2
✓ Branch 0 taken 571 times.
✗ Branch 1 not taken.
571 if (transferred != NULL) {
595 571 *transferred = transferred_total;
596 }
597
598 571 return LIBMPQ_SUCCESS;
599 }
600
601 /* Open a file entry and cache its packed block offset table for block operations.
602 * Compressed entries load and decrypt their serialized offsets, while raw or
603 * single-unit entries receive synthesized offsets from block metadata. */
604 int32_t
605 576 libmpq__block_open_offset(mpq_archive_s *mpq_archive, uint32_t file_number)
606 {
607
608 /* Packed block table state, file seed and read status. */
609 uint32_t blocks;
610 uint32_t i;
611 uint32_t block_table_index;
612 uint32_t packed_offset_count;
613 uint32_t packed_size;
614 576 int32_t result = 0;
615 576 uint8_t *packed_data = NULL;
616
617
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 575 times.
576 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
618 1 return LIBMPQ_ERROR_EXIST;
619 }
620
621
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 574 times.
575 if (mpq_archive->mpq_file[file_number]) {
622
623 /* Nested callers share the cached offsets through a reference count. */
624 1 mpq_archive->mpq_file[file_number]->open_count++;
625 1 return LIBMPQ_SUCCESS;
626 }
627
628 574 block_table_index = mpq_archive->mpq_map[file_number].block_table_indices;
629 574 blocks = libmpq__reader_count_file_blocks(mpq_archive, file_number);
630
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 574 times.
574 if (blocks > UINT32_MAX / sizeof(uint32_t) - 2U) {
631 return LIBMPQ_ERROR_FORMAT;
632 }
633 574 packed_offset_count = blocks + 1;
634 574 packed_size = sizeof(uint32_t) * packed_offset_count;
635
636
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 574 times.
574 if ((mpq_archive->mpq_block[block_table_index].flags & LIBMPQ_FLAG_CRC) != 0) {
637 packed_size += sizeof(uint32_t);
638 }
639
640
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 574 times.
574 if ((mpq_archive->mpq_file[file_number] = calloc(1, sizeof(mpq_file_s))) == NULL) {
641 result = LIBMPQ_ERROR_MALLOC;
642 goto error;
643 }
644
645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 574 times.
574 if ((mpq_archive->mpq_file[file_number]->packed_offset = calloc(1, packed_size)) == NULL) {
646 result = LIBMPQ_ERROR_MALLOC;
647 goto error;
648 }
649
650 574 mpq_archive->mpq_file[file_number]->packed_offset_count = packed_offset_count;
651 574 mpq_archive->mpq_file[file_number]->open_count = 1;
652
653 /* Compressed multi-sector files carry serialized offsets before their first
654 * payload, so load that table before any block can be read. */
655
2/2
✓ Branch 0 taken 440 times.
✓ Branch 1 taken 134 times.
574 if ((mpq_archive->mpq_block[block_table_index].flags &
656 440 (LIBMPQ_FLAG_COMPRESSED | LIBMPQ_FLAG_COMPRESS_PKZIP)) != 0 &&
657
2/2
✓ Branch 0 taken 422 times.
✓ Branch 1 taken 18 times.
440 (mpq_archive->mpq_block[block_table_index].flags & LIBMPQ_FLAG_SINGLE) == 0) {
658
2/4
✓ Branch 0 taken 422 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 422 times.
844 if (mpq_archive->mpq_block[block_table_index].packed_size < packed_size ||
659 422 libmpq__validate_payload_range(mpq_archive, block_table_index, 0, packed_size) < 0) {
660 result = LIBMPQ_ERROR_FORMAT;
661 goto error;
662 }
663
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 422 times.
422 if (fseeko(
664 mpq_archive->fp,
665 422 mpq_archive->mpq_block[block_table_index].offset +
666 422 (((long long)mpq_archive->mpq_block_ex[block_table_index].offset_high) << 32) +
667 422 mpq_archive->archive_offset,
668 SEEK_SET
669 ) < 0) {
670 result = LIBMPQ_ERROR_SEEK;
671 goto error;
672 }
673
674
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 422 times.
422 if ((packed_data = malloc(packed_size)) == NULL) {
675 result = LIBMPQ_ERROR_MALLOC;
676 goto error;
677 }
678
679
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 422 times.
422 if (fread(packed_data, 1, packed_size, mpq_archive->fp) != packed_size) {
680 result = LIBMPQ_ERROR_READ;
681 goto error;
682 }
683
684 /* Some protected archives omit the encrypted flag; a wrong first offset exposes that. */
685
2/2
✓ Branch 1 taken 111 times.
✓ Branch 2 taken 311 times.
422 if (libmpq__load_le32(packed_data) != packed_size &&
686
1/2
✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
111 libmpq__load_le32(packed_data) != packed_size + 4) {
687 111 mpq_archive->mpq_block[block_table_index].flags |= LIBMPQ_FLAG_ENCRYPTED;
688 }
689
690 /* The packed offset table uses seed - 1, so recover the file seed first. */
691
2/2
✓ Branch 0 taken 111 times.
✓ Branch 1 taken 311 times.
422 if (mpq_archive->mpq_block[block_table_index].flags & LIBMPQ_FLAG_ENCRYPTED) {
692 uint32_t seed;
693
694
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 111 times.
111 if (libmpq__crypto_derive_block_table_seed(
695 packed_data, packed_size, mpq_archive->block_size, &seed
696 ) < 0) {
697 result = LIBMPQ_ERROR_DECRYPT;
698 goto error;
699 }
700 111 mpq_archive->mpq_file[file_number]->seed = seed;
701 111 mpq_archive->mpq_file[file_number]->seed_known = TRUE;
702
703
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 111 times.
111 if (libmpq__crypto_decrypt_block(
704 111 packed_data, packed_size, mpq_archive->mpq_file[file_number]->seed - 1
705 ) < 0) {
706 result = LIBMPQ_ERROR_DECRYPT;
707 goto error;
708 }
709
710 /* A valid decrypted table starts with its own byte size. */
711
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 111 times.
111 if (libmpq__load_le32(packed_data) != packed_size) {
712 result = LIBMPQ_ERROR_DECRYPT;
713 goto error;
714 }
715 }
716
717 422 libmpq__reader_decode_uint32_table(
718 422 mpq_archive->mpq_file[file_number]->packed_offset, packed_data,
719 packed_size / sizeof(uint32_t)
720 );
721
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 422 times.
422 if (mpq_archive->mpq_file[file_number]->packed_offset[0] != packed_size) {
722 result = LIBMPQ_ERROR_FORMAT;
723 goto error;
724 }
725
2/2
✓ Branch 0 taken 736 times.
✓ Branch 1 taken 421 times.
1157 for (i = 1; i < packed_offset_count; i++) {
726 736 if (mpq_archive->mpq_file[file_number]->packed_offset[i] <
727
1/2
✓ Branch 0 taken 736 times.
✗ Branch 1 not taken.
736 mpq_archive->mpq_file[file_number]->packed_offset[i - 1] ||
728 736 mpq_archive->mpq_file[file_number]->packed_offset[i] >
729
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 735 times.
736 mpq_archive->mpq_block[block_table_index].packed_size) {
730 1 result = LIBMPQ_ERROR_FORMAT;
731 1 goto error;
732 }
733 }
734 421 free(packed_data);
735 421 packed_data = NULL;
736 } else {
737
738 /* Raw sectorized files derive offsets directly from their fixed sector size. */
739
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 56 times.
152 if ((mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
740 LIBMPQ_FLAG_SINGLE) == 0) {
741
742 /* Synthesize offsets for uncompressed multi-sector files. */
743
2/2
✓ Branch 0 taken 250 times.
✓ Branch 1 taken 96 times.
346 for (i = 0; i < packed_offset_count; i++) {
744
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 154 times.
250 if (i == blocks) {
745 96 mpq_archive->mpq_file[file_number]->packed_offset[i] =
746 mpq_archive
747 96 ->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
748 96 .unpacked_size;
749 } else {
750 154 mpq_archive->mpq_file[file_number]->packed_offset[i] =
751 154 i * mpq_archive->block_size;
752 }
753 }
754 } else {
755 56 mpq_archive->mpq_file[file_number]->packed_offset[0] = 0;
756 56 mpq_archive->mpq_file[file_number]->packed_offset[1] =
757 56 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
758 56 .packed_size;
759 }
760 }
761
762 /* Raw encrypted files have no encrypted offset table from which to derive a seed. */
763
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 524 times.
573 if ((mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
764 (LIBMPQ_FLAG_ENCRYPTED | LIBMPQ_FLAG_COMPRESSED)) == LIBMPQ_FLAG_ENCRYPTED) {
765 uint8_t first_block[8];
766 uint32_t first_offset;
767 uint32_t second_offset;
768 uint32_t first_size;
769 uint32_t seed;
770
771
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (packed_offset_count < 2) {
772 result = LIBMPQ_ERROR_FORMAT;
773 goto error;
774 }
775
776 49 first_offset = mpq_archive->mpq_file[file_number]->packed_offset[0];
777 49 second_offset = mpq_archive->mpq_file[file_number]->packed_offset[1];
778
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (second_offset < first_offset) {
779 result = LIBMPQ_ERROR_FORMAT;
780 goto error;
781 }
782
783 49 first_size = second_offset - first_offset;
784
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (first_size < sizeof(first_block)) {
785 result = LIBMPQ_ERROR_DECRYPT;
786 goto error;
787 }
788
789
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (fseeko(
790 mpq_archive->fp,
791 49 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
792 49 .offset +
793 49 (((long long)mpq_archive
794 49 ->mpq_block_ex[mpq_archive->mpq_map[file_number].block_table_indices]
795 49 .offset_high)
796 49 << 32) +
797 49 mpq_archive->archive_offset,
798 SEEK_SET
799
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 ) < 0 ||
800 49 fread(first_block, 1, sizeof(first_block), mpq_archive->fp) != sizeof(first_block)) {
801 result = LIBMPQ_ERROR_READ;
802 goto error;
803 }
804
805 /* Raw encrypted payloads require signature-based key recovery instead. */
806
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (libmpq__crypto_detect_file_key(
807 first_block, sizeof(first_block),
808 49 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
809 .unpacked_size,
810 &seed
811 ) < 0) {
812 result = LIBMPQ_ERROR_DECRYPT;
813 goto error;
814 }
815
816 49 mpq_archive->mpq_file[file_number]->seed = seed;
817 49 mpq_archive->mpq_file[file_number]->seed_known = TRUE;
818 }
819
820 573 return LIBMPQ_SUCCESS;
821
822 1 error:
823
824 1 free(packed_data);
825
826
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (mpq_archive->mpq_file[file_number] != NULL) {
827 1 free(mpq_archive->mpq_file[file_number]->packed_offset);
828 1 free(mpq_archive->mpq_file[file_number]);
829 1 mpq_archive->mpq_file[file_number] = NULL;
830 }
831
832 1 return result;
833 }
834
835 /* Release a cached block offset table when the last user closes it.
836 * Reference counting permits nested block operations while ensuring the cache
837 * is freed only after the final matching close. */
838 int32_t
839 575 libmpq__block_close_offset(mpq_archive_s *mpq_archive, uint32_t file_number)
840 {
841
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 575 times.
575 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
842 return LIBMPQ_ERROR_EXIST;
843 }
844
845
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 574 times.
575 if (mpq_archive->mpq_file[file_number] == NULL) {
846 1 return LIBMPQ_ERROR_OPEN;
847 }
848
849 574 mpq_archive->mpq_file[file_number]->open_count--;
850
851
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 573 times.
574 if (mpq_archive->mpq_file[file_number]->open_count != 0) {
852
853 /* Keep the cache alive until every matching open operation closes. */
854 1 return LIBMPQ_SUCCESS;
855 }
856
857 573 free(mpq_archive->mpq_file[file_number]->packed_offset);
858 573 free(mpq_archive->mpq_file[file_number]);
859
860 573 mpq_archive->mpq_file[file_number] = NULL;
861
862 573 return LIBMPQ_SUCCESS;
863 }
864
865 /* Return the unpacked size for one block of an opened file entry.
866 * Full sectors use the archive sector size, while the final sector is reduced
867 * to the remaining file bytes and single-unit files use their full size. */
868 int32_t
869 1890 libmpq__block_size_unpacked(
870 mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t block_number,
871 libmpq__off_t *unpacked_size
872 )
873 {
874
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1889 times.
1890 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
875 1 return LIBMPQ_ERROR_EXIST;
876 }
877
878
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1888 times.
1889 if (libmpq__reader_validate_block_number(mpq_archive, file_number, block_number) < 0) {
879 1 return LIBMPQ_ERROR_EXIST;
880 }
881
882
1/2
✓ Branch 0 taken 1888 times.
✗ Branch 1 not taken.
1888 if (mpq_archive->mpq_file[file_number] == NULL ||
883
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
1888 mpq_archive->mpq_file[file_number]->packed_offset == NULL) {
884 return LIBMPQ_ERROR_OPEN;
885 }
886
887
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1888 times.
1888 if (mpq_archive->mpq_file[file_number]->packed_offset_count <= block_number + 1) {
888 return LIBMPQ_ERROR_EXIST;
889 }
890
891
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 1776 times.
1888 if ((mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
892 LIBMPQ_FLAG_SINGLE) != 0) {
893
894 /* A single-unit entry has one logical block containing the whole file. */
895 112 *unpacked_size =
896 112 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
897 112 .unpacked_size;
898 }
899
900
2/2
✓ Branch 0 taken 1776 times.
✓ Branch 1 taken 112 times.
1888 if ((mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].flags &
901 LIBMPQ_FLAG_SINGLE) == 0) {
902
903 /* Every non-final sector is full-sized; only the tail uses a remainder. */
904
2/2
✓ Branch 1 taken 756 times.
✓ Branch 2 taken 1020 times.
1776 if (block_number < libmpq__reader_count_file_blocks(mpq_archive, file_number) - 1) {
905 756 *unpacked_size = mpq_archive->block_size;
906 } else {
907 1020 *unpacked_size =
908 1020 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
909 1020 .unpacked_size -
910 1020 mpq_archive->block_size * block_number;
911 }
912 }
913
914 1888 return LIBMPQ_SUCCESS;
915 }
916
917 /* Read, decrypt and decompress one block from an opened file entry.
918 * The routine computes packed bounds, applies per-block encryption, selects
919 * raw or codec output, and reports the exact unpacked byte count. */
920 int32_t
921 945 libmpq__block_read(
922 mpq_archive_s *mpq_archive, uint32_t file_number, uint32_t block_number, uint8_t *out_buf,
923 libmpq__off_t out_size, libmpq__off_t *transferred
924 )
925 {
926
927 /* Packed input buffer, size bookkeeping and block decryption state. */
928 uint8_t *in_buf;
929 945 uint32_t seed = 0;
930 945 uint32_t encrypted = 0;
931 945 uint32_t compressed = 0;
932 945 uint32_t imploded = 0;
933 945 int32_t tb = 0;
934 945 uint8_t use_out_buf = FALSE;
935 945 libmpq__off_t block_offset = 0;
936 945 libmpq__off_t in_size = 0;
937 945 libmpq__off_t unpacked_size = 0;
938
939
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 945 times.
945 if (libmpq__reader_validate_file_number(mpq_archive, file_number) < 0) {
940 return LIBMPQ_ERROR_EXIST;
941 }
942
943
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 944 times.
945 if (libmpq__reader_validate_block_number(mpq_archive, file_number, block_number) < 0) {
944 1 return LIBMPQ_ERROR_EXIST;
945 }
946
947
1/2
✓ Branch 0 taken 944 times.
✗ Branch 1 not taken.
944 if (mpq_archive->mpq_file[file_number] == NULL ||
948
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 944 times.
944 mpq_archive->mpq_file[file_number]->packed_offset == NULL) {
949 return LIBMPQ_ERROR_OPEN;
950 }
951
952
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 944 times.
944 if (mpq_archive->mpq_file[file_number]->packed_offset_count <= block_number + 1) {
953 return LIBMPQ_ERROR_EXIST;
954 }
955
956 944 libmpq__block_size_unpacked(mpq_archive, file_number, block_number, &unpacked_size);
957
958
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 943 times.
944 if (unpacked_size > out_size) {
959 1 return LIBMPQ_ERROR_SIZE;
960 }
961
962 /* Compute the absolute payload position from archive, file, and block offsets.
963 * The stored block offset is relative to the file payload start, not the
964 * beginning of the archive file. */
965 943 if (mpq_archive->mpq_file[file_number]->packed_offset[block_number + 1] <
966
1/2
✓ Branch 0 taken 943 times.
✗ Branch 1 not taken.
943 mpq_archive->mpq_file[file_number]->packed_offset[block_number] ||
967 943 mpq_archive->mpq_file[file_number]->packed_offset[block_number + 1] >
968 943 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices]
969
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 943 times.
943 .packed_size) {
970 return LIBMPQ_ERROR_FORMAT;
971 }
972 943 in_size = mpq_archive->mpq_file[file_number]->packed_offset[block_number + 1] -
973 943 mpq_archive->mpq_file[file_number]->packed_offset[block_number];
974
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 943 times.
943 if (libmpq__validate_payload_range(
975 943 mpq_archive, mpq_archive->mpq_map[file_number].block_table_indices,
976 943 mpq_archive->mpq_file[file_number]->packed_offset[block_number], (uint64_t)in_size
977 ) < 0) {
978 return LIBMPQ_ERROR_READ;
979 }
980 943 block_offset =
981 943 mpq_archive->mpq_block[mpq_archive->mpq_map[file_number].block_table_indices].offset +
982 943 (((long long)mpq_archive
983 943 ->mpq_block_ex[mpq_archive->mpq_map[file_number].block_table_indices]
984 943 .offset_high)
985 943 << 32) +
986 943 mpq_archive->mpq_file[file_number]->packed_offset[block_number];
987
988 943 libmpq__file_encrypted(mpq_archive, file_number, &encrypted);
989 943 libmpq__file_compressed(mpq_archive, file_number, &compressed);
990 943 libmpq__file_imploded(mpq_archive, file_number, &imploded);
991
992 /* Raw unencrypted blocks can be read directly into the caller's buffer. */
993
7/8
✓ Branch 0 taken 657 times.
✓ Branch 1 taken 286 times.
✓ Branch 2 taken 181 times.
✓ Branch 3 taken 476 times.
✓ Branch 4 taken 109 times.
✓ Branch 5 taken 72 times.
✓ Branch 6 taken 109 times.
✗ Branch 7 not taken.
943 use_out_buf = !encrypted && !compressed && !imploded && in_size <= out_size;
994
995
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 943 times.
943 if (fseeko(mpq_archive->fp, block_offset + mpq_archive->archive_offset, SEEK_SET) < 0) {
996 return LIBMPQ_ERROR_SEEK;
997 }
998
999
2/2
✓ Branch 0 taken 109 times.
✓ Branch 1 taken 834 times.
943 if (use_out_buf) {
1000
1001 /* Raw data can bypass a temporary allocation when no transform is needed. */
1002 109 in_buf = out_buf;
1003 } else {
1004
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 834 times.
834 if ((in_buf = calloc(1, in_size)) == NULL) {
1005 return LIBMPQ_ERROR_MALLOC;
1006 }
1007 }
1008
1009
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 943 times.
943 if (fread(in_buf, 1, (size_t)in_size, mpq_archive->fp) != (size_t)in_size) {
1010 if (!use_out_buf) {
1011 free(in_buf);
1012 }
1013 return LIBMPQ_ERROR_READ;
1014 }
1015
1016
2/2
✓ Branch 0 taken 286 times.
✓ Branch 1 taken 657 times.
943 if (encrypted) {
1017
1018 /* Encrypted blocks use a seed derived from the file and block number. */
1019
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 286 times.
286 if (libmpq__reader_get_block_seed(mpq_archive, file_number, block_number, &seed) < 0) {
1020 if (!use_out_buf) {
1021 free(in_buf);
1022 }
1023 return LIBMPQ_ERROR_DECRYPT;
1024 }
1025
1026
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 286 times.
286 if (libmpq__crypto_decrypt_block(in_buf, (uint32_t)in_size, seed) < 0) {
1027 if (!use_out_buf) {
1028 free(in_buf);
1029 }
1030 return LIBMPQ_ERROR_DECRYPT;
1031 }
1032 }
1033
1034 /* Blizzard multi-compression blocks declare their exact backend chain in the payload. */
1035
2/2
✓ Branch 0 taken 613 times.
✓ Branch 1 taken 330 times.
943 if (compressed) {
1036
1037 /* The payload's leading mask selects and orders its decompression stages. */
1038
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 613 times.
613 if ((tb = libmpq__compression_decompress_block(
1039 in_buf, in_size, out_buf, out_size, LIBMPQ_FLAG_COMPRESS_MULTI
1040 )) < 0) {
1041 if (!use_out_buf) {
1042 free(in_buf);
1043 }
1044 return LIBMPQ_ERROR_UNPACK;
1045 }
1046 }
1047
1048 /* PKWARE-imploded blocks use the legacy explode decoder. */
1049
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 805 times.
943 if (imploded) {
1050
1051 /* Standalone PKWARE payloads use the legacy decoder without a mask byte. */
1052
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
138 if ((tb = libmpq__compression_decompress_block(
1053 in_buf, in_size, out_buf, out_size, LIBMPQ_FLAG_COMPRESS_PKZIP
1054 )) < 0) {
1055 if (!use_out_buf) {
1056 free(in_buf);
1057 }
1058 return LIBMPQ_ERROR_UNPACK;
1059 }
1060 }
1061
1062
3/4
✓ Branch 0 taken 613 times.
✓ Branch 1 taken 330 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 613 times.
943 if (compressed && imploded) {
1063 if (!use_out_buf) {
1064 free(in_buf);
1065 }
1066 return LIBMPQ_ERROR_UNPACK;
1067 }
1068
1069
4/4
✓ Branch 0 taken 330 times.
✓ Branch 1 taken 613 times.
✓ Branch 2 taken 192 times.
✓ Branch 3 taken 138 times.
943 if (!compressed && !imploded) {
1070
1071 /* A raw block is copied only after encrypted and compressed paths are excluded. */
1072
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 192 times.
192 if ((tb = libmpq__compression_decompress_block(
1073 in_buf, in_size, out_buf, out_size, LIBMPQ_FLAG_COMPRESS_NONE
1074 )) < 0) {
1075 if (!use_out_buf) {
1076 free(in_buf);
1077 }
1078 return LIBMPQ_ERROR_UNPACK;
1079 }
1080 }
1081
1082
2/2
✓ Branch 0 taken 834 times.
✓ Branch 1 taken 109 times.
943 if (!use_out_buf) {
1083 834 free(in_buf);
1084 }
1085
1086
1/2
✓ Branch 0 taken 943 times.
✗ Branch 1 not taken.
943 if (transferred != NULL) {
1087 943 *transferred = tb;
1088 }
1089
1090 943 return LIBMPQ_SUCCESS;
1091 }
1092