diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index c5e7a3a7a5..adf27cfd8e 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -27,9 +27,21 @@ #include "Elfheader.h" #include "Counters.h" +#include "Patch.h" +#include "ZipTools/ThreadedZipTools.h" +#include "common/pxStreams.h" #include "common/SafeArray.inl" #include "SPU2/spu2.h" +#include "USB/USB.h" +#ifdef _WIN32 +#include "PAD/Windows/PAD.h" +#else +#include "PAD/Linux/PAD.h" +#endif + +#include + #include "gui/ConsoleLogger.h" using namespace R5900; @@ -314,3 +326,570 @@ wxString Exception::SaveStateLoadError::FormatDisplayMessage() const _formatUserMsg(retval); return retval; } + +// Used to hold the current state backup (fullcopy of PS2 memory and subcomponents states). +//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_InternalStructures = L"PCSX2 Internal Structures.dat"; + +struct SysState_Component +{ + const char* name; + int (*freeze)(FreezeAction, freezeData*); +}; + +int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP) +{ + ScopedCoreThreadPause paused_core; + MTGS_FreezeData sstate = { fP, 0 }; + GetMTGS().Freeze(mode, sstate); + paused_core.AllowResume(); + return sstate.retval; +} + +static constexpr SysState_Component SPU2{ "SPU2", SPU2freeze }; +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) +{ + freezeData fP = { 0, (u8*)dest }; + if (comp.freeze(FreezeAction::Size, &fP) != 0) + return; + if (!fP.size) + return; + + Console.Indent().WriteLn("Saving %s", comp.name); + + if (comp.freeze(FreezeAction::Save, &fP) != 0) + throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error saving state!\n")); +} + +void SysState_ComponentFreezeIn(pxInputStream& infp, SysState_Component comp) +{ + freezeData fP = { 0, nullptr }; + if (comp.freeze(FreezeAction::Size, &fP) != 0) + fP.size = 0; + + Console.Indent().WriteLn("Loading %s", comp.name); + + if (!infp.IsOk() || !infp.Length()) + { + // no state data to read, but component expects some state data? + // Issue a warning to console... + if (fP.size != 0) + Console.Indent().Warning("Warning: No data for %s found. Status may be unpredictable.", comp.name); + + return; + } + + ScopedAlloc data(fP.size); + fP.data = data.GetPtr(); + + infp.Read(fP.data, fP.size); + if (comp.freeze(FreezeAction::Load, &fP) != 0) + throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error loading state!\n")); +} + +void SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp) +{ + freezeData fP = { 0, NULL }; + if (comp.freeze(FreezeAction::Size, &fP) == 0) + { + const int size = fP.size; + writer.PrepBlock(size); + SysState_ComponentFreezeOutRoot(writer.GetBlockPtr(), comp); + writer.CommitBlock(size); + } + return; +} + +// -------------------------------------------------------------------------------------- +// BaseSavestateEntry +// -------------------------------------------------------------------------------------- +class BaseSavestateEntry +{ +protected: + BaseSavestateEntry() = default; + +public: + virtual ~BaseSavestateEntry() = default; + + virtual wxString GetFilename() const = 0; + virtual void FreezeIn(pxInputStream& reader) const = 0; + virtual void FreezeOut(SaveStateBase& writer) const = 0; + virtual bool IsRequired() const = 0; +}; + +class MemorySavestateEntry : public BaseSavestateEntry +{ +protected: + MemorySavestateEntry() {} + virtual ~MemorySavestateEntry() = default; + +public: + virtual void FreezeIn(pxInputStream& reader) const; + virtual void FreezeOut(SaveStateBase& writer) const; + virtual bool IsRequired() const { return true; } + +protected: + virtual u8* GetDataPtr() const = 0; + virtual uint GetDataSize() const = 0; +}; + +void MemorySavestateEntry::FreezeIn(pxInputStream& reader) const +{ + const uint entrySize = reader.Length(); + const uint expectedSize = GetDataSize(); + + if (entrySize < expectedSize) + { + Console.WriteLn(Color_Yellow, " '%s' is incomplete (expected 0x%x bytes, loading only 0x%x bytes)", + WX_STR(GetFilename()), expectedSize, entrySize); + } + + uint copylen = std::min(entrySize, expectedSize); + reader.Read(GetDataPtr(), copylen); +} + +void MemorySavestateEntry::FreezeOut(SaveStateBase& writer) const +{ + writer.FreezeMem(GetDataPtr(), GetDataSize()); +} + +// -------------------------------------------------------------------------------------- +// SavestateEntry_* (EmotionMemory, IopMemory, etc) +// -------------------------------------------------------------------------------------- +// Implementation Rationale: +// The address locations of PS2 virtual memory components is fully dynamic, so we need to +// resolve the pointers at the time they are requested (eeMem, iopMem, etc). Thusly, we +// cannot use static struct member initializers -- we need virtual functions that compute +// and resolve the addresses on-demand instead... --air + +class SavestateEntry_EmotionMemory : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_EmotionMemory() = default; + + wxString GetFilename() const { return L"eeMemory.bin"; } + u8* GetDataPtr() const { return eeMem->Main; } + uint GetDataSize() const { return sizeof(eeMem->Main); } + + virtual void FreezeIn(pxInputStream& reader) const + { + SysClearExecutionCache(); + MemorySavestateEntry::FreezeIn(reader); + } +}; + +class SavestateEntry_IopMemory : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_IopMemory() = default; + + wxString GetFilename() const { return L"iopMemory.bin"; } + u8* GetDataPtr() const { return iopMem->Main; } + uint GetDataSize() const { return sizeof(iopMem->Main); } +}; + +class SavestateEntry_HwRegs : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_HwRegs() = default; + + wxString GetFilename() const { return L"eeHwRegs.bin"; } + u8* GetDataPtr() const { return eeHw; } + uint GetDataSize() const { return sizeof(eeHw); } +}; + +class SavestateEntry_IopHwRegs : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_IopHwRegs() = default; + + wxString GetFilename() const { return L"iopHwRegs.bin"; } + u8* GetDataPtr() const { return iopHw; } + uint GetDataSize() const { return sizeof(iopHw); } +}; + +class SavestateEntry_Scratchpad : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_Scratchpad() = default; + + wxString GetFilename() const { return L"Scratchpad.bin"; } + u8* GetDataPtr() const { return eeMem->Scratch; } + uint GetDataSize() const { return sizeof(eeMem->Scratch); } +}; + +class SavestateEntry_VU0mem : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_VU0mem() = default; + + wxString GetFilename() const { return L"vu0Memory.bin"; } + u8* GetDataPtr() const { return vuRegs[0].Mem; } + uint GetDataSize() const { return VU0_MEMSIZE; } +}; + +class SavestateEntry_VU1mem : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_VU1mem() = default; + + wxString GetFilename() const { return L"vu1Memory.bin"; } + u8* GetDataPtr() const { return vuRegs[1].Mem; } + uint GetDataSize() const { return VU1_MEMSIZE; } +}; + +class SavestateEntry_VU0prog : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_VU0prog() = default; + + wxString GetFilename() const { return L"vu0MicroMem.bin"; } + u8* GetDataPtr() const { return vuRegs[0].Micro; } + uint GetDataSize() const { return VU0_PROGSIZE; } +}; + +class SavestateEntry_VU1prog : public MemorySavestateEntry +{ +public: + virtual ~SavestateEntry_VU1prog() = default; + + wxString GetFilename() const { return L"vu1MicroMem.bin"; } + u8* GetDataPtr() const { return vuRegs[1].Micro; } + uint GetDataSize() const { return VU1_PROGSIZE; } +}; + +class SavestateEntry_SPU2 : public BaseSavestateEntry +{ +public: + virtual ~SavestateEntry_SPU2() = default; + + wxString GetFilename() const { return L"SPU2.bin"; } + void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, SPU2); } + void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, SPU2); } + bool IsRequired() const { return true; } +}; + +class SavestateEntry_USB : public BaseSavestateEntry +{ +public: + virtual ~SavestateEntry_USB() = default; + + wxString GetFilename() const { return L"USB.bin"; } + void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, USB); } + void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, USB); } + bool IsRequired() const { return true; } +}; + +class SavestateEntry_PAD : public BaseSavestateEntry +{ +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); } + bool IsRequired() const { return true; } +}; + +class SavestateEntry_GS : public BaseSavestateEntry +{ +public: + virtual ~SavestateEntry_GS() = default; + + wxString GetFilename() const { return L"GS.bin"; } + void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, GS); } + void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, GS); } + bool IsRequired() const { return true; } +}; + + + +// (cpuRegs, iopRegs, VPU/GIF/DMAC structures should all remain as part of a larger unified +// block, since they're all PCSX2-dependent and having separate files in the archie for them +// would not be useful). +// + +static const std::unique_ptr SavestateEntries[] = { + std::unique_ptr(new SavestateEntry_EmotionMemory), + std::unique_ptr(new SavestateEntry_IopMemory), + std::unique_ptr(new SavestateEntry_HwRegs), + std::unique_ptr(new SavestateEntry_IopHwRegs), + std::unique_ptr(new SavestateEntry_Scratchpad), + std::unique_ptr(new SavestateEntry_VU0mem), + std::unique_ptr(new SavestateEntry_VU1mem), + std::unique_ptr(new SavestateEntry_VU0prog), + std::unique_ptr(new SavestateEntry_VU1prog), + std::unique_ptr(new SavestateEntry_SPU2), + std::unique_ptr(new SavestateEntry_USB), + std::unique_ptr(new SavestateEntry_PAD), + std::unique_ptr(new SavestateEntry_GS), +}; + +// It's bad mojo to have savestates trying to read and write from the same file at the +// same time. To prevent that we use this mutex lock, which is used by both the +// CompressThread and the UnzipFromDisk events. (note that CompressThread locks the +// mutex during OnStartInThread, which ensures that the ZipToDisk event blocks; preventing +// the SysExecutor's Idle Event from re-enabing savestates and slots.) +// +static Mutex mtx_CompressToDisk; + +static void CheckVersion(pxInputStream& thr) +{ + u32 savever; + thr.Read(savever); + + // Major version mismatch. Means we can't load this savestate at all. Support for it + // was removed entirely. + if (savever > g_SaveVersion) + throw Exception::SaveStateLoadError(thr.GetStreamName()) + .SetDiagMsg(pxsFmt(L"Savestate uses an unsupported or unknown savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever)) + .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version.")); + + // check for a "minor" version incompatibility; which happens if the savestate being loaded is a newer version + // than the emulator recognizes. 99% chance that trying to load it will just corrupt emulation or crash. + if ((savever >> 16) != (g_SaveVersion >> 16)) + throw Exception::SaveStateLoadError(thr.GetStreamName()) + .SetDiagMsg(pxsFmt(L"Savestate uses an unknown savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever)) + .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version.")); +}; + +void SaveState_DownloadState(ArchiveEntryList* destlist) +{ + if (!SysHasValidState()) + 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.")); + + memSavingState saveme(destlist->GetBuffer()); + ArchiveEntry internals(EntryFilename_InternalStructures); + internals.SetDataIndex(saveme.GetCurrentPos()); + + saveme.FreezeBios(); + saveme.FreezeInternals(); + + internals.SetDataSize(saveme.GetCurrentPos() - internals.GetDataIndex()); + destlist->Add(internals); + + for (uint i = 0; i < ArraySize(SavestateEntries); ++i) + { + uint startpos = saveme.GetCurrentPos(); + SavestateEntries[i]->FreezeOut(saveme); + destlist->Add(ArchiveEntry(SavestateEntries[i]->GetFilename()) + .SetDataIndex(startpos) + .SetDataSize(saveme.GetCurrentPos() - startpos)); + } +} + +// -------------------------------------------------------------------------------------- +// CompressThread_VmState +// -------------------------------------------------------------------------------------- +class VmStateCompressThread : public BaseCompressThread +{ + typedef BaseCompressThread _parent; + +protected: + ScopedLock m_lock_Compress; + +public: + VmStateCompressThread() + { + m_lock_Compress.Assign(mtx_CompressToDisk); + } + + virtual ~VmStateCompressThread() = default; + +protected: + void OnStartInThread() + { + _parent::OnStartInThread(); + m_lock_Compress.Acquire(); + } + + void OnCleanupInThread() + { + m_lock_Compress.Release(); + _parent::OnCleanupInThread(); + } +}; + +void SaveState_ZipToDisk(ArchiveEntryList* srclist, const wxString& filename) +{ + // Provisionals for scoped cleanup, in case of exception: + std::unique_ptr elist(srclist); + + wxString tempfile(filename + L".tmp"); + + wxFFileOutputStream* woot = new wxFFileOutputStream(tempfile); + if (!woot->IsOk()) + throw Exception::CannotCreateStream(tempfile); + + // Scheduler hint (yield) -- creating and saving the file is low priority compared to + // the emulator/vm thread. Sleeping the executor thread briefly before doing file + // transactions should help reduce overhead. --air + + pxYield(4); + + // Write the version and screenshot: + std::unique_ptr out(new pxOutputStream(tempfile, new wxZipOutputStream(woot))); + wxZipOutputStream* gzfp = (wxZipOutputStream*)out->GetWxStreamBase(); + + { + wxZipEntry* vent = new wxZipEntry(EntryFilename_StateVersion); + vent->SetMethod(wxZIP_METHOD_STORE); + gzfp->PutNextEntry(vent); + out->Write(g_SaveVersion); + gzfp->CloseEntry(); + } + + 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(); + } + + (*new VmStateCompressThread()) + .SetSource(srclist) + .SetOutStream(out.get()) + .SetFinishedPath(filename) + .Start(); + + // No errors? Release cleanup handlers: + elist.release(); + out.release(); +} + +void SaveState_UnzipFromDisk(const wxString& filename) +{ + ScopedLock lock(mtx_CompressToDisk); + + // Ugh. Exception handling made crappy because wxWidgets classes don't support scoped pointers yet. + + std::unique_ptr woot(new wxFFileInputStream(filename)); + if (!woot->IsOk()) + throw Exception::CannotCreateStream(filename).SetDiagMsg(L"Cannot open file for reading."); + + std::unique_ptr reader(new pxInputStream(filename, new wxZipInputStream(woot.get()))); + woot.release(); + + if (!reader->IsOk()) + { + throw Exception::SaveStateLoadError(filename) + .SetDiagMsg(L"Savestate file is not a valid gzip archive.") + .SetUserMsg(_("This savestate cannot be loaded because it is not a valid gzip archive. It may have been created by an older unsupported version of PCSX2, or it may be corrupted.")); + } + + wxZipInputStream* gzreader = (wxZipInputStream*)reader->GetWxStreamBase(); + + // look for version and screenshot information in the zip stream: + + bool foundVersion = false; + //bool foundScreenshot = false; + //bool foundEntry[ArraySize(SavestateEntries)] = false; + + std::unique_ptr foundInternal; + std::unique_ptr foundEntry[ArraySize(SavestateEntries)]; + + while (true) + { + Threading::pxTestCancel(); + + std::unique_ptr entry(gzreader->GetNextEntry()); + if (!entry) + break; + + if (entry->GetName().CmpNoCase(EntryFilename_StateVersion) == 0) + { + DevCon.WriteLn(Color_Green, L" ... found '%s'", EntryFilename_StateVersion); + foundVersion = true; + CheckVersion(*reader); + continue; + } + + if (entry->GetName().CmpNoCase(EntryFilename_InternalStructures) == 0) + { + DevCon.WriteLn(Color_Green, L" ... found '%s'", EntryFilename_InternalStructures); + foundInternal = std::move(entry); + continue; + } + + // No point in finding screenshots when loading states -- the screenshots are + // only useful for the UI savestate browser. + /*if (entry->GetName().CmpNoCase(EntryFilename_Screenshot) == 0) + { + foundScreenshot = true; + }*/ + + for (uint i = 0; i < ArraySize(SavestateEntries); ++i) + { + if (entry->GetName().CmpNoCase(SavestateEntries[i]->GetFilename()) == 0) + { + DevCon.WriteLn(Color_Green, L" ... found '%s'", WX_STR(SavestateEntries[i]->GetFilename())); + foundEntry[i] = std::move(entry); + break; + } + } + } + + if (!foundVersion || !foundInternal) + { + throw Exception::SaveStateLoadError(filename) + .SetDiagMsg(pxsFmt(L"Savestate file does not contain '%s'", + !foundVersion ? EntryFilename_StateVersion : EntryFilename_InternalStructures)) + .SetUserMsg(_("This file is not a valid PCSX2 savestate. See the logfile for details.")); + } + + // Log any parts and pieces that are missing, and then generate an exception. + bool throwIt = false; + for (uint i = 0; i < ArraySize(SavestateEntries); ++i) + { + if (foundEntry[i]) + continue; + + if (SavestateEntries[i]->IsRequired()) + { + throwIt = true; + Console.WriteLn(Color_Red, " ... not found '%s'!", WX_STR(SavestateEntries[i]->GetFilename())); + } + } + + if (throwIt) + throw Exception::SaveStateLoadError(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.")); + + PatchesVerboseReset(); + SysClearExecutionCache(); + + for (uint i = 0; i < ArraySize(SavestateEntries); ++i) + { + if (!foundEntry[i]) + continue; + + Threading::pxTestCancel(); + + gzreader->OpenEntry(*foundEntry[i]); + SavestateEntries[i]->FreezeIn(*reader); + } + + // Load all the internal data + + gzreader->OpenEntry(*foundInternal); + + VmStateBuffer buffer(foundInternal->GetSize(), L"StateBuffer_UnzipFromDisk"); // start with an 8 meg buffer to avoid frequent reallocation. + reader->Read(buffer.GetPtr(), foundInternal->GetSize()); + + memLoadingState(buffer).FreezeBios().FreezeInternals(); +} \ No newline at end of file diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index b1f6d44f88..a9fe7d8093 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -45,9 +45,13 @@ struct freezeData u8 *data; }; -// this function is meant to be used in the place of GSfreeze, and provides a safe layer -// between the GS saving function and the MTGS's needs. :) -extern s32 CALLBACK gsSafeFreeze( int mode, freezeData *data ); +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 void SaveState_UnzipFromDisk(const wxString& filename); // -------------------------------------------------------------------------------------- // SaveStateBase class diff --git a/pcsx2/gui/SysState.cpp b/pcsx2/gui/SysState.cpp index 0115f0a44c..cb3978a49f 100644 --- a/pcsx2/gui/SysState.cpp +++ b/pcsx2/gui/SysState.cpp @@ -38,340 +38,6 @@ #include "Patch.h" -// Used to hold the current state backup (fullcopy of PS2 memory and subcomponents states). -//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_InternalStructures = L"PCSX2 Internal Structures.dat"; - -struct SysState_Component -{ - const char* name; - int (*freeze)(FreezeAction, freezeData*); -}; - -int SysState_MTGSFreeze(FreezeAction mode, freezeData* fP) -{ - ScopedCoreThreadPause paused_core; - MTGS_FreezeData sstate = {fP, 0}; - GetMTGS().Freeze(mode, sstate); - paused_core.AllowResume(); - return sstate.retval; -} - -static constexpr SysState_Component SPU2{"SPU2", SPU2freeze}; -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) -{ - freezeData fP = {0, (u8*)dest}; - if (comp.freeze(FreezeAction::Size, &fP) != 0) - return; - if (!fP.size) - return; - - Console.Indent().WriteLn("Saving %s", comp.name); - - if (comp.freeze(FreezeAction::Save, &fP) != 0) - throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error saving state!\n")); -} - -void SysState_ComponentFreezeIn(pxInputStream& infp, SysState_Component comp) -{ - freezeData fP = {0, nullptr}; - if (comp.freeze(FreezeAction::Size, &fP) != 0) - fP.size = 0; - - Console.Indent().WriteLn("Loading %s", comp.name); - - if (!infp.IsOk() || !infp.Length()) - { - // no state data to read, but component expects some state data? - // Issue a warning to console... - if (fP.size != 0) - Console.Indent().Warning("Warning: No data for %s found. Status may be unpredictable.", comp.name); - - return; - } - - ScopedAlloc data(fP.size); - fP.data = data.GetPtr(); - - infp.Read(fP.data, fP.size); - if (comp.freeze(FreezeAction::Load, &fP) != 0) - throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error loading state!\n")); -} - -void SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp) -{ - freezeData fP = {0, NULL}; - if (comp.freeze(FreezeAction::Size, &fP) == 0) - { - const int size = fP.size; - writer.PrepBlock(size); - SysState_ComponentFreezeOutRoot(writer.GetBlockPtr(), comp); - writer.CommitBlock(size); - } - return; -} - -// -------------------------------------------------------------------------------------- -// BaseSavestateEntry -// -------------------------------------------------------------------------------------- -class BaseSavestateEntry -{ -protected: - BaseSavestateEntry() = default; - -public: - virtual ~BaseSavestateEntry() = default; - - virtual wxString GetFilename() const = 0; - virtual void FreezeIn(pxInputStream& reader) const = 0; - virtual void FreezeOut(SaveStateBase& writer) const = 0; - virtual bool IsRequired() const = 0; -}; - -class MemorySavestateEntry : public BaseSavestateEntry -{ -protected: - MemorySavestateEntry() {} - virtual ~MemorySavestateEntry() = default; - -public: - virtual void FreezeIn(pxInputStream& reader) const; - virtual void FreezeOut(SaveStateBase& writer) const; - virtual bool IsRequired() const { return true; } - -protected: - virtual u8* GetDataPtr() const = 0; - virtual uint GetDataSize() const = 0; -}; - -void MemorySavestateEntry::FreezeIn(pxInputStream& reader) const -{ - const uint entrySize = reader.Length(); - const uint expectedSize = GetDataSize(); - - if (entrySize < expectedSize) - { - Console.WriteLn(Color_Yellow, " '%s' is incomplete (expected 0x%x bytes, loading only 0x%x bytes)", - WX_STR(GetFilename()), expectedSize, entrySize); - } - - uint copylen = std::min(entrySize, expectedSize); - reader.Read(GetDataPtr(), copylen); -} - -void MemorySavestateEntry::FreezeOut(SaveStateBase& writer) const -{ - writer.FreezeMem(GetDataPtr(), GetDataSize()); -} - -// -------------------------------------------------------------------------------------- -// SavestateEntry_* (EmotionMemory, IopMemory, etc) -// -------------------------------------------------------------------------------------- -// Implementation Rationale: -// The address locations of PS2 virtual memory components is fully dynamic, so we need to -// resolve the pointers at the time they are requested (eeMem, iopMem, etc). Thusly, we -// cannot use static struct member initializers -- we need virtual functions that compute -// and resolve the addresses on-demand instead... --air - -class SavestateEntry_EmotionMemory : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_EmotionMemory() = default; - - wxString GetFilename() const { return L"eeMemory.bin"; } - u8* GetDataPtr() const { return eeMem->Main; } - uint GetDataSize() const { return sizeof(eeMem->Main); } - - virtual void FreezeIn(pxInputStream& reader) const - { - SysClearExecutionCache(); - MemorySavestateEntry::FreezeIn(reader); - } -}; - -class SavestateEntry_IopMemory : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_IopMemory() = default; - - wxString GetFilename() const { return L"iopMemory.bin"; } - u8* GetDataPtr() const { return iopMem->Main; } - uint GetDataSize() const { return sizeof(iopMem->Main); } -}; - -class SavestateEntry_HwRegs : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_HwRegs() = default; - - wxString GetFilename() const { return L"eeHwRegs.bin"; } - u8* GetDataPtr() const { return eeHw; } - uint GetDataSize() const { return sizeof(eeHw); } -}; - -class SavestateEntry_IopHwRegs : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_IopHwRegs() = default; - - wxString GetFilename() const { return L"iopHwRegs.bin"; } - u8* GetDataPtr() const { return iopHw; } - uint GetDataSize() const { return sizeof(iopHw); } -}; - -class SavestateEntry_Scratchpad : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_Scratchpad() = default; - - wxString GetFilename() const { return L"Scratchpad.bin"; } - u8* GetDataPtr() const { return eeMem->Scratch; } - uint GetDataSize() const { return sizeof(eeMem->Scratch); } -}; - -class SavestateEntry_VU0mem : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_VU0mem() = default; - - wxString GetFilename() const { return L"vu0Memory.bin"; } - u8* GetDataPtr() const { return vuRegs[0].Mem; } - uint GetDataSize() const { return VU0_MEMSIZE; } -}; - -class SavestateEntry_VU1mem : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_VU1mem() = default; - - wxString GetFilename() const { return L"vu1Memory.bin"; } - u8* GetDataPtr() const { return vuRegs[1].Mem; } - uint GetDataSize() const { return VU1_MEMSIZE; } -}; - -class SavestateEntry_VU0prog : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_VU0prog() = default; - - wxString GetFilename() const { return L"vu0MicroMem.bin"; } - u8* GetDataPtr() const { return vuRegs[0].Micro; } - uint GetDataSize() const { return VU0_PROGSIZE; } -}; - -class SavestateEntry_VU1prog : public MemorySavestateEntry -{ -public: - virtual ~SavestateEntry_VU1prog() = default; - - wxString GetFilename() const { return L"vu1MicroMem.bin"; } - u8* GetDataPtr() const { return vuRegs[1].Micro; } - uint GetDataSize() const { return VU1_PROGSIZE; } -}; - -class SavestateEntry_SPU2 : public BaseSavestateEntry -{ -public: - virtual ~SavestateEntry_SPU2() = default; - - wxString GetFilename() const { return L"SPU2.bin"; } - void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, SPU2); } - void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, SPU2); } - bool IsRequired() const { return true; } -}; - -class SavestateEntry_USB : public BaseSavestateEntry -{ -public: - virtual ~SavestateEntry_USB() = default; - - wxString GetFilename() const { return L"USB.bin"; } - void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, USB); } - void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, USB); } - bool IsRequired() const { return true; } -}; - -class SavestateEntry_PAD : public BaseSavestateEntry -{ -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); } - bool IsRequired() const { return true; } -}; - -class SavestateEntry_GS : public BaseSavestateEntry -{ -public: - virtual ~SavestateEntry_GS() = default; - - wxString GetFilename() const { return L"GS.bin"; } - void FreezeIn(pxInputStream& reader) const { return SysState_ComponentFreezeIn(reader, GS); } - void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, GS); } - bool IsRequired() const { return true; } -}; - - - -// (cpuRegs, iopRegs, VPU/GIF/DMAC structures should all remain as part of a larger unified -// block, since they're all PCSX2-dependent and having separate files in the archie for them -// would not be useful). -// - -static const std::unique_ptr SavestateEntries[] = { - std::unique_ptr(new SavestateEntry_EmotionMemory), - std::unique_ptr(new SavestateEntry_IopMemory), - std::unique_ptr(new SavestateEntry_HwRegs), - std::unique_ptr(new SavestateEntry_IopHwRegs), - std::unique_ptr(new SavestateEntry_Scratchpad), - std::unique_ptr(new SavestateEntry_VU0mem), - std::unique_ptr(new SavestateEntry_VU1mem), - std::unique_ptr(new SavestateEntry_VU0prog), - std::unique_ptr(new SavestateEntry_VU1prog), - std::unique_ptr(new SavestateEntry_SPU2), - std::unique_ptr(new SavestateEntry_USB), - std::unique_ptr(new SavestateEntry_PAD), - std::unique_ptr(new SavestateEntry_GS), -}; - -// It's bad mojo to have savestates trying to read and write from the same file at the -// same time. To prevent that we use this mutex lock, which is used by both the -// CompressThread and the UnzipFromDisk events. (note that CompressThread locks the -// mutex during OnStartInThread, which ensures that the ZipToDisk event blocks; preventing -// the SysExecutor's Idle Event from re-enabing savestates and slots.) -// -static Mutex mtx_CompressToDisk; - -static void CheckVersion(pxInputStream& thr) -{ - u32 savever; - thr.Read(savever); - - // Major version mismatch. Means we can't load this savestate at all. Support for it - // was removed entirely. - if (savever > g_SaveVersion) - throw Exception::SaveStateLoadError(thr.GetStreamName()) - .SetDiagMsg(pxsFmt(L"Savestate uses an unsupported or unknown savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever)) - .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version.")); - - // check for a "minor" version incompatibility; which happens if the savestate being loaded is a newer version - // than the emulator recognizes. 99% chance that trying to load it will just corrupt emulation or crash. - if ((savever >> 16) != (g_SaveVersion >> 16)) - throw Exception::SaveStateLoadError(thr.GetStreamName()) - .SetDiagMsg(pxsFmt(L"Savestate uses an unknown savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever)) - .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version.")); -}; - // -------------------------------------------------------------------------------------- // SysExecEvent_DownloadState // -------------------------------------------------------------------------------------- @@ -401,68 +67,14 @@ protected: void InvokeEvent() { ScopedCoreThreadPause paused_core; - - if (!SysHasValidState()) - 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.")); - - memSavingState saveme(m_dest_list->GetBuffer()); - ArchiveEntry internals(EntryFilename_InternalStructures); - internals.SetDataIndex(saveme.GetCurrentPos()); - - saveme.FreezeBios(); - saveme.FreezeInternals(); - - internals.SetDataSize(saveme.GetCurrentPos() - internals.GetDataIndex()); - m_dest_list->Add(internals); - - for (uint i = 0; i < ArraySize(SavestateEntries); ++i) - { - uint startpos = saveme.GetCurrentPos(); - SavestateEntries[i]->FreezeOut(saveme); - m_dest_list->Add(ArchiveEntry(SavestateEntries[i]->GetFilename()) - .SetDataIndex(startpos) - .SetDataSize(saveme.GetCurrentPos() - startpos)); - } - + SaveState_DownloadState(m_dest_list); UI_EnableStateActions(); paused_core.AllowResume(); } }; -// -------------------------------------------------------------------------------------- -// CompressThread_VmState -// -------------------------------------------------------------------------------------- -class VmStateCompressThread : public BaseCompressThread -{ - typedef BaseCompressThread _parent; -protected: - ScopedLock m_lock_Compress; - -public: - VmStateCompressThread() - { - m_lock_Compress.Assign(mtx_CompressToDisk); - } - - virtual ~VmStateCompressThread() = default; - -protected: - void OnStartInThread() - { - _parent::OnStartInThread(); - m_lock_Compress.Acquire(); - } - - void OnCleanupInThread() - { - m_lock_Compress.Release(); - _parent::OnCleanupInThread(); - } -}; // -------------------------------------------------------------------------------------- // SysExecEvent_ZipToDisk @@ -498,53 +110,7 @@ public: protected: void InvokeEvent() { - // Provisionals for scoped cleanup, in case of exception: - std::unique_ptr elist(m_src_list); - - wxString tempfile(m_filename + L".tmp"); - - wxFFileOutputStream* woot = new wxFFileOutputStream(tempfile); - if (!woot->IsOk()) - throw Exception::CannotCreateStream(tempfile); - - // Scheduler hint (yield) -- creating and saving the file is low priority compared to - // the emulator/vm thread. Sleeping the executor thread briefly before doing file - // transactions should help reduce overhead. --air - - pxYield(4); - - // Write the version and screenshot: - std::unique_ptr out(new pxOutputStream(tempfile, new wxZipOutputStream(woot))); - wxZipOutputStream* gzfp = (wxZipOutputStream*)out->GetWxStreamBase(); - - { - wxZipEntry* vent = new wxZipEntry(EntryFilename_StateVersion); - vent->SetMethod(wxZIP_METHOD_STORE); - gzfp->PutNextEntry(vent); - out->Write(g_SaveVersion); - gzfp->CloseEntry(); - } - - 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(); - } - - (*new VmStateCompressThread()) - .SetSource(elist.get()) - .SetOutStream(out.get()) - .SetFinishedPath(m_filename) - .Start(); - - // No errors? Release cleanup handlers: - elist.release(); - out.release(); + SaveState_ZipToDisk(m_src_list, m_filename); } void CleanupEvent() @@ -579,130 +145,10 @@ public: protected: void InvokeEvent() { - ScopedLock lock(mtx_CompressToDisk); - - // Ugh. Exception handling made crappy because wxWidgets classes don't support scoped pointers yet. - - std::unique_ptr woot(new wxFFileInputStream(m_filename)); - if (!woot->IsOk()) - throw Exception::CannotCreateStream(m_filename).SetDiagMsg(L"Cannot open file for reading."); - - std::unique_ptr reader(new pxInputStream(m_filename, new wxZipInputStream(woot.get()))); - woot.release(); - - if (!reader->IsOk()) - { - throw Exception::SaveStateLoadError(m_filename) - .SetDiagMsg(L"Savestate file is not a valid gzip archive.") - .SetUserMsg(_("This savestate cannot be loaded because it is not a valid gzip archive. It may have been created by an older unsupported version of PCSX2, or it may be corrupted.")); - } - - wxZipInputStream* gzreader = (wxZipInputStream*)reader->GetWxStreamBase(); - - // look for version and screenshot information in the zip stream: - - bool foundVersion = false; - //bool foundScreenshot = false; - //bool foundEntry[ArraySize(SavestateEntries)] = false; - - std::unique_ptr foundInternal; - std::unique_ptr foundEntry[ArraySize(SavestateEntries)]; - - while (true) - { - Threading::pxTestCancel(); - - std::unique_ptr entry(gzreader->GetNextEntry()); - if (!entry) - break; - - if (entry->GetName().CmpNoCase(EntryFilename_StateVersion) == 0) - { - DevCon.WriteLn(Color_Green, L" ... found '%s'", EntryFilename_StateVersion); - foundVersion = true; - CheckVersion(*reader); - continue; - } - - if (entry->GetName().CmpNoCase(EntryFilename_InternalStructures) == 0) - { - DevCon.WriteLn(Color_Green, L" ... found '%s'", EntryFilename_InternalStructures); - foundInternal = std::move(entry); - continue; - } - - // No point in finding screenshots when loading states -- the screenshots are - // only useful for the UI savestate browser. - /*if (entry->GetName().CmpNoCase(EntryFilename_Screenshot) == 0) - { - foundScreenshot = true; - }*/ - - for (uint i = 0; i < ArraySize(SavestateEntries); ++i) - { - if (entry->GetName().CmpNoCase(SavestateEntries[i]->GetFilename()) == 0) - { - DevCon.WriteLn(Color_Green, L" ... found '%s'", WX_STR(SavestateEntries[i]->GetFilename())); - foundEntry[i] = std::move(entry); - break; - } - } - } - - if (!foundVersion || !foundInternal) - { - throw Exception::SaveStateLoadError(m_filename) - .SetDiagMsg(pxsFmt(L"Savestate file does not contain '%s'", - !foundVersion ? EntryFilename_StateVersion : EntryFilename_InternalStructures)) - .SetUserMsg(_("This file is not a valid PCSX2 savestate. See the logfile for details.")); - } - - // Log any parts and pieces that are missing, and then generate an exception. - bool throwIt = false; - for (uint i = 0; i < ArraySize(SavestateEntries); ++i) - { - if (foundEntry[i]) - continue; - - if (SavestateEntries[i]->IsRequired()) - { - throwIt = true; - Console.WriteLn(Color_Red, " ... not found '%s'!", WX_STR(SavestateEntries[i]->GetFilename())); - } - } - - if (throwIt) - throw Exception::SaveStateLoadError(m_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.")); - // We use direct Suspend/Resume control here, since it's desirable that emulation // *ALWAYS* start execution after the new savestate is loaded. - - PatchesVerboseReset(); - GetCoreThread().Pause({}); - SysClearExecutionCache(); - - for (uint i = 0; i < ArraySize(SavestateEntries); ++i) - { - if (!foundEntry[i]) - continue; - - Threading::pxTestCancel(); - - gzreader->OpenEntry(*foundEntry[i]); - SavestateEntries[i]->FreezeIn(*reader); - } - - // Load all the internal data - - gzreader->OpenEntry(*foundInternal); - - VmStateBuffer buffer(foundInternal->GetSize(), L"StateBuffer_UnzipFromDisk"); // start with an 8 meg buffer to avoid frequent reallocation. - reader->Read(buffer.GetPtr(), foundInternal->GetSize()); - - memLoadingState(buffer).FreezeBios().FreezeInternals(); + SaveState_UnzipFromDisk(m_filename); GetCoreThread().Resume(); // force resume regardless of emulation state earlier. } };