System: Support saving screenshots in save states
This commit is contained in:
parent
aaf9dcaf02
commit
08c8d1a521
|
@ -18,7 +18,7 @@ void HostDisplay::WindowResized(s32 new_window_width, s32 new_window_height)
|
||||||
m_window_height = new_window_height;
|
m_window_height = new_window_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect() const
|
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, s32 top_margin) const
|
||||||
{
|
{
|
||||||
const float y_scale =
|
const float y_scale =
|
||||||
(static_cast<float>(m_display_width) / static_cast<float>(m_display_height)) / m_display_pixel_aspect_ratio;
|
(static_cast<float>(m_display_width) / static_cast<float>(m_display_height)) / m_display_pixel_aspect_ratio;
|
||||||
|
@ -30,8 +30,6 @@ std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect() const
|
||||||
const float active_height = static_cast<float>(m_display_active_height) * y_scale;
|
const float active_height = static_cast<float>(m_display_active_height) * y_scale;
|
||||||
|
|
||||||
// now fit it within the window
|
// now fit it within the window
|
||||||
const s32 window_width = m_window_width;
|
|
||||||
const s32 window_height = m_window_height - m_display_top_margin;
|
|
||||||
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
|
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
|
||||||
|
|
||||||
float scale;
|
float scale;
|
||||||
|
@ -41,12 +39,12 @@ std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect() const
|
||||||
{
|
{
|
||||||
// align in middle vertically
|
// align in middle vertically
|
||||||
scale = static_cast<float>(window_width) / display_width;
|
scale = static_cast<float>(window_width) / display_width;
|
||||||
top_padding = (window_height - static_cast<s32>(display_height * scale)) / 2;
|
top_padding = (window_height - top_margin - static_cast<s32>(display_height * scale)) / 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// align in middle horizontally
|
// align in middle horizontally
|
||||||
scale = static_cast<float>(window_height) / display_height;
|
scale = static_cast<float>(window_height - top_margin) / display_height;
|
||||||
left_padding = (window_width - static_cast<s32>(display_width * scale)) / 2;
|
left_padding = (window_width - static_cast<s32>(display_width * scale)) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +58,7 @@ std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect() const
|
||||||
top += std::max(top_padding, 0);
|
top += std::max(top_padding, 0);
|
||||||
|
|
||||||
// add in margin
|
// add in margin
|
||||||
top += m_display_top_margin;
|
top += top_margin;
|
||||||
return std::tie(left, top, width, height);
|
return std::tie(left, top, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,13 +173,13 @@ bool HostDisplay::WriteDisplayTextureToFile(const char* filename, bool full_reso
|
||||||
}
|
}
|
||||||
else if (apply_aspect_ratio)
|
else if (apply_aspect_ratio)
|
||||||
{
|
{
|
||||||
const auto [left, top, right, bottom] = CalculateDrawRect();
|
const auto [left, top, right, bottom] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
resize_width = right - left;
|
resize_width = right - left;
|
||||||
resize_height = bottom - top;
|
resize_height = bottom - top;
|
||||||
}
|
}
|
||||||
else if (!full_resolution)
|
else if (!full_resolution)
|
||||||
{
|
{
|
||||||
const auto [left, top, right, bottom] = CalculateDrawRect();
|
const auto [left, top, right, bottom] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
const float ratio =
|
const float ratio =
|
||||||
static_cast<float>(m_display_texture_view_width) / static_cast<float>(std::abs(m_display_texture_view_height));
|
static_cast<float>(m_display_texture_view_width) / static_cast<float>(std::abs(m_display_texture_view_height));
|
||||||
if (ratio > 1.0f)
|
if (ratio > 1.0f)
|
||||||
|
@ -214,3 +212,75 @@ bool HostDisplay::WriteDisplayTextureToFile(const char* filename, bool full_reso
|
||||||
read_height, filename, true, flip_y, static_cast<u32>(resize_width),
|
read_height, filename, true, flip_y, static_cast<u32>(resize_width),
|
||||||
static_cast<u32>(resize_height));
|
static_cast<u32>(resize_height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HostDisplay::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width /* = 0 */,
|
||||||
|
u32 resize_height /* = 0 */, bool clear_alpha /* = true */)
|
||||||
|
{
|
||||||
|
if (!m_display_texture_handle)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const bool flip_y = (m_display_texture_view_height < 0);
|
||||||
|
s32 read_width = m_display_texture_view_width;
|
||||||
|
s32 read_height = m_display_texture_view_height;
|
||||||
|
s32 read_x = m_display_texture_view_x;
|
||||||
|
s32 read_y = m_display_texture_view_y;
|
||||||
|
if (flip_y)
|
||||||
|
{
|
||||||
|
read_height = -m_display_texture_view_height;
|
||||||
|
read_y = (m_display_texture_height - read_height) - (m_display_texture_height - m_display_texture_view_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 width = static_cast<u32>(read_width);
|
||||||
|
u32 height = static_cast<u32>(read_height);
|
||||||
|
std::vector<u32> texture_data(width * height);
|
||||||
|
u32 texture_data_stride = sizeof(u32) * width;
|
||||||
|
if (!DownloadTexture(m_display_texture_handle, read_x, read_y, width, height, texture_data.data(),
|
||||||
|
texture_data_stride))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to download texture from GPU.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear_alpha)
|
||||||
|
{
|
||||||
|
for (u32& pixel : texture_data)
|
||||||
|
pixel |= 0xFF000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flip_y)
|
||||||
|
{
|
||||||
|
std::vector<u32> temp(width);
|
||||||
|
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
|
||||||
|
{
|
||||||
|
u32* top_ptr = &texture_data[flip_row * width];
|
||||||
|
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
|
||||||
|
std::memcpy(temp.data(), top_ptr, texture_data_stride);
|
||||||
|
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
|
||||||
|
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
|
||||||
|
{
|
||||||
|
std::vector<u32> resized_texture_data(resize_width * resize_height);
|
||||||
|
u32 resized_texture_stride = sizeof(u32) * resize_width;
|
||||||
|
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
|
||||||
|
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
|
||||||
|
resized_texture_stride, 4))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
width = resize_width;
|
||||||
|
height = resize_height;
|
||||||
|
*buffer = std::move(resized_texture_data);
|
||||||
|
texture_data_stride = resized_texture_stride;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*buffer = texture_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
// An abstracted RGBA8 texture.
|
// An abstracted RGBA8 texture.
|
||||||
class HostDisplayTexture
|
class HostDisplayTexture
|
||||||
|
@ -97,7 +98,7 @@ public:
|
||||||
void SetDisplayTopMargin(s32 height) { m_display_top_margin = height; }
|
void SetDisplayTopMargin(s32 height) { m_display_top_margin = height; }
|
||||||
|
|
||||||
/// Helper function for computing the draw rectangle in a larger window.
|
/// Helper function for computing the draw rectangle in a larger window.
|
||||||
std::tuple<s32, s32, s32, s32> CalculateDrawRect() const;
|
std::tuple<s32, s32, s32, s32> CalculateDrawRect(s32 window_width, s32 window_height, s32 top_margin) const;
|
||||||
|
|
||||||
/// Helper function to save texture data to a PNG. If flip_y is set, the image will be flipped aka OpenGL.
|
/// Helper function to save texture data to a PNG. If flip_y is set, the image will be flipped aka OpenGL.
|
||||||
bool WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, const char* filename,
|
bool WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, const char* filename,
|
||||||
|
@ -106,6 +107,10 @@ public:
|
||||||
/// Helper function to save current display texture to PNG.
|
/// Helper function to save current display texture to PNG.
|
||||||
bool WriteDisplayTextureToFile(const char* filename, bool full_resolution = true, bool apply_aspect_ratio = true);
|
bool WriteDisplayTextureToFile(const char* filename, bool full_resolution = true, bool apply_aspect_ratio = true);
|
||||||
|
|
||||||
|
/// Helper function to save current display texture to a buffer.
|
||||||
|
bool WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width = 0, u32 resize_height = 0,
|
||||||
|
bool clear_alpha = true);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
s32 m_window_width = 0;
|
s32 m_window_width = 0;
|
||||||
s32 m_window_height = 0;
|
s32 m_window_height = 0;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "gpu.h"
|
#include "gpu.h"
|
||||||
#include "host_display.h"
|
#include "host_display.h"
|
||||||
#include "mdec.h"
|
#include "mdec.h"
|
||||||
|
#include "save_state_version.h"
|
||||||
#include "spu.h"
|
#include "spu.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "timers.h"
|
#include "timers.h"
|
||||||
|
@ -809,6 +810,61 @@ std::optional<HostInterface::SaveStateInfo> HostInterface::GetSaveStateInfo(cons
|
||||||
return SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), slot, global};
|
return SaveStateInfo{std::move(path), sd.ModificationTime.AsUnixTimestamp(), slot, global};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<HostInterface::ExtendedSaveStateInfo> HostInterface::GetExtendedSaveStateInfo(const char* game_code,
|
||||||
|
s32 slot)
|
||||||
|
{
|
||||||
|
const bool global = (!game_code || game_code[0] == 0);
|
||||||
|
std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(game_code, slot);
|
||||||
|
|
||||||
|
FILESYSTEM_STAT_DATA sd;
|
||||||
|
if (!FileSystem::StatFile(path.c_str(), &sd))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
std::unique_ptr<ByteStream> stream =
|
||||||
|
FileSystem::OpenFile(path.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_SEEKABLE);
|
||||||
|
if (!stream)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
SAVE_STATE_HEADER header;
|
||||||
|
if (!stream->Read(&header, sizeof(header)) || header.magic != SAVE_STATE_MAGIC)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
ExtendedSaveStateInfo ssi;
|
||||||
|
ssi.path = std::move(path);
|
||||||
|
ssi.timestamp = sd.ModificationTime.AsUnixTimestamp();
|
||||||
|
ssi.slot = slot;
|
||||||
|
ssi.global = global;
|
||||||
|
|
||||||
|
if (header.version != SAVE_STATE_VERSION)
|
||||||
|
{
|
||||||
|
ssi.title = StringUtil::StdStringFromFormat("Invalid version %u (expected %u)", header.version, header.magic,
|
||||||
|
SAVE_STATE_VERSION);
|
||||||
|
return ssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.title[sizeof(header.title) - 1] = 0;
|
||||||
|
ssi.title = header.title;
|
||||||
|
header.game_code[sizeof(header.game_code) - 1] = 0;
|
||||||
|
ssi.game_code = header.game_code;
|
||||||
|
|
||||||
|
if (header.screenshot_width > 0 && header.screenshot_height > 0 && header.screenshot_size > 0 &&
|
||||||
|
(static_cast<u64>(header.offset_to_screenshot) + static_cast<u64>(header.screenshot_size)) <= stream->GetSize())
|
||||||
|
{
|
||||||
|
ssi.screenshot_data.resize((header.screenshot_size + 3u) / 4u);
|
||||||
|
if (stream->Read2(ssi.screenshot_data.data(), header.screenshot_size))
|
||||||
|
{
|
||||||
|
ssi.screenshot_width = header.screenshot_width;
|
||||||
|
ssi.screenshot_height = header.screenshot_height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decltype(ssi.screenshot_data)().swap(ssi.screenshot_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssi;
|
||||||
|
}
|
||||||
|
|
||||||
void HostInterface::DeleteSaveStates(const char* game_code, bool resume)
|
void HostInterface::DeleteSaveStates(const char* game_code, bool resume)
|
||||||
{
|
{
|
||||||
const std::vector<SaveStateInfo> states(GetAvailableSaveStates(game_code));
|
const std::vector<SaveStateInfo> states(GetAvailableSaveStates(game_code));
|
||||||
|
|
|
@ -26,6 +26,35 @@ class HostInterface
|
||||||
friend System;
|
friend System;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum : s32
|
||||||
|
{
|
||||||
|
PER_GAME_SAVE_STATE_SLOTS = 10,
|
||||||
|
GLOBAL_SAVE_STATE_SLOTS = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SaveStateInfo
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
u64 timestamp;
|
||||||
|
s32 slot;
|
||||||
|
bool global;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtendedSaveStateInfo
|
||||||
|
{
|
||||||
|
std::string path;
|
||||||
|
u64 timestamp;
|
||||||
|
s32 slot;
|
||||||
|
bool global;
|
||||||
|
|
||||||
|
std::string title;
|
||||||
|
std::string game_code;
|
||||||
|
|
||||||
|
u32 screenshot_width;
|
||||||
|
u32 screenshot_height;
|
||||||
|
std::vector<u32> screenshot_data;
|
||||||
|
};
|
||||||
|
|
||||||
HostInterface();
|
HostInterface();
|
||||||
virtual ~HostInterface();
|
virtual ~HostInterface();
|
||||||
|
|
||||||
|
@ -113,6 +142,15 @@ public:
|
||||||
/// such as compiling shaders when starting up.
|
/// such as compiling shaders when starting up.
|
||||||
void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1);
|
void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1);
|
||||||
|
|
||||||
|
/// Returns a list of save states for the specified game code.
|
||||||
|
std::vector<SaveStateInfo> GetAvailableSaveStates(const char* game_code) const;
|
||||||
|
|
||||||
|
/// Returns save state info if present. If game_code is null or empty, assumes global state.
|
||||||
|
std::optional<SaveStateInfo> GetSaveStateInfo(const char* game_code, s32 slot);
|
||||||
|
|
||||||
|
/// Returns save state info if present. If game_code is null or empty, assumes global state.
|
||||||
|
std::optional<ExtendedSaveStateInfo> GetExtendedSaveStateInfo(const char* game_code, s32 slot);
|
||||||
|
|
||||||
/// Deletes save states for the specified game code. If resume is set, the resume state is deleted too.
|
/// Deletes save states for the specified game code. If resume is set, the resume state is deleted too.
|
||||||
void DeleteSaveStates(const char* game_code, bool resume);
|
void DeleteSaveStates(const char* game_code, bool resume);
|
||||||
|
|
||||||
|
@ -123,9 +161,7 @@ protected:
|
||||||
AUDIO_SAMPLE_RATE = 44100,
|
AUDIO_SAMPLE_RATE = 44100,
|
||||||
AUDIO_CHANNELS = 2,
|
AUDIO_CHANNELS = 2,
|
||||||
AUDIO_BUFFER_SIZE = 2048,
|
AUDIO_BUFFER_SIZE = 2048,
|
||||||
AUDIO_BUFFERS = 2,
|
AUDIO_BUFFERS = 2
|
||||||
PER_GAME_SAVE_STATE_SLOTS = 10,
|
|
||||||
GLOBAL_SAVE_STATE_SLOTS = 10,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OSDMessage
|
struct OSDMessage
|
||||||
|
@ -135,14 +171,6 @@ protected:
|
||||||
float duration;
|
float duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SaveStateInfo
|
|
||||||
{
|
|
||||||
std::string path;
|
|
||||||
u64 timestamp;
|
|
||||||
s32 slot;
|
|
||||||
bool global;
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual bool AcquireHostDisplay() = 0;
|
virtual bool AcquireHostDisplay() = 0;
|
||||||
virtual void ReleaseHostDisplay() = 0;
|
virtual void ReleaseHostDisplay() = 0;
|
||||||
virtual std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) = 0;
|
virtual std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) = 0;
|
||||||
|
@ -186,12 +214,6 @@ protected:
|
||||||
/// Returns the default path to a memory card for a specific game.
|
/// Returns the default path to a memory card for a specific game.
|
||||||
std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const;
|
std::string GetGameMemoryCardPath(const char* game_code, u32 slot) const;
|
||||||
|
|
||||||
/// Returns a list of save states for the specified game code.
|
|
||||||
std::vector<SaveStateInfo> GetAvailableSaveStates(const char* game_code) const;
|
|
||||||
|
|
||||||
/// Returns save state info if present. If game_code is null or empty, assumes global state.
|
|
||||||
std::optional<SaveStateInfo> GetSaveStateInfo(const char* game_code, s32 slot);
|
|
||||||
|
|
||||||
/// Returns the most recent resume save state.
|
/// Returns the most recent resume save state.
|
||||||
std::string GetMostRecentResumeSaveStatePath() const;
|
std::string GetMostRecentResumeSaveStatePath() const;
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,30 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||||
static constexpr u32 SAVE_STATE_VERSION = 23;
|
static constexpr u32 SAVE_STATE_VERSION = 24;
|
||||||
|
|
||||||
|
#pragma pack(push, 4)
|
||||||
|
struct SAVE_STATE_HEADER
|
||||||
|
{
|
||||||
|
enum : u32
|
||||||
|
{
|
||||||
|
MAX_TITLE_LENGTH = 128,
|
||||||
|
MAX_GAME_CODE_LENGTH = 32
|
||||||
|
};
|
||||||
|
|
||||||
|
u32 magic;
|
||||||
|
u32 version;
|
||||||
|
char title[MAX_TITLE_LENGTH];
|
||||||
|
char game_code[MAX_GAME_CODE_LENGTH];
|
||||||
|
|
||||||
|
u32 screenshot_width;
|
||||||
|
u32 screenshot_height;
|
||||||
|
u32 screenshot_size;
|
||||||
|
u32 offset_to_screenshot;
|
||||||
|
|
||||||
|
u32 data_compression_type;
|
||||||
|
u32 data_compressed_size;
|
||||||
|
u32 data_uncompressed_size;
|
||||||
|
u32 offset_to_data;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "common/audio_stream.h"
|
#include "common/audio_stream.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/state_wrapper.h"
|
#include "common/state_wrapper.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "cpu_code_cache.h"
|
#include "cpu_code_cache.h"
|
||||||
#include "cpu_core.h"
|
#include "cpu_core.h"
|
||||||
|
@ -309,20 +310,6 @@ bool System::CreateGPU(GPURenderer renderer)
|
||||||
|
|
||||||
bool System::DoState(StateWrapper& sw)
|
bool System::DoState(StateWrapper& sw)
|
||||||
{
|
{
|
||||||
u32 magic = SAVE_STATE_MAGIC;
|
|
||||||
u32 version = SAVE_STATE_VERSION;
|
|
||||||
sw.Do(&magic);
|
|
||||||
if (magic != SAVE_STATE_MAGIC)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
sw.Do(&version);
|
|
||||||
if (version != SAVE_STATE_VERSION)
|
|
||||||
{
|
|
||||||
m_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.",
|
|
||||||
SAVE_STATE_VERSION, version);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sw.DoMarker("System"))
|
if (!sw.DoMarker("System"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -424,14 +411,87 @@ void System::Reset()
|
||||||
|
|
||||||
bool System::LoadState(ByteStream* state)
|
bool System::LoadState(ByteStream* state)
|
||||||
{
|
{
|
||||||
|
SAVE_STATE_HEADER header;
|
||||||
|
if (!state->Read2(&header, sizeof(header)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (header.magic != SAVE_STATE_MAGIC)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (header.version != SAVE_STATE_VERSION)
|
||||||
|
{
|
||||||
|
m_host_interface->ReportFormattedError("Save state is incompatible: expecting version %u but state is version %u.",
|
||||||
|
SAVE_STATE_VERSION, header.version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.data_compression_type != 0)
|
||||||
|
{
|
||||||
|
m_host_interface->ReportFormattedError("Unknown save state compression type %u", header.data_compression_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state->SeekAbsolute(header.offset_to_data))
|
||||||
|
return false;
|
||||||
|
|
||||||
StateWrapper sw(state, StateWrapper::Mode::Read);
|
StateWrapper sw(state, StateWrapper::Mode::Read);
|
||||||
return DoState(sw);
|
return DoState(sw);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::SaveState(ByteStream* state)
|
bool System::SaveState(ByteStream* state)
|
||||||
{
|
{
|
||||||
StateWrapper sw(state, StateWrapper::Mode::Write);
|
SAVE_STATE_HEADER header = {};
|
||||||
return DoState(sw);
|
|
||||||
|
const u64 header_position = state->GetPosition();
|
||||||
|
if (!state->Write2(&header, sizeof(header)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// fill in header
|
||||||
|
header.magic = SAVE_STATE_MAGIC;
|
||||||
|
header.version = SAVE_STATE_VERSION;
|
||||||
|
StringUtil::Strlcpy(header.title, m_running_game_title.c_str(), sizeof(header.title));
|
||||||
|
StringUtil::Strlcpy(header.game_code, m_running_game_code.c_str(), sizeof(header.game_code));
|
||||||
|
|
||||||
|
// save screenshot
|
||||||
|
{
|
||||||
|
const u32 screenshot_width = 128;
|
||||||
|
const u32 screenshot_height = 128;
|
||||||
|
|
||||||
|
std::vector<u32> screenshot_buffer;
|
||||||
|
if (m_host_interface->GetDisplay()->WriteDisplayTextureToBuffer(&screenshot_buffer, screenshot_width,
|
||||||
|
screenshot_height) &&
|
||||||
|
!screenshot_buffer.empty())
|
||||||
|
{
|
||||||
|
header.offset_to_screenshot = static_cast<u32>(state->GetPosition());
|
||||||
|
header.screenshot_width = screenshot_width;
|
||||||
|
header.screenshot_height = screenshot_height;
|
||||||
|
header.screenshot_size = static_cast<u32>(screenshot_buffer.size() * sizeof(u32));
|
||||||
|
if (!state->Write2(screenshot_buffer.data(), header.screenshot_size))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data
|
||||||
|
{
|
||||||
|
header.offset_to_data = static_cast<u32>(state->GetPosition());
|
||||||
|
|
||||||
|
StateWrapper sw(state, StateWrapper::Mode::Write);
|
||||||
|
if (!DoState(sw))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
header.data_compression_type = 0;
|
||||||
|
header.data_uncompressed_size = static_cast<u32>(state->GetPosition() - header.offset_to_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-write header
|
||||||
|
const u64 end_position = state->GetPosition();
|
||||||
|
if (!state->SeekAbsolute(header_position) || !state->Write2(&header, sizeof(header)) ||
|
||||||
|
!state->SeekAbsolute(end_position))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::RunFrame()
|
void System::RunFrame()
|
||||||
|
|
|
@ -477,7 +477,7 @@ void D3D11DisplayWidget::renderDisplay()
|
||||||
if (!m_display_texture_handle)
|
if (!m_display_texture_handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect();
|
auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
|
|
||||||
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0);
|
m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0);
|
||||||
|
|
|
@ -494,7 +494,7 @@ void OpenGLDisplayWidget::renderDisplay()
|
||||||
if (!m_display_texture_handle)
|
if (!m_display_texture_handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect();
|
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
|
|
||||||
glViewport(vp_left, m_window_height - (m_display_top_margin + vp_top) - vp_height, vp_width, vp_height);
|
glViewport(vp_left, m_window_height - (m_display_top_margin + vp_top) - vp_height, vp_width, vp_height);
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
|
@ -419,7 +419,7 @@ void D3D11HostDisplay::RenderDisplay()
|
||||||
if (!m_display_texture_handle)
|
if (!m_display_texture_handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect();
|
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
|
|
||||||
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||||
m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0);
|
m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0);
|
||||||
|
|
|
@ -401,7 +401,7 @@ void OpenGLHostDisplay::RenderDisplay()
|
||||||
if (!m_display_texture_handle)
|
if (!m_display_texture_handle)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect();
|
const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin);
|
||||||
|
|
||||||
glViewport(vp_left, m_window_height - vp_top - vp_height, vp_width, vp_height);
|
glViewport(vp_left, m_window_height - vp_top - vp_height, vp_width, vp_height);
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
Loading…
Reference in New Issue