GCC Code Coverage Report


Directory: src/
File: src/mpq-compression.c
Date: 2026-09-02 16:40:44
Exec Total Coverage
Lines: 222 283 78.4%
Functions: 10 11 90.9%
Branches: 89 152 58.6%

Line Branch Exec Source
1 /*
2 * mpq-compression.c -- MPQ compression and decompression orchestration.
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-compression.h"
21 #include "mpq-endian.h"
22 #include "mpq-huffman.h"
23 #include "mpq-internal.h"
24 #include "mpq-pkware.h"
25 #include "mpq-wave.h"
26 #include <libmpq/mpq.h>
27
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <bzlib.h>
32 #include <zlib.h>
33
34 /* Map MPQ compression flags to the backend that can decode that payload. */
35 static decompress_table_s dcmp_table[] = {
36
37 /* Reverse of the canonical writer order. */
38 { LIBMPQ_COMPRESSION_BZIP2, libmpq__compression_decompress_bzip2 },
39 { LIBMPQ_COMPRESSION_PKZIP, libmpq__compression_decompress_pkzip },
40 { LIBMPQ_COMPRESSION_ZLIB, libmpq__compression_decompress_zlib },
41 { LIBMPQ_COMPRESSION_HUFFMAN, libmpq__compression_decompress_huffman },
42 { LIBMPQ_COMPRESSION_WAVE_STEREO, libmpq__compression_decompress_wave_stereo },
43 { LIBMPQ_COMPRESSION_WAVE_MONO, libmpq__compression_decompress_wave_mono }
44 };
45
46 /* Decompress an MPQ Huffman-compressed stream into the caller-provided buffer.
47 * The stream owns adaptive tree state, so the function initializes and frees
48 * a separate tree and bit reader for each archive block. */
49 int32_t
50 165 libmpq__compression_decompress_huffman(
51 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
52 )
53 {
54
55 /* Huffman state and transferred-byte count for this stream. */
56 165 int32_t tb = 0;
57 struct huffman_tree_s *ht;
58 struct huffman_input_stream_s *is;
59
60
3/6
✓ Branch 0 taken 165 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 165 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 165 times.
165 if (in_buf == NULL || out_buf == NULL || in_size < sizeof(uint32_t)) {
61 return LIBMPQ_ERROR_FORMAT;
62 }
63
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 165 times.
165 if ((ht = malloc(sizeof(struct huffman_tree_s))) == NULL) {
65 return LIBMPQ_ERROR_MALLOC;
66 }
67
68
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 165 times.
165 if ((is = malloc(sizeof(struct huffman_input_stream_s))) == NULL) {
69 free(ht);
70 return LIBMPQ_ERROR_MALLOC;
71 }
72
73 /* Start from a clean tree and input stream because both keep adaptive state. */
74 165 memset(ht, 0, sizeof(struct huffman_tree_s));
75 165 memset(is, 0, sizeof(struct huffman_input_stream_s));
76
77 /* The first four bytes seed the bit buffer; remaining bytes form the stream. */
78 165 is->bit_buf = libmpq__load_le32(in_buf);
79 165 in_buf += sizeof(int32_t);
80 165 is->in_buf = in_buf;
81 165 is->in_end = in_buf + in_size - sizeof(uint32_t);
82 165 is->bits = 32;
83
84 165 libmpq__huffman_tree_init(ht, LIBMPQ_HUFF_DECOMPRESS);
85
86 165 tb = libmpq__huffman_decode(ht, is, out_buf, out_size);
87
88 165 free(is);
89 165 free(ht);
90
91 165 return tb;
92 }
93
94 /* Decompress an MPQ zlib-compressed stream into the caller-provided buffer.
95 * The output count returned by zlib is converted to the libmpq block API's
96 * signed transfer convention, while zlib failures are propagated unchanged. */
97 int32_t
98 242 libmpq__compression_decompress_zlib(
99 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
100 )
101 {
102
103 /* Zlib stream state and transferred-byte count for this stream. */
104 242 int32_t result = 0;
105 242 int32_t tb = 0;
106 z_stream z;
107
108 /* Zlib consumes the complete MPQ block and writes directly to the caller buffer. */
109 242 memset(&z, 0, sizeof(z));
110 242 z.next_in = (Bytef *)in_buf;
111 242 z.avail_in = (uInt)in_size;
112 242 z.total_in = in_size;
113 242 z.next_out = (Bytef *)out_buf;
114 242 z.avail_out = (uInt)out_size;
115 242 z.total_out = 0;
116 242 z.zalloc = NULL;
117 242 z.zfree = NULL;
118
119 /* Use zlib's default window handling; MPQ streams are standard zlib payloads. */
120
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 242 times.
242 if ((result = inflateInit(&z)) != Z_OK) {
121 return result;
122 }
123
124
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 242 times.
242 if ((result = inflate(&z, Z_FINISH)) != Z_STREAM_END) {
125 inflateEnd(&z);
126 return result;
127 }
128
129 242 tb = z.total_out;
130
131
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 242 times.
242 if ((result = inflateEnd(&z)) != Z_OK) {
132 return result;
133 }
134
135 242 return tb;
136 }
137
138 /* Decompress an MPQ PKWARE Data Compression Library stream.
139 * A scratch codec object and callback state isolate the decoder from the
140 * caller's buffers while preserving the exact number of produced bytes. */
141 int32_t
142 3 libmpq__compression_decompress_pkzip(
143 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
144 )
145 {
146
147 /* PKZIP work buffer, callback state and transferred-byte count. */
148 3 int32_t tb = 0;
149 pkzip_cmp_s *work_buf;
150 pkzip_data_s info;
151
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if ((work_buf = malloc(sizeof(*work_buf))) == NULL) {
153 return LIBMPQ_ERROR_MALLOC;
154 }
155
156 /* The PKWARE decoder uses caller-provided scratch memory as its full state. */
157 3 memset(work_buf, 0, sizeof(*work_buf));
158
159 /* Callback state tracks input and output positions for the decoder. */
160 3 info.in_buf = in_buf;
161 3 info.in_pos = 0;
162 3 info.in_bytes = in_size;
163 3 info.out_buf = out_buf;
164 3 info.out_pos = 0;
165 3 info.max_out = out_size;
166
167
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((tb = libmpq__pkzip_decompress((uint8_t *)work_buf, &info)) < 0) {
168 free(work_buf);
169 return tb;
170 }
171
172 3 tb = info.out_pos;
173
174 3 free(work_buf);
175
176 3 return tb;
177 }
178
179 /* Decompress an MPQ bzip2-compressed stream into the caller-provided buffer.
180 * The bzip2 state consumes the compressed block and writes decoded bytes
181 * directly to the destination supplied by the archive reader. */
182 int32_t
183 77 libmpq__compression_decompress_bzip2(
184 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
185 )
186 {
187
188 /* Bzip2 stream state and transferred-byte count for this stream. */
189 77 int32_t result = 0;
190 77 int32_t tb = 0;
191 bz_stream strm;
192
193 /* Default bzip2 allocators are sufficient for MPQ block decompression. */
194 77 strm.bzalloc = NULL;
195 77 strm.bzfree = NULL;
196
197
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 77 times.
77 if ((result = BZ2_bzDecompressInit(&strm, 0, 0)) != BZ_OK) {
198 return result;
199 }
200
201 /* Bzip2 consumes the complete MPQ block and writes directly to the caller buffer. */
202 77 strm.next_in = (char *)in_buf;
203 77 strm.avail_in = in_size;
204 77 strm.next_out = (char *)out_buf;
205 77 strm.avail_out = out_size;
206
207 for (;;) {
208 77 uint32_t available_in = strm.avail_in;
209 77 uint32_t available_out = strm.avail_out;
210
211 77 result = BZ2_bzDecompress(&strm);
212
1/2
✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
77 if (result == BZ_STREAM_END) {
213 77 break;
214 }
215 if (result != BZ_OK || (strm.avail_in == available_in && strm.avail_out == available_out)) {
216 BZ2_bzDecompressEnd(&strm);
217 return LIBMPQ_ERROR_UNPACK;
218 }
219 if (strm.avail_out == 0) {
220 BZ2_bzDecompressEnd(&strm);
221 return LIBMPQ_ERROR_SIZE;
222 }
223 if (strm.avail_in == 0) {
224 BZ2_bzDecompressEnd(&strm);
225 return LIBMPQ_ERROR_UNPACK;
226 }
227 }
228
229 77 tb = strm.total_out_lo32;
230
231 77 BZ2_bzDecompressEnd(&strm);
232
233 77 return tb;
234 }
235
236 /* Decompress an MPQ mono WAVE-compressed stream.
237 * The channel count is fixed to one so the shared WAVE decoder can validate
238 * the payload format and reconstruct the original PCM byte stream. */
239 int32_t
240 3 libmpq__compression_decompress_wave_mono(
241 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
242 )
243 {
244
245 /* Transferred-byte count reported by the shared WAVE decoder. */
246 3 int32_t tb = 0;
247
248
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((tb = libmpq__wave_decompress(out_buf, out_size, in_buf, in_size, 1)) < 0) {
249 return tb;
250 }
251
252 3 return tb;
253 }
254
255 /* Decompress an MPQ stereo WAVE-compressed stream.
256 * The channel count is fixed to two, matching the stereo ADPCM framing and
257 * predictor state expected by the shared WAVE decoder. */
258 int32_t
259 libmpq__compression_decompress_wave_stereo(
260 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
261 )
262 {
263
264 /* Transferred-byte count reported by the shared WAVE decoder. */
265 int32_t tb = 0;
266
267 if ((tb = libmpq__wave_decompress(out_buf, out_size, in_buf, in_size, 2)) < 0) {
268 return tb;
269 }
270
271 return tb;
272 }
273
274 /* Decode a Blizzard multi-compression stream by applying each flagged backend in order.
275 * The leading mask selects supported codecs, and intermediate buffers preserve
276 * each stage's output while the next stage consumes it. */
277 int32_t
278 412 libmpq__compression_decompress_multi(
279 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size
280 )
281 {
282
283 /* Compression mask state, temporary buffers and transferred-byte count. */
284 412 int32_t tb = 0;
285 412 uint32_t count = 0;
286 412 uint32_t entries = (sizeof(dcmp_table) / sizeof(decompress_table_s));
287 412 uint8_t *temp_buf = NULL;
288 412 uint8_t *work_buf = 0;
289 uint8_t decompress_flag;
290 uint8_t decompress_unsupp;
291 uint32_t i;
292
293
3/6
✓ Branch 0 taken 412 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 412 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 412 times.
412 if (in_buf == NULL || out_buf == NULL || in_size == 0) {
294 return LIBMPQ_ERROR_UNPACK;
295 }
296
297 /* First byte selects the chained decompression backends for this block. */
298 412 decompress_flag = decompress_unsupp = *in_buf++;
299
300 412 in_size--;
301
302 /* Count supported algorithms and remember flags that have no local backend. */
303
2/2
✓ Branch 0 taken 2472 times.
✓ Branch 1 taken 412 times.
2884 for (i = 0; i < entries; i++) {
304
2/2
✓ Branch 0 taken 487 times.
✓ Branch 1 taken 1985 times.
2472 if (decompress_flag & dcmp_table[i].mask) {
305 487 count++;
306 487 decompress_unsupp &= ~dcmp_table[i].mask;
307 }
308 }
309
310 /* Refuse streams that use a compression method from a newer unsupported format. */
311
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 412 times.
412 if (decompress_unsupp) {
312 return LIBMPQ_ERROR_UNPACK;
313 }
314
315 /* Multiple backends need a temporary buffer between decompression stages. */
316
2/2
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 337 times.
412 if (count > 1) {
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if ((temp_buf = malloc(out_size)) == NULL) {
318 return LIBMPQ_ERROR_MALLOC;
319 }
320
321 75 memset(temp_buf, 0, out_size);
322 }
323
324 /* Apply selected backends in table order, alternating buffers between stages. */
325
2/2
✓ Branch 0 taken 2472 times.
✓ Branch 1 taken 412 times.
2884 for (i = 0, count = 0; i < entries; i++) {
326
327 /* Apply this decompressor if its bit is present in the stream header. */
328
2/2
✓ Branch 0 taken 487 times.
✓ Branch 1 taken 1985 times.
2472 if (decompress_flag & dcmp_table[i].mask) {
329
330 /* Chained stages ping-pong between output and temporary storage. */
331
2/2
✓ Branch 0 taken 412 times.
✓ Branch 1 taken 75 times.
487 if (count == 0)
332
2/2
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 337 times.
412 work_buf = temp_buf != NULL ? temp_buf : out_buf;
333 else
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 work_buf = (in_buf == out_buf) ? temp_buf : out_buf;
335
336 /* Decompress the current stage with the mapped backend. */
337
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 487 times.
487 if ((tb = dcmp_table[i].decompress(in_buf, in_size, work_buf, out_size)) < 0) {
338 free(temp_buf);
339 return tb;
340 }
341
342 /* Feed this stage's output into the next decompression stage. */
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 487 times.
487 if (tb < 0) {
344 free(temp_buf);
345 return tb;
346 }
347 487 in_size = (uint32_t)tb;
348 487 in_buf = work_buf;
349
350 487 count++;
351 }
352 }
353
354 /* Copy the final stage back if it ended in the temporary buffer. */
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 412 times.
412 if (work_buf != out_buf) {
356 memcpy(out_buf, in_buf, (size_t)tb);
357 }
358
359 412 free(temp_buf);
360
361 412 return tb;
362 }
363
364 /* Return whether every requested compression bit has a local implementation.
365 * Unknown bits are rejected before any codec or archive state is modified. */
366 int
367 1318 libmpq__compression_supported_mask(uint32_t mask)
368 {
369 1318 return (mask & ~(LIBMPQ_COMPRESSION_HUFFMAN | LIBMPQ_COMPRESSION_ZLIB |
370 LIBMPQ_COMPRESSION_PKZIP | LIBMPQ_COMPRESSION_BZIP2 |
371 1318 LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO)) == 0;
372 }
373
374 /* Apply one selected compression backend and replace the current buffer.
375 * Each backend receives the current stage output and returns a newly owned
376 * buffer, allowing the caller to retain the previous stage on fallback. */
377 static int32_t
378 1029 compression_stage(uint8_t **data, size_t *size, uint32_t mask)
379 {
380 uint8_t *out;
381 1029 size_t out_size = *size + (*size / 100) + 1024;
382 z_stream z;
383 bz_stream b;
384 int result;
385
386
2/2
✓ Branch 0 taken 213 times.
✓ Branch 1 taken 816 times.
1029 if (mask == LIBMPQ_COMPRESSION_HUFFMAN)
387 213 out_size = *size * 2 + 64;
388
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 out = malloc(out_size ? out_size : 1);
389
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1029 times.
1029 if (out == NULL)
390 return LIBMPQ_ERROR_MALLOC;
391
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 819 times.
1029 if (mask == LIBMPQ_COMPRESSION_PKZIP) {
392 210 uint8_t *candidate = NULL;
393 210 uint32_t candidate_size = 0;
394 int32_t status;
395 210 free(out);
396 210 status = libmpq__pkzip_compress(*data, (uint32_t)*size, &candidate, &candidate_size);
397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 210 times.
210 if (status < 0)
398 return status;
399 210 free(*data);
400 210 *data = candidate;
401 210 *size = candidate_size;
402 210 return LIBMPQ_SUCCESS;
403
2/2
✓ Branch 0 taken 213 times.
✓ Branch 1 taken 606 times.
819 } else if (mask == LIBMPQ_COMPRESSION_HUFFMAN) {
404 213 struct huffman_tree_s *tree = calloc(1, sizeof(*tree));
405 struct huffman_output_stream_s stream;
406 int32_t encoded;
407
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 213 times.
213 if (tree == NULL) {
408 free(out);
409 return LIBMPQ_ERROR_MALLOC;
410 }
411 213 stream.out_buf = out;
412 213 stream.capacity = (uint32_t)out_size;
413 213 encoded = libmpq__huffman_encode(tree, &stream, *data, (uint32_t)*size);
414 213 free(tree);
415
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 213 times.
213 if (encoded < 0) {
416 free(out);
417 return encoded;
418 }
419 213 free(*data);
420 213 *data = out;
421 213 *size = (size_t)encoded;
422 213 return LIBMPQ_SUCCESS;
423
3/4
✓ Branch 0 taken 603 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 603 times.
606 } else if (mask == LIBMPQ_COMPRESSION_WAVE_MONO || mask == LIBMPQ_COMPRESSION_WAVE_STEREO) {
424 3 uint8_t *candidate = NULL;
425 3 uint32_t candidate_size = 0;
426
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 int32_t status = libmpq__wave_compress(
427 3 *data, (uint32_t)*size, &candidate, &candidate_size,
428 mask == LIBMPQ_COMPRESSION_WAVE_MONO ? 1 : 2
429 );
430 3 free(out);
431
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (status < 0)
432 return status;
433 3 free(*data);
434 3 *data = candidate;
435 3 *size = candidate_size;
436 3 return LIBMPQ_SUCCESS;
437
2/2
✓ Branch 0 taken 358 times.
✓ Branch 1 taken 245 times.
603 } else if (mask == LIBMPQ_COMPRESSION_ZLIB) {
438 358 memset(&z, 0, sizeof(z));
439 358 z.next_in = *data;
440 358 z.avail_in = (uInt)*size;
441 358 z.next_out = out;
442 358 z.avail_out = (uInt)out_size;
443 358 result = deflateInit(&z, Z_DEFAULT_COMPRESSION);
444
1/2
✓ Branch 0 taken 358 times.
✗ Branch 1 not taken.
358 if (result == Z_OK)
445 358 result = deflate(&z, Z_FINISH);
446
1/2
✓ Branch 0 taken 358 times.
✗ Branch 1 not taken.
358 if (result == Z_STREAM_END)
447 358 result = deflateEnd(&z);
448 else
449 deflateEnd(&z);
450
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 358 times.
358 if (result != Z_OK) {
451 free(out);
452 return LIBMPQ_ERROR_UNPACK;
453 }
454 358 out_size = z.total_out;
455 } else {
456 245 memset(&b, 0, sizeof(b));
457 245 b.next_in = (char *)*data;
458 245 b.avail_in = (unsigned int)*size;
459 245 b.next_out = (char *)out;
460 245 b.avail_out = (unsigned int)out_size;
461 245 result = BZ2_bzCompressInit(&b, 9, 0, 30);
462
1/2
✓ Branch 0 taken 245 times.
✗ Branch 1 not taken.
245 if (result == BZ_OK) {
463 do {
464 245 result = BZ2_bzCompress(&b, BZ_FINISH);
465
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 245 times.
245 } while (result == BZ_FINISH_OK);
466 }
467
1/2
✓ Branch 0 taken 245 times.
✗ Branch 1 not taken.
245 if (result == BZ_STREAM_END)
468 245 result = BZ2_bzCompressEnd(&b);
469 else
470 BZ2_bzCompressEnd(&b);
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 245 times.
245 if (result != BZ_OK) {
472 free(out);
473 return LIBMPQ_ERROR_UNPACK;
474 }
475 245 out_size = b.total_out_lo32;
476 }
477 603 free(*data);
478 603 *data = out;
479 603 *size = out_size;
480 603 return LIBMPQ_SUCCESS;
481 }
482
483 /* Apply the selected compression chain and return its actual successful mask.
484 * Stages run in canonical Storm order, and a stage is kept only when it saves
485 * at least two bytes; the emitted mask therefore describes actual reductions. */
486 int32_t
487 614 libmpq__compression_encode_sector(
488 const uint8_t *input, size_t input_size, uint32_t requested, uint8_t **output,
489 size_t *output_size, uint8_t *emitted_mask
490 )
491 {
492 uint8_t *data;
493 size_t size;
494 614 uint32_t masks[] = { LIBMPQ_COMPRESSION_WAVE_MONO, LIBMPQ_COMPRESSION_WAVE_STEREO,
495 LIBMPQ_COMPRESSION_HUFFMAN, LIBMPQ_COMPRESSION_ZLIB,
496 LIBMPQ_COMPRESSION_PKZIP, LIBMPQ_COMPRESSION_BZIP2 };
497 size_t i;
498
499
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 614 times.
614 if (!libmpq__compression_supported_mask(requested))
500 return LIBMPQ_ERROR_FORMAT;
501
1/2
✓ Branch 0 taken 614 times.
✗ Branch 1 not taken.
614 data = malloc(input_size ? input_size : 1);
502
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 614 times.
614 if (data == NULL)
503 return LIBMPQ_ERROR_MALLOC;
504 614 memcpy(data, input, input_size);
505 614 size = input_size;
506 614 *emitted_mask = 0;
507
508 /* Try each requested codec independently so expansion does not poison later stages. */
509
2/2
✓ Branch 0 taken 3684 times.
✓ Branch 1 taken 614 times.
4298 for (i = 0; i < sizeof(masks) / sizeof(masks[0]); i++) {
510
2/2
✓ Branch 0 taken 1029 times.
✓ Branch 1 taken 2655 times.
3684 if (requested & masks[i]) {
511 1029 size_t before = size;
512
1/2
✓ Branch 0 taken 1029 times.
✗ Branch 1 not taken.
1029 uint8_t *saved = malloc(before ? before : 1);
513 int32_t result;
514
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1029 times.
1029 if (saved == NULL) {
515 free(data);
516 return LIBMPQ_ERROR_MALLOC;
517 }
518 1029 memcpy(saved, data, before);
519 1029 result = compression_stage(&data, &size, masks[i]);
520
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1029 times.
1029 if (result < 0) {
521 free(saved);
522 continue;
523 }
524
2/2
✓ Branch 0 taken 495 times.
✓ Branch 1 taken 534 times.
1029 if (size <= before - (before >= 2 ? 2 : before))
525 495 *emitted_mask |= (uint8_t)masks[i];
526 else {
527 534 free(data);
528 534 data = saved;
529 534 size = before;
530 534 saved = NULL;
531 }
532 1029 free(saved);
533 }
534 }
535
3/4
✓ Branch 0 taken 420 times.
✓ Branch 1 taken 194 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 420 times.
614 if (*emitted_mask == 0 || size + 1 >= input_size) {
536 194 *emitted_mask = 0;
537 194 *output = data;
538 194 *output_size = size;
539 } else {
540 420 uint8_t *packed = realloc(data, size + 1);
541
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 420 times.
420 if (packed == NULL) {
542 free(data);
543 return LIBMPQ_ERROR_MALLOC;
544 }
545 420 memmove(packed + 1, packed, size);
546 420 packed[0] = *emitted_mask;
547 420 *output = packed;
548 420 *output_size = size + 1;
549 }
550 614 return LIBMPQ_SUCCESS;
551 }
552
553 /* Decompress one archive block according to its MPQ compression flags.
554 * Raw data is copied directly, while PKWARE and multi-compression payloads
555 * are dispatched to the codec layer with MPQ-compatible expansion semantics. */
556 int32_t
557 943 libmpq__compression_decompress_block(
558 uint8_t *in_buf, uint32_t in_size, uint8_t *out_buf, uint32_t out_size,
559 uint32_t compression_type
560 )
561 {
562 943 int32_t tb = 0;
563
564
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 751 times.
943 if (compression_type == LIBMPQ_FLAG_COMPRESS_NONE) {
565
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 192 times.
192 if (in_size < out_size)
566 return LIBMPQ_ERROR_SIZE;
567 192 memcpy(out_buf, in_buf, out_size);
568 192 tb = out_size;
569
3/4
✓ Branch 0 taken 613 times.
✓ Branch 1 taken 138 times.
✓ Branch 2 taken 613 times.
✗ Branch 3 not taken.
751 } else if (compression_type == LIBMPQ_FLAG_COMPRESS_PKZIP ||
570 compression_type == LIBMPQ_FLAG_COMPRESS_MULTI) {
571
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 613 times.
751 if (compression_type == LIBMPQ_FLAG_COMPRESS_PKZIP) {
572
1/2
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
138 if (in_size >= out_size) {
573 138 memcpy(out_buf, in_buf, out_size);
574 138 tb = out_size;
575 } else if ((tb = libmpq__compression_decompress_pkzip(
576 in_buf, in_size, out_buf, out_size
577 )) < 0) {
578 return tb;
579 }
580
2/2
✓ Branch 0 taken 412 times.
✓ Branch 1 taken 201 times.
613 } else if (in_size < out_size) {
581
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 412 times.
412 if ((tb = libmpq__compression_decompress_multi(in_buf, in_size, out_buf, out_size)) < 0)
582 return tb;
583 } else {
584 201 memcpy(out_buf, in_buf, out_size);
585 201 tb = out_size;
586 }
587 }
588 943 return tb;
589 }
590