mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-02-11 06:40:39 +00:00
Savestates: Asynchronous compression
This commit is contained in:
parent
aaf776ddc9
commit
9abf0b7176
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include "Emu/savestate_utils.hpp"
|
#include "util/serialization_ext.hpp"
|
||||||
|
|
||||||
std::vector<u8> unzip(const void* src, usz size)
|
std::vector<u8> unzip(const void* src, usz size)
|
||||||
{
|
{
|
||||||
@ -132,14 +132,14 @@ bool unzip(const void* src, usz size, fs::file& out)
|
|||||||
return is_valid;
|
return is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool zip(const void* src, usz size, fs::file& out)
|
bool zip(const void* src, usz size, fs::file& out, bool multi_thread_it)
|
||||||
{
|
{
|
||||||
if (!src || !size || !out)
|
if (!src || !size || !out)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::serial compressor(false);
|
utils::serial compressor(!multi_thread_it || size < 0x40'0000);
|
||||||
compressor.m_file_handler = make_compressed_serialization_file_handler(out);
|
compressor.m_file_handler = make_compressed_serialization_file_handler(out);
|
||||||
|
|
||||||
std::string_view buffer_view{static_cast<const char*>(src), size};
|
std::string_view buffer_view{static_cast<const char*>(src), size};
|
||||||
|
@ -16,7 +16,7 @@ inline bool unzip(const std::vector<u8>& src, fs::file& out)
|
|||||||
return unzip(src.data(), src.size(), out);
|
return unzip(src.data(), src.size(), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool zip(const void* src, usz size, fs::file& out);
|
bool zip(const void* src, usz size, fs::file& out, bool multi_thread_it = false);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline bool zip(const T& src, fs::file& out)
|
inline bool zip(const T& src, fs::file& out)
|
||||||
|
@ -50,6 +50,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||||||
../util/dyn_lib.cpp
|
../util/dyn_lib.cpp
|
||||||
../util/sysinfo.cpp
|
../util/sysinfo.cpp
|
||||||
../util/cpu_stats.cpp
|
../util/cpu_stats.cpp
|
||||||
|
../util/serialization_ext.cpp
|
||||||
../../Utilities/bin_patch.cpp
|
../../Utilities/bin_patch.cpp
|
||||||
../../Utilities/cheat_info.cpp
|
../../Utilities/cheat_info.cpp
|
||||||
../../Utilities/cond.cpp
|
../../Utilities/cond.cpp
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include "Emu/Cell/PPUCallback.h"
|
#include "Emu/Cell/PPUCallback.h"
|
||||||
#include "Emu/Cell/SPUThread.h"
|
#include "Emu/Cell/SPUThread.h"
|
||||||
#include "Emu/Cell/timers.hpp"
|
#include "Emu/Cell/timers.hpp"
|
||||||
#include "Emu/savestate_utils.hpp"
|
|
||||||
|
|
||||||
#include "Capture/rsx_capture.h"
|
#include "Capture/rsx_capture.h"
|
||||||
#include "Common/BufferUtils.h"
|
#include "Common/BufferUtils.h"
|
||||||
@ -20,7 +19,7 @@
|
|||||||
#include "Emu/Cell/lv2/sys_event.h"
|
#include "Emu/Cell/lv2/sys_event.h"
|
||||||
#include "Emu/Cell/lv2/sys_time.h"
|
#include "Emu/Cell/lv2/sys_time.h"
|
||||||
#include "Emu/Cell/Modules/cellGcmSys.h"
|
#include "Emu/Cell/Modules/cellGcmSys.h"
|
||||||
#include "Emu/savestate_utils.hpp"
|
#include "util/serialization_ext.hpp"
|
||||||
#include "Overlays/overlay_perf_metrics.h"
|
#include "Overlays/overlay_perf_metrics.h"
|
||||||
#include "Overlays/overlay_message.h"
|
#include "Overlays/overlay_message.h"
|
||||||
#include "Program/GLSLCommon.h"
|
#include "Program/GLSLCommon.h"
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "Emu/perf_monitor.hpp"
|
#include "Emu/perf_monitor.hpp"
|
||||||
#include "Emu/vfs_config.h"
|
#include "Emu/vfs_config.h"
|
||||||
#include "Emu/IPC_config.h"
|
#include "Emu/IPC_config.h"
|
||||||
|
#include "Emu/savestate_utils.hpp"
|
||||||
|
|
||||||
#include "Emu/Cell/ErrorCodes.h"
|
#include "Emu/Cell/ErrorCodes.h"
|
||||||
#include "Emu/Cell/PPUThread.h"
|
#include "Emu/Cell/PPUThread.h"
|
||||||
@ -40,8 +41,6 @@
|
|||||||
#include "../Crypto/unself.h"
|
#include "../Crypto/unself.h"
|
||||||
#include "../Crypto/unzip.h"
|
#include "../Crypto/unzip.h"
|
||||||
#include "util/logs.hpp"
|
#include "util/logs.hpp"
|
||||||
#include "util/serialization.hpp"
|
|
||||||
#include "savestate_utils.hpp"
|
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -12,25 +12,11 @@
|
|||||||
#include "System.h"
|
#include "System.h"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <any>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
LOG_CHANNEL(sys_log, "SYS");
|
LOG_CHANNEL(sys_log, "SYS");
|
||||||
|
|
||||||
template <>
|
|
||||||
void fmt_class_string<utils::serial>::format(std::string& out, u64 arg)
|
|
||||||
{
|
|
||||||
const utils::serial& ar = get_object(arg);
|
|
||||||
|
|
||||||
|
|
||||||
be_t<u64> sample64 = 0;
|
|
||||||
const usz read_size = std::min<usz>(ar.data.size(), sizeof(sample64));
|
|
||||||
std::memcpy(&sample64, ar.data.data() + ar.data.size() - read_size, read_size);
|
|
||||||
|
|
||||||
fmt::append(out, "{ %s, 0x%x/0x%x, memory=0x%x, sample64=0x%016x }", ar.is_writing() ? "writing" : "reading", ar.pos, ar.data_offset + ar.data.size(), ar.data.size(), sample64);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct serial_ver_t
|
struct serial_ver_t
|
||||||
{
|
{
|
||||||
bool used = false;
|
bool used = false;
|
||||||
@ -109,7 +95,7 @@ std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::s
|
|||||||
file.seek(0);
|
file.seek(0);
|
||||||
|
|
||||||
utils::serial ar;
|
utils::serial ar;
|
||||||
ar.set_reading_state();
|
ar.set_reading_state({}, true);
|
||||||
|
|
||||||
ar.m_file_handler = filepath.ends_with(".gz") ? static_cast<std::unique_ptr<utils::serialization_file_handler>>(make_compressed_serialization_file_handler(std::move(file)))
|
ar.m_file_handler = filepath.ends_with(".gz") ? static_cast<std::unique_ptr<utils::serialization_file_handler>>(make_compressed_serialization_file_handler(std::move(file)))
|
||||||
: make_uncompressed_serialization_file_handler(std::move(file));
|
: make_uncompressed_serialization_file_handler(std::move(file));
|
||||||
@ -287,544 +273,6 @@ bool boot_last_savestate(bool testing)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool uncompressed_serialization_file_handler::handle_file_op(utils::serial& ar, usz pos, usz size, const void* data)
|
|
||||||
{
|
|
||||||
if (ar.is_writing())
|
|
||||||
{
|
|
||||||
if (data)
|
|
||||||
{
|
|
||||||
m_file->seek(pos);
|
|
||||||
m_file->write(data, size);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_file->seek(ar.data_offset);
|
|
||||||
m_file->write(ar.data);
|
|
||||||
|
|
||||||
if (pos == umax && size == umax)
|
|
||||||
{
|
|
||||||
// Request to flush the file to disk
|
|
||||||
m_file->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
ar.seek_end();
|
|
||||||
ar.data_offset = ar.pos;
|
|
||||||
ar.data.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!size)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos == 0 && size == umax)
|
|
||||||
{
|
|
||||||
// Discard loaded data until pos if profitable
|
|
||||||
const usz limit = ar.data_offset + ar.data.size();
|
|
||||||
|
|
||||||
if (ar.pos > ar.data_offset && ar.pos < limit)
|
|
||||||
{
|
|
||||||
const usz may_discard_bytes = ar.pos - ar.data_offset;
|
|
||||||
const usz moved_byte_count_on_discard = limit - ar.pos;
|
|
||||||
|
|
||||||
// Cheeck profitability (check recycled memory and std::memmove costs)
|
|
||||||
if (may_discard_bytes >= 0x50'0000 || (may_discard_bytes >= 0x20'0000 && moved_byte_count_on_discard / may_discard_bytes < 3))
|
|
||||||
{
|
|
||||||
ar.data_offset += may_discard_bytes;
|
|
||||||
ar.data.erase(ar.data.begin(), ar.data.begin() + may_discard_bytes);
|
|
||||||
|
|
||||||
if (ar.data.capacity() >= 0x200'0000)
|
|
||||||
{
|
|
||||||
// Discard memory
|
|
||||||
ar.data.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard all loaded data
|
|
||||||
ar.data_offset = ar.pos;
|
|
||||||
ar.data.clear();
|
|
||||||
|
|
||||||
if (ar.data.capacity() >= 0x200'0000)
|
|
||||||
{
|
|
||||||
// Discard memory
|
|
||||||
ar.data.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (~pos < size - 1)
|
|
||||||
{
|
|
||||||
// Overflow
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ar.data.empty() && pos != ar.pos)
|
|
||||||
{
|
|
||||||
// Relocate instead of over-fetch
|
|
||||||
ar.seek_pos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
const usz read_pre_buffer = ar.data.empty() ? 0 : utils::sub_saturate<usz>(ar.data_offset, pos);
|
|
||||||
|
|
||||||
if (read_pre_buffer)
|
|
||||||
{
|
|
||||||
// Read past data
|
|
||||||
// Harsh operation on performance, luckily rare and not typically needed
|
|
||||||
// Also this may would be disallowed when moving to compressed files
|
|
||||||
// This may be a result of wrong usage of breathe() function
|
|
||||||
ar.data.resize(ar.data.size() + read_pre_buffer);
|
|
||||||
std::memmove(ar.data.data() + read_pre_buffer, ar.data.data(), ar.data.size() - read_pre_buffer);
|
|
||||||
ensure(m_file->read_at(pos, ar.data.data(), read_pre_buffer) == read_pre_buffer);
|
|
||||||
ar.data_offset -= read_pre_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjustment to prevent overflow
|
|
||||||
const usz subtrahend = ar.data.empty() ? 0 : 1;
|
|
||||||
const usz read_past_buffer = utils::sub_saturate<usz>(pos + (size - subtrahend), ar.data_offset + (ar.data.size() - subtrahend));
|
|
||||||
const usz read_limit = utils::sub_saturate<usz>(ar.m_max_data, ar.data_offset);
|
|
||||||
|
|
||||||
if (read_past_buffer)
|
|
||||||
{
|
|
||||||
// Read proceeding data
|
|
||||||
// More lightweight operation, this is the common operation
|
|
||||||
// Allowed to fail, if memory is truly needed an assert would take place later
|
|
||||||
const usz old_size = ar.data.size();
|
|
||||||
|
|
||||||
// Try to prefetch data by reading more than requested
|
|
||||||
ar.data.resize(std::min<usz>(read_limit, std::max<usz>({ ar.data.capacity(), ar.data.size() + read_past_buffer * 3 / 2, ar.m_avoid_large_prefetch ? usz{4096} : usz{0x10'0000} })));
|
|
||||||
ar.data.resize(m_file->read_at(old_size + ar.data_offset, data ? const_cast<void*>(data) : ar.data.data() + old_size, ar.data.size() - old_size) + old_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
usz uncompressed_serialization_file_handler::get_size(const utils::serial& ar, usz recommended) const
|
|
||||||
{
|
|
||||||
if (ar.is_writing())
|
|
||||||
{
|
|
||||||
return m_file->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const usz memory_available = ar.data_offset + ar.data.size();
|
|
||||||
|
|
||||||
if (memory_available >= recommended)
|
|
||||||
{
|
|
||||||
// Avoid calling size() if possible
|
|
||||||
return memory_available;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::max<usz>(m_file->size(), memory_available);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uncompressed_serialization_file_handler::finalize(utils::serial& ar)
|
|
||||||
{
|
|
||||||
ar.seek_end();
|
|
||||||
handle_file_op(ar, 0, umax, nullptr);
|
|
||||||
ar.data = {}; // Deallocate and clear
|
|
||||||
}
|
|
||||||
|
|
||||||
void compressed_serialization_file_handler::initialize(utils::serial& ar)
|
|
||||||
{
|
|
||||||
if (!m_stream.has_value())
|
|
||||||
{
|
|
||||||
m_stream.emplace<z_stream>();
|
|
||||||
}
|
|
||||||
|
|
||||||
z_stream& m_zs = std::any_cast<z_stream&>(m_stream);
|
|
||||||
|
|
||||||
if (ar.is_writing())
|
|
||||||
{
|
|
||||||
if (m_write_inited)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
||||||
#endif
|
|
||||||
if (m_read_inited)
|
|
||||||
{
|
|
||||||
finalize(ar);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_zs = {};
|
|
||||||
ensure(deflateInit2(&m_zs, 9, Z_DEFLATED, 16 + 15, 9, Z_DEFAULT_STRATEGY) == Z_OK);
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
m_write_inited = true;
|
|
||||||
m_errored = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (m_read_inited)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_write_inited)
|
|
||||||
{
|
|
||||||
finalize(ar);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_zs.avail_in = 0;
|
|
||||||
m_zs.avail_out = 0;
|
|
||||||
m_zs.next_in = nullptr;
|
|
||||||
m_zs.next_out = nullptr;
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
||||||
#endif
|
|
||||||
ensure(inflateInit2(&m_zs, 16 + 15) == Z_OK);
|
|
||||||
m_read_inited = true;
|
|
||||||
m_errored = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool compressed_serialization_file_handler::handle_file_op(utils::serial& ar, usz pos, usz size, const void* data)
|
|
||||||
{
|
|
||||||
if (ar.is_writing())
|
|
||||||
{
|
|
||||||
initialize(ar);
|
|
||||||
|
|
||||||
if (m_errored)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
z_stream& m_zs = std::any_cast<z_stream&>(m_stream);
|
|
||||||
|
|
||||||
if (data)
|
|
||||||
{
|
|
||||||
ensure(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writing not at the end is forbidden
|
|
||||||
ensure(ar.pos == ar.data_offset + ar.data.size());
|
|
||||||
|
|
||||||
m_zs.avail_in = static_cast<uInt>(ar.data.size());
|
|
||||||
m_zs.next_in = ar.data.data();
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
m_stream_data.resize(std::max<usz>(m_stream_data.size(), ::compressBound(m_zs.avail_in)));
|
|
||||||
m_zs.avail_out = static_cast<uInt>(m_stream_data.size());
|
|
||||||
m_zs.next_out = m_stream_data.data();
|
|
||||||
|
|
||||||
if (deflate(&m_zs, Z_NO_FLUSH) == Z_STREAM_ERROR || m_file->write(m_stream_data.data(), m_stream_data.size() - m_zs.avail_out) != m_stream_data.size() - m_zs.avail_out)
|
|
||||||
{
|
|
||||||
m_errored = true;
|
|
||||||
deflateEnd(&m_zs);
|
|
||||||
//m_file->close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (m_zs.avail_out == 0);
|
|
||||||
|
|
||||||
ar.seek_end();
|
|
||||||
ar.data_offset = ar.pos;
|
|
||||||
ar.data.clear();
|
|
||||||
|
|
||||||
if (pos == umax && size == umax && *m_file)
|
|
||||||
{
|
|
||||||
// Request to flush the file to disk
|
|
||||||
m_file->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(ar);
|
|
||||||
|
|
||||||
if (m_errored)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!size)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos == 0 && size == umax)
|
|
||||||
{
|
|
||||||
// Discard loaded data until pos if profitable
|
|
||||||
const usz limit = ar.data_offset + ar.data.size();
|
|
||||||
|
|
||||||
if (ar.pos > ar.data_offset && ar.pos < limit)
|
|
||||||
{
|
|
||||||
const usz may_discard_bytes = ar.pos - ar.data_offset;
|
|
||||||
const usz moved_byte_count_on_discard = limit - ar.pos;
|
|
||||||
|
|
||||||
// Cheeck profitability (check recycled memory and std::memmove costs)
|
|
||||||
if (may_discard_bytes >= 0x50'0000 || (may_discard_bytes >= 0x20'0000 && moved_byte_count_on_discard / may_discard_bytes < 3))
|
|
||||||
{
|
|
||||||
ar.data_offset += may_discard_bytes;
|
|
||||||
ar.data.erase(ar.data.begin(), ar.data.begin() + may_discard_bytes);
|
|
||||||
|
|
||||||
if (ar.data.capacity() >= 0x200'0000)
|
|
||||||
{
|
|
||||||
// Discard memory
|
|
||||||
ar.data.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard all loaded data
|
|
||||||
ar.data_offset += ar.data.size();
|
|
||||||
ensure(ar.pos >= ar.data_offset);
|
|
||||||
ar.data.clear();
|
|
||||||
|
|
||||||
if (ar.data.capacity() >= 0x200'0000)
|
|
||||||
{
|
|
||||||
// Discard memory
|
|
||||||
ar.data.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (~pos < size - 1)
|
|
||||||
{
|
|
||||||
// Overflow
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Investigate if this optimization is worth an implementation for compressed stream
|
|
||||||
// if (ar.data.empty() && pos != ar.pos)
|
|
||||||
// {
|
|
||||||
// // Relocate instead of over-fetch
|
|
||||||
// ar.seek_pos(pos);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const usz read_pre_buffer = utils::sub_saturate<usz>(ar.data_offset, pos);
|
|
||||||
|
|
||||||
if (read_pre_buffer)
|
|
||||||
{
|
|
||||||
// Not allowed with compressed data for now
|
|
||||||
// Unless someone implements mechanism for it
|
|
||||||
ensure(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjustment to prevent overflow
|
|
||||||
const usz subtrahend = ar.data.empty() ? 0 : 1;
|
|
||||||
const usz read_past_buffer = utils::sub_saturate<usz>(pos + (size - subtrahend), ar.data_offset + (ar.data.size() - subtrahend));
|
|
||||||
const usz read_limit = utils::sub_saturate<usz>(ar.m_max_data, ar.data_offset);
|
|
||||||
|
|
||||||
if (read_past_buffer)
|
|
||||||
{
|
|
||||||
// Read proceeding data
|
|
||||||
// More lightweight operation, this is the common operation
|
|
||||||
// Allowed to fail, if memory is truly needed an assert would take place later
|
|
||||||
const usz old_size = ar.data.size();
|
|
||||||
|
|
||||||
// Try to prefetch data by reading more than requested
|
|
||||||
ar.data.resize(std::min<usz>(read_limit, std::max<usz>({ ar.data.capacity(), ar.data.size() + read_past_buffer * 3 / 2, ar.m_avoid_large_prefetch ? usz{4096} : usz{0x10'0000} })));
|
|
||||||
ar.data.resize(this->read_at(ar, old_size + ar.data_offset, data ? const_cast<void*>(data) : ar.data.data() + old_size, ar.data.size() - old_size) + old_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
usz compressed_serialization_file_handler::read_at(utils::serial& ar, usz read_pos, void* data, usz size)
|
|
||||||
{
|
|
||||||
ensure(read_pos == ar.data.size() + ar.data_offset - size);
|
|
||||||
|
|
||||||
if (!size || m_errored)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(ar);
|
|
||||||
|
|
||||||
z_stream& m_zs = std::any_cast<z_stream&>(m_stream);
|
|
||||||
|
|
||||||
const usz total_to_read = size;
|
|
||||||
usz read_size = 0;
|
|
||||||
u8* out_data = static_cast<u8*>(data);
|
|
||||||
|
|
||||||
auto adjust_for_uint = [](usz size)
|
|
||||||
{
|
|
||||||
return static_cast<uInt>(std::min<usz>(uInt{umax}, size));
|
|
||||||
};
|
|
||||||
|
|
||||||
for (; read_size < total_to_read;)
|
|
||||||
{
|
|
||||||
// Drain extracted memory stash (also before first file read)
|
|
||||||
out_data = static_cast<u8*>(data) + read_size;
|
|
||||||
m_zs.avail_in = adjust_for_uint(m_stream_data.size() - m_stream_data_index);
|
|
||||||
m_zs.next_in = reinterpret_cast<const u8*>(m_stream_data.data() + m_stream_data_index);
|
|
||||||
m_zs.next_out = out_data;
|
|
||||||
m_zs.avail_out = adjust_for_uint(size - read_size);
|
|
||||||
|
|
||||||
while (read_size < total_to_read && m_zs.avail_in)
|
|
||||||
{
|
|
||||||
const int res = inflate(&m_zs, Z_BLOCK);
|
|
||||||
|
|
||||||
bool need_more_file_memory = false;
|
|
||||||
|
|
||||||
switch (res)
|
|
||||||
{
|
|
||||||
case Z_OK:
|
|
||||||
case Z_STREAM_END:
|
|
||||||
break;
|
|
||||||
case Z_BUF_ERROR:
|
|
||||||
{
|
|
||||||
if (m_zs.avail_in)
|
|
||||||
{
|
|
||||||
need_more_file_memory = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[fallthrough]];
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
m_errored = true;
|
|
||||||
inflateEnd(&m_zs);
|
|
||||||
m_read_inited = false;
|
|
||||||
sys_log.error("Failure of compressed data reading. (res=%d, read_size=0x%x, avail_in=0x%x, avail_out=0x%x, ar=%s)", res, read_size, m_zs.avail_in, m_zs.avail_out, ar);
|
|
||||||
return read_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
read_size = m_zs.next_out - static_cast<u8*>(data);
|
|
||||||
m_stream_data_index = m_zs.avail_in ? m_zs.next_in - m_stream_data.data() : m_stream_data.size();
|
|
||||||
|
|
||||||
// Adjust again in case the values simply did not fit into uInt
|
|
||||||
m_zs.avail_out = adjust_for_uint(utils::sub_saturate<usz>(total_to_read, read_size));
|
|
||||||
m_zs.avail_in = adjust_for_uint(m_stream_data.size() - m_stream_data_index);
|
|
||||||
|
|
||||||
if (need_more_file_memory)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read_size >= total_to_read)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usz add_size = ar.m_avoid_large_prefetch ? 0x1'0000 : 0x10'0000;
|
|
||||||
const usz old_file_buf_size = m_stream_data.size();
|
|
||||||
|
|
||||||
m_stream_data.resize(old_file_buf_size + add_size);
|
|
||||||
m_stream_data.resize(old_file_buf_size + m_file->read_at(m_file_read_index, m_stream_data.data() + old_file_buf_size, add_size));
|
|
||||||
|
|
||||||
if (m_stream_data.size() == old_file_buf_size)
|
|
||||||
{
|
|
||||||
// EOF
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_file_read_index += m_stream_data.size() - old_file_buf_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_stream_data.size() - m_stream_data_index <= m_stream_data_index / 5)
|
|
||||||
{
|
|
||||||
// Shrink to required memory size
|
|
||||||
m_stream_data.erase(m_stream_data.begin(), m_stream_data.begin() + m_stream_data_index);
|
|
||||||
|
|
||||||
if (m_stream_data.capacity() >= 0x200'0000)
|
|
||||||
{
|
|
||||||
// Discard memory
|
|
||||||
m_stream_data.shrink_to_fit();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_stream_data_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return read_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void compressed_serialization_file_handler::skip_until(utils::serial& ar)
|
|
||||||
{
|
|
||||||
ensure(!ar.is_writing() && ar.pos >= ar.data_offset);
|
|
||||||
|
|
||||||
if (ar.pos > ar.data_offset)
|
|
||||||
{
|
|
||||||
handle_file_op(ar, ar.data_offset, ar.pos - ar.data_offset, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void compressed_serialization_file_handler::finalize(utils::serial& ar)
|
|
||||||
{
|
|
||||||
handle_file_op(ar, 0, umax, nullptr);
|
|
||||||
|
|
||||||
if (!m_stream.has_value())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
z_stream& m_zs = std::any_cast<z_stream&>(m_stream);
|
|
||||||
|
|
||||||
if (m_read_inited)
|
|
||||||
{
|
|
||||||
m_read_inited = false;
|
|
||||||
ensure(inflateEnd(&m_zs) == Z_OK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_write_inited = false;
|
|
||||||
|
|
||||||
m_zs.avail_in = 0;
|
|
||||||
m_zs.next_in = nullptr;
|
|
||||||
|
|
||||||
m_stream_data.resize(m_zs.avail_out);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
m_zs.avail_out = static_cast<uInt>(m_stream_data.size());
|
|
||||||
m_zs.next_out = m_stream_data.data();
|
|
||||||
|
|
||||||
if (deflate(&m_zs, Z_FINISH) == Z_STREAM_ERROR)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_file->write(m_stream_data.data(), m_stream_data.size() - m_zs.avail_out);
|
|
||||||
}
|
|
||||||
while (m_zs.avail_out == 0);
|
|
||||||
|
|
||||||
m_stream_data = {};
|
|
||||||
ensure(deflateEnd(&m_zs) == Z_OK);
|
|
||||||
ar.data = {}; // Deallocate and clear
|
|
||||||
}
|
|
||||||
|
|
||||||
usz compressed_serialization_file_handler::get_size(const utils::serial& ar, usz recommended) const
|
|
||||||
{
|
|
||||||
if (ar.is_writing())
|
|
||||||
{
|
|
||||||
return m_file->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const usz memory_available = ar.data_offset + ar.data.size();
|
|
||||||
|
|
||||||
if (memory_available >= recommended)
|
|
||||||
{
|
|
||||||
// Avoid calling size() if possible
|
|
||||||
return memory_available;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::max<usz>(utils::mul_saturate<usz>(m_file->size(), 6), memory_available);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool null_serialization_file_handler::handle_file_op(utils::serial&, usz, usz, const void*)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void null_serialization_file_handler::finalize(utils::serial&)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_and_check_reserved(utils::serial& ar, usz size)
|
bool load_and_check_reserved(utils::serial& ar, usz size)
|
||||||
{
|
{
|
||||||
u8 bytes[4096];
|
u8 bytes[4096];
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "util/serialization.hpp"
|
#include "util/serialization_ext.hpp"
|
||||||
|
|
||||||
#include <any>
|
|
||||||
|
|
||||||
namespace fs
|
|
||||||
{
|
|
||||||
class file;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct version_entry
|
struct version_entry
|
||||||
{
|
{
|
||||||
@ -17,128 +10,9 @@ struct version_entry
|
|||||||
ENABLE_BITWISE_SERIALIZATION;
|
ENABLE_BITWISE_SERIALIZATION;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Uncompressed file serialization handler
|
|
||||||
struct uncompressed_serialization_file_handler : utils::serialization_file_handler
|
|
||||||
{
|
|
||||||
const std::unique_ptr<fs::file> m_file_storage;
|
|
||||||
const std::add_pointer_t<const fs::file> m_file;
|
|
||||||
|
|
||||||
explicit uncompressed_serialization_file_handler(fs::file&& file) noexcept
|
|
||||||
: utils::serialization_file_handler()
|
|
||||||
, m_file_storage(std::make_unique<fs::file>(std::move(file)))
|
|
||||||
, m_file(m_file_storage.get())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit uncompressed_serialization_file_handler(const fs::file& file) noexcept
|
|
||||||
: utils::serialization_file_handler()
|
|
||||||
, m_file_storage(nullptr)
|
|
||||||
, m_file(std::addressof(file))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
uncompressed_serialization_file_handler(const uncompressed_serialization_file_handler&) = delete;
|
|
||||||
|
|
||||||
// Handle file read and write requests
|
|
||||||
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
|
||||||
|
|
||||||
// Get available memory or file size
|
|
||||||
// Preferably memory size if is already greater/equal to recommended to avoid additional file ops
|
|
||||||
usz get_size(const utils::serial& ar, usz recommended) const override;
|
|
||||||
|
|
||||||
void finalize(utils::serial& ar) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename File> requires (std::is_same_v<std::remove_cvref_t<File>, fs::file>)
|
|
||||||
inline std::unique_ptr<uncompressed_serialization_file_handler> make_uncompressed_serialization_file_handler(File&& file)
|
|
||||||
{
|
|
||||||
ensure(file);
|
|
||||||
return std::make_unique<uncompressed_serialization_file_handler>(std::forward<File>(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compressed file serialization handler
|
|
||||||
struct compressed_serialization_file_handler : utils::serialization_file_handler
|
|
||||||
{
|
|
||||||
const std::unique_ptr<fs::file> m_file_storage;
|
|
||||||
const std::add_pointer_t<const fs::file> m_file;
|
|
||||||
std::vector<u8> m_stream_data;
|
|
||||||
usz m_stream_data_index = 0;
|
|
||||||
usz m_file_read_index = 0;
|
|
||||||
bool m_write_inited = false;
|
|
||||||
bool m_read_inited = false;
|
|
||||||
bool m_errored = false;
|
|
||||||
std::any m_stream;
|
|
||||||
|
|
||||||
explicit compressed_serialization_file_handler(fs::file&& file) noexcept
|
|
||||||
: utils::serialization_file_handler()
|
|
||||||
, m_file_storage(std::make_unique<fs::file>(std::move(file)))
|
|
||||||
, m_file(m_file_storage.get())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit compressed_serialization_file_handler(const fs::file& file) noexcept
|
|
||||||
: utils::serialization_file_handler()
|
|
||||||
, m_file_storage(nullptr)
|
|
||||||
, m_file(std::addressof(file))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
compressed_serialization_file_handler(const compressed_serialization_file_handler&) = delete;
|
|
||||||
|
|
||||||
// Handle file read and write requests
|
|
||||||
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
|
||||||
|
|
||||||
// Get available memory or file size
|
|
||||||
// Preferably memory size if is already greater/equal to recommended to avoid additional file ops
|
|
||||||
usz get_size(const utils::serial& ar, usz recommended) const override;
|
|
||||||
void skip_until(utils::serial& ar) override;
|
|
||||||
|
|
||||||
bool is_valid() const override
|
|
||||||
{
|
|
||||||
return !m_errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
void finalize(utils::serial& ar) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
usz read_at(utils::serial& ar, usz read_pos, void* data, usz size);
|
|
||||||
void initialize(utils::serial& ar);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename File> requires (std::is_same_v<std::remove_cvref_t<File>, fs::file>)
|
|
||||||
inline std::unique_ptr<compressed_serialization_file_handler> make_compressed_serialization_file_handler(File&& file)
|
|
||||||
{
|
|
||||||
ensure(file);
|
|
||||||
return std::make_unique<compressed_serialization_file_handler>(std::forward<File>(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null file serialization handler
|
|
||||||
struct null_serialization_file_handler : utils::serialization_file_handler
|
|
||||||
{
|
|
||||||
explicit null_serialization_file_handler() noexcept
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle file read and write requests
|
|
||||||
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
|
||||||
|
|
||||||
void finalize(utils::serial& ar) override;
|
|
||||||
|
|
||||||
bool is_null() const override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline std::unique_ptr<null_serialization_file_handler> make_null_serialization_file_handler()
|
|
||||||
{
|
|
||||||
return std::make_unique<null_serialization_file_handler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load_and_check_reserved(utils::serial& ar, usz size);
|
bool load_and_check_reserved(utils::serial& ar, usz size);
|
||||||
bool is_savestate_version_compatible(const std::vector<version_entry>& data, bool is_boot_check);
|
bool is_savestate_version_compatible(const std::vector<version_entry>& data, bool is_boot_check);
|
||||||
std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::string_view filepath);
|
std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::string_view filepath);
|
||||||
bool is_savestate_compatible(fs::file&& file, std::string_view filepath);
|
bool is_savestate_compatible(fs::file&& file, std::string_view filepath);
|
||||||
std::vector<version_entry> read_used_savestate_versions();
|
std::vector<version_entry> read_used_savestate_versions();
|
||||||
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id);
|
std::string get_savestate_file(std::string_view title_id, std::string_view boot_path, s64 abs_id, s64 rel_id);
|
||||||
|
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include "util/asm.hpp"
|
#include "util/asm.hpp"
|
||||||
|
|
||||||
#include "Emu/savestate_utils.hpp"
|
#include "util/serialization_ext.hpp"
|
||||||
|
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <span>
|
#include <span>
|
||||||
@ -139,7 +139,7 @@ std::unique_ptr<utils::serial> tar_object::get_file(const std::string& path, std
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tar_log.error("tar_object::get_file() failed to parse header: offset=0x%x, filesize=0x%x", largest_offset, max_size);
|
tar_log.error("tar_object::get_file() failed to parse header: offset=0x%x, filesize=0x%x", offset, max_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { size, {} };
|
return { size, {} };
|
||||||
|
@ -135,6 +135,9 @@
|
|||||||
<ClCompile Include="Emu\NP\rpcn_client.cpp" />
|
<ClCompile Include="Emu\NP\rpcn_client.cpp" />
|
||||||
<ClCompile Include="Emu\vfs_config.cpp" />
|
<ClCompile Include="Emu\vfs_config.cpp" />
|
||||||
<ClCompile Include="Loader\disc.cpp" />
|
<ClCompile Include="Loader\disc.cpp" />
|
||||||
|
<ClCompile Include="util\serialization_ext.cpp">
|
||||||
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="util\atomic.cpp">
|
<ClCompile Include="util\atomic.cpp">
|
||||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -622,6 +625,7 @@
|
|||||||
<ClInclude Include="util\video_provider.h" />
|
<ClInclude Include="util\video_provider.h" />
|
||||||
<ClInclude Include="util\media_utils.h" />
|
<ClInclude Include="util\media_utils.h" />
|
||||||
<ClInclude Include="util\serialization.hpp" />
|
<ClInclude Include="util\serialization.hpp" />
|
||||||
|
<ClInclude Include="util\serialization_ext.hpp" />
|
||||||
<ClInclude Include="util\v128.hpp" />
|
<ClInclude Include="util\v128.hpp" />
|
||||||
<ClInclude Include="util\simd.hpp" />
|
<ClInclude Include="util\simd.hpp" />
|
||||||
<ClInclude Include="util\to_endian.hpp" />
|
<ClInclude Include="util\to_endian.hpp" />
|
||||||
|
@ -1099,6 +1099,9 @@
|
|||||||
<ClCompile Include="Emu\RSX\Overlays\overlay_cursor.cpp">
|
<ClCompile Include="Emu\RSX\Overlays\overlay_cursor.cpp">
|
||||||
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="util\serialization_ext.cpp">
|
||||||
|
<Filter>Emu</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="Emu\savestate_utils.cpp">
|
<ClCompile Include="Emu\savestate_utils.cpp">
|
||||||
<Filter>Emu</Filter>
|
<Filter>Emu</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -2109,6 +2112,9 @@
|
|||||||
<ClInclude Include="Emu\system_progress.hpp">
|
<ClInclude Include="Emu\system_progress.hpp">
|
||||||
<Filter>Emu</Filter>
|
<Filter>Emu</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\savestate_utils.hpp">
|
||||||
|
<Filter>Utilities</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="rpcs3_version.h">
|
<ClInclude Include="rpcs3_version.h">
|
||||||
<Filter>Emu</Filter>
|
<Filter>Emu</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@ -2389,7 +2395,7 @@
|
|||||||
<ClInclude Include="Crypto\unzip.h">
|
<ClInclude Include="Crypto\unzip.h">
|
||||||
<Filter>Crypto</Filter>
|
<Filter>Crypto</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="Emu\savestate_utils.hpp">
|
<ClInclude Include="util\serialization_ext.hpp">
|
||||||
<Filter>Emu</Filter>
|
<Filter>Emu</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -56,7 +56,6 @@
|
|||||||
#include "Emu/vfs_config.h"
|
#include "Emu/vfs_config.h"
|
||||||
#include "Emu/System.h"
|
#include "Emu/System.h"
|
||||||
#include "Emu/system_utils.hpp"
|
#include "Emu/system_utils.hpp"
|
||||||
#include "Emu/savestate_utils.hpp"
|
|
||||||
|
|
||||||
#include "Crypto/unpkg.h"
|
#include "Crypto/unpkg.h"
|
||||||
#include "Crypto/unself.h"
|
#include "Crypto/unself.h"
|
||||||
@ -70,6 +69,7 @@
|
|||||||
|
|
||||||
#include "Utilities/Thread.h"
|
#include "Utilities/Thread.h"
|
||||||
#include "util/sysinfo.hpp"
|
#include "util/sysinfo.hpp"
|
||||||
|
#include "util/serialization_ext.hpp"
|
||||||
|
|
||||||
#include "ui_main_window.h"
|
#include "ui_main_window.h"
|
||||||
|
|
||||||
|
@ -73,15 +73,21 @@ namespace utils
|
|||||||
|
|
||||||
struct serial
|
struct serial
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
bool m_is_writing = true;
|
||||||
|
bool m_expect_little_data = false;
|
||||||
|
public:
|
||||||
std::vector<u8> data;
|
std::vector<u8> data;
|
||||||
usz data_offset = 0;
|
usz data_offset = 0;
|
||||||
usz pos = 0;
|
usz pos = 0;
|
||||||
usz m_max_data = umax;
|
usz m_max_data = umax;
|
||||||
bool m_is_writing = true;
|
|
||||||
bool m_avoid_large_prefetch = false;
|
|
||||||
std::unique_ptr<serialization_file_handler> m_file_handler;
|
std::unique_ptr<serialization_file_handler> m_file_handler;
|
||||||
|
|
||||||
serial() noexcept = default;
|
serial(bool expect_little_data = false) noexcept
|
||||||
|
: m_expect_little_data(expect_little_data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
serial(const serial&) = delete;
|
serial(const serial&) = delete;
|
||||||
serial& operator=(const serial&) = delete;
|
serial& operator=(const serial&) = delete;
|
||||||
explicit serial(serial&&) noexcept = default;
|
explicit serial(serial&&) noexcept = default;
|
||||||
@ -94,6 +100,12 @@ namespace utils
|
|||||||
return m_is_writing;
|
return m_is_writing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true if small amounts of both input and output memory are expected (performance hint)
|
||||||
|
bool expect_little_data() const
|
||||||
|
{
|
||||||
|
return m_expect_little_data;
|
||||||
|
}
|
||||||
|
|
||||||
// Reserve memory for serialization
|
// Reserve memory for serialization
|
||||||
void reserve(usz size)
|
void reserve(usz size)
|
||||||
{
|
{
|
||||||
@ -393,7 +405,7 @@ namespace utils
|
|||||||
|
|
||||||
// Convert serialization manager to deserializion manager
|
// Convert serialization manager to deserializion manager
|
||||||
// If no arg is provided reuse saved buffer
|
// If no arg is provided reuse saved buffer
|
||||||
void set_reading_state(std::vector<u8>&& _data = std::vector<u8>{}, bool avoid_large_prefetch = false)
|
void set_reading_state(std::vector<u8>&& _data = std::vector<u8>{}, bool expect_little_data = false)
|
||||||
{
|
{
|
||||||
if (!_data.empty())
|
if (!_data.empty())
|
||||||
{
|
{
|
||||||
@ -401,7 +413,8 @@ namespace utils
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_is_writing = false;
|
m_is_writing = false;
|
||||||
m_avoid_large_prefetch = avoid_large_prefetch;
|
m_expect_little_data = expect_little_data;
|
||||||
|
m_max_data = umax;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
data_offset = 0;
|
data_offset = 0;
|
||||||
}
|
}
|
||||||
|
738
rpcs3/util/serialization_ext.cpp
Normal file
738
rpcs3/util/serialization_ext.cpp
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
#include "util/types.hpp"
|
||||||
|
#include "util/logs.hpp"
|
||||||
|
#include "util/asm.hpp"
|
||||||
|
#include "util/simd.hpp"
|
||||||
|
#include "util/endian.hpp"
|
||||||
|
|
||||||
|
#include "Utilities/lockless.h"
|
||||||
|
#include "Utilities/File.h"
|
||||||
|
#include "Utilities/StrFmt.h"
|
||||||
|
#include "serialization_ext.hpp"
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
LOG_CHANNEL(sys_log, "SYS");
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void fmt_class_string<utils::serial>::format(std::string& out, u64 arg)
|
||||||
|
{
|
||||||
|
const utils::serial& ar = get_object(arg);
|
||||||
|
|
||||||
|
be_t<u64> sample64 = 0;
|
||||||
|
const usz read_size = std::min<usz>(ar.data.size(), sizeof(sample64));
|
||||||
|
std::memcpy(&sample64, ar.data.data() + ar.data.size() - read_size, read_size);
|
||||||
|
|
||||||
|
fmt::append(out, "{ %s, 0x%x/0x%x, memory=0x%x, sample64=0x%016x }", ar.is_writing() ? "writing" : "reading", ar.pos, ar.data_offset + ar.data.size(), ar.data.size(), sample64);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr uInt adjust_for_uint(usz size)
|
||||||
|
{
|
||||||
|
return static_cast<uInt>(std::min<usz>(uInt{umax}, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uncompressed_serialization_file_handler::handle_file_op(utils::serial& ar, usz pos, usz size, const void* data)
|
||||||
|
{
|
||||||
|
if (ar.is_writing())
|
||||||
|
{
|
||||||
|
if (data)
|
||||||
|
{
|
||||||
|
m_file->seek(pos);
|
||||||
|
m_file->write(data, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file->seek(ar.data_offset);
|
||||||
|
m_file->write(ar.data);
|
||||||
|
|
||||||
|
if (pos == umax && size == umax)
|
||||||
|
{
|
||||||
|
// Request to flush the file to disk
|
||||||
|
m_file->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
ar.seek_end();
|
||||||
|
ar.data_offset = ar.pos;
|
||||||
|
ar.data.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == 0 && size == umax)
|
||||||
|
{
|
||||||
|
// Discard loaded data until pos if profitable
|
||||||
|
const usz limit = ar.data_offset + ar.data.size();
|
||||||
|
|
||||||
|
if (ar.pos > ar.data_offset && ar.pos < limit)
|
||||||
|
{
|
||||||
|
const usz may_discard_bytes = ar.pos - ar.data_offset;
|
||||||
|
const usz moved_byte_count_on_discard = limit - ar.pos;
|
||||||
|
|
||||||
|
// Cheeck profitability (check recycled memory and std::memmove costs)
|
||||||
|
if (may_discard_bytes >= 0x50'0000 || (may_discard_bytes >= 0x20'0000 && moved_byte_count_on_discard / may_discard_bytes < 3))
|
||||||
|
{
|
||||||
|
ar.data_offset += may_discard_bytes;
|
||||||
|
ar.data.erase(ar.data.begin(), ar.data.begin() + may_discard_bytes);
|
||||||
|
|
||||||
|
if (ar.data.capacity() >= 0x200'0000)
|
||||||
|
{
|
||||||
|
// Discard memory
|
||||||
|
ar.data.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard all loaded data
|
||||||
|
ar.data_offset = ar.pos;
|
||||||
|
ar.data.clear();
|
||||||
|
|
||||||
|
if (ar.data.capacity() >= 0x200'0000)
|
||||||
|
{
|
||||||
|
// Discard memory
|
||||||
|
ar.data.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (~pos < size - 1)
|
||||||
|
{
|
||||||
|
// Overflow
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar.data.empty())
|
||||||
|
{
|
||||||
|
// Relocate instead of over-fetch
|
||||||
|
ar.data_offset = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usz read_pre_buffer = ar.data.empty() ? 0 : utils::sub_saturate<usz>(ar.data_offset, pos);
|
||||||
|
|
||||||
|
if (read_pre_buffer)
|
||||||
|
{
|
||||||
|
// Read past data
|
||||||
|
// Harsh operation on performance, luckily rare and not typically needed
|
||||||
|
// Also this may would be disallowed when moving to compressed files
|
||||||
|
// This may be a result of wrong usage of breathe() function
|
||||||
|
ar.data.resize(ar.data.size() + read_pre_buffer);
|
||||||
|
std::memmove(ar.data.data() + read_pre_buffer, ar.data.data(), ar.data.size() - read_pre_buffer);
|
||||||
|
ensure(m_file->read_at(pos, ar.data.data(), read_pre_buffer) == read_pre_buffer);
|
||||||
|
ar.data_offset -= read_pre_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjustment to prevent overflow
|
||||||
|
const usz subtrahend = ar.data.empty() ? 0 : 1;
|
||||||
|
const usz read_past_buffer = utils::sub_saturate<usz>(pos + (size - subtrahend), ar.data_offset + (ar.data.size() - subtrahend));
|
||||||
|
const usz read_limit = utils::sub_saturate<usz>(ar.m_max_data, ar.data_offset);
|
||||||
|
|
||||||
|
if (read_past_buffer)
|
||||||
|
{
|
||||||
|
// Read proceeding data
|
||||||
|
// More lightweight operation, this is the common operation
|
||||||
|
// Allowed to fail, if memory is truly needed an assert would take place later
|
||||||
|
const usz old_size = ar.data.size();
|
||||||
|
|
||||||
|
// Try to prefetch data by reading more than requested
|
||||||
|
ar.data.resize(std::min<usz>(read_limit, std::max<usz>({ ar.data.capacity(), ar.data.size() + read_past_buffer * 3 / 2, ar.expect_little_data() ? usz{4096} : usz{0x10'0000} })));
|
||||||
|
ar.data.resize(m_file->read_at(old_size + ar.data_offset, data ? const_cast<void*>(data) : ar.data.data() + old_size, ar.data.size() - old_size) + old_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
usz uncompressed_serialization_file_handler::get_size(const utils::serial& ar, usz recommended) const
|
||||||
|
{
|
||||||
|
if (ar.is_writing())
|
||||||
|
{
|
||||||
|
return m_file->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const usz memory_available = ar.data_offset + ar.data.size();
|
||||||
|
|
||||||
|
if (memory_available >= recommended)
|
||||||
|
{
|
||||||
|
// Avoid calling size() if possible
|
||||||
|
return memory_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::max<usz>(m_file->size(), memory_available);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uncompressed_serialization_file_handler::finalize(utils::serial& ar)
|
||||||
|
{
|
||||||
|
ar.seek_end();
|
||||||
|
handle_file_op(ar, 0, umax, nullptr);
|
||||||
|
ar.data = {}; // Deallocate and clear
|
||||||
|
}
|
||||||
|
|
||||||
|
struct compressed_stream_data
|
||||||
|
{
|
||||||
|
z_stream m_zs{};
|
||||||
|
lf_queue<std::vector<u8>> m_queued_data_to_process;
|
||||||
|
lf_queue<std::vector<u8>> m_queued_data_to_write;
|
||||||
|
atomic_t<usz> m_pending_bytes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::initialize(utils::serial& ar)
|
||||||
|
{
|
||||||
|
if (!m_stream)
|
||||||
|
{
|
||||||
|
m_stream = std::make_shared<compressed_stream_data>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar.is_writing())
|
||||||
|
{
|
||||||
|
if (m_write_inited)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
z_stream& m_zs = m_stream->m_zs;
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||||
|
#endif
|
||||||
|
if (m_read_inited)
|
||||||
|
{
|
||||||
|
finalize(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_zs = {};
|
||||||
|
ensure(deflateInit2(&m_zs, 9, Z_DEFLATED, 16 + 15, 9, Z_DEFAULT_STRATEGY) == Z_OK);
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
m_write_inited = true;
|
||||||
|
m_errored = false;
|
||||||
|
|
||||||
|
if (!ar.expect_little_data())
|
||||||
|
{
|
||||||
|
m_stream_data_prepare_thread = std::make_unique<named_thread<std::function<void()>>>("Compressed Data Prepare Thread"sv, [this]() { this->stream_data_prepare_thread_op(); });
|
||||||
|
m_file_writer_thread = std::make_unique<named_thread<std::function<void()>>>("Compressed File Writer Thread"sv, [this]() { this->file_writer_thread_op(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_read_inited)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_write_inited)
|
||||||
|
{
|
||||||
|
finalize(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
z_stream& m_zs = m_stream->m_zs;
|
||||||
|
|
||||||
|
m_zs.avail_in = 0;
|
||||||
|
m_zs.avail_out = 0;
|
||||||
|
m_zs.next_in = nullptr;
|
||||||
|
m_zs.next_out = nullptr;
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||||
|
#endif
|
||||||
|
ensure(inflateInit2(&m_zs, 16 + 15) == Z_OK);
|
||||||
|
m_read_inited = true;
|
||||||
|
m_errored = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compressed_serialization_file_handler::handle_file_op(utils::serial& ar, usz pos, usz size, const void* data)
|
||||||
|
{
|
||||||
|
if (ar.is_writing())
|
||||||
|
{
|
||||||
|
initialize(ar);
|
||||||
|
|
||||||
|
if (m_errored)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& manager = *m_stream;
|
||||||
|
auto& stream_data = manager.m_queued_data_to_process;
|
||||||
|
|
||||||
|
if (data)
|
||||||
|
{
|
||||||
|
ensure(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writing not at the end is forbidden
|
||||||
|
ensure(ar.pos == ar.data_offset + ar.data.size());
|
||||||
|
|
||||||
|
if (ar.data.empty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ar.seek_end();
|
||||||
|
|
||||||
|
if (!m_file_writer_thread)
|
||||||
|
{
|
||||||
|
// Avoid multi-threading for small files
|
||||||
|
blocked_compressed_write(ar.data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Avoid flooding RAM, wait if there is too much pending memory
|
||||||
|
const usz new_value = m_pending_bytes.atomic_op([&](usz v)
|
||||||
|
{
|
||||||
|
v &= ~(1ull << 63);
|
||||||
|
|
||||||
|
if (v + ar.data.size() > 0x400'0000)
|
||||||
|
{
|
||||||
|
v |= 1ull << 63;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
v += ar.data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (new_value & (1ull << 63))
|
||||||
|
{
|
||||||
|
m_pending_bytes.wait(new_value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_data.push(std::move(ar.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ar.data_offset = ar.pos;
|
||||||
|
ar.data.clear();
|
||||||
|
|
||||||
|
if (pos == umax && size == umax && *m_file)
|
||||||
|
{
|
||||||
|
// Request to flush the file to disk
|
||||||
|
m_file->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(ar);
|
||||||
|
|
||||||
|
if (m_errored)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos == 0 && size == umax)
|
||||||
|
{
|
||||||
|
// Discard loaded data until pos if profitable
|
||||||
|
const usz limit = ar.data_offset + ar.data.size();
|
||||||
|
|
||||||
|
if (ar.pos > ar.data_offset && ar.pos < limit)
|
||||||
|
{
|
||||||
|
const usz may_discard_bytes = ar.pos - ar.data_offset;
|
||||||
|
const usz moved_byte_count_on_discard = limit - ar.pos;
|
||||||
|
|
||||||
|
// Cheeck profitability (check recycled memory and std::memmove costs)
|
||||||
|
if (may_discard_bytes >= 0x50'0000 || (may_discard_bytes >= 0x20'0000 && moved_byte_count_on_discard / may_discard_bytes < 3))
|
||||||
|
{
|
||||||
|
ar.data_offset += may_discard_bytes;
|
||||||
|
ar.data.erase(ar.data.begin(), ar.data.begin() + may_discard_bytes);
|
||||||
|
|
||||||
|
if (ar.data.capacity() >= 0x200'0000)
|
||||||
|
{
|
||||||
|
// Discard memory
|
||||||
|
ar.data.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard all loaded data
|
||||||
|
ar.data_offset += ar.data.size();
|
||||||
|
ensure(ar.pos >= ar.data_offset);
|
||||||
|
ar.data.clear();
|
||||||
|
|
||||||
|
if (ar.data.capacity() >= 0x200'0000)
|
||||||
|
{
|
||||||
|
// Discard memory
|
||||||
|
ar.data.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (~pos < size - 1)
|
||||||
|
{
|
||||||
|
// Overflow
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Investigate if this optimization is worth an implementation for compressed stream
|
||||||
|
// if (ar.data.empty() && pos != ar.pos)
|
||||||
|
// {
|
||||||
|
// // Relocate instead of over-fetch
|
||||||
|
// ar.seek_pos(pos);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const usz read_pre_buffer = utils::sub_saturate<usz>(ar.data_offset, pos);
|
||||||
|
|
||||||
|
if (read_pre_buffer)
|
||||||
|
{
|
||||||
|
// Not allowed with compressed data for now
|
||||||
|
// Unless someone implements mechanism for it
|
||||||
|
ensure(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjustment to prevent overflow
|
||||||
|
const usz subtrahend = ar.data.empty() ? 0 : 1;
|
||||||
|
const usz read_past_buffer = utils::sub_saturate<usz>(pos + (size - subtrahend), ar.data_offset + (ar.data.size() - subtrahend));
|
||||||
|
const usz read_limit = utils::sub_saturate<usz>(ar.m_max_data, ar.data_offset);
|
||||||
|
|
||||||
|
if (read_past_buffer)
|
||||||
|
{
|
||||||
|
// Read proceeding data
|
||||||
|
// More lightweight operation, this is the common operation
|
||||||
|
// Allowed to fail, if memory is truly needed an assert would take place later
|
||||||
|
const usz old_size = ar.data.size();
|
||||||
|
|
||||||
|
// Try to prefetch data by reading more than requested
|
||||||
|
ar.data.resize(std::min<usz>(read_limit, std::max<usz>({ ar.data.capacity(), ar.data.size() + read_past_buffer * 3 / 2, ar.expect_little_data() ? usz{4096} : usz{0x10'0000} })));
|
||||||
|
ar.data.resize(this->read_at(ar, old_size + ar.data_offset, data ? const_cast<void*>(data) : ar.data.data() + old_size, ar.data.size() - old_size) + old_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
usz compressed_serialization_file_handler::read_at(utils::serial& ar, usz read_pos, void* data, usz size)
|
||||||
|
{
|
||||||
|
ensure(read_pos == ar.data.size() + ar.data_offset - size);
|
||||||
|
|
||||||
|
if (!size || m_errored)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(ar);
|
||||||
|
|
||||||
|
z_stream& m_zs = m_stream->m_zs;
|
||||||
|
|
||||||
|
const usz total_to_read = size;
|
||||||
|
usz read_size = 0;
|
||||||
|
u8* out_data = static_cast<u8*>(data);
|
||||||
|
|
||||||
|
for (; read_size < total_to_read;)
|
||||||
|
{
|
||||||
|
// Drain extracted memory stash (also before first file read)
|
||||||
|
out_data = static_cast<u8*>(data) + read_size;
|
||||||
|
m_zs.avail_in = adjust_for_uint(m_stream_data.size() - m_stream_data_index);
|
||||||
|
m_zs.next_in = reinterpret_cast<const u8*>(m_stream_data.data() + m_stream_data_index);
|
||||||
|
m_zs.next_out = out_data;
|
||||||
|
m_zs.avail_out = adjust_for_uint(size - read_size);
|
||||||
|
|
||||||
|
while (read_size < total_to_read && m_zs.avail_in)
|
||||||
|
{
|
||||||
|
const int res = inflate(&m_zs, Z_BLOCK);
|
||||||
|
|
||||||
|
bool need_more_file_memory = false;
|
||||||
|
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case Z_OK:
|
||||||
|
case Z_STREAM_END:
|
||||||
|
break;
|
||||||
|
case Z_BUF_ERROR:
|
||||||
|
{
|
||||||
|
if (m_zs.avail_in)
|
||||||
|
{
|
||||||
|
need_more_file_memory = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[fallthrough]];
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
m_errored = true;
|
||||||
|
inflateEnd(&m_zs);
|
||||||
|
m_read_inited = false;
|
||||||
|
sys_log.error("Failure of compressed data reading. (res=%d, read_size=0x%x, avail_in=0x%x, avail_out=0x%x, ar=%s)", res, read_size, m_zs.avail_in, m_zs.avail_out, ar);
|
||||||
|
return read_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_size = m_zs.next_out - static_cast<u8*>(data);
|
||||||
|
m_stream_data_index = m_zs.avail_in ? m_zs.next_in - m_stream_data.data() : m_stream_data.size();
|
||||||
|
|
||||||
|
// Adjust again in case the values simply did not fit into uInt
|
||||||
|
m_zs.avail_out = adjust_for_uint(utils::sub_saturate<usz>(total_to_read, read_size));
|
||||||
|
m_zs.avail_in = adjust_for_uint(m_stream_data.size() - m_stream_data_index);
|
||||||
|
|
||||||
|
if (need_more_file_memory)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_size >= total_to_read)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usz add_size = ar.expect_little_data() ? 0x1'0000 : 0x10'0000;
|
||||||
|
const usz old_file_buf_size = m_stream_data.size();
|
||||||
|
|
||||||
|
m_stream_data.resize(old_file_buf_size + add_size);
|
||||||
|
m_stream_data.resize(old_file_buf_size + m_file->read_at(m_file_read_index, m_stream_data.data() + old_file_buf_size, add_size));
|
||||||
|
|
||||||
|
if (m_stream_data.size() == old_file_buf_size)
|
||||||
|
{
|
||||||
|
// EOF
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file_read_index += m_stream_data.size() - old_file_buf_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_stream_data.size() - m_stream_data_index <= m_stream_data_index / 5)
|
||||||
|
{
|
||||||
|
// Shrink to required memory size
|
||||||
|
m_stream_data.erase(m_stream_data.begin(), m_stream_data.begin() + m_stream_data_index);
|
||||||
|
|
||||||
|
if (m_stream_data.capacity() >= 0x200'0000)
|
||||||
|
{
|
||||||
|
// Discard memory
|
||||||
|
m_stream_data.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_stream_data_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::skip_until(utils::serial& ar)
|
||||||
|
{
|
||||||
|
ensure(!ar.is_writing() && ar.pos >= ar.data_offset);
|
||||||
|
|
||||||
|
if (ar.pos > ar.data_offset)
|
||||||
|
{
|
||||||
|
handle_file_op(ar, ar.data_offset, ar.pos - ar.data_offset, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::finalize(utils::serial& ar)
|
||||||
|
{
|
||||||
|
handle_file_op(ar, 0, umax, nullptr);
|
||||||
|
|
||||||
|
if (!m_stream)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& stream = *m_stream;
|
||||||
|
z_stream& m_zs = m_stream->m_zs;
|
||||||
|
|
||||||
|
if (m_read_inited)
|
||||||
|
{
|
||||||
|
ensure(inflateEnd(&m_zs) == Z_OK);
|
||||||
|
m_read_inited = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.m_queued_data_to_process.push(std::vector<u8>());
|
||||||
|
|
||||||
|
if (m_file_writer_thread)
|
||||||
|
{
|
||||||
|
// Join here to avoid log messages in the destructor
|
||||||
|
(*m_file_writer_thread)();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_stream_data_prepare_thread.reset();
|
||||||
|
m_file_writer_thread.reset();
|
||||||
|
|
||||||
|
m_zs.avail_in = 0;
|
||||||
|
m_zs.next_in = nullptr;
|
||||||
|
|
||||||
|
m_stream_data.resize(0x10'0000);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
m_zs.avail_out = static_cast<uInt>(m_stream_data.size());
|
||||||
|
m_zs.next_out = m_stream_data.data();
|
||||||
|
|
||||||
|
if (deflate(&m_zs, Z_FINISH) == Z_STREAM_ERROR)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_file->write(m_stream_data.data(), m_stream_data.size() - m_zs.avail_out);
|
||||||
|
}
|
||||||
|
while (m_zs.avail_out == 0);
|
||||||
|
|
||||||
|
m_stream_data = {};
|
||||||
|
ensure(deflateEnd(&m_zs) == Z_OK);
|
||||||
|
m_write_inited = false;
|
||||||
|
ar.data = {}; // Deallocate and clear
|
||||||
|
}
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::stream_data_prepare_thread_op()
|
||||||
|
{
|
||||||
|
compressed_stream_data& stream = *m_stream;
|
||||||
|
z_stream& m_zs = stream.m_zs;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
stream.m_queued_data_to_process.wait();
|
||||||
|
|
||||||
|
for (auto&& data : stream.m_queued_data_to_process.pop_all())
|
||||||
|
{
|
||||||
|
if (data.empty())
|
||||||
|
{
|
||||||
|
// Abort is requested, flush data and exit
|
||||||
|
if (!m_stream_data.empty())
|
||||||
|
{
|
||||||
|
stream.m_queued_data_to_write.push(std::move(m_stream_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.m_queued_data_to_write.push(std::vector<u8>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_zs.avail_in = adjust_for_uint(data.size());
|
||||||
|
m_zs.next_in = data.data();
|
||||||
|
|
||||||
|
usz buffer_offset = 0;
|
||||||
|
m_stream_data.resize(::compressBound(m_zs.avail_in));
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
m_zs.avail_out = adjust_for_uint(m_stream_data.size() - buffer_offset);
|
||||||
|
m_zs.next_out = m_stream_data.data() + buffer_offset;
|
||||||
|
|
||||||
|
if (deflate(&m_zs, Z_NO_FLUSH) == Z_STREAM_ERROR)
|
||||||
|
{
|
||||||
|
m_errored = true;
|
||||||
|
deflateEnd(&m_zs);
|
||||||
|
|
||||||
|
// Abort
|
||||||
|
stream.m_queued_data_to_write.push(std::vector<u8>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_offset = m_zs.next_out - m_stream_data.data();
|
||||||
|
|
||||||
|
if (m_zs.avail_out == 0)
|
||||||
|
{
|
||||||
|
m_stream_data.resize(m_stream_data.size() + (m_zs.avail_in + 3ull) / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_zs.avail_in = adjust_for_uint(data.size() - (m_zs.next_in - data.data()));
|
||||||
|
}
|
||||||
|
while (m_zs.avail_out == 0 || m_zs.avail_in != 0);
|
||||||
|
|
||||||
|
// Forward for file write
|
||||||
|
const usz queued_size = data.size();
|
||||||
|
ensure(buffer_offset);
|
||||||
|
m_pending_bytes += buffer_offset - queued_size;
|
||||||
|
m_stream_data.resize(buffer_offset);
|
||||||
|
stream.m_queued_data_to_write.push(std::move(m_stream_data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::file_writer_thread_op()
|
||||||
|
{
|
||||||
|
compressed_stream_data& stream = *m_stream;
|
||||||
|
|
||||||
|
// Data recheck after an abort request is detected so there will not be any missed data
|
||||||
|
bool rechecked = false;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
stream.m_queued_data_to_write.wait();
|
||||||
|
|
||||||
|
for (auto&& data : stream.m_queued_data_to_write.pop_all())
|
||||||
|
{
|
||||||
|
if (data.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usz last_size = data.size();
|
||||||
|
m_file->write(data);
|
||||||
|
data = {}; // Deallocate before notification
|
||||||
|
|
||||||
|
if (m_pending_bytes.sub_fetch(last_size) == 1ull << 63)
|
||||||
|
{
|
||||||
|
m_pending_bytes.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void compressed_serialization_file_handler::blocked_compressed_write(const std::vector<u8>& data)
|
||||||
|
{
|
||||||
|
z_stream& m_zs = m_stream->m_zs;
|
||||||
|
|
||||||
|
m_zs.avail_in = adjust_for_uint(data.size());
|
||||||
|
m_zs.next_in = data.data();
|
||||||
|
|
||||||
|
m_stream_data.resize(::compressBound(m_zs.avail_in));
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
m_zs.avail_out = adjust_for_uint(m_stream_data.size());
|
||||||
|
m_zs.next_out = m_stream_data.data();
|
||||||
|
|
||||||
|
if (deflate(&m_zs, Z_NO_FLUSH) == Z_STREAM_ERROR || m_file->write(m_stream_data.data(), m_stream_data.size() - m_zs.avail_out) != m_stream_data.size() - m_zs.avail_out)
|
||||||
|
{
|
||||||
|
m_errored = true;
|
||||||
|
deflateEnd(&m_zs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_zs.avail_in = adjust_for_uint(data.size() - (m_zs.next_in - data.data()));
|
||||||
|
}
|
||||||
|
while (m_zs.avail_out == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
usz compressed_serialization_file_handler::get_size(const utils::serial& ar, usz recommended) const
|
||||||
|
{
|
||||||
|
if (ar.is_writing())
|
||||||
|
{
|
||||||
|
return m_file->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const usz memory_available = ar.data_offset + ar.data.size();
|
||||||
|
|
||||||
|
if (memory_available >= recommended)
|
||||||
|
{
|
||||||
|
// Avoid calling size() if possible
|
||||||
|
return memory_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::max<usz>(utils::mul_saturate<usz>(m_file->size(), 6), memory_available);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool null_serialization_file_handler::handle_file_op(utils::serial&, usz, usz, const void*)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void null_serialization_file_handler::finalize(utils::serial&)
|
||||||
|
{
|
||||||
|
}
|
136
rpcs3/util/serialization_ext.hpp
Normal file
136
rpcs3/util/serialization_ext.hpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/serialization.hpp"
|
||||||
|
|
||||||
|
#include "Utilities/Thread.h"
|
||||||
|
|
||||||
|
namespace fs
|
||||||
|
{
|
||||||
|
class file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncompressed file serialization handler
|
||||||
|
struct uncompressed_serialization_file_handler : utils::serialization_file_handler
|
||||||
|
{
|
||||||
|
const std::unique_ptr<fs::file> m_file_storage;
|
||||||
|
const std::add_pointer_t<const fs::file> m_file;
|
||||||
|
|
||||||
|
explicit uncompressed_serialization_file_handler(fs::file&& file) noexcept
|
||||||
|
: utils::serialization_file_handler()
|
||||||
|
, m_file_storage(std::make_unique<fs::file>(std::move(file)))
|
||||||
|
, m_file(m_file_storage.get())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit uncompressed_serialization_file_handler(const fs::file& file) noexcept
|
||||||
|
: utils::serialization_file_handler()
|
||||||
|
, m_file_storage(nullptr)
|
||||||
|
, m_file(std::addressof(file))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uncompressed_serialization_file_handler(const uncompressed_serialization_file_handler&) = delete;
|
||||||
|
|
||||||
|
// Handle file read and write requests
|
||||||
|
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
||||||
|
|
||||||
|
// Get available memory or file size
|
||||||
|
// Preferably memory size if is already greater/equal to recommended to avoid additional file ops
|
||||||
|
usz get_size(const utils::serial& ar, usz recommended) const override;
|
||||||
|
|
||||||
|
void finalize(utils::serial& ar) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename File> requires (std::is_same_v<std::remove_cvref_t<File>, fs::file>)
|
||||||
|
inline std::unique_ptr<uncompressed_serialization_file_handler> make_uncompressed_serialization_file_handler(File&& file)
|
||||||
|
{
|
||||||
|
ensure(file);
|
||||||
|
return std::make_unique<uncompressed_serialization_file_handler>(std::forward<File>(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct compressed_stream_data;
|
||||||
|
|
||||||
|
// Compressed file serialization handler
|
||||||
|
struct compressed_serialization_file_handler : utils::serialization_file_handler
|
||||||
|
{
|
||||||
|
explicit compressed_serialization_file_handler(fs::file&& file) noexcept
|
||||||
|
: utils::serialization_file_handler()
|
||||||
|
, m_file_storage(std::make_unique<fs::file>(std::move(file)))
|
||||||
|
, m_file(m_file_storage.get())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit compressed_serialization_file_handler(const fs::file& file) noexcept
|
||||||
|
: utils::serialization_file_handler()
|
||||||
|
, m_file_storage(nullptr)
|
||||||
|
, m_file(std::addressof(file))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed_serialization_file_handler(const compressed_serialization_file_handler&) = delete;
|
||||||
|
|
||||||
|
// Handle file read and write requests
|
||||||
|
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
||||||
|
|
||||||
|
// Get available memory or file size
|
||||||
|
// Preferably memory size if is already greater/equal to recommended to avoid additional file ops
|
||||||
|
usz get_size(const utils::serial& ar, usz recommended) const override;
|
||||||
|
void skip_until(utils::serial& ar) override;
|
||||||
|
|
||||||
|
bool is_valid() const override
|
||||||
|
{
|
||||||
|
return !m_errored;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalize(utils::serial& ar) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::unique_ptr<fs::file> m_file_storage;
|
||||||
|
const std::add_pointer_t<const fs::file> m_file;
|
||||||
|
std::vector<u8> m_stream_data;
|
||||||
|
usz m_stream_data_index = 0;
|
||||||
|
usz m_file_read_index = 0;
|
||||||
|
atomic_t<usz> m_pending_bytes = 0;
|
||||||
|
bool m_write_inited = false;
|
||||||
|
bool m_read_inited = false;
|
||||||
|
bool m_errored = false;
|
||||||
|
std::shared_ptr<compressed_stream_data> m_stream;
|
||||||
|
std::unique_ptr<named_thread<std::function<void()>>> m_stream_data_prepare_thread;
|
||||||
|
std::unique_ptr<named_thread<std::function<void()>>> m_file_writer_thread;
|
||||||
|
|
||||||
|
usz read_at(utils::serial& ar, usz read_pos, void* data, usz size);
|
||||||
|
void initialize(utils::serial& ar);
|
||||||
|
void stream_data_prepare_thread_op();
|
||||||
|
void file_writer_thread_op();
|
||||||
|
void blocked_compressed_write(const std::vector<u8>& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename File> requires (std::is_same_v<std::remove_cvref_t<File>, fs::file>)
|
||||||
|
inline std::unique_ptr<compressed_serialization_file_handler> make_compressed_serialization_file_handler(File&& file)
|
||||||
|
{
|
||||||
|
ensure(file);
|
||||||
|
return std::make_unique<compressed_serialization_file_handler>(std::forward<File>(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null file serialization handler
|
||||||
|
struct null_serialization_file_handler : utils::serialization_file_handler
|
||||||
|
{
|
||||||
|
explicit null_serialization_file_handler() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file read and write requests
|
||||||
|
bool handle_file_op(utils::serial& ar, usz pos, usz size, const void* data) override;
|
||||||
|
|
||||||
|
void finalize(utils::serial& ar) override;
|
||||||
|
|
||||||
|
bool is_null() const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::unique_ptr<null_serialization_file_handler> make_null_serialization_file_handler()
|
||||||
|
{
|
||||||
|
return std::make_unique<null_serialization_file_handler>();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user