wxSavestates branch: (WIP, does not compile yet)

* Preliminary implementation of wx-based zip support (using wxZipInputStream and wxZipOutputStream).

git-svn-id: http://pcsx2.googlecode.com/svn/branches/wxSavestates@4033 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
Jake.Stine 2010-11-17 14:06:51 +00:00
parent e90fa1af8d
commit 5efe38b270
9 changed files with 334 additions and 287 deletions

View File

@ -95,7 +95,7 @@ extern bool SIGNAL_IMR_Pending;
__fi void gsInterrupt() __fi void gsInterrupt()
{ {
GIF_LOG("gsInterrupt: %8.8x", cpuRegs.cycle); GIF_LOG("gsInterrupt caught!");
if(SIGNAL_IMR_Pending == true) if(SIGNAL_IMR_Pending == true)
{ {

View File

@ -84,7 +84,6 @@ typedef int BOOL;
// unchanged for long periods of time, or happen to be used by almost everything, so they // unchanged for long periods of time, or happen to be used by almost everything, so they
// need a full recompile anyway, when modified (etc) // need a full recompile anyway, when modified (etc)
#include "zlib.h"
#include "Pcsx2Defs.h" #include "Pcsx2Defs.h"
#include "i18n.h" #include "i18n.h"

View File

@ -88,7 +88,7 @@ void SaveStateBase::PrepBlock( int size )
void SaveStateBase::FreezeTag( const char* src ) void SaveStateBase::FreezeTag( const char* src )
{ {
const uint allowedlen = sizeof( m_tagspace )-1; const uint allowedlen = sizeof( m_tagspace )-1;
pxAssertDev( strlen(src) < allowedlen, wxsFormat( L"Tag name exceeds the allowed length of %d chars.", allowedlen) ); pxAssertDev( strlen(src) < allowedlen, pxsFmt( L"Tag name exceeds the allowed length of %d chars.", allowedlen) );
memzero( m_tagspace ); memzero( m_tagspace );
strcpy( m_tagspace, src ); strcpy( m_tagspace, src );
@ -141,14 +141,13 @@ void SaveStateBase::FreezeBios()
m_DidBios = true; m_DidBios = true;
} }
static const int MainMemorySizeInBytes = static const uint MainMemorySizeInBytes =
Ps2MemSize::MainRam + Ps2MemSize::Scratch + Ps2MemSize::Hardware + Ps2MemSize::MainRam + Ps2MemSize::Scratch + Ps2MemSize::Hardware +
Ps2MemSize::IopRam + Ps2MemSize::IopHardware + 0x0100; Ps2MemSize::IopRam + Ps2MemSize::IopHardware;
void SaveStateBase::FreezeMainMemory() void SaveStateBase::FreezeMainMemory()
{ {
if( IsLoading() ) if (IsLoading()) PreLoadPrep();
PreLoadPrep();
// First Block - Memory Dumps // First Block - Memory Dumps
// --------------------------- // ---------------------------
@ -158,7 +157,6 @@ void SaveStateBase::FreezeMainMemory()
FreezeMem(iopMem->Main, Ps2MemSize::IopRam); // 2 MB main memory FreezeMem(iopMem->Main, Ps2MemSize::IopRam); // 2 MB main memory
FreezeMem(iopHw, Ps2MemSize::IopHardware); // hardware memory FreezeMem(iopHw, Ps2MemSize::IopHardware); // hardware memory
FreezeMem(iopMem->Sif, 0x000100); // iop's sif memory
} }
void SaveStateBase::FreezeRegisters() void SaveStateBase::FreezeRegisters()
@ -201,6 +199,8 @@ void SaveStateBase::FreezeRegisters()
// Fifth Block - iop-related systems // Fifth Block - iop-related systems
// --------------------------------- // ---------------------------------
FreezeTag( "IOP-Subsystems" ); FreezeTag( "IOP-Subsystems" );
FreezeMem(iopMem->Sif, sizeof(iopMem->Sif)); // iop's sif memory (not really needed, but oh well)
#ifdef ENABLE_NEW_IOPDMA #ifdef ENABLE_NEW_IOPDMA
iopDmacFreeze(); iopDmacFreeze();
#endif #endif
@ -231,13 +231,13 @@ void SaveStateBase::WritebackSectionLength( int seekpos, int sectlen, const wxCh
if( sectlen != realsectsize ) // if they don't match then we have a problem, jim. if( sectlen != realsectsize ) // if they don't match then we have a problem, jim.
{ {
throw Exception::SaveStateLoadError() throw Exception::SaveStateLoadError()
.SetDiagMsg(wxsFormat(L"Invalid size encountered on section '%s'.", sectname )) .SetDiagMsg(pxsFmt(L"Invalid size encountered on section '%s'.", sectname ))
.SetUserMsg(_("The savestate data is invalid or corrupted.")); .SetUserMsg(_("The savestate data is invalid or corrupted."));
} }
} }
} }
bool SaveStateBase::FreezeSection( int seek_section ) bool SaveStateBase::FreezeSection( bool freezeMem, int seek_section )
{ {
const bool isSeeking = (seek_section != FreezeId_NotSeeking ); const bool isSeeking = (seek_section != FreezeId_NotSeeking );
if( IsSaving() ) pxAssertDev( !isSeeking, "Cannot seek on a saving-mode savestate stream." ); if( IsSaving() ) pxAssertDev( !isSeeking, "Cannot seek on a saving-mode savestate stream." );
@ -272,6 +272,8 @@ bool SaveStateBase::FreezeSection( int seek_section )
break; break;
case FreezeId_Memory: case FreezeId_Memory:
{
if (freezeMem)
{ {
FreezeTag( "MainMemory" ); FreezeTag( "MainMemory" );
@ -292,6 +294,7 @@ bool SaveStateBase::FreezeSection( int seek_section )
int realsectsize = m_idx - seekpos; int realsectsize = m_idx - seekpos;
pxAssert( sectlen == realsectsize ); pxAssert( sectlen == realsectsize );
}
m_sectid++; m_sectid++;
} }
break; break;
@ -360,7 +363,7 @@ bool SaveStateBase::FreezeSection( int seek_section )
return true; return true;
} }
void SaveStateBase::FreezeAll() void SaveStateBase::FreezeAll( bool freezeMem )
{ {
if( IsSaving() ) if( IsSaving() )
{ {
@ -371,7 +374,7 @@ void SaveStateBase::FreezeAll()
m_pid = PluginId_GS; m_pid = PluginId_GS;
} }
while( FreezeSection() ); while( FreezeSection( freezeMem ) );
} }
////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////
@ -395,15 +398,14 @@ void memSavingState::FreezeMem( void* data, int size )
m_idx += size; m_idx += size;
} }
void memSavingState::FreezeAll() void memSavingState::FreezeAll( bool freezeMem )
{ {
pxAssumeDev( m_memory, "Savestate memory/buffer pointer is null!" ); pxAssumeDev( m_memory, "Savestate memory/buffer pointer is null!" );
// 90% of all savestates fit in under 45 megs (and require more than 43 megs, so might as well...)
m_memory->ChunkSize = ReallocThreshold; m_memory->ChunkSize = ReallocThreshold;
m_memory->MakeRoomFor( MemoryBaseAllocSize ); m_memory->MakeRoomFor( MemoryBaseAllocSize + (freezeMem ? MainMemorySizeInBytes : 0) );
_parent::FreezeAll(); _parent::FreezeAll( freezeMem );
} }
memLoadingState::memLoadingState( const SafeArray<u8>& load_from ) memLoadingState::memLoadingState( const SafeArray<u8>& load_from )
@ -452,22 +454,21 @@ bool memLoadingState::SeekToSection( PluginsEnum_t pid )
wxString Exception::UnsupportedStateVersion::FormatDiagnosticMessage() const wxString Exception::UnsupportedStateVersion::FormatDiagnosticMessage() const
{ {
// Note: no stacktrace needed for this one... // Note: no stacktrace needed for this one...
return wxsFormat( L"Unknown or unsupported savestate version: 0x%x", Version ); return pxsFmt( L"Unknown or unsupported savestate version: 0x%x", Version );
} }
wxString Exception::UnsupportedStateVersion::FormatDisplayMessage() const wxString Exception::UnsupportedStateVersion::FormatDisplayMessage() const
{ {
// m_message_user contains a recoverable savestate error which is helpful to the user. // m_message_user contains a recoverable savestate error which is helpful to the user.
return wxsFormat( return
m_message_user + L"\n\n" + m_message_user + L"\n\n" +
wxsFormat( _("Cannot load savestate. It is of an unknown or unsupported version."), Version ) pxsFmt( _("Cannot load savestate. It is of an unknown or unsupported version."), Version );
);
} }
wxString Exception::StateCrcMismatch::FormatDiagnosticMessage() const wxString Exception::StateCrcMismatch::FormatDiagnosticMessage() const
{ {
// Note: no stacktrace needed for this one... // Note: no stacktrace needed for this one...
return wxsFormat( return pxsFmt(
L"Game/CDVD does not match the savestate CRC.\n" L"Game/CDVD does not match the savestate CRC.\n"
L"\tCdvd CRC: 0x%X\n\tGame CRC: 0x%X\n", L"\tCdvd CRC: 0x%X\n\tGame CRC: 0x%X\n",
Crc_Savestate, Crc_Cdvd Crc_Savestate, Crc_Cdvd
@ -476,11 +477,10 @@ wxString Exception::StateCrcMismatch::FormatDiagnosticMessage() const
wxString Exception::StateCrcMismatch::FormatDisplayMessage() const wxString Exception::StateCrcMismatch::FormatDisplayMessage() const
{ {
return wxsFormat( return
m_message_user + L"\n\n" + m_message_user + L"\n\n" +
wxsFormat( pxsFmt(
L"Savestate game/crc mismatch. Cdvd CRC: 0x%X Game CRC: 0x%X\n", L"Savestate game/crc mismatch. Cdvd CRC: 0x%X Game CRC: 0x%X\n",
Crc_Savestate, Crc_Cdvd Crc_Savestate, Crc_Cdvd
)
); );
} }

View File

@ -24,7 +24,7 @@
// the lower 16 bit value. IF the change is breaking of all compatibility with old // the lower 16 bit value. IF the change is breaking of all compatibility with old
// states, increment the upper 16 bit value, and clear the lower 16 bits to 0. // states, increment the upper 16 bit value, and clear the lower 16 bits to 0.
static const u32 g_SaveVersion = 0x8b4c0000; static const u32 g_SaveVersion = (0x9A01 << 16) | 0x0000;
// this function is meant to be used in the place of GSfreeze, and provides a safe layer // 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. :) // between the GS saving function and the MTGS's needs. :)
@ -39,10 +39,8 @@ enum FreezeSectionId
// A BIOS tag should always be saved in conjunction with Memory or Registers tags, // A BIOS tag should always be saved in conjunction with Memory or Registers tags,
// but can be skipped if the savestate has only plugins. // but can be skipped if the savestate has only plugins.
FreezeId_Bios, FreezeId_Bios,
FreezeId_Memory, FreezeId_Memory,
FreezeId_Registers, FreezeId_Registers,
FreezeId_Plugin, FreezeId_Plugin,
// anything here and beyond we can skip, with a warning // anything here and beyond we can skip, with a warning
@ -144,7 +142,7 @@ public:
// Loads or saves the entire emulation state. // Loads or saves the entire emulation state.
// Note: The Cpu state must be reset, and plugins *open*, prior to Defrosting // Note: The Cpu state must be reset, and plugins *open*, prior to Defrosting
// (loading) a state! // (loading) a state!
virtual void FreezeAll(); virtual void FreezeAll( bool freezeMemory=true );
// 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.
@ -175,7 +173,7 @@ public:
} }
void WritebackSectionLength( int seekpos, int sectlen, const wxChar* sectname ); void WritebackSectionLength( int seekpos, int sectlen, const wxChar* sectname );
bool FreezeSection( int seek_section = FreezeId_NotSeeking ); bool FreezeSection( bool freezeMem, int seek_section = FreezeId_NotSeeking );
// Freezes an identifier value into the savestate for troubleshooting purposes. // Freezes an identifier value into the savestate for troubleshooting purposes.
// 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
@ -238,8 +236,8 @@ class memSavingState : public SaveStateBase
typedef SaveStateBase _parent; typedef SaveStateBase _parent;
protected: protected:
static const int ReallocThreshold = 0x200000; // 256k reallocation block size. static const int ReallocThreshold = _1mb / 4; // 256k reallocation block size.
static const int MemoryBaseAllocSize = 0x02b00000; // 45 meg base alloc static const int MemoryBaseAllocSize = _8mb; // 8 meg base alloc when PS2 main memory is excluded
public: public:
virtual ~memSavingState() throw() { } virtual ~memSavingState() throw() { }
@ -248,7 +246,7 @@ public:
// Saving of state data to a memory buffer // Saving of state data to a memory buffer
void FreezeMem( void* data, int size ); void FreezeMem( void* data, int size );
void FreezeAll(); void FreezeAll( bool freezeMemory );
bool IsSaving() const { return true; } bool IsSaving() const { return true; }
}; };

View File

@ -150,7 +150,6 @@ void SysCoreThread::UploadStateCopy( const VmStateBuffer& copy )
{ {
if( !pxAssertDev( IsPaused(), "CoreThread is not paused; new VM state cannot be uploaded." ) ) return; if( !pxAssertDev( IsPaused(), "CoreThread is not paused; new VM state cannot be uploaded." ) ) return;
SysClearExecutionCache();
memLoadingState( copy ).FreezeAll(); memLoadingState( copy ).FreezeAll();
m_resetVirtualMachine = false; m_resetVirtualMachine = false;
} }

View File

@ -16,92 +16,52 @@
#pragma once #pragma once
#include "Utilities/PersistentThread.h" #include "Utilities/PersistentThread.h"
//#include <zlib/zlib.h> #include "Utilities/pxStreams.h"
#include "wx/zipstrm.h"
using namespace Threading; using namespace Threading;
class IStreamWriter
{
public:
virtual ~IStreamWriter() throw() {}
virtual void Write( const void* data, size_t size )=0;
virtual wxString GetStreamName() const=0;
template< typename T >
void Write( const T& data )
{
Write( &data, sizeof(data) );
}
};
class IStreamReader
{
public:
virtual ~IStreamReader() throw() {}
virtual void Read( void* dest, size_t size )=0;
virtual wxString GetStreamName() const=0;
template< typename T >
void Read( T& dest )
{
Read( &dest, sizeof(dest) );
}
};
typedef void FnType_WriteCompressedHeader( IStreamWriter& thr );
typedef void FnType_ReadCompressedHeader( IStreamReader& thr );
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// BaseCompressThread // BaseCompressThread
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
class BaseCompressThread class BaseCompressThread
: public pxThread : public pxThread
, public IStreamWriter
{ {
typedef pxThread _parent; typedef pxThread _parent;
protected: protected:
FnType_WriteCompressedHeader* m_WriteHeaderInThread;
const wxString m_filename;
ScopedPtr< SafeArray< u8 > > m_src_buffer; ScopedPtr< SafeArray< u8 > > m_src_buffer;
ScopedPtr< pxStreamWriter > m_gzfp;
bool m_PendingSaveFlag; bool m_PendingSaveFlag;
BaseCompressThread( const wxString& file, SafeArray<u8>* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL) wxString m_final_filename;
: m_filename( file )
, m_src_buffer( srcdata ) BaseCompressThread( SafeArray<u8>* srcdata, pxStreamWriter* outarchive)
: m_src_buffer( srcdata )
{ {
m_WriteHeaderInThread = writeHeader; m_gzfp = outarchive;
m_PendingSaveFlag = false;
}
BaseCompressThread( SafeArray<u8>* srcdata, ScopedPtr<pxStreamWriter>& outarchive)
: m_src_buffer( srcdata )
{
m_gzfp = outarchive.DetachPtr();
m_PendingSaveFlag = false; m_PendingSaveFlag = false;
} }
virtual ~BaseCompressThread() throw(); virtual ~BaseCompressThread() throw();
void SetPendingSave(); void SetPendingSave();
public:
wxString GetStreamName() const { return m_filename; }
};
// --------------------------------------------------------------------------------------
// CompressThread_gzip
// --------------------------------------------------------------------------------------
class CompressThread_gzip : public BaseCompressThread
{
typedef BaseCompressThread _parent;
protected:
gzFile m_gzfp;
public:
CompressThread_gzip( const wxString& file, SafeArray<u8>* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL );
CompressThread_gzip( const wxString& file, ScopedPtr<SafeArray<u8> >& srcdata, FnType_WriteCompressedHeader* writeHeader=NULL );
virtual ~CompressThread_gzip() throw();
protected:
void Write( const void* data, size_t size );
void ExecuteTaskInThread(); void ExecuteTaskInThread();
void OnCleanupInThread(); void OnCleanupInThread();
public:
wxString GetStreamName() const { return m_gzfp->GetStreamName(); }
BaseCompressThread& SetTargetFilename(const wxString& filename)
{
m_final_filename = filename;
return *this;
}
}; };

View File

@ -19,10 +19,12 @@
#include "SaveState.h" #include "SaveState.h"
#include "ThreadedZipTools.h" #include "ThreadedZipTools.h"
#include "Utilities/SafeArray.inl" #include "Utilities/SafeArray.inl"
#include "wx/wfstream.h"
BaseCompressThread::~BaseCompressThread() throw() BaseCompressThread::~BaseCompressThread() throw()
{ {
_parent::Cancel();
if( m_PendingSaveFlag ) if( m_PendingSaveFlag )
{ {
wxGetApp().ClearPendingSave(); wxGetApp().ClearPendingSave();
@ -36,33 +38,7 @@ void BaseCompressThread::SetPendingSave()
m_PendingSaveFlag = true; m_PendingSaveFlag = true;
} }
CompressThread_gzip::CompressThread_gzip( const wxString& file, SafeArray<u8>* srcdata, FnType_WriteCompressedHeader* writeheader ) void BaseCompressThread::ExecuteTaskInThread()
: BaseCompressThread( file, srcdata, writeheader )
{
m_gzfp = NULL;
}
// Believe it or not, the space in > > is required.
CompressThread_gzip::CompressThread_gzip( const wxString& file, ScopedPtr< SafeArray<u8> >& srcdata, FnType_WriteCompressedHeader* writeheader )
: BaseCompressThread( file, srcdata.DetachPtr(), writeheader )
{
m_gzfp = NULL;
}
CompressThread_gzip::~CompressThread_gzip() throw()
{
_parent::Cancel();
if( m_gzfp ) gzclose( m_gzfp );
}
void CompressThread_gzip::Write( const void* data, size_t size )
{
if( gzwrite( m_gzfp, data, size ) == 0 )
throw Exception::BadStream( m_filename ).SetDiagMsg(L"Write to zip file failed.");
}
void CompressThread_gzip::ExecuteTaskInThread()
{ {
// TODO : Add an API to PersistentThread for this! :) --air // TODO : Add an API to PersistentThread for this! :) --air
//SetThreadPriority( THREAD_PRIORITY_BELOW_NORMAL ); //SetThreadPriority( THREAD_PRIORITY_BELOW_NORMAL );
@ -73,46 +49,31 @@ void CompressThread_gzip::ExecuteTaskInThread()
Yield( 3 ); Yield( 3 );
// Safeguard against corruption by writing to a temp file, and then
// copying the final result over the original:
wxString tempfile( m_filename + L".tmp" );
if( !(m_gzfp = gzopen(tempfile.ToUTF8(), "wb")) )
throw Exception::CannotCreateStream( m_filename );
gzsetparams(m_gzfp, Z_BEST_SPEED, Z_FILTERED); // Best speed at good compression
#if defined(ZLIB_VERNUM) && (ZLIB_VERNUM >= 0x1240)
gzbuffer(m_gzfp, 0x100000); // 1mb buffer size for less file fragments (Windows/NTFS)
#endif
if( m_WriteHeaderInThread )
m_WriteHeaderInThread( *this );
static const int BlockSize = 0x64000; static const int BlockSize = 0x64000;
int curidx = 0; int curidx = 0;
// Safeguard against corruption by writing to a temp file, and then
// copying the final result over the original:
do { do {
int thisBlockSize = std::min( BlockSize, m_src_buffer->GetSizeInBytes() - curidx ); int thisBlockSize = std::min( BlockSize, m_src_buffer->GetSizeInBytes() - curidx );
if( gzwrite( m_gzfp, m_src_buffer->GetPtr(curidx), thisBlockSize ) < thisBlockSize ) m_gzfp->Write(m_src_buffer->GetPtr(curidx), thisBlockSize);
throw Exception::BadStream( m_filename );
curidx += thisBlockSize; curidx += thisBlockSize;
Yield( 2 ); Yield( 2 );
} while( curidx < m_src_buffer->GetSizeInBytes() ); } while( curidx < m_src_buffer->GetSizeInBytes() );
gzclose( m_gzfp ); //m_gzfp->CloseEntry();
m_gzfp = NULL; m_gzfp->Close();
if( !wxRenameFile( tempfile, m_filename, true ) ) if( !wxRenameFile( m_gzfp->GetStreamName(), m_final_filename, true ) )
throw Exception::BadStream( m_filename ) throw Exception::BadStream( m_final_filename )
.SetDiagMsg(L"Failed to move or copy the temporary archive to the destination filename.") .SetDiagMsg(L"Failed to move or copy the temporary archive to the destination filename.")
.SetUserMsg(_("The savestate was not properly saved. The temporary file was created successfully but could not be moved to its final resting place.")); .SetUserMsg(_("The savestate was not properly saved. The temporary file was created successfully but could not be moved to its final resting place."));
Console.WriteLn( "(gzipThread) Data saved to disk without error." ); Console.WriteLn( "(gzipThread) Data saved to disk without error." );
} }
void CompressThread_gzip::OnCleanupInThread() void BaseCompressThread::OnCleanupInThread()
{ {
_parent::OnCleanupInThread(); _parent::OnCleanupInThread();
wxGetApp().DeleteThread( this ); wxGetApp().DeleteThread( this );

View File

@ -481,9 +481,6 @@ class GameDatabaseLoaderThread : public pxThread
{ {
typedef pxThread _parent; typedef pxThread _parent;
protected:
gzFile m_gzfp;
public: public:
GameDatabaseLoaderThread() GameDatabaseLoaderThread()
: pxThread( L"GameDatabaseLoader" ) : pxThread( L"GameDatabaseLoader" )

View File

@ -14,6 +14,7 @@
*/ */
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include "MemoryTypes.h"
#include "App.h" #include "App.h"
#include "System/SysThreads.h" #include "System/SysThreads.h"
@ -21,29 +22,93 @@
#include "ZipTools/ThreadedZipTools.h" #include "ZipTools/ThreadedZipTools.h"
#include "wx/wfstream.h"
// Used to hold the current state backup (fullcopy of PS2 memory and plugin states). // Used to hold the current state backup (fullcopy of PS2 memory and plugin states).
//static VmStateBuffer state_buffer( L"Public Savestate Buffer" ); //static VmStateBuffer state_buffer( L"Public Savestate Buffer" );
static const char SavestateIdentString[] = "PCSX2 Savestate"; static const wxChar* EntryFilename_StateVersion = L"PCSX2 Savestate Version.id";
static const uint SavestateIdentLen = sizeof(SavestateIdentString); static const wxChar* EntryFilename_Screenshot = L"Screenshot.jpg";
static const wxChar* EntryFilename_InternalStructures = L"PCSX2 Internal Structures.bin";
static void SaveStateFile_WriteHeader( IStreamWriter& thr ) class BaseSavestateEntry
{ {
thr.Write( SavestateIdentString ); public:
thr.Write( g_SaveVersion ); virtual const wxChar* GetFilename() const=0;
} virtual void* GetDataPtr() const=0;
virtual uint GetDataSize() const=0;
};
static void SaveStateFile_ReadHeader( IStreamReader& thr ) class SavestateEntry_EmotionMemory : public BaseSavestateEntry
{ {
char ident[SavestateIdentLen] = {0}; public:
const wxChar* GetFilename() const { return L"eeMemory.bin"; }
void* GetDataPtr() const { return eeMem->Main; }
uint GetDataSize() const { return sizeof(eeMem->Main); }
};
thr.Read( ident ); class SavestateEntry_IopMemory : public BaseSavestateEntry
{
public:
const wxChar* GetFilename() const { return L"iopMemory.bin"; }
void* GetDataPtr() const { return iopMem->Main; }
uint GetDataSize() const { return sizeof(iopMem->Main); }
};
if( strcmp(SavestateIdentString, ident) ) class SavestateEntry_HwRegs : public BaseSavestateEntry
throw Exception::SaveStateLoadError( thr.GetStreamName() ) {
.SetDiagMsg(wxsFormat( L"Unrecognized file signature while loading savestate.")) public:
.SetUserMsg(_("This is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2.")); const wxChar* GetFilename() const { return L"eeHwRegs.bin"; }
void* GetDataPtr() const { return eeHw; }
uint GetDataSize() const { return sizeof(eeHw); }
};
class SavestateEntry_IopHwRegs : public BaseSavestateEntry
{
public:
const wxChar* GetFilename() const { return L"iopHwRegs.bin"; }
void* GetDataPtr() const { return iopHw; }
uint GetDataSize() const { return sizeof(iopHw); }
};
class SavestateEntry_Scratchpad : public BaseSavestateEntry
{
public:
const wxChar* GetFilename() const { return L"Scratchpad.bin"; }
void* GetDataPtr() const { return eeMem->Scratch; }
uint GetDataSize() const { return sizeof(eeMem->Scratch); }
};
// [TODO] : Add other components as files to the savestate gzip?
// * VU0/VU1 memory banks? VU0prog, VU1prog, VU0data, VU1data.
// * GS register data?
// * Individual plugins?
// (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 BaseSavestateEntry* const SavestateEntries[] =
{
new SavestateEntry_EmotionMemory,
new SavestateEntry_IopMemory,
new SavestateEntry_HwRegs,
new SavestateEntry_IopHwRegs,
new SavestateEntry_Scratchpad,
};
static const uint NumSavestateEntries = ArraySize(SavestateEntries);
// 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( pxStreamReader& thr )
{
u32 savever; u32 savever;
thr.Read( savever ); thr.Read( savever );
@ -51,64 +116,17 @@ static void SaveStateFile_ReadHeader( IStreamReader& thr )
// was removed entirely. // was removed entirely.
if( savever > g_SaveVersion ) if( savever > g_SaveVersion )
throw Exception::SaveStateLoadError( thr.GetStreamName() ) throw Exception::SaveStateLoadError( thr.GetStreamName() )
.SetDiagMsg(wxsFormat( L"Savestate uses an unsupported or unknown savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever )) .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 from an incompatible edition of PCSX2 that is either newer than this version, or is no longer supported.")); .SetUserMsg(_("Cannot load this savestate. The state is from an incompatible edition of PCSX2 that is either newer than this version, or is no longer supported."));
// 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 >> 16) != (g_SaveVersion >> 16) )
throw Exception::SaveStateLoadError( thr.GetStreamName() ) throw Exception::SaveStateLoadError( thr.GetStreamName() )
.SetDiagMsg(wxsFormat( L"Savestate uses an unknown (future?!) savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever )) .SetDiagMsg(pxsFmt( L"Savestate uses an unknown (future?!) savestate version.\n(PCSX2 ver=%x, state ver=%x)", g_SaveVersion, savever ))
.SetUserMsg(_("Cannot load this savestate. The state is an unsupported version, likely created by a newer edition of PCSX2.")); .SetUserMsg(_("Cannot load this savestate. The state is an unsupported version, likely created by a newer edition of PCSX2."));
}; };
// --------------------------------------------------------------------------------------
// gzipReader
// --------------------------------------------------------------------------------------
// Interface for reading data from a gzip stream.
//
class gzipReader : public IStreamReader
{
DeclareNoncopyableObject(gzipReader);
protected:
wxString m_filename;
gzFile m_gzfp;
public:
gzipReader( const wxString& filename )
: m_filename( filename )
{
if( NULL == (m_gzfp = gzopen( m_filename.ToUTF8(), "rb" )) )
throw Exception::CannotCreateStream( m_filename ).SetDiagMsg(L"Cannot open file for reading.");
#if defined(ZLIB_VERNUM) && (ZLIB_VERNUM >= 0x1240)
gzbuffer(m_gzfp, 0x100000); // 1mb buffer for zlib internal operations
#endif
}
virtual ~gzipReader() throw ()
{
if( m_gzfp ) gzclose( m_gzfp );
}
wxString GetStreamName() const { return m_filename; }
void Read( void* dest, size_t size )
{
int result = gzread( m_gzfp, dest, size );
if( result == -1)
//throw Exception::gzError( m_filename );
throw Exception::BadStream( m_filename ).SetBothMsgs(wxLt("Data read failed: Invalid or corrupted gzip archive."));
if( (size_t)result < size )
throw Exception::EndOfStream( m_filename );
}
};
//static bool IsSavingOrLoading = false;
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// SysExecEvent_DownloadState // SysExecEvent_DownloadState
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -142,47 +160,39 @@ protected:
.SetDiagMsg(L"SysExecEvent_DownloadState: Cannot freeze/download an invalid VM state!") .SetDiagMsg(L"SysExecEvent_DownloadState: Cannot freeze/download an invalid VM state!")
.SetUserMsg(L"There is no active virtual machine state to download or save." ); .SetUserMsg(L"There is no active virtual machine state to download or save." );
memSavingState(m_dest_buffer).FreezeAll(); memSavingState( m_dest_buffer ).FreezeAll( false );
UI_EnableStateActions(); UI_EnableStateActions();
paused_core.AllowResume(); paused_core.AllowResume();
} }
}; };
// 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;
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// CompressThread_VmState // CompressThread_VmState
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
class VmStateZipThread : public CompressThread_gzip class VmStateCompressThread : public BaseCompressThread
{ {
typedef CompressThread_gzip _parent; typedef BaseCompressThread _parent;
protected: protected:
ScopedLock m_lock_Compress; ScopedLock m_lock_Compress;
public: public:
VmStateZipThread( const wxString& file, VmStateBuffer* srcdata ) VmStateCompressThread( VmStateBuffer* srcdata, pxStreamWriter* outarchive )
: _parent( file, srcdata, SaveStateFile_WriteHeader ) : _parent( srcdata, outarchive )
{ {
m_lock_Compress.Assign(mtx_CompressToDisk); m_lock_Compress.Assign(mtx_CompressToDisk);
} }
VmStateZipThread( const wxString& file, ScopedPtr<VmStateBuffer>& srcdata ) VmStateCompressThread( ScopedPtr<VmStateBuffer>& srcdata, ScopedPtr<pxStreamWriter>& outarchive )
: _parent( file, srcdata, SaveStateFile_WriteHeader ) : _parent( srcdata, outarchive )
{ {
m_lock_Compress.Assign(mtx_CompressToDisk); m_lock_Compress.Assign(mtx_CompressToDisk);
} }
virtual ~VmStateZipThread() throw() virtual ~VmStateCompressThread() throw()
{ {
} }
protected: protected:
@ -213,7 +223,6 @@ public:
virtual ~SysExecEvent_ZipToDisk() throw() virtual ~SysExecEvent_ZipToDisk() throw()
{ {
delete m_src_buffer;
} }
SysExecEvent_ZipToDisk* Clone() const { return new SysExecEvent_ZipToDisk( *this ); } SysExecEvent_ZipToDisk* Clone() const { return new SysExecEvent_ZipToDisk( *this ); }
@ -236,7 +245,46 @@ public:
protected: protected:
void InvokeEvent() void InvokeEvent()
{ {
(new VmStateZipThread( m_filename, m_src_buffer ))->Start(); wxString tempfile( m_filename + L".tmp" );
wxFFileOutputStream* woot = new wxFFileOutputStream(tempfile);
if (!woot->IsOk())
throw Exception::CannotCreateStream(tempfile);
// Write the version and screenshot:
ScopedPtr<pxStreamWriter> out( new pxStreamWriter(tempfile, new wxZipOutputStream(woot)) );
wxZipOutputStream* gzfp = (wxZipOutputStream*)out->GetBaseStream();
{
wxZipEntry* vent = new wxZipEntry(EntryFilename_StateVersion);
vent->SetMethod( wxZIP_METHOD_STORE );
gzfp->PutNextEntry( vent );
out->Write(g_SaveVersion);
gzfp->CloseEntry();
}
ScopedPtr<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();
}
//m_gzfp->PutNextEntry(EntryFilename_Screenshot);
//m_gzfp->Write();
//m_gzfp->CloseEntry();
(new VmStateCompressThread( m_src_buffer, out ))->
SetTargetFilename(m_filename).Start();
}
void CleanupEvent()
{
m_src_buffer = NULL; m_src_buffer = NULL;
} }
}; };
@ -269,42 +317,127 @@ protected:
void InvokeEvent() void InvokeEvent()
{ {
ScopedLock lock( mtx_CompressToDisk ); ScopedLock lock( mtx_CompressToDisk );
gzipReader m_gzreader(m_filename );
SaveStateFile_ReadHeader( m_gzreader ); // Ugh. Exception handling made crappy because wxWidgets classes don't support scoped pointers yet.
ScopedPtr<wxFFileInputStream> woot( new wxFFileInputStream(m_filename) );
if (!woot->IsOk())
throw Exception::CannotCreateStream( m_filename ).SetDiagMsg(L"Cannot open file for reading.");
ScopedPtr<pxStreamReader> reader( new pxStreamReader(m_filename, new wxZipInputStream(woot)) );
woot.DetachPtr();
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->GetBaseStream();
// look for version and screenshot information in the zip stream:
bool foundVersion = false;
//bool foundScreenshot = false;
//bool foundEntry[NumSavestateEntries] = false;
ScopedPtr<wxZipEntry> foundInternal;
ScopedPtr<wxZipEntry> foundEntry[NumSavestateEntries];
while(true)
{
Threading::pxTestCancel();
ScopedPtr<wxZipEntry> entry( gzreader->GetNextEntry() );
if (!entry) break;
if (entry->GetName().CmpNoCase(EntryFilename_StateVersion) == 0)
{
DevCon.WriteLn(L" ... found '%s'", EntryFilename_StateVersion);
foundVersion = true;
CheckVersion(*reader);
}
if (entry->GetName().CmpNoCase(EntryFilename_InternalStructures) == 0)
{
DevCon.WriteLn(L" ... found '%s'", EntryFilename_InternalStructures);
foundInternal = entry.DetachPtr();
}
// 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<NumSavestateEntries; ++i)
{
if (entry->GetName().CmpNoCase(SavestateEntries[i]->GetFilename()) == 0)
{
DevCon.WriteLn( Color_Green, L" ... found '%s'", SavestateEntries[i]->GetFilename() );
foundEntry[i] = entry.DetachPtr();
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<NumSavestateEntries; ++i)
{
if (foundEntry[i]) continue;
throwIt = true;
Console.WriteLn( Color_Red, " ... not found '%s'!", 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 // We use direct Suspend/Resume control here, since it's desirable that emulation
// *ALWAYS* start execution after the new savestate is loaded. // *ALWAYS* start execution after the new savestate is loaded.
GetCoreThread().Pause(); GetCoreThread().Pause();
// fixme: should start initially with the file size, and then grow from there. for (uint i=0; i<NumSavestateEntries; ++i)
static const int BlockSize = 0x100000;
VmStateBuffer buffer( 0x800000, L"StateBuffer_UnzipFromDisk" ); // start with an 8 meg buffer to avoid frequent reallocation.
int curidx = 0;
try {
while(true) {
buffer.MakeRoomFor( curidx+BlockSize );
m_gzreader.Read( buffer.GetPtr(curidx), BlockSize );
curidx += BlockSize;
Threading::pxTestCancel();
}
}
catch( Exception::EndOfStream& )
{ {
// This exception actually means success! Any others we let get sent Threading::pxTestCancel();
// to the main event handler/thread for handling.
gzreader->OpenEntry( *foundEntry[i] );
const uint entrySize = foundEntry[i]->GetSize();
const uint expectedSize = SavestateEntries[i]->GetDataSize();
if (entrySize < expectedSize)
{
Console.WriteLn( Color_Yellow, " '%s' is incomplete (expected 0x%x bytes, loading only 0x%x bytes)",
SavestateEntries[i]->GetFilename(), expectedSize, entrySize );
} }
// Optional shutdown of plugins when loading states? I'm not implementing it yet because some uint copylen = std::min(entrySize, expectedSize);
// things, like the SPU2-recovery trick, rely on not resetting the plugins prior to loading reader->Read( SavestateEntries[i]->GetDataPtr(), copylen );
// the new savestate data. }
//if( ShutdownOnStateLoad ) GetCoreThread().Cancel();
// Load all the internal data
GetCoreThread().UploadStateCopy( buffer ); 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() );
//GetCoreThread().UploadStateCopy( buffer );
memLoadingState( buffer ).FreezeAll( false );
GetCoreThread().Resume(); // force resume regardless of emulation state earlier. GetCoreThread().Resume(); // force resume regardless of emulation state earlier.
} }
}; };