From 060650e0d8c987a22ffe85c5370d21c6d2e39c34 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 26 Dec 2020 09:58:53 +0100 Subject: [PATCH] support for compressed save-states --- CMakeLists.txt | 4 +- core/archive/rzip.cpp | 132 ++++++++++++++++++ core/archive/rzip.h | 43 ++++++ core/nullDC.cpp | 88 ++++++++---- core/sdl/sdl.cpp | 5 +- .../reicast-osx.xcodeproj/project.pbxproj | 6 + 6 files changed, 249 insertions(+), 29 deletions(-) create mode 100644 core/archive/rzip.cpp create mode 100644 core/archive/rzip.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a29ee3c06..26a13d786 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,7 +431,9 @@ target_sources(${PROJECT_NAME} PRIVATE core/archive/7zArchive.cpp core/archive/7zArchive.h core/archive/archive.cpp - core/archive/archive.h) + core/archive/archive.h + core/archive/rzip.cpp + core/archive/rzip.h) if(NOT MSVC) target_sources(${PROJECT_NAME} PRIVATE core/archive/ZipArchive.cpp diff --git a/core/archive/rzip.cpp b/core/archive/rzip.cpp new file mode 100644 index 000000000..f48bf7b43 --- /dev/null +++ b/core/archive/rzip.cpp @@ -0,0 +1,132 @@ +/* + Copyright 2020 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "rzip.h" +#include + +const u8 RZipHeader[8] = { '#', 'R', 'Z', 'I', 'P', 'v', 1, '#' }; + +bool RZipFile::Open(const std::string& path, bool write) +{ + verify(file == nullptr); + + file = fopen(path.c_str(), write ? "wb" : "rb"); + if (file == nullptr) + return false; + if (!write) + { + u8 header[sizeof(RZipHeader)]; + if (fread(header, sizeof(header), 1, file) != 1 + || memcmp(header, RZipHeader, sizeof(header)) + || fread(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1 + || fread(&size, sizeof(size), 1, file) != 1) + { + Close(); + return false; + } + chunk = new u8[maxChunkSize]; + chunkIndex = 0; + chunkSize = 0; + } + + return true; +} + +void RZipFile::Close() +{ + if (file != nullptr) + { + fclose(file); + file = nullptr; + if (chunk != nullptr) + { + delete [] chunk; + chunk = nullptr; + } + } +} + +size_t RZipFile::Read(void *data, size_t length) +{ + verify(file != nullptr); + + u8 *p = (u8 *)data; + size_t rv = 0; + while (rv < length) + { + if (chunkIndex == chunkSize) + { + chunkSize = 0; + chunkIndex = 0; + u32 zippedSize; + if (fread(&zippedSize, sizeof(zippedSize), 1, file) != 1) + break; + u8 *zipped = new u8[zippedSize]; + if (fread(zipped, zippedSize, 1, file) != 1) + { + delete [] zipped; + break; + } + uLongf tl = maxChunkSize; + if (uncompress(chunk, &tl, zipped, zippedSize) != Z_OK) + { + delete [] zipped; + break; + } + delete [] zipped; + chunkSize = (u32)tl; + } + u32 l = std::min(chunkSize - chunkIndex, (u32)length); + memcpy(p, chunk + chunkIndex, l); + p += l; + chunkIndex += l; + rv += l; + } + + return rv; +} + +size_t RZipFile::Write(const void *data, size_t length) +{ + verify(file != nullptr); + verify(ftell(file) == 0); + + maxChunkSize = 1024 * 1024; + if (fwrite(RZipHeader, sizeof(RZipHeader), 1, file) != 1 + || fwrite(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1 + || fwrite(&length, sizeof(length), 1, file) != 1) + return 0; + const u8 *p = (const u8 *)data; + u8 *zipped = new u8[maxChunkSize]; + size_t rv = 0; + while (rv < length) + { + uLongf zippedSize = maxChunkSize; + uLongf uncompressedSize = std::min(maxChunkSize, (u32)(length - rv)); + if (compress(zipped, &zippedSize, p, uncompressedSize) != Z_OK) + break; + u32 sz = (u32)zippedSize; + if (fwrite(&sz, sizeof(sz), 1, file) != 1 + || fwrite(zipped, zippedSize, 1, file) != 1) + return 0; + p += uncompressedSize; + rv += uncompressedSize; + } + + return rv; +} diff --git a/core/archive/rzip.h b/core/archive/rzip.h new file mode 100644 index 000000000..7a265e3c2 --- /dev/null +++ b/core/archive/rzip.h @@ -0,0 +1,43 @@ +/* + Copyright 2020 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +// Implementation of the RZIP stream format as defined by libretro +// https://github.com/libretro/libretro-common/blob/master/include/streams/rzip_stream.h + +#pragma once +#include "types.h" + +class RZipFile +{ +public: + ~RZipFile() { Close(); } + + bool Open(const std::string& path, bool write); + void Close(); + size_t Size() const { return size; } + size_t Read(void *data, size_t length); + size_t Write(const void *data, size_t length); + +private: + FILE *file = nullptr; + size_t size = 0; + u32 maxChunkSize = 0; + u8 *chunk = nullptr; + u32 chunkSize = 0; + u32 chunkIndex = 0; +}; diff --git a/core/nullDC.cpp b/core/nullDC.cpp index 9c0589b94..bf2e41543 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -35,6 +35,7 @@ #include "hw/maple/maple_devs.h" #include "network/naomi_network.h" #include "rend/mainui.h" +#include "archive/rzip.h" void FlushCache(); static void LoadCustom(); @@ -1106,11 +1107,8 @@ static std::string get_savestate_file_path(bool writable) void dc_savestate() { - std::string filename; unsigned int total_size = 0 ; void *data = NULL ; - void *data_ptr = NULL ; - FILE *f ; dc_stop(); @@ -1131,7 +1129,7 @@ void dc_savestate() return; } - data_ptr = data ; + void *data_ptr = data; if ( ! dc_serialize(&data_ptr, &total_size) ) { @@ -1141,8 +1139,9 @@ void dc_savestate() return; } - filename = get_savestate_file_path(true); - f = fopen(filename.c_str(), "wb") ; + std::string filename = get_savestate_file_path(true); +#if 0 + FILE *f = fopen(filename.c_str(), "wb") ; if ( f == NULL ) { @@ -1154,6 +1153,25 @@ void dc_savestate() fwrite(data, 1, total_size, f) ; fclose(f); +#else + RZipFile zipFile; + if (!zipFile.Open(filename, true)) + { + WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); + gui_display_notification("Cannot open save file", 2000); + cleanup_serialize(data); + return; + } + if (zipFile.Write(data, total_size) != total_size) + { + WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str()); + gui_display_notification("Error saving state", 2000); + zipFile.Close(); + cleanup_serialize(data); + return; + } + zipFile.Close(); +#endif cleanup_serialize(data) ; INFO_LOG(SAVESTATE, "Saved state to %s size %d", filename.c_str(), total_size) ; @@ -1162,38 +1180,54 @@ void dc_savestate() void dc_loadstate() { - std::string filename; - unsigned int total_size = 0 ; - void *data = NULL ; - void *data_ptr = NULL ; - FILE *f ; + u32 total_size = 0; + FILE *f = nullptr; dc_stop(); - filename = get_savestate_file_path(false); - f = fopen(filename.c_str(), "rb") ; - - if ( f == NULL ) + std::string filename = get_savestate_file_path(false); + RZipFile zipFile; + if (zipFile.Open(filename, false)) { - WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str()) ; - gui_display_notification("Save state not found", 2000); - cleanup_serialize(data) ; - return; + total_size = (u32)zipFile.Size(); } - fseek(f, 0, SEEK_END); - total_size = ftell(f); - fseek(f, 0, SEEK_SET); - data = malloc(total_size) ; + else + { + f = fopen(filename.c_str(), "rb") ; + + if ( f == NULL ) + { + WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str()) ; + gui_display_notification("Save state not found", 2000); + return; + } + fseek(f, 0, SEEK_END); + total_size = (u32)ftell(f); + fseek(f, 0, SEEK_SET); + } + void *data = malloc(total_size); if ( data == NULL ) { WARN_LOG(SAVESTATE, "Failed to load state - could not malloc %d bytes", total_size) ; gui_display_notification("Failed to load state - memory full", 2000); - cleanup_serialize(data) ; + if (f != nullptr) + fclose(f); + else + zipFile.Close(); return; } - size_t read_size = fread(data, 1, total_size, f) ; - fclose(f); + size_t read_size; + if (f == nullptr) + { + read_size = zipFile.Read(data, total_size); + zipFile.Close(); + } + else + { + read_size = fread(data, 1, total_size, f) ; + fclose(f); + } if (read_size != total_size) { WARN_LOG(SAVESTATE, "Failed to load state - I/O error"); @@ -1202,7 +1236,7 @@ void dc_loadstate() return; } - data_ptr = data ; + void *data_ptr = data; custom_texture.Terminate(); #if FEAT_AREC == DYNAREC_JIT diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index ca788716e..8de7d47b4 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -64,8 +64,11 @@ void input_sdl_init() SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); #ifdef _WIN32 if (cfgLoadBool("input", "DisableXInput", false)) - // Disable XInput for some old joytsicks + { + // Disable XInput for some old joysticks + NOTICE_LOG(INPUT, "Disabling XInput, using DirectInput"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "0"); + } #endif if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) die("SDL: error initializing Joystick subsystem"); diff --git a/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj b/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj index 36bac8b3c..55a788363 100644 --- a/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj +++ b/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj @@ -315,6 +315,7 @@ AEF256512294060400348550 /* 7zArchive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEF2564C2294060300348550 /* 7zArchive.cpp */; }; AEF256522294060400348550 /* archive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEF2564E2294060300348550 /* archive.cpp */; }; AEF77FDB24AA76BA00171080 /* audiobackend_null.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEF77FDA24AA76B900171080 /* audiobackend_null.cpp */; }; + AEF99730259682850038E0B8 /* rzip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEF9972F259682850038E0B8 /* rzip.cpp */; }; AEFF7ECC214AEC810068CE11 /* modem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEFF7EC7214AEC800068CE11 /* modem.cpp */; }; AEFF7F4D214D9D590068CE11 /* pico_arp.c in Sources */ = {isa = PBXBuildFile; fileRef = AEFF7EFA214D9D590068CE11 /* pico_arp.c */; }; AEFF7F4E214D9D590068CE11 /* pico_dev_ppp.c in Sources */ = {isa = PBXBuildFile; fileRef = AEFF7EFE214D9D590068CE11 /* pico_dev_ppp.c */; }; @@ -967,6 +968,8 @@ AEF2564E2294060300348550 /* archive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = archive.cpp; sourceTree = ""; }; AEF2564F2294060400348550 /* 7zArchive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 7zArchive.h; sourceTree = ""; }; AEF77FDA24AA76B900171080 /* audiobackend_null.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = audiobackend_null.cpp; sourceTree = ""; }; + AEF9972E259682850038E0B8 /* rzip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rzip.h; sourceTree = ""; }; + AEF9972F259682850038E0B8 /* rzip.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rzip.cpp; sourceTree = ""; }; AEFF7EC7214AEC800068CE11 /* modem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = modem.cpp; sourceTree = ""; }; AEFF7EC8214AEC800068CE11 /* modem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem.h; sourceTree = ""; }; AEFF7EC9214AEC800068CE11 /* modem_regs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modem_regs.h; sourceTree = ""; }; @@ -1831,6 +1834,8 @@ AEF2564C2294060300348550 /* 7zArchive.cpp */, AEF2564F2294060400348550 /* 7zArchive.h */, AEF2564E2294060300348550 /* archive.cpp */, + AEF9972F259682850038E0B8 /* rzip.cpp */, + AEF9972E259682850038E0B8 /* rzip.h */, AEF2564B2294060300348550 /* archive.h */, AEF2564A2294060300348550 /* ZipArchive.cpp */, AEF2564D2294060300348550 /* ZipArchive.h */, @@ -2630,6 +2635,7 @@ AED73E6F2348E45000ECDB64 /* PpAtom.cpp in Sources */, 84B7BF191B72720200F9733F /* deflate.c in Sources */, 84B7BF7E1B72720200F9733F /* TexCache.cpp in Sources */, + AEF99730259682850038E0B8 /* rzip.cpp in Sources */, AE43537522C9420C005E19CE /* LogManager.cpp in Sources */, AEFF7F4E214D9D590068CE11 /* pico_dev_ppp.c in Sources */, 84B7BEE41B72720200F9733F /* zip_delete.c in Sources */,