pcsx2/pcsx2/RecoverySystem.cpp

291 lines
9.2 KiB
C++

/* Pcsx2 - Pc Ps2 Emulator
* Copyright (C) 2002-2009 Pcsx2 Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "PrecompiledHeader.h"
#include "Common.h"
#include "HostGui.h"
//////////////////////////////////////////////////////////////////////////////////////////
// RecoverySystem.cpp -- houses code for recovering from on-the-fly changes to the emu
// configuration, and for saving/restoring the GS state (for more seamless exiting of
// fullscreen GS operation).
//
// The following handful of local classes are implemented att he bottom of this file.
static SafeArray<u8>* g_RecoveryState = NULL;
static SafeArray<u8>* g_gsRecoveryState = NULL;
// This class type creates a memory savestate using the existing Recovery information
// (if present) to generate the savestate material. If no recovery data is present,
// the current emulation state is used instead.
class RecoveryMemSavingState : public memSavingState, Sealed
{
public:
virtual ~RecoveryMemSavingState() { }
RecoveryMemSavingState();
void gsFreeze();
void FreezePlugin( const char* name, s32 (CALLBACK* freezer)(int mode, freezeData *data) );
};
// This class type creates an on-disk (zipped) savestate using the existing Recovery
// information (if present) to generate the savestate material. If no recovery data is
// present, the current emulation state is used instead.
class RecoveryZipSavingState : public gzSavingState, Sealed
{
public:
virtual ~RecoveryZipSavingState() { }
RecoveryZipSavingState( const string& filename );
void gsFreeze();
void FreezePlugin( const char* name, s32 (CALLBACK* freezer)(int mode, freezeData *data) );
};
// Special helper class used to save *just* the GS-relevant state information.
class JustGsSavingState : public memSavingState, Sealed
{
public:
virtual ~JustGsSavingState() { }
JustGsSavingState();
// This special override saves the gs info to m_idx+4, and then goes back and
// writes in the length of data saved.
void gsFreeze();
};
namespace StateRecovery {
bool HasState()
{
return g_RecoveryState != NULL || g_gsRecoveryState != NULL;
}
// Exceptions:
void Recover()
{
// Just in case they weren't initialized earlier (no harm in calling this multiple times)
if( OpenPlugins(NULL) == -1 ) return;
if( g_RecoveryState != NULL )
{
Console::Status( "Resuming execution from full memory state..." );
memLoadingState( *g_RecoveryState ).FreezeAll();
}
else if( g_gsRecoveryState != NULL )
{
s32 dummylen;
Console::Status( "Resuming execution from gsState..." );
memLoadingState eddie( *g_gsRecoveryState );
eddie.FreezePlugin( "GS", gsSafeFreeze );
eddie.Freeze( dummylen ); // reads the length value recorded earlier.
eddie.gsFreeze();
}
StateRecovery::Clear();
// this needs to be called for every new game!
// (note: sometimes launching games through bios will give a crc of 0)
if( GSsetGameCRC != NULL )
GSsetGameCRC(ElfCRC, g_ZeroGSOptions);
}
// Saves recovery state info to the given filename, or saves the active emulation state
// (if one exists) and no recovery data was found. This is needed because when a recovery
// state is made, the emulation state is usually reset so the only persisting state is
// the one in the memory save. :)
void SaveToFile( const string& file )
{
if( g_RecoveryState != NULL )
{
// State is already saved into memory, and the emulator (and in-progress flag)
// have likely been cleared out. So save from the Recovery buffer instead of
// doing a "standard" save:
gzFile fileptr = gzopen( file.c_str(), "wb" );
if( fileptr == NULL )
{
Msgbox::Alert( _("File permissions error while trying to save to file:\n\t%ts"), params &file );
return;
}
gzwrite( fileptr, &g_SaveVersion, sizeof( u32 ) );
gzwrite( fileptr, g_RecoveryState->GetPtr(), g_RecoveryState->GetSizeInBytes() );
gzclose( fileptr );
}
else if( g_gsRecoveryState != NULL )
{
RecoveryZipSavingState( file ).FreezeAll();
}
else
{
if( !g_EmulationInProgress )
{
Msgbox::Alert( "You need to start a game first before you can save it's state." );
return;
}
States_Save( file );
}
}
// Saves recovery state info to the given saveslot, or saves the active emulation state
// (if one exists) and no recovery data was found. This is needed because when a recovery
// state is made, the emulation state is usually reset so the only persisting state is
// the one in the memory save. :)
void SaveToSlot( uint num )
{
SaveToFile( SaveState::GetFilename( num ) );
}
// This method will override any existing recovery states, so call it with caution, if you
// think that there could be existing important state info in the recovery buffers (but
// really there shouldn't be, unless you're calling this function when it's not intended
// to be called).
void MakeGsOnly()
{
StateRecovery::Clear();
if( !g_EmulationInProgress ) return;
g_gsRecoveryState = new SafeArray<u8>();
JustGsSavingState eddie;
eddie.FreezePlugin( "GS", gsSafeFreeze ) ;
eddie.gsFreeze();
}
// Creates a full recovery of the entire emulation state (CPU and all plugins).
// If a current recovery state is already present, then nothing is done (the
// existing recovery state takes precedence).
void MakeFull()
{
if( g_RecoveryState != NULL ) return;
try
{
g_RecoveryState = new SafeArray<u8>( "Memory Savestate Recovery" );
RecoveryMemSavingState().FreezeAll();
safe_delete( g_gsRecoveryState );
g_EmulationInProgress = false;
}
catch( Exception::RuntimeError& ex )
{
Msgbox::Alert(
"Pcsx2 gamestate recovery failed. Some options may have been reverted to protect your game's state.\n"
"Error: %s", params ex.cMessage() );
safe_delete( g_RecoveryState );
}
}
// Clears and deallocates any recovery states.
void Clear()
{
safe_delete( g_RecoveryState );
safe_delete( g_gsRecoveryState );
}
}
RecoveryMemSavingState::RecoveryMemSavingState() : memSavingState( *g_RecoveryState )
{
}
void RecoveryMemSavingState::gsFreeze()
{
if( g_gsRecoveryState != NULL )
{
// just copy the data from src to dst.
// the normal savestate doesn't expect a length prefix for internal structures,
// so don't copy that part.
const u32 pluginlen = *((u32*)g_gsRecoveryState->GetPtr());
const u32 gslen = *((u32*)g_gsRecoveryState->GetPtr(pluginlen+4));
memcpy( m_memory.GetPtr(m_idx), g_gsRecoveryState->GetPtr(pluginlen+8), gslen );
m_idx += gslen;
}
else
memSavingState::gsFreeze();
}
void RecoveryMemSavingState::FreezePlugin( const char* name, s32 (CALLBACK* freezer)(int mode, freezeData *data) )
{
if( (freezer == gsSafeFreeze) && (g_gsRecoveryState != NULL) )
{
// Gs data is already in memory, so just copy from src to dest:
// length of the GS data is stored as the first u32, so use that to run the copy:
const u32 len = *((u32*)g_gsRecoveryState->GetPtr());
memcpy( m_memory.GetPtr(m_idx), g_gsRecoveryState->GetPtr(), len+4 );
m_idx += len+4;
}
else
memSavingState::FreezePlugin( name, freezer );
}
RecoveryZipSavingState::RecoveryZipSavingState( const string& filename ) : gzSavingState( filename )
{
}
void RecoveryZipSavingState::gsFreeze()
{
if( g_gsRecoveryState != NULL )
{
// read data from the gsRecoveryState allocation instead of the GS, since the gs
// info was invalidated when the plugin was shut down.
// the normal savestate doesn't expect a length prefix for internal structures,
// so don't copy that part.
u32& pluginlen = *((u32*)g_gsRecoveryState->GetPtr(0));
u32& gslen = *((u32*)g_gsRecoveryState->GetPtr(pluginlen+4));
gzwrite( m_file, g_gsRecoveryState->GetPtr(pluginlen+4), gslen );
}
else
gzSavingState::gsFreeze();
}
void RecoveryZipSavingState::FreezePlugin( const char* name, s32 (CALLBACK* freezer)(int mode, freezeData *data) )
{
if( (freezer == gsSafeFreeze) && (g_gsRecoveryState != NULL) )
{
// Gs data is already in memory, so just copy from there into the gzip file.
// length of the GS data is stored as the first u32, so use that to run the copy:
u32& len = *((u32*)g_gsRecoveryState->GetPtr());
gzwrite( m_file, g_gsRecoveryState->GetPtr(), len+4 );
}
else
gzSavingState::FreezePlugin( name, freezer );
}
JustGsSavingState::JustGsSavingState() : memSavingState( *g_gsRecoveryState )
{
}
// This special override saves the gs info to m_idx+4, and then goes back and
// writes in the length of data saved.
void JustGsSavingState::gsFreeze()
{
int oldmidx = m_idx;
m_idx += 4;
memSavingState::gsFreeze();
if( IsSaving() )
{
s32& len = *((s32*)m_memory.GetPtr( oldmidx ));
len = (m_idx - oldmidx)-4;
}
}