DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

sys_savegame.cpp (27807B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 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.
     23 
     24 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.
     25 
     26 ===========================================================================
     27 */
     28 #pragma hdrstop
     29 #include "../idlib/precompiled.h"
     30 #include "sys_session_local.h"
     31 
     32 
     33 idCVar saveGame_verbose( "saveGame_verbose", "0", CVAR_BOOL | CVAR_ARCHIVE, "debug spam" );
     34 idCVar saveGame_checksum( "saveGame_checksum", "1", CVAR_BOOL, "data integrity check" );
     35 idCVar saveGame_enable( "saveGame_enable", "1", CVAR_BOOL, "are savegames enabled" );
     36 
     37 void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms );
     38 
     39 
     40 /*
     41 ========================
     42 Sys_ExecuteSavegameCommandAsync
     43 ========================
     44 */
     45 void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms ) {
     46 	if ( savegameParms == NULL ) {
     47 		idLib::Error( "Programming Error with [%s]", __FUNCTION__ );
     48 		return;
     49 	}
     50 
     51 	if ( !saveGame_enable.GetBool() ) {
     52 		idLib::Warning( "Savegames are disabled (saveGame_enable = 0). Skipping physical save to media." );
     53 		savegameParms->errorCode = SAVEGAME_E_CANCELLED;
     54 		savegameParms->callbackSignal.Raise();
     55 		return;
     56 	}
     57 
     58 	Sys_ExecuteSavegameCommandAsyncImpl( savegameParms );
     59 }
     60 
     61 #define ASSERT_ENUM_STRING_BITFIELD( string, index )		( 1 / (int)!( string - ( 1 << index ) ) ) ? #string : ""
     62 
     63 const char * saveGameErrorStrings[ SAVEGAME_E_NUM ] = {
     64 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CANCELLED,							0 ),
     65 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INSUFFICIENT_ROOM,					1 ),
     66 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CORRUPTED,							2 ),
     67 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE,	3 ),
     68 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNKNOWN,							4 ),
     69 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_FILENAME,					5 ),
     70 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_STEAM_ERROR,						6 ),
     71 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FOLDER_NOT_FOUND,					7 ),
     72 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FILE_NOT_FOUND,						8 ),
     73 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DLC_NOT_FOUND,						9 ),
     74 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_USER,						10 ),
     75 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_PROFILE_TOO_BIG,					11 ),
     76 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DISC_SWAP,							12 ),
     77 	ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION,			13 ),
     78 };
     79 
     80 CONSOLE_COMMAND( savegamePrintErrors, "Prints error code corresponding to each bit", 0 ) {
     81 	idLib::Printf( "Bit  Description\n"
     82 				   "---  -----------\n" );
     83 	for ( int i = 0; i < SAVEGAME_E_BITS_USED; i++ ) {
     84 		idLib::Printf( "%03d  %s\n", i, saveGameErrorStrings[i] );
     85 	}
     86 }
     87 
     88 /*
     89 ========================
     90 GetSaveGameErrorString
     91 
     92 This returns a comma delimited string of all the errors within the bitfield.  We don't ever expect more than one error,
     93 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
     94 game.
     95 
     96 Example: 
     97 	SAVEGAME_E_LOAD, SAVEGAME_E_INVALID_FILENAME
     98 ========================
     99 */
    100 idStr GetSaveGameErrorString( int errorMask ) {
    101 	idStr errorString;
    102 	bool continueProcessing = errorMask > 0;
    103 	int localError = errorMask;
    104 
    105 	for ( int i = 0; i < SAVEGAME_E_NUM && continueProcessing; ++i ) {
    106 		int mask = ( 1 << i );
    107 
    108 		if ( localError & mask ) {
    109 			localError ^= mask;	// turn off this error so we can quickly see if we are done
    110 
    111 			continueProcessing = localError > 0;
    112 			
    113 			errorString.Append( saveGameErrorStrings[i] );
    114 
    115 			if ( continueProcessing ) {
    116 				errorString.Append( ", " );
    117 			}
    118 		}
    119 	}
    120 
    121 	return errorString;
    122 }
    123 
    124 /*
    125 ========================
    126 GetSaveFolder
    127 
    128 Prefixes help the PS3 filter what to show in lists
    129 Files using the hidden prefix are not shown in the load game screen
    130 Directory name for savegames, slot number or user-defined name appended to it
    131 TRC R116 - PS3 folder must start with the product code
    132 ========================
    133 */
    134 const idStr & GetSaveFolder( idSaveGameManager::packageType_t type ) {
    135 	static bool initialized = false;
    136 	static idStrStatic<MAX_FOLDER_NAME_LENGTH>	saveFolder[idSaveGameManager::PACKAGE_NUM];
    137 
    138 	if ( !initialized ) {
    139 		initialized = true;
    140 
    141 		idStr ps3Header = "";
    142 
    143 		saveFolder[idSaveGameManager::PACKAGE_GAME].Format( "%s%s", ps3Header.c_str(), SAVEGAME_GAME_DIRECTORY_PREFIX );
    144 		saveFolder[idSaveGameManager::PACKAGE_PROFILE].Format( "%s%s", ps3Header.c_str(), SAVEGAME_PROFILE_DIRECTORY_PREFIX );
    145 		saveFolder[idSaveGameManager::PACKAGE_RAW].Format( "%s%s", ps3Header.c_str(), SAVEGAME_RAW_DIRECTORY_PREFIX );
    146 	}
    147 
    148 	return saveFolder[type];
    149 }
    150 
    151 /*
    152 ========================
    153 idStr AddSaveFolderPrefix
    154 
    155 	input	= RAGE_0
    156 	output	= GAMES-RAGE_0
    157 ========================
    158 */
    159 idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) {
    160 	idStr dir = GetSaveFolder( type );
    161 	dir.Append( folder );
    162 
    163 
    164 	return dir;
    165 }
    166 
    167 /*
    168 ========================
    169 RemoveSaveFolderPrefix
    170 
    171 	input	= GAMES-RAGE_0
    172 	output	= RAGE_0
    173 ========================
    174 */
    175 idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) {
    176 	idStr dir = folder;
    177 	idStr prefix = GetSaveFolder( type );
    178 	dir.StripLeading( prefix );
    179 	return dir;
    180 }
    181 
    182 /*
    183 ========================
    184 bool SavegameReadDetailsFromFile
    185 
    186 returns false when catastrophic error occurs, not when damaged
    187 ========================
    188 */
    189 bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details ) {
    190 	details.damaged = false;
    191 
    192 	// Read the DETAIL file for the enumerated data
    193 	if ( !details.descriptors.ReadFromIniFile( file ) ) {
    194 		details.damaged = true;
    195 	}
    196 
    197 	bool ignoreChecksum = details.descriptors.GetBool( "ignore_checksum", false );
    198 	if ( !ignoreChecksum ) {
    199 		// Get the checksum from the dict
    200 		int readChecksum = details.descriptors.GetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, 0 );
    201 
    202 		// Calculate checksum
    203 		details.descriptors.Delete( SAVEGAME_DETAIL_FIELD_CHECKSUM );
    204 		int checksum = (int)details.descriptors.Checksum();
    205 		if ( readChecksum == 0 || checksum != readChecksum ) {
    206 			details.damaged = true;
    207 		}
    208 	}
    209 
    210 	return true;
    211 }
    212 
    213 /*
    214 ========================
    215 idSaveGameDetails::idSaveGameDetails
    216 ========================
    217 */
    218 idSaveGameDetails::idSaveGameDetails() { 
    219 	Clear(); 
    220 }
    221 
    222 /*
    223 ========================
    224 idSaveGameDetails::Clear
    225 ========================
    226 */
    227 void idSaveGameDetails::Clear() {
    228 	descriptors.Clear();
    229 	damaged = false;
    230 	date = 0;
    231 	slotName[0] = NULL;
    232 }
    233 
    234 /*
    235 ========================
    236 idSaveLoadParms::idSaveLoadParms
    237 ========================
    238 */
    239 idSaveLoadParms::idSaveLoadParms() {
    240 	// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
    241 	// these are set once and shouldn't be touched until the processor is re-initialized
    242 	cancelled = false;
    243 
    244 	Init();
    245 }
    246 
    247 /*
    248 ========================
    249 idSaveLoadParms::~idSaveLoadParms
    250 ========================
    251 */
    252 idSaveLoadParms::~idSaveLoadParms() {
    253 	for ( int i = 0; i < files.Num(); ++i ) {
    254 		if ( files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) {
    255 			delete files[i];
    256 		}
    257 	}
    258 }
    259 
    260 /*
    261 ========================
    262 idSaveLoadParms::ResetCancelled
    263 ========================
    264 */
    265 void idSaveLoadParms::ResetCancelled() {
    266 	cancelled = false;
    267 }
    268 
    269 /*
    270 ========================
    271 idSaveLoadParms::Init
    272 
    273 This should not touch anything statically created outside this class!
    274 ========================
    275 */
    276 void idSaveLoadParms::Init() {
    277 	files.Clear();
    278 	mode = SAVEGAME_MBF_NONE;
    279 	directory = "";
    280 	pattern = "";
    281 	postPattern = "";
    282 	requiredSpaceInBytes = 0;
    283 	description.Clear();
    284 	detailList.Clear();
    285 	callbackSignal.Clear();
    286 	errorCode = SAVEGAME_E_NONE;
    287 	inputDeviceId = -1;
    288 	skipErrorDialogMask = 0;
    289 	
    290 	// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
    291 	// these are set once and shouldn't be touched until the processor is re-initialized
    292 	// cancelled = false;
    293 }
    294 
    295 /*
    296 ========================
    297 idSaveLoadParms::SetDefaults
    298 ========================
    299 */
    300 void idSaveLoadParms::SetDefaults( int newInputDevice ) {
    301 	// These are pulled out so SetDefaults() isn't called during global instantiation of objects that have savegame processors
    302 	// in them that then require a session reference.
    303 	Init();	
    304 
    305 	// fill in the user information (inputDeviceId & userId) from the master user
    306 	idLocalUser * user = NULL;
    307 
    308 	if ( newInputDevice != -1 ) {
    309 		user = session->GetSignInManager().GetLocalUserByInputDevice( newInputDevice );
    310 	} else if ( session != NULL ) {
    311 		user = session->GetSignInManager().GetMasterLocalUser();
    312 	}
    313 
    314 	if ( user != NULL ) {
    315 		idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user );
    316 		userId = idStr::Hash( userWin->GetGamerTag() );
    317 		idLib::PrintfIf( saveGame_verbose.GetBool(), "profile userId/gamertag: %s (%d)\n", userWin->GetGamerTag(), userId );
    318 		inputDeviceId = user->GetInputDevice();
    319 	}
    320 }
    321 
    322 /*
    323 ========================
    324 idSaveLoadParms::CancelSaveGameFilePipelines
    325 ========================
    326 */
    327 void idSaveLoadParms::CancelSaveGameFilePipelines() {
    328 	for ( int i = 0; i < files.Num(); i++ ) {
    329 		if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) {
    330 			idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] );
    331 			assert( file != NULL );
    332 
    333 			if ( file->GetMode() == idFile_SaveGamePipelined::WRITE ) {
    334 				// Notify the save game file that all writes failed which will cause all
    335 				// writes on the other end of the pipeline to drop on the floor.
    336 				file->NextWriteBlock( NULL );
    337 			} else if ( file->GetMode() == idFile_SaveGamePipelined::READ ) {
    338 				// Notify end-of-file to the save game file which will cause all
    339 				// reads on the other end of the pipeline to return zero bytes.
    340 				file->NextReadBlock( NULL, 0 );
    341 			}
    342 		}
    343 	}
    344 }
    345 
    346 /*
    347 ========================
    348 idSaveLoadParms::AbortSaveGameFilePipeline
    349 ========================
    350 */
    351 void idSaveLoadParms::AbortSaveGameFilePipeline() {
    352 	for ( int i = 0; i < files.Num(); i++ ) {
    353 		if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) {
    354 			idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] );
    355 			assert( file != NULL );
    356 			file->Abort();
    357 		}
    358 	}
    359 }
    360 
    361 /*
    362 ================================================================================================
    363 idSaveGameProcessor
    364 ================================================================================================
    365 */
    366 
    367 /*
    368 ========================
    369 idSaveGameProcessor::idSaveGameProcessor
    370 ========================
    371 */
    372 idSaveGameProcessor::idSaveGameProcessor() : working( false ), init( false ) {
    373 }
    374 
    375 /*
    376 ========================
    377 idSaveGameProcessor::Init
    378 ========================
    379 */
    380 bool idSaveGameProcessor::Init() {
    381 	if ( !verify( !IsWorking() ) ) {
    382 		idLib::Warning( "[%s] Someone is trying to execute this processor twice, this is really bad!", this->Name() );
    383 		return false;
    384 	}
    385 	
    386 	parms.ResetCancelled();
    387 	parms.SetDefaults();
    388 	savegameLogicTestIterator = 0;
    389 	working = false; 
    390 	init = true;
    391 	completedCallbacks.Clear();
    392 
    393 	return true;
    394 }
    395 
    396 /*
    397 ========================
    398 idSaveGameProcessor::IsThreadFinished
    399 ========================
    400 */
    401 bool idSaveGameProcessor::IsThreadFinished() {
    402 	return parms.callbackSignal.Wait( 0 );
    403 }
    404 
    405 /*
    406 ========================
    407 idSaveGameProcessor::AddCompletedCallback
    408 ========================
    409 */
    410 void idSaveGameProcessor::AddCompletedCallback( const idCallback & callback ) {
    411 	completedCallbacks.Append( callback.Clone() );
    412 }
    413 
    414 /*
    415 ================================================================================================
    416 idSaveGameManager
    417 ================================================================================================
    418 */
    419 
    420 /*
    421 ========================
    422 idSaveGameManager::idSaveGameManager
    423 ========================
    424 */
    425 idSaveGameManager::idSaveGameManager() :
    426 	processor( NULL ),
    427 	cancel( false ),
    428 	startTime( 0 ),
    429 	continueProcessing( false ),
    430 	submittedProcessorHandle( 0 ),
    431 	executingProcessorHandle( 0 ),
    432 	lastExecutedProcessorHandle( 0 ),
    433 	storageAvailable( true ),
    434 	retryFolder( NULL ) {
    435 }
    436 
    437 /*
    438 ========================
    439 idSaveGameManager::~idSaveGameManager
    440 ========================
    441 */
    442 idSaveGameManager::~idSaveGameManager() {	
    443 	processor = NULL;
    444 	enumeratedSaveGames.Clear();
    445 }
    446 
    447 /*
    448 ========================
    449 idSaveGameManager::ExecuteProcessor
    450 ========================
    451 */
    452 saveGameHandle_t idSaveGameManager::ExecuteProcessor( idSaveGameProcessor * processor ) {
    453 	idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s\n", __FUNCTION__, processor->Name() );
    454 
    455 	// may not be running yet, but if we've init'd successfuly, the IsWorking() call should return true if this 
    456 	// method has been called.  You have problems when callees are asking if the processor is done working by using IsWorking()
    457 	// the next frame after they've executed the processor.
    458 	processor->working = true;
    459 
    460 	if ( this->processor != NULL ) {
    461 		if ( !verify( this->processor != processor ) ) {
    462 			idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:1 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
    463 			return processor->GetHandle();
    464 		} else {
    465 			idSaveGameProcessor ** localProcessor = processorQueue.Find( processor );
    466 			if ( !verify( localProcessor == NULL ) ) {
    467 				idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:2 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
    468 				return (*localProcessor)->GetHandle();
    469 			}
    470 		}
    471 	}
    472 
    473 	processorQueue.Append( processor );
    474 	
    475 	// Don't allow processors to start sub-processors.
    476 	// They need to manage their own internal state.
    477 	assert( idLib::IsMainThread() );
    478 
    479 	Sys_InterlockedIncrement( submittedProcessorHandle );
    480 	processor->parms.handle = submittedProcessorHandle;
    481 
    482 	return submittedProcessorHandle;
    483 }
    484 
    485 /*
    486 ========================
    487 idSaveGameManager::ExecuteProcessorAndWait
    488 ========================
    489 */
    490 saveGameHandle_t idSaveGameManager::ExecuteProcessorAndWait( idSaveGameProcessor * processor ) {
    491 	saveGameHandle_t handle = ExecuteProcessor( processor );
    492 	if ( handle == 0 ) {
    493 		return 0;
    494 	}
    495 
    496 	while ( !IsSaveGameCompletedFromHandle( handle ) ) {
    497 		Pump();
    498 		Sys_Sleep( 10 );
    499 	}
    500 
    501 	// One more pump to get the completed callback
    502 	//Pump();
    503 
    504 	return handle;
    505 }
    506 
    507 /*
    508 ========================
    509 idSaveGameManager::WaitForAllProcessors
    510 
    511 Since the load & nextMap processors calls execute map change, we can't wait if they are the only ones in the queue
    512 If there are only resettable processors in the queue or no items in the queue, don't wait.
    513 
    514 We would need to overrideSimpleProcessorCheck if we were sure we had done something that would cause the processors
    515 to bail out nicely.  Something like canceling a disc swap during a loading disc swap dialog...
    516 ========================
    517 */
    518 void idSaveGameManager::WaitForAllProcessors( bool overrideSimpleProcessorCheck ) {
    519 	assert( idLib::IsMainThread() );
    520 
    521 	while ( IsWorking() || ( processorQueue.Num() > 0 ) ) {
    522 
    523 		if ( !overrideSimpleProcessorCheck ) {
    524 			// BEFORE WE WAIT, and potentially hang everything, make sure processors about to be executed won't sit and
    525 			// wait for themselves to complete.
    526 			// Since we pull off simple processors first, we can stop waiting when the processor being executed is not simple
    527 			if ( processor != NULL ) {
    528 				if ( !processor->IsSimpleProcessor() ) {
    529 					break;
    530 				}
    531 			} else if ( !processorQueue[0]->IsSimpleProcessor() ) {
    532 				break;
    533 			}
    534 		}
    535 
    536 		saveThread.WaitForThread();
    537 		Pump();
    538 	}
    539 }
    540 
    541 /*
    542 ========================
    543 idSaveGameManager::CancelAllProcessors
    544 ========================
    545 */
    546 void idSaveGameManager::CancelAllProcessors( const bool forceCancelInFlightProcessor ) {
    547 	assert( idLib::IsMainThread() );
    548 
    549 	cancel = true;
    550 	
    551 	if ( forceCancelInFlightProcessor ) {
    552 		if ( processor != NULL ) {
    553 			processor->GetSignal().Raise();
    554 		}
    555 	}
    556 
    557 	Pump();	// must be called from the main thread
    558 	Clear();
    559 	cancel = false;
    560 }
    561 
    562 /*
    563 ========================
    564 idSaveGameManager::CancelToTerminate
    565 ========================
    566 */
    567 void idSaveGameManager::CancelToTerminate() {
    568 	if ( processor != NULL ) {
    569 		processor->parms.cancelled = true;
    570 		processor->GetSignal().Raise();
    571 		saveThread.WaitForThread();
    572 	}
    573 }
    574 
    575 /*
    576 ========================
    577 idSaveGameManager::DeviceSelectorWaitingOnSaveRetry
    578 ========================
    579 */
    580 bool idSaveGameManager::DeviceSelectorWaitingOnSaveRetry() {
    581 
    582 	if ( retryFolder == NULL ) {
    583 		return false;
    584 	}
    585 
    586 	return ( idStr::Icmp( retryFolder, "GAME-autosave" ) == 0 );
    587 }
    588 
    589 /*
    590 ========================
    591 idSaveGameManager::Set360RetrySaveAfterDeviceSelected
    592 ========================
    593 */
    594 void idSaveGameManager::Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes ) {
    595 	retryFolder = folder;
    596 	retryBytes = bytes;
    597 }
    598 
    599 /*
    600 ========================
    601 idSaveGameManager::ClearRetryInfo
    602 ========================
    603 */
    604 void idSaveGameManager::ClearRetryInfo() {
    605 	retryFolder = NULL;
    606 	retryBytes = 0;
    607 }
    608 
    609 /*
    610 ========================
    611 idSaveGameManager::RetrySave
    612 ========================
    613 */
    614 void idSaveGameManager::RetrySave() {
    615 	if ( DeviceSelectorWaitingOnSaveRetry() && !common->Dialog().HasDialogMsg( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, false ) ) {
    616 		cmdSystem->AppendCommandText( "savegame autosave\n" );
    617 	}
    618 }
    619 
    620 /*
    621 ========================
    622 idSaveGameManager::ShowRetySaveDialog
    623 ========================
    624 */
    625 void idSaveGameManager::ShowRetySaveDialog() {
    626 	ShowRetySaveDialog( retryFolder, retryBytes );
    627 }
    628 
    629 /*
    630 ========================
    631 idSaveGameManager::ShowRetySaveDialog
    632 ========================
    633 */
    634 void idSaveGameManager::ShowRetySaveDialog( const char * folder, const int64 bytes ) {
    635 
    636 	idStaticList< idSWFScriptFunction *, 4 > callbacks;
    637 	idStaticList< idStrId, 4 > optionText;
    638 
    639 	class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted {
    640 	public:
    641 		idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) {
    642 			common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE );
    643 			session->GetSaveGameManager().ClearRetryInfo();
    644 			return idSWFScriptVar();
    645 		}
    646 	};
    647 
    648 	callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() );
    649 	optionText.Append( idStrId( "#str_dlg_continue_without_saving" ) );
    650 
    651 
    652 
    653 	// build custom space required string
    654 	// #str_dlg_space_required ~= "There is insufficient storage available.  Please free %s and try again."
    655 	idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString();
    656 	idStr size;
    657 	if ( bytes > ( 1024 * 1024 ) ) {
    658 		const float roundUp = ( ( 1024.0f * 1024.0f / 10.0f )- 1.0f );
    659 		size = va( "%.1f MB", ( roundUp + (float) bytes ) / ( 1024.0f * 1024.0f ) );
    660 	} else {
    661 		const float roundUp = 1024.0f - 1.0f;
    662 		size = va( "%.0f KB", ( roundUp + (float) bytes ) / 1024.0f );
    663 	}
    664 	idStr msg = va( format.c_str(), size.c_str() );
    665 
    666 	common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg, true );
    667 }
    668 
    669 /*
    670 ========================
    671 idSaveGameManager::CancelWithHandle
    672 ========================
    673 */
    674 void idSaveGameManager::CancelWithHandle( const saveGameHandle_t & handle ) {
    675 	if ( handle == 0 || IsSaveGameCompletedFromHandle( handle ) ) {
    676 		return;
    677 	}
    678 
    679 	// check processor in flight first
    680 	if ( processor != NULL ) {
    681 		if ( processor->GetHandle() == handle ) {
    682 			processor->Cancel();
    683 			return;
    684 		}
    685 	}
    686 
    687 	// remove from queue
    688 	for ( int i = 0; i < processorQueue.Num(); ++i ) {
    689 		if ( processorQueue[i]->GetHandle() == handle ) {
    690 			processorQueue[i]->Cancel();
    691 			return;
    692 		}
    693 	}
    694 }
    695 
    696 /*
    697 ========================
    698 idSaveGameManager::StartNextProcessor
    699 
    700 Get the next not-reset-capable processor.  If there aren't any left, just get what's next.
    701 ========================
    702 */
    703 void idSaveGameManager::StartNextProcessor() {
    704 	if ( cancel ) {
    705 		return;
    706 	}
    707 
    708 	idSaveGameProcessor * nextProcessor = NULL;
    709 	int index = 0;
    710 
    711 	// pick off the first simple processor
    712 	for ( int i = 0; i < processorQueue.Num(); ++i ) {
    713 		if ( processorQueue[i]->IsSimpleProcessor() ) {
    714 			index = i;
    715 			break;
    716 		}
    717 	}
    718 
    719 	if ( processorQueue.Num() > 0 ) {
    720 		nextProcessor = processorQueue[index];
    721 
    722 
    723 		Sys_InterlockedIncrement( executingProcessorHandle );
    724 
    725 		processorQueue.RemoveIndex( index );
    726 		processor = nextProcessor;
    727 		processor->parms.callbackSignal.Raise();	// signal that the thread is ready for work
    728 		startTime = Sys_Milliseconds();
    729 	}
    730 }
    731 
    732 /*
    733 ========================
    734 idSaveGameManager::FinishProcessor
    735 ========================
    736 */
    737 void idSaveGameManager::FinishProcessor( idSaveGameProcessor * localProcessor ) {
    738 
    739 	assert( localProcessor != NULL );
    740 	idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s, %d ms\n", __FUNCTION__, localProcessor->Name(), Sys_Milliseconds() - startTime );
    741 
    742 	// This will delete from the files set for auto-deletion
    743 	// Don't remove files not set for auto-deletion, they may be used outside of the savegame manager by game-side callbacks for example
    744 	for ( int i = ( localProcessor->parms.files.Num() - 1 ); i >= 0; --i ) {
    745 		if ( localProcessor->parms.files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) {
    746 			delete localProcessor->parms.files[i];
    747 			localProcessor->parms.files.RemoveIndexFast( i );
    748 		}
    749 	}
    750 
    751 	localProcessor->init = false;
    752 	localProcessor = NULL;
    753 }
    754 
    755 /*
    756 ========================
    757 idSaveGameManager::Clear
    758 ========================
    759 */
    760 void idSaveGameManager::Clear() {
    761 	processorQueue.Clear();
    762 }
    763 
    764 /*
    765 ========================
    766 idSaveGameManager::IsWorking
    767 ========================
    768 */
    769 bool idSaveGameManager::IsWorking() const {
    770 	return processor != NULL;
    771 }
    772 
    773 /*
    774 ========================
    775 idSaveGameManager::Pump
    776 
    777 Important sections called out with -- EXTRA LARGE -- comments!
    778 ========================
    779 */
    780 void idSaveGameManager::Pump() {
    781 
    782 
    783 
    784 	// After a processor is done, the next is pulled off the queue so the only way the manager isn't working is if
    785 	// there isn't something executing or in the queue.
    786 	if ( !IsWorking() ) {
    787 		// Unified start to initialize system on PS3 and do appropriate checks for system combination issues
    788 		// ------------------------------------
    789 		// START
    790 		// ------------------------------------
    791 		StartNextProcessor();
    792 
    793 		if ( !IsWorking() ) {
    794 			return;
    795 		}
    796 
    797 		continueProcessing = true;
    798 	}
    799 
    800 	if ( cancel ) {
    801 		processor->parms.AbortSaveGameFilePipeline();
    802 	}
    803 
    804 	// Quickly checks to see if the savegame thread is done, otherwise, exit and continue frame commands
    805 	if ( processor->IsThreadFinished() ) {
    806 		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() );
    807 
    808 		if ( !cancel && continueProcessing ) {
    809 			// Check for available storage unit
    810 			if ( session->GetSignInManager().GetMasterLocalUser() != NULL ) {
    811 				if ( !session->GetSignInManager().GetMasterLocalUser()->IsStorageDeviceAvailable() ) {
    812 					// this will not allow further processing
    813 					processor->parms.errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE;
    814 				}
    815 			}
    816 
    817 			// Execute Process() on the processor, if there was an error in a previous Process() call, give the
    818 			// processor the chance to validate that error and either clean itself up or convert it to another error or none.
    819 			if ( processor->GetError() == SAVEGAME_E_NONE || processor->ValidateLastError() ) {
    820 				idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::Process(), error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() );
    821 
    822 				// ------------------------------------
    823 				// PROCESS
    824 				// ------------------------------------
    825 				continueProcessing = processor->Process();
    826 
    827 				// If we don't return here, the completedCallback will be executed before it's done with it's async operation
    828 				// during it's last process stage.
    829 				return;
    830 			} else { 
    831 				continueProcessing = false;
    832 			}
    833 		}
    834 
    835 		// This section does specific post-processing for each of the save commands
    836 		if ( !continueProcessing ) {
    837 
    838 			// Clear out details if we detect corruption but keep directory/slot information
    839 			for ( int i = 0; i < processor->parms.detailList.Num(); ++i ) {
    840 				idSaveGameDetails & details = processor->parms.detailList[i];
    841 				if ( details.damaged ) {
    842 					details.descriptors.Clear();
    843 				}
    844 			}
    845 
    846 			idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::CompletedCallback()\n", __FUNCTION__, processor->Name() );
    847 			processor->working = false;
    848 
    849 			// This ensures that the savegame manager will believe the processor is done when there is a potentially
    850 			// catastrophic thing that will happen within CompletedCallback which might try to sync all threads
    851 			// The most common case of this is executing a map change (which we no longer do).
    852 			// We flush the heap and wait for all background processes to finish.  After all this is called, we will 
    853 			// cleanup the old processor within FinishProcessor()
    854 			idSaveGameProcessor * localProcessor = processor;
    855 			processor = NULL;
    856 
    857 			// ------------------------------------
    858 			// COMPLETEDCALLBACK
    859 			// At this point, the handle will be completed
    860 			// ------------------------------------
    861 			Sys_InterlockedIncrement( lastExecutedProcessorHandle );
    862 			
    863 			for ( int i = 0; i < localProcessor->completedCallbacks.Num(); i++ ) {
    864 				localProcessor->completedCallbacks[i]->Call();
    865 			}
    866 			localProcessor->completedCallbacks.DeleteContents( true );
    867 
    868 			// ------------------------------------
    869 			// FINISHPROCESSOR
    870 			// ------------------------------------
    871 			FinishProcessor( localProcessor );
    872 		}
    873 	} else if ( processor->ShouldTimeout() ) {
    874 		// Hack for the PS3 threading hang
    875 		idLib::PrintfIf( saveGame_verbose.GetBool(), "----- PROCESSOR TIMEOUT ----- (%s)\n", processor->Name() );
    876 
    877 		idSaveGameProcessor * tempProcessor = processor;
    878 
    879 		CancelAllProcessors( true );
    880 
    881 		class idSWFScriptFunction_TryAgain : public idSWFScriptFunction_RefCounted {
    882 		public:
    883 			idSWFScriptFunction_TryAgain( idSaveGameManager * manager, idSaveGameProcessor * processor ) {
    884 				this->manager = manager;
    885 				this->processor = processor; 
    886 			}
    887 			idSWFScriptVar Call ( idSWFScriptObject * thisObject, const idSWFParmList & parms ) {
    888 				common->Dialog().ClearDialog( GDM_ERROR_SAVING_SAVEGAME );
    889 				manager->ExecuteProcessor( processor );
    890 				return idSWFScriptVar();
    891 			}
    892 		private:
    893 			idSaveGameManager * manager;
    894 			idSaveGameProcessor * processor;
    895 		};
    896 
    897 		idStaticList< idSWFScriptFunction *, 4 > callbacks;
    898 		idStaticList< idStrId, 4 > optionText;
    899 		callbacks.Append( new (TAG_SWF) idSWFScriptFunction_TryAgain( this, tempProcessor ) );
    900 		optionText.Append( idStrId( "#STR_SWF_RETRY" ) );
    901 		common->Dialog().AddDynamicDialog( GDM_ERROR_SAVING_SAVEGAME, callbacks, optionText, true, "" );
    902 	}
    903 }