| 1 | /* Copyright (C) 2010-2020 The RetroArch team |
| 2 | * |
| 3 | * --------------------------------------------------------------------------------------- |
| 4 | * The following license statement only applies to this file (archive_file_zlib.c). |
| 5 | * --------------------------------------------------------------------------------------- |
| 6 | * |
| 7 | * Permission is hereby granted, free of charge, |
| 8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), |
| 9 | * to deal in the Software without restriction, including without limitation the rights to |
| 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, |
| 11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
| 12 | * |
| 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
| 14 | * |
| 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 21 | */ |
| 22 | |
| 23 | #include <stdlib.h> |
| 24 | #include <string.h> |
| 25 | |
| 26 | #include <file/archive_file.h> |
| 27 | #include <streams/file_stream.h> |
| 28 | #include <retro_inline.h> |
| 29 | #include <retro_miscellaneous.h> |
| 30 | #include <encodings/crc32.h> |
| 31 | |
| 32 | #include <zlib.h> |
| 33 | |
| 34 | #ifndef CENTRAL_FILE_HEADER_SIGNATURE |
| 35 | #define CENTRAL_FILE_HEADER_SIGNATURE 0x02014b50 |
| 36 | #endif |
| 37 | |
| 38 | #ifndef END_OF_CENTRAL_DIR_SIGNATURE |
| 39 | #define END_OF_CENTRAL_DIR_SIGNATURE 0x06054b50 |
| 40 | #endif |
| 41 | |
| 42 | #define _READ_CHUNK_SIZE (128*1024) /* Read 128KiB compressed chunks */ |
| 43 | |
| 44 | enum file_archive_compression_mode |
| 45 | { |
| 46 | ZIP_MODE_STORED = 0, |
| 47 | ZIP_MODE_DEFLATED = 8 |
| 48 | }; |
| 49 | |
| 50 | typedef struct |
| 51 | { |
| 52 | struct file_archive_transfer *state; |
| 53 | uint8_t *directory; |
| 54 | uint8_t *directory_entry; |
| 55 | uint8_t *directory_end; |
| 56 | uint64_t fdoffset; |
| 57 | uint32_t boffset, csize, usize; |
| 58 | unsigned cmode; |
| 59 | z_stream *zstream; |
| 60 | uint8_t *tmpbuf; |
| 61 | uint8_t *decompressed_data; |
| 62 | } zip_context_t; |
| 63 | |
| 64 | static INLINE uint32_t read_le(const uint8_t *data, unsigned size) |
| 65 | { |
| 66 | unsigned i; |
| 67 | uint32_t val = 0; |
| 68 | |
| 69 | size *= 8; |
| 70 | for (i = 0; i < size; i += 8) |
| 71 | val |= (uint32_t)*data++ << i; |
| 72 | |
| 73 | return val; |
| 74 | } |
| 75 | |
| 76 | static void zip_context_free_stream( |
| 77 | zip_context_t *zip_context, bool keep_decompressed) |
| 78 | { |
| 79 | if (zip_context->zstream) |
| 80 | { |
| 81 | inflateEnd(zip_context->zstream); |
| 82 | free(zip_context->zstream); |
| 83 | zip_context->fdoffset = 0; |
| 84 | zip_context->csize = 0; |
| 85 | zip_context->usize = 0; |
| 86 | zip_context->zstream = NULL; |
| 87 | } |
| 88 | if (zip_context->tmpbuf) |
| 89 | { |
| 90 | free(zip_context->tmpbuf); |
| 91 | zip_context->tmpbuf = NULL; |
| 92 | } |
| 93 | if (zip_context->decompressed_data && !keep_decompressed) |
| 94 | { |
| 95 | free(zip_context->decompressed_data); |
| 96 | zip_context->decompressed_data = NULL; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | static bool zlib_stream_decompress_data_to_file_init( |
| 101 | void *context, file_archive_file_handle_t *handle, |
| 102 | const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size) |
| 103 | { |
| 104 | zip_context_t *zip_context = (zip_context_t *)context; |
| 105 | struct file_archive_transfer *state = zip_context->state; |
| 106 | uint8_t local_header_buf[4]; |
| 107 | uint8_t *local_header; |
| 108 | uint32_t offsetNL, offsetEL; |
| 109 | int64_t offsetData; |
| 110 | |
| 111 | /* free previous data and stream if left unfinished */ |
| 112 | zip_context_free_stream(zip_context, false); |
| 113 | |
| 114 | /* seek past most of the local directory header */ |
| 115 | #ifdef HAVE_MMAP |
| 116 | if (state->archive_mmap_data) |
| 117 | { |
| 118 | local_header = state->archive_mmap_data + (size_t)cdata + 26; |
| 119 | } |
| 120 | else |
| 121 | #endif |
| 122 | { |
| 123 | filestream_seek(state->archive_file, (int64_t)(size_t)cdata + 26, RETRO_VFS_SEEK_POSITION_START); |
| 124 | if (filestream_read(state->archive_file, local_header_buf, 4) != 4) |
| 125 | goto error; |
| 126 | local_header = local_header_buf; |
| 127 | } |
| 128 | |
| 129 | offsetNL = read_le(local_header, 2); /* file name length */ |
| 130 | offsetEL = read_le(local_header + 2, 2); /* extra field length */ |
| 131 | offsetData = (int64_t)(size_t)cdata + 26 + 4 + offsetNL + offsetEL; |
| 132 | |
| 133 | zip_context->fdoffset = offsetData; |
| 134 | zip_context->usize = size; |
| 135 | zip_context->csize = csize; |
| 136 | zip_context->boffset = 0; |
| 137 | zip_context->cmode = cmode; |
| 138 | zip_context->decompressed_data = (uint8_t*)malloc(size); |
| 139 | zip_context->zstream = NULL; |
| 140 | zip_context->tmpbuf = NULL; |
| 141 | |
| 142 | if (cmode == ZIP_MODE_DEFLATED) |
| 143 | { |
| 144 | /* Initialize the zlib inflate machinery */ |
| 145 | zip_context->zstream = (z_stream*)malloc(sizeof(z_stream)); |
| 146 | zip_context->tmpbuf = malloc(_READ_CHUNK_SIZE); |
| 147 | |
| 148 | zip_context->zstream->next_in = NULL; |
| 149 | zip_context->zstream->avail_in = 0; |
| 150 | zip_context->zstream->total_in = 0; |
| 151 | zip_context->zstream->next_out = zip_context->decompressed_data; |
| 152 | zip_context->zstream->avail_out = size; |
| 153 | zip_context->zstream->total_out = 0; |
| 154 | |
| 155 | zip_context->zstream->zalloc = NULL; |
| 156 | zip_context->zstream->zfree = NULL; |
| 157 | zip_context->zstream->opaque = NULL; |
| 158 | |
| 159 | if (inflateInit2(zip_context->zstream, -MAX_WBITS) != Z_OK) { |
| 160 | free(zip_context->zstream); |
| 161 | zip_context->zstream = NULL; |
| 162 | goto error; |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | return true; |
| 167 | |
| 168 | error: |
| 169 | zip_context_free_stream(zip_context, false); |
| 170 | return false; |
| 171 | } |
| 172 | |
| 173 | static int zlib_stream_decompress_data_to_file_iterate( |
| 174 | void *context, file_archive_file_handle_t *handle) |
| 175 | { |
| 176 | zip_context_t *zip_context = (zip_context_t *)context; |
| 177 | struct file_archive_transfer *state = zip_context->state; |
| 178 | int64_t rd; |
| 179 | |
| 180 | if (zip_context->cmode == ZIP_MODE_STORED) |
| 181 | { |
| 182 | #ifdef HAVE_MMAP |
| 183 | if (zip_context->state->archive_mmap_data) |
| 184 | { |
| 185 | /* Simply copy the data to the output buffer */ |
| 186 | memcpy(zip_context->decompressed_data, |
| 187 | zip_context->state->archive_mmap_data + (size_t)zip_context->fdoffset, |
| 188 | zip_context->usize); |
| 189 | } |
| 190 | else |
| 191 | #endif |
| 192 | { |
| 193 | /* Read the entire file to memory */ |
| 194 | filestream_seek(state->archive_file, zip_context->fdoffset, RETRO_VFS_SEEK_POSITION_START); |
| 195 | if (filestream_read(state->archive_file, |
| 196 | zip_context->decompressed_data, |
| 197 | zip_context->usize) < 0) |
| 198 | return -1; |
| 199 | } |
| 200 | |
| 201 | handle->data = zip_context->decompressed_data; |
| 202 | return 1; |
| 203 | } |
| 204 | else if (zip_context->cmode == ZIP_MODE_DEFLATED) |
| 205 | { |
| 206 | int to_read = MIN(zip_context->csize - zip_context->boffset, _READ_CHUNK_SIZE); |
| 207 | uint8_t *dptr; |
| 208 | if (!zip_context->zstream) |
| 209 | { |
| 210 | /* file was uncompressed or decompression finished before */ |
| 211 | return 1; |
| 212 | } |
| 213 | |
| 214 | #ifdef HAVE_MMAP |
| 215 | if (state->archive_mmap_data) |
| 216 | { |
| 217 | /* Decompress from the mapped file */ |
| 218 | dptr = state->archive_mmap_data + (size_t)zip_context->fdoffset + zip_context->boffset; |
| 219 | rd = to_read; |
| 220 | } |
| 221 | else |
| 222 | #endif |
| 223 | { |
| 224 | /* Read some compressed data from file to the temp buffer */ |
| 225 | filestream_seek(state->archive_file, zip_context->fdoffset + zip_context->boffset, |
| 226 | RETRO_VFS_SEEK_POSITION_START); |
| 227 | rd = filestream_read(state->archive_file, zip_context->tmpbuf, to_read); |
| 228 | if (rd < 0) |
| 229 | return -1; |
| 230 | dptr = zip_context->tmpbuf; |
| 231 | } |
| 232 | |
| 233 | zip_context->boffset += rd; |
| 234 | zip_context->zstream->next_in = dptr; |
| 235 | zip_context->zstream->avail_in = (uInt)rd; |
| 236 | |
| 237 | if (inflate(zip_context->zstream, 0) < 0) |
| 238 | return -1; |
| 239 | |
| 240 | if (zip_context->boffset >= zip_context->csize) |
| 241 | { |
| 242 | inflateEnd(zip_context->zstream); |
| 243 | free(zip_context->zstream); |
| 244 | zip_context->zstream = NULL; |
| 245 | |
| 246 | handle->data = zip_context->decompressed_data; |
| 247 | return 1; |
| 248 | } |
| 249 | |
| 250 | return 0; /* still more data to process */ |
| 251 | } |
| 252 | |
| 253 | /* No idea what kind of compression this is */ |
| 254 | return -1; |
| 255 | } |
| 256 | |
| 257 | static uint32_t zlib_stream_crc32_calculate(uint32_t crc, |
| 258 | const uint8_t *data, size_t length) |
| 259 | { |
| 260 | return encoding_crc32(crc, data, length); |
| 261 | } |
| 262 | |
| 263 | static bool zip_file_decompressed_handle( |
| 264 | file_archive_transfer_t *transfer, |
| 265 | file_archive_file_handle_t* handle, |
| 266 | const uint8_t *cdata, unsigned cmode, uint32_t csize, |
| 267 | uint32_t size, uint32_t crc32) |
| 268 | { |
| 269 | int ret = 0; |
| 270 | |
| 271 | transfer->backend = &zlib_backend; |
| 272 | |
| 273 | if (!transfer->backend->stream_decompress_data_to_file_init( |
| 274 | transfer->context, handle, cdata, cmode, csize, size)) |
| 275 | return false; |
| 276 | |
| 277 | do |
| 278 | { |
| 279 | ret = transfer->backend->stream_decompress_data_to_file_iterate( |
| 280 | transfer->context, handle); |
| 281 | if (ret < 0) |
| 282 | return false; |
| 283 | }while (ret == 0); |
| 284 | |
| 285 | return true; |
| 286 | } |
| 287 | |
| 288 | typedef struct |
| 289 | { |
| 290 | char *opt_file; |
| 291 | char *needle; |
| 292 | void **buf; |
| 293 | size_t size; |
| 294 | bool found; |
| 295 | } decomp_state_t; |
| 296 | |
| 297 | /* Extract the relative path (needle) from a |
| 298 | * ZIP archive (path) and allocate a buffer for it to write it in. |
| 299 | * |
| 300 | * optional_outfile if not NULL will be used to extract the file to. |
| 301 | * buf will be 0 then. |
| 302 | */ |
| 303 | |
| 304 | static int zip_file_decompressed( |
| 305 | const char *name, const char *valid_exts, |
| 306 | const uint8_t *cdata, unsigned cmode, |
| 307 | uint32_t csize, uint32_t size, |
| 308 | uint32_t crc32, struct archive_extract_userdata *userdata) |
| 309 | { |
| 310 | decomp_state_t* decomp_state = (decomp_state_t*)userdata->cb_data; |
| 311 | char last_char = name[strlen(name) - 1]; |
| 312 | /* Ignore directories. */ |
| 313 | if (last_char == '/' || last_char == '\\') |
| 314 | return 1; |
| 315 | |
| 316 | if (strstr(name, decomp_state->needle)) |
| 317 | { |
| 318 | file_archive_file_handle_t handle = {0}; |
| 319 | |
| 320 | if (zip_file_decompressed_handle(userdata->transfer, |
| 321 | &handle, cdata, cmode, csize, size, crc32)) |
| 322 | { |
| 323 | if (decomp_state->opt_file != 0) |
| 324 | { |
| 325 | /* Called in case core has need_fullpath enabled. */ |
| 326 | bool success = filestream_write_file(decomp_state->opt_file, handle.data, size); |
| 327 | |
| 328 | /* Note: Do not free handle.data here - this |
| 329 | * will be done when stream is deinitialised */ |
| 330 | handle.data = NULL; |
| 331 | |
| 332 | decomp_state->size = 0; |
| 333 | |
| 334 | if (!success) |
| 335 | return -1; |
| 336 | } |
| 337 | else |
| 338 | { |
| 339 | /* Called in case core has need_fullpath disabled. |
| 340 | * Will move decompressed content directly into |
| 341 | * RetroArch's ROM buffer. */ |
| 342 | zip_context_t *zip_context = (zip_context_t *)userdata->transfer->context; |
| 343 | |
| 344 | decomp_state->size = 0; |
| 345 | *decomp_state->buf = handle.data; |
| 346 | decomp_state->size = size; |
| 347 | /* We keep the data, prevent its deallocation during free */ |
| 348 | zip_context->decompressed_data = NULL; |
| 349 | handle.data = NULL; |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | decomp_state->found = true; |
| 354 | } |
| 355 | |
| 356 | return 1; |
| 357 | } |
| 358 | |
| 359 | static int64_t zip_file_read( |
| 360 | const char *path, |
| 361 | const char *needle, void **buf, |
| 362 | const char *optional_outfile) |
| 363 | { |
| 364 | file_archive_transfer_t state = {0}; |
| 365 | decomp_state_t decomp = {0}; |
| 366 | struct archive_extract_userdata userdata = {0}; |
| 367 | bool returnerr = true; |
| 368 | int ret = 0; |
| 369 | |
| 370 | if (needle) |
| 371 | decomp.needle = strdup(needle); |
| 372 | if (optional_outfile) |
| 373 | decomp.opt_file = strdup(optional_outfile); |
| 374 | |
| 375 | state.type = ARCHIVE_TRANSFER_INIT; |
| 376 | userdata.transfer = &state; |
| 377 | userdata.cb_data = &decomp; |
| 378 | decomp.buf = buf; |
| 379 | |
| 380 | do |
| 381 | { |
| 382 | ret = file_archive_parse_file_iterate(&state, &returnerr, path, |
| 383 | "", zip_file_decompressed, &userdata); |
| 384 | if (!returnerr) |
| 385 | break; |
| 386 | }while (ret == 0 && !decomp.found); |
| 387 | |
| 388 | file_archive_parse_file_iterate_stop(&state); |
| 389 | |
| 390 | if (decomp.opt_file) |
| 391 | free(decomp.opt_file); |
| 392 | if (decomp.needle) |
| 393 | free(decomp.needle); |
| 394 | |
| 395 | if (!decomp.found) |
| 396 | return -1; |
| 397 | |
| 398 | return (int64_t)decomp.size; |
| 399 | } |
| 400 | |
| 401 | static int zip_parse_file_init(file_archive_transfer_t *state, |
| 402 | const char *file) |
| 403 | { |
| 404 | uint8_t footer_buf[1024]; |
| 405 | uint8_t *footer = footer_buf; |
| 406 | int64_t read_pos = state->archive_size; |
| 407 | int64_t read_block = MIN(read_pos, (ssize_t)sizeof(footer_buf)); |
| 408 | int64_t directory_size, directory_offset; |
| 409 | zip_context_t *zip_context = NULL; |
| 410 | |
| 411 | /* Minimal ZIP file size is 22 bytes */ |
| 412 | if (read_block < 22) |
| 413 | return -1; |
| 414 | |
| 415 | /* Find the end of central directory record by scanning |
| 416 | * the file from the end towards the beginning. |
| 417 | */ |
| 418 | for (;;) |
| 419 | { |
| 420 | if (--footer < footer_buf) |
| 421 | { |
| 422 | if (read_pos <= 0) |
| 423 | return -1; /* reached beginning of file */ |
| 424 | |
| 425 | /* Read 21 bytes of overlaps except on the first block. */ |
| 426 | if (read_pos == state->archive_size) |
| 427 | read_pos = read_pos - read_block; |
| 428 | else |
| 429 | read_pos = MAX(read_pos - read_block + 21, 0); |
| 430 | |
| 431 | /* Seek to read_pos and read read_block bytes. */ |
| 432 | filestream_seek(state->archive_file, read_pos, RETRO_VFS_SEEK_POSITION_START); |
| 433 | if (filestream_read(state->archive_file, footer_buf, read_block) != read_block) |
| 434 | return -1; |
| 435 | |
| 436 | footer = footer_buf + read_block - 22; |
| 437 | } |
| 438 | if (read_le(footer, 4) == END_OF_CENTRAL_DIR_SIGNATURE) |
| 439 | { |
| 440 | unsigned comment_len = read_le(footer + 20, 2); |
| 441 | if (read_pos + (footer - footer_buf) + 22 + comment_len == state->archive_size) |
| 442 | break; /* found it! */ |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | /* Read directory info and do basic sanity checks. */ |
| 447 | directory_size = read_le(footer + 12, 4); |
| 448 | directory_offset = read_le(footer + 16, 4); |
| 449 | if (directory_size > state->archive_size |
| 450 | || directory_offset > state->archive_size) |
| 451 | return -1; |
| 452 | |
| 453 | /* This is a ZIP file, allocate one block of memory for both the |
| 454 | * context and the entire directory, then read the directory. |
| 455 | */ |
| 456 | zip_context = (zip_context_t*)malloc(sizeof(zip_context_t) + (size_t)directory_size); |
| 457 | zip_context->state = state; |
| 458 | zip_context->directory = (uint8_t*)(zip_context + 1); |
| 459 | zip_context->directory_entry = zip_context->directory; |
| 460 | zip_context->directory_end = zip_context->directory + (size_t)directory_size; |
| 461 | zip_context->zstream = NULL; |
| 462 | zip_context->tmpbuf = NULL; |
| 463 | zip_context->decompressed_data = NULL; |
| 464 | |
| 465 | filestream_seek(state->archive_file, directory_offset, RETRO_VFS_SEEK_POSITION_START); |
| 466 | if (filestream_read(state->archive_file, zip_context->directory, directory_size) != directory_size) |
| 467 | { |
| 468 | free(zip_context); |
| 469 | return -1; |
| 470 | } |
| 471 | |
| 472 | state->context = zip_context; |
| 473 | state->step_total = read_le(footer + 10, 2); /* total entries */; |
| 474 | |
| 475 | return 0; |
| 476 | } |
| 477 | |
| 478 | static int zip_parse_file_iterate_step_internal( |
| 479 | zip_context_t * zip_context, char *filename, |
| 480 | const uint8_t **cdata, |
| 481 | unsigned *cmode, uint32_t *size, uint32_t *csize, |
| 482 | uint32_t *checksum, unsigned *payback) |
| 483 | { |
| 484 | uint8_t *entry = zip_context->directory_entry; |
| 485 | uint32_t signature, namelength, extralength, commentlength, offset; |
| 486 | |
| 487 | if (entry < zip_context->directory || entry >= zip_context->directory_end) |
| 488 | return 0; |
| 489 | |
| 490 | signature = read_le(zip_context->directory_entry + 0, 4); |
| 491 | |
| 492 | if (signature != CENTRAL_FILE_HEADER_SIGNATURE) |
| 493 | return 0; |
| 494 | |
| 495 | *cmode = read_le(zip_context->directory_entry + 10, 2); /* compression mode, 0 = store, 8 = deflate */ |
| 496 | *checksum = read_le(zip_context->directory_entry + 16, 4); /* CRC32 */ |
| 497 | *csize = read_le(zip_context->directory_entry + 20, 4); /* compressed size */ |
| 498 | *size = read_le(zip_context->directory_entry + 24, 4); /* uncompressed size */ |
| 499 | |
| 500 | namelength = read_le(zip_context->directory_entry + 28, 2); /* file name length */ |
| 501 | extralength = read_le(zip_context->directory_entry + 30, 2); /* extra field length */ |
| 502 | commentlength = read_le(zip_context->directory_entry + 32, 2); /* file comment length */ |
| 503 | |
| 504 | if (namelength >= PATH_MAX_LENGTH) |
| 505 | return -1; |
| 506 | |
| 507 | memcpy(filename, zip_context->directory_entry + 46, namelength); /* file name */ |
| 508 | filename[namelength] = '\0'; |
| 509 | |
| 510 | offset = read_le(zip_context->directory_entry + 42, 4); /* relative offset of local file header */ |
| 511 | |
| 512 | *cdata = (uint8_t*)(size_t)offset; /* store file offset in data pointer */ |
| 513 | |
| 514 | *payback = 46 + namelength + extralength + commentlength; |
| 515 | |
| 516 | return 1; |
| 517 | } |
| 518 | |
| 519 | static int zip_parse_file_iterate_step(void *context, |
| 520 | const char *valid_exts, struct archive_extract_userdata *userdata, |
| 521 | file_archive_file_cb file_cb) |
| 522 | { |
| 523 | zip_context_t *zip_context = (zip_context_t *)context; |
| 524 | const uint8_t *cdata = NULL; |
| 525 | uint32_t checksum = 0; |
| 526 | uint32_t size = 0; |
| 527 | uint32_t csize = 0; |
| 528 | unsigned cmode = 0; |
| 529 | unsigned payload = 0; |
| 530 | int ret = zip_parse_file_iterate_step_internal(zip_context, |
| 531 | userdata->current_file_path, &cdata, &cmode, &size, &csize, &checksum, &payload); |
| 532 | |
| 533 | if (ret != 1) |
| 534 | return ret; |
| 535 | |
| 536 | userdata->crc = checksum; |
| 537 | |
| 538 | if (file_cb && !file_cb(userdata->current_file_path, valid_exts, cdata, cmode, |
| 539 | csize, size, checksum, userdata)) |
| 540 | return 0; |
| 541 | |
| 542 | zip_context->directory_entry += payload; |
| 543 | |
| 544 | return 1; |
| 545 | } |
| 546 | |
| 547 | static void zip_parse_file_free(void *context) |
| 548 | { |
| 549 | zip_context_t *zip_context = (zip_context_t *)context; |
| 550 | zip_context_free_stream(zip_context, false); |
| 551 | free(zip_context); |
| 552 | } |
| 553 | |
| 554 | const struct file_archive_file_backend zlib_backend = { |
| 555 | zip_parse_file_init, |
| 556 | zip_parse_file_iterate_step, |
| 557 | zip_parse_file_free, |
| 558 | zlib_stream_decompress_data_to_file_init, |
| 559 | zlib_stream_decompress_data_to_file_iterate, |
| 560 | zlib_stream_crc32_calculate, |
| 561 | zip_file_read, |
| 562 | "zlib" |
| 563 | }; |