mirror of https://github.com/PCSX2/pcsx2.git
SaveState: Move file generation logic out of GUI
This commit is contained in:
parent
640e955c38
commit
c234f83ca6
|
@ -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 <wx/wfstream.h>
|
||||
|
||||
#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<u8> 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<BaseSavestateEntry> SavestateEntries[] = {
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_EmotionMemory),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_IopMemory),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_HwRegs),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_IopHwRegs),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_Scratchpad),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU0mem),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU1mem),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU0prog),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU1prog),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_SPU2),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_USB),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_PAD),
|
||||
std::unique_ptr<BaseSavestateEntry>(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<ArchiveEntryList> 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<pxOutputStream> 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<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();
|
||||
}
|
||||
|
||||
(*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<wxFFileInputStream> woot(new wxFFileInputStream(filename));
|
||||
if (!woot->IsOk())
|
||||
throw Exception::CannotCreateStream(filename).SetDiagMsg(L"Cannot open file for reading.");
|
||||
|
||||
std::unique_ptr<pxInputStream> 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<wxZipEntry> foundInternal;
|
||||
std::unique_ptr<wxZipEntry> foundEntry[ArraySize(SavestateEntries)];
|
||||
|
||||
while (true)
|
||||
{
|
||||
Threading::pxTestCancel();
|
||||
|
||||
std::unique_ptr<wxZipEntry> 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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<u8> 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<BaseSavestateEntry> SavestateEntries[] = {
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_EmotionMemory),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_IopMemory),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_HwRegs),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_IopHwRegs),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_Scratchpad),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU0mem),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU1mem),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU0prog),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_VU1prog),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_SPU2),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_USB),
|
||||
std::unique_ptr<BaseSavestateEntry>(new SavestateEntry_PAD),
|
||||
std::unique_ptr<BaseSavestateEntry>(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<ArchiveEntryList> 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<pxOutputStream> 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<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();
|
||||
}
|
||||
|
||||
(*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<wxFFileInputStream> woot(new wxFFileInputStream(m_filename));
|
||||
if (!woot->IsOk())
|
||||
throw Exception::CannotCreateStream(m_filename).SetDiagMsg(L"Cannot open file for reading.");
|
||||
|
||||
std::unique_ptr<pxInputStream> 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<wxZipEntry> foundInternal;
|
||||
std::unique_ptr<wxZipEntry> foundEntry[ArraySize(SavestateEntries)];
|
||||
|
||||
while (true)
|
||||
{
|
||||
Threading::pxTestCancel();
|
||||
|
||||
std::unique_ptr<wxZipEntry> 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.
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue