/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see .
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_session_local.h"
idCVar saveGame_verbose( "saveGame_verbose", "0", CVAR_BOOL | CVAR_ARCHIVE, "debug spam" );
idCVar saveGame_checksum( "saveGame_checksum", "1", CVAR_BOOL, "data integrity check" );
idCVar saveGame_enable( "saveGame_enable", "1", CVAR_BOOL, "are savegames enabled" );
void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms* savegameParms );
/*
========================
Sys_ExecuteSavegameCommandAsync
========================
*/
void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms* savegameParms )
{
if( savegameParms == NULL )
{
idLib::Error( "Programming Error with [%s]", __FUNCTION__ );
return;
}
if( !saveGame_enable.GetBool() )
{
idLib::Warning( "Savegames are disabled (saveGame_enable = 0). Skipping physical save to media." );
savegameParms->errorCode = SAVEGAME_E_CANCELLED;
savegameParms->callbackSignal.Raise();
return;
}
Sys_ExecuteSavegameCommandAsyncImpl( savegameParms );
}
#define ASSERT_ENUM_STRING_BITFIELD( string, index ) ( 1 / (int)!( string - ( 1 << index ) ) ) ? #string : ""
const char* saveGameErrorStrings[ SAVEGAME_E_NUM ] =
{
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CANCELLED, 0 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INSUFFICIENT_ROOM, 1 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CORRUPTED, 2 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE, 3 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNKNOWN, 4 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_FILENAME, 5 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_STEAM_ERROR, 6 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FOLDER_NOT_FOUND, 7 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FILE_NOT_FOUND, 8 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DLC_NOT_FOUND, 9 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_USER, 10 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_PROFILE_TOO_BIG, 11 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DISC_SWAP, 12 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION, 13 ),
};
CONSOLE_COMMAND( savegamePrintErrors, "Prints error code corresponding to each bit", 0 )
{
idLib::Printf( "Bit Description\n"
"--- -----------\n" );
for( int i = 0; i < SAVEGAME_E_BITS_USED; i++ )
{
idLib::Printf( "%03d %s\n", i, saveGameErrorStrings[i] );
}
}
/*
========================
GetSaveGameErrorString
This returns a comma delimited string of all the errors within the bitfield. We don't ever expect more than one error,
the errors are bitfields so that they can be used to mask which errors we want to handle in the engine or leave up to the
game.
Example:
SAVEGAME_E_LOAD, SAVEGAME_E_INVALID_FILENAME
========================
*/
idStr GetSaveGameErrorString( int errorMask )
{
idStr errorString;
bool continueProcessing = errorMask > 0;
int localError = errorMask;
for( int i = 0; i < SAVEGAME_E_NUM && continueProcessing; ++i )
{
int mask = ( 1 << i );
if( localError & mask )
{
localError ^= mask; // turn off this error so we can quickly see if we are done
continueProcessing = localError > 0;
errorString.Append( saveGameErrorStrings[i] );
if( continueProcessing )
{
errorString.Append( ", " );
}
}
}
return errorString;
}
/*
========================
GetSaveFolder
Prefixes help the PS3 filter what to show in lists
Files using the hidden prefix are not shown in the load game screen
Directory name for savegames, slot number or user-defined name appended to it
TRC R116 - PS3 folder must start with the product code
========================
*/
const idStr& GetSaveFolder( idSaveGameManager::packageType_t type )
{
static bool initialized = false;
static idStrStatic saveFolder[idSaveGameManager::PACKAGE_NUM];
if( !initialized )
{
initialized = true;
idStr ps3Header = "";
saveFolder[idSaveGameManager::PACKAGE_GAME].Format( "%s%s", ps3Header.c_str(), SAVEGAME_GAME_DIRECTORY_PREFIX );
saveFolder[idSaveGameManager::PACKAGE_PROFILE].Format( "%s%s", ps3Header.c_str(), SAVEGAME_PROFILE_DIRECTORY_PREFIX );
saveFolder[idSaveGameManager::PACKAGE_RAW].Format( "%s%s", ps3Header.c_str(), SAVEGAME_RAW_DIRECTORY_PREFIX );
}
return saveFolder[type];
}
/*
========================
idStr AddSaveFolderPrefix
input = RAGE_0
output = GAMES-RAGE_0
========================
*/
idStr AddSaveFolderPrefix( const char* folder, idSaveGameManager::packageType_t type )
{
idStr dir = GetSaveFolder( type );
dir.Append( folder );
return dir;
}
/*
========================
RemoveSaveFolderPrefix
input = GAMES-RAGE_0
output = RAGE_0
========================
*/
idStr RemoveSaveFolderPrefix( const char* folder, idSaveGameManager::packageType_t type )
{
idStr dir = folder;
idStr prefix = GetSaveFolder( type );
dir.StripLeading( prefix );
return dir;
}
/*
========================
bool SavegameReadDetailsFromFile
returns false when catastrophic error occurs, not when damaged
========================
*/
bool SavegameReadDetailsFromFile( idFile* file, idSaveGameDetails& details )
{
details.damaged = false;
// Read the DETAIL file for the enumerated data
if( !details.descriptors.ReadFromIniFile( file ) )
{
details.damaged = true;
}
bool ignoreChecksum = details.descriptors.GetBool( "ignore_checksum", false );
if( !ignoreChecksum )
{
// Get the checksum from the dict
int readChecksum = details.descriptors.GetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, 0 );
// Calculate checksum
details.descriptors.Delete( SAVEGAME_DETAIL_FIELD_CHECKSUM );
int checksum = ( int )details.descriptors.Checksum();
if( readChecksum == 0 || checksum != readChecksum )
{
details.damaged = true;
}
}
return true;
}
/*
========================
idSaveGameDetails::idSaveGameDetails
========================
*/
idSaveGameDetails::idSaveGameDetails()
{
Clear();
}
/*
========================
idSaveGameDetails::Clear
========================
*/
void idSaveGameDetails::Clear()
{
descriptors.Clear();
damaged = false;
date = 0;
slotName[0] = NULL;
}
/*
========================
idSaveLoadParms::idSaveLoadParms
========================
*/
idSaveLoadParms::idSaveLoadParms()
{
// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
// these are set once and shouldn't be touched until the processor is re-initialized
cancelled = false;
Init();
}
/*
========================
idSaveLoadParms::~idSaveLoadParms
========================
*/
idSaveLoadParms::~idSaveLoadParms()
{
for( int i = 0; i < files.Num(); ++i )
{
if( files[i]->type & SAVEGAMEFILE_AUTO_DELETE )
{
delete files[i];
}
}
}
/*
========================
idSaveLoadParms::ResetCancelled
========================
*/
void idSaveLoadParms::ResetCancelled()
{
cancelled = false;
}
/*
========================
idSaveLoadParms::Init
This should not touch anything statically created outside this class!
========================
*/
void idSaveLoadParms::Init()
{
files.Clear();
mode = SAVEGAME_MBF_NONE;
directory = "";
pattern = "";
postPattern = "";
requiredSpaceInBytes = 0;
description.Clear();
detailList.Clear();
callbackSignal.Clear();
errorCode = SAVEGAME_E_NONE;
inputDeviceId = -1;
skipErrorDialogMask = 0;
// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
// these are set once and shouldn't be touched until the processor is re-initialized
// cancelled = false;
}
/*
========================
idSaveLoadParms::SetDefaults
========================
*/
void idSaveLoadParms::SetDefaults( int newInputDevice )
{
// These are pulled out so SetDefaults() isn't called during global instantiation of objects that have savegame processors
// in them that then require a session reference.
Init();
// fill in the user information (inputDeviceId & userId) from the master user
idLocalUser* user = NULL;
if( newInputDevice != -1 )
{
user = session->GetSignInManager().GetLocalUserByInputDevice( newInputDevice );
}
else if( session != NULL )
{
user = session->GetSignInManager().GetMasterLocalUser();
}
if( user != NULL )
{
idLocalUserWin* userWin = static_cast< idLocalUserWin* >( user );
userId = idStr::Hash( userWin->GetGamerTag() );
idLib::PrintfIf( saveGame_verbose.GetBool(), "profile userId/gamertag: %s (%d)\n", userWin->GetGamerTag(), userId );
inputDeviceId = user->GetInputDevice();
}
}
/*
========================
idSaveLoadParms::CancelSaveGameFilePipelines
========================
*/
void idSaveLoadParms::CancelSaveGameFilePipelines()
{
for( int i = 0; i < files.Num(); i++ )
{
if( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 )
{
idFile_SaveGamePipelined* file = dynamic_cast< idFile_SaveGamePipelined* >( files[i] );
assert( file != NULL );
if( file->GetMode() == idFile_SaveGamePipelined::WRITE )
{
// Notify the save game file that all writes failed which will cause all
// writes on the other end of the pipeline to drop on the floor.
file->NextWriteBlock( NULL );
}
else if( file->GetMode() == idFile_SaveGamePipelined::READ )
{
// Notify end-of-file to the save game file which will cause all
// reads on the other end of the pipeline to return zero bytes.
file->NextReadBlock( NULL, 0 );
}
}
}
}
/*
========================
idSaveLoadParms::AbortSaveGameFilePipeline
========================
*/
void idSaveLoadParms::AbortSaveGameFilePipeline()
{
for( int i = 0; i < files.Num(); i++ )
{
if( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 )
{
idFile_SaveGamePipelined* file = dynamic_cast< idFile_SaveGamePipelined* >( files[i] );
assert( file != NULL );
file->Abort();
}
}
}
/*
================================================================================================
idSaveGameProcessor
================================================================================================
*/
/*
========================
idSaveGameProcessor::idSaveGameProcessor
========================
*/
idSaveGameProcessor::idSaveGameProcessor() : working( false ), init( false )
{
}
/*
========================
idSaveGameProcessor::Init
========================
*/
bool idSaveGameProcessor::Init()
{
if( !verify( !IsWorking() ) )
{
idLib::Warning( "[%s] Someone is trying to execute this processor twice, this is really bad!", this->Name() );
return false;
}
parms.ResetCancelled();
parms.SetDefaults();
savegameLogicTestIterator = 0;
working = false;
init = true;
completedCallbacks.Clear();
return true;
}
/*
========================
idSaveGameProcessor::IsThreadFinished
========================
*/
bool idSaveGameProcessor::IsThreadFinished()
{
return parms.callbackSignal.Wait( 0 );
}
/*
========================
idSaveGameProcessor::AddCompletedCallback
========================
*/
void idSaveGameProcessor::AddCompletedCallback( const idCallback& callback )
{
completedCallbacks.Append( callback.Clone() );
}
/*
================================================================================================
idSaveGameManager
================================================================================================
*/
/*
========================
idSaveGameManager::idSaveGameManager
========================
*/
idSaveGameManager::idSaveGameManager() :
processor( NULL ),
cancel( false ),
startTime( 0 ),
continueProcessing( false ),
submittedProcessorHandle( 0 ),
executingProcessorHandle( 0 ),
lastExecutedProcessorHandle( 0 ),
storageAvailable( true ),
retryFolder( NULL )
{
}
/*
========================
idSaveGameManager::~idSaveGameManager
========================
*/
idSaveGameManager::~idSaveGameManager()
{
processor = NULL;
enumeratedSaveGames.Clear();
}
/*
========================
idSaveGameManager::ExecuteProcessor
========================
*/
saveGameHandle_t idSaveGameManager::ExecuteProcessor( idSaveGameProcessor* processor )
{
idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s\n", __FUNCTION__, processor->Name() );
// may not be running yet, but if we've init'd successfuly, the IsWorking() call should return true if this
// method has been called. You have problems when callees are asking if the processor is done working by using IsWorking()
// the next frame after they've executed the processor.
processor->working = true;
if( this->processor != NULL )
{
if( !verify( this->processor != processor ) )
{
idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:1 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
return processor->GetHandle();
}
else
{
idSaveGameProcessor** localProcessor = processorQueue.Find( processor );
if( !verify( localProcessor == NULL ) )
{
idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:2 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
return ( *localProcessor )->GetHandle();
}
}
}
processorQueue.Append( processor );
// Don't allow processors to start sub-processors.
// They need to manage their own internal state.
assert( idLib::IsMainThread() );
Sys_InterlockedIncrement( submittedProcessorHandle );
processor->parms.handle = submittedProcessorHandle;
return submittedProcessorHandle;
}
/*
========================
idSaveGameManager::ExecuteProcessorAndWait
========================
*/
saveGameHandle_t idSaveGameManager::ExecuteProcessorAndWait( idSaveGameProcessor* processor )
{
saveGameHandle_t handle = ExecuteProcessor( processor );
if( handle == 0 )
{
return 0;
}
while( !IsSaveGameCompletedFromHandle( handle ) )
{
Pump();
Sys_Sleep( 10 );
}
// One more pump to get the completed callback
//Pump();
return handle;
}
/*
========================
idSaveGameManager::WaitForAllProcessors
Since the load & nextMap processors calls execute map change, we can't wait if they are the only ones in the queue
If there are only resettable processors in the queue or no items in the queue, don't wait.
We would need to overrideSimpleProcessorCheck if we were sure we had done something that would cause the processors
to bail out nicely. Something like canceling a disc swap during a loading disc swap dialog...
========================
*/
void idSaveGameManager::WaitForAllProcessors( bool overrideSimpleProcessorCheck )
{
assert( idLib::IsMainThread() );
while( IsWorking() || ( processorQueue.Num() > 0 ) )
{
if( !overrideSimpleProcessorCheck )
{
// BEFORE WE WAIT, and potentially hang everything, make sure processors about to be executed won't sit and
// wait for themselves to complete.
// Since we pull off simple processors first, we can stop waiting when the processor being executed is not simple
if( processor != NULL )
{
if( !processor->IsSimpleProcessor() )
{
break;
}
}
else if( !processorQueue[0]->IsSimpleProcessor() )
{
break;
}
}
saveThread.WaitForThread();
Pump();
}
}
/*
========================
idSaveGameManager::CancelAllProcessors
========================
*/
void idSaveGameManager::CancelAllProcessors( const bool forceCancelInFlightProcessor )
{
assert( idLib::IsMainThread() );
cancel = true;
if( forceCancelInFlightProcessor )
{
if( processor != NULL )
{
processor->GetSignal().Raise();
}
}
Pump(); // must be called from the main thread
Clear();
cancel = false;
}
/*
========================
idSaveGameManager::CancelToTerminate
========================
*/
void idSaveGameManager::CancelToTerminate()
{
if( processor != NULL )
{
processor->parms.cancelled = true;
processor->GetSignal().Raise();
saveThread.WaitForThread();
}
}
/*
========================
idSaveGameManager::DeviceSelectorWaitingOnSaveRetry
========================
*/
bool idSaveGameManager::DeviceSelectorWaitingOnSaveRetry()
{
if( retryFolder == NULL )
{
return false;
}
return ( idStr::Icmp( retryFolder, "GAME-autosave" ) == 0 );
}
/*
========================
idSaveGameManager::Set360RetrySaveAfterDeviceSelected
========================
*/
void idSaveGameManager::Set360RetrySaveAfterDeviceSelected( const char* folder, const int64 bytes )
{
retryFolder = folder;
retryBytes = bytes;
}
/*
========================
idSaveGameManager::ClearRetryInfo
========================
*/
void idSaveGameManager::ClearRetryInfo()
{
retryFolder = NULL;
retryBytes = 0;
}
/*
========================
idSaveGameManager::RetrySave
========================
*/
void idSaveGameManager::RetrySave()
{
if( DeviceSelectorWaitingOnSaveRetry() && !common->Dialog().HasDialogMsg( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, false ) )
{
cmdSystem->AppendCommandText( "savegame autosave\n" );
}
}
/*
========================
idSaveGameManager::ShowRetySaveDialog
========================
*/
void idSaveGameManager::ShowRetySaveDialog()
{
ShowRetySaveDialog( retryFolder, retryBytes );
}
/*
========================
idSaveGameManager::ShowRetySaveDialog
========================
*/
void idSaveGameManager::ShowRetySaveDialog( const char* folder, const int64 bytes )
{
idStaticList< idSWFScriptFunction*, 4 > callbacks;
idStaticList< idStrId, 4 > optionText;
class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted
{
public:
idSWFScriptVar Call( idSWFScriptObject* thisObject, const idSWFParmList& parms )
{
common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE );
session->GetSaveGameManager().ClearRetryInfo();
return idSWFScriptVar();
}
};
callbacks.Append( new( TAG_SWF ) idSWFScriptFunction_Continue() );
optionText.Append( idStrId( "#str_dlg_continue_without_saving" ) );
// build custom space required string
// #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again."
idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString();
idStr size;
if( bytes > ( 1024 * 1024 ) )
{
const float roundUp = ( ( 1024.0f * 1024.0f / 10.0f ) - 1.0f );
size = va( "%.1f MB", ( roundUp + ( float ) bytes ) / ( 1024.0f * 1024.0f ) );
}
else
{
const float roundUp = 1024.0f - 1.0f;
size = va( "%.0f KB", ( roundUp + ( float ) bytes ) / 1024.0f );
}
idStr msg = va( format.c_str(), size.c_str() );
common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg, true );
}
/*
========================
idSaveGameManager::CancelWithHandle
========================
*/
void idSaveGameManager::CancelWithHandle( const saveGameHandle_t& handle )
{
if( handle == 0 || IsSaveGameCompletedFromHandle( handle ) )
{
return;
}
// check processor in flight first
if( processor != NULL )
{
if( processor->GetHandle() == handle )
{
processor->Cancel();
return;
}
}
// remove from queue
for( int i = 0; i < processorQueue.Num(); ++i )
{
if( processorQueue[i]->GetHandle() == handle )
{
processorQueue[i]->Cancel();
return;
}
}
}
/*
========================
idSaveGameManager::StartNextProcessor
Get the next not-reset-capable processor. If there aren't any left, just get what's next.
========================
*/
void idSaveGameManager::StartNextProcessor()
{
if( cancel )
{
return;
}
idSaveGameProcessor* nextProcessor = NULL;
int index = 0;
// pick off the first simple processor
for( int i = 0; i < processorQueue.Num(); ++i )
{
if( processorQueue[i]->IsSimpleProcessor() )
{
index = i;
break;
}
}
if( processorQueue.Num() > 0 )
{
nextProcessor = processorQueue[index];
Sys_InterlockedIncrement( executingProcessorHandle );
processorQueue.RemoveIndex( index );
processor = nextProcessor;
processor->parms.callbackSignal.Raise(); // signal that the thread is ready for work
startTime = Sys_Milliseconds();
}
}
/*
========================
idSaveGameManager::FinishProcessor
========================
*/
void idSaveGameManager::FinishProcessor( idSaveGameProcessor* localProcessor )
{
assert( localProcessor != NULL );
idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s, %d ms\n", __FUNCTION__, localProcessor->Name(), Sys_Milliseconds() - startTime );
// This will delete from the files set for auto-deletion
// Don't remove files not set for auto-deletion, they may be used outside of the savegame manager by game-side callbacks for example
for( int i = ( localProcessor->parms.files.Num() - 1 ); i >= 0; --i )
{
if( localProcessor->parms.files[i]->type & SAVEGAMEFILE_AUTO_DELETE )
{
delete localProcessor->parms.files[i];
localProcessor->parms.files.RemoveIndexFast( i );
}
}
localProcessor->init = false;
localProcessor = NULL;
}
/*
========================
idSaveGameManager::Clear
========================
*/
void idSaveGameManager::Clear()
{
processorQueue.Clear();
}
/*
========================
idSaveGameManager::IsWorking
========================
*/
bool idSaveGameManager::IsWorking() const
{
return processor != NULL;
}
/*
========================
idSaveGameManager::Pump
Important sections called out with -- EXTRA LARGE -- comments!
========================
*/
void idSaveGameManager::Pump()
{
// After a processor is done, the next is pulled off the queue so the only way the manager isn't working is if
// there isn't something executing or in the queue.
if( !IsWorking() )
{
// Unified start to initialize system on PS3 and do appropriate checks for system combination issues
// ------------------------------------
// START
// ------------------------------------
StartNextProcessor();
if( !IsWorking() )
{
return;
}
continueProcessing = true;
}
if( cancel )
{
processor->parms.AbortSaveGameFilePipeline();
}
// Quickly checks to see if the savegame thread is done, otherwise, exit and continue frame commands
if( processor->IsThreadFinished() )
{
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s waited on processor [%s], error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() );
if( !cancel && continueProcessing )
{
// Check for available storage unit
if( session->GetSignInManager().GetMasterLocalUser() != NULL )
{
if( !session->GetSignInManager().GetMasterLocalUser()->IsStorageDeviceAvailable() )
{
// this will not allow further processing
processor->parms.errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE;
}
}
// Execute Process() on the processor, if there was an error in a previous Process() call, give the
// processor the chance to validate that error and either clean itself up or convert it to another error or none.
if( processor->GetError() == SAVEGAME_E_NONE || processor->ValidateLastError() )
{
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::Process(), error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() );
// ------------------------------------
// PROCESS
// ------------------------------------
continueProcessing = processor->Process();
// If we don't return here, the completedCallback will be executed before it's done with it's async operation
// during it's last process stage.
return;
}
else
{
continueProcessing = false;
}
}
// This section does specific post-processing for each of the save commands
if( !continueProcessing )
{
// Clear out details if we detect corruption but keep directory/slot information
for( int i = 0; i < processor->parms.detailList.Num(); ++i )
{
idSaveGameDetails& details = processor->parms.detailList[i];
if( details.damaged )
{
details.descriptors.Clear();
}
}
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::CompletedCallback()\n", __FUNCTION__, processor->Name() );
processor->working = false;
// This ensures that the savegame manager will believe the processor is done when there is a potentially
// catastrophic thing that will happen within CompletedCallback which might try to sync all threads
// The most common case of this is executing a map change (which we no longer do).
// We flush the heap and wait for all background processes to finish. After all this is called, we will
// cleanup the old processor within FinishProcessor()
idSaveGameProcessor* localProcessor = processor;
processor = NULL;
// ------------------------------------
// COMPLETEDCALLBACK
// At this point, the handle will be completed
// ------------------------------------
Sys_InterlockedIncrement( lastExecutedProcessorHandle );
for( int i = 0; i < localProcessor->completedCallbacks.Num(); i++ )
{
localProcessor->completedCallbacks[i]->Call();
}
localProcessor->completedCallbacks.DeleteContents( true );
// ------------------------------------
// FINISHPROCESSOR
// ------------------------------------
FinishProcessor( localProcessor );
}
}
else if( processor->ShouldTimeout() )
{
// Hack for the PS3 threading hang
idLib::PrintfIf( saveGame_verbose.GetBool(), "----- PROCESSOR TIMEOUT ----- (%s)\n", processor->Name() );
idSaveGameProcessor* tempProcessor = processor;
CancelAllProcessors( true );
class idSWFScriptFunction_TryAgain : public idSWFScriptFunction_RefCounted
{
public:
idSWFScriptFunction_TryAgain( idSaveGameManager* manager, idSaveGameProcessor* processor )
{
this->manager = manager;
this->processor = processor;
}
idSWFScriptVar Call( idSWFScriptObject* thisObject, const idSWFParmList& parms )
{
common->Dialog().ClearDialog( GDM_ERROR_SAVING_SAVEGAME );
manager->ExecuteProcessor( processor );
return idSWFScriptVar();
}
private:
idSaveGameManager* manager;
idSaveGameProcessor* processor;
};
idStaticList< idSWFScriptFunction*, 4 > callbacks;
idStaticList< idStrId, 4 > optionText;
callbacks.Append( new( TAG_SWF ) idSWFScriptFunction_TryAgain( this, tempProcessor ) );
optionText.Append( idStrId( "#STR_SWF_RETRY" ) );
common->Dialog().AddDynamicDialog( GDM_ERROR_SAVING_SAVEGAME, callbacks, optionText, true, "" );
}
}