SaveState: Remove exceptions

This commit is contained in:
Stenzek 2022-12-28 21:13:38 +10:00 committed by Connor McLaughlin
parent 52266d7ac0
commit 81236209db
21 changed files with 487 additions and 368 deletions

View File

@ -934,10 +934,14 @@ void cdvdReset()
cdvdCtrlTrayClose(); cdvdCtrlTrayClose();
} }
void SaveStateBase::cdvdFreeze() bool SaveStateBase::cdvdFreeze()
{ {
FreezeTag("cdvd"); if (!FreezeTag("cdvd"))
return false;
Freeze(cdvd); Freeze(cdvd);
if (!IsOkay())
return false;
if (IsLoading()) if (IsLoading())
{ {
@ -948,6 +952,8 @@ void SaveStateBase::cdvdFreeze()
if (cdvd.Reading) if (cdvd.Reading)
cdvd.RErr = DoCDVDreadTrack(cdvd.Readed ? cdvd.Sector : cdvd.SeekToSector, cdvd.ReadMode); cdvd.RErr = DoCDVDreadTrack(cdvd.Readed ? cdvd.Sector : cdvd.SeekToSector, cdvd.ReadMode);
} }
return true;
} }
void cdvdNewDiskCB() void cdvdNewDiskCB()

View File

@ -1114,8 +1114,11 @@ void cdrReset()
cdReadTime = (PSXCLK / 1757) * BIAS; cdReadTime = (PSXCLK / 1757) * BIAS;
} }
void SaveStateBase::cdrFreeze() bool SaveStateBase::cdrFreeze()
{ {
FreezeTag("cdrom"); if (!FreezeTag("cdrom"))
return false;
Freeze(cdr); Freeze(cdr);
return IsOkay();
} }

View File

@ -1156,7 +1156,7 @@ template u16 rcntRead32<0x01>( u32 mem );
template bool rcntWrite32<0x00>( u32 mem, mem32_t& value ); template bool rcntWrite32<0x00>( u32 mem, mem32_t& value );
template bool rcntWrite32<0x01>( u32 mem, mem32_t& value ); template bool rcntWrite32<0x01>( u32 mem, mem32_t& value );
void SaveStateBase::rcntFreeze() bool SaveStateBase::rcntFreeze()
{ {
Freeze( counters ); Freeze( counters );
Freeze( hsyncCounter ); Freeze( hsyncCounter );
@ -1170,4 +1170,6 @@ void SaveStateBase::rcntFreeze()
if( IsLoading() ) if( IsLoading() )
cpuRcntSet(); cpuRcntSet();
return IsOkay();
} }

View File

@ -372,9 +372,10 @@ void gsPostVsyncStart()
MTGS::PostVsyncStart(registers_written); MTGS::PostVsyncStart(registers_written);
} }
void SaveStateBase::gsFreeze() bool SaveStateBase::gsFreeze()
{ {
FreezeMem(PS2MEM_GS, 0x2000); FreezeMem(PS2MEM_GS, 0x2000);
Freeze(gsVideoMode); Freeze(gsVideoMode);
return IsOkay();
} }

View File

@ -804,10 +804,14 @@ void gifMFIFOInterrupt()
DMA_LOG("GIF MFIFO DMA End"); DMA_LOG("GIF MFIFO DMA End");
} }
void SaveStateBase::gifDmaFreeze() bool SaveStateBase::gifDmaFreeze()
{ {
// Note: mfifocycles is not a persistent var, so no need to save it here. // Note: mfifocycles is not a persistent var, so no need to save it here.
FreezeTag("GIFdma"); if (!FreezeTag("GIFdma"))
return false;
Freeze(gif); Freeze(gif);
Freeze(gif_fifo); Freeze(gif_fifo);
return IsOkay();
} }

View File

@ -195,7 +195,7 @@ void Gif_FinishIRQ()
} }
} }
void SaveStateBase::gifPathFreeze(u32 path) bool SaveStateBase::gifPathFreeze(u32 path)
{ {
Gif_Path& gifPath = gifUnit.gifPath[path]; Gif_Path& gifPath = gifUnit.gifPath[path];
@ -220,14 +220,18 @@ void SaveStateBase::gifPathFreeze(u32 path)
gifPath.readAmount = 0; gifPath.readAmount = 0;
gifPath.gsPack.readAmount = 0; gifPath.gsPack.readAmount = 0;
} }
return IsOkay();
} }
void SaveStateBase::gifFreeze() bool SaveStateBase::gifFreeze()
{ {
bool mtvuMode = THREAD_VU1; bool mtvuMode = THREAD_VU1;
pxAssert(vu1Thread.IsDone()); pxAssert(vu1Thread.IsDone());
MTGS::WaitGS(); MTGS::WaitGS();
FreezeTag("Gif Unit"); if (!FreezeTag("Gif Unit"))
return false;
Freeze(mtvuMode); Freeze(mtvuMode);
Freeze(gifUnit.stat); Freeze(gifUnit.stat);
Freeze(gifUnit.gsSIGNAL); Freeze(gifUnit.gsSIGNAL);
@ -244,4 +248,6 @@ void SaveStateBase::gifFreeze()
// ToDo: gifUnit.SwitchMTVU(mtvuMode); // ToDo: gifUnit.SwitchMTVU(mtvuMode);
} }
} }
return true;
} }

View File

@ -134,11 +134,13 @@ void ReportIPU()
Console.Newline(); Console.Newline();
} }
void SaveStateBase::ipuFreeze() bool SaveStateBase::ipuFreeze()
{ {
// Get a report of the status of the ipu variables when saving and loading savestates. // Get a report of the status of the ipu variables when saving and loading savestates.
//ReportIPU(); //ReportIPU();
FreezeTag("IPU"); if (!FreezeTag("IPU"))
return false;
Freeze(ipu_fifo); Freeze(ipu_fifo);
Freeze(g_BP); Freeze(g_BP);
@ -147,6 +149,8 @@ void SaveStateBase::ipuFreeze()
Freeze(coded_block_pattern); Freeze(coded_block_pattern);
Freeze(decoder); Freeze(decoder);
Freeze(ipu_cmd); Freeze(ipu_cmd);
return IsOkay();
} }
void tIPU_CMD_IDEC::log() const void tIPU_CMD_IDEC::log() const

View File

@ -31,11 +31,14 @@ void ipuDmaReset()
ProcessedData = 0; ProcessedData = 0;
} }
void SaveStateBase::ipuDmaFreeze() bool SaveStateBase::ipuDmaFreeze()
{ {
FreezeTag( "IPUdma" ); if (!FreezeTag("IPUdma"))
return false;
Freeze(IPU1Status); Freeze(IPU1Status);
Freeze(CommandExecuteQueued); Freeze(CommandExecuteQueued);
return IsOkay();
} }
static __fi int IPU1chain() { static __fi int IPU1chain() {

View File

@ -880,9 +880,10 @@ void psxRcntSetGates()
psxvblankgate &= ~(1 << 3); psxvblankgate &= ~(1 << 3);
} }
void SaveStateBase::psxRcntFreeze() bool SaveStateBase::psxRcntFreeze()
{ {
FreezeTag("iopCounters"); if (!FreezeTag("iopCounters"))
return false;
Freeze(psxCounters); Freeze(psxCounters);
Freeze(psxNextCounter); Freeze(psxNextCounter);
@ -890,6 +891,11 @@ void SaveStateBase::psxRcntFreeze()
Freeze(psxvblankgate); Freeze(psxvblankgate);
Freeze(psxhblankgate); Freeze(psxhblankgate);
if (!IsOkay())
return false;
if (IsLoading()) if (IsLoading())
psxRcntUpdate(); psxRcntUpdate();
return true;
} }

View File

@ -57,9 +57,11 @@ static void MTVU_Unpack(void* data, VIFregisters& vifRegs)
} }
// Called on Saving/Loading states... // Called on Saving/Loading states...
void SaveStateBase::mtvuFreeze() bool SaveStateBase::mtvuFreeze()
{ {
FreezeTag("MTVU"); if (!FreezeTag("MTVU"))
return false;
pxAssert(vu1Thread.IsDone()); pxAssert(vu1Thread.IsDone());
if (!IsSaving()) if (!IsSaving())
{ {
@ -88,6 +90,7 @@ void SaveStateBase::mtvuFreeze()
vu1Thread.gsLabel.store(gsLabel); vu1Thread.gsLabel.store(gsLabel);
Freeze(vu1Thread.vuCycleIdx); Freeze(vu1Thread.vuCycleIdx);
return IsOkay();
} }
VU_Thread::VU_Thread() VU_Thread::VU_Thread()

View File

@ -157,13 +157,16 @@ void Deci2Reset()
std::memset(deci2buffer, 0, sizeof(deci2buffer)); std::memset(deci2buffer, 0, sizeof(deci2buffer));
} }
void SaveStateBase::deci2Freeze() bool SaveStateBase::deci2Freeze()
{ {
FreezeTag( "deci2" ); if (!FreezeTag("deci2"))
return false;
Freeze( deci2addr ); Freeze( deci2addr );
Freeze( deci2handler ); Freeze( deci2handler );
Freeze( deci2buffer ); Freeze( deci2buffer );
return IsOkay();
} }
/* /*

View File

@ -19,12 +19,15 @@
#include "MTGS.h" #include "MTGS.h"
#include "SaveState.h" #include "SaveState.h"
void SaveStateBase::InputRecordingFreeze() bool SaveStateBase::InputRecordingFreeze()
{ {
// NOTE - BE CAREFUL // NOTE - BE CAREFUL
// CHANGING THIS WILL BREAK BACKWARDS COMPATIBILITY ON SAVESTATES // CHANGING THIS WILL BREAK BACKWARDS COMPATIBILITY ON SAVESTATES
FreezeTag("InputRecording"); if (!FreezeTag("InputRecording"))
return false;
Freeze(g_FrameCount); Freeze(g_FrameCount);
return IsOkay();
} }
#include "InputRecording.h" #include "InputRecording.h"

View File

@ -541,11 +541,14 @@ void SPRTOinterrupt()
hwDmacIrq(DMAC_TO_SPR); hwDmacIrq(DMAC_TO_SPR);
} }
void SaveStateBase::sprFreeze() bool SaveStateBase::sprFreeze()
{ {
FreezeTag("SPRdma"); if (!FreezeTag("SPRdma"))
return false;
Freeze(spr0finished); Freeze(spr0finished);
Freeze(spr1finished); Freeze(spr1finished);
Freeze(mfifotransferred); Freeze(mfifotransferred);
return IsOkay();
} }

View File

@ -39,6 +39,7 @@
#include "VUmicro.h" #include "VUmicro.h"
#include "ps2/BiosTools.h" #include "ps2/BiosTools.h"
#include "common/Error.h"
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/Path.h" #include "common/Path.h"
#include "common/SafeArray.inl" #include "common/SafeArray.inl"
@ -109,24 +110,35 @@ void SaveStateBase::Init( SafeArray<u8>* memblock )
m_memory = memblock; m_memory = memblock;
m_version = g_SaveVersion; m_version = g_SaveVersion;
m_idx = 0; m_idx = 0;
m_error = false;
} }
void SaveStateBase::PrepBlock( int size ) void SaveStateBase::PrepBlock( int size )
{ {
pxAssertDev( m_memory, "Savestate memory/buffer pointer is null!" ); pxAssertDev( m_memory, "Savestate memory/buffer pointer is null!" );
if (m_error)
return;
const int end = m_idx+size; const int end = m_idx+size;
if( IsSaving() ) if (IsSaving())
m_memory->MakeRoomFor( end ); {
m_memory->MakeRoomFor(end);
}
else else
{ {
if( m_memory->GetSizeInBytes() < end ) if (m_memory->GetSizeInBytes() < end)
throw Exception::SaveStateLoadError(); {
Console.Error("(SaveStateBase) Buffer overflow in PrepBlock(), expected %d got %d", end, m_memory->GetSizeInBytes());
m_error = true;
}
} }
} }
void SaveStateBase::FreezeTag(const char* src) bool SaveStateBase::FreezeTag(const char* src)
{ {
if (m_error)
return false;
char tagspace[32]; char tagspace[32];
pxAssertDev(std::strlen(src) < (sizeof(tagspace) - 1), "Tag name exceeds the allowed length"); pxAssertDev(std::strlen(src) < (sizeof(tagspace) - 1), "Tag name exceeds the allowed length");
@ -136,15 +148,18 @@ void SaveStateBase::FreezeTag(const char* src)
if (std::strcmp(tagspace, src) != 0) if (std::strcmp(tagspace, src) != 0)
{ {
std::string msg(fmt::format("Savestate data corruption detected while reading tag: {}", src)); Console.Error(fmt::format("Savestate data corruption detected while reading tag: {}", src));
pxFail(msg.c_str()); m_error = true;
throw Exception::SaveStateLoadError().SetDiagMsg(std::move(msg)); return false;
} }
return true;
} }
SaveStateBase& SaveStateBase::FreezeBios() bool SaveStateBase::FreezeBios()
{ {
FreezeTag( "BIOS" ); if (!FreezeTag("BIOS"))
return false;
// Check the BIOS, and issue a warning if the bios for this state // Check the BIOS, and issue a warning if the bios for this state
// doesn't match the bios currently being used (chances are it'll still // doesn't match the bios currently being used (chances are it'll still
@ -170,19 +185,23 @@ SaveStateBase& SaveStateBase::FreezeBios()
); );
} }
return *this; return IsOkay();
} }
SaveStateBase& SaveStateBase::FreezeInternals() bool SaveStateBase::FreezeInternals()
{ {
// Print this until the MTVU problem in gifPathFreeze is taken care of (rama) // Print this until the MTVU problem in gifPathFreeze is taken care of (rama)
if (THREAD_VU1) Console.Warning("MTVU speedhack is enabled, saved states may not be stable"); if (THREAD_VU1)
Console.Warning("MTVU speedhack is enabled, saved states may not be stable");
vmFreeze(); if (!vmFreeze())
return false;
// Second Block - Various CPU Registers and States // Second Block - Various CPU Registers and States
// ----------------------------------------------- // -----------------------------------------------
FreezeTag( "cpuRegs" ); if (!FreezeTag("cpuRegs"))
return false;
Freeze(cpuRegs); // cpu regs + COP0 Freeze(cpuRegs); // cpu regs + COP0
Freeze(psxRegs); // iop regs Freeze(psxRegs); // iop regs
Freeze(fpuRegs); Freeze(fpuRegs);
@ -192,7 +211,9 @@ SaveStateBase& SaveStateBase::FreezeInternals()
// Third Block - Cycle Timers and Events // Third Block - Cycle Timers and Events
// ------------------------------------- // -------------------------------------
FreezeTag( "Cycles" ); if (!FreezeTag("Cycles"))
return false;
Freeze(EEsCycle); Freeze(EEsCycle);
Freeze(EEoCycle); Freeze(EEoCycle);
Freeze(nextCounter); Freeze(nextCounter);
@ -202,39 +223,45 @@ SaveStateBase& SaveStateBase::FreezeInternals()
// Fourth Block - EE-related systems // Fourth Block - EE-related systems
// --------------------------------- // ---------------------------------
FreezeTag( "EE-Subsystems" ); if (!FreezeTag("EE-Subsystems"))
rcntFreeze(); return false;
gsFreeze();
vuMicroFreeze(); bool okay = rcntFreeze();
vuJITFreeze(); okay = okay && gsFreeze();
vif0Freeze(); okay = okay && vuMicroFreeze();
vif1Freeze(); okay = okay && vuJITFreeze();
sifFreeze(); okay = okay && vif0Freeze();
ipuFreeze(); okay = okay && vif1Freeze();
ipuDmaFreeze(); okay = okay && sifFreeze();
gifFreeze(); okay = okay && ipuFreeze();
gifDmaFreeze(); okay = okay && ipuDmaFreeze();
sprFreeze(); okay = okay && gifFreeze();
mtvuFreeze(); okay = okay && gifDmaFreeze();
okay = okay && sprFreeze();
okay = okay && mtvuFreeze();
if (!okay)
return false;
// Fifth Block - iop-related systems // Fifth Block - iop-related systems
// --------------------------------- // ---------------------------------
FreezeTag( "IOP-Subsystems" ); if (!FreezeTag("IOP-Subsystems"))
return false;
FreezeMem(iopMem->Sif, sizeof(iopMem->Sif)); // iop's sif memory (not really needed, but oh well) FreezeMem(iopMem->Sif, sizeof(iopMem->Sif)); // iop's sif memory (not really needed, but oh well)
psxRcntFreeze(); okay = okay && psxRcntFreeze();
sioFreeze(); okay = okay && sioFreeze();
sio2Freeze(); okay = okay && sio2Freeze();
cdrFreeze(); okay = okay && cdrFreeze();
cdvdFreeze(); okay = okay && cdvdFreeze();
// technically this is HLE BIOS territory, but we don't have enough such stuff // technically this is HLE BIOS territory, but we don't have enough such stuff
// to merit an HLE Bios sub-section... yet. // to merit an HLE Bios sub-section... yet.
deci2Freeze(); okay = okay && deci2Freeze();
InputRecordingFreeze(); okay = okay && InputRecordingFreeze();
return *this; return okay;
} }
@ -287,27 +314,17 @@ memLoadingState::memLoadingState( const SafeArray<u8>* load_from )
// Loading of state data from a memory buffer... // Loading of state data from a memory buffer...
void memLoadingState::FreezeMem( void* data, int size ) void memLoadingState::FreezeMem( void* data, int size )
{ {
if (m_error)
{
std::memset(data, 0, size);
return;
}
const u8* const src = m_memory->GetPtr(m_idx); const u8* const src = m_memory->GetPtr(m_idx);
m_idx += size; m_idx += size;
memcpy( data, src, size ); memcpy( data, src, size );
} }
std::string Exception::SaveStateLoadError::FormatDiagnosticMessage() const
{
std::string retval = "Savestate is corrupt or incomplete!\n";
Host::AddOSDMessage("Error: Savestate is corrupt or incomplete!", 15.0f);
_formatDiagMsg(retval);
return retval;
}
std::string Exception::SaveStateLoadError::FormatDisplayMessage() const
{
std::string retval = "The savestate cannot be loaded, as it appears to be corrupt or incomplete.\n";
Host::AddOSDMessage("Error: The savestate cannot be loaded, as it appears to be corrupt or incomplete.", 15.0f);
_formatUserMsg(retval);
return retval;
}
// Used to hold the current state backup (fullcopy of PS2 memory and subcomponents states). // Used to hold the current state backup (fullcopy of PS2 memory and subcomponents states).
//static VmStateBuffer state_buffer( L"Public Savestate Buffer" ); //static VmStateBuffer state_buffer( L"Public Savestate Buffer" );
@ -332,25 +349,10 @@ 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 GS{ "GS", SysState_MTGSFreeze }; static constexpr SysState_Component GS{ "GS", SysState_MTGSFreeze };
static bool SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp)
static 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"));
}
static void SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp)
{ {
if (!zf) if (!zf)
return; return true;
freezeData fP = { 0, nullptr }; freezeData fP = { 0, nullptr };
if (comp.freeze(FreezeAction::Size, &fP) != 0) if (comp.freeze(FreezeAction::Size, &fP) != 0)
@ -358,27 +360,57 @@ static void SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp)
Console.Indent().WriteLn("Loading %s", comp.name); Console.Indent().WriteLn("Loading %s", comp.name);
auto data = std::make_unique<u8[]>(fP.size); std::unique_ptr<u8[]> data;
fP.data = data.get(); if (fP.size > 0)
if (zip_fread(zf, data.get(), fP.size) != static_cast<zip_int64_t>(fP.size) || comp.freeze(FreezeAction::Load, &fP) != 0)
throw std::runtime_error(std::string(" * ") + comp.name + std::string(": Error loading state!\n"));
}
static void SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp)
{
freezeData fP = { 0, NULL };
if (comp.freeze(FreezeAction::Size, &fP) == 0)
{ {
const int size = fP.size; data = std::make_unique<u8[]>(fP.size);
writer.PrepBlock(size); fP.data = data.get();
SysState_ComponentFreezeOutRoot(writer.GetBlockPtr(), comp);
writer.CommitBlock(size); if (zip_fread(zf, data.get(), fP.size) != static_cast<zip_int64_t>(fP.size))
{
Console.Error(fmt::format("* {}: Failed to decompress save data", comp.name));
return false;
}
} }
return;
if (comp.freeze(FreezeAction::Load, &fP) != 0)
{
Console.Error(fmt::format("* {}: Failed to load freeze data", comp.name));
return false;
}
return true;
} }
static void SysState_ComponentFreezeInNew(zip_file_t* zf, const char* name, bool(*do_state_func)(StateWrapper&)) static bool SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Component comp)
{
freezeData fP = {};
if (comp.freeze(FreezeAction::Size, &fP) != 0)
{
Console.Error(fmt::format("* {}: Failed to get freeze size", comp.name));
return false;
}
if (fP.size == 0)
return true;
const int size = fP.size;
writer.PrepBlock(size);
Console.Indent().WriteLn("Saving %s", comp.name);
fP.data = writer.GetBlockPtr();
if (comp.freeze(FreezeAction::Save, &fP) != 0)
{
Console.Error(fmt::format("* {}: Failed to save freeze data", comp.name));
return false;
}
writer.CommitBlock(size);
return true;
}
static bool SysState_ComponentFreezeInNew(zip_file_t* zf, const char* name, bool(*do_state_func)(StateWrapper&))
{ {
// TODO: We could decompress on the fly here for a little bit more speed. // TODO: We could decompress on the fly here for a little bit more speed.
std::vector<u8> data; std::vector<u8> data;
@ -392,19 +424,16 @@ static void SysState_ComponentFreezeInNew(zip_file_t* zf, const char* name, bool
StateWrapper::ReadOnlyMemoryStream stream(data.empty() ? nullptr : data.data(), data.size()); StateWrapper::ReadOnlyMemoryStream stream(data.empty() ? nullptr : data.data(), data.size());
StateWrapper sw(&stream, StateWrapper::Mode::Read, g_SaveVersion); StateWrapper sw(&stream, StateWrapper::Mode::Read, g_SaveVersion);
// TODO: Get rid of the bloody exceptions. return do_state_func(sw);
if (!do_state_func(sw))
throw std::runtime_error(fmt::format(" * {}: Error loading state!", name));
} }
static void SysState_ComponentFreezeOutNew(SaveStateBase& writer, const char* name, u32 reserve, bool (*do_state_func)(StateWrapper&)) static bool SysState_ComponentFreezeOutNew(SaveStateBase& writer, const char* name, u32 reserve, bool (*do_state_func)(StateWrapper&))
{ {
StateWrapper::VectorMemoryStream stream(reserve); StateWrapper::VectorMemoryStream stream(reserve);
StateWrapper sw(&stream, StateWrapper::Mode::Write, g_SaveVersion); StateWrapper sw(&stream, StateWrapper::Mode::Write, g_SaveVersion);
// TODO: Get rid of the bloody exceptions.
if (!do_state_func(sw)) if (!do_state_func(sw))
throw std::runtime_error(fmt::format(" * {}: Error saving state!", name)); return false;
const int size = static_cast<int>(stream.GetBuffer().size()); const int size = static_cast<int>(stream.GetBuffer().size());
if (size > 0) if (size > 0)
@ -413,6 +442,8 @@ static void SysState_ComponentFreezeOutNew(SaveStateBase& writer, const char* na
std::memcpy(writer.GetBlockPtr(), stream.GetBuffer().data(), size); std::memcpy(writer.GetBlockPtr(), stream.GetBuffer().data(), size);
writer.CommitBlock(size); writer.CommitBlock(size);
} }
return true;
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -427,8 +458,8 @@ public:
virtual ~BaseSavestateEntry() = default; virtual ~BaseSavestateEntry() = default;
virtual const char* GetFilename() const = 0; virtual const char* GetFilename() const = 0;
virtual void FreezeIn(zip_file_t* zf) const = 0; virtual bool FreezeIn(zip_file_t* zf) const = 0;
virtual void FreezeOut(SaveStateBase& writer) const = 0; virtual bool FreezeOut(SaveStateBase& writer) const = 0;
virtual bool IsRequired() const = 0; virtual bool IsRequired() const = 0;
}; };
@ -439,8 +470,8 @@ protected:
virtual ~MemorySavestateEntry() = default; virtual ~MemorySavestateEntry() = default;
public: public:
virtual void FreezeIn(zip_file_t* zf) const; virtual bool FreezeIn(zip_file_t* zf) const;
virtual void FreezeOut(SaveStateBase& writer) const; virtual bool FreezeOut(SaveStateBase& writer) const;
virtual bool IsRequired() const { return true; } virtual bool IsRequired() const { return true; }
protected: protected:
@ -448,7 +479,7 @@ protected:
virtual u32 GetDataSize() const = 0; virtual u32 GetDataSize() const = 0;
}; };
void MemorySavestateEntry::FreezeIn(zip_file_t* zf) const bool MemorySavestateEntry::FreezeIn(zip_file_t* zf) const
{ {
const u32 expectedSize = GetDataSize(); const u32 expectedSize = GetDataSize();
const s64 bytesRead = zip_fread(zf, GetDataPtr(), expectedSize); const s64 bytesRead = zip_fread(zf, GetDataPtr(), expectedSize);
@ -457,11 +488,14 @@ void MemorySavestateEntry::FreezeIn(zip_file_t* zf) const
Console.WriteLn(Color_Yellow, " '%s' is incomplete (expected 0x%x bytes, loading only 0x%x bytes)", Console.WriteLn(Color_Yellow, " '%s' is incomplete (expected 0x%x bytes, loading only 0x%x bytes)",
GetFilename(), expectedSize, static_cast<u32>(bytesRead)); GetFilename(), expectedSize, static_cast<u32>(bytesRead));
} }
return true;
} }
void MemorySavestateEntry::FreezeOut(SaveStateBase& writer) const bool MemorySavestateEntry::FreezeOut(SaveStateBase& writer) const
{ {
writer.FreezeMem(GetDataPtr(), GetDataSize()); writer.FreezeMem(GetDataPtr(), GetDataSize());
return writer.IsOkay();
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -473,156 +507,156 @@ void MemorySavestateEntry::FreezeOut(SaveStateBase& writer) const
// cannot use static struct member initializers -- we need virtual functions that compute // cannot use static struct member initializers -- we need virtual functions that compute
// and resolve the addresses on-demand instead... --air // and resolve the addresses on-demand instead... --air
class SavestateEntry_EmotionMemory : public MemorySavestateEntry class SavestateEntry_EmotionMemory final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_EmotionMemory() = default; ~SavestateEntry_EmotionMemory() override = default;
const char* GetFilename() const { return "eeMemory.bin"; } const char* GetFilename() const override { return "eeMemory.bin"; }
u8* GetDataPtr() const { return eeMem->Main; } u8* GetDataPtr() const override { return eeMem->Main; }
uint GetDataSize() const { return sizeof(eeMem->Main); } uint GetDataSize() const override { return sizeof(eeMem->Main); }
virtual void FreezeIn(zip_file_t* zf) const virtual bool FreezeIn(zip_file_t* zf) const override
{ {
SysClearExecutionCache(); SysClearExecutionCache();
MemorySavestateEntry::FreezeIn(zf); return MemorySavestateEntry::FreezeIn(zf);
} }
}; };
class SavestateEntry_IopMemory : public MemorySavestateEntry class SavestateEntry_IopMemory final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_IopMemory() = default; ~SavestateEntry_IopMemory() override = default;
const char* GetFilename() const { return "iopMemory.bin"; } const char* GetFilename() const override { return "iopMemory.bin"; }
u8* GetDataPtr() const { return iopMem->Main; } u8* GetDataPtr() const override { return iopMem->Main; }
uint GetDataSize() const { return sizeof(iopMem->Main); } uint GetDataSize() const override { return sizeof(iopMem->Main); }
}; };
class SavestateEntry_HwRegs : public MemorySavestateEntry class SavestateEntry_HwRegs final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_HwRegs() = default; ~SavestateEntry_HwRegs() override = default;
const char* GetFilename() const { return "eeHwRegs.bin"; } const char* GetFilename() const override { return "eeHwRegs.bin"; }
u8* GetDataPtr() const { return eeHw; } u8* GetDataPtr() const override { return eeHw; }
uint GetDataSize() const { return sizeof(eeHw); } uint GetDataSize() const override { return sizeof(eeHw); }
}; };
class SavestateEntry_IopHwRegs : public MemorySavestateEntry class SavestateEntry_IopHwRegs final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_IopHwRegs() = default; ~SavestateEntry_IopHwRegs() = default;
const char* GetFilename() const { return "iopHwRegs.bin"; } const char* GetFilename() const override { return "iopHwRegs.bin"; }
u8* GetDataPtr() const { return iopHw; } u8* GetDataPtr() const override { return iopHw; }
uint GetDataSize() const { return sizeof(iopHw); } uint GetDataSize() const override { return sizeof(iopHw); }
}; };
class SavestateEntry_Scratchpad : public MemorySavestateEntry class SavestateEntry_Scratchpad final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_Scratchpad() = default; ~SavestateEntry_Scratchpad() = default;
const char* GetFilename() const { return "Scratchpad.bin"; } const char* GetFilename() const override { return "Scratchpad.bin"; }
u8* GetDataPtr() const { return eeMem->Scratch; } u8* GetDataPtr() const override { return eeMem->Scratch; }
uint GetDataSize() const { return sizeof(eeMem->Scratch); } uint GetDataSize() const override { return sizeof(eeMem->Scratch); }
}; };
class SavestateEntry_VU0mem : public MemorySavestateEntry class SavestateEntry_VU0mem final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_VU0mem() = default; ~SavestateEntry_VU0mem() = default;
const char* GetFilename() const { return "vu0Memory.bin"; } const char* GetFilename() const override { return "vu0Memory.bin"; }
u8* GetDataPtr() const { return vuRegs[0].Mem; } u8* GetDataPtr() const override { return vuRegs[0].Mem; }
uint GetDataSize() const { return VU0_MEMSIZE; } uint GetDataSize() const override { return VU0_MEMSIZE; }
}; };
class SavestateEntry_VU1mem : public MemorySavestateEntry class SavestateEntry_VU1mem final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_VU1mem() = default; ~SavestateEntry_VU1mem() = default;
const char* GetFilename() const { return "vu1Memory.bin"; } const char* GetFilename() const override { return "vu1Memory.bin"; }
u8* GetDataPtr() const { return vuRegs[1].Mem; } u8* GetDataPtr() const override { return vuRegs[1].Mem; }
uint GetDataSize() const { return VU1_MEMSIZE; } uint GetDataSize() const override { return VU1_MEMSIZE; }
}; };
class SavestateEntry_VU0prog : public MemorySavestateEntry class SavestateEntry_VU0prog final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_VU0prog() = default; ~SavestateEntry_VU0prog() = default;
const char* GetFilename() const { return "vu0MicroMem.bin"; } const char* GetFilename() const override { return "vu0MicroMem.bin"; }
u8* GetDataPtr() const { return vuRegs[0].Micro; } u8* GetDataPtr() const override { return vuRegs[0].Micro; }
uint GetDataSize() const { return VU0_PROGSIZE; } uint GetDataSize() const override { return VU0_PROGSIZE; }
}; };
class SavestateEntry_VU1prog : public MemorySavestateEntry class SavestateEntry_VU1prog final : public MemorySavestateEntry
{ {
public: public:
virtual ~SavestateEntry_VU1prog() = default; ~SavestateEntry_VU1prog() = default;
const char* GetFilename() const { return "vu1MicroMem.bin"; } const char* GetFilename() const override { return "vu1MicroMem.bin"; }
u8* GetDataPtr() const { return vuRegs[1].Micro; } u8* GetDataPtr() const override { return vuRegs[1].Micro; }
uint GetDataSize() const { return VU1_PROGSIZE; } uint GetDataSize() const override { return VU1_PROGSIZE; }
}; };
class SavestateEntry_SPU2 : public BaseSavestateEntry class SavestateEntry_SPU2 final : public BaseSavestateEntry
{ {
public: public:
virtual ~SavestateEntry_SPU2() = default; ~SavestateEntry_SPU2() override = default;
const char* GetFilename() const { return "SPU2.bin"; } const char* GetFilename() const override { return "SPU2.bin"; }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, SPU2_); } bool FreezeIn(zip_file_t* zf) const override { return SysState_ComponentFreezeIn(zf, SPU2_); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, SPU2_); } bool FreezeOut(SaveStateBase& writer) const override { return SysState_ComponentFreezeOut(writer, SPU2_); }
bool IsRequired() const { return true; } bool IsRequired() const override { return true; }
}; };
class SavestateEntry_USB : public BaseSavestateEntry class SavestateEntry_USB final : public BaseSavestateEntry
{ {
public: public:
virtual ~SavestateEntry_USB() = default; ~SavestateEntry_USB() override = default;
const char* GetFilename() const { return "USB.bin"; } const char* GetFilename() const override { return "USB.bin"; }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeInNew(zf, "USB", &USB::DoState); } bool FreezeIn(zip_file_t* zf) const override { return SysState_ComponentFreezeInNew(zf, "USB", &USB::DoState); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOutNew(writer, "USB", 16 * 1024, &USB::DoState); } bool FreezeOut(SaveStateBase& writer) const override { return SysState_ComponentFreezeOutNew(writer, "USB", 16 * 1024, &USB::DoState); }
bool IsRequired() const { return false; } bool IsRequired() const override { return false; }
}; };
class SavestateEntry_PAD : public BaseSavestateEntry class SavestateEntry_PAD final : public BaseSavestateEntry
{ {
public: public:
virtual ~SavestateEntry_PAD() = default; ~SavestateEntry_PAD() override = default;
const char* GetFilename() const { return "PAD.bin"; } const char* GetFilename() const override { return "PAD.bin"; }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, PAD_); } bool FreezeIn(zip_file_t* zf) const override { return SysState_ComponentFreezeIn(zf, PAD_); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, PAD_); } bool FreezeOut(SaveStateBase& writer) const override { return SysState_ComponentFreezeOut(writer, PAD_); }
bool IsRequired() const { return true; } bool IsRequired() const override { return true; }
}; };
class SavestateEntry_GS : public BaseSavestateEntry class SavestateEntry_GS final : public BaseSavestateEntry
{ {
public: public:
virtual ~SavestateEntry_GS() = default; ~SavestateEntry_GS() = default;
const char* GetFilename() const { return "GS.bin"; } const char* GetFilename() const { return "GS.bin"; }
void FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, GS); } bool FreezeIn(zip_file_t* zf) const { return SysState_ComponentFreezeIn(zf, GS); }
void FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, GS); } bool FreezeOut(SaveStateBase& writer) const { return SysState_ComponentFreezeOut(writer, GS); }
bool IsRequired() const { return true; } bool IsRequired() const { return true; }
}; };
#ifdef ENABLE_ACHIEVEMENTS #ifdef ENABLE_ACHIEVEMENTS
class SaveStateEntry_Achievements : public BaseSavestateEntry class SaveStateEntry_Achievements final : public BaseSavestateEntry
{ {
virtual ~SaveStateEntry_Achievements() override = default; ~SaveStateEntry_Achievements() override = default;
const char* GetFilename() const override { return "Achievements.bin"; } const char* GetFilename() const override { return "Achievements.bin"; }
void FreezeIn(zip_file_t* zf) const override bool FreezeIn(zip_file_t* zf) const override
{ {
if (!Achievements::IsActive()) if (!Achievements::IsActive())
return; return true;
std::optional<std::vector<u8>> data; std::optional<std::vector<u8>> data;
if (zf) if (zf)
@ -632,12 +666,14 @@ class SaveStateEntry_Achievements : public BaseSavestateEntry
Achievements::LoadState(data->data(), data->size()); Achievements::LoadState(data->data(), data->size());
else else
Achievements::LoadState(nullptr, 0); Achievements::LoadState(nullptr, 0);
return true;
} }
void FreezeOut(SaveStateBase& writer) const override bool FreezeOut(SaveStateBase& writer) const override
{ {
if (!Achievements::IsActive()) if (!Achievements::IsActive())
return; return true;
std::vector<u8> data(Achievements::SaveState()); std::vector<u8> data(Achievements::SaveState());
if (!data.empty()) if (!data.empty())
@ -646,6 +682,8 @@ class SaveStateEntry_Achievements : public BaseSavestateEntry
std::memcpy(writer.GetBlockPtr(), data.data(), data.size()); std::memcpy(writer.GetBlockPtr(), data.data(), data.size());
writer.CommitBlock(static_cast<int>(data.size())); writer.CommitBlock(static_cast<int>(data.size()));
} }
return writer.IsOkay();
} }
bool IsRequired() const override { return false; } bool IsRequired() const override { return false; }
@ -676,7 +714,7 @@ static const std::unique_ptr<BaseSavestateEntry> SavestateEntries[] = {
#endif #endif
}; };
std::unique_ptr<ArchiveEntryList> SaveState_DownloadState() std::unique_ptr<ArchiveEntryList> SaveState_DownloadState(Error* error)
{ {
std::unique_ptr<ArchiveEntryList> destlist = std::make_unique<ArchiveEntryList>(new VmStateBuffer("Zippable Savestate")); std::unique_ptr<ArchiveEntryList> destlist = std::make_unique<ArchiveEntryList>(new VmStateBuffer("Zippable Savestate"));
@ -684,8 +722,17 @@ std::unique_ptr<ArchiveEntryList> SaveState_DownloadState()
ArchiveEntry internals(EntryFilename_InternalStructures); ArchiveEntry internals(EntryFilename_InternalStructures);
internals.SetDataIndex(saveme.GetCurrentPos()); internals.SetDataIndex(saveme.GetCurrentPos());
saveme.FreezeBios(); if (!saveme.FreezeBios())
saveme.FreezeInternals(); {
Error::SetString(error, "FreezeBios() failed");
return nullptr;
}
if (!saveme.FreezeInternals())
{
Error::SetString(error, "FreezeInternals() failed");
return nullptr;
}
internals.SetDataSize(saveme.GetCurrentPos() - internals.GetDataIndex()); internals.SetDataSize(saveme.GetCurrentPos() - internals.GetDataIndex());
destlist->Add(internals); destlist->Add(internals);
@ -693,7 +740,13 @@ std::unique_ptr<ArchiveEntryList> SaveState_DownloadState()
for (const std::unique_ptr<BaseSavestateEntry>& entry : SavestateEntries) for (const std::unique_ptr<BaseSavestateEntry>& entry : SavestateEntries)
{ {
uint startpos = saveme.GetCurrentPos(); uint startpos = saveme.GetCurrentPos();
entry->FreezeOut(saveme); if (!entry->FreezeOut(saveme))
{
Error::SetString(error, fmt::format("FreezeOut() failed for {}.", entry->GetFilename()));
destlist.reset();
break;
}
destlist->Add( destlist->Add(
ArchiveEntry(entry->GetFilename()) ArchiveEntry(entry->GetFilename())
.SetDataIndex(startpos) .SetDataIndex(startpos)
@ -965,30 +1018,32 @@ bool SaveState_ReadScreenshot(const std::string& filename, u32* out_width, u32*
return SaveState_ReadScreenshot(zf.get(), out_width, out_height, out_pixels); return SaveState_ReadScreenshot(zf.get(), out_width, out_height, out_pixels);
} }
static void CheckVersion(const std::string& filename, zip_t* zf) static bool CheckVersion(const std::string& filename, zip_t* zf, Error* error)
{ {
u32 savever; u32 savever;
auto zff = zip_fopen_managed(zf, EntryFilename_StateVersion, 0); auto zff = zip_fopen_managed(zf, EntryFilename_StateVersion, 0);
if (!zff || zip_fread(zff.get(), &savever, sizeof(savever)) != sizeof(savever)) if (!zff || zip_fread(zff.get(), &savever, sizeof(savever)) != sizeof(savever))
{ {
throw Exception::SaveStateLoadError(filename) Error::SetString(error, "Savestate file does not contain version indicator.");
.SetDiagMsg("Savestate file does not contain version indicator.") return false;
.SetUserMsg("This file is not a valid PCSX2 savestate. See the logfile for details.");
} }
// Major version mismatch. Means we can't load this savestate at all. Support for it // Major version mismatch. Means we can't load this savestate at all. Support for it
// was removed entirely. // was removed entirely.
if (savever > g_SaveVersion)
throw Exception::SaveStateLoadError(filename)
.SetDiagMsg(fmt::format("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.\nOption 1: Download an older PCSX2 version from pcsx2.net and make a memcard save like on the physical PS2.\nOption 2: Delete the savestates.");
// check for a "minor" version incompatibility; which happens if the savestate being loaded is a newer 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. // than the emulator recognizes. 99% chance that trying to load it will just corrupt emulation or crash.
if ((savever >> 16) != (g_SaveVersion >> 16)) if (savever > g_SaveVersion || (savever >> 16) != (g_SaveVersion >> 16))
throw Exception::SaveStateLoadError(filename) {
.SetDiagMsg(fmt::format("Savestate uses an unknown savestate version.\n(PCSX2 ver={:x}, state ver={:x})", g_SaveVersion, savever)) Error::SetString(error, fmt::format("The state is an unsupported version. (PCSX2 ver={:x}, state ver={:x}).\n"
.SetUserMsg("Cannot load this savestate. The state is an unsupported version.\nOption 1: Download an older PCSX2 version from pcsx2.net and make a memcard save like on the physical PS2.\nOption 2: Delete the savestates.");} "Option 1: Download an older PCSX2 version from pcsx2.net and make a memcard save like on the physical PS2.\n"
"Option 2: Delete the savestates.",
g_SaveVersion, savever));
return false;
}
return true;
}
static zip_int64_t CheckFileExistsInState(zip_t* zf, const char* name, bool required) static zip_int64_t CheckFileExistsInState(zip_t* zf, const char* name, bool required)
{ {
@ -1022,11 +1077,17 @@ static bool LoadInternalStructuresState(zip_t* zf, s64 index)
if (zip_fread(zff.get(), buffer.GetPtr(), buffer.GetSizeInBytes()) != buffer.GetSizeInBytes()) if (zip_fread(zff.get(), buffer.GetPtr(), buffer.GetSizeInBytes()) != buffer.GetSizeInBytes())
return false; return false;
memLoadingState(buffer).FreezeBios().FreezeInternals(); memLoadingState state(buffer);
if (!state.FreezeBios())
return false;
if (!state.FreezeInternals())
return false;
return true; return true;
} }
void SaveState_UnzipFromDisk(const std::string& filename) bool SaveState_UnzipFromDisk(const std::string& filename, Error* error)
{ {
zip_error_t ze = {}; zip_error_t ze = {};
auto zf = zip_open_managed(filename.c_str(), ZIP_RDONLY, &ze); auto zf = zip_open_managed(filename.c_str(), ZIP_RDONLY, &ze);
@ -1034,69 +1095,63 @@ void SaveState_UnzipFromDisk(const std::string& filename)
{ {
Console.Error("Failed to open zip file '%s' for save state load: %s", filename.c_str(), zip_error_strerror(&ze)); Console.Error("Failed to open zip file '%s' for save state load: %s", filename.c_str(), zip_error_strerror(&ze));
if (zip_error_code_zip(&ze) == ZIP_ER_NOENT) if (zip_error_code_zip(&ze) == ZIP_ER_NOENT)
{ Error::SetString(error, "Savestate file does not exist.");
throw Exception::SaveStateLoadError(filename)
.SetDiagMsg("Savestate file does not exist.")
.SetUserMsg("This savestate cannot be loaded because the file does not exist.");
}
else else
{ Error::SetString(error, fmt::format("Savestate zip error: {}", zip_error_strerror(&ze)));
throw Exception::SaveStateLoadError(filename)
.SetDiagMsg("Savestate file is not a valid gzip archive.") return false;
.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.");
}
} }
// look for version and screenshot information in the zip stream: // look for version and screenshot information in the zip stream:
CheckVersion(filename, zf.get()); if (!CheckVersion(filename, zf.get(), error))
return false;
// check that all parts are included // check that all parts are included
const s64 internal_index = CheckFileExistsInState(zf.get(), EntryFilename_InternalStructures, true); const s64 internal_index = CheckFileExistsInState(zf.get(), EntryFilename_InternalStructures, true);
s64 entryIndices[std::size(SavestateEntries)]; s64 entryIndices[std::size(SavestateEntries)];
// Log any parts and pieces that are missing, and then generate an exception. // Log any parts and pieces that are missing, and then generate an exception.
bool throwIt = (internal_index < 0); bool allPresent = (internal_index >= 0);
for (u32 i = 0; i < std::size(SavestateEntries); i++) for (u32 i = 0; i < std::size(SavestateEntries); i++)
{ {
const bool required = SavestateEntries[i]->IsRequired(); const bool required = SavestateEntries[i]->IsRequired();
entryIndices[i] = CheckFileExistsInState(zf.get(), SavestateEntries[i]->GetFilename(), required); entryIndices[i] = CheckFileExistsInState(zf.get(), SavestateEntries[i]->GetFilename(), required);
if (entryIndices[i] < 0 && required) if (entryIndices[i] < 0 && required)
throwIt = true;
}
if (!throwIt)
{
PreLoadPrep();
throwIt = !LoadInternalStructuresState(zf.get(), internal_index);
}
if (!throwIt)
{
for (u32 i = 0; i < std::size(SavestateEntries); ++i)
{ {
if (entryIndices[i] < 0) allPresent = false;
{ break;
SavestateEntries[i]->FreezeIn(nullptr); }
continue; }
} if (!allPresent)
{
Error::SetString(error, "Some required components were not found or are incomplete.");
return false;
}
auto zff = zip_fopen_index_managed(zf.get(), entryIndices[i], 0); PreLoadPrep();
if (!zff)
{
throwIt = true;
break;
}
SavestateEntries[i]->FreezeIn(zff.get()); if (!LoadInternalStructuresState(zf.get(), internal_index))
{
Error::SetString(error, "Save state corruption in internal structures.");
return false;
}
for (u32 i = 0; i < std::size(SavestateEntries); ++i)
{
if (entryIndices[i] < 0)
{
SavestateEntries[i]->FreezeIn(nullptr);
continue;
}
auto zff = zip_fopen_index_managed(zf.get(), entryIndices[i], 0);
if (!zff || !SavestateEntries[i]->FreezeIn(zff.get()))
{
Error::SetString(error, fmt::format("Save state corruption in {}.", SavestateEntries[i]->GetFilename()));
return false;
} }
} }
if (throwIt)
{
throw Exception::SaveStateLoadError(filename)
.SetDiagMsg("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.");
}
PostLoadPrep(); PostLoadPrep();
return true;
} }

View File

@ -21,7 +21,8 @@
#include "System.h" #include "System.h"
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/Exceptions.h"
class Error;
enum class FreezeAction enum class FreezeAction
{ {
@ -62,11 +63,11 @@ class ArchiveEntryList;
// Wrappers to generate a save state compatible across all frontends. // Wrappers to generate a save state compatible across all frontends.
// These functions assume that the caller has paused the core thread. // These functions assume that the caller has paused the core thread.
extern std::unique_ptr<ArchiveEntryList> SaveState_DownloadState(); extern std::unique_ptr<ArchiveEntryList> SaveState_DownloadState(Error* error);
extern std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot(); extern std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot();
extern bool SaveState_ZipToDisk(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename); extern bool SaveState_ZipToDisk(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename);
extern bool SaveState_ReadScreenshot(const std::string& filename, u32* out_width, u32* out_height, std::vector<u32>* out_pixels); extern bool SaveState_ReadScreenshot(const std::string& filename, u32* out_width, u32* out_height, std::vector<u32>* out_pixels);
extern void SaveState_UnzipFromDisk(const std::string& filename); extern bool SaveState_UnzipFromDisk(const std::string& filename, Error* error);
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// SaveStateBase class // SaveStateBase class
@ -83,11 +84,16 @@ protected:
int m_idx; // current read/write index of the allocation int m_idx; // current read/write index of the allocation
bool m_error; // error occurred while reading/writing
public: public:
SaveStateBase( VmStateBuffer& memblock ); SaveStateBase( VmStateBuffer& memblock );
SaveStateBase( VmStateBuffer* memblock ); SaveStateBase( VmStateBuffer* memblock );
virtual ~SaveStateBase() { } virtual ~SaveStateBase() { }
__fi bool HasError() const { return m_error; }
__fi bool IsOkay() const { return !m_error; }
// Gets the version of savestate that this object is acting on. // 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) // The version refers to the low 16 bits only (high 16 bits classifies Pcsx2 build types)
u32 GetVersion() const u32 GetVersion() const
@ -95,8 +101,8 @@ public:
return (m_version & 0xffff); return (m_version & 0xffff);
} }
virtual SaveStateBase& FreezeBios(); bool FreezeBios();
virtual SaveStateBase& FreezeInternals(); bool FreezeInternals();
// Loads or saves an arbitrary data type. Usable on atomic types, structs, and arrays. // Loads or saves an arbitrary data type. Usable on atomic types, structs, and arrays.
// For dynamically allocated pointers use FreezeMem instead. // For dynamically allocated pointers use FreezeMem instead.
@ -182,7 +188,7 @@ public:
// Identifiers can be used to determine where in a savestate that data has become // Identifiers can be used to determine where in a savestate that data has become
// skewed (if the value does not match then the error occurs somewhere prior to that // skewed (if the value does not match then the error occurs somewhere prior to that
// position). // position).
void FreezeTag( const char* src ); bool FreezeTag( const char* src );
// Returns true if this object is a StateLoading type object. // Returns true if this object is a StateLoading type object.
bool IsLoading() const { return !IsSaving(); } bool IsLoading() const { return !IsSaving(); }
@ -195,41 +201,41 @@ public:
public: public:
// note: gsFreeze() needs to be public because of the GSState recorder. // note: gsFreeze() needs to be public because of the GSState recorder.
void gsFreeze(); bool gsFreeze();
protected: protected:
void Init( VmStateBuffer* memblock ); void Init( VmStateBuffer* memblock );
void vmFreeze(); bool vmFreeze();
void mtvuFreeze(); bool mtvuFreeze();
void rcntFreeze(); bool rcntFreeze();
void vuMicroFreeze(); bool vuMicroFreeze();
void vuJITFreeze(); bool vuJITFreeze();
void vif0Freeze(); bool vif0Freeze();
void vif1Freeze(); bool vif1Freeze();
void sifFreeze(); bool sifFreeze();
void ipuFreeze(); bool ipuFreeze();
void ipuDmaFreeze(); bool ipuDmaFreeze();
void gifFreeze(); bool gifFreeze();
void gifDmaFreeze(); bool gifDmaFreeze();
void gifPathFreeze(u32 path); // called by gifFreeze() bool gifPathFreeze(u32 path); // called by gifFreeze()
void sprFreeze(); bool sprFreeze();
void sioFreeze(); bool sioFreeze();
void cdrFreeze(); bool cdrFreeze();
void cdvdFreeze(); bool cdvdFreeze();
void psxRcntFreeze(); bool psxRcntFreeze();
void sio2Freeze(); bool sio2Freeze();
void deci2Freeze(); bool deci2Freeze();
// Save or load PCSX2's global frame counter (g_FrameCount) along with each savestate // Save or load PCSX2's global frame counter (g_FrameCount) along with each savestate
// //
// This is to prevent any inaccuracy issues caused by having a different // This is to prevent any inaccuracy issues caused by having a different
// internal emulation frame count than what it was at the beginning of the // internal emulation frame count than what it was at the beginning of the
// original recording // original recording
void InputRecordingFreeze(); bool InputRecordingFreeze();
}; };
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -387,16 +393,3 @@ public:
bool IsSaving() const { return false; } bool IsSaving() const { return false; }
bool IsFinished() const { return m_idx >= m_memory->GetSizeInBytes(); } bool IsFinished() const { return m_idx >= m_memory->GetSizeInBytes(); }
}; };
namespace Exception
{
// Exception thrown when a corrupted or truncated savestate is encountered.
class SaveStateLoadError : public BadStream
{
DEFINE_STREAM_EXCEPTION(SaveStateLoadError, BadStream)
virtual std::string FormatDiagnosticMessage() const override;
virtual std::string FormatDisplayMessage() const override;
};
}; // namespace Exception

View File

@ -27,10 +27,12 @@ void sifReset()
std::memset(&sif1, 0, sizeof(sif1)); std::memset(&sif1, 0, sizeof(sif1));
} }
void SaveStateBase::sifFreeze() bool SaveStateBase::sifFreeze()
{ {
FreezeTag("SIFdma"); if (!FreezeTag("SIFdma"))
return false;
Freeze(sif0); Freeze(sif0);
Freeze(sif1); Freeze(sif1);
return IsOkay();
} }

View File

@ -822,14 +822,18 @@ void sioSetGameSerial( const std::string& serial ) {
} }
} }
void SaveStateBase::sio2Freeze() bool SaveStateBase::sio2Freeze()
{ {
FreezeTag("sio2"); if (!FreezeTag("sio2"))
return false;
Freeze(sio2); Freeze(sio2);
FreezeDeque(fifoIn); FreezeDeque(fifoIn);
FreezeDeque(fifoOut); FreezeDeque(fifoOut);
if (!IsOkay())
return false;
// CRCs for memory cards. // CRCs for memory cards.
// If the memory card hasn't changed when loading state, we can safely skip ejecting it. // If the memory card hasn't changed when loading state, we can safely skip ejecting it.
u64 mcdCrcs[SIO::PORTS][SIO::SLOTS]; u64 mcdCrcs[SIO::PORTS][SIO::SLOTS];
@ -842,6 +846,8 @@ void SaveStateBase::sio2Freeze()
} }
} }
Freeze(mcdCrcs); Freeze(mcdCrcs);
if (!IsOkay())
return false;
if (IsLoading()) if (IsLoading())
{ {
@ -859,12 +865,17 @@ void SaveStateBase::sio2Freeze()
} }
} }
} }
return true;
} }
void SaveStateBase::sioFreeze() bool SaveStateBase::sioFreeze()
{ {
FreezeTag("sio0"); if (!FreezeTag("sio0"))
return false;
Freeze(sio0); Freeze(sio0);
return IsOkay();
} }
std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index) std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index)

View File

@ -52,6 +52,7 @@
#include "ps2/BiosTools.h" #include "ps2/BiosTools.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/Error.h"
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/ScopedGuard.h" #include "common/ScopedGuard.h"
#include "common/SettingsWrapper.h" #include "common/SettingsWrapper.h"
@ -1427,7 +1428,7 @@ void VMManager::Reset()
s_state.store(VMState::Running, std::memory_order_release); s_state.store(VMState::Running, std::memory_order_release);
} }
void SaveStateBase::vmFreeze() bool SaveStateBase::vmFreeze()
{ {
const u32 prev_crc = s_current_crc; const u32 prev_crc = s_current_crc;
const std::string prev_elf = s_elf_path; const std::string prev_elf = s_elf_path;
@ -1458,6 +1459,8 @@ void SaveStateBase::vmFreeze()
if (s_current_crc != prev_crc || s_elf_path != prev_elf || s_elf_executed != prev_elf_executed) if (s_current_crc != prev_crc || s_elf_path != prev_elf || s_elf_executed != prev_elf_executed)
VMManager::HandleELFChange(true); VMManager::HandleELFChange(true);
} }
return IsOkay();
} }
std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot) std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot)
@ -1506,24 +1509,23 @@ bool VMManager::DoLoadState(const char* filename)
if (GSDumpReplayer::IsReplayingDump()) if (GSDumpReplayer::IsReplayingDump())
return false; return false;
try Host::OnSaveStateLoading(filename);
Error error;
if (!SaveState_UnzipFromDisk(filename, &error))
{ {
Host::OnSaveStateLoading(filename); Host::ReportErrorAsync("Failed to load save state", error.GetDescription());
SaveState_UnzipFromDisk(filename);
Host::OnSaveStateLoaded(filename, true);
if (g_InputRecording.isActive())
{
g_InputRecording.handleLoadingSavestate();
MTGS::PresentCurrentFrame();
}
return true;
}
catch (Exception::BaseException& e)
{
Host::ReportErrorAsync("Failed to load save state", e.UserMsg());
Host::OnSaveStateLoaded(filename, false);
return false; return false;
} }
Host::OnSaveStateLoaded(filename, true);
if (g_InputRecording.isActive())
{
g_InputRecording.handleLoadingSavestate();
MTGS::PresentCurrentFrame();
}
return true;
} }
bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state) bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state)
@ -1532,47 +1534,46 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
return false; return false;
std::string osd_key(fmt::format("SaveStateSlot{}", slot_for_message)); std::string osd_key(fmt::format("SaveStateSlot{}", slot_for_message));
Error error;
try std::unique_ptr<ArchiveEntryList> elist = SaveState_DownloadState(&error);
{ if (!elist)
std::unique_ptr<ArchiveEntryList> elist(SaveState_DownloadState());
std::unique_ptr<SaveStateScreenshotData> screenshot(SaveState_SaveScreenshot());
if (FileSystem::FileExists(filename) && backup_old_state)
{
const std::string backup_filename(fmt::format("{}.backup", filename));
Console.WriteLn(fmt::format("Creating save state backup {}...", backup_filename));
if (!FileSystem::RenamePath(filename, backup_filename.c_str()))
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(
TRANSLATE_SV("VMManager", "Failed to back up old save state {}."), Path::GetFileName(filename)),
Host::OSD_ERROR_DURATION);
}
}
if (zip_on_thread)
{
// lock order here is important; the thread could exit before we resume here.
std::unique_lock lock(s_save_state_threads_mutex);
s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot),
std::move(osd_key), std::string(filename), slot_for_message);
}
else
{
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename, slot_for_message);
}
Host::OnSaveStateSaved(filename);
return true;
}
catch (Exception::BaseException& e)
{ {
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE, Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_SV("VMManager", "Failed to save save state: {}."), e.DiagMsg()), fmt::format(TRANSLATE_SV("VMManager", "Failed to save save state: {}."), error.GetDescription()),
Host::OSD_ERROR_DURATION); Host::OSD_ERROR_DURATION);
return false; return false;
} }
std::unique_ptr<SaveStateScreenshotData> screenshot = SaveState_SaveScreenshot();
if (FileSystem::FileExists(filename) && backup_old_state)
{
const std::string backup_filename(fmt::format("{}.backup", filename));
Console.WriteLn(fmt::format("Creating save state backup {}...", backup_filename));
if (!FileSystem::RenamePath(filename, backup_filename.c_str()))
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(
TRANSLATE_SV("VMManager", "Failed to back up old save state {}."), Path::GetFileName(filename)),
Host::OSD_ERROR_DURATION);
}
}
if (zip_on_thread)
{
// lock order here is important; the thread could exit before we resume here.
std::unique_lock lock(s_save_state_threads_mutex);
s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot),
std::move(osd_key), std::string(filename), slot_for_message);
}
else
{
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename, slot_for_message);
}
Host::OnSaveStateSaved(filename);
return true;
} }
void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist, void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,

View File

@ -84,12 +84,13 @@ void vuMemoryReserve::Reset()
VU1.VI[0].UL = 0; VU1.VI[0].UL = 0;
} }
void SaveStateBase::vuMicroFreeze() bool SaveStateBase::vuMicroFreeze()
{ {
if(IsSaving()) if(IsSaving())
vu1Thread.WaitVU(); vu1Thread.WaitVU();
FreezeTag( "vuMicroRegs" ); if (!FreezeTag("vuMicroRegs"))
return false;
// VU0 state information // VU0 state information
@ -175,4 +176,6 @@ void SaveStateBase::vuMicroFreeze()
Freeze(VU1.ialureadpos); Freeze(VU1.ialureadpos);
Freeze(VU1.ialuwritepos); Freeze(VU1.ialuwritepos);
Freeze(VU1.ialucount); Freeze(VU1.ialucount);
return IsOkay();
} }

View File

@ -44,9 +44,10 @@ void vif1Reset()
resetNewVif(1); resetNewVif(1);
} }
void SaveStateBase::vif0Freeze() bool SaveStateBase::vif0Freeze()
{ {
FreezeTag("VIF0dma"); if (!FreezeTag("VIF0dma"))
return false;
Freeze(g_vif0Cycles); Freeze(g_vif0Cycles);
@ -54,11 +55,14 @@ void SaveStateBase::vif0Freeze()
Freeze(nVif[0].bSize); Freeze(nVif[0].bSize);
FreezeMem(nVif[0].buffer, nVif[0].bSize); FreezeMem(nVif[0].buffer, nVif[0].bSize);
return IsOkay();
} }
void SaveStateBase::vif1Freeze() bool SaveStateBase::vif1Freeze()
{ {
FreezeTag("VIF1dma"); if (!FreezeTag("VIF1dma"))
return false;
Freeze(g_vif1Cycles); Freeze(g_vif1Cycles);
@ -66,6 +70,8 @@ void SaveStateBase::vif1Freeze()
Freeze(nVif[1].bSize); Freeze(nVif[1].bSize);
FreezeMem(nVif[1].buffer, nVif[1].bSize); FreezeMem(nVif[1].buffer, nVif[1].bSize);
return IsOkay();
} }
//------------------------------------------------------------------ //------------------------------------------------------------------

View File

@ -462,13 +462,14 @@ void recMicroVU1::ResumeXGkick()
((mVUrecCallXG)microVU1.startFunctXG)(); ((mVUrecCallXG)microVU1.startFunctXG)();
} }
void SaveStateBase::vuJITFreeze() bool SaveStateBase::vuJITFreeze()
{ {
if (IsSaving()) if (IsSaving())
vu1Thread.WaitVU(); vu1Thread.WaitVU();
Freeze(microVU0.prog.lpState); Freeze(microVU0.prog.lpState);
Freeze(microVU1.prog.lpState); Freeze(microVU1.prog.lpState);
return IsOkay();
} }
#if 0 #if 0