diff --git a/Makefile.common b/Makefile.common
index 013fd9ce7d..0fab3ee1d8 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -1681,7 +1681,8 @@ endif
 
 ifeq ($(HAVE_ZLIB_COMMON), 1)
    OBJ += $(LIBRETRO_COMM_DIR)/file/archive_file_zlib.o \
-          $(LIBRETRO_COMM_DIR)/streams/trans_stream_zlib.o
+          $(LIBRETRO_COMM_DIR)/streams/trans_stream_zlib.o \
+          $(LIBRETRO_COMM_DIR)/streams/rzip_stream.o
    DEFINES += -DHAVE_ZLIB
    HAVE_COMPRESSION = 1
 
diff --git a/config.def.h b/config.def.h
index a7b643efec..24c8e8866c 100644
--- a/config.def.h
+++ b/config.def.h
@@ -911,6 +911,10 @@ static const bool savestate_auto_load = false;
 
 static const bool savestate_thumbnail_enable = false;
 
+/* When creating save state files, compress
+ * written data */
+#define DEFAULT_SAVESTATE_FILE_COMPRESSION false
+
 /* Slowmotion ratio. */
 #define DEFAULT_SLOWMOTION_RATIO 3.0
 
diff --git a/configuration.c b/configuration.c
index d3e5a6585a..78b08b9d9e 100644
--- a/configuration.c
+++ b/configuration.c
@@ -1614,6 +1614,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings,
    SETTING_BOOL("savestate_auto_save",          &settings->bools.savestate_auto_save, true, savestate_auto_save, false);
    SETTING_BOOL("savestate_auto_load",          &settings->bools.savestate_auto_load, true, savestate_auto_load, false);
    SETTING_BOOL("savestate_thumbnail_enable",   &settings->bools.savestate_thumbnail_enable, true, savestate_thumbnail_enable, false);
+   SETTING_BOOL("savestate_file_compression",   &settings->bools.savestate_file_compression, true, DEFAULT_SAVESTATE_FILE_COMPRESSION, false);
    SETTING_BOOL("history_list_enable",          &settings->bools.history_list_enable, true, DEFAULT_HISTORY_LIST_ENABLE, false);
    SETTING_BOOL("playlist_entry_rename",        &settings->bools.playlist_entry_rename, true, DEFAULT_PLAYLIST_ENTRY_RENAME, false);
    SETTING_BOOL("game_specific_options",        &settings->bools.game_specific_options, true, default_game_specific_options, false);
diff --git a/configuration.h b/configuration.h
index a86052f2f1..2f69dfd785 100644
--- a/configuration.h
+++ b/configuration.h
@@ -344,6 +344,7 @@ typedef struct settings
       bool savestate_auto_save;
       bool savestate_auto_load;
       bool savestate_thumbnail_enable;
+      bool savestate_file_compression;
       bool network_cmd_enable;
       bool stdin_cmd_enable;
       bool keymapper_enable;
diff --git a/griffin/griffin.c b/griffin/griffin.c
index b03ba37afb..b002383690 100644
--- a/griffin/griffin.c
+++ b/griffin/griffin.c
@@ -128,6 +128,7 @@ COMPRESSION
 
 #ifdef HAVE_ZLIB
 #include "../libretro-common/streams/trans_stream_zlib.c"
+#include "../libretro-common/streams/rzip_stream.c"
 #endif
 
 /*============================================================
diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h
index 5f5a46425b..adf86be403 100644
--- a/intl/msg_hash_lbl.h
+++ b/intl/msg_hash_lbl.h
@@ -1200,6 +1200,8 @@ MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD,
       "savestate_auto_load")
 MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE,
       "savestate_thumbnails")
+MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION,
+      "savestate_file_compression")
 MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE,
       "savestate_auto_save")
 MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_DIRECTORY,
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 0dd655d831..22c5669ddb 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -2996,6 +2996,14 @@ MSG_HASH(
     MENU_ENUM_LABEL_VALUE_SAVESTATE_THUMBNAIL_ENABLE,
     "Savestate Thumbnails"
     )
+MSG_HASH(
+    MENU_ENUM_LABEL_VALUE_SAVESTATE_FILE_COMPRESSION,
+    "Savestate Compression"
+    )
+MSG_HASH(
+    MENU_ENUM_SUBLABEL_SAVESTATE_FILE_COMPRESSION,
+    "Write save state files in an archived format. Dramatically reduces file size at the expense of increased saving/loading times."
+    )
 MSG_HASH(
     MENU_ENUM_LABEL_VALUE_SAVE_CURRENT_CONFIG,
     "Save Current Configuration"
diff --git a/libretro-common/include/streams/interface_stream.h b/libretro-common/include/streams/interface_stream.h
index 858ec950fd..813b3551cf 100644
--- a/libretro-common/include/streams/interface_stream.h
+++ b/libretro-common/include/streams/interface_stream.h
@@ -36,7 +36,8 @@ enum intfstream_type
 {
    INTFSTREAM_FILE = 0,
    INTFSTREAM_MEMORY,
-   INTFSTREAM_CHD
+   INTFSTREAM_CHD,
+   INTFSTREAM_RZIP
 };
 
 typedef struct intfstream_internal intfstream_internal_t, intfstream_t;
@@ -112,6 +113,9 @@ intfstream_t *intfstream_open_writable_memory(void *data,
 intfstream_t *intfstream_open_chd_track(const char *path,
       unsigned mode, unsigned hints, int32_t track);
 
+intfstream_t* intfstream_open_rzip_file(const char *path,
+      unsigned mode);
+
 RETRO_END_DECLS
 
 #endif
diff --git a/libretro-common/include/streams/rzip_stream.h b/libretro-common/include/streams/rzip_stream.h
new file mode 100644
index 0000000000..8b53033516
--- /dev/null
+++ b/libretro-common/include/streams/rzip_stream.h
@@ -0,0 +1,122 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rzip_stream.h).
+ * ---------------------------------------------------------------------------------------
+ *
+ * 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.
+ */
+
+#ifndef _LIBRETRO_SDK_FILE_RZIP_STREAM_H
+#define _LIBRETRO_SDK_FILE_RZIP_STREAM_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include <retro_common_api.h>
+
+RETRO_BEGIN_DECLS
+
+/* Rudimentary interface for streaming data to/from a
+ * zlib-compressed chunk-based RZIP archive file.
+ * 
+ * This is somewhat less efficient than using regular
+ * gzip code, but this is by design - the intention here
+ * is to create an interface that integrates seamlessly
+ * with normal RetroArch functionality, using only
+ * standard/existing libretro-common routines.
+ * (Actual efficiency is pretty good, regardless:
+ * archived file size is almost identical to a solid
+ * zip file, and compression/decompression speed is
+ * not substantially worse than external archiving tools;
+ * it is certainly acceptable for use in real-time
+ * frontend applications)
+ * 
+ * When reading existing files, uncompressed content
+ * is handled automatically. File type (compressed/
+ * uncompressed) is detected via the RZIP header.
+ * 
+ * ## RZIP file format:
+ * 
+ * <file id header>:                8 bytes
+ *                                  - [#][R][Z][I][P][v][file format version][#]
+ * <uncompressed chunk size>:       4 bytes, little endian order
+ *                                  - nominal (maximum) size of each uncompressed
+ *                                    chunk, in bytes
+ * <total uncompressed data size>:  8 bytes, little endian order
+ * <size of next compressed chunk>: 4 bytes, little endian order
+ *                                  - size on-disk of next compressed data
+ *                                    chunk, in bytes
+ * <next compressed chunk>:         n bytes of zlib compressed data
+ * ...
+ * <size of next compressed chunk> : repeated until end of file
+ * <next compressed chunk>         :
+ * 
+ */
+
+/* Prevent direct access to rzipstream_t members */
+typedef struct rzipstream rzipstream_t;
+
+/* File Open */
+
+/* Opens a new or existing RZIP file
+ * > Supported 'mode' values are:
+ *   - RETRO_VFS_FILE_ACCESS_READ
+ *   - RETRO_VFS_FILE_ACCESS_WRITE
+ * > When reading, 'path' may reference compressed
+ *   or uncompressed data
+ * Returns NULL if arguments are invalid, file
+ * is invalid or an IO error occurs */
+rzipstream_t* rzipstream_open(const char *path, unsigned mode);
+
+/* File Read */
+
+/* Reads (a maximum of) 'len' bytes from an RZIP file.
+ * Returns actual number of bytes read, or -1 in
+ * the event of an error */
+int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len);
+
+/* File Write */
+
+/* Writes 'len' bytes to an RZIP file.
+ * Returns actual number of bytes written, or -1
+ * in the event of an error */
+int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t len);
+
+/* File Status */
+
+/* Returns total size (in bytes) of the *uncompressed*
+ * data in an RZIP file.
+ * (If reading an uncompressed file, this corresponds
+ * to the 'physical' file size in bytes)
+ * Returns -1 in the event of a error. */
+int64_t rzipstream_get_size(rzipstream_t *stream);
+
+/* Returns EOF when no further *uncompressed* data
+ * can be read from an RZIP file. */
+int rzipstream_eof(rzipstream_t *stream);
+
+/* File Close */
+
+/* Closes RZIP file. If file is open for writing,
+ * flushes any remaining buffered data to disk.
+ * Returns -1 in the event of a error. */
+int rzipstream_close(rzipstream_t *stream);
+
+RETRO_END_DECLS
+
+#endif
diff --git a/libretro-common/streams/interface_stream.c b/libretro-common/streams/interface_stream.c
index b31f047db9..c2ad9edba8 100644
--- a/libretro-common/streams/interface_stream.c
+++ b/libretro-common/streams/interface_stream.c
@@ -28,6 +28,9 @@
 #ifdef HAVE_CHD
 #include <streams/chd_stream.h>
 #endif
+#if defined(HAVE_ZLIB)
+#include <streams/rzip_stream.h>
+#endif
 
 struct intfstream_internal
 {
@@ -55,6 +58,12 @@ struct intfstream_internal
       chdstream_t *fp;
    } chd;
 #endif
+#if defined(HAVE_ZLIB)
+   struct
+   {
+      rzipstream_t *fp;
+   } rzip;
+#endif
 };
 
 int64_t intfstream_get_size(intfstream_internal_t *intf)
@@ -73,6 +82,12 @@ int64_t intfstream_get_size(intfstream_internal_t *intf)
         return chdstream_get_size(intf->chd.fp);
 #else
         break;
+#endif
+      case INTFSTREAM_RZIP:
+#if defined(HAVE_ZLIB)
+         return rzipstream_get_size(intf->rzip.fp);
+#else
+         break;
 #endif
    }
 
@@ -99,6 +114,9 @@ bool intfstream_resize(intfstream_internal_t *intf, intfstream_info_t *info)
 #ifdef HAVE_CHD
 #endif
          break;
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         return false;
    }
 
    return true;
@@ -130,6 +148,15 @@ bool intfstream_open(intfstream_internal_t *intf, const char *path,
          break;
 #else
          return false;
+#endif
+      case INTFSTREAM_RZIP:
+#if defined(HAVE_ZLIB)
+         intf->rzip.fp = rzipstream_open(path, mode);
+         if (!intf->rzip.fp)
+            return false;
+         break;
+#else
+         return false;
 #endif
    }
 
@@ -147,6 +174,7 @@ int intfstream_flush(intfstream_internal_t *intf)
          return filestream_flush(intf->file.fp);
       case INTFSTREAM_MEMORY:
       case INTFSTREAM_CHD:
+      case INTFSTREAM_RZIP:
          /* Should we stub this for these interfaces? */
          break;
    }
@@ -173,6 +201,12 @@ int intfstream_close(intfstream_internal_t *intf)
 #ifdef HAVE_CHD
          if (intf->chd.fp)
             chdstream_close(intf->chd.fp);
+#endif
+         return 0;
+      case INTFSTREAM_RZIP:
+#if defined(HAVE_ZLIB)
+         if (intf->rzip.fp)
+            return rzipstream_close(intf->rzip.fp);
 #endif
          return 0;
    }
@@ -209,6 +243,8 @@ void *intfstream_init(intfstream_info_t *info)
 #else
          goto error;
 #endif
+      case INTFSTREAM_RZIP:
+         break;
    }
 
    return intf;
@@ -252,6 +288,9 @@ int64_t intfstream_seek(intfstream_internal_t *intf, int64_t offset, int whence)
 #else
          break;
 #endif
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 
    return -1;
@@ -273,6 +312,12 @@ int64_t intfstream_read(intfstream_internal_t *intf, void *s, uint64_t len)
          return chdstream_read(intf->chd.fp, s, len);
 #else
          break;
+#endif
+      case INTFSTREAM_RZIP:
+#if defined(HAVE_ZLIB)
+         return rzipstream_read(intf->rzip.fp, s, len);
+#else
+         break;
 #endif
    }
 
@@ -293,6 +338,12 @@ int64_t intfstream_write(intfstream_internal_t *intf,
          return memstream_write(intf->memory.fp, s, len);
       case INTFSTREAM_CHD:
          return -1;
+      case INTFSTREAM_RZIP:
+#if defined(HAVE_ZLIB)
+         return rzipstream_write(intf->rzip.fp, s, len);
+#else
+         return -1;
+#endif
    }
 
    return 0;
@@ -311,6 +362,8 @@ int64_t intfstream_get_ptr(intfstream_internal_t* intf)
          return memstream_get_ptr(intf->memory.fp);
       case INTFSTREAM_CHD:
          return -1;
+      case INTFSTREAM_RZIP:
+         return -1;
    }
 
    return 0;
@@ -336,6 +389,9 @@ char *intfstream_gets(intfstream_internal_t *intf,
 #else
          break;
 #endif
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 
    return NULL;
@@ -358,6 +414,9 @@ int intfstream_getc(intfstream_internal_t *intf)
 #else
          break;
 #endif
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 
    return -1;
@@ -380,6 +439,9 @@ int64_t intfstream_tell(intfstream_internal_t *intf)
 #else
          break;
 #endif
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 
    return -1;
@@ -400,6 +462,9 @@ void intfstream_rewind(intfstream_internal_t *intf)
          chdstream_rewind(intf->chd.fp);
 #endif
          break;
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 }
 
@@ -418,6 +483,9 @@ void intfstream_putc(intfstream_internal_t *intf, int c)
          break;
       case INTFSTREAM_CHD:
          break;
+      case INTFSTREAM_RZIP:
+         /* Unsupported */
+         break;
    }
 }
 
@@ -531,8 +599,6 @@ error:
    return NULL;
 }
 
-
-
 intfstream_t *intfstream_open_chd_track(const char *path,
       unsigned mode, unsigned hints, int32_t track)
 {
@@ -560,3 +626,29 @@ error:
    }
    return NULL;
 }
+
+intfstream_t* intfstream_open_rzip_file(const char *path,
+      unsigned mode)
+{
+   intfstream_info_t info;
+   intfstream_t *fd = NULL;
+
+   info.type        = INTFSTREAM_RZIP;
+   fd               = (intfstream_t*)intfstream_init(&info);
+
+   if (!fd)
+      return NULL;
+
+   if (!intfstream_open(fd, path, mode, RETRO_VFS_FILE_ACCESS_HINT_NONE))
+      goto error;
+
+   return fd;
+
+error:
+   if (fd)
+   {
+      intfstream_close(fd);
+      free(fd);
+   }
+   return NULL;
+}
diff --git a/libretro-common/streams/rzip_stream.c b/libretro-common/streams/rzip_stream.c
new file mode 100644
index 0000000000..94fa121caf
--- /dev/null
+++ b/libretro-common/streams/rzip_stream.c
@@ -0,0 +1,742 @@
+/* Copyright  (C) 2010-2020 The RetroArch team
+ *
+ * ---------------------------------------------------------------------------------------
+ * The following license statement only applies to this file (rzip_stream.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 <string/stdstring.h>
+#include <file/file_path.h>
+
+#include <streams/file_stream.h>
+#include <streams/trans_stream.h>
+
+#include <streams/rzip_stream.h>
+
+/* Current RZIP file format version */
+#define RZIP_VERSION 1
+
+/* Compression level
+ * > zlib default of 6 provides the best
+ *   balance between file size and
+ *   compression speed */
+#define RZIP_COMPRESSION_LEVEL 6
+
+/* Default chunk size: 128kb */
+#define RZIP_DEFAULT_CHUNK_SIZE 131072
+
+/* Header sizes (in bytes) */
+#define RZIP_HEADER_SIZE 20
+#define RZIP_CHUNK_HEADER_SIZE 4
+
+/* Holds all metadata for an RZIP file stream */
+struct rzipstream
+{
+   bool is_compressed;
+   bool is_writing;
+   uint64_t size;
+   uint32_t chunk_size;
+   /* virtual_ptr: Used to track how much
+    * uncompressed data has been read */
+   uint64_t virtual_ptr;
+   RFILE* file;
+   const struct trans_stream_backend *deflate_backend;
+   void *deflate_stream;
+   const struct trans_stream_backend *inflate_backend;
+   void *inflate_stream;
+   uint8_t *in_buf;
+   uint32_t in_buf_size;
+   uint32_t in_buf_ptr;
+   uint8_t *out_buf;
+   uint32_t out_buf_size;
+   uint32_t out_buf_ptr;
+   uint32_t out_buf_occupancy;
+};
+
+/* Header Functions */
+
+/* Reads header information from RZIP file
+ * > Detects whether file is compressed or
+ *   uncompressed data
+ * > If compressed, extracts uncompressed
+ *   file/chunk sizes */
+static bool rzipstream_read_file_header(rzipstream_t *stream)
+{
+   uint8_t header_bytes[RZIP_HEADER_SIZE] = {0};
+   int64_t length;
+
+   if (!stream)
+      return false;
+
+   /* Attempt to read header bytes */
+   length = filestream_read(stream->file, header_bytes, sizeof(header_bytes));
+   if (length <= 0)
+      return false;
+
+   /* If file length is less than header size
+    * then assume this is uncompressed data */
+   if (length < RZIP_HEADER_SIZE)
+      goto file_uncompressed;
+
+   /* Check 'magic numbers' - first 8 bytes
+    * of header */
+   if ((header_bytes[0] !=           35) || /* # */
+       (header_bytes[1] !=           82) || /* R */
+       (header_bytes[2] !=           90) || /* Z */
+       (header_bytes[3] !=           73) || /* I */
+       (header_bytes[4] !=           80) || /* P */
+       (header_bytes[5] !=          118) || /* v */
+       (header_bytes[6] != RZIP_VERSION) || /* file format version number */
+       (header_bytes[7] !=           35))   /* # */
+      goto file_uncompressed;
+
+   /* Get uncompressed chunk size - next 4 bytes */
+   stream->chunk_size = ((uint32_t)header_bytes[11] << 24) |
+                        ((uint32_t)header_bytes[10] << 16) |
+                        ((uint32_t)header_bytes[9]  <<  8) |
+                         (uint32_t)header_bytes[8];
+   if (stream->chunk_size == 0)
+      return false;
+
+   /* Get total uncompressed data size - next 8 bytes */
+   stream->size = ((uint64_t)header_bytes[19] << 56) |
+                  ((uint64_t)header_bytes[18] << 48) |
+                  ((uint64_t)header_bytes[17] << 40) |
+                  ((uint64_t)header_bytes[16] << 32) |
+                  ((uint64_t)header_bytes[15] << 24) |
+                  ((uint64_t)header_bytes[14] << 16) |
+                  ((uint64_t)header_bytes[13] <<  8) |
+                   (uint64_t)header_bytes[12];
+   if (stream->size == 0)
+      return false;
+
+   stream->is_compressed = true;
+   return true;
+
+file_uncompressed:
+
+   /* Reset file to start */
+   filestream_seek(stream->file, 0, SEEK_SET);
+
+   /* Get 'raw' file size */
+   stream->size = filestream_get_size(stream->file);
+
+   stream->is_compressed = false;
+   return true;
+}
+
+/* Writes header information to RZIP file
+ * > ID 'magic numbers' + uncompressed
+ *   file/chunk sizes */
+static bool rzipstream_write_file_header(rzipstream_t *stream)
+{
+   uint8_t header_bytes[RZIP_HEADER_SIZE] = {0};
+   int64_t length;
+
+   if (!stream)
+      return false;
+
+   /* Populate header array */
+
+   /* > 'Magic numbers' - first 8 bytes */
+   header_bytes[0] =           35; /* # */
+   header_bytes[1] =           82; /* R */
+   header_bytes[2] =           90; /* Z */
+   header_bytes[3] =           73; /* I */
+   header_bytes[4] =           80; /* P */
+   header_bytes[5] =          118; /* v */
+   header_bytes[6] = RZIP_VERSION; /* file format version number */
+   header_bytes[7] =           35; /* # */
+
+   /* > Uncompressed chunk size - next 4 bytes */
+   header_bytes[11] = (stream->chunk_size >> 24) & 0xFF;
+   header_bytes[10] = (stream->chunk_size >> 16) & 0xFF;
+   header_bytes[9]  = (stream->chunk_size >>  8) & 0xFF;
+   header_bytes[8]  =  stream->chunk_size        & 0xFF;
+
+   /* > Total uncompressed data size - next 8 bytes */
+   header_bytes[19] = (stream->size >> 56) & 0xFF;
+   header_bytes[18] = (stream->size >> 48) & 0xFF;
+   header_bytes[17] = (stream->size >> 40) & 0xFF;
+   header_bytes[16] = (stream->size >> 32) & 0xFF;
+   header_bytes[15] = (stream->size >> 24) & 0xFF;
+   header_bytes[14] = (stream->size >> 16) & 0xFF;
+   header_bytes[13] = (stream->size >>  8) & 0xFF;
+   header_bytes[12] =  stream->size        & 0xFF;
+
+   /* Reset file to start */
+   filestream_seek(stream->file, 0, SEEK_SET);
+
+   /* Write header bytes */
+   length = filestream_write(stream->file, header_bytes, sizeof(header_bytes));
+   if (length != RZIP_HEADER_SIZE)
+      return false;
+
+   return true;
+}
+
+/* Stream Initialisation/De-initialisation */
+
+/* Initialises all members of an rzipstream_t struct,
+ * reading config from existing file header if available */
+static bool rzipstream_init_stream(
+      rzipstream_t *stream, const char *path, bool is_writing)
+{
+   unsigned file_mode;
+
+   if (!stream)
+      return false;
+
+   /* Ensure stream has valid initial values */
+   stream->size              = 0;
+   stream->chunk_size        = RZIP_DEFAULT_CHUNK_SIZE;
+   stream->file              = NULL;
+   stream->deflate_backend   = NULL;
+   stream->deflate_stream    = NULL;
+   stream->inflate_backend   = NULL;
+   stream->inflate_stream    = NULL;
+   stream->in_buf            = NULL;
+   stream->in_buf_size       = 0;
+   stream->in_buf_ptr        = 0;
+   stream->out_buf           = NULL;
+   stream->out_buf_size      = 0;
+   stream->out_buf_ptr       = 0;
+   stream->out_buf_occupancy = 0;
+
+   /* Check whether this is a read or write stream */
+   stream->is_writing = is_writing;
+   if (stream->is_writing)
+   {
+      /* Written files are always compressed */
+      stream->is_compressed = true;
+      file_mode             = RETRO_VFS_FILE_ACCESS_WRITE;
+   }
+   /* For read files, must get compression status
+    * from file itself... */
+   else
+      file_mode             = RETRO_VFS_FILE_ACCESS_READ;
+
+   /* Open file */
+   stream->file = filestream_open(
+         path, file_mode, RETRO_VFS_FILE_ACCESS_HINT_NONE);
+   if (!stream->file)
+      return false;
+
+   /* If file is open for writing, output header
+    * (Size component cannot be written until
+    * file is closed...) */
+   if (stream->is_writing)
+   {
+      /* Note: could just write zeros here, but
+       * still want to identify this as an RZIP
+       * file if writing fails partway through */
+      if (!rzipstream_write_file_header(stream))
+         return false;
+   }
+   /* If file is open for reading, parse any existing
+    * header */
+   else if (!rzipstream_read_file_header(stream))
+      return false;
+
+   /* Initialise appropriate transform stream
+    * and determine associated buffer sizes */
+   if (stream->is_writing)
+   {
+      /* Compression */
+      stream->deflate_backend = trans_stream_get_zlib_deflate_backend();
+      if (!stream->deflate_backend)
+         return false;
+
+      stream->deflate_stream = stream->deflate_backend->stream_new();
+      if (!stream->deflate_stream)
+         return false;
+
+      /* Set compression level */
+      if (!stream->deflate_backend->define(
+            stream->deflate_stream, "level", RZIP_COMPRESSION_LEVEL))
+         return false;
+
+      /* Buffers
+       * > Input: uncompressed
+       * > Output: compressed */
+      stream->in_buf_size  = stream->chunk_size;
+      stream->out_buf_size = stream->chunk_size * 2;
+      /* > Account for minimum zlib overhead
+       *   of 11 bytes... */ 
+      stream->out_buf_size =
+            (stream->out_buf_size < (stream->in_buf_size + 11)) ?
+                  stream->out_buf_size + 11 :
+                  stream->out_buf_size;
+
+      /* Redundant safety check */
+      if ((stream->in_buf_size == 0) ||
+          (stream->out_buf_size == 0))
+         return false;
+   }
+   /* When reading, don't need an inflate transform
+    * stream (or buffers) if source file is uncompressed */
+   else if (stream->is_compressed)
+   {
+      /* Decompression */
+      stream->inflate_backend = trans_stream_get_zlib_inflate_backend();
+      if (!stream->inflate_backend)
+         return false;
+
+      stream->inflate_stream = stream->inflate_backend->stream_new();
+      if (!stream->inflate_stream)
+         return false;
+
+      /* Buffers
+       * > Input: compressed
+       * > Output: uncompressed
+       * Note 1: Actual compressed chunk sizes are read
+       *         from the file - just allocate a sensible
+       *         default to minimise memory reallocations
+       * Note 2: If file header is valid, output buffer
+       *         should have a size of exactly stream->chunk_size.
+       *         Allocate some additional space, just for
+       *         redundant safety... */
+      stream->in_buf_size  = stream->chunk_size * 2;
+      stream->out_buf_size = stream->chunk_size + (stream->chunk_size >> 2);
+
+      /* Redundant safety check */
+      if ((stream->in_buf_size == 0) ||
+          (stream->out_buf_size == 0))
+         return false;
+   }
+
+   /* Allocate buffers */
+   if (stream->in_buf_size > 0)
+   {
+      stream->in_buf = (uint8_t *)calloc(stream->in_buf_size, 1);
+      if (!stream->in_buf)
+         return false;
+   }
+
+   if (stream->out_buf_size > 0)
+   {
+      stream->out_buf = (uint8_t *)calloc(stream->out_buf_size, 1);
+      if (!stream->out_buf)
+         return false;
+   }
+
+   return true;
+}
+
+/* free()'s all members of an rzipstream_t struct
+ * > Also closes associated file, if currently open */
+static int rzipstream_free_stream(rzipstream_t *stream)
+{
+   int ret = 0;
+
+   if (!stream)
+      return -1;
+
+   /* Free transform streams */
+   if (stream->deflate_stream && stream->deflate_backend)
+      stream->deflate_backend->stream_free(stream->deflate_stream);
+
+   stream->deflate_stream  = NULL;
+   stream->deflate_backend = NULL;
+
+   if (stream->inflate_stream && stream->inflate_backend)
+      stream->inflate_backend->stream_free(stream->inflate_stream);
+
+   stream->inflate_stream  = NULL;
+   stream->inflate_backend = NULL;
+
+   /* Free buffers */
+   if (stream->in_buf)
+      free(stream->in_buf);
+   stream->in_buf = NULL;
+
+   if (stream->out_buf)
+      free(stream->out_buf);
+   stream->out_buf = NULL;
+
+   /* Close file */
+   if (stream->file)
+      ret = filestream_close(stream->file);
+   stream->file = NULL;
+
+   free(stream);
+
+   return ret;
+}
+
+/* File Open */
+
+/* Opens a new or existing RZIP file
+ * > Supported 'mode' values are:
+ *   - RETRO_VFS_FILE_ACCESS_READ
+ *   - RETRO_VFS_FILE_ACCESS_WRITE
+ * > When reading, 'path' may reference compressed
+ *   or uncompressed data
+ * Returns NULL if arguments are invalid, file
+ * is invalid or an IO error occurs */
+rzipstream_t* rzipstream_open(const char *path, unsigned mode)
+{
+   rzipstream_t *stream = NULL;
+
+   /* Sanity check
+    * > Only RETRO_VFS_FILE_ACCESS_READ and
+    *   RETRO_VFS_FILE_ACCESS_WRITE are supported */
+   if (string_is_empty(path) ||
+       ((mode != RETRO_VFS_FILE_ACCESS_READ) &&
+        (mode != RETRO_VFS_FILE_ACCESS_WRITE)))
+      return NULL;
+
+   /* If opening in read mode, ensure file exists */
+   if ((mode == RETRO_VFS_FILE_ACCESS_READ) &&
+       !path_is_valid(path))
+      return NULL;
+
+   /* Allocate stream object */
+   stream = (rzipstream_t*)calloc(1, sizeof(*stream));
+   if (!stream)
+      return NULL;
+
+   /* Initialise stream */
+   if (!rzipstream_init_stream(
+         stream, path,
+         (mode == RETRO_VFS_FILE_ACCESS_WRITE)))
+   {
+      rzipstream_free_stream(stream);
+      return NULL;
+   }
+
+   return stream;
+}
+
+/* File Read */
+
+/* Reads and decompresses the next chunk of data
+ * in the RZIP file */
+static bool rzipstream_read_chunk(rzipstream_t *stream)
+{
+   uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE] = {0};
+   uint32_t compressed_chunk_size;
+   uint32_t inflate_read;
+   uint32_t inflate_written;
+   int64_t length;
+
+   if (!stream || !stream->inflate_backend || !stream->inflate_stream)
+      return false;
+
+   /* Attempt to read chunk header bytes */
+   length = filestream_read(
+         stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
+   if (length != RZIP_CHUNK_HEADER_SIZE)
+      return false;
+
+   /* Get size of next compressed chunk */
+   compressed_chunk_size = ((uint32_t)chunk_header_bytes[3] << 24) |
+                           ((uint32_t)chunk_header_bytes[2] << 16) |
+                           ((uint32_t)chunk_header_bytes[1] <<  8) |
+                            (uint32_t)chunk_header_bytes[0];
+   if (compressed_chunk_size == 0)
+      return false;
+
+   /* Resize input buffer, if required */
+   if (compressed_chunk_size > stream->in_buf_size)
+   {
+      free(stream->in_buf);
+      stream->in_buf      = NULL;
+
+      stream->in_buf_size = compressed_chunk_size;
+      stream->in_buf      = (uint8_t *)calloc(stream->in_buf_size, 1);
+      if (!stream->in_buf)
+         return false;
+
+      /* Note: Uncompressed data size is fixed, and read
+       * from the file header - we therefore don't attempt
+       * to resize the output buffer (if it's too small, then
+       * that's an error condition) */
+   }
+
+   /* Read compressed chunk from file */
+   length = filestream_read(
+         stream->file, stream->in_buf, compressed_chunk_size);
+   if (length != compressed_chunk_size)
+      return false;
+
+   /* Decompress chunk data */
+   stream->inflate_backend->set_in(
+         stream->inflate_stream,
+         stream->in_buf, compressed_chunk_size);
+
+   stream->inflate_backend->set_out(
+         stream->inflate_stream,
+         stream->out_buf, stream->out_buf_size);
+
+   /* Note: We have to set 'flush == true' here, otherwise we
+    * can't guarantee that the entire chunk will be written
+    * to the output buffer - this is inefficient, but not
+    * much we can do... */
+   if (!stream->inflate_backend->trans(
+         stream->inflate_stream, true,
+         &inflate_read, &inflate_written, NULL))
+      return false;
+
+   /* Error checking */
+   if (inflate_read != compressed_chunk_size)
+      return false;
+
+   if ((inflate_written == 0) ||
+       (inflate_written > stream->out_buf_size))
+      return false;
+
+   /* Record current output buffer occupancy
+    * and reset pointer */
+   stream->out_buf_occupancy = inflate_written;
+   stream->out_buf_ptr       = 0;
+
+   return true;
+}
+
+/* Reads (a maximum of) 'len' bytes from an RZIP file.
+ * Returns actual number of bytes read, or -1 in
+ * the event of an error */
+int64_t rzipstream_read(rzipstream_t *stream, void *data, int64_t len)
+{
+   int64_t data_len  = len;
+   uint8_t *data_ptr = (uint8_t *)data;
+   int64_t data_read = 0;
+
+   if (!stream || stream->is_writing)
+      return -1;
+
+   /* If we are reading uncompressed data, simply
+    * 'pass on' the direct file access request */
+   if (!stream->is_compressed)
+      return filestream_read(stream->file, data, len);
+
+   /* Process input data */
+   while (data_len > 0)
+   {
+      uint32_t read_size = 0;
+
+      /* Check whether we have reached the end
+       * of the file */
+      if (stream->virtual_ptr >= stream->size)
+         return data_read;
+
+      /* If everything in the output buffer has already
+       * been read, grab and extract the next chunk
+       * from disk */
+      if (stream->out_buf_ptr >= stream->out_buf_occupancy)
+         if (!rzipstream_read_chunk(stream))
+            return -1;
+
+      /* Get amount of data to 'read out' this loop
+       * > i.e. minimum of remaining output buffer
+       *   occupancy and remaining 'read data' size */
+      read_size = stream->out_buf_occupancy - stream->out_buf_ptr;
+      read_size = (read_size > data_len) ? data_len : read_size;
+
+      /* Copy as much cached data as possible into
+       * the read buffer */
+      memcpy(data_ptr, stream->out_buf + stream->out_buf_ptr, read_size);
+
+      /* Increment pointers and remaining length */
+      stream->out_buf_ptr += read_size;
+      data_ptr            += read_size;
+      data_len            -= read_size;
+
+      stream->virtual_ptr += read_size;
+
+      data_read           += read_size;
+   }
+
+   return data_read;
+}
+
+/* File Write */
+
+/* Compresses currently cached data and writes it
+ * as the next RZIP file chunk */
+static bool rzipstream_write_chunk(rzipstream_t *stream)
+{
+   uint8_t chunk_header_bytes[RZIP_CHUNK_HEADER_SIZE] = {0};
+   uint32_t deflate_read;
+   uint32_t deflate_written;
+   int64_t length;
+
+   if (!stream || !stream->deflate_backend || !stream->deflate_stream)
+      return false;
+
+   /* Compress data currently held in input buffer */
+   stream->deflate_backend->set_in(
+         stream->deflate_stream,
+         stream->in_buf, stream->in_buf_ptr);
+
+   stream->deflate_backend->set_out(
+         stream->deflate_stream,
+         stream->out_buf, stream->out_buf_size);
+
+   /* Note: We have to set 'flush == true' here, otherwise we
+    * can't guarantee that the entire chunk will be written
+    * to the output buffer - this is inefficient, but not
+    * much we can do... */
+   if (!stream->deflate_backend->trans(
+         stream->deflate_stream, true,
+         &deflate_read, &deflate_written, NULL))
+      return false;
+
+   /* Error checking */
+   if (deflate_read != stream->in_buf_ptr)
+      return false;
+
+   if ((deflate_written == 0) ||
+       (deflate_written > stream->out_buf_size))
+      return false;
+
+   /* Write compressed chunk size to file */
+   chunk_header_bytes[3] = (deflate_written >> 24) & 0xFF;
+   chunk_header_bytes[2] = (deflate_written >> 16) & 0xFF;
+   chunk_header_bytes[1] = (deflate_written >>  8) & 0xFF;
+   chunk_header_bytes[0] =  deflate_written        & 0xFF;
+
+   length = filestream_write(
+         stream->file, chunk_header_bytes, sizeof(chunk_header_bytes));
+   if (length != RZIP_CHUNK_HEADER_SIZE)
+      return false;
+
+   /* Write compressed data to file */
+   length = filestream_write(
+         stream->file, stream->out_buf, deflate_written);
+
+   if (length != deflate_written)
+      return false;
+
+   /* Reset input buffer pointer */
+   stream->in_buf_ptr = 0;
+
+   return true;
+}
+
+/* Writes 'len' bytes to an RZIP file.
+ * Returns actual number of bytes written, or -1
+ * in the event of an error */
+int64_t rzipstream_write(rzipstream_t *stream, const void *data, int64_t len)
+{
+   int64_t data_len        = len;
+   const uint8_t *data_ptr = (const uint8_t *)data;
+
+   if (!stream || !stream->is_writing)
+      return -1;
+
+   /* Process input data */
+   while (data_len > 0)
+   {
+      uint32_t cache_size = 0;
+
+      /* If input buffer is full, compress and write to disk */
+      if (stream->in_buf_ptr >= stream->in_buf_size)
+         if (!rzipstream_write_chunk(stream))
+            return -1;
+
+      /* Get amount of data to cache during this loop
+       * > i.e. minimum of space remaining in input buffer
+       *   and remaining 'write data' size */
+      cache_size = stream->in_buf_size - stream->in_buf_ptr;
+      cache_size = (cache_size > data_len) ? data_len : cache_size;
+
+      /* Copy as much data as possible into
+       * the input buffer */
+      memcpy(stream->in_buf + stream->in_buf_ptr, data_ptr, cache_size);
+
+      /* Increment pointers and remaining length */
+      stream->in_buf_ptr  += cache_size;
+      data_ptr            += cache_size;
+      data_len            -= cache_size;
+
+      stream->size        += cache_size;
+      stream->virtual_ptr += cache_size;
+   }
+
+   /* We always write the specified number of bytes
+    * (unless rzipstream_write_chunk() fails, in
+    * which we register a complete failure...) */
+   return len;
+}
+
+/* File Status */
+
+/* Returns total size (in bytes) of the *uncompressed*
+ * data in an RZIP file.
+ * (If reading an uncompressed file, this corresponds
+ * to the 'physical' file size in bytes)
+ * Returns -1 in the event of a error. */
+int64_t rzipstream_get_size(rzipstream_t *stream)
+{
+   if (!stream)
+      return -1;
+
+   if (stream->is_compressed)
+      return stream->size;
+   else
+      return filestream_get_size(stream->file);
+}
+
+/* Returns EOF when no further *uncompressed* data
+ * can be read from an RZIP file. */
+int rzipstream_eof(rzipstream_t *stream)
+{
+   if (!stream)
+      return -1;
+
+   if (stream->is_compressed)
+      return (stream->virtual_ptr >= stream->size) ?
+            EOF : 0;
+   else
+      return filestream_eof(stream->file);
+}
+
+/* File Close */
+
+/* Closes RZIP file. If file is open for writing,
+ * flushes any remaining buffered data to disk.
+ * Returns -1 in the event of a error. */
+int rzipstream_close(rzipstream_t *stream)
+{
+   if (!stream)
+      return -1;
+
+   /* If we are writing, ensure that any
+    * remaining uncompressed data is flushed to
+    * disk and update file header */
+   if (stream->is_writing)
+   {
+      if (stream->in_buf_ptr > 0)
+         if (!rzipstream_write_chunk(stream))
+            goto error;
+
+      if (!rzipstream_write_file_header(stream))
+         goto error;
+   }
+
+   /* Free stream
+    * > This also closes the file */
+   return rzipstream_free_stream(stream);
+
+error:
+   /* Stream must be free()'d regardless */
+   rzipstream_free_stream(stream);
+   return -1;
+}
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index 82e6cb51f6..f798140732 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -381,6 +381,7 @@ default_sublabel_macro(action_bind_sublabel_perfcnt_enable,                MENU_
 default_sublabel_macro(action_bind_sublabel_savestate_auto_save,           MENU_ENUM_SUBLABEL_SAVESTATE_AUTO_SAVE)
 default_sublabel_macro(action_bind_sublabel_savestate_auto_load,           MENU_ENUM_SUBLABEL_SAVESTATE_AUTO_LOAD)
 default_sublabel_macro(action_bind_sublabel_savestate_thumbnail_enable,    MENU_ENUM_SUBLABEL_SAVESTATE_THUMBNAIL_ENABLE)
+default_sublabel_macro(action_bind_sublabel_savestate_file_compression,    MENU_ENUM_SUBLABEL_SAVESTATE_FILE_COMPRESSION)
 default_sublabel_macro(action_bind_sublabel_autosave_interval,             MENU_ENUM_SUBLABEL_AUTOSAVE_INTERVAL)
 default_sublabel_macro(action_bind_sublabel_input_remap_binds_enable,      MENU_ENUM_SUBLABEL_INPUT_REMAP_BINDS_ENABLE)
 default_sublabel_macro(action_bind_sublabel_input_autodetect_enable,       MENU_ENUM_SUBLABEL_INPUT_AUTODETECT_ENABLE)
@@ -2327,6 +2328,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
          case MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_thumbnail_enable);
             break;
+         case MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION:
+            BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_file_compression);
+            break;
          case MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_auto_save);
             break;
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index 1a394f8e94..e6098c9720 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -6832,6 +6832,7 @@ unsigned menu_displaylist_build_list(
                {MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE,   PARSE_ONLY_BOOL},
                {MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD,   PARSE_ONLY_BOOL},
                {MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE,   PARSE_ONLY_BOOL},
+               {MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION,   PARSE_ONLY_BOOL},
                {MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE,   PARSE_ONLY_BOOL},
                {MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE,   PARSE_ONLY_BOOL},
                {MENU_ENUM_LABEL_SYSTEMFILES_IN_CONTENT_DIR_ENABLE,   PARSE_ONLY_BOOL},
diff --git a/menu/menu_setting.c b/menu/menu_setting.c
index bc4c099ac9..ff78f75837 100644
--- a/menu/menu_setting.c
+++ b/menu/menu_setting.c
@@ -8950,6 +8950,24 @@ static bool setting_append_list(
                   general_read_handler,
                   SD_FLAG_NONE);
 
+#if defined(HAVE_ZLIB)
+            CONFIG_BOOL(
+                  list, list_info,
+                  &settings->bools.savestate_file_compression,
+                  MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION,
+                  MENU_ENUM_LABEL_VALUE_SAVESTATE_FILE_COMPRESSION,
+                  DEFAULT_SAVESTATE_FILE_COMPRESSION,
+                  MENU_ENUM_LABEL_VALUE_OFF,
+                  MENU_ENUM_LABEL_VALUE_ON,
+                  &group_info,
+                  &subgroup_info,
+                  parent_group,
+                  general_write_handler,
+                  general_read_handler,
+                  SD_FLAG_NONE);
+#endif
+
+            /* TODO/FIXME: This is in the wrong group... */
             CONFIG_BOOL(
                   list, list_info,
                   &settings->bools.scan_without_core_match,
diff --git a/msg_hash.h b/msg_hash.h
index 99060cf68c..061a01134d 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -1607,6 +1607,7 @@ enum msg_hash_enums
    MENU_LABEL(SAVESTATE_AUTO_SAVE),
    MENU_LABEL(SAVESTATE_AUTO_LOAD),
    MENU_LABEL(SAVESTATE_THUMBNAIL_ENABLE),
+   MENU_LABEL(SAVESTATE_FILE_COMPRESSION),
 
    MENU_LABEL(SUSPEND_SCREENSAVER_ENABLE),
    MENU_ENUM_LABEL_VOLUME_UP,
diff --git a/retroarch.c b/retroarch.c
index a7ecd685fc..46e0060656 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -7818,7 +7818,14 @@ static void command_event_undo_save_state(char *s, size_t len)
    }
 
    if (!content_undo_save_state())
+   {
+      strlcpy(s,
+         msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE), len);
       return;
+   }
+
+   strlcpy(s,
+         msg_hash_to_str(MSG_UNDOING_SAVE_STATE), len);
 }
 
 static void command_event_undo_load_state(char *s, size_t len)
diff --git a/tasks/task_save.c b/tasks/task_save.c
index d5804f1386..d11c133ca7 100644
--- a/tasks/task_save.c
+++ b/tasks/task_save.c
@@ -101,6 +101,7 @@ typedef struct
    int state_slot;
    bool thumbnail_enable;
    bool has_valid_framebuffer;
+   bool compress_files;
 } save_task_state_t;
 
 typedef save_task_state_t load_task_data_t;
@@ -560,7 +561,12 @@ static void *get_serialized_data(const char *path, size_t serial_size)
    if (!serial_size)
       return NULL;
 
-   data = malloc(serial_size);
+   /* Ensure buffer is initialised to zero
+    * > Prevents inconsistent compressed state file
+    *   sizes when core requests a larger buffer
+    *   than it needs (and leaves the excess
+    *   as uninitialised garbage) */
+   data = calloc(serial_size, 1);
 
    if (!data)
       return NULL;
@@ -597,9 +603,13 @@ static void task_save_handler(retro_task_t *task)
 
    if (!state->file)
    {
-      state->file   = intfstream_open_file(
-            state->path, RETRO_VFS_FILE_ACCESS_WRITE,
-            RETRO_VFS_FILE_ACCESS_HINT_NONE);
+      if (state->compress_files)
+         state->file   = intfstream_open_rzip_file(
+               state->path, RETRO_VFS_FILE_ACCESS_WRITE);
+      else
+         state->file   = intfstream_open_file(
+               state->path, RETRO_VFS_FILE_ACCESS_WRITE,
+               RETRO_VFS_FILE_ACCESS_HINT_NONE);
 
       if (!state->file)
          return;
@@ -694,6 +704,11 @@ static bool task_push_undo_save_state(const char *path, void *data, size_t size)
    retro_task_t       *task = task_init();
    save_task_state_t *state = (save_task_state_t*)calloc(1, sizeof(*state));
    settings_t     *settings = config_get_ptr();
+#if defined(HAVE_ZLIB)
+   bool compress_files      = settings->bools.savestate_file_compression;
+#else
+   bool compress_files      = false;
+#endif
 
    if (!task || !state)
       goto error;
@@ -704,6 +719,7 @@ static bool task_push_undo_save_state(const char *path, void *data, size_t size)
    state->undo_save              = true;
    state->state_slot             = settings->ints.state_slot;
    state->has_valid_framebuffer  = video_driver_cached_frame_has_valid_framebuffer();
+   state->compress_files         = compress_files;
 
    task->type                    = TASK_TYPE_BLOCKING;
    task->state                   = state;
@@ -787,23 +803,26 @@ static void task_load_handler(retro_task_t *task)
 
    if (!state->file)
    {
+#if defined(HAVE_ZLIB)
+      /* Always use RZIP interface when reading state
+       * files - this will automatically handle uncompressed
+       * data */
+      state->file = intfstream_open_rzip_file(state->path,
+            RETRO_VFS_FILE_ACCESS_READ);
+#else
       state->file = intfstream_open_file(state->path,
             RETRO_VFS_FILE_ACCESS_READ,
             RETRO_VFS_FILE_ACCESS_HINT_NONE);
+#endif
 
       if (!state->file)
          goto error;
 
-      if (intfstream_seek(state->file, 0, SEEK_END) != 0)
-         goto error;
-
-      state->size = intfstream_tell(state->file);
+      state->size = intfstream_get_size(state->file);
 
       if (state->size < 0)
          goto error;
 
-      intfstream_rewind(state->file);
-
       state->data = malloc(state->size + 1);
 
       if (!state->data)
@@ -1076,6 +1095,11 @@ static void task_push_save_state(const char *path, void *data, size_t size, bool
    settings_t     *settings        = config_get_ptr();
    bool savestate_thumbnail_enable = settings->bools.savestate_thumbnail_enable;
    int state_slot                  = settings->ints.state_slot;
+#if defined(HAVE_ZLIB)
+   bool compress_files             = settings->bools.savestate_file_compression;
+#else
+   bool compress_files             = false;
+#endif
 
    if (!task || !state)
       goto error;
@@ -1088,6 +1112,7 @@ static void task_push_save_state(const char *path, void *data, size_t size, bool
    state->thumbnail_enable       = savestate_thumbnail_enable;
    state->state_slot             = state_slot;
    state->has_valid_framebuffer  = video_driver_cached_frame_has_valid_framebuffer();
+   state->compress_files         = compress_files;
 
    task->type              = TASK_TYPE_BLOCKING;
    task->state             = state;
@@ -1161,6 +1186,11 @@ static void task_push_load_and_save_state(const char *path, void *data,
    retro_task_t      *task     = NULL;
    settings_t        *settings = config_get_ptr();
    int state_slot              = settings->ints.state_slot;
+#if defined(HAVE_ZLIB)
+   bool compress_files         = settings->bools.savestate_file_compression;
+#else
+   bool compress_files         = false;
+#endif
    save_task_state_t *state    = (save_task_state_t*)
       calloc(1, sizeof(*state));
 
@@ -1188,6 +1218,7 @@ static void task_push_load_and_save_state(const char *path, void *data,
    state->state_slot             = state_slot;
    state->has_valid_framebuffer  = 
       video_driver_cached_frame_has_valid_framebuffer();
+   state->compress_files         = compress_files;
 
    task->state       = state;
    task->type        = TASK_TYPE_BLOCKING;
@@ -1318,6 +1349,11 @@ bool content_load_state(const char *path,
    save_task_state_t *state     = (save_task_state_t*)calloc(1, sizeof(*state));
    settings_t *settings         = config_get_ptr();
    int state_slot               = settings->ints.state_slot;
+#if defined(HAVE_ZLIB)
+   bool compress_files          = settings->bools.savestate_file_compression;
+#else
+   bool compress_files          = false;
+#endif
 
    if (!task || !state)
       goto error;
@@ -1328,6 +1364,7 @@ bool content_load_state(const char *path,
    state->state_slot            = state_slot;
    state->has_valid_framebuffer = 
       video_driver_cached_frame_has_valid_framebuffer();
+   state->compress_files        = compress_files;
 
    task->type                   = TASK_TYPE_BLOCKING;
    task->state                  = state;