/* Copyright (C) 2010-2020 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (archive_file_zlib.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #ifndef CENTRAL_FILE_HEADER_SIGNATURE #define CENTRAL_FILE_HEADER_SIGNATURE 0x02014b50 #endif #ifndef END_OF_CENTRAL_DIR_SIGNATURE #define END_OF_CENTRAL_DIR_SIGNATURE 0x06054b50 #endif #define _READ_CHUNK_SIZE (128*1024) /* Read 128KiB compressed chunks */ enum file_archive_compression_mode { ZIP_MODE_STORED = 0, ZIP_MODE_DEFLATED = 8 }; typedef struct { struct file_archive_transfer *state; uint8_t *directory; uint8_t *directory_entry; uint8_t *directory_end; uint64_t fdoffset; uint32_t boffset, csize, usize; unsigned cmode; z_stream *zstream; uint8_t *tmpbuf; uint8_t *decompressed_data; } zip_context_t; static INLINE uint32_t read_le(const uint8_t *data, unsigned size) { unsigned i; uint32_t val = 0; size *= 8; for (i = 0; i < size; i += 8) val |= (uint32_t)*data++ << i; return val; } static void zip_context_free_stream( zip_context_t *zip_context, bool keep_decompressed) { if (zip_context->zstream) { inflateEnd(zip_context->zstream); free(zip_context->zstream); zip_context->fdoffset = 0; zip_context->csize = 0; zip_context->usize = 0; zip_context->zstream = NULL; } if (zip_context->tmpbuf) { free(zip_context->tmpbuf); zip_context->tmpbuf = NULL; } if (zip_context->decompressed_data && !keep_decompressed) { free(zip_context->decompressed_data); zip_context->decompressed_data = NULL; } } static bool zlib_stream_decompress_data_to_file_init( void *context, file_archive_file_handle_t *handle, const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size) { zip_context_t *zip_context = (zip_context_t *)context; struct file_archive_transfer *state = zip_context->state; uint8_t local_header_buf[4]; uint8_t *local_header; uint32_t offsetNL, offsetEL; int64_t offsetData; /* free previous data and stream if left unfinished */ zip_context_free_stream(zip_context, false); /* seek past most of the local directory header */ #ifdef HAVE_MMAP if (state->archive_mmap_data) { local_header = state->archive_mmap_data + (size_t)cdata + 26; } else #endif { filestream_seek(state->archive_file, (int64_t)(size_t)cdata + 26, RETRO_VFS_SEEK_POSITION_START); if (filestream_read(state->archive_file, local_header_buf, 4) != 4) goto error; local_header = local_header_buf; } offsetNL = read_le(local_header, 2); /* file name length */ offsetEL = read_le(local_header + 2, 2); /* extra field length */ offsetData = (int64_t)(size_t)cdata + 26 + 4 + offsetNL + offsetEL; zip_context->fdoffset = offsetData; zip_context->usize = size; zip_context->csize = csize; zip_context->boffset = 0; zip_context->cmode = cmode; zip_context->decompressed_data = (uint8_t*)malloc(size); zip_context->zstream = NULL; zip_context->tmpbuf = NULL; if (cmode == ZIP_MODE_DEFLATED) { /* Initialize the zlib inflate machinery */ zip_context->zstream = (z_stream*)malloc(sizeof(z_stream)); zip_context->tmpbuf = malloc(_READ_CHUNK_SIZE); zip_context->zstream->next_in = NULL; zip_context->zstream->avail_in = 0; zip_context->zstream->total_in = 0; zip_context->zstream->next_out = zip_context->decompressed_data; zip_context->zstream->avail_out = size; zip_context->zstream->total_out = 0; zip_context->zstream->zalloc = NULL; zip_context->zstream->zfree = NULL; zip_context->zstream->opaque = NULL; if (inflateInit2(zip_context->zstream, -MAX_WBITS) != Z_OK) { free(zip_context->zstream); zip_context->zstream = NULL; goto error; } } return true; error: zip_context_free_stream(zip_context, false); return false; } static int zlib_stream_decompress_data_to_file_iterate( void *context, file_archive_file_handle_t *handle) { zip_context_t *zip_context = (zip_context_t *)context; struct file_archive_transfer *state = zip_context->state; int64_t rd; if (zip_context->cmode == ZIP_MODE_STORED) { #ifdef HAVE_MMAP if (zip_context->state->archive_mmap_data) { /* Simply copy the data to the output buffer */ memcpy(zip_context->decompressed_data, zip_context->state->archive_mmap_data + (size_t)zip_context->fdoffset, zip_context->usize); } else #endif { /* Read the entire file to memory */ filestream_seek(state->archive_file, zip_context->fdoffset, RETRO_VFS_SEEK_POSITION_START); if (filestream_read(state->archive_file, zip_context->decompressed_data, zip_context->usize) < 0) return -1; } handle->data = zip_context->decompressed_data; return 1; } else if (zip_context->cmode == ZIP_MODE_DEFLATED) { int to_read = MIN(zip_context->csize - zip_context->boffset, _READ_CHUNK_SIZE); uint8_t *dptr; if (!zip_context->zstream) { /* file was uncompressed or decompression finished before */ return 1; } #ifdef HAVE_MMAP if (state->archive_mmap_data) { /* Decompress from the mapped file */ dptr = state->archive_mmap_data + (size_t)zip_context->fdoffset + zip_context->boffset; rd = to_read; } else #endif { /* Read some compressed data from file to the temp buffer */ filestream_seek(state->archive_file, zip_context->fdoffset + zip_context->boffset, RETRO_VFS_SEEK_POSITION_START); rd = filestream_read(state->archive_file, zip_context->tmpbuf, to_read); if (rd < 0) return -1; dptr = zip_context->tmpbuf; } zip_context->boffset += rd; zip_context->zstream->next_in = dptr; zip_context->zstream->avail_in = (uInt)rd; if (inflate(zip_context->zstream, 0) < 0) return -1; if (zip_context->boffset >= zip_context->csize) { inflateEnd(zip_context->zstream); free(zip_context->zstream); zip_context->zstream = NULL; handle->data = zip_context->decompressed_data; return 1; } return 0; /* still more data to process */ } /* No idea what kind of compression this is */ return -1; } static uint32_t zlib_stream_crc32_calculate(uint32_t crc, const uint8_t *data, size_t length) { return encoding_crc32(crc, data, length); } static bool zip_file_decompressed_handle( file_archive_transfer_t *transfer, file_archive_file_handle_t* handle, const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size, uint32_t crc32) { int ret = 0; transfer->backend = &zlib_backend; if (!transfer->backend->stream_decompress_data_to_file_init( transfer->context, handle, cdata, cmode, csize, size)) return false; do { ret = transfer->backend->stream_decompress_data_to_file_iterate( transfer->context, handle); if (ret < 0) return false; }while (ret == 0); return true; } typedef struct { char *opt_file; char *needle; void **buf; size_t size; bool found; } decomp_state_t; /* Extract the relative path (needle) from a * ZIP archive (path) and allocate a buffer for it to write it in. * * optional_outfile if not NULL will be used to extract the file to. * buf will be 0 then. */ static int zip_file_decompressed( const char *name, const char *valid_exts, const uint8_t *cdata, unsigned cmode, uint32_t csize, uint32_t size, uint32_t crc32, struct archive_extract_userdata *userdata) { decomp_state_t* decomp_state = (decomp_state_t*)userdata->cb_data; char last_char = name[strlen(name) - 1]; /* Ignore directories. */ if (last_char == '/' || last_char == '\\') return 1; if (strstr(name, decomp_state->needle)) { file_archive_file_handle_t handle = {0}; if (zip_file_decompressed_handle(userdata->transfer, &handle, cdata, cmode, csize, size, crc32)) { if (decomp_state->opt_file != 0) { /* Called in case core has need_fullpath enabled. */ bool success = filestream_write_file(decomp_state->opt_file, handle.data, size); /* Note: Do not free handle.data here - this * will be done when stream is deinitialised */ handle.data = NULL; decomp_state->size = 0; if (!success) return -1; } else { /* Called in case core has need_fullpath disabled. * Will move decompressed content directly into * RetroArch's ROM buffer. */ zip_context_t *zip_context = (zip_context_t *)userdata->transfer->context; decomp_state->size = 0; *decomp_state->buf = handle.data; decomp_state->size = size; /* We keep the data, prevent its deallocation during free */ zip_context->decompressed_data = NULL; handle.data = NULL; } } decomp_state->found = true; } return 1; } static int64_t zip_file_read( const char *path, const char *needle, void **buf, const char *optional_outfile) { file_archive_transfer_t state = {0}; decomp_state_t decomp = {0}; struct archive_extract_userdata userdata = {0}; bool returnerr = true; int ret = 0; if (needle) decomp.needle = strdup(needle); if (optional_outfile) decomp.opt_file = strdup(optional_outfile); state.type = ARCHIVE_TRANSFER_INIT; userdata.transfer = &state; userdata.cb_data = &decomp; decomp.buf = buf; do { ret = file_archive_parse_file_iterate(&state, &returnerr, path, "", zip_file_decompressed, &userdata); if (!returnerr) break; }while (ret == 0 && !decomp.found); file_archive_parse_file_iterate_stop(&state); if (decomp.opt_file) free(decomp.opt_file); if (decomp.needle) free(decomp.needle); if (!decomp.found) return -1; return (int64_t)decomp.size; } static int zip_parse_file_init(file_archive_transfer_t *state, const char *file) { uint8_t footer_buf[1024]; uint8_t *footer = footer_buf; int64_t read_pos = state->archive_size; int64_t read_block = MIN(read_pos, (ssize_t)sizeof(footer_buf)); int64_t directory_size, directory_offset; zip_context_t *zip_context = NULL; /* Minimal ZIP file size is 22 bytes */ if (read_block < 22) return -1; /* Find the end of central directory record by scanning * the file from the end towards the beginning. */ for (;;) { if (--footer < footer_buf) { if (read_pos <= 0) return -1; /* reached beginning of file */ /* Read 21 bytes of overlaps except on the first block. */ if (read_pos == state->archive_size) read_pos = read_pos - read_block; else read_pos = MAX(read_pos - read_block + 21, 0); /* Seek to read_pos and read read_block bytes. */ filestream_seek(state->archive_file, read_pos, RETRO_VFS_SEEK_POSITION_START); if (filestream_read(state->archive_file, footer_buf, read_block) != read_block) return -1; footer = footer_buf + read_block - 22; } if (read_le(footer, 4) == END_OF_CENTRAL_DIR_SIGNATURE) { unsigned comment_len = read_le(footer + 20, 2); if (read_pos + (footer - footer_buf) + 22 + comment_len == state->archive_size) break; /* found it! */ } } /* Read directory info and do basic sanity checks. */ directory_size = read_le(footer + 12, 4); directory_offset = read_le(footer + 16, 4); if (directory_size > state->archive_size || directory_offset > state->archive_size) return -1; /* This is a ZIP file, allocate one block of memory for both the * context and the entire directory, then read the directory. */ zip_context = (zip_context_t*)malloc(sizeof(zip_context_t) + (size_t)directory_size); zip_context->state = state; zip_context->directory = (uint8_t*)(zip_context + 1); zip_context->directory_entry = zip_context->directory; zip_context->directory_end = zip_context->directory + (size_t)directory_size; zip_context->zstream = NULL; zip_context->tmpbuf = NULL; zip_context->decompressed_data = NULL; filestream_seek(state->archive_file, directory_offset, RETRO_VFS_SEEK_POSITION_START); if (filestream_read(state->archive_file, zip_context->directory, directory_size) != directory_size) { free(zip_context); return -1; } state->context = zip_context; state->step_total = read_le(footer + 10, 2); /* total entries */; return 0; } static int zip_parse_file_iterate_step_internal( zip_context_t * zip_context, char *filename, const uint8_t **cdata, unsigned *cmode, uint32_t *size, uint32_t *csize, uint32_t *checksum, unsigned *payback) { uint8_t *entry = zip_context->directory_entry; uint32_t signature, namelength, extralength, commentlength, offset; if (entry < zip_context->directory || entry >= zip_context->directory_end) return 0; signature = read_le(zip_context->directory_entry + 0, 4); if (signature != CENTRAL_FILE_HEADER_SIGNATURE) return 0; *cmode = read_le(zip_context->directory_entry + 10, 2); /* compression mode, 0 = store, 8 = deflate */ *checksum = read_le(zip_context->directory_entry + 16, 4); /* CRC32 */ *csize = read_le(zip_context->directory_entry + 20, 4); /* compressed size */ *size = read_le(zip_context->directory_entry + 24, 4); /* uncompressed size */ namelength = read_le(zip_context->directory_entry + 28, 2); /* file name length */ extralength = read_le(zip_context->directory_entry + 30, 2); /* extra field length */ commentlength = read_le(zip_context->directory_entry + 32, 2); /* file comment length */ if (namelength >= PATH_MAX_LENGTH) return -1; memcpy(filename, zip_context->directory_entry + 46, namelength); /* file name */ filename[namelength] = '\0'; offset = read_le(zip_context->directory_entry + 42, 4); /* relative offset of local file header */ *cdata = (uint8_t*)(size_t)offset; /* store file offset in data pointer */ *payback = 46 + namelength + extralength + commentlength; return 1; } static int zip_parse_file_iterate_step(void *context, const char *valid_exts, struct archive_extract_userdata *userdata, file_archive_file_cb file_cb) { zip_context_t *zip_context = (zip_context_t *)context; const uint8_t *cdata = NULL; uint32_t checksum = 0; uint32_t size = 0; uint32_t csize = 0; unsigned cmode = 0; unsigned payload = 0; int ret = zip_parse_file_iterate_step_internal(zip_context, userdata->current_file_path, &cdata, &cmode, &size, &csize, &checksum, &payload); if (ret != 1) return ret; userdata->crc = checksum; if (file_cb && !file_cb(userdata->current_file_path, valid_exts, cdata, cmode, csize, size, checksum, userdata)) return 0; zip_context->directory_entry += payload; return 1; } static void zip_parse_file_free(void *context) { zip_context_t *zip_context = (zip_context_t *)context; zip_context_free_stream(zip_context, false); free(zip_context); } const struct file_archive_file_backend zlib_backend = { zip_parse_file_init, zip_parse_file_iterate_step, zip_parse_file_free, zlib_stream_decompress_data_to_file_init, zlib_stream_decompress_data_to_file_iterate, zlib_stream_crc32_calculate, zip_file_read, "zlib" };