X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=deps%2Flibretro-common%2Ffile%2Farchive_file_zlib.c;fp=deps%2Flibretro-common%2Ffile%2Farchive_file_zlib.c;h=f9cd64be5c890788957f33124ae81dae06ce1b64;hb=3719602cbe883fb394a71353e20a10a4a306e814;hp=0000000000000000000000000000000000000000;hpb=8659d7fd2cdb11f63724ead0997f47f4c694f8c2;p=pcsx_rearmed.git diff --git a/deps/libretro-common/file/archive_file_zlib.c b/deps/libretro-common/file/archive_file_zlib.c new file mode 100644 index 00000000..f9cd64be --- /dev/null +++ b/deps/libretro-common/file/archive_file_zlib.c @@ -0,0 +1,563 @@ +/* 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" +};