mirror of
https://github.com/stenzek/duckstation.git
synced 2024-11-27 08:00:36 +00:00
System: Rewrite save state I/O
No more ByteStream or virtual calls for every piece of data.
This commit is contained in:
parent
dd8bf2c9d9
commit
a6518ff9dc
@ -124,6 +124,12 @@ std::size_t StringUtil::Strlcpy(char* dst, const char* src, std::size_t size)
|
||||
return len;
|
||||
}
|
||||
|
||||
std::size_t StringUtil::Strnlen(const char* str, std::size_t max_size)
|
||||
{
|
||||
const char* loc = static_cast<const char*>(std::memchr(str, 0, max_size));
|
||||
return loc ? static_cast<size_t>(loc - str) : max_size;
|
||||
}
|
||||
|
||||
std::size_t StringUtil::Strlcpy(char* dst, const std::string_view src, std::size_t size)
|
||||
{
|
||||
std::size_t len = src.length();
|
||||
|
@ -35,6 +35,9 @@ std::size_t Strlcpy(char* dst, const char* src, std::size_t size);
|
||||
/// Strlcpy from string_view.
|
||||
std::size_t Strlcpy(char* dst, const std::string_view src, std::size_t size);
|
||||
|
||||
/// Bounds checked version of strlen.
|
||||
std::size_t Strnlen(const char* str, std::size_t max_size);
|
||||
|
||||
/// Platform-independent strcasecmp
|
||||
static inline int Strcasecmp(const char* s1, const char* s2)
|
||||
{
|
||||
|
@ -136,8 +136,8 @@ set(NEWREC_SOURCES
|
||||
target_precompile_headers(core PRIVATE "pch.h")
|
||||
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_link_libraries(core PUBLIC Threads::Threads common util ZLIB::ZLIB)
|
||||
target_link_libraries(core PRIVATE xxhash imgui rapidyaml rcheevos cpuinfo::cpuinfo)
|
||||
target_link_libraries(core PUBLIC Threads::Threads common util)
|
||||
target_link_libraries(core PRIVATE xxhash imgui rapidyaml rcheevos cpuinfo::cpuinfo ZLIB::ZLIB Zstd::Zstd)
|
||||
|
||||
if(CPU_ARCH_X64)
|
||||
target_compile_definitions(core PUBLIC "ENABLE_RECOMPILER=1" "ENABLE_NEWREC=1" "ENABLE_MMAP_FASTMEM=1")
|
||||
|
@ -5559,11 +5559,11 @@ bool FullscreenUI::InitializeSaveStateListEntryFromPath(SaveStateListEntry* li,
|
||||
void FullscreenUI::PopulateSaveStateScreenshot(SaveStateListEntry* li, const ExtendedSaveStateInfo* ssi)
|
||||
{
|
||||
li->preview_texture.reset();
|
||||
if (ssi && !ssi->screenshot_data.empty())
|
||||
if (ssi && ssi->screenshot.IsValid())
|
||||
{
|
||||
li->preview_texture = g_gpu_device->FetchTexture(ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1,
|
||||
li->preview_texture = g_gpu_device->FetchTexture(ssi->screenshot.GetWidth(), ssi->screenshot.GetHeight(), 1, 1, 1,
|
||||
GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
|
||||
ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width);
|
||||
ssi->screenshot.GetPixels(), ssi->screenshot.GetPitch());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -6112,7 +6112,7 @@ void FullscreenUI::DoLoadState(std::string path)
|
||||
else
|
||||
{
|
||||
Error error;
|
||||
if (!System::LoadState(path.c_str(), &error))
|
||||
if (!System::LoadState(path.c_str(), &error, true))
|
||||
{
|
||||
ShowToast(std::string(),
|
||||
fmt::format(TRANSLATE_FS("System", "Failed to load state: {}"), error.GetDescription()));
|
||||
|
@ -84,7 +84,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot)
|
||||
}
|
||||
|
||||
Error error;
|
||||
if (!System::LoadState(path.c_str(), &error))
|
||||
if (!System::LoadState(path.c_str(), &error, true))
|
||||
{
|
||||
Host::AddKeyedOSDMessage(
|
||||
"LoadState",
|
||||
|
@ -891,11 +891,11 @@ void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateIn
|
||||
{
|
||||
g_gpu_device->RecycleTexture(std::move(li->preview_texture));
|
||||
|
||||
if (ssi && !ssi->screenshot_data.empty())
|
||||
if (ssi->screenshot.IsValid())
|
||||
{
|
||||
li->preview_texture = g_gpu_device->FetchTexture(
|
||||
ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
|
||||
ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width);
|
||||
li->preview_texture = g_gpu_device->FetchTexture(ssi->screenshot.GetWidth(), ssi->screenshot.GetHeight(), 1, 1, 1,
|
||||
GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
|
||||
ssi->screenshot.GetPixels(), ssi->screenshot.GetPitch());
|
||||
if (!li->preview_texture) [[unlikely]]
|
||||
ERROR_LOG("Failed to upload save state image to GPU");
|
||||
}
|
||||
@ -1084,7 +1084,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
Error error;
|
||||
if (!System::LoadState(path.c_str(), &error))
|
||||
if (!System::LoadState(path.c_str(), &error, true))
|
||||
{
|
||||
Host::AddKeyedOSDMessage("LoadState",
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"),
|
||||
|
@ -133,7 +133,7 @@ static bool s_receive_buffer_full = false;
|
||||
static bool s_transmit_buffer_full = false;
|
||||
|
||||
static u32 s_last_memory_card_transfer_frame = 0;
|
||||
static std::unique_ptr<GrowableMemoryByteStream> s_memory_card_backup;
|
||||
static DynamicHeapArray<u8> s_memory_card_backup;
|
||||
static std::unique_ptr<MemoryCard> s_dummy_card;
|
||||
|
||||
} // namespace Pad
|
||||
@ -145,7 +145,7 @@ void Pad::Initialize()
|
||||
|
||||
void Pad::Shutdown()
|
||||
{
|
||||
s_memory_card_backup.reset();
|
||||
s_memory_card_backup.deallocate();
|
||||
|
||||
s_transfer_event.Deactivate();
|
||||
|
||||
@ -197,7 +197,7 @@ bool Pad::DoStateController(StateWrapper& sw, u32 i)
|
||||
if (controller_type != state_controller_type)
|
||||
{
|
||||
const Controller::ControllerInfo* state_cinfo = Controller::GetControllerInfo(state_controller_type);
|
||||
Assert(sw.GetMode() == StateWrapper::Mode::Read);
|
||||
Assert(sw.IsReading());
|
||||
|
||||
// UI notification portion is separated from emulation portion (intentional condition check redundancy)
|
||||
if (g_settings.load_devices_from_save_states)
|
||||
@ -363,16 +363,10 @@ void Pad::BackupMemoryCardState()
|
||||
{
|
||||
DEV_LOG("Backing up memory card state.");
|
||||
|
||||
if (!s_memory_card_backup)
|
||||
{
|
||||
s_memory_card_backup =
|
||||
std::make_unique<GrowableMemoryByteStream>(nullptr, MemoryCard::STATE_SIZE * NUM_CONTROLLER_AND_CARD_PORTS);
|
||||
}
|
||||
|
||||
s_memory_card_backup->SeekAbsolute(0);
|
||||
|
||||
StateWrapper sw(s_memory_card_backup.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
|
||||
if (s_memory_card_backup.empty())
|
||||
s_memory_card_backup.resize(MemoryCard::STATE_SIZE * NUM_CONTROLLER_AND_CARD_PORTS);
|
||||
|
||||
StateWrapper sw(s_memory_card_backup.span(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
|
||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
if (s_memory_cards[i])
|
||||
@ -382,13 +376,11 @@ void Pad::BackupMemoryCardState()
|
||||
|
||||
void Pad::RestoreMemoryCardState()
|
||||
{
|
||||
DebugAssert(s_memory_card_backup);
|
||||
DebugAssert(!s_memory_card_backup.empty());
|
||||
|
||||
VERBOSE_LOG("Restoring backed up memory card state.");
|
||||
|
||||
s_memory_card_backup->SeekAbsolute(0);
|
||||
StateWrapper sw(s_memory_card_backup.get(), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
|
||||
|
||||
StateWrapper sw(s_memory_card_backup.cspan(), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
|
||||
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
|
||||
{
|
||||
if (s_memory_cards[i])
|
||||
|
@ -493,7 +493,7 @@ bool PINEServer::PINESocket::HandleCommand(IPCCommand command, BinarySpanReader
|
||||
|
||||
Host::RunOnCPUThread([state_filename = std::move(state_filename)] {
|
||||
Error error;
|
||||
if (!System::LoadState(state_filename.c_str(), &error))
|
||||
if (!System::LoadState(state_filename.c_str(), &error, true))
|
||||
ERROR_LOG("PINE: Load state failed: {}", error.GetDescription());
|
||||
});
|
||||
|
||||
|
@ -5,11 +5,18 @@
|
||||
#include "types.h"
|
||||
|
||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 68;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 69;
|
||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||
|
||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||
|
||||
enum class SaveStateCompression : u32
|
||||
{
|
||||
None = 0,
|
||||
ZLib = 1,
|
||||
ZStd = 2,
|
||||
};
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct SAVE_STATE_HEADER
|
||||
{
|
||||
@ -17,10 +24,6 @@ struct SAVE_STATE_HEADER
|
||||
{
|
||||
MAX_TITLE_LENGTH = 128,
|
||||
MAX_SERIAL_LENGTH = 32,
|
||||
|
||||
COMPRESSION_TYPE_NONE = 0,
|
||||
COMPRESSION_TYPE_ZLIB = 1,
|
||||
COMPRESSION_TYPE_ZSTD = 2,
|
||||
};
|
||||
|
||||
u32 magic;
|
||||
@ -28,14 +31,16 @@ struct SAVE_STATE_HEADER
|
||||
char title[MAX_TITLE_LENGTH];
|
||||
char serial[MAX_SERIAL_LENGTH];
|
||||
|
||||
u32 media_filename_length;
|
||||
u32 offset_to_media_filename;
|
||||
u32 media_path_length;
|
||||
u32 offset_to_media_path;
|
||||
u32 media_subimage_index;
|
||||
u32 unused_offset_to_playlist_filename; // Unused as of version 51.
|
||||
|
||||
|
||||
// Screenshot compression added in version 69.
|
||||
// Uncompressed size not stored, it can be inferred from width/height.
|
||||
u32 screenshot_compression_type;
|
||||
u32 screenshot_width;
|
||||
u32 screenshot_height;
|
||||
u32 screenshot_size;
|
||||
u32 screenshot_compressed_size;
|
||||
u32 offset_to_screenshot;
|
||||
|
||||
u32 data_compression_type;
|
||||
|
1024
src/core/system.cpp
1024
src/core/system.cpp
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,8 @@
|
||||
#include "timing_event.h"
|
||||
#include "types.h"
|
||||
|
||||
#include "util/image.h"
|
||||
|
||||
#include "common/timer.h"
|
||||
|
||||
#include <memory>
|
||||
@ -76,9 +78,7 @@ struct ExtendedSaveStateInfo
|
||||
std::string media_path;
|
||||
std::time_t timestamp;
|
||||
|
||||
u32 screenshot_width;
|
||||
u32 screenshot_height;
|
||||
std::vector<u32> screenshot_data;
|
||||
RGBA8Image screenshot;
|
||||
};
|
||||
|
||||
namespace System {
|
||||
@ -263,23 +263,11 @@ bool BootSystem(SystemBootParameters parameters, Error* error);
|
||||
void PauseSystem(bool paused);
|
||||
void ResetSystem();
|
||||
|
||||
/// Loads state from the specified filename.
|
||||
bool LoadState(const char* filename, Error* error);
|
||||
bool SaveState(const char* filename, Error* error, bool backup_existing_save);
|
||||
/// Loads state from the specified path.
|
||||
bool LoadState(const char* path, Error* error, bool save_undo_state);
|
||||
bool SaveState(const char* path, Error* error, bool backup_existing_save);
|
||||
bool SaveResumeState(Error* error);
|
||||
|
||||
/// Memory save states - only for internal use.
|
||||
struct MemorySaveState
|
||||
{
|
||||
std::unique_ptr<GPUTexture> vram_texture;
|
||||
std::unique_ptr<GrowableMemoryByteStream> state_stream;
|
||||
};
|
||||
bool SaveMemoryState(MemorySaveState* mss);
|
||||
bool LoadMemoryState(const MemorySaveState& mss);
|
||||
bool LoadStateFromStream(ByteStream* stream, Error* error, bool update_display, bool ignore_media = false);
|
||||
bool SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_size = 256, u32 compression_method = 0,
|
||||
bool ignore_media = false);
|
||||
|
||||
/// Runs the VM until the CPU execution is canceled.
|
||||
void Execute();
|
||||
|
||||
|
@ -840,7 +840,7 @@ void EmuThread::bootOrLoadState(std::string path)
|
||||
if (System::IsValid())
|
||||
{
|
||||
Error error;
|
||||
if (!System::LoadState(path.c_str(), &error))
|
||||
if (!System::LoadState(path.c_str(), &error, true))
|
||||
{
|
||||
emit errorReported(tr("Error"),
|
||||
tr("Failed to load state: %1").arg(QString::fromStdString(error.GetDescription())));
|
||||
|
@ -8,24 +8,29 @@
|
||||
#include <cstring>
|
||||
Log_SetChannel(StateWrapper);
|
||||
|
||||
StateWrapper::StateWrapper(ByteStream* stream, Mode mode, u32 version)
|
||||
: m_stream(stream), m_mode(mode), m_version(version)
|
||||
StateWrapper::StateWrapper(std::span<u8> data, Mode mode, u32 version)
|
||||
: m_data(data.data()), m_size(data.size()), m_mode(mode), m_version(version)
|
||||
{
|
||||
}
|
||||
|
||||
StateWrapper::StateWrapper(std::span<const u8> data, Mode mode, u32 version)
|
||||
: m_data(const_cast<u8*>(data.data())), m_size(data.size()), m_mode(mode), m_version(version)
|
||||
{
|
||||
Assert(mode == Mode::Read);
|
||||
}
|
||||
|
||||
StateWrapper::~StateWrapper() = default;
|
||||
|
||||
void StateWrapper::DoBytes(void* data, size_t length)
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(data, static_cast<u32>(length))) == true)
|
||||
if (!ReadData(data, length))
|
||||
std::memset(data, 0, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(data, static_cast<u32>(length));
|
||||
WriteData(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,15 +50,14 @@ void StateWrapper::Do(bool* value_ptr)
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
u8 value = 0;
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->ReadByte(&value);
|
||||
if (!(m_error = m_error || (m_pos + 1) > m_size)) [[likely]]
|
||||
value = m_data[m_pos++];
|
||||
*value_ptr = (value != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 value = static_cast<u8>(*value_ptr);
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->WriteByte(value);
|
||||
if (!(m_error = m_error || (m_pos + 1) > m_size)) [[likely]]
|
||||
m_data[m_pos++] = static_cast<u8>(*value_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +66,11 @@ void StateWrapper::Do(std::string* value_ptr)
|
||||
u32 length = static_cast<u32>(value_ptr->length());
|
||||
Do(&length);
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if ((m_error = (m_error || ((m_pos + length) > m_size)))) [[unlikely]]
|
||||
return;
|
||||
value_ptr->resize(length);
|
||||
}
|
||||
DoBytes(&(*value_ptr)[0], length);
|
||||
value_ptr->resize(std::strlen(&(*value_ptr)[0]));
|
||||
}
|
||||
@ -72,7 +80,11 @@ void StateWrapper::Do(SmallStringBase* value_ptr)
|
||||
u32 length = static_cast<u32>(value_ptr->length());
|
||||
Do(&length);
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if ((m_error = (m_error || ((m_pos + length) > m_size)))) [[unlikely]]
|
||||
return;
|
||||
value_ptr->resize(length);
|
||||
}
|
||||
DoBytes(value_ptr->data(), length);
|
||||
value_ptr->update_size();
|
||||
}
|
||||
@ -95,6 +107,26 @@ bool StateWrapper::DoMarker(const char* marker)
|
||||
if (m_mode == Mode::Write || file_value.equals(marker))
|
||||
return true;
|
||||
|
||||
ERROR_LOG("Marker mismatch at offset {}: found '{}' expected '{}'", m_stream->GetPosition(), file_value, marker);
|
||||
ERROR_LOG("Marker mismatch at offset {}: found '{}' expected '{}'", m_pos, file_value, marker);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StateWrapper::ReadData(void* buf, size_t size)
|
||||
{
|
||||
if ((m_error = (m_error || (m_pos + size) > m_size))) [[unlikely]]
|
||||
return false;
|
||||
|
||||
std::memcpy(buf, &m_data[m_pos], size);
|
||||
m_pos += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StateWrapper::WriteData(const void* buf, size_t size)
|
||||
{
|
||||
if ((m_error = (m_error || (m_pos + size) > m_size))) [[unlikely]]
|
||||
return false;
|
||||
|
||||
std::memcpy(&m_data[m_pos], buf, size);
|
||||
m_pos += size;
|
||||
return true;
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "common/byte_stream.h"
|
||||
|
||||
#include "common/fifo_queue.h"
|
||||
#include "common/heap_array.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
@ -23,17 +25,16 @@ public:
|
||||
Write
|
||||
};
|
||||
|
||||
StateWrapper(ByteStream* stream, Mode mode, u32 version);
|
||||
StateWrapper(std::span<u8> data, Mode mode, u32 version);
|
||||
StateWrapper(std::span<const u8> data, Mode mode, u32 version);
|
||||
StateWrapper(const StateWrapper&) = delete;
|
||||
~StateWrapper();
|
||||
|
||||
ByteStream* GetStream() const { return m_stream; }
|
||||
bool HasError() const { return m_error; }
|
||||
bool IsReading() const { return (m_mode == Mode::Read); }
|
||||
bool IsWriting() const { return (m_mode == Mode::Write); }
|
||||
Mode GetMode() const { return m_mode; }
|
||||
void SetMode(Mode mode) { m_mode = mode; }
|
||||
u32 GetVersion() const { return m_version; }
|
||||
ALWAYS_INLINE bool HasError() const { return m_error; }
|
||||
ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); }
|
||||
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
|
||||
ALWAYS_INLINE u32 GetVersion() const { return m_version; }
|
||||
ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
|
||||
|
||||
/// Overload for integral or floating-point types. Writes bytes as-is.
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0>
|
||||
@ -41,13 +42,12 @@ public:
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true)
|
||||
if (!ReadData(value_ptr, sizeof(T))) [[unlikely]]
|
||||
*value_ptr = static_cast<T>(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(value_ptr, sizeof(T));
|
||||
WriteData(value_ptr, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,17 +59,15 @@ public:
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
TType temp;
|
||||
if (m_error || (m_error |= !m_stream->Read2(&temp, sizeof(TType))) == true)
|
||||
if (!ReadData(&temp, sizeof(temp))) [[unlikely]]
|
||||
temp = static_cast<TType>(0);
|
||||
|
||||
*value_ptr = static_cast<T>(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
TType temp;
|
||||
std::memcpy(&temp, value_ptr, sizeof(TType));
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(&temp, sizeof(TType));
|
||||
const TType temp = static_cast<TType>(*value_ptr);
|
||||
WriteData(&temp, sizeof(temp));
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,13 +77,12 @@ public:
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true)
|
||||
if (!ReadData(value_ptr, sizeof(T))) [[unlikely]]
|
||||
std::memset(value_ptr, 0, sizeof(*value_ptr));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(value_ptr, sizeof(T));
|
||||
WriteData(value_ptr, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,12 +198,18 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_error)
|
||||
m_error = !m_stream->SeekRelative(static_cast<s64>(count));
|
||||
m_error = (m_error || (m_pos + count) > m_size);
|
||||
if (!m_error) [[likely]]
|
||||
m_pos += count;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteStream* m_stream;
|
||||
bool ReadData(void* buf, size_t size);
|
||||
bool WriteData(const void* buf, size_t size);
|
||||
|
||||
u8* m_data;
|
||||
size_t m_size;
|
||||
size_t m_pos = 0;
|
||||
Mode m_mode;
|
||||
u32 m_version;
|
||||
bool m_error = false;
|
||||
|
Loading…
Reference in New Issue
Block a user