SaveState: Save screenshot in state archive

This commit is contained in:
Connor McLaughlin 2021-12-07 19:21:46 +10:00 committed by refractionpcsx2
parent c263c12448
commit f446e5ad3d
3 changed files with 121 additions and 29 deletions

View File

@ -30,26 +30,34 @@
#include "Patch.h"
#include "System/SysThreads.h"
#include "DebugTools/Breakpoints.h"
#include "Host.h"
#include "GS.h"
#include "common/pxStreams.h"
#include "common/SafeArray.inl"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "GS/GS.h"
#include "SPU2/spu2.h"
#include "USB/USB.h"
#include "PAD/Gamepad.h"
#include <wx/wfstream.h>
#include "gui/ConsoleLogger.h"
#ifndef PCSX2_CORE
#include "gui/App.h"
#include "gui/ConsoleLogger.h"
#else
#include "VMManager.h"
#endif
#include "common/pxStreams.h"
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <csetjmp>
#include <png.h>
using namespace R5900;
@ -73,6 +81,7 @@ static void PostLoadPrep()
// --------------------------------------------------------------------------------------
// SaveStateBase (implementations)
// --------------------------------------------------------------------------------------
#ifndef PCSX2_CORE
wxString SaveStateBase::GetSavestateFolder( int slot, bool isSavingOrLoading )
{
wxString CRCvalue = wxString::Format(wxT("%08X"), ElfCRC);
@ -124,6 +133,7 @@ wxString SaveStateBase::GetSavestateFolder( int slot, bool isSavingOrLoading )
return (dirname.GetPath() + "/" +
pxsFmt( L"%s (%s).%02d.p2s", WX_STR(serialName), WX_STR(CRCvalue), slot ));
}
#endif
SaveStateBase::SaveStateBase( SafeArray<u8>& memblock )
{
@ -307,7 +317,9 @@ SaveStateBase& SaveStateBase::FreezeInternals()
// to merit an HLE Bios sub-section... yet.
deci2Freeze();
#ifndef DISABLE_RECORDING
InputRecordingFreeze();
#endif
if( IsLoading() )
PostLoadPrep();
@ -374,7 +386,7 @@ wxString Exception::SaveStateLoadError::FormatDiagnosticMessage() const
{
FastFormatUnicode retval;
retval.Write("Savestate is corrupt or incomplete!\n");
OSDlog(Color_Red, false, "Error: Savestate is corrupt or incomplete!");
Host::AddOSDMessage("Error: Savestate is corrupt or incomplete!", 15.0f);
_formatDiagMsg(retval);
return retval;
}
@ -384,7 +396,7 @@ wxString Exception::SaveStateLoadError::FormatDisplayMessage() const
FastFormatUnicode retval;
retval.Write(_("The savestate cannot be loaded, as it appears to be corrupt or incomplete."));
retval.Write("\n");
OSDlog(Color_Red, false, "Error: The savestate cannot be loaded, as it appears to be corrupt or incomplete.");
Host::AddOSDMessage("Error: The savestate cannot be loaded, as it appears to be corrupt or incomplete.", 15.0f);
_formatUserMsg(retval);
return retval;
}
@ -393,7 +405,7 @@ wxString Exception::SaveStateLoadError::FormatDisplayMessage() const
//static VmStateBuffer state_buffer( L"Public Savestate Buffer" );
static const wxChar* EntryFilename_StateVersion = L"PCSX2 Savestate Version.id";
//static const wxChar* EntryFilename_Screenshot = L"Screenshot.jpg";
static const wxChar* EntryFilename_Screenshot = L"Screenshot.png";
static const wxChar* EntryFilename_InternalStructures = L"PCSX2 Internal Structures.dat";
struct SysState_Component
@ -402,22 +414,26 @@ struct SysState_Component
int (*freeze)(FreezeAction, freezeData*);
};
int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP)
static int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP)
{
#ifndef PCSX2_CORE
ScopedCoreThreadPause paused_core;
#endif
MTGS_FreezeData sstate = { fP, 0 };
GetMTGS().Freeze(mode, sstate);
#ifndef PCSX2_CORE
paused_core.AllowResume();
#endif
return sstate.retval;
}
static constexpr SysState_Component SPU2{ "SPU2", SPU2freeze };
static constexpr SysState_Component PAD{ "PAD", PADfreeze };
static constexpr SysState_Component PAD_{ "PAD", PADfreeze };
static constexpr SysState_Component USB{ "USB", USBfreeze };
static constexpr SysState_Component GS{ "GS", SysState_MTGSFreeze };
void SysState_ComponentFreezeOutRoot(void* dest, SysState_Component comp)
static void SysState_ComponentFreezeOutRoot(void* dest, SysState_Component comp)
{
freezeData fP = { 0, (u8*)dest };
if (comp.freeze(FreezeAction::Size, &fP) != 0)
@ -431,7 +447,7 @@ void SysState_ComponentFreezeOutRoot(void* dest, SysState_Component comp)
throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error saving state!\n"));
}
void SysState_ComponentFreezeIn(pxInputStream& infp, SysState_Component comp)
static void SysState_ComponentFreezeIn(pxInputStream& infp, SysState_Component comp)
{
freezeData fP = { 0, nullptr };
if (comp.freeze(FreezeAction::Size, &fP) != 0)
@ -457,7 +473,7 @@ void SysState_ComponentFreezeIn(pxInputStream& infp, SysState_Component comp)
throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error loading state!\n"));
}
void SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp)
static void SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp)
{
freezeData fP = { 0, NULL };
if (comp.freeze(FreezeAction::Size, &fP) == 0)
@ -656,8 +672,8 @@ public:
virtual ~SavestateEntry_PAD() = default;
wxString GetFilename() const { return L"PAD.bin"; }
void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, PAD); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, PAD); }
void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, PAD_); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, PAD_); }
bool IsRequired() const { return true; }
};
@ -690,7 +706,9 @@ static const std::unique_ptr<BaseSavestateEntry> SavestateEntries[] = {
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU0prog),
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU1prog),
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_SPU2),
#ifndef PCSX2_CORE
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_USB),
#endif
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_PAD),
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_GS),
};
@ -725,10 +743,12 @@ static void CheckVersion(pxInputStream& thr)
void SaveState_DownloadState(ArchiveEntryList* destlist)
{
#ifndef PCSX2_CORE
if (!GetCoreThread().HasActiveMachine())
throw Exception::RuntimeError()
.SetDiagMsg(L"SysExecEvent_DownloadState: Cannot freeze/download an invalid VM state!")
.SetUserMsg(_("There is no active virtual machine state to download or save."));
#endif
memSavingState saveme(destlist->GetBuffer());
ArchiveEntry internals(EntryFilename_InternalStructures);
@ -751,10 +771,75 @@ void SaveState_DownloadState(ArchiveEntryList* destlist)
}
}
std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot()
{
static constexpr u32 SCREENSHOT_WIDTH = 640;
static constexpr u32 SCREENSHOT_HEIGHT = 480;
std::vector<u32> pixels(SCREENSHOT_WIDTH * SCREENSHOT_HEIGHT);
if (!GetMTGS().SaveMemorySnapshot(SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT, &pixels))
{
// saving failed for some reason, device lost?
return nullptr;
}
std::unique_ptr<SaveStateScreenshotData> data = std::make_unique<SaveStateScreenshotData>();
data->width = SCREENSHOT_WIDTH;
data->height = SCREENSHOT_HEIGHT;
data->pixels = std::move(pixels);
return data;
}
static bool SaveState_CompressScreenshot(SaveStateScreenshotData* data, wxZipOutputStream* gzfp)
{
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr;
if (!png_ptr)
return false;
wxZipEntry* const vent = new wxZipEntry(EntryFilename_Screenshot);
vent->SetMethod(wxZIP_METHOD_STORE);
gzfp->PutNextEntry(vent);
ScopedGuard cleanup([&png_ptr, &info_ptr, gzfp]() {
if (png_ptr)
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
gzfp->CloseEntry();
});
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
return false;
if (setjmp(png_jmpbuf(png_ptr)))
return false;
png_set_write_fn(png_ptr, gzfp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
((wxZipOutputStream*)png_get_io_ptr(png_ptr))->Write(data_ptr, size);
}, [](png_structp png_ptr) {});
png_set_compression_level(png_ptr, 5);
png_set_IHDR(png_ptr, info_ptr, data->width, data->height, 8, PNG_COLOR_TYPE_RGBA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
for (u32 y = 0; y < data->height; ++y)
{
// ensure the alpha channel is set to opaque
u32* row = &data->pixels[y * data->width];
for (u32 x = 0; x < data->width; x++)
row[x] |= 0xFF000000u;
png_write_row(png_ptr, reinterpret_cast<png_bytep>(row));
}
png_write_end(png_ptr, nullptr);
return true;
}
// --------------------------------------------------------------------------------------
// CompressThread_VmState
// --------------------------------------------------------------------------------------
static void ZipStateToDiskOnThread(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<wxFFileOutputStream> outbase, wxString filename, wxString tempfile)
static void ZipStateToDiskOnThread(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, std::unique_ptr<wxFFileOutputStream> outbase, wxString filename, wxString tempfile, s32 slot_for_message)
{
#ifndef PCSX2_CORE
wxGetApp().StartPendingSave();
@ -771,18 +856,8 @@ static void ZipStateToDiskOnThread(std::unique_ptr<ArchiveEntryList> srclist, st
gzfp->CloseEntry();
}
#if 0
// This was never actually initialized...
std::unique_ptr<wxImage> m_screenshot;
if (m_screenshot)
{
wxZipEntry* vent = new wxZipEntry(EntryFilename_Screenshot);
vent->SetMethod(wxZIP_METHOD_STORE);
gzfp->PutNextEntry(vent);
m_screenshot->SaveFile(*gzfp, wxBITMAP_TYPE_JPEG);
gzfp->CloseEntry();
}
#endif
if (screenshot)
SaveState_CompressScreenshot(screenshot.get(), gzfp);
uint listlen = srclist->GetLength();
for (uint i = 0; i < listlen; ++i)
@ -820,12 +895,17 @@ static void ZipStateToDiskOnThread(std::unique_ptr<ArchiveEntryList> srclist, st
Console.WriteLn("(gzipThread) Data saved to disk without error.");
}
#ifdef PCSX2_CORE
if (slot_for_message >= 0 && VMManager::HasValidVM())
Host::AddKeyedFormattedOSDMessage(StringUtil::StdStringFromFormat("SaveStateSlot%d", slot_for_message), 10.0f, "State saved to slot %d.", slot_for_message);
#endif
#ifndef PCSX2_CORE
wxGetApp().ClearPendingSave();
#endif
}
void SaveState_ZipToDisk(ArchiveEntryList* srclist, const wxString& filename)
void SaveState_ZipToDisk(ArchiveEntryList* srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const wxString& filename, s32 slot_for_message)
{
// Provisionals for scoped cleanup, in case of exception:
std::unique_ptr<ArchiveEntryList> elist(srclist);
@ -835,7 +915,7 @@ void SaveState_ZipToDisk(ArchiveEntryList* srclist, const wxString& filename)
if (!out->IsOk())
throw Exception::CannotCreateStream(tempfile);
std::thread threaded_save(ZipStateToDiskOnThread, std::move(elist), std::move(out), filename, tempfile);
std::thread threaded_save(ZipStateToDiskOnThread, std::move(elist), std::move(screenshot), std::move(out), filename, tempfile, slot_for_message);
threaded_save.detach();
}
@ -938,7 +1018,9 @@ void SaveState_UnzipFromDisk(const wxString& filename)
.SetDiagMsg(L"Savestate cannot be loaded: some required components were not found or are incomplete.")
.SetUserMsg(_("This savestate cannot be loaded due to missing critical components. See the log file for details."));
#ifndef PCSX2_CORE
PatchesVerboseReset();
#endif
SysClearExecutionCache();
for (uint i = 0; i < std::size(SavestateEntries); ++i)

View File

@ -45,12 +45,20 @@ struct freezeData
u8 *data;
};
struct SaveStateScreenshotData
{
u32 width;
u32 height;
std::vector<u32> pixels;
};
class ArchiveEntryList;
// Wrappers to generate a save state compatible across all frontends.
// These functions assume that the caller has paused the core thread.
extern void SaveState_DownloadState(ArchiveEntryList* destlist);
extern void SaveState_ZipToDisk(ArchiveEntryList* srclist, const wxString& filename);
extern std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot();
extern void SaveState_ZipToDisk(ArchiveEntryList* srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const wxString& filename, s32 slot_for_message);
extern void SaveState_UnzipFromDisk(const wxString& filename);
// --------------------------------------------------------------------------------------
@ -74,7 +82,9 @@ public:
SaveStateBase( VmStateBuffer* memblock );
virtual ~SaveStateBase() { }
#ifndef PCSX2_CORE
static wxString GetSavestateFolder( int slot, bool isSavingOrLoading = false );
#endif
// Gets the version of savestate that this object is acting on.
// The version refers to the low 16 bits only (high 16 bits classifies Pcsx2 build types)

View File

@ -110,7 +110,7 @@ public:
protected:
void InvokeEvent()
{
SaveState_ZipToDisk(m_src_list, m_filename);
SaveState_ZipToDisk(m_src_list, nullptr, m_filename, -1);
}
void CleanupEvent()