sys_savegame.cpp 27 KB

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