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()
{
GIF_LOG("gsInterrupt: %8.8x", cpuRegs.cycle);
GIF_LOG("gsInterrupt caught!");
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
// need a full recompile anyway, when modified (etc)
#include "zlib.h"
#include "Pcsx2Defs.h"
#include "i18n.h"

View File

@ -88,7 +88,7 @@ void SaveStateBase::PrepBlock( int size )
void SaveStateBase::FreezeTag( const char* src )
{
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 );
strcpy( m_tagspace, src );
@ -141,24 +141,22 @@ void SaveStateBase::FreezeBios()
m_DidBios = true;
}
static const int MainMemorySizeInBytes =
Ps2MemSize::MainRam + Ps2MemSize::Scratch + Ps2MemSize::Hardware +
Ps2MemSize::IopRam + Ps2MemSize::IopHardware + 0x0100;
static const uint MainMemorySizeInBytes =
Ps2MemSize::MainRam + Ps2MemSize::Scratch + Ps2MemSize::Hardware +
Ps2MemSize::IopRam + Ps2MemSize::IopHardware;
void SaveStateBase::FreezeMainMemory()
{
if( IsLoading() )
PreLoadPrep();
if (IsLoading()) PreLoadPrep();
// First Block - Memory Dumps
// ---------------------------
FreezeMem(eeMem->Main, Ps2MemSize::MainRam); // 32 MB main memory
FreezeMem(eeMem->Scratch, Ps2MemSize::Scratch); // scratch pad
FreezeMem(eeHw, Ps2MemSize::Hardware); // hardware memory
FreezeMem(eeMem->Scratch, Ps2MemSize::Scratch); // scratch pad
FreezeMem(eeHw, Ps2MemSize::Hardware); // hardware memory
FreezeMem(iopMem->Main, Ps2MemSize::IopRam); // 2 MB main memory
FreezeMem(iopHw, Ps2MemSize::IopHardware); // hardware memory
FreezeMem(iopMem->Sif, 0x000100); // iop's sif memory
FreezeMem(iopMem->Main, Ps2MemSize::IopRam); // 2 MB main memory
FreezeMem(iopHw, Ps2MemSize::IopHardware); // hardware memory
}
void SaveStateBase::FreezeRegisters()
@ -201,6 +199,8 @@ void SaveStateBase::FreezeRegisters()
// Fifth Block - iop-related systems
// ---------------------------------
FreezeTag( "IOP-Subsystems" );
FreezeMem(iopMem->Sif, sizeof(iopMem->Sif)); // iop's sif memory (not really needed, but oh well)
#ifdef ENABLE_NEW_IOPDMA
iopDmacFreeze();
#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.
{
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."));
}
}
}
bool SaveStateBase::FreezeSection( int seek_section )
bool SaveStateBase::FreezeSection( bool freezeMem, int seek_section )
{
const bool isSeeking = (seek_section != FreezeId_NotSeeking );
if( IsSaving() ) pxAssertDev( !isSeeking, "Cannot seek on a saving-mode savestate stream." );
@ -273,25 +273,28 @@ bool SaveStateBase::FreezeSection( int seek_section )
case FreezeId_Memory:
{
FreezeTag( "MainMemory" );
int seekpos = m_idx+4;
int sectlen = MainMemorySizeInBytes;
Freeze( sectlen );
if( sectlen != MainMemorySizeInBytes )
if (freezeMem)
{
throw Exception::SaveStateLoadError()
.SetDiagMsg(L"Invalid size encountered on MainMemory section.")
.SetUserMsg(_("The savestate data is invalid or corrupted."));
FreezeTag( "MainMemory" );
int seekpos = m_idx+4;
int sectlen = MainMemorySizeInBytes;
Freeze( sectlen );
if( sectlen != MainMemorySizeInBytes )
{
throw Exception::SaveStateLoadError()
.SetDiagMsg(L"Invalid size encountered on MainMemory section.")
.SetUserMsg(_("The savestate data is invalid or corrupted."));
}
if( isSeeking )
m_idx += sectlen;
else
FreezeMainMemory();
int realsectsize = m_idx - seekpos;
pxAssert( sectlen == realsectsize );
}
if( isSeeking )
m_idx += sectlen;
else
FreezeMainMemory();
int realsectsize = m_idx - seekpos;
pxAssert( sectlen == realsectsize );
m_sectid++;
}
break;
@ -360,7 +363,7 @@ bool SaveStateBase::FreezeSection( int seek_section )
return true;
}
void SaveStateBase::FreezeAll()
void SaveStateBase::FreezeAll( bool freezeMem )
{
if( IsSaving() )
{
@ -371,7 +374,7 @@ void SaveStateBase::FreezeAll()
m_pid = PluginId_GS;
}
while( FreezeSection() );
while( FreezeSection( freezeMem ) );
}
//////////////////////////////////////////////////////////////////////////////////
@ -395,15 +398,14 @@ void memSavingState::FreezeMem( void* data, int size )
m_idx += size;
}
void memSavingState::FreezeAll()
void memSavingState::FreezeAll( bool freezeMem )
{
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->MakeRoomFor( MemoryBaseAllocSize );
m_memory->MakeRoomFor( MemoryBaseAllocSize + (freezeMem ? MainMemorySizeInBytes : 0) );
_parent::FreezeAll();
_parent::FreezeAll( freezeMem );
}
memLoadingState::memLoadingState( const SafeArray<u8>& load_from )
@ -452,22 +454,21 @@ bool memLoadingState::SeekToSection( PluginsEnum_t pid )
wxString Exception::UnsupportedStateVersion::FormatDiagnosticMessage() const
{
// 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
{
// m_message_user contains a recoverable savestate error which is helpful to the user.
return wxsFormat(
return
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
{
// Note: no stacktrace needed for this one...
return wxsFormat(
return pxsFmt(
L"Game/CDVD does not match the savestate CRC.\n"
L"\tCdvd CRC: 0x%X\n\tGame CRC: 0x%X\n",
Crc_Savestate, Crc_Cdvd
@ -476,11 +477,10 @@ wxString Exception::StateCrcMismatch::FormatDiagnosticMessage() const
wxString Exception::StateCrcMismatch::FormatDisplayMessage() const
{
return wxsFormat(
return
m_message_user + L"\n\n" +
wxsFormat(
pxsFmt(
L"Savestate game/crc mismatch. Cdvd CRC: 0x%X Game CRC: 0x%X\n",
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
// 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
// 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,
// but can be skipped if the savestate has only plugins.
FreezeId_Bios,
FreezeId_Memory,
FreezeId_Registers,
FreezeId_Plugin,
// anything here and beyond we can skip, with a warning
@ -144,7 +142,7 @@ public:
// Loads or saves the entire emulation state.
// Note: The Cpu state must be reset, and plugins *open*, prior to Defrosting
// (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.
// For dynamically allocated pointers use FreezeMem instead.
@ -175,7 +173,7 @@ public:
}
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.
// 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;
protected:
static const int ReallocThreshold = 0x200000; // 256k reallocation block size.
static const int MemoryBaseAllocSize = 0x02b00000; // 45 meg base alloc
static const int ReallocThreshold = _1mb / 4; // 256k reallocation block size.
static const int MemoryBaseAllocSize = _8mb; // 8 meg base alloc when PS2 main memory is excluded
public:
virtual ~memSavingState() throw() { }
@ -248,7 +246,7 @@ public:
// Saving of state data to a memory buffer
void FreezeMem( void* data, int size );
void FreezeAll();
void FreezeAll( bool freezeMemory );
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;
SysClearExecutionCache();
memLoadingState( copy ).FreezeAll();
m_resetVirtualMachine = false;
}

View File

@ -16,92 +16,52 @@
#pragma once
#include "Utilities/PersistentThread.h"
//#include <zlib/zlib.h>
#include "Utilities/pxStreams.h"
#include "wx/zipstrm.h"
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
// --------------------------------------------------------------------------------------
class BaseCompressThread
: public pxThread
, public IStreamWriter
{
typedef pxThread _parent;
protected:
FnType_WriteCompressedHeader* m_WriteHeaderInThread;
const wxString m_filename;
ScopedPtr< SafeArray< u8 > > m_src_buffer;
ScopedPtr< pxStreamWriter > m_gzfp;
bool m_PendingSaveFlag;
wxString m_final_filename;
BaseCompressThread( const wxString& file, SafeArray<u8>* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL)
: m_filename( file )
, m_src_buffer( srcdata )
BaseCompressThread( SafeArray<u8>* srcdata, pxStreamWriter* outarchive)
: m_src_buffer( srcdata )
{
m_WriteHeaderInThread = writeHeader;
m_PendingSaveFlag = false;
m_gzfp = outarchive;
m_PendingSaveFlag = false;
}
BaseCompressThread( SafeArray<u8>* srcdata, ScopedPtr<pxStreamWriter>& outarchive)
: m_src_buffer( srcdata )
{
m_gzfp = outarchive.DetachPtr();
m_PendingSaveFlag = false;
}
virtual ~BaseCompressThread() throw();
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 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 "ThreadedZipTools.h"
#include "Utilities/SafeArray.inl"
#include "wx/wfstream.h"
BaseCompressThread::~BaseCompressThread() throw()
{
_parent::Cancel();
if( m_PendingSaveFlag )
{
wxGetApp().ClearPendingSave();
@ -36,33 +38,7 @@ void BaseCompressThread::SetPendingSave()
m_PendingSaveFlag = true;
}
CompressThread_gzip::CompressThread_gzip( const wxString& file, SafeArray<u8>* srcdata, FnType_WriteCompressedHeader* writeheader )
: 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()
void BaseCompressThread::ExecuteTaskInThread()
{
// TODO : Add an API to PersistentThread for this! :) --air
//SetThreadPriority( THREAD_PRIORITY_BELOW_NORMAL );
@ -73,46 +49,31 @@ void CompressThread_gzip::ExecuteTaskInThread()
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;
int curidx = 0;
// Safeguard against corruption by writing to a temp file, and then
// copying the final result over the original:
do {
int thisBlockSize = std::min( BlockSize, m_src_buffer->GetSizeInBytes() - curidx );
if( gzwrite( m_gzfp, m_src_buffer->GetPtr(curidx), thisBlockSize ) < thisBlockSize )
throw Exception::BadStream( m_filename );
m_gzfp->Write(m_src_buffer->GetPtr(curidx), thisBlockSize);
curidx += thisBlockSize;
Yield( 2 );
} while( curidx < m_src_buffer->GetSizeInBytes() );
gzclose( m_gzfp );
m_gzfp = NULL;
//m_gzfp->CloseEntry();
m_gzfp->Close();
if( !wxRenameFile( tempfile, m_filename, true ) )
throw Exception::BadStream( m_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."));
if( !wxRenameFile( m_gzfp->GetStreamName(), m_final_filename, true ) )
throw Exception::BadStream( m_final_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."));
Console.WriteLn( "(gzipThread) Data saved to disk without error." );
}
void CompressThread_gzip::OnCleanupInThread()
void BaseCompressThread::OnCleanupInThread()
{
_parent::OnCleanupInThread();
wxGetApp().DeleteThread( this );

View File

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

View File

@ -14,6 +14,7 @@
*/
#include "PrecompiledHeader.h"
#include "MemoryTypes.h"
#include "App.h"
#include "System/SysThreads.h"
@ -21,29 +22,93 @@
#include "ZipTools/ThreadedZipTools.h"
#include "wx/wfstream.h"
// Used to hold the current state backup (fullcopy of PS2 memory and plugin states).
//static VmStateBuffer state_buffer( L"Public Savestate Buffer" );
static const char SavestateIdentString[] = "PCSX2 Savestate";
static const uint SavestateIdentLen = sizeof(SavestateIdentString);
static const wxChar* EntryFilename_StateVersion = L"PCSX2 Savestate Version.id";
static const wxChar* EntryFilename_Screenshot = L"Screenshot.jpg";
static const wxChar* EntryFilename_InternalStructures = L"PCSX2 Internal Structures.bin";
static void SaveStateFile_WriteHeader( IStreamWriter& thr )
class BaseSavestateEntry
{
thr.Write( SavestateIdentString );
thr.Write( g_SaveVersion );
}
public:
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) )
throw Exception::SaveStateLoadError( thr.GetStreamName() )
.SetDiagMsg(wxsFormat( L"Unrecognized file signature while loading savestate."))
.SetUserMsg(_("This is not a valid PCSX2 savestate, or is from an older unsupported version of PCSX2."));
class SavestateEntry_HwRegs : public BaseSavestateEntry
{
public:
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;
thr.Read( savever );
@ -51,64 +116,17 @@ static void SaveStateFile_ReadHeader( IStreamReader& thr )
// was removed entirely.
if( savever > g_SaveVersion )
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 ))
.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."));
.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."));
// check for a "minor" version incompatibility; which happens if the savestate being loaded is a newer version
// than the emulator recognizes. 99% chance that trying to load it will just corrupt emulation or crash.
if( (savever >> 16) != (g_SaveVersion >> 16) )
throw Exception::SaveStateLoadError( thr.GetStreamName() )
.SetDiagMsg(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."));
};
// --------------------------------------------------------------------------------------
// 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
// --------------------------------------------------------------------------------------
@ -142,47 +160,39 @@ protected:
.SetDiagMsg(L"SysExecEvent_DownloadState: Cannot freeze/download an invalid VM state!")
.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();
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
// --------------------------------------------------------------------------------------
class VmStateZipThread : public CompressThread_gzip
class VmStateCompressThread : public BaseCompressThread
{
typedef CompressThread_gzip _parent;
typedef BaseCompressThread _parent;
protected:
ScopedLock m_lock_Compress;
public:
VmStateZipThread( const wxString& file, VmStateBuffer* srcdata )
: _parent( file, srcdata, SaveStateFile_WriteHeader )
VmStateCompressThread( VmStateBuffer* srcdata, pxStreamWriter* outarchive )
: _parent( srcdata, outarchive )
{
m_lock_Compress.Assign(mtx_CompressToDisk);
}
VmStateZipThread( const wxString& file, ScopedPtr<VmStateBuffer>& srcdata )
: _parent( file, srcdata, SaveStateFile_WriteHeader )
VmStateCompressThread( ScopedPtr<VmStateBuffer>& srcdata, ScopedPtr<pxStreamWriter>& outarchive )
: _parent( srcdata, outarchive )
{
m_lock_Compress.Assign(mtx_CompressToDisk);
}
virtual ~VmStateZipThread() throw()
virtual ~VmStateCompressThread() throw()
{
}
protected:
@ -213,7 +223,6 @@ public:
virtual ~SysExecEvent_ZipToDisk() throw()
{
delete m_src_buffer;
}
SysExecEvent_ZipToDisk* Clone() const { return new SysExecEvent_ZipToDisk( *this ); }
@ -236,8 +245,47 @@ public:
protected:
void InvokeEvent()
{
(new VmStateZipThread( m_filename, m_src_buffer ))->Start();
m_src_buffer = NULL;
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;
}
};
@ -251,8 +299,8 @@ protected:
class SysExecEvent_UnzipFromDisk : public SysExecEvent
{
protected:
wxString m_filename;
wxString m_filename;
public:
wxString GetEventName() const { return L"VM_UnzipFromDisk"; }
@ -269,42 +317,127 @@ protected:
void InvokeEvent()
{
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
// *ALWAYS* start execution after the new savestate is loaded.
GetCoreThread().Pause();
// fixme: should start initially with the file size, and then grow from there.
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& )
for (uint i=0; i<NumSavestateEntries; ++i)
{
// This exception actually means success! Any others we let get sent
// to the main event handler/thread for handling.
}
Threading::pxTestCancel();
// Optional shutdown of plugins when loading states? I'm not implementing it yet because some
// things, like the SPU2-recovery trick, rely on not resetting the plugins prior to loading
// the new savestate data.
//if( ShutdownOnStateLoad ) GetCoreThread().Cancel();
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 );
}
GetCoreThread().UploadStateCopy( buffer );
uint copylen = std::min(entrySize, expectedSize);
reader->Read( SavestateEntries[i]->GetDataPtr(), copylen );
}
// Load all the internal data
gzreader->OpenEntry( *foundInternal );
VmStateBuffer buffer( foundInternal->GetSize(), L"StateBuffer_UnzipFromDisk" ); // start with an 8 meg buffer to avoid frequent reallocation.
reader->Read( buffer.GetPtr(), foundInternal->GetSize() );
//GetCoreThread().UploadStateCopy( buffer );
memLoadingState( buffer ).FreezeAll( false );
GetCoreThread().Resume(); // force resume regardless of emulation state earlier.
}
};