diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index 540e0fe8a1..b0d052fc86 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -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 -#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 #include +#include +#include + 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& 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 SavestateEntries[] = { std::unique_ptr(new SavestateEntry_VU0prog), std::unique_ptr(new SavestateEntry_VU1prog), std::unique_ptr(new SavestateEntry_SPU2), +#ifndef PCSX2_CORE std::unique_ptr(new SavestateEntry_USB), +#endif std::unique_ptr(new SavestateEntry_PAD), std::unique_ptr(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 SaveState_SaveScreenshot() +{ + static constexpr u32 SCREENSHOT_WIDTH = 640; + static constexpr u32 SCREENSHOT_HEIGHT = 480; + + std::vector 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 data = std::make_unique(); + 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(row)); + } + + png_write_end(png_ptr, nullptr); + return true; +} + // -------------------------------------------------------------------------------------- // CompressThread_VmState // -------------------------------------------------------------------------------------- -static void ZipStateToDiskOnThread(std::unique_ptr srclist, std::unique_ptr outbase, wxString filename, wxString tempfile) +static void ZipStateToDiskOnThread(std::unique_ptr srclist, std::unique_ptr screenshot, std::unique_ptr outbase, wxString filename, wxString tempfile, s32 slot_for_message) { #ifndef PCSX2_CORE wxGetApp().StartPendingSave(); @@ -771,18 +856,8 @@ static void ZipStateToDiskOnThread(std::unique_ptr srclist, st gzfp->CloseEntry(); } -#if 0 - // This was never actually initialized... - std::unique_ptr 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 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 screenshot, const wxString& filename, s32 slot_for_message) { // Provisionals for scoped cleanup, in case of exception: std::unique_ptr 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) diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index abd5b6058e..aad07b6187 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -45,12 +45,20 @@ struct freezeData u8 *data; }; +struct SaveStateScreenshotData +{ + u32 width; + u32 height; + std::vector 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 SaveState_SaveScreenshot(); +extern void SaveState_ZipToDisk(ArchiveEntryList* srclist, std::unique_ptr 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) diff --git a/pcsx2/gui/SysState.cpp b/pcsx2/gui/SysState.cpp index cdb5712f2d..360cfcbb22 100644 --- a/pcsx2/gui/SysState.cpp +++ b/pcsx2/gui/SysState.cpp @@ -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()