mirror of https://github.com/PCSX2/pcsx2.git
SaveState: Save screenshot in state archive
This commit is contained in:
parent
c263c12448
commit
f446e5ad3d
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue