git subrepo clone https://github.com/libretro/libretro-common.git deps/libretro-common
[pcsx_rearmed.git] / deps / libretro-common / file / archive_file_zlib.c
diff --git a/deps/libretro-common/file/archive_file_zlib.c b/deps/libretro-common/file/archive_file_zlib.c
new file mode 100644 (file)
index 0000000..f9cd64b
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+
+#include <file/archive_file.h>
+#include <streams/file_stream.h>
+#include <retro_inline.h>
+#include <retro_miscellaneous.h>
+#include <encodings/crc32.h>
+
+#include <zlib.h>
+
+#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"
+};