System: Add XZ save state compression

For when space is really at a premium.

Also use CompressHelpers to reduce duplication.
pull/3528/head
Stenzek 2 months ago
parent bbd150823c
commit 1ef1a2d3f4
No known key found for this signature in database

@ -26,6 +26,7 @@ struct SAVE_STATE_HEADER
None = 0, None = 0,
Deflate = 1, Deflate = 1,
Zstandard = 2, Zstandard = 2,
XZ = 3,
}; };
u32 magic; u32 magic;

@ -2376,7 +2376,8 @@ const char* Settings::GetCDROMMechVersionDisplayName(CDROMMechaconVersion mode)
} }
static constexpr const std::array s_save_state_compression_mode_names = { static constexpr const std::array s_save_state_compression_mode_names = {
"Uncompressed", "DeflateLow", "DeflateDefault", "DeflateHigh", "ZstLow", "ZstDefault", "ZstHigh", "Uncompressed", "DeflateLow", "DeflateDefault", "DeflateHigh", "ZstLow",
"ZstDefault", "ZstHigh", "XZLow", "XZDefault", "XZHigh",
}; };
static constexpr const std::array s_save_state_compression_mode_display_names = { static constexpr const std::array s_save_state_compression_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "Uncompressed", "SaveStateCompressionMode"), TRANSLATE_DISAMBIG_NOOP("Settings", "Uncompressed", "SaveStateCompressionMode"),
@ -2386,6 +2387,9 @@ static constexpr const std::array s_save_state_compression_mode_display_names =
TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (Low)", "SaveStateCompressionMode"), TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (Low)", "SaveStateCompressionMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (Default)", "SaveStateCompressionMode"), TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (Default)", "SaveStateCompressionMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (High)", "SaveStateCompressionMode"), TRANSLATE_DISAMBIG_NOOP("Settings", "Zstandard (High)", "SaveStateCompressionMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "XZ (Low)", "SaveStateCompressionMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "XZ (Default)", "SaveStateCompressionMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "XZ (High)", "SaveStateCompressionMode"),
}; };
static_assert(s_save_state_compression_mode_names.size() == static_cast<size_t>(SaveStateCompressionMode::Count)); static_assert(s_save_state_compression_mode_names.size() == static_cast<size_t>(SaveStateCompressionMode::Count));
static_assert(s_save_state_compression_mode_display_names.size() == static_assert(s_save_state_compression_mode_display_names.size() ==

@ -43,6 +43,7 @@
#include "util/audio_stream.h" #include "util/audio_stream.h"
#include "util/cd_image.h" #include "util/cd_image.h"
#include "util/compress_helpers.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
#include "util/imgui_manager.h" #include "util/imgui_manager.h"
#include "util/ini_settings_interface.h" #include "util/ini_settings_interface.h"
@ -84,9 +85,6 @@
#include <cstdio> #include <cstdio>
#include <limits> #include <limits>
#include <thread> #include <thread>
#include <zlib.h>
#include <zstd.h>
#include <zstd_errors.h>
LOG_CHANNEL(System); LOG_CHANNEL(System);
@ -3145,49 +3143,32 @@ bool System::ReadAndDecompressStateData(std::FILE* fp, std::span<u8> dst, u32 fi
return false; return false;
} }
if (method == SAVE_STATE_HEADER::CompressionType::Deflate) CompressHelpers::CompressType type;
switch (method)
{ {
uLong source_len = compressed_size; case SAVE_STATE_HEADER::CompressionType::Deflate:
uLong dest_len = static_cast<uLong>(dst.size()); type = CompressHelpers::CompressType::Deflate;
const int err = uncompress2(dst.data(), &dest_len, compressed_data.data(), &source_len); break;
if (err != Z_OK) [[unlikely]]
{
Error::SetStringFmt(error, "uncompress2() failed: ", err);
return false;
}
else if (dest_len < dst.size()) [[unlikely]]
{
Error::SetStringFmt(error, "Only decompressed {} of {} bytes", dest_len, dst.size());
return false;
}
if (source_len < compressed_size) [[unlikely]] case SAVE_STATE_HEADER::CompressionType::Zstandard:
WARNING_LOG("Only consumed {} of {} compressed bytes", source_len, compressed_size); type = CompressHelpers::CompressType::Zstandard;
break;
return true; case SAVE_STATE_HEADER::CompressionType::XZ:
} type = CompressHelpers::CompressType::XZ;
else if (method == SAVE_STATE_HEADER::CompressionType::Zstandard) break;
{
const size_t result = ZSTD_decompress(dst.data(), dst.size(), compressed_data.data(), compressed_size);
if (ZSTD_isError(result)) [[unlikely]]
{
const char* errstr = ZSTD_getErrorString(ZSTD_getErrorCode(result));
Error::SetStringFmt(error, "ZSTD_decompress() failed: {}", errstr ? errstr : "<unknown>");
return false;
}
else if (result < dst.size())
{
Error::SetStringFmt(error, "Only decompressed {} of {} bytes", result, dst.size());
return false;
}
return true; default:
Error::SetStringFmt(error, "Unknown compression method {}", static_cast<u32>(method));
return false;
} }
else [[unlikely]]
{ const std::optional<size_t> decompressed_size =
Error::SetStringView(error, "Unknown method."); CompressHelpers::DecompressBuffer(dst, type, compressed_data.cspan(), dst.size(), error);
if (!decompressed_size.has_value() || decompressed_size.value() != dst.size())
return false; return false;
}
return true;
} }
bool System::SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy) bool System::SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy)
@ -3443,59 +3424,48 @@ u32 System::CompressAndWriteStateData(std::FILE* fp, std::span<const u8> src, Sa
return static_cast<u32>(src.size()); return static_cast<u32>(src.size());
} }
DynamicHeapArray<u8> buffer; CompressHelpers::CompressType ctype;
u32 write_size; int clevel;
if (method >= SaveStateCompressionMode::DeflateLow && method <= SaveStateCompressionMode::DeflateHigh) if (method >= SaveStateCompressionMode::DeflateLow && method <= SaveStateCompressionMode::DeflateHigh)
{ {
const size_t buffer_size = compressBound(static_cast<uLong>(src.size())); ctype = CompressHelpers::CompressType::Deflate;
buffer.resize(buffer_size);
uLongf compressed_size = static_cast<uLongf>(buffer_size);
const int level =
((method == SaveStateCompressionMode::DeflateLow) ?
Z_BEST_SPEED :
((method == SaveStateCompressionMode::DeflateHigh) ? Z_BEST_COMPRESSION : Z_DEFAULT_COMPRESSION));
const int err = compress2(buffer.data(), &compressed_size, src.data(), static_cast<uLong>(src.size()), level);
if (err != Z_OK) [[unlikely]]
{
Error::SetStringFmt(error, "compress2() failed: {}", err);
return 0;
}
*header_type = static_cast<u32>(SAVE_STATE_HEADER::CompressionType::Deflate); *header_type = static_cast<u32>(SAVE_STATE_HEADER::CompressionType::Deflate);
write_size = static_cast<u32>(compressed_size); clevel =
((method == SaveStateCompressionMode::DeflateLow) ? 1 :
((method == SaveStateCompressionMode::DeflateHigh) ? 9 : -1));
} }
else if (method >= SaveStateCompressionMode::ZstLow && method <= SaveStateCompressionMode::ZstHigh) else if (method >= SaveStateCompressionMode::ZstLow && method <= SaveStateCompressionMode::ZstHigh)
{ {
const size_t buffer_size = ZSTD_compressBound(src.size()); ctype = CompressHelpers::CompressType::Zstandard;
buffer.resize(buffer_size);
const int level =
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 18 : 0));
const size_t compressed_size = ZSTD_compress(buffer.data(), buffer_size, src.data(), src.size(), level);
if (ZSTD_isError(compressed_size)) [[unlikely]]
{
const char* errstr = ZSTD_getErrorString(ZSTD_getErrorCode(compressed_size));
Error::SetStringFmt(error, "ZSTD_compress() failed: {}", errstr ? errstr : "<unknown>");
return 0;
}
*header_type = static_cast<u32>(SAVE_STATE_HEADER::CompressionType::Zstandard); *header_type = static_cast<u32>(SAVE_STATE_HEADER::CompressionType::Zstandard);
write_size = static_cast<u32>(compressed_size); clevel =
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 18 : 0));
} }
else [[unlikely]] else if (method >= SaveStateCompressionMode::XZLow && method <= SaveStateCompressionMode::XZHigh)
{ {
Error::SetStringView(error, "Unknown method."); ctype = CompressHelpers::CompressType::XZ;
*header_type = static_cast<u32>(SAVE_STATE_HEADER::CompressionType::XZ);
clevel =
((method == SaveStateCompressionMode::XZLow) ? 1 : ((method == SaveStateCompressionMode::XZHigh) ? 9 : 5));
}
else
{
Error::SetStringFmt(error, "Unknown compression type {}", static_cast<u32>(method));
return 0; return 0;
} }
if (std::fwrite(buffer.data(), write_size, 1, fp) != 1) [[unlikely]] const CompressHelpers::OptionalByteBuffer compressed_data =
CompressHelpers::CompressToBuffer(ctype, src, clevel, error);
if (!compressed_data.has_value())
return 0;
if (std::fwrite(compressed_data->data(), compressed_data->size(), 1, fp) != 1) [[unlikely]]
{ {
Error::SetStringFmt(error, "fwrite() failed: {}", errno); Error::SetStringFmt(error, "fwrite() failed: {}", errno);
return 0; return 0;
} }
return write_size; return static_cast<u32>(compressed_data->size());
} }
float System::GetTargetSpeed() float System::GetTargetSpeed()

@ -318,6 +318,9 @@ enum class SaveStateCompressionMode : u8
ZstLow, ZstLow,
ZstDefault, ZstDefault,
ZstHigh, ZstHigh,
XZLow,
XZDefault,
XZHigh,
Count, Count,
}; };

Loading…
Cancel
Save