| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * mpq-patch-writer.c -- private MPQ whole-file patch 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 | |||
| 20 | #include "mpq-patch-writer.h" | ||
| 21 | #include "mpq-archive.h" | ||
| 22 | #include "mpq-crypto.h" | ||
| 23 | #include "mpq-endian.h" | ||
| 24 | #include "mpq-file.h" | ||
| 25 | #include "mpq-md5.h" | ||
| 26 | #include "mpq-patch-reader.h" | ||
| 27 | #include "mpq-reader.h" | ||
| 28 | #include "mpq-writer.h" | ||
| 29 | |||
| 30 | #include <limits.h> | ||
| 31 | #include <stdlib.h> | ||
| 32 | #include <string.h> | ||
| 33 | |||
| 34 | #define LIBMPQ_PATCH_COPY_HEADER_SIZE 68u | ||
| 35 | #define LIBMPQ_PATCH_WRITER_CAPACITY 1024u | ||
| 36 | |||
| 37 | #define LIBMPQ_BSD0_HEADER_SIZE 32u | ||
| 38 | #define LIBMPQ_BSD0_CONTROL_SIZE 12u | ||
| 39 | |||
| 40 | /* Encode zero runs and literal runs using the reader's bounded RLE format. */ | ||
| 41 | static size_t | ||
| 42 | 24 | patch_bsd0_rle(const uint8_t *raw, size_t size, uint8_t *output) | |
| 43 | { | ||
| 44 | 24 | size_t input = 0; | |
| 45 | 24 | size_t used = 4; | |
| 46 | |||
| 47 | 24 | memset(output, 0, 4); | |
| 48 |
2/2✓ Branch 0 taken 1848 times.
✓ Branch 1 taken 24 times.
|
1872 | while (input < size) { |
| 49 | 1848 | size_t count = 0; | |
| 50 | 1848 | uint8_t zero = raw[input] == 0; | |
| 51 | |||
| 52 |
6/6✓ Branch 0 taken 114172 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 113429 times.
✓ Branch 3 taken 743 times.
✓ Branch 4 taken 112348 times.
✓ Branch 5 taken 1081 times.
|
114196 | while (count < size - input && count < 128 && (raw[input + count] == 0) == zero) |
| 53 | 112348 | count++; | |
| 54 |
2/2✓ Branch 0 taken 630 times.
✓ Branch 1 taken 1218 times.
|
1848 | output[used++] = (uint8_t)(count - 1) | (zero ? 0 : 0x80u); |
| 55 |
2/2✓ Branch 0 taken 1218 times.
✓ Branch 1 taken 630 times.
|
1848 | if (!zero) { |
| 56 | 1218 | memcpy(output + used, raw + input, count); | |
| 57 | 1218 | used += count; | |
| 58 | } | ||
| 59 | 1848 | input += count; | |
| 60 | } | ||
| 61 | 24 | return used; | |
| 62 | } | ||
| 63 | |||
| 64 | /* Successful BSD0 output always uses the reader's RLE wrapper. */ | ||
| 65 | static int32_t | ||
| 66 | 24 | patch_bsd0_pack(uint8_t *raw, size_t raw_size, uint8_t **encoded, size_t *encoded_size) | |
| 67 | { | ||
| 68 | size_t rle_capacity; | ||
| 69 | size_t rle_size; | ||
| 70 | uint8_t *rle; | ||
| 71 | |||
| 72 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (raw_size > (SIZE_MAX - 4) / 2) { |
| 73 | ✗ | free(raw); | |
| 74 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 75 | } | ||
| 76 | 24 | rle_capacity = raw_size * 2 + 4; | |
| 77 | 24 | rle = malloc(rle_capacity); | |
| 78 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (rle == NULL) { |
| 79 | ✗ | free(raw); | |
| 80 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 81 | } | ||
| 82 | 24 | rle_size = patch_bsd0_rle(raw, raw_size, rle); | |
| 83 | 24 | free(raw); | |
| 84 | 24 | *encoded = rle; | |
| 85 | 24 | *encoded_size = rle_size; | |
| 86 | 24 | return 0; | |
| 87 | } | ||
| 88 | |||
| 89 | /* Build the aligned candidate, where unchanged bytes become zero differences. */ | ||
| 90 | static uint8_t * | ||
| 91 | 22 | patch_bsd0_aligned( | |
| 92 | const uint8_t *before, size_t before_size, const uint8_t *after, size_t after_size, | ||
| 93 | size_t *raw_size | ||
| 94 | ) | ||
| 95 | { | ||
| 96 | 22 | size_t overlap = before_size < after_size ? before_size : after_size; | |
| 97 | uint8_t *raw; | ||
| 98 | |||
| 99 | 22 | *raw_size = LIBMPQ_BSD0_HEADER_SIZE + LIBMPQ_BSD0_CONTROL_SIZE + after_size; | |
| 100 | 22 | raw = calloc(1, *raw_size); | |
| 101 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (raw == NULL) |
| 102 | ✗ | return NULL; | |
| 103 | 22 | memcpy(raw, "BSDIFF40", 8); | |
| 104 | 22 | libmpq__store_le64(raw + 8, LIBMPQ_BSD0_CONTROL_SIZE); | |
| 105 | 22 | libmpq__store_le64(raw + 16, overlap); | |
| 106 | 22 | libmpq__store_le64(raw + 24, after_size); | |
| 107 | 22 | libmpq__store_le32(raw + 32, (uint32_t)overlap); | |
| 108 | 22 | libmpq__store_le32(raw + 36, (uint32_t)(after_size - overlap)); | |
| 109 |
2/2✓ Branch 0 taken 24803 times.
✓ Branch 1 taken 22 times.
|
24825 | for (size_t i = 0; i < overlap; i++) |
| 110 | 24803 | raw[LIBMPQ_BSD0_HEADER_SIZE + LIBMPQ_BSD0_CONTROL_SIZE + i] = | |
| 111 | 24803 | (uint8_t)(after[i] - before[i]); | |
| 112 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 3 times.
|
22 | if (after_size != overlap) |
| 113 | 19 | memcpy( | |
| 114 | 19 | raw + LIBMPQ_BSD0_HEADER_SIZE + LIBMPQ_BSD0_CONTROL_SIZE + overlap, after + overlap, | |
| 115 | after_size - overlap | ||
| 116 | ); | ||
| 117 | 22 | return raw; | |
| 118 | } | ||
| 119 | |||
| 120 | /* | ||
| 121 | * A BSD0 tuple's add count combines base bytes with diff bytes. Its copy count | ||
| 122 | * consumes literal extra-block bytes, not bytes from the base. The first | ||
| 123 | * tuple copies the unchanged prefix literally and seeks to the old middle. | ||
| 124 | * The second diffs overlapping middle bytes, copies inserted bytes from the | ||
| 125 | * extra block, then skips any removed old-middle bytes. The final tuple | ||
| 126 | * copies the unchanged suffix literally. Empty ranges omit their tuple. | ||
| 127 | * This matches libmpq's BSD0 decoder, not a generic bsdiff copy operation. | ||
| 128 | */ | ||
| 129 | static uint8_t * | ||
| 130 | 2 | patch_bsd0_splice( | |
| 131 | const uint8_t *before, const uint8_t *after, size_t after_size, size_t prefix, size_t suffix, | ||
| 132 | size_t old_middle, size_t *raw_size | ||
| 133 | ) | ||
| 134 | { | ||
| 135 | 2 | size_t new_middle = after_size - prefix - suffix; | |
| 136 | 2 | size_t overlap = old_middle < new_middle ? old_middle : new_middle; | |
| 137 | 2 | size_t inserted = new_middle - overlap; | |
| 138 | 2 | size_t control_count = (prefix != 0) + (overlap + inserted != 0) + (suffix != 0); | |
| 139 | 2 | size_t control_size = control_count * LIBMPQ_BSD0_CONTROL_SIZE; | |
| 140 | 2 | size_t delta_offset = LIBMPQ_BSD0_HEADER_SIZE + control_size; | |
| 141 | 2 | size_t extra_offset = delta_offset + overlap; | |
| 142 | 2 | size_t control_offset = LIBMPQ_BSD0_HEADER_SIZE; | |
| 143 | uint8_t *raw; | ||
| 144 | |||
| 145 | 2 | *raw_size = LIBMPQ_BSD0_HEADER_SIZE + control_size + after_size; | |
| 146 | 2 | raw = calloc(1, *raw_size); | |
| 147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (raw == NULL) |
| 148 | ✗ | return NULL; | |
| 149 | 2 | memcpy(raw, "BSDIFF40", 8); | |
| 150 | 2 | libmpq__store_le64(raw + 8, control_size); | |
| 151 | 2 | libmpq__store_le64(raw + 16, overlap); | |
| 152 | 2 | libmpq__store_le64(raw + 24, after_size); | |
| 153 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (prefix != 0) { |
| 154 | 2 | libmpq__store_le32(raw + control_offset + 4, (uint32_t)prefix); | |
| 155 | 2 | libmpq__store_le32(raw + control_offset + 8, (uint32_t)prefix); | |
| 156 | 2 | control_offset += LIBMPQ_BSD0_CONTROL_SIZE; | |
| 157 | 2 | memcpy(raw + extra_offset, after, prefix); | |
| 158 | } | ||
| 159 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (overlap + inserted != 0) { |
| 160 | 2 | libmpq__store_le32(raw + control_offset, (uint32_t)overlap); | |
| 161 | 2 | libmpq__store_le32(raw + control_offset + 4, (uint32_t)inserted); | |
| 162 | 2 | libmpq__store_le32(raw + control_offset + 8, (uint32_t)(old_middle - overlap)); | |
| 163 | 2 | control_offset += LIBMPQ_BSD0_CONTROL_SIZE; | |
| 164 | } | ||
| 165 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 2 times.
|
98 | for (size_t i = 0; i < overlap; i++) |
| 166 | 96 | raw[delta_offset + i] = (uint8_t)(after[prefix + i] - before[prefix + i]); | |
| 167 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (inserted != 0) |
| 168 | 2 | memcpy(raw + extra_offset + prefix, after + prefix + overlap, inserted); | |
| 169 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (suffix != 0) { |
| 170 | 2 | libmpq__store_le32(raw + control_offset + 4, (uint32_t)suffix); | |
| 171 | 2 | memcpy(raw + extra_offset + prefix + inserted, after + prefix + new_middle, suffix); | |
| 172 | } | ||
| 173 | 2 | return raw; | |
| 174 | } | ||
| 175 | |||
| 176 | /* | ||
| 177 | * PTCH BSD0 uses libmpq's existing RLE-wrapped BSDIFF40-compatible layout. | ||
| 178 | * The splice tuples above retain that decoder contract; the output is not a | ||
| 179 | * generic bsdiff patch file. | ||
| 180 | */ | ||
| 181 | int32_t | ||
| 182 | 45 | libmpq__patch_bsd0_encode( | |
| 183 | const uint8_t *before, size_t before_size, const uint8_t *after, size_t after_size, | ||
| 184 | uint8_t splice, uint8_t **encoded, size_t *encoded_size, size_t *patch_stream_size | ||
| 185 | ) | ||
| 186 | { | ||
| 187 | 45 | size_t prefix = 0; | |
| 188 | 45 | size_t suffix = 0; | |
| 189 | size_t raw_size; | ||
| 190 | uint8_t *raw; | ||
| 191 | |||
| 192 |
3/6✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 45 times.
|
45 | if (encoded == NULL || encoded_size == NULL || patch_stream_size == NULL) |
| 193 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 194 | 45 | *encoded = NULL; | |
| 195 | 45 | *encoded_size = 0; | |
| 196 | 45 | *patch_stream_size = 0; | |
| 197 |
2/8✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 45 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
45 | if ((before == NULL && before_size != 0) || (after == NULL && after_size != 0)) |
| 198 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 199 |
3/6✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45 times.
✗ Branch 5 not taken.
|
45 | if (before_size > UINT32_MAX || after_size > UINT32_MAX || |
| 200 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
|
45 | after_size > SIZE_MAX - LIBMPQ_BSD0_HEADER_SIZE - 3 * LIBMPQ_BSD0_CONTROL_SIZE || |
| 201 | after_size == 0) | ||
| 202 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 203 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 23 times.
|
45 | if (splice == 0) { |
| 204 | 22 | raw = patch_bsd0_aligned(before, before_size, after, after_size, &raw_size); | |
| 205 | } else { | ||
| 206 |
4/6✓ Branch 0 taken 5160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5160 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5137 times.
✓ Branch 5 taken 23 times.
|
5160 | while (prefix < before_size && prefix < after_size && before[prefix] == after[prefix]) |
| 207 | 5137 | prefix++; | |
| 208 |
3/4✓ Branch 0 taken 11191 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11191 times.
✗ Branch 3 not taken.
|
11192 | while (suffix < before_size - prefix && suffix < after_size - prefix && |
| 209 |
2/2✓ Branch 0 taken 11169 times.
✓ Branch 1 taken 22 times.
|
11191 | before[before_size - suffix - 1] == after[after_size - suffix - 1]) |
| 210 | 11169 | suffix++; | |
| 211 |
3/6✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
23 | if (suffix < 16 || (prefix == 0 && prefix + suffix == after_size) || |
| 212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | before_size - prefix - suffix > INT32_MAX) |
| 213 | 21 | return 0; | |
| 214 | 2 | raw = patch_bsd0_splice( | |
| 215 | 2 | before, after, after_size, prefix, suffix, before_size - prefix - suffix, &raw_size | |
| 216 | ); | ||
| 217 | } | ||
| 218 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (raw == NULL) |
| 219 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 220 | 24 | *patch_stream_size = raw_size; | |
| 221 | 24 | return patch_bsd0_pack(raw, raw_size, encoded, encoded_size); | |
| 222 | } | ||
| 223 | |||
| 224 | struct mpq_patch | ||
| 225 | { | ||
| 226 | mpq_archive_s *base; | ||
| 227 | mpq_archive_s *archive; | ||
| 228 | mpq_directory_s *directory; | ||
| 229 | char *destination; | ||
| 230 | char *temporary; | ||
| 231 | }; | ||
| 232 | |||
| 233 | /* The patch archive itself owns no references to the source archive. */ | ||
| 234 | static int32_t | ||
| 235 | 27 | patch_writer_cleanup(mpq_patch_writer_s *state, uint8_t publish) | |
| 236 | { | ||
| 237 | 27 | int32_t result = 0; | |
| 238 | int32_t cleanup; | ||
| 239 | |||
| 240 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 2 times.
|
27 | if (state->archive != NULL) { |
| 241 | 25 | result = libmpq__archive_close(state->archive); | |
| 242 | 25 | state->archive = NULL; | |
| 243 | } | ||
| 244 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | if (state->base != NULL) { |
| 245 | 27 | cleanup = libmpq__archive_close(state->base); | |
| 246 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | if (result == 0) |
| 247 | 27 | result = cleanup; | |
| 248 | } | ||
| 249 |
3/4✓ Branch 0 taken 21 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
27 | if (publish && result == 0) { |
| 250 | 21 | result = libmpq__directory_replace(state->directory, state->temporary, state->destination); | |
| 251 | } | ||
| 252 |
3/4✓ Branch 0 taken 21 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
|
27 | if (!publish || result != 0) { |
| 253 | 6 | cleanup = libmpq__directory_remove(state->directory, state->temporary); | |
| 254 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (result == 0) |
| 255 | 6 | result = cleanup; | |
| 256 | } | ||
| 257 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | if (state->directory != NULL) |
| 258 | 27 | libmpq__directory_close(state->directory); | |
| 259 | 27 | free(state->destination); | |
| 260 | 27 | free(state->temporary); | |
| 261 | 27 | free(state); | |
| 262 | 27 | return result; | |
| 263 | } | ||
| 264 | |||
| 265 | /* Resolve a unique named base identity, retaining its locale and platform. */ | ||
| 266 | static int32_t | ||
| 267 | 35 | patch_writer_member( | |
| 268 | mpq_patch_writer_s *state, const char *name, uint32_t *number, uint16_t *locale, | ||
| 269 | uint16_t *platform | ||
| 270 | ) | ||
| 271 | { | ||
| 272 | uint32_t hash1; | ||
| 273 | uint32_t hash_a; | ||
| 274 | uint32_t hash_b; | ||
| 275 | uint32_t block; | ||
| 276 | 35 | uint32_t found = UINT32_MAX; | |
| 277 | int32_t result; | ||
| 278 | |||
| 279 |
2/4✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 35 times.
|
35 | if (name == NULL || name[0] == '\0') |
| 280 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 281 | 35 | result = libmpq__file_number(state->base, name, number); | |
| 282 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 34 times.
|
35 | if (result != 0) |
| 283 | 1 | return result; | |
| 284 | 34 | block = state->base->mpq_map[*number].block_table_indices; | |
| 285 | 34 | libmpq__file_hash(name, &hash1, &hash_a, &hash_b); | |
| 286 |
2/2✓ Branch 0 taken 1120 times.
✓ Branch 1 taken 34 times.
|
1154 | for (uint32_t i = 0; i < state->base->mpq_header.hash_table_count; i++) { |
| 287 | 1120 | const mpq_hash_s *entry = &state->base->mpq_hash[i]; | |
| 288 | |||
| 289 |
3/4✓ Branch 0 taken 34 times.
✓ Branch 1 taken 1086 times.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
|
1120 | if (entry->block_table_index == block && entry->hash_a == hash_a && |
| 290 |
1/2✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
|
34 | entry->hash_b == hash_b) { |
| 291 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | if (found != UINT32_MAX) |
| 292 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 293 | 34 | found = i; | |
| 294 | } | ||
| 295 | } | ||
| 296 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
|
34 | if (found == UINT32_MAX) |
| 297 | ✗ | return LIBMPQ_ERROR_FORMAT; | |
| 298 | 34 | *locale = state->base->mpq_hash[found].locale; | |
| 299 | 34 | *platform = state->base->mpq_hash[found].platform; | |
| 300 | 34 | return 0; | |
| 301 | } | ||
| 302 | |||
| 303 | /* Read a named base member with its filename-derived decryption key. */ | ||
| 304 | static int32_t | ||
| 305 | 22 | patch_writer_read_base( | |
| 306 | mpq_patch_writer_s *state, const char *name, uint32_t number, uint8_t **data, size_t *size | ||
| 307 | ) | ||
| 308 | { | ||
| 309 | 22 | libmpq__off_t length = 0; | |
| 310 | 22 | libmpq__off_t transferred = 0; | |
| 311 | 22 | uint8_t *bytes = NULL; | |
| 312 | int32_t result; | ||
| 313 | |||
| 314 | 22 | *data = NULL; | |
| 315 | 22 | *size = 0; | |
| 316 | 22 | result = libmpq__file_size_unpacked(state->base, number, &length); | |
| 317 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (result != 0) |
| 318 | ✗ | return result; | |
| 319 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
22 | if (length < 0 || (uint64_t)length > UINT32_MAX || (uint64_t)length > SIZE_MAX) |
| 320 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 321 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | bytes = malloc(length == 0 ? 1 : (size_t)length); |
| 322 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (bytes == NULL) |
| 323 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 324 | 22 | result = libmpq__reader_offsets_acquire(state->base, number, name); | |
| 325 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) { |
| 326 | 22 | result = libmpq__file_read(state->base, number, bytes, length, &transferred); | |
| 327 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
|
22 | if (result == 0 && transferred != length) |
| 328 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 329 | { | ||
| 330 | 22 | int32_t release = libmpq__reader_offsets_release(state->base, number); | |
| 331 | |||
| 332 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) |
| 333 | 22 | result = release; | |
| 334 | } | ||
| 335 | } | ||
| 336 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) { |
| 337 | 22 | *data = bytes; | |
| 338 | 22 | *size = (size_t)length; | |
| 339 | 22 | bytes = NULL; | |
| 340 | } | ||
| 341 | 22 | free(bytes); | |
| 342 | 22 | return result; | |
| 343 | } | ||
| 344 | |||
| 345 | /* Build one complete PTCH candidate without changing its transform afterward. */ | ||
| 346 | static int32_t | ||
| 347 | 22 | patch_writer_build_payload( | |
| 348 | size_t before_size, size_t after_size, const uint8_t *transform, size_t transform_size, | ||
| 349 | size_t patch_stream_size, uint8_t use_bsd0, const uint8_t before_md5[LIBMPQ_MD5_SIZE], | ||
| 350 | const uint8_t after_md5[LIBMPQ_MD5_SIZE], uint8_t **payload, size_t *payload_size | ||
| 351 | ) | ||
| 352 | { | ||
| 353 | static const uint8_t ptch_tag[4] = { 'P', 'T', 'C', 'H' }; | ||
| 354 | static const uint8_t md5_tag[4] = { 'M', 'D', '5', '_' }; | ||
| 355 | static const uint8_t xfrm_tag[4] = { 'X', 'F', 'R', 'M' }; | ||
| 356 | static const uint8_t copy_tag[4] = { 'C', 'O', 'P', 'Y' }; | ||
| 357 | static const uint8_t bsd0_tag[4] = { 'B', 'S', 'D', '0' }; | ||
| 358 | size_t encoded_size; | ||
| 359 | uint8_t *raw; | ||
| 360 | uint8_t *patch; | ||
| 361 | mpq_md5_s md5; | ||
| 362 | |||
| 363 | 22 | *payload = NULL; | |
| 364 | 22 | *payload_size = 0; | |
| 365 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
22 | if (transform_size > UINT32_MAX - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 366 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | patch_stream_size > UINT32_MAX - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 367 | transform_size > SIZE_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE) | ||
| 368 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 369 | 22 | encoded_size = LIBMPQ_PATCH_COPY_HEADER_SIZE + transform_size; | |
| 370 | 22 | raw = calloc(1, LIBMPQ_PATCH_INFO_SIZE + encoded_size); | |
| 371 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | if (raw == NULL) |
| 372 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 373 | 22 | patch = raw + LIBMPQ_PATCH_INFO_SIZE; | |
| 374 | 22 | libmpq__store_le32(raw, LIBMPQ_PATCH_INFO_SIZE); | |
| 375 | 22 | libmpq__store_le32(raw + 4, 0x80000000u); | |
| 376 | 22 | libmpq__store_le32(raw + 8, (uint32_t)encoded_size); | |
| 377 | 22 | memcpy(patch, ptch_tag, sizeof(ptch_tag)); | |
| 378 | 22 | libmpq__store_le32(patch + 4, (uint32_t)(LIBMPQ_PATCH_COPY_HEADER_SIZE + patch_stream_size)); | |
| 379 | 22 | libmpq__store_le32(patch + 8, (uint32_t)before_size); | |
| 380 | 22 | libmpq__store_le32(patch + 12, (uint32_t)after_size); | |
| 381 | 22 | memcpy(patch + 16, md5_tag, sizeof(md5_tag)); | |
| 382 | 22 | libmpq__store_le32(patch + 20, 40); | |
| 383 | 22 | memcpy(patch + 24, before_md5, LIBMPQ_MD5_SIZE); | |
| 384 | 22 | memcpy(patch + 40, after_md5, LIBMPQ_MD5_SIZE); | |
| 385 | 22 | memcpy(patch + 56, xfrm_tag, sizeof(xfrm_tag)); | |
| 386 | 22 | libmpq__store_le32(patch + 60, (uint32_t)transform_size + 12); | |
| 387 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
|
22 | memcpy(patch + 64, use_bsd0 ? bsd0_tag : copy_tag, sizeof(copy_tag)); |
| 388 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (transform_size != 0) |
| 389 | 22 | memcpy(patch + LIBMPQ_PATCH_COPY_HEADER_SIZE, transform, transform_size); | |
| 390 | 22 | libmpq__md5_init(&md5); | |
| 391 | 22 | libmpq__md5_update(&md5, patch, encoded_size); | |
| 392 | 22 | libmpq__md5_final(&md5, raw + 12); | |
| 393 | |||
| 394 | 22 | *payload = raw; | |
| 395 | 22 | *payload_size = LIBMPQ_PATCH_INFO_SIZE + encoded_size; | |
| 396 | 22 | return 0; | |
| 397 | } | ||
| 398 | |||
| 399 | /* Select BSD0 only when its complete candidate is smaller and decodes correctly. */ | ||
| 400 | static int32_t | ||
| 401 | 22 | patch_writer_payload( | |
| 402 | const uint8_t *before, size_t before_size, const uint8_t *after, size_t after_size, | ||
| 403 | uint8_t after_md5[LIBMPQ_MD5_SIZE], uint8_t **payload, size_t *payload_size | ||
| 404 | ) | ||
| 405 | { | ||
| 406 | uint8_t before_md5[LIBMPQ_MD5_SIZE]; | ||
| 407 | mpq_md5_s md5; | ||
| 408 | 22 | size_t transform_size = 0; | |
| 409 | 22 | size_t patch_stream_size = 0; | |
| 410 | 22 | size_t verified_size = 0; | |
| 411 | 22 | uint8_t *transform = NULL; | |
| 412 | 22 | uint8_t *candidate = NULL; | |
| 413 | 22 | uint8_t *best = NULL; | |
| 414 | 22 | uint8_t *verified = NULL; | |
| 415 | 22 | size_t candidate_size = 0; | |
| 416 | size_t best_size; | ||
| 417 | int32_t result; | ||
| 418 | |||
| 419 | 22 | *payload = NULL; | |
| 420 | 22 | *payload_size = 0; | |
| 421 |
2/4✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
22 | if (before_size > UINT32_MAX || |
| 422 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
|
22 | after_size > UINT32_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 423 | after_size > SIZE_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE) | ||
| 424 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 425 | 22 | best_size = LIBMPQ_PATCH_INFO_SIZE + LIBMPQ_PATCH_COPY_HEADER_SIZE + after_size; | |
| 426 | 22 | libmpq__md5_init(&md5); | |
| 427 | 22 | libmpq__md5_update(&md5, before, before_size); | |
| 428 | 22 | libmpq__md5_final(&md5, before_md5); | |
| 429 | 22 | libmpq__md5_init(&md5); | |
| 430 | 22 | libmpq__md5_update(&md5, after, after_size); | |
| 431 | 22 | libmpq__md5_final(&md5, after_md5); | |
| 432 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (after_size != 0) { |
| 433 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 22 times.
|
66 | for (uint8_t splice = 0; splice < 2; splice++) { |
| 434 | 44 | result = libmpq__patch_bsd0_encode( | |
| 435 | before, before_size, after, after_size, splice, &transform, &transform_size, | ||
| 436 | &patch_stream_size | ||
| 437 | ); | ||
| 438 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
|
44 | if (result != 0) { |
| 439 | ✗ | free(best); | |
| 440 | ✗ | return result; | |
| 441 | } | ||
| 442 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 23 times.
|
44 | if (transform == NULL) |
| 443 | 21 | continue; | |
| 444 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | if (patch_stream_size > UINT32_MAX - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 445 |
1/2✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
|
23 | transform_size > UINT32_MAX - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 446 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
|
23 | transform_size > |
| 447 | SIZE_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE) { | ||
| 448 | ✗ | free(transform); | |
| 449 | ✗ | transform = NULL; | |
| 450 | ✗ | continue; | |
| 451 | } | ||
| 452 | |||
| 453 | /* Equal or larger candidates cannot win; only contenders need validation. */ | ||
| 454 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 2 times.
|
23 | if (LIBMPQ_PATCH_INFO_SIZE + LIBMPQ_PATCH_COPY_HEADER_SIZE + transform_size >= |
| 455 | best_size) { | ||
| 456 | 21 | free(transform); | |
| 457 | 21 | transform = NULL; | |
| 458 | 21 | continue; | |
| 459 | } | ||
| 460 | 2 | result = patch_writer_build_payload( | |
| 461 | before_size, after_size, transform, transform_size, patch_stream_size, 1, | ||
| 462 | before_md5, after_md5, &candidate, &candidate_size | ||
| 463 | ); | ||
| 464 | 2 | free(transform); | |
| 465 | 2 | transform = NULL; | |
| 466 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (result != 0) { |
| 467 | ✗ | free(best); | |
| 468 | ✗ | return result; | |
| 469 | } | ||
| 470 | 2 | result = libmpq__patch_apply( | |
| 471 | 2 | before, before_size, candidate + LIBMPQ_PATCH_INFO_SIZE, | |
| 472 | candidate_size - LIBMPQ_PATCH_INFO_SIZE, &verified, &verified_size | ||
| 473 | ); | ||
| 474 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | if (result == 0 && verified_size == after_size && verified != NULL && |
| 475 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | memcmp(verified, after, after_size) == 0 && candidate_size < best_size) { |
| 476 | 2 | free(best); | |
| 477 | 2 | best = candidate; | |
| 478 | 2 | best_size = candidate_size; | |
| 479 | 2 | candidate = NULL; | |
| 480 | } | ||
| 481 | 2 | free(verified); | |
| 482 | 2 | verified = NULL; | |
| 483 | 2 | free(candidate); | |
| 484 | 2 | candidate = NULL; | |
| 485 | } | ||
| 486 | } | ||
| 487 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
|
22 | if (best != NULL) { |
| 488 | 2 | *payload = best; | |
| 489 | 2 | *payload_size = best_size; | |
| 490 | 2 | return 0; | |
| 491 | } | ||
| 492 | 20 | return patch_writer_build_payload( | |
| 493 | before_size, after_size, after, after_size, after_size, 0, before_md5, after_md5, payload, | ||
| 494 | payload_size | ||
| 495 | ); | ||
| 496 | } | ||
| 497 | |||
| 498 | /* Refuse to publish a patch over its own base archive. */ | ||
| 499 | static int32_t | ||
| 500 | 27 | patch_writer_check_destination(mpq_patch_writer_s *state) | |
| 501 | { | ||
| 502 | 27 | FILE *existing = libmpq__directory_file_open(state->directory, state->destination, "rb"); | |
| 503 | uint64_t device; | ||
| 504 | uint64_t inode; | ||
| 505 | 27 | int32_t result = 0; | |
| 506 | |||
| 507 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | if (existing == NULL) |
| 508 | 27 | return 0; | |
| 509 | ✗ | if (state->base->file_identity_valid) { | |
| 510 | ✗ | result = libmpq__file_identity(existing, &device, &inode); | |
| 511 | ✗ | if (result == 0 && device == state->base->file_device && inode == state->base->file_inode) | |
| 512 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 513 | } | ||
| 514 | ✗ | if (fclose(existing) != 0 && result == 0) | |
| 515 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 516 | ✗ | return result; | |
| 517 | } | ||
| 518 | |||
| 519 | /* Resolve the sibling temporary path for the existing MPQE archive writer. */ | ||
| 520 | static char * | ||
| 521 | 6 | patch_writer_staged_path(const char *absolute, const char *temporary) | |
| 522 | { | ||
| 523 | 6 | const char *slash = strrchr(absolute, '/'); | |
| 524 | size_t directory_size; | ||
| 525 | 6 | size_t name_size = strlen(temporary); | |
| 526 | char *path; | ||
| 527 | |||
| 528 | #ifdef _WIN32 | ||
| 529 | { | ||
| 530 | const char *backslash = strrchr(absolute, '\\'); | ||
| 531 | |||
| 532 | if (backslash != NULL && (slash == NULL || backslash > slash)) | ||
| 533 | slash = backslash; | ||
| 534 | } | ||
| 535 | #endif | ||
| 536 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (slash == NULL) |
| 537 | ✗ | return NULL; | |
| 538 | 6 | directory_size = (size_t)(slash - absolute) + 1; | |
| 539 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (directory_size > SIZE_MAX - name_size - 1) |
| 540 | ✗ | return NULL; | |
| 541 | 6 | path = malloc(directory_size + name_size + 1); | |
| 542 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (path != NULL) { |
| 543 | 6 | memcpy(path, absolute, directory_size); | |
| 544 | 6 | memcpy(path + directory_size, temporary, name_size + 1); | |
| 545 | } | ||
| 546 | 6 | return path; | |
| 547 | } | ||
| 548 | |||
| 549 | /* Stage a same-directory patch; MPQE output delegates to the existing writer. */ | ||
| 550 | static int32_t | ||
| 551 | 31 | patch_writer_begin_internal( | |
| 552 | mpq_patch_writer_s **patch_writer, const char *base_path, const char *patch_path, | ||
| 553 | const uint8_t *auth_code, size_t auth_code_size, uint8_t mpqe | ||
| 554 | ) | ||
| 555 | { | ||
| 556 | 31 | mpq_patch_writer_s *state = NULL; | |
| 557 | mpq_archive_create_options_s options; | ||
| 558 | 31 | FILE *temporary_file = NULL; | |
| 559 | 31 | char *absolute = NULL; | |
| 560 | 31 | char *staged_path = NULL; | |
| 561 | int32_t result; | ||
| 562 | |||
| 563 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
|
31 | if (patch_writer == NULL) |
| 564 | 2 | return LIBMPQ_ERROR_EXIST; | |
| 565 | 29 | *patch_writer = NULL; | |
| 566 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
|
29 | if (base_path == NULL || patch_path == NULL) |
| 567 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 568 | 29 | state = calloc(1, sizeof(*state)); | |
| 569 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (state == NULL) |
| 570 | ✗ | return LIBMPQ_ERROR_MALLOC; | |
| 571 | 29 | result = libmpq__archive_open(&state->base, base_path, 0); | |
| 572 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 27 times.
|
29 | if (result != 0) |
| 573 | 2 | goto fail; | |
| 574 |
1/2✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
|
27 | if (state->base->archive_offset != 0 || |
| 575 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | state->base->mpq_header.version > LIBMPQ_ARCHIVE_VERSION_TWO) { |
| 576 | ✗ | result = LIBMPQ_ERROR_FORMAT; | |
| 577 | ✗ | goto fail; | |
| 578 | } | ||
| 579 | 27 | absolute = libmpq__file_absolute_path(patch_path); | |
| 580 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (absolute == NULL) { |
| 581 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 582 | ✗ | goto fail; | |
| 583 | } | ||
| 584 | 27 | result = libmpq__directory_open(absolute, &state->directory, &state->destination); | |
| 585 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (result != 0) |
| 586 | ✗ | goto fail; | |
| 587 | 27 | result = patch_writer_check_destination(state); | |
| 588 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (result != 0) |
| 589 | ✗ | goto fail; | |
| 590 | 27 | result = libmpq__directory_temporary( | |
| 591 | state->directory, ".libmpq-patch-write-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 1, | ||
| 592 | &state->temporary, &temporary_file | ||
| 593 | ); | ||
| 594 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
|
27 | if (result != 0) |
| 595 | ✗ | goto fail; | |
| 596 | 27 | options.version = state->base->mpq_header.version; | |
| 597 | 27 | options.max_files = LIBMPQ_PATCH_WRITER_CAPACITY; | |
| 598 | 27 | options.sector_size = state->base->block_size; | |
| 599 | 27 | options.flags = LIBMPQ_ARCHIVE_CREATE_LISTFILE; | |
| 600 | 27 | options.attributes = LIBMPQ_ATTRIBUTE_CRC32 | LIBMPQ_ATTRIBUTE_MD5 | LIBMPQ_ATTRIBUTE_PATCH_BIT; | |
| 601 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 21 times.
|
27 | if (mpqe) { |
| 602 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if (fclose(temporary_file) != 0) { |
| 603 | ✗ | temporary_file = NULL; | |
| 604 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 605 | ✗ | goto fail; | |
| 606 | } | ||
| 607 | 6 | temporary_file = NULL; | |
| 608 | 6 | staged_path = patch_writer_staged_path(absolute, state->temporary); | |
| 609 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (staged_path == NULL) { |
| 610 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 611 | ✗ | goto fail; | |
| 612 | } | ||
| 613 | 6 | result = libmpq__writer_archive_create_mpqe( | |
| 614 | &state->archive, staged_path, auth_code, auth_code_size, &options | ||
| 615 | ); | ||
| 616 | } else { | ||
| 617 | 21 | result = libmpq__writer_archive_create_file( | |
| 618 | 21 | &state->archive, state->temporary, temporary_file, &options | |
| 619 | ); | ||
| 620 | 21 | temporary_file = NULL; | |
| 621 | } | ||
| 622 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 25 times.
|
27 | if (result != 0) |
| 623 | 2 | goto fail; | |
| 624 | 25 | state->archive->write_patch_mode = 1; | |
| 625 | 25 | free(staged_path); | |
| 626 | 25 | free(absolute); | |
| 627 | 25 | *patch_writer = state; | |
| 628 | 25 | return 0; | |
| 629 | |||
| 630 | 4 | fail: | |
| 631 | 4 | free(staged_path); | |
| 632 | 4 | free(absolute); | |
| 633 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (temporary_file != NULL) |
| 634 | ✗ | (void)fclose(temporary_file); | |
| 635 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (state->temporary != NULL) |
| 636 | 2 | (void)patch_writer_cleanup(state, 0); | |
| 637 | else { | ||
| 638 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (state->base != NULL) |
| 639 | ✗ | (void)libmpq__archive_close(state->base); | |
| 640 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (state->directory != NULL) |
| 641 | ✗ | libmpq__directory_close(state->directory); | |
| 642 | 2 | free(state->destination); | |
| 643 | 2 | free(state); | |
| 644 | } | ||
| 645 | 4 | return result; | |
| 646 | } | ||
| 647 | |||
| 648 | /* Preserve ordinary MPQ patch creation and its existing lifecycle. */ | ||
| 649 | int32_t | ||
| 650 | 24 | libmpq__patch_writer_begin( | |
| 651 | mpq_patch_writer_s **patch_writer, const char *base_path, const char *patch_path | ||
| 652 | ) | ||
| 653 | { | ||
| 654 | 24 | return patch_writer_begin_internal(patch_writer, base_path, patch_path, NULL, 0, 0); | |
| 655 | } | ||
| 656 | |||
| 657 | /* Stage an MPQE patch using the archive writer's authentication and encryption. */ | ||
| 658 | int32_t | ||
| 659 | 7 | libmpq__patch_writer_begin_mpqe( | |
| 660 | mpq_patch_writer_s **patch_writer, const char *base_path, const char *patch_path, | ||
| 661 | const uint8_t *auth_code, size_t auth_code_size | ||
| 662 | ) | ||
| 663 | { | ||
| 664 | 7 | return patch_writer_begin_internal( | |
| 665 | patch_writer, base_path, patch_path, auth_code, auth_code_size, 1 | ||
| 666 | ); | ||
| 667 | } | ||
| 668 | |||
| 669 | /* Let archive finalization generate the configured weak or strong signature. */ | ||
| 670 | int32_t | ||
| 671 | 30 | libmpq__patch_writer_sign( | |
| 672 | mpq_patch_writer_s *state, uint32_t signature_type, const uint8_t *private_key, | ||
| 673 | size_t private_key_size | ||
| 674 | ) | ||
| 675 | { | ||
| 676 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 29 times.
|
30 | if (state == NULL) |
| 677 | 1 | return LIBMPQ_ERROR_EXIST; | |
| 678 | 29 | return libmpq__archive_sign(state->archive, signature_type, private_key, private_key_size); | |
| 679 | } | ||
| 680 | |||
| 681 | /* Stage a named replacement, falling back to COPY unless BSD0 is smaller. */ | ||
| 682 | int32_t | ||
| 683 | 25 | libmpq__patch_writer_replace( | |
| 684 | mpq_patch_writer_s *state, const char *name, const uint8_t *data, libmpq__off_t size, | ||
| 685 | const mpq_file_options_s *options | ||
| 686 | ) | ||
| 687 | { | ||
| 688 | 25 | mpq_file_options_s storage = { LIBMPQ_FILE_FLAG_SINGLE, 0, 0, 0, 0 }; | |
| 689 | 25 | uint8_t *before = NULL; | |
| 690 | 25 | uint8_t *payload = NULL; | |
| 691 | uint8_t after_md5[LIBMPQ_MD5_SIZE]; | ||
| 692 | 25 | size_t before_size = 0; | |
| 693 | 25 | size_t payload_size = 0; | |
| 694 | uint32_t number; | ||
| 695 | uint32_t block; | ||
| 696 | uint16_t locale; | ||
| 697 | uint16_t platform; | ||
| 698 | int32_t result; | ||
| 699 | |||
| 700 |
2/6✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
25 | if (state == NULL || (data == NULL && size != 0)) |
| 701 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 702 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | if (size < 0 || |
| 703 |
1/2✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
|
25 | (uint64_t)size > UINT32_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 704 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | (uint64_t)size > SIZE_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE) |
| 705 | ✗ | return LIBMPQ_ERROR_SIZE; | |
| 706 | 25 | result = patch_writer_member(state, name, &number, &locale, &platform); | |
| 707 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 24 times.
|
25 | if (result != 0) |
| 708 | 1 | return result; | |
| 709 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 15 times.
|
24 | if (options != NULL) { |
| 710 |
3/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
9 | if (options->locale != locale || options->platform != platform || |
| 711 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | (options->flags & |
| 712 | ~(LIBMPQ_FILE_FLAG_IMPLODE | LIBMPQ_FILE_FLAG_COMPRESS | LIBMPQ_FILE_FLAG_ENCRYPTED | | ||
| 713 | LIBMPQ_FILE_FLAG_SINGLE | LIBMPQ_FILE_FLAG_SECTOR_CRC)) != 0) | ||
| 714 | 2 | return LIBMPQ_ERROR_FORMAT; | |
| 715 | 7 | storage = *options; | |
| 716 | } else { | ||
| 717 | 15 | storage.locale = locale; | |
| 718 | 15 | storage.platform = platform; | |
| 719 | } | ||
| 720 | 22 | result = patch_writer_read_base(state, name, number, &before, &before_size); | |
| 721 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) |
| 722 | 22 | result = patch_writer_payload( | |
| 723 | before, before_size, data, (size_t)size, after_md5, &payload, &payload_size | ||
| 724 | ); | ||
| 725 |
5/6✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 13 times.
|
22 | if (result == 0 && options == NULL && payload_size > state->archive->write_sector_size) |
| 726 | 2 | storage.flags = 0; | |
| 727 | |||
| 728 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) |
| 729 | 22 | result = libmpq__writer_patch_file_add( | |
| 730 | 22 | state->archive, name, payload, LIBMPQ_PATCH_INFO_SIZE, payload + LIBMPQ_PATCH_INFO_SIZE, | |
| 731 | 22 | (libmpq__off_t)(payload_size - LIBMPQ_PATCH_INFO_SIZE), size, &storage | |
| 732 | ); | ||
| 733 |
1/2✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
|
22 | if (result == 0) { |
| 734 | 22 | block = state->archive->write_next_block - 1; | |
| 735 | 22 | memcpy(state->archive->write_attributes[block].md5, after_md5, LIBMPQ_MD5_SIZE); | |
| 736 | } | ||
| 737 | 22 | free(payload); | |
| 738 | 22 | free(before); | |
| 739 | 22 | return result; | |
| 740 | } | ||
| 741 | |||
| 742 | /* Read one filesystem replacement and reuse the same staged data path. */ | ||
| 743 | int32_t | ||
| 744 | 3 | libmpq__patch_writer_replace_path( | |
| 745 | mpq_patch_writer_s *state, const char *name, const char *source_path, | ||
| 746 | const mpq_file_options_s *options | ||
| 747 | ) | ||
| 748 | { | ||
| 749 | FILE *input; | ||
| 750 | libmpq__off_t size; | ||
| 751 | 3 | uint8_t *data = NULL; | |
| 752 | int32_t result; | ||
| 753 | |||
| 754 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
3 | if (state == NULL || source_path == NULL || source_path[0] == '\0') |
| 755 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 756 | 3 | input = libmpq__file_open(source_path, "rb"); | |
| 757 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (input == NULL) |
| 758 | ✗ | return LIBMPQ_ERROR_OPEN; | |
| 759 | 3 | result = libmpq__file_seek(input, 0, SEEK_END); | |
| 760 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (result == 0) { |
| 761 | 3 | size = libmpq__file_tell(input); | |
| 762 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (size < 0 || |
| 763 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | (uint64_t)size > UINT32_MAX - LIBMPQ_PATCH_INFO_SIZE - LIBMPQ_PATCH_COPY_HEADER_SIZE || |
| 764 | (uint64_t)size > SIZE_MAX) | ||
| 765 | ✗ | result = LIBMPQ_ERROR_SIZE; | |
| 766 | } | ||
| 767 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (result == 0) |
| 768 | 3 | result = libmpq__file_seek(input, 0, SEEK_SET); | |
| 769 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (result == 0) { |
| 770 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | data = malloc(size == 0 ? 1 : (size_t)size); |
| 771 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (data == NULL) |
| 772 | ✗ | result = LIBMPQ_ERROR_MALLOC; | |
| 773 | } | ||
| 774 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
|
3 | if (result == 0 && size != 0 && fread(data, 1, (size_t)size, input) != (size_t)size) |
| 775 | ✗ | result = LIBMPQ_ERROR_READ; | |
| 776 |
1/4✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
3 | if (fclose(input) != 0 && result == 0) |
| 777 | ✗ | result = LIBMPQ_ERROR_CLOSE; | |
| 778 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (result == 0) |
| 779 | 3 | result = libmpq__patch_writer_replace(state, name, data, size, options); | |
| 780 | 3 | free(data); | |
| 781 | 3 | return result; | |
| 782 | } | ||
| 783 | |||
| 784 | /* Stage a named delete marker without touching the base archive. */ | ||
| 785 | int32_t | ||
| 786 | 10 | libmpq__patch_writer_remove(mpq_patch_writer_s *state, const char *name) | |
| 787 | { | ||
| 788 | uint16_t locale; | ||
| 789 | uint16_t platform; | ||
| 790 | uint32_t number; | ||
| 791 | int32_t result; | ||
| 792 | |||
| 793 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (state == NULL) |
| 794 | ✗ | return LIBMPQ_ERROR_EXIST; | |
| 795 | 10 | result = patch_writer_member(state, name, &number, &locale, &platform); | |
| 796 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (result != 0) |
| 797 | ✗ | return result; | |
| 798 | 10 | return libmpq__writer_patch_delete_marker(state->archive, name, locale, platform); | |
| 799 | } | ||
| 800 | |||
| 801 | /* Publish the finalized patch only after its writer has closed successfully. */ | ||
| 802 | int32_t | ||
| 803 | 23 | libmpq__patch_writer_finish(mpq_patch_writer_s *state) | |
| 804 | { | ||
| 805 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
|
23 | if (state == NULL) |
| 806 | 2 | return LIBMPQ_ERROR_EXIST; | |
| 807 | 21 | return patch_writer_cleanup(state, 1); | |
| 808 | } | ||
| 809 | |||
| 810 | /* Discard a private patch writer and its unpublished same-directory file. */ | ||
| 811 | int32_t | ||
| 812 | 5 | libmpq__patch_writer_abort(mpq_patch_writer_s *state) | |
| 813 | { | ||
| 814 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (state == NULL) |
| 815 | 1 | return LIBMPQ_ERROR_EXIST; | |
| 816 | 4 | return patch_writer_cleanup(state, 0); | |
| 817 | } | ||
| 818 |