| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * mpq-writer.c -- seekable MPQ archive creation. | ||
| 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 | #ifdef HAVE_CONFIG_H | ||
| 20 | #include "config.h" | ||
| 21 | #endif | ||
| 22 | |||
| 23 | #include "mpq-archive.h" | ||
| 24 | #include "mpq-attributes.h" | ||
| 25 | #include "mpq-block.h" | ||
| 26 | #include "mpq-compression.h" | ||
| 27 | #include "mpq-crypto.h" | ||
| 28 | #include "mpq-endian.h" | ||
| 29 | #include "mpq-file.h" | ||
| 30 | #include "mpq-mpqe.h" | ||
| 31 | #include "mpq-patch-reader.h" | ||
| 32 | #include "mpq-pkware.h" | ||
| 33 | #include "mpq-signature.h" | ||
| 34 | #include "mpq-wave.h" | ||
| 35 | #include "mpq-writer.h" | ||
| 36 | #include <libmpq/mpq.h> | ||
| 37 | |||
| 38 | #include <stdio.h> | ||
| 39 | #include <stdlib.h> | ||
| 40 | #include <string.h> | ||
| 41 | #include <zlib.h> | ||
| 42 | |||
| 43 | /* Resolve the private writer policy from the existing archive-creation flags. */ | ||
| 44 | static libmpq_compression_policy_t | ||
| 45 | 11745 | compression_policy(const mpq_archive_s *archive) | |
| 46 | { | ||
| 47 | 11745 | return (archive->write_flags & LIBMPQ_ARCHIVE_CREATE_COMPRESSION_EXTENDED) != 0 | |
| 48 | ? LIBMPQ_COMPRESSION_POLICY_EXTENDED | ||
| 49 | 11745 | : LIBMPQ_COMPRESSION_POLICY_STANDARD; | |
| 50 | } | ||
| 51 | |||
| 52 | /* | ||
| 53 | * Return the smallest supported power-of-two table capacity at or above value. | ||
| 54 | * MPQ hash tables use power-of-two probing, so this helper provides the next | ||
| 55 | * legal capacity without exceeding the format's 32-bit range. | ||
| 56 | */ | ||
| 57 | static uint32_t | ||
| 58 | 618 | next_power_two(uint32_t value) | |
| 59 | { | ||
| 60 | 618 | uint32_t result = 4; | |
| 61 |
3/4✓ Branch 0 taken 1233 times.
✓ Branch 1 taken 618 times.
✓ Branch 2 taken 1233 times.
✗ Branch 3 not taken.
|
1851 | while (result < value && result < 0x80000000u) |
| 62 | 1233 | result <<= 1; | |
| 63 | 618 | return result; | |
| 64 | } | ||
| 65 | |||
| 66 | /* | ||
| 67 | * Write one byte range at an absolute archive offset. | ||
| 68 | * Seeking and writing are treated as one archive operation so short writes | ||
| 69 | * and positioning failures are converted to the library's write error. | ||
| 70 | */ | ||
| 71 | static int32_t | ||
| 72 | 3842 | write_at(FILE *fp, uint64_t offset, const void *data, size_t size) | |
| 73 | { | ||
| 74 |
3/6✓ Branch 1 taken 3842 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3842 times.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3842 times.
|
3842 | if (libmpq__file_seek(fp, offset, SEEK_SET) < 0 || (size && fwrite(data, 1, size, fp) != size)) |
| 75 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 76 | 3842 | return 0; | |
| 77 | } | ||
| 78 | |||
| 79 | /* | ||
| 80 | * Calculate the MPQ file encryption key from a normalized file name. | ||
| 81 | * Slash normalization matches MPQ's Windows-oriented name hashing while the | ||
| 82 | * temporary normalized string remains private to this calculation. | ||
| 83 | */ | ||
| 84 | static uint32_t | ||
| 85 | 2436 | file_key(const char *name) | |
| 86 | { | ||
| 87 | 2436 | char *normalized = NULL; | |
| 88 | size_t i; | ||
| 89 | 2436 | size_t length = strlen(name); | |
| 90 | uint32_t key; | ||
| 91 | |||
| 92 | 2436 | normalized = malloc(length + 1); | |
| 93 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2436 times.
|
2436 | if (normalized == NULL) |
| 94 | ✗ | return 0; | |
| 95 |
2/2✓ Branch 0 taken 26343 times.
✓ Branch 1 taken 2436 times.
|
28779 | for (i = 0; i < length; i++) |
| 96 |
1/2✓ Branch 0 taken 26343 times.
✗ Branch 1 not taken.
|
26343 | normalized[i] = name[i] == '/' ? '\\' : name[i]; |
| 97 | 2436 | normalized[length] = 0; | |
| 98 | 2436 | key = libmpq__crypto_hash_string(normalized, 0x300); | |
| 99 | 2436 | free(normalized); | |
| 100 | 2436 | return key; | |
| 101 | } | ||
| 102 | |||
| 103 | /* | ||
| 104 | * Compress, encrypt, and append the writer's currently buffered sector. | ||
| 105 | * Sector zero and later sectors may use different masks; the resulting offset | ||
| 106 | * is recorded relative to the payload for the table written during finish. | ||
| 107 | */ | ||
| 108 | static int32_t | ||
| 109 | 5931 | stream_flush_sector(mpq_writer_s *writer) | |
| 110 | { | ||
| 111 | 5931 | mpq_archive_s *archive = writer->archive; | |
| 112 | 5931 | uint8_t *packed = NULL; | |
| 113 | 5931 | size_t packed_size = writer->data_size; | |
| 114 | 5931 | uint8_t emitted = 0; | |
| 115 | 11862 | uint32_t requested = writer->sector_index == 0 ? writer->options.compression_first | |
| 116 |
2/2✓ Branch 0 taken 2954 times.
✓ Branch 1 taken 2977 times.
|
5931 | : writer->options.compression_next; |
| 117 | int32_t result; | ||
| 118 | |||
| 119 | /* Reject an extra flush before touching the archive stream. */ | ||
| 120 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5931 times.
|
5931 | if (writer->sector_index >= writer->block_count) |
| 121 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 122 |
2/2✓ Branch 0 taken 4646 times.
✓ Branch 1 taken 1285 times.
|
5931 | if ((writer->options.flags & LIBMPQ_FILE_FLAG_COMPRESS) != 0) |
| 123 | 4646 | result = libmpq__compression_encode_sector( | |
| 124 | 4646 | writer->data, writer->data_size, requested, archive->mpq_header.version, | |
| 125 | compression_policy(archive), &packed, &packed_size, &emitted | ||
| 126 | ); | ||
| 127 |
2/2✓ Branch 0 taken 259 times.
✓ Branch 1 taken 1026 times.
|
1285 | else if ((writer->options.flags & LIBMPQ_FILE_FLAG_IMPLODE) != 0) { |
| 128 | 259 | uint32_t packed32 = 0; | |
| 129 | 259 | result = libmpq__pkzip_compress(writer->data, writer->data_size, &packed, &packed32); | |
| 130 | 259 | packed_size = packed32; | |
| 131 |
3/4✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✓ Branch 3 taken 208 times.
|
259 | if (result == 0 && packed_size >= writer->data_size) { |
| 132 | 51 | free(packed); | |
| 133 |
2/2✓ Branch 0 taken 45 times.
✓ Branch 1 taken 6 times.
|
51 | packed = malloc(writer->data_size ? writer->data_size : 1); |
| 134 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 51 times.
|
51 | if (packed == NULL) |
| 135 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 136 | else { | ||
| 137 | 51 | memcpy(packed, writer->data, writer->data_size); | |
| 138 | 51 | packed_size = writer->data_size; | |
| 139 | } | ||
| 140 | } | ||
| 141 | } else { | ||
| 142 |
2/2✓ Branch 0 taken 950 times.
✓ Branch 1 taken 76 times.
|
1026 | packed = malloc(packed_size ? packed_size : 1); |
| 143 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1026 times.
|
1026 | result = packed == NULL ? LIBMPQ_ERROR_MALLOC : 0; |
| 144 |
1/2✓ Branch 0 taken 1026 times.
✗ Branch 1 not taken.
|
1026 | if (result == 0) |
| 145 | 1026 | memcpy(packed, writer->data, packed_size); | |
| 146 | } | ||
| 147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5931 times.
|
5931 | if (result < 0) { |
| 148 | ✗ | free(packed); | |
| 149 | ✗ | return result; | |
| 150 | } | ||
| 151 | |||
| 152 | /* Sector checksums cover packed bytes before payload encryption. */ | ||
| 153 |
2/2✓ Branch 0 taken 196 times.
✓ Branch 1 taken 5735 times.
|
5931 | if (writer->checksums != NULL) |
| 154 | 196 | writer->checksums[writer->sector_index] = (uint32_t)adler32(0, packed, (uInt)packed_size); | |
| 155 | |||
| 156 | /* Payload encryption uses the file seed advanced by the sector number. */ | ||
| 157 |
2/2✓ Branch 0 taken 1917 times.
✓ Branch 1 taken 4014 times.
|
5931 | if (writer->options.flags & LIBMPQ_FILE_FLAG_ENCRYPTED) |
| 158 | 1917 | libmpq__crypto_encrypt_block( | |
| 159 | 1917 | packed, (uint32_t)packed_size, file_key(writer->name) + writer->sector_index | |
| 160 | ); | ||
| 161 |
2/2✓ Branch 0 taken 4514 times.
✓ Branch 1 taken 1417 times.
|
5931 | if (writer->offsets) { |
| 162 | 4514 | libmpq__off_t position = libmpq__file_tell(archive->fp); | |
| 163 | |||
| 164 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4514 times.
|
4514 | if (position < 0) { |
| 165 | ✗ | free(packed); | |
| 166 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 167 | } | ||
| 168 | 4514 | writer->offsets[writer->sector_index] = | |
| 169 | 4514 | (uint32_t)((uint64_t)position - writer->payload_offset); | |
| 170 | } | ||
| 171 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5931 times.
|
5931 | if (fwrite(packed, 1, packed_size, archive->fp) != packed_size) { |
| 172 | ✗ | free(packed); | |
| 173 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 174 | } | ||
| 175 | 5931 | writer->packed_total += packed_size; | |
| 176 |
2/2✓ Branch 0 taken 2954 times.
✓ Branch 1 taken 2977 times.
|
5931 | if (writer->sector_index == 0 && |
| 177 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2949 times.
|
2954 | (writer->options.compression_next & |
| 178 | (LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO))) { | ||
| 179 | libmpq_wave_info_s wave; | ||
| 180 | |||
| 181 | /* Validate the complete WAVE geometry before preserving lossy output. */ | ||
| 182 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (libmpq__wave_probe_pcm16_prefix( |
| 183 | 5 | writer->data, writer->data_size, (uint64_t)writer->expected, &wave | |
| 184 | ) == 0) { | ||
| 185 | 4 | writer->options.compression_next &= | |
| 186 | ~(LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO); | ||
| 187 | 4 | writer->options.compression_next |= | |
| 188 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | wave.channels == 1 ? LIBMPQ_COMPRESSION_WAVE_MONO : LIBMPQ_COMPRESSION_WAVE_STEREO; |
| 189 | } else { | ||
| 190 | 1 | free(packed); | |
| 191 | 1 | return LIBMPQ_ERROR_FORMAT; | |
| 192 | } | ||
| 193 | } | ||
| 194 | 5930 | writer->sector_index++; | |
| 195 | 5930 | writer->data_size = 0; | |
| 196 | 5930 | free(packed); | |
| 197 | 5930 | return 0; | |
| 198 | } | ||
| 199 | |||
| 200 | /* | ||
| 201 | * Append the optional checksum table without file encryption. Like sector data, | ||
| 202 | * zlib framing is retained only when it reduces the serialized table size. | ||
| 203 | */ | ||
| 204 | static int32_t | ||
| 205 | 42 | stream_write_checksums(mpq_writer_s *writer) | |
| 206 | { | ||
| 207 | 42 | size_t size = (size_t)writer->block_count * sizeof(uint32_t); | |
| 208 | 42 | uint8_t *raw = malloc(size); | |
| 209 | 42 | uint8_t *packed = NULL; | |
| 210 | 42 | size_t packed_size = 0; | |
| 211 | 42 | uint8_t emitted = 0; | |
| 212 | uint32_t i; | ||
| 213 | int32_t result; | ||
| 214 | |||
| 215 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (raw == NULL) |
| 216 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 217 |
2/2✓ Branch 0 taken 196 times.
✓ Branch 1 taken 42 times.
|
238 | for (i = 0; i < writer->block_count; ++i) |
| 218 | 196 | libmpq__store_le32(raw + i * 4, writer->checksums[i]); | |
| 219 | 42 | result = libmpq__compression_encode_sector( | |
| 220 | 42 | raw, size, LIBMPQ_COMPRESSION_ZLIB, writer->archive->mpq_header.version, | |
| 221 | LIBMPQ_COMPRESSION_POLICY_STANDARD, &packed, &packed_size, &emitted | ||
| 222 | ); | ||
| 223 | 42 | free(raw); | |
| 224 |
1/2✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
|
42 | if (result == 0) { |
| 225 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (packed_size > UINT32_MAX - writer->offsets[writer->block_count]) |
| 226 | ✗ | result = LIBMPQ_ERROR_SIZE; | |
| 227 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
|
42 | else if (fwrite(packed, 1, packed_size, writer->archive->fp) != packed_size) |
| 228 | ✗ | result = LIBMPQ_ERROR_WRITE; | |
| 229 | else { | ||
| 230 | 42 | writer->packed_total += packed_size; | |
| 231 | 42 | writer->offsets[writer->block_count + 1] = | |
| 232 | 42 | writer->offsets[writer->block_count] + (uint32_t)packed_size; | |
| 233 | } | ||
| 234 | } | ||
| 235 | 42 | free(packed); | |
| 236 | 42 | return result; | |
| 237 | } | ||
| 238 | |||
| 239 | /* | ||
| 240 | * Finish the streamed file by writing its offset table and archive metadata. | ||
| 241 | * The serialized table is placed before packed sectors, encrypted separately, | ||
| 242 | * and then the completed file is inserted into the block and hash tables. | ||
| 243 | */ | ||
| 244 | static int32_t | ||
| 245 | 2954 | stream_finish(mpq_writer_s *writer) | |
| 246 | { | ||
| 247 | 2954 | mpq_archive_s *archive = writer->archive; | |
| 248 | uint32_t index; | ||
| 249 | uint32_t slot; | ||
| 250 | uint32_t hash1; | ||
| 251 | uint32_t hash2; | ||
| 252 | uint32_t hash3; | ||
| 253 | uint32_t i; | ||
| 254 | uint64_t total; | ||
| 255 | uint64_t block_offset; | ||
| 256 | size_t table_size; | ||
| 257 | uint8_t *table; | ||
| 258 | int32_t result; | ||
| 259 | |||
| 260 | /* A file is publishable only after its declared byte and sector counts match. */ | ||
| 261 |
3/4✓ Branch 0 taken 2952 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2952 times.
|
2954 | if (writer->written != writer->expected || writer->sector_index != writer->block_count) |
| 262 | 2 | return LIBMPQ_ERROR_SIZE; | |
| 263 |
2/2✓ Branch 0 taken 1738 times.
✓ Branch 1 taken 1214 times.
|
2952 | if (writer->offsets) { |
| 264 | 1738 | uint32_t entries = writer->block_count + 1 + (writer->checksums != NULL); | |
| 265 | 1738 | table_size = (size_t)entries * 4; | |
| 266 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1738 times.
|
1738 | if (writer->packed_total > UINT32_MAX - table_size) |
| 267 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 268 | 1738 | writer->offsets[writer->block_count] = | |
| 269 | 1738 | (uint32_t)writer->packed_total + (uint32_t)table_size; | |
| 270 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 1696 times.
|
1738 | if (writer->checksums != NULL) { |
| 271 | 42 | result = stream_write_checksums(writer); | |
| 272 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
|
42 | if (result < 0) |
| 273 | ✗ | return result; | |
| 274 | } | ||
| 275 | 1738 | table = malloc(table_size); | |
| 276 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1738 times.
|
1738 | if (table == NULL) |
| 277 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 278 |
2/2✓ Branch 0 taken 6293 times.
✓ Branch 1 taken 1738 times.
|
8031 | for (i = 0; i < entries; i++) |
| 279 | 6293 | libmpq__store_le32(table + i * 4, writer->offsets[i]); | |
| 280 |
2/2✓ Branch 0 taken 519 times.
✓ Branch 1 taken 1219 times.
|
1738 | if (writer->options.flags & LIBMPQ_FILE_FLAG_ENCRYPTED) |
| 281 | 519 | libmpq__crypto_encrypt_block(table, (uint32_t)table_size, file_key(writer->name) - 1); | |
| 282 | 1738 | result = write_at(archive->fp, writer->payload_offset, table, table_size); | |
| 283 | 1738 | free(table); | |
| 284 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1738 times.
|
1738 | if (result < 0) |
| 285 | ✗ | return result; | |
| 286 | 1738 | total = writer->packed_total + table_size; | |
| 287 | } else { | ||
| 288 | 1214 | total = writer->packed_total; | |
| 289 | } | ||
| 290 | |||
| 291 | /* Restore the append position after rewriting the table at the file start. */ | ||
| 292 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2952 times.
|
2952 | if (libmpq__file_seek(archive->fp, (writer->payload_offset + total), SEEK_SET) < 0) |
| 293 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 294 |
1/2✓ Branch 0 taken 2952 times.
✗ Branch 1 not taken.
|
2952 | if (writer->payload_offset < writer->prefix_size || |
| 295 |
3/4✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2930 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
2952 | (writer->prefix_size != 0 && total > UINT32_MAX - writer->prefix_size)) |
| 296 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 297 | 2952 | block_offset = writer->payload_offset - writer->prefix_size; | |
| 298 |
3/6✓ Branch 0 taken 2952 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2952 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2952 times.
|
2952 | if (writer->payload_offset > UINT32_MAX || total > UINT32_MAX || writer->expected > UINT32_MAX) |
| 299 | ✗ | return archive->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_ONE | |
| 300 | ? LIBMPQ_ERROR_SIZE | ||
| 301 | ✗ | : (writer->payload_offset >> 32 > UINT16_MAX ? LIBMPQ_ERROR_SIZE : 0); | |
| 302 | |||
| 303 | /* Reserve the next block slot only after payload and offset sizes validate. */ | ||
| 304 | 2952 | index = archive->write_next_block; | |
| 305 | 2952 | libmpq__file_hash(writer->name, &hash1, &hash2, &hash3); | |
| 306 | 2952 | archive->mpq_block[index].offset = (uint32_t)block_offset; | |
| 307 | 2952 | archive->mpq_block[index].packed_size = (uint32_t)total + writer->prefix_size; | |
| 308 | 2952 | archive->mpq_block[index].unpacked_size = | |
| 309 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2930 times.
|
2952 | writer->patch_file ? writer->patch_result_size : (uint32_t)writer->expected; |
| 310 | 5904 | archive->mpq_block[index].flags = LIBMPQ_FLAG_EXISTS | writer->options.flags | | |
| 311 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2930 times.
|
2952 | (writer->patch_file ? LIBMPQ_FILE_FLAG_PATCH_FILE : 0); |
| 312 |
2/2✓ Branch 0 taken 2014 times.
✓ Branch 1 taken 938 times.
|
2952 | if (writer->options.flags & LIBMPQ_FILE_FLAG_COMPRESS) |
| 313 | 2014 | archive->mpq_block[index].flags |= LIBMPQ_FLAG_COMPRESS_MULTI; | |
| 314 | 2952 | archive->mpq_block_ex[index].offset_high = (uint16_t)(block_offset >> 32); | |
| 315 | 2952 | archive->write_names[index] = writer->name; | |
| 316 | 2952 | archive->write_locales[index] = writer->options.locale; | |
| 317 | 2952 | archive->write_platforms[index] = writer->options.platform; | |
| 318 | 2952 | writer->name = NULL; | |
| 319 | 2952 | archive->write_next_block++; | |
| 320 | 2952 | archive->files = archive->write_next_block; | |
| 321 |
1/2✓ Branch 0 taken 3225 times.
✗ Branch 1 not taken.
|
3225 | for (slot = 0; slot < archive->write_hash_capacity; slot++) { |
| 322 | 3225 | uint32_t pos = (hash1 + slot) & (archive->write_hash_capacity - 1); | |
| 323 |
2/2✓ Branch 0 taken 2952 times.
✓ Branch 1 taken 273 times.
|
3225 | if (archive->mpq_hash[pos].block_table_index == LIBMPQ_HASH_FREE) { |
| 324 | 2952 | archive->mpq_hash[pos].hash_a = hash2; | |
| 325 | 2952 | archive->mpq_hash[pos].hash_b = hash3; | |
| 326 | 2952 | archive->mpq_hash[pos].locale = writer->options.locale; | |
| 327 | 2952 | archive->mpq_hash[pos].platform = writer->options.platform; | |
| 328 | 2952 | archive->mpq_hash[pos].block_table_index = index; | |
| 329 |
2/2✓ Branch 0 taken 904 times.
✓ Branch 1 taken 2048 times.
|
2952 | if (archive->write_attributes != NULL) { |
| 330 |
2/2✓ Branch 0 taken 792 times.
✓ Branch 1 taken 112 times.
|
904 | if (writer->attributes.flags & LIBMPQ_ATTRIBUTE_MD5) |
| 331 | 792 | libmpq__md5_final(&writer->md5, writer->attributes.md5); | |
| 332 | 904 | archive->write_attributes[index] = writer->attributes; | |
| 333 | } | ||
| 334 | 2952 | return 0; | |
| 335 | } | ||
| 336 | } | ||
| 337 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 338 | } | ||
| 339 | |||
| 340 | /* | ||
| 341 | * Serialize the completed hash, block, extended tables, and archive header. | ||
| 342 | * Finalization optionally creates the listfile, encrypts metadata with the | ||
| 343 | * fixed MPQ table keys, writes v2 high offsets, and commits the header last. | ||
| 344 | */ | ||
| 345 | static int32_t | ||
| 346 | 616 | finalize_archive(mpq_archive_s *a) | |
| 347 | { | ||
| 348 | uint8_t *raw; | ||
| 349 | uint32_t i; | ||
| 350 | size_t bytes; | ||
| 351 | uint64_t end; | ||
| 352 | uint8_t header[LIBMPQ_HEADER_WIRE_SIZE + LIBMPQ_HEADER_EX_WIRE_SIZE]; | ||
| 353 | mpq_header_s wire_header; | ||
| 354 | mpq_header_ex_s wire_ex; | ||
| 355 | int32_t result; | ||
| 356 | |||
| 357 | /* An unfinished streamed file would leave archive tables inconsistent. */ | ||
| 358 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 615 times.
|
616 | if (a->write_current) |
| 359 | 1 | return LIBMPQ_ERROR_SIZE; | |
| 360 | 615 | a->write_internal = 1; | |
| 361 |
3/4✓ Branch 1 taken 349 times.
✓ Branch 2 taken 266 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 349 times.
|
615 | if (libmpq__attributes_write_flags(a) != 0 && a->write_attributes == NULL) { |
| 362 | ✗ | a->write_attributes = calloc(a->write_capacity, sizeof(*a->write_attributes)); | |
| 363 | ✗ | if (a->write_attributes == NULL) | |
| 364 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 365 | } | ||
| 366 |
2/2✓ Branch 0 taken 133 times.
✓ Branch 1 taken 482 times.
|
615 | if ((a->write_flags & LIBMPQ_ARCHIVE_CREATE_LISTFILE) != 0) { |
| 367 |
2/2✓ Branch 1 taken 92 times.
✓ Branch 2 taken 41 times.
|
133 | size_t total = 1 + (libmpq__attributes_write_flags(a) ? sizeof(LIBMPQ_ATTRIBUTES_NAME) : 0); |
| 368 | uint8_t *list; | ||
| 369 |
2/2✓ Branch 0 taken 863 times.
✓ Branch 1 taken 133 times.
|
996 | for (i = 0; i < a->write_next_block; i++) |
| 370 | 863 | total += strlen(a->write_names[i]) + 1; | |
| 371 | 133 | list = malloc(total); | |
| 372 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 133 times.
|
133 | if (!list) |
| 373 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 374 | 133 | total = 0; | |
| 375 |
2/2✓ Branch 0 taken 863 times.
✓ Branch 1 taken 133 times.
|
996 | for (i = 0; i < a->write_next_block; i++) { |
| 376 | 863 | size_t n = strlen(a->write_names[i]); | |
| 377 | 863 | memcpy(list + total, a->write_names[i], n); | |
| 378 | 863 | total += n; | |
| 379 | 863 | list[total++] = '\n'; | |
| 380 | } | ||
| 381 |
2/2✓ Branch 1 taken 92 times.
✓ Branch 2 taken 41 times.
|
133 | if (libmpq__attributes_write_flags(a)) { |
| 382 | 92 | memcpy(list + total, LIBMPQ_ATTRIBUTES_NAME "\n", sizeof(LIBMPQ_ATTRIBUTES_NAME)); | |
| 383 | 92 | total += sizeof(LIBMPQ_ATTRIBUTES_NAME); | |
| 384 | } | ||
| 385 | { | ||
| 386 | 133 | mpq_file_options_s o = { LIBMPQ_FILE_FLAG_SINGLE, 0, 0, 0, 0 }; | |
| 387 | int32_t r = | ||
| 388 | 133 | libmpq__writer_file_add(a, LIBMPQ_LISTFILE_NAME, list, (libmpq__off_t)total, &o); | |
| 389 | 133 | free(list); | |
| 390 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 133 times.
|
133 | if (r < 0) |
| 391 | ✗ | return r; | |
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 |
2/2✓ Branch 1 taken 349 times.
✓ Branch 2 taken 266 times.
|
615 | if (libmpq__attributes_write_flags(a) != 0) { |
| 396 | 349 | mpq_file_options_s options = { LIBMPQ_FILE_FLAG_COMPRESS, LIBMPQ_COMPRESSION_ZLIB, | |
| 397 | LIBMPQ_COMPRESSION_ZLIB, 0, 0 }; | ||
| 398 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 324 times.
|
349 | if (a->write_patch_mode && |
| 399 |
1/2✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
|
25 | (libmpq__attributes_write_flags(a) & LIBMPQ_ATTRIBUTE_PATCH_BIT) != 0) { |
| 400 |
2/2✓ Branch 0 taken 61 times.
✓ Branch 1 taken 25 times.
|
86 | for (i = 0; i < a->write_next_block; i++) |
| 401 | 61 | a->write_attributes[i].patch_bit = | |
| 402 | 61 | (a->mpq_block[i].flags & LIBMPQ_FILE_FLAG_PATCH_FILE) != 0; | |
| 403 | } | ||
| 404 | int32_t result = | ||
| 405 | 349 | (a->write_patch_mode ? libmpq__attributes_serialize_patch_bits | |
| 406 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 324 times.
|
349 | : libmpq__attributes_serialize)( |
| 407 | 349 | a->write_attributes, a->write_capacity, a->write_next_block, | |
| 408 | libmpq__attributes_write_flags(a), &raw, &bytes | ||
| 409 | ); | ||
| 410 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 349 times.
|
349 | if (result != 0) |
| 411 | ✗ | return result; | |
| 412 | result = | ||
| 413 | 349 | libmpq__writer_file_add(a, LIBMPQ_ATTRIBUTES_NAME, raw, (libmpq__off_t)bytes, &options); | |
| 414 | 349 | free(raw); | |
| 415 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 349 times.
|
349 | if (result != 0) |
| 416 | ✗ | return result; | |
| 417 | } | ||
| 418 | |||
| 419 | /* Serialize and encrypt the hash table before writing the block metadata. */ | ||
| 420 | 615 | bytes = (size_t)a->write_hash_capacity * LIBMPQ_HASH_ENTRY_WIRE_SIZE; | |
| 421 | 615 | raw = malloc(bytes); | |
| 422 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (!raw) |
| 423 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 424 | 615 | result = libmpq__hash_table_encode(a->mpq_hash, a->write_hash_capacity, raw, bytes); | |
| 425 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (result < 0) { |
| 426 | ✗ | free(raw); | |
| 427 | ✗ | return result; | |
| 428 | } | ||
| 429 | 615 | libmpq__crypto_encrypt_block( | |
| 430 | raw, (uint32_t)bytes, libmpq__crypto_hash_string("(hash table)", 0x300) | ||
| 431 | ); | ||
| 432 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 615 times.
|
615 | if (write_at(a->fp, a->mpq_header.hash_table_offset, raw, bytes) < 0) { |
| 433 | ✗ | free(raw); | |
| 434 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 435 | } | ||
| 436 | 615 | free(raw); | |
| 437 | |||
| 438 | /* Serialize the fixed-capacity block table before its existing encryption. */ | ||
| 439 | 615 | bytes = (size_t)a->write_capacity * LIBMPQ_BLOCK_ENTRY_WIRE_SIZE; | |
| 440 | 615 | raw = malloc(bytes); | |
| 441 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (!raw) |
| 442 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 443 | 615 | result = libmpq__block_table_encode(a->mpq_block, a->write_capacity, raw, bytes); | |
| 444 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (result < 0) { |
| 445 | ✗ | free(raw); | |
| 446 | ✗ | return result; | |
| 447 | } | ||
| 448 | 615 | libmpq__crypto_encrypt_block( | |
| 449 | raw, (uint32_t)bytes, libmpq__crypto_hash_string("(block table)", 0x300) | ||
| 450 | ); | ||
| 451 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 615 times.
|
615 | if (write_at(a->fp, a->mpq_header.block_table_offset, raw, bytes) < 0) { |
| 452 | ✗ | free(raw); | |
| 453 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 454 | } | ||
| 455 | 615 | free(raw); | |
| 456 |
2/2✓ Branch 0 taken 237 times.
✓ Branch 1 taken 378 times.
|
615 | if (a->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 457 | 237 | bytes = (size_t)a->write_capacity * LIBMPQ_BLOCK_EX_ENTRY_WIRE_SIZE; | |
| 458 | 237 | raw = malloc(bytes); | |
| 459 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 237 times.
|
237 | if (!raw) |
| 460 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 461 | 237 | result = libmpq__block_ex_table_encode(a->mpq_block_ex, a->write_capacity, raw, bytes); | |
| 462 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 237 times.
|
237 | if (result < 0) { |
| 463 | ✗ | free(raw); | |
| 464 | ✗ | return result; | |
| 465 | } | ||
| 466 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 237 times.
|
237 | if (write_at(a->fp, a->mpq_header_ex.extended_offset, raw, bytes) < 0) { |
| 467 | ✗ | free(raw); | |
| 468 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 469 | } | ||
| 470 | 237 | free(raw); | |
| 471 | } | ||
| 472 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 615 times.
|
615 | if (libmpq__file_seek(a->fp, 0, SEEK_END) < 0) |
| 473 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 474 | 615 | end = (uint64_t)libmpq__file_tell(a->fp); | |
| 475 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (end > INT64_MAX) |
| 476 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 477 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (end > UINT32_MAX) |
| 478 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 479 | 615 | memset(header, 0, sizeof(header)); | |
| 480 | 615 | wire_header = a->mpq_header; | |
| 481 | 615 | wire_header.mpq_magic = LIBMPQ_HEADER; | |
| 482 | 615 | wire_header.archive_size = (uint32_t)end; | |
| 483 | 615 | result = libmpq__header_encode(&wire_header, header, sizeof(header)); | |
| 484 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (result < 0) |
| 485 | ✗ | return result; | |
| 486 |
2/2✓ Branch 0 taken 237 times.
✓ Branch 1 taken 378 times.
|
615 | if (a->mpq_header.version == LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 487 | 237 | wire_ex = a->mpq_header_ex; | |
| 488 | 237 | wire_ex.hash_table_offset_high = | |
| 489 | 237 | (uint16_t)(((uint64_t)a->mpq_header.hash_table_offset) >> 32); | |
| 490 | 237 | wire_ex.block_table_offset_high = | |
| 491 | 237 | (uint16_t)(((uint64_t)a->mpq_header.block_table_offset) >> 32); | |
| 492 | 237 | result = libmpq__header_ex_encode( | |
| 493 | &wire_ex, header + LIBMPQ_HEADER_WIRE_SIZE, LIBMPQ_HEADER_EX_WIRE_SIZE | ||
| 494 | ); | ||
| 495 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 237 times.
|
237 | if (result < 0) |
| 496 | ✗ | return result; | |
| 497 | } | ||
| 498 |
2/4✓ Branch 1 taken 615 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 615 times.
|
615 | if (write_at(a->fp, 0, header, a->mpq_header.header_size) < 0 || fflush(a->fp) != 0) |
| 499 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 500 | { | ||
| 501 | 615 | int32_t result = libmpq__signature_finish(a, end); | |
| 502 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 615 times.
|
615 | if (result != 0) |
| 503 | ✗ | return result; | |
| 504 | } | ||
| 505 |
2/2✓ Branch 0 taken 532 times.
✓ Branch 1 taken 83 times.
|
615 | if (!a->write_mpqe) |
| 506 | 532 | a->write_finalized = 1; | |
| 507 | 615 | return 0; | |
| 508 | } | ||
| 509 | |||
| 510 | /* Transform the finalized raw archive with bounded sequential MPQE I/O. */ | ||
| 511 | int32_t | ||
| 512 | 87 | libmpq__writer_mpqe_transform_file( | |
| 513 | FILE *input, FILE *output, const uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE] | ||
| 514 | ) | ||
| 515 | { | ||
| 516 | uint8_t chunk[LIBMPQ_MPQE_CHUNK_SIZE]; | ||
| 517 | 87 | uint64_t offset = 0; | |
| 518 | size_t read; | ||
| 519 | |||
| 520 |
3/6✓ Branch 0 taken 87 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 87 times.
|
87 | if (input == NULL || output == NULL || key == NULL) |
| 521 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 522 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
|
87 | if (fflush(input) != 0) |
| 523 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 524 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 87 times.
|
87 | if (libmpq__file_seek(input, 0, SEEK_SET) < 0) |
| 525 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 526 |
2/2✓ Branch 1 taken 13498 times.
✓ Branch 2 taken 87 times.
|
13585 | while ((read = fread(chunk, 1, sizeof(chunk), input)) != 0) { |
| 527 |
2/2✓ Branch 0 taken 86 times.
✓ Branch 1 taken 13412 times.
|
13498 | if (read < sizeof(chunk)) |
| 528 | 86 | memset(chunk + read, 0, sizeof(chunk) - read); | |
| 529 | 13498 | libmpq__mpqe_transform_chunk(chunk, key, offset); | |
| 530 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 13498 times.
|
13498 | if (fwrite(chunk, 1, read, output) != read) { |
| 531 | ✗ | libmpq__mpqe_clear(chunk, sizeof(chunk)); | |
| 532 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 533 | } | ||
| 534 | 13498 | offset += read; | |
| 535 | } | ||
| 536 | 87 | libmpq__mpqe_clear(chunk, sizeof(chunk)); | |
| 537 |
2/4✓ Branch 1 taken 87 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 87 times.
|
87 | if (ferror(input) || fflush(output) != 0) |
| 538 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 539 | 87 | return 0; | |
| 540 | } | ||
| 541 | |||
| 542 | /* Keep the archive-writer finalizer on the shared MPQE transform path. */ | ||
| 543 | static int32_t | ||
| 544 | 81 | transform_mpqe(mpq_archive_s *a) | |
| 545 | { | ||
| 546 | 81 | return libmpq__writer_mpqe_transform_file(a->fp, a->write_mpqe_output, a->write_mpqe_key); | |
| 547 | } | ||
| 548 | |||
| 549 | /* Production MPQE finalization operations used by every newly created MPQE archive. */ | ||
| 550 | static const mpq_writer_mpqe_ops_s default_mpqe_ops = { | ||
| 551 | finalize_archive, | ||
| 552 | transform_mpqe, | ||
| 553 | fclose, | ||
| 554 | libmpq__directory_replace, | ||
| 555 | }; | ||
| 556 | |||
| 557 | /* Finish MPQE output only after the ordinary MPQ writer has finalized its raw stream. */ | ||
| 558 | int32_t | ||
| 559 | 83 | libmpq__writer_finalize_mpqe(mpq_archive_s *a) | |
| 560 | { | ||
| 561 | const mpq_writer_mpqe_ops_s *ops; | ||
| 562 | int32_t result; | ||
| 563 | |||
| 564 |
2/4✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 83 times.
|
83 | if (a == NULL || !a->write_mpqe) |
| 565 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 566 | 83 | ops = a->write_mpqe_ops; | |
| 567 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 83 times.
|
83 | if (ops == NULL) |
| 568 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 569 | 83 | result = ops->transform(a); | |
| 570 |
2/6✓ Branch 0 taken 83 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 83 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
83 | if (a->fp != NULL && fclose(a->fp) != 0 && result == 0) |
| 571 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 572 | 83 | a->fp = NULL; | |
| 573 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 81 times.
|
83 | if (result != 0) |
| 574 | 2 | return result; | |
| 575 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 81 times.
|
81 | if (libmpq__directory_remove(a->write_mpqe_directory, a->filename) != 0) |
| 576 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 577 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 79 times.
|
81 | if (ops->close_output(a->write_mpqe_output) != 0) { |
| 578 | 2 | a->write_mpqe_output = NULL; | |
| 579 | 2 | return LIBMPQ_ERROR_CLOSE; | |
| 580 | } | ||
| 581 | 79 | a->write_mpqe_output = NULL; | |
| 582 | result = | ||
| 583 | 79 | ops->publish(a->write_mpqe_directory, a->write_mpqe_output_path, a->write_mpqe_destination); | |
| 584 |
2/2✓ Branch 0 taken 77 times.
✓ Branch 1 taken 2 times.
|
79 | if (result == 0) |
| 585 | 77 | a->write_finalized = 1; | |
| 586 | 79 | return result; | |
| 587 | } | ||
| 588 | |||
| 589 | /* Close/unlink private MPQE artifacts on every handled completion or failure. */ | ||
| 590 | void | ||
| 591 | 618 | libmpq__writer_mpqe_cleanup(mpq_archive_s *a) | |
| 592 | { | ||
| 593 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 618 times.
|
618 | if (a == NULL) |
| 594 | ✗ | return; | |
| 595 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 614 times.
|
618 | if (a->write_mpqe_output != NULL) |
| 596 | 4 | (void)fclose(a->write_mpqe_output); | |
| 597 | 618 | a->write_mpqe_output = NULL; | |
| 598 |
4/6✓ Branch 0 taken 85 times.
✓ Branch 1 taken 533 times.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 85 times.
✗ Branch 5 not taken.
|
618 | if (a->write_mpqe && a->write_mpqe_directory != NULL && a->filename != NULL) |
| 599 | 85 | (void)libmpq__directory_remove(a->write_mpqe_directory, a->filename); | |
| 600 |
3/4✓ Branch 0 taken 85 times.
✓ Branch 1 taken 533 times.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
|
618 | if (a->write_mpqe_directory != NULL && a->write_mpqe_output_path != NULL) |
| 601 | 85 | (void)libmpq__directory_remove(a->write_mpqe_directory, a->write_mpqe_output_path); | |
| 602 |
2/2✓ Branch 0 taken 85 times.
✓ Branch 1 taken 533 times.
|
618 | if (a->write_mpqe_directory != NULL) |
| 603 | 85 | (void)libmpq__directory_close(a->write_mpqe_directory); | |
| 604 | 618 | a->write_mpqe_directory = NULL; | |
| 605 | 618 | libmpq__mpqe_clear(a->write_mpqe_key, sizeof(a->write_mpqe_key)); | |
| 606 | } | ||
| 607 | |||
| 608 | /* | ||
| 609 | * Create a new seekable MPQ v1 or v2 archive with reserved metadata tables. | ||
| 610 | * The function validates creation options, reserves table space, initializes | ||
| 611 | * empty hash entries, and leaves the file positioned at the first payload. | ||
| 612 | */ | ||
| 613 | static int32_t | ||
| 614 | 1131 | writer_validate_options(const mpq_archive_create_options_s *options) | |
| 615 | { | ||
| 616 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1129 times.
|
1131 | if (options == NULL) |
| 617 | 2 | return 0; | |
| 618 |
4/4✓ Branch 0 taken 1128 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1127 times.
✓ Branch 3 taken 1 times.
|
1129 | if (options->version > LIBMPQ_ARCHIVE_VERSION_TWO || options->max_files == UINT32_MAX || |
| 619 |
2/4✓ Branch 0 taken 1127 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1127 times.
|
1127 | (options->max_files && options->max_files > 0x40000000u)) |
| 620 | 2 | return LIBMPQ_ERROR_FORMAT; | |
| 621 |
1/2✓ Branch 0 taken 1127 times.
✗ Branch 1 not taken.
|
1127 | if (options->sector_size && |
| 622 |
3/4✓ Branch 0 taken 1127 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1126 times.
|
1127 | (options->sector_size < 512 || (options->sector_size & (options->sector_size - 1)) != 0)) |
| 623 | 1 | return LIBMPQ_ERROR_FORMAT; | |
| 624 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1122 times.
|
1126 | if ((options->attributes & ~LIBMPQ_ATTRIBUTES_ALL) != 0) |
| 625 | 4 | return LIBMPQ_ERROR_FORMAT; | |
| 626 |
4/6✓ Branch 0 taken 679 times.
✓ Branch 1 taken 443 times.
✓ Branch 2 taken 679 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 679 times.
|
1801 | if (options->attributes != 0 && options->max_files != 0 && |
| 627 |
2/2✓ Branch 0 taken 165 times.
✓ Branch 1 taken 514 times.
|
679 | options->max_files < 1u + ((options->flags & LIBMPQ_ARCHIVE_CREATE_LISTFILE) != 0)) |
| 628 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 629 | 1122 | return 0; | |
| 630 | } | ||
| 631 | |||
| 632 | /* Allocate the writer before opening ordinary output, or adopt a private stream. */ | ||
| 633 | static int32_t | ||
| 634 | 618 | writer_archive_create_handle( | |
| 635 | mpq_archive_s **out, const char *path, FILE *file, const mpq_archive_create_options_s *options | ||
| 636 | ) | ||
| 637 | { | ||
| 638 | 618 | mpq_archive_create_options_s defaults = { LIBMPQ_ARCHIVE_VERSION_ONE, 1024, 4096, 0, 0 }; | |
| 639 | mpq_archive_s *a; | ||
| 640 | uint32_t i; | ||
| 641 | uint32_t header_size; | ||
| 642 | uint64_t offset; | ||
| 643 | uint8_t *zero; | ||
| 644 | |||
| 645 |
2/4✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 618 times.
|
618 | if (out == NULL || path == NULL) |
| 646 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 647 | 618 | *out = NULL; | |
| 648 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 616 times.
|
618 | if (options == NULL) |
| 649 | 2 | options = &defaults; | |
| 650 | |||
| 651 | /* Reject versions, capacities, and sector sizes that cannot be represented safely. */ | ||
| 652 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 618 times.
|
618 | if (writer_validate_options(options) != 0) { |
| 653 | ✗ | if (file != NULL) | |
| 654 | ✗ | (void)fclose(file); | |
| 655 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 656 | } | ||
| 657 | 618 | a = calloc(1, sizeof(*a)); | |
| 658 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 618 times.
|
618 | if (a == NULL) { |
| 659 | ✗ | if (file != NULL) | |
| 660 | ✗ | (void)fclose(file); | |
| 661 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 662 | } | ||
| 663 | 618 | a->write_mpqe_directory = NULL; | |
| 664 | 618 | a->fp = file; | |
| 665 |
2/2✓ Branch 0 taken 421 times.
✓ Branch 1 taken 197 times.
|
618 | if (a->fp == NULL) |
| 666 | 421 | a->fp = libmpq__file_open(path, "w+b"); | |
| 667 | 618 | a->filename = libmpq__string_duplicate(path); | |
| 668 |
2/4✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 618 times.
|
618 | if (a->fp == NULL || a->filename == NULL) { |
| 669 | ✗ | if (a->fp != NULL) | |
| 670 | ✗ | (void)fclose(a->fp); | |
| 671 | ✗ | free(a->filename); | |
| 672 | ✗ | free(a); | |
| 673 | ✗ | return LIBMPQ_ERROR_OPEN; | |
| 674 | } | ||
| 675 | 618 | a->write_mode = 1; | |
| 676 |
1/2✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
|
618 | a->write_capacity = options->max_files ? options->max_files : 1024; |
| 677 |
1/2✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
|
618 | a->write_sector_size = options->sector_size ? options->sector_size : 4096; |
| 678 | 618 | a->write_flags = options->flags; | |
| 679 | 618 | a->write_attributes_flags = options->attributes; | |
| 680 | |||
| 681 | /* Keep hash load below one half so linear probing remains bounded. */ | ||
| 682 | 618 | a->write_hash_capacity = next_power_two(a->write_capacity * 2); | |
| 683 | 1236 | header_size = options->version == LIBMPQ_ARCHIVE_VERSION_TWO | |
| 684 | ? LIBMPQ_HEADER_WIRE_SIZE + LIBMPQ_HEADER_EX_WIRE_SIZE | ||
| 685 |
2/2✓ Branch 0 taken 237 times.
✓ Branch 1 taken 381 times.
|
618 | : LIBMPQ_HEADER_WIRE_SIZE; |
| 686 | 618 | a->mpq_header.version = (uint16_t)options->version; | |
| 687 | 618 | a->mpq_header.header_size = header_size; | |
| 688 | 618 | a->mpq_header.block_size = 0; | |
| 689 |
2/2✓ Branch 0 taken 868 times.
✓ Branch 1 taken 618 times.
|
1486 | while ((512u << a->mpq_header.block_size) < a->write_sector_size) |
| 690 | 868 | a->mpq_header.block_size++; | |
| 691 | 618 | a->mpq_header.hash_table_count = a->write_hash_capacity; | |
| 692 | 618 | a->mpq_header.block_table_count = a->write_capacity; | |
| 693 | 618 | a->mpq_header.hash_table_offset = header_size; | |
| 694 | |||
| 695 | /* | ||
| 696 | * StormLib skips v1 attributes when a table starts exactly at header end. | ||
| 697 | * Reserve a small gap only for opt-in attributes, leaving ordinary output unchanged. | ||
| 698 | */ | ||
| 699 |
4/4✓ Branch 0 taken 381 times.
✓ Branch 1 taken 237 times.
✓ Branch 3 taken 185 times.
✓ Branch 4 taken 196 times.
|
618 | if (options->version == LIBMPQ_ARCHIVE_VERSION_ONE && libmpq__attributes_write_flags(a)) |
| 700 | 185 | a->mpq_header.hash_table_offset += 16; | |
| 701 | 618 | a->mpq_header.block_table_offset = | |
| 702 | 618 | a->mpq_header.hash_table_offset + a->write_hash_capacity * LIBMPQ_HASH_ENTRY_WIRE_SIZE; | |
| 703 | 618 | a->mpq_header_ex.extended_offset = 0; | |
| 704 | 618 | offset = (uint64_t)a->mpq_header.block_table_offset + | |
| 705 | 618 | (uint64_t)a->write_capacity * LIBMPQ_BLOCK_ENTRY_WIRE_SIZE; | |
| 706 |
2/2✓ Branch 0 taken 237 times.
✓ Branch 1 taken 381 times.
|
618 | if (options->version == LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 707 | 237 | a->mpq_header_ex.extended_offset = offset; | |
| 708 | 237 | offset += (uint64_t)a->write_capacity * LIBMPQ_BLOCK_EX_ENTRY_WIRE_SIZE; | |
| 709 | } | ||
| 710 | 618 | offset = (offset + 511) & ~UINT64_C(511); | |
| 711 |
3/4✓ Branch 0 taken 381 times.
✓ Branch 1 taken 237 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 381 times.
|
618 | if (options->version == LIBMPQ_ARCHIVE_VERSION_ONE && offset > UINT32_MAX) { |
| 712 | ✗ | fclose(a->fp); | |
| 713 | ✗ | free(a->filename); | |
| 714 | ✗ | free(a); | |
| 715 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 716 | } | ||
| 717 | 618 | a->mpq_hash = calloc(a->write_hash_capacity, sizeof(*a->mpq_hash)); | |
| 718 | 618 | a->mpq_block = calloc(a->write_capacity, sizeof(*a->mpq_block)); | |
| 719 | 618 | a->mpq_block_ex = calloc(a->write_capacity, sizeof(*a->mpq_block_ex)); | |
| 720 | 618 | a->write_names = calloc(a->write_capacity, sizeof(*a->write_names)); | |
| 721 | 618 | a->write_locales = calloc(a->write_capacity, sizeof(*a->write_locales)); | |
| 722 | 618 | a->write_platforms = calloc(a->write_capacity, sizeof(*a->write_platforms)); | |
| 723 |
5/10✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 618 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 618 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 618 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 618 times.
✗ Branch 9 not taken.
|
618 | if (!a->mpq_hash || !a->mpq_block || !a->mpq_block_ex || !a->write_names || !a->write_locales || |
| 724 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 618 times.
|
618 | !a->write_platforms) { |
| 725 | ✗ | fclose(a->fp); | |
| 726 | ✗ | free(a->filename); | |
| 727 | ✗ | free(a->mpq_hash); | |
| 728 | ✗ | free(a->mpq_block); | |
| 729 | ✗ | free(a->mpq_block_ex); | |
| 730 | ✗ | free(a->write_names); | |
| 731 | ✗ | free(a->write_locales); | |
| 732 | ✗ | free(a->write_platforms); | |
| 733 | ✗ | free(a); | |
| 734 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 735 | } | ||
| 736 |
2/2✓ Branch 0 taken 74600 times.
✓ Branch 1 taken 618 times.
|
75218 | for (i = 0; i < a->write_hash_capacity; i++) |
| 737 | 74600 | a->mpq_hash[i].block_table_index = LIBMPQ_HASH_FREE; | |
| 738 | 618 | zero = calloc(1, (size_t)(offset > 4096 ? 4096 : offset)); | |
| 739 |
1/2✓ Branch 0 taken 618 times.
✗ Branch 1 not taken.
|
618 | if (zero == NULL || fwrite(zero, 1, (size_t)(offset > 4096 ? 4096 : offset), a->fp) != |
| 740 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 618 times.
|
618 | (size_t)(offset > 4096 ? 4096 : offset)) { |
| 741 | ✗ | free(zero); | |
| 742 | ✗ | fclose(a->fp); | |
| 743 | ✗ | free(a->filename); | |
| 744 | ✗ | free(a->mpq_hash); | |
| 745 | ✗ | free(a->mpq_block); | |
| 746 | ✗ | free(a->mpq_block_ex); | |
| 747 | ✗ | free(a->write_names); | |
| 748 | ✗ | free(a->write_locales); | |
| 749 | ✗ | free(a->write_platforms); | |
| 750 | ✗ | free(a); | |
| 751 | ✗ | return LIBMPQ_ERROR_WRITE; | |
| 752 | } | ||
| 753 | 618 | free(zero); | |
| 754 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 618 times.
|
618 | if (libmpq__file_seek(a->fp, offset, SEEK_SET) < 0) { |
| 755 | ✗ | fclose(a->fp); | |
| 756 | ✗ | free(a->filename); | |
| 757 | ✗ | free(a->mpq_hash); | |
| 758 | ✗ | free(a->mpq_block); | |
| 759 | ✗ | free(a->mpq_block_ex); | |
| 760 | ✗ | free(a->write_names); | |
| 761 | ✗ | free(a->write_locales); | |
| 762 | ✗ | free(a->write_platforms); | |
| 763 | ✗ | free(a); | |
| 764 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 765 | } | ||
| 766 | 618 | *out = a; | |
| 767 | 618 | return 0; | |
| 768 | } | ||
| 769 | |||
| 770 | /* Create an ordinary seekable MPQ directly at its requested destination. */ | ||
| 771 | int32_t | ||
| 772 | 428 | libmpq__writer_archive_create( | |
| 773 | mpq_archive_s **out, const char *path, const mpq_archive_create_options_s *options | ||
| 774 | ) | ||
| 775 | { | ||
| 776 |
4/4✓ Branch 0 taken 427 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 426 times.
|
428 | if (out == NULL || path == NULL) |
| 777 | 2 | return LIBMPQ_ERROR_EXIST; | |
| 778 | 426 | *out = NULL; | |
| 779 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 421 times.
|
426 | if (writer_validate_options(options) != 0) |
| 780 | 5 | return LIBMPQ_ERROR_FORMAT; | |
| 781 | 421 | return writer_archive_create_handle(out, path, NULL, options); | |
| 782 | } | ||
| 783 | |||
| 784 | int32_t | ||
| 785 | 112 | libmpq__writer_archive_create_file( | |
| 786 | mpq_archive_s **out, const char *path, FILE *file, const mpq_archive_create_options_s *options | ||
| 787 | ) | ||
| 788 | { | ||
| 789 | 112 | return writer_archive_create_handle(out, path, file, options); | |
| 790 | } | ||
| 791 | |||
| 792 | /* | ||
| 793 | * Create raw and encrypted private files, then initialize the unchanged MPQ writer on raw output. | ||
| 794 | */ | ||
| 795 | int32_t | ||
| 796 | 90 | libmpq__writer_archive_create_mpqe( | |
| 797 | mpq_archive_s **out, const char *path, const uint8_t *auth_code, size_t auth_code_size, | ||
| 798 | const mpq_archive_create_options_s *options | ||
| 799 | ) | ||
| 800 | { | ||
| 801 | 90 | FILE *raw = NULL; | |
| 802 | 90 | FILE *encrypted = NULL; | |
| 803 | 90 | char *raw_path = NULL; | |
| 804 | 90 | char *encrypted_path = NULL; | |
| 805 | 90 | char *destination = NULL; | |
| 806 | uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE]; | ||
| 807 | 90 | mpq_directory_s *directory = NULL; | |
| 808 | int32_t result; | ||
| 809 | |||
| 810 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
|
90 | if (out == NULL) |
| 811 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 812 | 90 | *out = NULL; | |
| 813 | 90 | result = libmpq__mpqe_key(key, auth_code, auth_code_size); | |
| 814 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 87 times.
|
90 | if (result != 0) |
| 815 | 3 | return result; | |
| 816 | 87 | result = writer_validate_options(options); | |
| 817 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 85 times.
|
87 | if (result != 0) { |
| 818 | 2 | libmpq__mpqe_clear(key, sizeof(key)); | |
| 819 | 2 | return result; | |
| 820 | } | ||
| 821 | 85 | result = libmpq__directory_open(path, &directory, &destination); | |
| 822 |
1/2✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
|
85 | if (result == 0) |
| 823 | 85 | result = libmpq__directory_temporary( | |
| 824 | directory, ".libmpq-raw-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, &raw_path, &raw | ||
| 825 | ); | ||
| 826 |
1/2✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
|
85 | if (result == 0) |
| 827 | 85 | result = libmpq__directory_temporary( | |
| 828 | directory, ".libmpq-mpqe-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 0, &encrypted_path, | ||
| 829 | &encrypted | ||
| 830 | ); | ||
| 831 |
1/2✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
|
85 | if (result == 0) { |
| 832 | 85 | result = writer_archive_create_handle(out, raw_path, raw, options); | |
| 833 | 85 | raw = NULL; | |
| 834 | } | ||
| 835 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
|
85 | if (result != 0) { |
| 836 | ✗ | if (raw != NULL) | |
| 837 | ✗ | (void)fclose(raw); | |
| 838 | ✗ | if (encrypted != NULL) | |
| 839 | ✗ | (void)fclose(encrypted); | |
| 840 | ✗ | if (directory != NULL && raw_path != NULL) | |
| 841 | ✗ | (void)libmpq__directory_remove(directory, raw_path); | |
| 842 | ✗ | if (directory != NULL && encrypted_path != NULL) | |
| 843 | ✗ | (void)libmpq__directory_remove(directory, encrypted_path); | |
| 844 | ✗ | if (directory != NULL) | |
| 845 | ✗ | (void)libmpq__directory_close(directory); | |
| 846 | ✗ | free(raw_path); | |
| 847 | ✗ | free(encrypted_path); | |
| 848 | ✗ | free(destination); | |
| 849 | ✗ | libmpq__mpqe_clear(key, sizeof(key)); | |
| 850 | ✗ | return result; | |
| 851 | } | ||
| 852 | 85 | (*out)->write_mpqe = 1; | |
| 853 | 85 | memcpy((*out)->write_mpqe_key, key, sizeof(key)); | |
| 854 | 85 | (*out)->write_mpqe_output = encrypted; | |
| 855 | 85 | (*out)->write_mpqe_directory = directory; | |
| 856 | 85 | (*out)->write_mpqe_destination = destination; | |
| 857 | 85 | (*out)->write_mpqe_output_path = encrypted_path; | |
| 858 | 85 | (*out)->write_mpqe_ops = &default_mpqe_ops; | |
| 859 | 85 | encrypted = NULL; | |
| 860 | 85 | encrypted_path = NULL; | |
| 861 | 85 | destination = NULL; | |
| 862 | 85 | directory = NULL; | |
| 863 | 85 | free(raw_path); | |
| 864 | 85 | libmpq__mpqe_clear(key, sizeof(key)); | |
| 865 | 85 | return 0; | |
| 866 | } | ||
| 867 | |||
| 868 | /* | ||
| 869 | * Begin one file, optionally reserving a plaintext patch prefix before its | ||
| 870 | * normally encoded body. Option and duplicate-name validation precedes the | ||
| 871 | * prefix write; the eventual block entry remains unpublished until finish. | ||
| 872 | */ | ||
| 873 | static int32_t | ||
| 874 | 3147 | writer_file_begin( | |
| 875 | mpq_archive_s *a, const char *name, libmpq__off_t size, const mpq_file_options_s *options, | ||
| 876 | const uint8_t *prefix, uint32_t prefix_size, libmpq__off_t result_size, mpq_writer_s **out | ||
| 877 | ) | ||
| 878 | { | ||
| 879 | 3147 | mpq_file_options_s defaults = { 0, 0, 0, 0, 0 }; | |
| 880 | mpq_writer_s *w; | ||
| 881 |
13/14✓ Branch 0 taken 3146 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3146 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3145 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 3144 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 3142 times.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 3141 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 22 times.
✓ Branch 13 taken 3119 times.
|
3147 | if (!a || !a->write_mode || !name || !out || size < 0 || a->write_current || |
| 882 |
3/6✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
|
22 | (prefix_size != 0 && (!a->write_patch_mode || prefix == NULL || result_size < 0 || |
| 883 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | (uint64_t)result_size > UINT32_MAX))) |
| 884 | 6 | return LIBMPQ_ERROR_FORMAT; | |
| 885 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3120 times.
|
3141 | if (a->write_signature) { |
| 886 | uint32_t h1; | ||
| 887 | uint32_t h2; | ||
| 888 | uint32_t h3; | ||
| 889 | uint32_t s1; | ||
| 890 | uint32_t s2; | ||
| 891 | uint32_t s3; | ||
| 892 | 21 | libmpq__file_hash(name, &h1, &h2, &h3); | |
| 893 | 21 | libmpq__file_hash(LIBMPQ_SIGNATURE_NAME, &s1, &s2, &s3); | |
| 894 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
21 | if (h2 == s2 && h3 == s3) |
| 895 | 1 | return LIBMPQ_ERROR_FORMAT; | |
| 896 | } | ||
| 897 |
2/2✓ Branch 0 taken 346 times.
✓ Branch 1 taken 2794 times.
|
3140 | if (options == NULL) |
| 898 | 346 | options = &defaults; | |
| 899 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 3117 times.
|
3140 | if ((options->flags & LIBMPQ_FILE_FLAG_DELETE_MARKER) != 0 && |
| 900 |
4/6✓ Branch 0 taken 10 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
|
23 | (!a->write_patch_mode || options->flags != LIBMPQ_FILE_FLAG_DELETE_MARKER || size != 0)) |
| 901 | 13 | return LIBMPQ_ERROR_FORMAT; | |
| 902 |
2/2✓ Branch 1 taken 994 times.
✓ Branch 2 taken 2133 times.
|
3127 | if (libmpq__attributes_write_flags(a) != 0) { |
| 903 |
2/2✓ Branch 0 taken 405 times.
✓ Branch 1 taken 589 times.
|
994 | uint32_t reserved = 1u + ((a->write_flags & LIBMPQ_ARCHIVE_CREATE_LISTFILE) != 0); |
| 904 | uint32_t hash1; | ||
| 905 | uint32_t hash2; | ||
| 906 | uint32_t hash3; | ||
| 907 | uint32_t internal1; | ||
| 908 | uint32_t internal2; | ||
| 909 | uint32_t internal3; | ||
| 910 |
2/2✓ Branch 0 taken 553 times.
✓ Branch 1 taken 441 times.
|
994 | if (!a->write_internal && |
| 911 |
3/4✓ Branch 0 taken 553 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 493 times.
|
553 | (reserved > a->write_capacity || a->write_next_block >= a->write_capacity - reserved)) |
| 912 | 90 | return LIBMPQ_ERROR_SIZE; | |
| 913 | 934 | libmpq__file_hash(name, &hash1, &hash2, &hash3); | |
| 914 | 934 | libmpq__file_hash(LIBMPQ_ATTRIBUTES_NAME, &internal1, &internal2, &internal3); | |
| 915 |
6/8✓ Branch 0 taken 493 times.
✓ Branch 1 taken 441 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 463 times.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 7 not taken.
|
934 | if (!a->write_internal && hash1 == internal1 && hash2 == internal2 && hash3 == internal3) |
| 916 | 30 | return LIBMPQ_ERROR_FORMAT; | |
| 917 |
2/2✓ Branch 0 taken 350 times.
✓ Branch 1 taken 554 times.
|
904 | if (a->write_attributes == NULL) { |
| 918 | if ((uint64_t)a->write_capacity * sizeof(*a->write_attributes) > SIZE_MAX) | ||
| 919 | return LIBMPQ_ERROR_SIZE; | ||
| 920 | 350 | a->write_attributes = calloc(a->write_capacity, sizeof(*a->write_attributes)); | |
| 921 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 350 times.
|
350 | if (a->write_attributes == NULL) |
| 922 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 923 | } | ||
| 924 | } | ||
| 925 | 3037 | w = calloc(1, sizeof(*w)); | |
| 926 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3037 times.
|
3037 | if (w == NULL) |
| 927 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 928 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3037 times.
|
3037 | if ((uint64_t)size > SIZE_MAX || a->write_next_block >= a->write_capacity) { |
| 929 | ✗ | free(w); | |
| 930 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 931 | } | ||
| 932 |
2/2✓ Branch 0 taken 117 times.
✓ Branch 1 taken 2920 times.
|
3037 | if ((options->flags & LIBMPQ_FILE_FLAG_IMPLODE) && |
| 933 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 115 times.
|
117 | (options->flags & LIBMPQ_FILE_FLAG_COMPRESS)) { |
| 934 | 2 | free(w); | |
| 935 | 2 | return LIBMPQ_ERROR_FORMAT; | |
| 936 | } | ||
| 937 |
4/4✓ Branch 0 taken 2091 times.
✓ Branch 1 taken 944 times.
✓ Branch 2 taken 2052 times.
✓ Branch 3 taken 39 times.
|
5126 | if ((options->flags & LIBMPQ_FILE_FLAG_COMPRESS) && |
| 938 | 2091 | (!libmpq__compression_allowed( | |
| 939 | 2091 | a->mpq_header.version, options->compression_first, compression_policy(a) | |
| 940 |
2/2✓ Branch 0 taken 37 times.
✓ Branch 1 taken 2015 times.
|
2052 | ) || |
| 941 | 2052 | !libmpq__compression_allowed( | |
| 942 | 2052 | a->mpq_header.version, options->compression_next, compression_policy(a) | |
| 943 | ))) { | ||
| 944 | 76 | free(w); | |
| 945 | 76 | return LIBMPQ_ERROR_FORMAT; | |
| 946 | } | ||
| 947 |
2/2✓ Branch 0 taken 772 times.
✓ Branch 1 taken 2187 times.
|
2959 | if ((options->flags & LIBMPQ_FILE_FLAG_SINGLE) && |
| 948 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 772 times.
|
772 | ((options->compression_first | options->compression_next) & |
| 949 | (LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO))) { | ||
| 950 | ✗ | free(w); | |
| 951 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 952 | } | ||
| 953 | |||
| 954 | /* Existing writer entries already occupy their MPQ hash probe sequence. */ | ||
| 955 | { | ||
| 956 | uint32_t h1; | ||
| 957 | uint32_t h2; | ||
| 958 | uint32_t h3; | ||
| 959 | uint32_t i; | ||
| 960 | 2959 | libmpq__file_hash(name, &h1, &h2, &h3); | |
| 961 |
1/2✓ Branch 0 taken 3233 times.
✗ Branch 1 not taken.
|
3233 | for (i = 0; i < a->write_hash_capacity; i++) { |
| 962 | 3233 | const mpq_hash_s *entry = &a->mpq_hash[(h1 + i) & (a->write_hash_capacity - 1)]; | |
| 963 | |||
| 964 |
2/2✓ Branch 0 taken 2956 times.
✓ Branch 1 taken 277 times.
|
3233 | if (entry->block_table_index == LIBMPQ_HASH_FREE) |
| 965 | 2956 | break; | |
| 966 |
5/6✓ Branch 0 taken 6 times.
✓ Branch 1 taken 271 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 2 times.
|
277 | if (entry->hash_a == h2 && entry->hash_b == h3 && entry->locale == options->locale && |
| 967 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | entry->platform == options->platform) { |
| 968 | uint32_t existing1; | ||
| 969 | uint32_t existing2; | ||
| 970 | uint32_t existing3; | ||
| 971 | |||
| 972 | /* Hash entries omit h1, so confirm it for matching identities. */ | ||
| 973 | 3 | libmpq__file_hash( | |
| 974 | 3 | a->write_names[entry->block_table_index], &existing1, &existing2, &existing3 | |
| 975 | ); | ||
| 976 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (existing1 == h1) { |
| 977 | 3 | free(w); | |
| 978 | 3 | return LIBMPQ_ERROR_EXIST; | |
| 979 | } | ||
| 980 | } | ||
| 981 | } | ||
| 982 | } | ||
| 983 |
1/2✓ Branch 0 taken 2956 times.
✗ Branch 1 not taken.
|
2956 | w->data = malloc(a->write_sector_size ? a->write_sector_size : 1); |
| 984 | 2956 | w->name = libmpq__string_duplicate(name); | |
| 985 |
2/4✓ Branch 0 taken 2956 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2956 times.
|
2956 | if (w->data == NULL || w->name == NULL) { |
| 986 | ✗ | free(w->data); | |
| 987 | ✗ | free(w->name); | |
| 988 | ✗ | free(w); | |
| 989 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 990 | } | ||
| 991 | 2956 | w->archive = a; | |
| 992 | 2956 | w->expected = size; | |
| 993 | 2956 | w->options = *options; | |
| 994 | 2956 | w->prefix_size = prefix_size; | |
| 995 | 2956 | w->patch_file = prefix_size != 0; | |
| 996 | 2956 | w->patch_result_size = (uint32_t)result_size; | |
| 997 |
4/4✓ Branch 0 taken 2868 times.
✓ Branch 1 taken 88 times.
✓ Branch 2 taken 2117 times.
✓ Branch 3 taken 751 times.
|
2956 | if (size == 0 || (w->options.flags & LIBMPQ_FILE_FLAG_SINGLE) != 0 || |
| 998 |
2/2✓ Branch 0 taken 386 times.
✓ Branch 1 taken 1731 times.
|
2117 | (w->options.flags & (LIBMPQ_FILE_FLAG_COMPRESS | LIBMPQ_FILE_FLAG_IMPLODE)) == 0) |
| 999 | 1225 | w->options.flags &= ~LIBMPQ_FILE_FLAG_SECTOR_CRC; | |
| 1000 | 2956 | w->options.compression_first &= | |
| 1001 | ~(LIBMPQ_COMPRESSION_WAVE_MONO | LIBMPQ_COMPRESSION_WAVE_STEREO); | ||
| 1002 | |||
| 1003 | /* STANDARD v2 keeps the first WAVE sector lossless without standalone Huffman. */ | ||
| 1004 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2954 times.
|
5912 | if (!libmpq__compression_allowed( |
| 1005 | 2956 | a->mpq_header.version, w->options.compression_first, compression_policy(a) | |
| 1006 | )) | ||
| 1007 | 2 | w->options.compression_first = LIBMPQ_COMPRESSION_ZLIB; | |
| 1008 |
2/2✓ Branch 0 taken 867 times.
✓ Branch 1 taken 2089 times.
|
2956 | if (w->options.compression_next == 0) |
| 1009 | 867 | w->options.compression_next = w->options.compression_first; | |
| 1010 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2956 times.
|
2956 | if (a->write_sector_size == 0) { |
| 1011 | ✗ | free(w->name); | |
| 1012 | ✗ | free(w->data); | |
| 1013 | ✗ | free(w); | |
| 1014 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 1015 | } | ||
| 1016 | 5912 | w->block_count = (w->options.flags & LIBMPQ_FILE_FLAG_SINGLE) | |
| 1017 | ? 1 | ||
| 1018 |
2/2✓ Branch 0 taken 2184 times.
✓ Branch 1 taken 772 times.
|
2956 | : (uint32_t)((size + a->write_sector_size - 1) / a->write_sector_size); |
| 1019 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 2889 times.
|
2956 | if (w->block_count == 0) |
| 1020 | 67 | w->block_count = 1; | |
| 1021 | 2956 | w->payload_offset = (uint64_t)libmpq__file_tell(a->fp); | |
| 1022 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2956 times.
|
2956 | if (w->payload_offset > (uint64_t)INT64_MAX - prefix_size) { |
| 1023 | ✗ | free(w->data); | |
| 1024 | ✗ | free(w->name); | |
| 1025 | ✗ | free(w); | |
| 1026 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1027 | } | ||
| 1028 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2934 times.
|
2956 | if (prefix_size != 0) { |
| 1029 | 22 | int32_t result = write_at(a->fp, w->payload_offset, prefix, prefix_size); | |
| 1030 | |||
| 1031 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (result != 0) { |
| 1032 | ✗ | free(w->data); | |
| 1033 | ✗ | free(w->name); | |
| 1034 | ✗ | free(w); | |
| 1035 | ✗ | return result; | |
| 1036 | } | ||
| 1037 | 22 | w->payload_offset += prefix_size; | |
| 1038 | } | ||
| 1039 | |||
| 1040 | /* Reserve offset-table space before the first packed sector is written. */ | ||
| 1041 |
2/2✓ Branch 0 taken 2184 times.
✓ Branch 1 taken 772 times.
|
2956 | if (!(w->options.flags & LIBMPQ_FILE_FLAG_SINGLE) && |
| 1042 |
2/2✓ Branch 0 taken 1739 times.
✓ Branch 1 taken 445 times.
|
2184 | (w->options.flags & (LIBMPQ_FILE_FLAG_COMPRESS | LIBMPQ_FILE_FLAG_IMPLODE))) { |
| 1043 | 1739 | uint32_t crc = (w->options.flags & LIBMPQ_FILE_FLAG_SECTOR_CRC) != 0; | |
| 1044 | 1739 | uint64_t entries = (uint64_t)w->block_count + 1 + crc; | |
| 1045 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 1697 times.
|
1739 | uint64_t allocated = entries + (crc ? w->block_count : 0); |
| 1046 | size_t table_size; | ||
| 1047 |
2/4✓ Branch 0 taken 1739 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1739 times.
|
1739 | if (entries > UINT32_MAX / 4 || allocated > SIZE_MAX / sizeof(*w->offsets)) { |
| 1048 | ✗ | free(w->data); | |
| 1049 | ✗ | free(w->name); | |
| 1050 | ✗ | free(w); | |
| 1051 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 1052 | } | ||
| 1053 | 1739 | table_size = (size_t)entries * 4; | |
| 1054 | 1739 | w->offsets = calloc((size_t)allocated, sizeof(*w->offsets)); | |
| 1055 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1739 times.
|
1739 | if (w->offsets == NULL) { |
| 1056 | ✗ | free(w->offsets); | |
| 1057 | ✗ | free(w->data); | |
| 1058 | ✗ | free(w->name); | |
| 1059 | ✗ | free(w); | |
| 1060 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 1061 | } | ||
| 1062 | 1739 | w->offsets[0] = (uint32_t)table_size; | |
| 1063 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 1697 times.
|
1739 | if (crc) |
| 1064 | 42 | w->checksums = w->offsets + (size_t)entries; | |
| 1065 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1739 times.
|
1739 | if (libmpq__file_seek(a->fp, (w->payload_offset + table_size), SEEK_SET) < 0) { |
| 1066 | ✗ | free(w->offsets); | |
| 1067 | ✗ | free(w->data); | |
| 1068 | ✗ | free(w->name); | |
| 1069 | ✗ | free(w); | |
| 1070 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1071 | } | ||
| 1072 | } | ||
| 1073 | 2956 | a->write_current = w; | |
| 1074 | 2956 | w->attributes.flags = libmpq__attributes_write_flags(a); | |
| 1075 | |||
| 1076 | /* Include the plaintext patch prefix before normal body writes extend CRC32. */ | ||
| 1077 |
3/4✓ Branch 0 taken 22 times.
✓ Branch 1 taken 2934 times.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
2956 | if (prefix_size != 0 && (w->attributes.flags & LIBMPQ_ATTRIBUTE_CRC32) != 0) |
| 1078 | 22 | w->attributes.crc32 = (uint32_t)crc32(w->attributes.crc32, prefix, prefix_size); | |
| 1079 |
2/2✓ Branch 0 taken 792 times.
✓ Branch 1 taken 2164 times.
|
2956 | if (w->attributes.flags & LIBMPQ_ATTRIBUTE_MD5) |
| 1080 | 792 | libmpq__md5_init(&w->md5); | |
| 1081 | 2956 | *out = w; | |
| 1082 | 2956 | return 0; | |
| 1083 | } | ||
| 1084 | |||
| 1085 | /* | ||
| 1086 | * Begin an ordinary streamed member with no plaintext prefix or patch flags. | ||
| 1087 | * This wrapper preserves the public file-writer path unchanged. | ||
| 1088 | */ | ||
| 1089 | int32_t | ||
| 1090 | 3125 | libmpq__writer_file_begin( | |
| 1091 | mpq_archive_s *archive, const char *name, libmpq__off_t size, const mpq_file_options_s *options, | ||
| 1092 | mpq_writer_s **out | ||
| 1093 | ) | ||
| 1094 | { | ||
| 1095 | 3125 | return writer_file_begin(archive, name, size, options, NULL, 0, 0, out); | |
| 1096 | } | ||
| 1097 | |||
| 1098 | /* | ||
| 1099 | * Append caller-provided bytes and flush complete sectors immediately. | ||
| 1100 | * The input is copied into a single sector buffer, so memory use is bounded | ||
| 1101 | * independently of the total file size. | ||
| 1102 | */ | ||
| 1103 | int32_t | ||
| 1104 | 2995 | libmpq__writer_file_write(mpq_writer_s *w, const uint8_t *buffer, libmpq__off_t size) | |
| 1105 | { | ||
| 1106 |
9/10✓ Branch 0 taken 2993 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 59 times.
✓ Branch 3 taken 2934 times.
✓ Branch 4 taken 58 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2992 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 2989 times.
|
2995 | if (!w || (!buffer && size != 0) || size < 0 || size > w->expected - w->written) |
| 1107 | 6 | return LIBMPQ_ERROR_SIZE; | |
| 1108 | |||
| 1109 | /* Fill the current sector, flushing exactly when it reaches capacity. */ | ||
| 1110 |
2/2✓ Branch 0 taken 5879 times.
✓ Branch 1 taken 2988 times.
|
8867 | while (size != 0) { |
| 1111 | 5879 | uint32_t room = w->archive->write_sector_size - w->data_size; | |
| 1112 |
2/2✓ Branch 0 taken 2462 times.
✓ Branch 1 taken 3417 times.
|
5879 | uint32_t take = (uint32_t)((size < room) ? size : room); |
| 1113 |
2/2✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 4641 times.
|
5879 | if (w->attributes.flags & LIBMPQ_ATTRIBUTE_CRC32) |
| 1114 | 1238 | w->attributes.crc32 = (uint32_t)crc32(w->attributes.crc32, buffer, take); | |
| 1115 |
2/2✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 4641 times.
|
5879 | if (w->attributes.flags & LIBMPQ_ATTRIBUTE_MD5) |
| 1116 | 1238 | libmpq__md5_update(&w->md5, buffer, take); | |
| 1117 | 5879 | memcpy(w->data + w->data_size, buffer, take); | |
| 1118 | 5879 | w->data_size += take; | |
| 1119 | 5879 | w->written += take; | |
| 1120 | 5879 | buffer += take; | |
| 1121 | 5879 | size -= take; | |
| 1122 |
2/2✓ Branch 0 taken 3417 times.
✓ Branch 1 taken 2462 times.
|
5879 | if (w->data_size == w->archive->write_sector_size) { |
| 1123 | 3417 | int32_t result = stream_flush_sector(w); | |
| 1124 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3416 times.
|
3417 | if (result < 0) |
| 1125 | 1 | return result; | |
| 1126 | } | ||
| 1127 | } | ||
| 1128 | 2988 | return 0; | |
| 1129 | } | ||
| 1130 | |||
| 1131 | /* | ||
| 1132 | * Assign FILETIME without importing filesystem state or altering file bytes. | ||
| 1133 | * Only the currently active writer can accept this metadata. | ||
| 1134 | */ | ||
| 1135 | int32_t | ||
| 1136 | 33 | libmpq__writer_file_timestamp(mpq_writer_s *writer, uint64_t filetime) | |
| 1137 | { | ||
| 1138 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 32 times.
|
33 | if (writer == NULL) |
| 1139 | 1 | return LIBMPQ_ERROR_EXIST; | |
| 1140 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32 times.
|
32 | if (writer->archive->write_current != writer) |
| 1141 | ✗ | return LIBMPQ_ERROR_NOT_INITIALIZED; | |
| 1142 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
|
32 | if (!(writer->attributes.flags & LIBMPQ_ATTRIBUTE_FILETIME)) |
| 1143 | 16 | return LIBMPQ_ERROR_FORMAT; | |
| 1144 | 16 | writer->attributes.filetime = filetime; | |
| 1145 | 16 | return 0; | |
| 1146 | } | ||
| 1147 | |||
| 1148 | /* | ||
| 1149 | * Flush the final sector and commit the file's block and hash-table entries. | ||
| 1150 | * Cleanup occurs on both success and failure so the archive never retains an | ||
| 1151 | * active writer after this function returns. | ||
| 1152 | */ | ||
| 1153 | int32_t | ||
| 1154 | 2955 | libmpq__writer_file_finish(mpq_writer_s *w) | |
| 1155 | { | ||
| 1156 | int32_t result; | ||
| 1157 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2954 times.
|
2955 | if (!w) |
| 1158 | 1 | return LIBMPQ_ERROR_EXIST; | |
| 1159 |
4/4✓ Branch 0 taken 528 times.
✓ Branch 1 taken 2426 times.
✓ Branch 2 taken 88 times.
✓ Branch 3 taken 440 times.
|
2954 | if (w->data_size != 0 || w->expected == 0) { |
| 1160 | 2514 | result = stream_flush_sector(w); | |
| 1161 | } else { | ||
| 1162 | 440 | result = 0; | |
| 1163 | } | ||
| 1164 |
1/2✓ Branch 0 taken 2954 times.
✗ Branch 1 not taken.
|
2954 | if (result == 0) |
| 1165 | 2954 | result = stream_finish(w); | |
| 1166 | 2954 | w->archive->write_current = NULL; | |
| 1167 | 2954 | free(w->name); | |
| 1168 | 2954 | free(w->data); | |
| 1169 | 2954 | free(w->offsets); | |
| 1170 | 2954 | free(w); | |
| 1171 | 2954 | return result; | |
| 1172 | } | ||
| 1173 | |||
| 1174 | /* | ||
| 1175 | * Discard an unfinished file after a write failure or archive close. | ||
| 1176 | * Any partially written payload remains unreachable because no block or hash | ||
| 1177 | * table entry is committed until writer_file_finish succeeds. | ||
| 1178 | */ | ||
| 1179 | void | ||
| 1180 | 619 | libmpq__writer_file_abort(mpq_writer_s *writer) | |
| 1181 | { | ||
| 1182 |
2/2✓ Branch 0 taken 617 times.
✓ Branch 1 taken 2 times.
|
619 | if (writer == NULL) |
| 1183 | 617 | return; | |
| 1184 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (writer->archive->write_current == writer) |
| 1185 | 2 | writer->archive->write_current = NULL; | |
| 1186 | 2 | free(writer->name); | |
| 1187 | 2 | free(writer->data); | |
| 1188 | 2 | free(writer->offsets); | |
| 1189 | 2 | free(writer); | |
| 1190 | } | ||
| 1191 | |||
| 1192 | /* | ||
| 1193 | * Add an in-memory file through the streaming writer interface. | ||
| 1194 | * This convenience wrapper uses the same sector pipeline as explicit begin, | ||
| 1195 | * write, and finish calls and cleans up an aborted stream. | ||
| 1196 | */ | ||
| 1197 | int32_t | ||
| 1198 | 2983 | libmpq__writer_file_add( | |
| 1199 | mpq_archive_s *a, const char *name, const uint8_t *data, libmpq__off_t size, | ||
| 1200 | const mpq_file_options_s *options | ||
| 1201 | ) | ||
| 1202 | { | ||
| 1203 | mpq_writer_s *w; | ||
| 1204 | 2983 | int32_t result = libmpq__writer_file_begin(a, name, size, options, &w); | |
| 1205 |
2/2✓ Branch 0 taken 185 times.
✓ Branch 1 taken 2798 times.
|
2983 | if (result < 0) |
| 1206 | 185 | return result; | |
| 1207 | 2798 | result = libmpq__writer_file_write(w, data, size); | |
| 1208 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2797 times.
|
2798 | if (result < 0) { |
| 1209 | 1 | libmpq__writer_file_abort(w); | |
| 1210 | 1 | return result; | |
| 1211 | } | ||
| 1212 | 2797 | return libmpq__writer_file_finish(w); | |
| 1213 | } | ||
| 1214 | |||
| 1215 | /* | ||
| 1216 | * Store one patch member with a plaintext patch prefix followed by a | ||
| 1217 | * normally encoded PTCH body. The writer state owns the composite block | ||
| 1218 | * offset, packed size, flags, and final reconstructed size from the outset. | ||
| 1219 | */ | ||
| 1220 | int32_t | ||
| 1221 | 22 | libmpq__writer_patch_file_add( | |
| 1222 | mpq_archive_s *archive, const char *name, const uint8_t *prefix, uint32_t prefix_size, | ||
| 1223 | const uint8_t *body, libmpq__off_t body_size, libmpq__off_t result_size, | ||
| 1224 | const mpq_file_options_s *options | ||
| 1225 | ) | ||
| 1226 | { | ||
| 1227 | mpq_writer_s *writer; | ||
| 1228 | int32_t result; | ||
| 1229 | |||
| 1230 |
5/10✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 22 times.
|
22 | if (archive == NULL || !archive->write_patch_mode || name == NULL || prefix == NULL || |
| 1231 | ✗ | (body == NULL && body_size != 0)) | |
| 1232 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 1233 |
3/6✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
|
22 | if (body_size < 0 || result_size < 0 || (uint64_t)body_size > UINT32_MAX || |
| 1234 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | (uint64_t)result_size > UINT32_MAX) |
| 1235 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 1236 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
|
22 | if (prefix_size < LIBMPQ_PATCH_INFO_SIZE || libmpq__load_le32(prefix) != prefix_size || |
| 1237 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | libmpq__load_le32(prefix + 8) != (uint32_t)body_size || |
| 1238 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
|
22 | (libmpq__load_le32(prefix + 4) & 0x80000000u) == 0) |
| 1239 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 1240 | 22 | result = writer_file_begin( | |
| 1241 | archive, name, body_size, options, prefix, prefix_size, result_size, &writer | ||
| 1242 | ); | ||
| 1243 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (result != 0) |
| 1244 | ✗ | return result; | |
| 1245 | 22 | result = libmpq__writer_file_write(writer, body, body_size); | |
| 1246 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (result != 0) { |
| 1247 | ✗ | libmpq__writer_file_abort(writer); | |
| 1248 | ✗ | return result; | |
| 1249 | } | ||
| 1250 | 22 | return libmpq__writer_file_finish(writer); | |
| 1251 | } | ||
| 1252 | |||
| 1253 | /* Supply marker storage flags at begin, before its block and hash entries exist. */ | ||
| 1254 | int32_t | ||
| 1255 | 10 | libmpq__writer_patch_delete_marker( | |
| 1256 | mpq_archive_s *archive, const char *name, uint16_t locale, uint16_t platform | ||
| 1257 | ) | ||
| 1258 | { | ||
| 1259 | 10 | mpq_file_options_s options = { LIBMPQ_FILE_FLAG_DELETE_MARKER, 0, 0, locale, platform }; | |
| 1260 | |||
| 1261 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (archive == NULL || !archive->write_patch_mode) |
| 1262 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 1263 | 10 | return libmpq__writer_file_add(archive, name, NULL, 0, &options); | |
| 1264 | } | ||
| 1265 | |||
| 1266 | /* | ||
| 1267 | * Add a filesystem file while keeping only one input sector in memory. | ||
| 1268 | * The source length is determined first so the streaming writer can enforce | ||
| 1269 | * its declared size while reading bounded chunks from disk. | ||
| 1270 | */ | ||
| 1271 | int32_t | ||
| 1272 | 5 | libmpq__writer_file_add_path( | |
| 1273 | mpq_archive_s *a, const char *name, const char *source, const mpq_file_options_s *options | ||
| 1274 | ) | ||
| 1275 | { | ||
| 1276 | FILE *fp; | ||
| 1277 | libmpq__off_t size; | ||
| 1278 | uint8_t buffer[4096]; | ||
| 1279 | size_t got; | ||
| 1280 | int32_t result; | ||
| 1281 | mpq_writer_s *writer; | ||
| 1282 | 5 | fp = libmpq__file_open(source, "rb"); | |
| 1283 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (!fp) |
| 1284 | 1 | return LIBMPQ_ERROR_OPEN; | |
| 1285 |
3/6✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
|
8 | if (libmpq__file_seek(fp, 0, SEEK_END) < 0 || (size = libmpq__file_tell(fp)) < 0 || |
| 1286 | 4 | libmpq__file_seek(fp, 0, SEEK_SET) < 0) { | |
| 1287 | ✗ | fclose(fp); | |
| 1288 | ✗ | return LIBMPQ_ERROR_SEEK; | |
| 1289 | } | ||
| 1290 | 4 | result = libmpq__writer_file_begin(a, name, size, options, &writer); | |
| 1291 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (result < 0) { |
| 1292 | ✗ | fclose(fp); | |
| 1293 | ✗ | return result; | |
| 1294 | } | ||
| 1295 |
3/4✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
8 | while (!feof(fp) && !ferror(fp)) { |
| 1296 | 4 | got = fread(buffer, 1, sizeof(buffer), fp); | |
| 1297 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (got == 0) |
| 1298 | ✗ | break; | |
| 1299 | 4 | result = libmpq__writer_file_write(writer, buffer, (libmpq__off_t)got); | |
| 1300 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (result < 0) |
| 1301 | ✗ | break; | |
| 1302 | } | ||
| 1303 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (ferror(fp)) |
| 1304 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 1305 | 4 | fclose(fp); | |
| 1306 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (result == 0) |
| 1307 | 4 | result = libmpq__writer_file_finish(writer); | |
| 1308 | else | ||
| 1309 | ✗ | libmpq__writer_file_abort(writer); | |
| 1310 | 4 | return result; | |
| 1311 | } | ||
| 1312 | |||
| 1313 | /* | ||
| 1314 | * Finalize a writer archive and make it readable by libmpq. | ||
| 1315 | * The public close path uses this internal wrapper to keep serialization in | ||
| 1316 | * the writer module while preserving the archive handle lifecycle. | ||
| 1317 | */ | ||
| 1318 | int32_t | ||
| 1319 | 618 | libmpq__writer_finalize(mpq_archive_s *a) | |
| 1320 | { | ||
| 1321 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 618 times.
|
618 | if (a == NULL) |
| 1322 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 1323 |
3/4✓ Branch 0 taken 85 times.
✓ Branch 1 taken 533 times.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
|
618 | if (a->write_mpqe && a->write_mpqe_ops != NULL) |
| 1324 | 85 | return a->write_mpqe_ops->finalize(a); | |
| 1325 | 533 | return finalize_archive(a); | |
| 1326 | } | ||
| 1327 |