| 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 |