diff --git a/pcsx2/Gif.cpp b/pcsx2/Gif.cpp index d3d2840451..b6dd611119 100644 --- a/pcsx2/Gif.cpp +++ b/pcsx2/Gif.cpp @@ -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) { diff --git a/pcsx2/PrecompiledHeader.h b/pcsx2/PrecompiledHeader.h index 5788d481cd..26bc5d847d 100644 --- a/pcsx2/PrecompiledHeader.h +++ b/pcsx2/PrecompiledHeader.h @@ -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" diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index c9a547942e..05dd445e27 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -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& 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 - ) - ); + ); } diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index 9aa3c0b5bd..bfef4eb893 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -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; } }; diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index d52b89866c..0d351f6bac 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -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; } diff --git a/pcsx2/ZipTools/ThreadedZipTools.h b/pcsx2/ZipTools/ThreadedZipTools.h index d9ec6c80d3..d12adfb159 100644 --- a/pcsx2/ZipTools/ThreadedZipTools.h +++ b/pcsx2/ZipTools/ThreadedZipTools.h @@ -16,92 +16,52 @@ #pragma once #include "Utilities/PersistentThread.h" -//#include +#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* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL) - : m_filename( file ) - , m_src_buffer( srcdata ) + BaseCompressThread( SafeArray* srcdata, pxStreamWriter* outarchive) + : m_src_buffer( srcdata ) { - m_WriteHeaderInThread = writeHeader; - m_PendingSaveFlag = false; + m_gzfp = outarchive; + m_PendingSaveFlag = false; } + BaseCompressThread( SafeArray* srcdata, ScopedPtr& 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* srcdata, FnType_WriteCompressedHeader* writeHeader=NULL ); - CompressThread_gzip( const wxString& file, ScopedPtr >& 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; + } }; diff --git a/pcsx2/ZipTools/thread_gzip.cpp b/pcsx2/ZipTools/thread_gzip.cpp index c1fbd41c48..aea95b766d 100644 --- a/pcsx2/ZipTools/thread_gzip.cpp +++ b/pcsx2/ZipTools/thread_gzip.cpp @@ -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* 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 >& 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 ); diff --git a/pcsx2/gui/AppInit.cpp b/pcsx2/gui/AppInit.cpp index d9f539b5eb..a24629cd65 100644 --- a/pcsx2/gui/AppInit.cpp +++ b/pcsx2/gui/AppInit.cpp @@ -481,9 +481,6 @@ class GameDatabaseLoaderThread : public pxThread { typedef pxThread _parent; -protected: - gzFile m_gzfp; - public: GameDatabaseLoaderThread() : pxThread( L"GameDatabaseLoader" ) diff --git a/pcsx2/gui/SysState.cpp b/pcsx2/gui/SysState.cpp index 5059dbdc9f..d2d697b776 100644 --- a/pcsx2/gui/SysState.cpp +++ b/pcsx2/gui/SysState.cpp @@ -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& srcdata ) - : _parent( file, srcdata, SaveStateFile_WriteHeader ) + VmStateCompressThread( ScopedPtr& srcdata, ScopedPtr& 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 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 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 woot( new wxFFileInputStream(m_filename) ); + if (!woot->IsOk()) + throw Exception::CannotCreateStream( m_filename ).SetDiagMsg(L"Cannot open file for reading."); + + + ScopedPtr 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 foundInternal; + ScopedPtr foundEntry[NumSavestateEntries]; + + while(true) + { + Threading::pxTestCancel(); + + ScopedPtr 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; iGetName().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; iGetFilename() ); + } + + 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; iOpenEntry( *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. } };