GCC Code Coverage Report


Directory: src/
File: src/mpq-stream.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 157 199 78.9%
Functions: 9 9 100.0%
Branches: 103 168 61.3%

Line Branch Exec Source
1 /*
2 * mpq-stream.c -- incremental logical MPQ member stream.
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-stream.h"
21 #include "mpq-archive.h"
22 #include "mpq-reader.h"
23
24 #include <limits.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 struct mpq_stream
30 {
31 mpq_archive_s *archive;
32 uint32_t file_number;
33 uint32_t blocks;
34 uint32_t cached_block;
35 uint32_t *checksums;
36 uint8_t *buffer;
37 uint8_t offsets_acquired;
38 uint8_t block_valid;
39 libmpq__off_t size;
40 libmpq__off_t position;
41 libmpq__off_t buffer_size;
42 libmpq__off_t block_size;
43 };
44
45 static int32_t
46 633 stream_load_block(mpq_stream_s *stream, uint32_t block)
47 {
48 633 libmpq__off_t size = 0;
49 633 libmpq__off_t transferred = 0;
50 633 uint32_t mismatches = 0;
51 int32_t result;
52
53
4/4
✓ Branch 0 taken 534 times.
✓ Branch 1 taken 99 times.
✓ Branch 2 taken 491 times.
✓ Branch 3 taken 43 times.
633 if (stream->block_valid && stream->cached_block == block)
54 491 return 0;
55 142 result = libmpq__block_size_unpacked(stream->archive, stream->file_number, block, &size);
56
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
142 if (result != 0)
57 ✗ return result;
58
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 142 times.
142 if (size < 0 || (uint64_t)size > SIZE_MAX)
59 ✗ return LIBMPQ_ERROR_SIZE;
60
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 43 times.
142 if (size > stream->buffer_size) {
61 99 uint8_t *buffer = realloc(stream->buffer, (size_t)size);
62
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
99 if (buffer == NULL && size != 0)
63 ✗ return LIBMPQ_ERROR_MALLOC;
64 99 stream->buffer = buffer;
65 99 stream->buffer_size = size;
66 }
67 142 result = libmpq__reader_block_read_acquired(
68 stream->archive, stream->file_number, block, stream->buffer, stream->buffer_size,
69
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 136 times.
142 &transferred, stream->checksums != NULL ? stream->checksums + block : NULL, &mismatches,
70 NULL
71 );
72
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 140 times.
142 if (mismatches != 0)
73 2 return LIBMPQ_ERROR_READ;
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 140 times.
140 if (result != 0)
75 ✗ return result;
76 140 stream->cached_block = block;
77 140 stream->block_size = transferred;
78 140 stream->block_valid = 1;
79 140 return 0;
80 }
81
82 /* Take ownership of a private archive clone and initialize one logical stream. */
83 static int32_t
84 101 stream_adopt(mpq_archive_s *archive, uint32_t file_number, const char *name, mpq_stream_s **stream)
85 {
86 101 mpq_stream_s *result = NULL;
87 101 uint32_t *checksums = NULL;
88 int32_t status;
89
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (stream == NULL) {
91 ✗ if (archive != NULL)
92 ✗ (void)libmpq__archive_close(archive);
93 ✗ return LIBMPQ_ERROR_EXIST;
94 }
95 101 *stream = NULL;
96
3/6
✓ Branch 0 taken 101 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 101 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 101 times.
202 if (archive == NULL || archive->write_mode ||
97 101 libmpq__reader_validate_file_number(archive, file_number) != 0) {
98 ✗ if (archive != NULL)
99 ✗ (void)libmpq__archive_close(archive);
100 ✗ return LIBMPQ_ERROR_EXIST;
101 }
102 101 result = calloc(1, sizeof(*result));
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (result == NULL) {
104 ✗ (void)libmpq__archive_close(archive);
105 ✗ return LIBMPQ_ERROR_MALLOC;
106 }
107 101 result->archive = archive;
108 101 result->file_number = file_number;
109 101 status = libmpq__reader_offsets_acquire(result->archive, file_number, name);
110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (status != 0)
111 ✗ goto error;
112 101 result->offsets_acquired = 1;
113 101 status = libmpq__file_size_unpacked(result->archive, file_number, &result->size);
114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (status != 0)
115 ✗ goto error;
116 101 status = libmpq__file_blocks(result->archive, file_number, &result->blocks);
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (status != 0)
118 ✗ goto error;
119
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 1 times.
101 if (result->blocks != 0) {
120 mpq_file_s *file;
121 uint32_t flags;
122 uint32_t start;
123 uint32_t end;
124
125 100 status = libmpq__file_flags(result->archive, file_number, &flags);
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (status != 0)
127 1 goto error;
128
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 85 times.
100 if ((flags & LIBMPQ_FILE_FLAG_ENCRYPTED) != 0) {
129 15 file = result->archive->mpq_file[file_number];
130
3/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 15 times.
15 if (file == NULL || file->packed_offset == NULL || file->packed_offset_count < 2) {
131 ✗ status = LIBMPQ_ERROR_FORMAT;
132 ✗ goto error;
133 }
134 15 start = file->packed_offset[0];
135 15 end = file->packed_offset[1];
136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (end < start) {
137 ✗ status = LIBMPQ_ERROR_FORMAT;
138 ✗ goto error;
139 }
140
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 1 times.
15 if (end - start >= sizeof(uint32_t)) {
141 uint32_t seed;
142
143 14 status = libmpq__reader_get_block_seed(result->archive, file_number, 0, &seed);
144
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (status != 0)
145 1 goto error;
146 }
147 }
148 }
149
1/2
✓ Branch 1 taken 100 times.
✗ Branch 2 not taken.
100 if (libmpq__reader_sector_checksums(result->archive, file_number, &checksums) == 0)
150 100 result->checksums = checksums;
151 100 result->cached_block = UINT32_MAX;
152 100 *stream = result;
153 100 return 0;
154
155 1 error:
156 1 free(checksums);
157
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (result->offsets_acquired)
158 1 (void)libmpq__reader_offsets_release(result->archive, file_number);
159
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (result->archive != NULL)
160 1 (void)libmpq__archive_close(result->archive);
161 1 free(result);
162 1 return status;
163 }
164
165 int32_t
166 7 libmpq__reader_stream_open(mpq_archive_s *archive, uint32_t file_number, mpq_stream_s **stream)
167 {
168 7 mpq_archive_s *clone = NULL;
169 int32_t result;
170
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (stream == NULL)
172 ✗ return LIBMPQ_ERROR_EXIST;
173 7 *stream = NULL;
174
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
14 if (archive == NULL || archive->write_mode ||
175 7 libmpq__reader_validate_file_number(archive, file_number) != 0)
176 ✗ return LIBMPQ_ERROR_EXIST;
177 7 result = libmpq__archive_clone(&clone, archive);
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (result != 0)
179 ✗ return result;
180 7 return stream_adopt(clone, file_number, NULL, stream);
181 }
182
183 int32_t
184 94 libmpq__reader_stream_open_name(mpq_archive_s *archive, const char *filename, mpq_stream_s **stream)
185 {
186 94 mpq_archive_s *clone = NULL;
187 uint32_t file_number;
188 int32_t result;
189
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 if (stream == NULL)
191 ✗ return LIBMPQ_ERROR_EXIST;
192 94 *stream = NULL;
193
3/6
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 94 times.
94 if (archive == NULL || filename == NULL || archive->write_mode)
194 ✗ return LIBMPQ_ERROR_EXIST;
195 94 result = libmpq__archive_clone(&clone, archive);
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 if (result != 0)
197 ✗ return result;
198 94 result = libmpq__file_number(clone, filename, &file_number);
199
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94 if (result != 0) {
200 ✗ (void)libmpq__archive_close(clone);
201 ✗ return result;
202 }
203 94 return stream_adopt(clone, file_number, filename, stream);
204 }
205
206 int32_t
207 606 libmpq__reader_stream_read(
208 mpq_stream_s *stream, uint8_t *buffer, libmpq__off_t size, libmpq__off_t *transferred
209 )
210 {
211 606 libmpq__off_t total = 0;
212
213
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606 if (transferred != NULL)
214 606 *transferred = 0;
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 606 times.
606 if (size < 0 || (uint64_t)size > SIZE_MAX)
216 ✗ return LIBMPQ_ERROR_SIZE;
217
4/6
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 605 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
606 if (stream == NULL || (buffer == NULL && size != 0))
218 ✗ return LIBMPQ_ERROR_EXIST;
219
4/4
✓ Branch 0 taken 640 times.
✓ Branch 1 taken 597 times.
✓ Branch 2 taken 633 times.
✓ Branch 3 taken 7 times.
1237 while (total < size && stream->position < stream->size) {
220 libmpq__off_t base;
221 libmpq__off_t offset;
222 libmpq__off_t available;
223 libmpq__off_t count;
224 uint32_t block;
225 int32_t result;
226
227 633 block =
228
2/2
✓ Branch 0 taken 544 times.
✓ Branch 1 taken 89 times.
633 stream->blocks == 1 ? 0 : (uint32_t)(stream->position / stream->archive->block_size);
229
2/2
✓ Branch 0 taken 544 times.
✓ Branch 1 taken 89 times.
633 base = stream->blocks == 1 ? 0 : (libmpq__off_t)block * stream->archive->block_size;
230 633 result = stream_load_block(stream, block);
231
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 631 times.
633 if (result != 0) {
232
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (transferred != NULL)
233 2 *transferred = total;
234 2 return result;
235 }
236 631 offset = stream->position - base;
237
2/4
✓ Branch 0 taken 631 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 631 times.
631 if (offset < 0 || offset > stream->block_size) {
238 ✗ if (transferred != NULL)
239 ✗ *transferred = total;
240 ✗ return LIBMPQ_ERROR_READ;
241 }
242 631 available = stream->block_size - offset;
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 631 times.
631 if (available == 0) {
244 ✗ if (transferred != NULL)
245 ✗ *transferred = total;
246 ✗ return LIBMPQ_ERROR_READ;
247 }
248 631 count = size - total;
249
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 596 times.
631 if (count > available)
250 35 count = available;
251
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 631 times.
631 if (count > stream->size - stream->position)
252 ✗ count = stream->size - stream->position;
253 631 memcpy(buffer + total, stream->buffer + offset, (size_t)count);
254 631 total += count;
255 631 stream->position += count;
256 }
257
1/2
✓ Branch 0 taken 604 times.
✗ Branch 1 not taken.
604 if (transferred != NULL)
258 604 *transferred = total;
259 604 return 0;
260 }
261
262 int32_t
263 30 libmpq__reader_stream_seek(mpq_stream_s *stream, libmpq__off_t offset, int32_t origin)
264 {
265 libmpq__off_t base;
266 libmpq__off_t position;
267
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if (stream == NULL)
269 ✗ return LIBMPQ_ERROR_EXIST;
270
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 16 times.
30 if (origin == LIBMPQ_SEEK_SET)
271 14 base = 0;
272
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 9 times.
16 else if (origin == LIBMPQ_SEEK_CUR)
273 7 base = stream->position;
274
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 else if (origin == LIBMPQ_SEEK_END)
275 8 base = stream->size;
276 else
277 1 return LIBMPQ_ERROR_SEEK;
278
7/8
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
29 if ((offset > 0 && base > INT64_MAX - offset) || (offset < 0 && base < INT64_MIN - offset))
279 1 return LIBMPQ_ERROR_SEEK;
280 28 position = base + offset;
281
4/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 21 times.
28 if (position < 0 || position > stream->size)
282 7 return LIBMPQ_ERROR_SEEK;
283 21 stream->position = position;
284 21 return 0;
285 }
286
287 int32_t
288 17 libmpq__reader_stream_tell(mpq_stream_s *stream, libmpq__off_t *position)
289 {
290
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 if (position != NULL)
291 17 *position = 0;
292
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
17 if (stream == NULL || position == NULL)
293 ✗ return LIBMPQ_ERROR_EXIST;
294 17 *position = stream->position;
295 17 return 0;
296 }
297
298 int32_t
299 36 libmpq__reader_stream_size(mpq_stream_s *stream, libmpq__off_t *size)
300 {
301
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 if (size != NULL)
302 36 *size = 0;
303
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
36 if (stream == NULL || size == NULL)
304 ✗ return LIBMPQ_ERROR_EXIST;
305 36 *size = stream->size;
306 36 return 0;
307 }
308
309 int32_t
310 100 libmpq__reader_stream_close(mpq_stream_s *stream)
311 {
312 100 int32_t result = 0;
313
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (stream == NULL)
315 ✗ return LIBMPQ_ERROR_EXIST;
316 100 free(stream->buffer);
317 100 free(stream->checksums);
318
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 if (stream->offsets_acquired) {
319 100 int32_t cleanup = libmpq__reader_offsets_release(stream->archive, stream->file_number);
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (cleanup != 0)
321 ✗ result = cleanup;
322 }
323
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 if (stream->archive != NULL) {
324 100 int32_t cleanup = libmpq__archive_close(stream->archive);
325
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 100 times.
100 if (result == 0 && cleanup != 0)
326 ✗ result = cleanup;
327 }
328 100 free(stream);
329 100 return result;
330 }
331