win_savegame.cpp (22680B)
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 29 #pragma hdrstop 30 #include "../../idlib/precompiled.h" 31 #include "../sys_session_local.h" 32 #include "../sys_savegame.h" 33 34 idCVar savegame_winInduceDelay( "savegame_winInduceDelay", "0", CVAR_INTEGER, "on windows, this is a delay induced before any file operation occurs" ); 35 extern idCVar fs_savepath; 36 extern idCVar saveGame_checksum; 37 extern idCVar savegame_error; 38 39 #define SAVEGAME_SENTINAL 0x12358932 40 41 /* 42 ======================== 43 void Sys_ExecuteSavegameCommandAsync 44 ======================== 45 */ 46 void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms ) { 47 assert( savegameParms != NULL ); 48 49 session->GetSaveGameManager().GetSaveGameThread().data.saveLoadParms = savegameParms; 50 51 if ( session->GetSaveGameManager().GetSaveGameThread().GetThreadHandle() == 0 ) { 52 session->GetSaveGameManager().GetSaveGameThread().StartWorkerThread( "Savegame", CORE_ANY ); 53 } 54 55 session->GetSaveGameManager().GetSaveGameThread().SignalWork(); 56 } 57 58 /* 59 ======================== 60 idLocalUser * GetLocalUserFromUserId 61 ======================== 62 */ 63 idLocalUserWin * GetLocalUserFromSaveParms( const saveGameThreadArgs_t & data ) { 64 if ( ( data.saveLoadParms != NULL) && ( data.saveLoadParms->inputDeviceId >= 0 ) ) { 65 idLocalUser * user = session->GetSignInManager().GetLocalUserByInputDevice( data.saveLoadParms->inputDeviceId ); 66 if ( user != NULL ) { 67 idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user ); 68 if ( userWin != NULL && data.saveLoadParms->userId == idStr::Hash( userWin->GetGamerTag() ) ) { 69 return userWin; 70 } 71 } 72 } 73 74 return NULL; 75 } 76 77 /* 78 ======================== 79 idSaveGameThread::SaveGame 80 ======================== 81 */ 82 int idSaveGameThread::Save() { 83 idLocalUserWin * user = GetLocalUserFromSaveParms( data ); 84 if ( user == NULL ) { 85 data.saveLoadParms->errorCode = SAVEGAME_E_INVALID_USER; 86 return -1; 87 } 88 89 idSaveLoadParms * callback = data.saveLoadParms; 90 idStr saveFolder = "savegame"; 91 92 saveFolder.AppendPath( callback->directory ); 93 94 // Check for the required storage space. 95 int64 requiredSizeBytes = 0; 96 { 97 for ( int i = 0; i < callback->files.Num(); i++ ) { 98 idFile_SaveGame * file = callback->files[i]; 99 requiredSizeBytes += ( file->Length() + sizeof( unsigned int ) ); // uint for checksum 100 if ( file->type == SAVEGAMEFILE_PIPELINED ) { 101 requiredSizeBytes += MIN_SAVEGAME_SIZE_BYTES; 102 } 103 } 104 } 105 106 int ret = ERROR_SUCCESS; 107 108 // Check size of previous files if needed 109 // ALL THE FILES RIGHT NOW---- could use pattern later... 110 idStrList filesToDelete; 111 if ( ( callback->mode & SAVEGAME_MBF_DELETE_FILES ) && !callback->cancelled ) { 112 if ( fileSystem->IsFolder( saveFolder.c_str(), "fs_savePath" ) == FOLDER_YES ) { 113 idFileList * files = fileSystem->ListFilesTree( saveFolder.c_str(), "*.*" ); 114 for ( int i = 0; i < files->GetNumFiles(); i++ ) { 115 requiredSizeBytes -= fileSystem->GetFileLength( files->GetFile( i ) ); 116 filesToDelete.Append( files->GetFile( i ) ); 117 } 118 fileSystem->FreeFileList( files ); 119 } 120 } 121 122 // Inform user about size required if necessary 123 if ( requiredSizeBytes > 0 && !callback->cancelled ) { 124 user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); 125 if ( callback->requiredSpaceInBytes > 0 ) { 126 // check to make sure savepath actually exists before erroring 127 idStr directory = fs_savepath.GetString(); 128 directory += "\\"; // so it doesn't think the last part is a file and ignores in the directory creation 129 fileSystem->CreateOSPath( directory ); // we can't actually check FileExists in production builds, so just try to create it 130 user->StorageSizeAvailable( requiredSizeBytes, callback->requiredSpaceInBytes ); 131 132 if ( callback->requiredSpaceInBytes > 0 ) { 133 callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; 134 // safe to return, haven't written any files yet 135 return -1; 136 } 137 } 138 } 139 140 // Delete all previous files if needed 141 // ALL THE FILES RIGHT NOW---- could use pattern later... 142 for ( int i = 0; i < filesToDelete.Num() && !callback->cancelled; i++ ) { 143 fileSystem->RemoveFile( filesToDelete[i].c_str() ); 144 } 145 146 // Save the raw files. 147 for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { 148 idFile_SaveGame * file = callback->files[i]; 149 150 idStr fileName = saveFolder; 151 fileName.AppendPath( file->GetName() ); 152 idStr tempFileName = va( "%s.temp", fileName.c_str() ); 153 154 idFile * outputFile = fileSystem->OpenFileWrite( tempFileName, "fs_savePath" ); 155 if ( outputFile == NULL ) { 156 idLib::Warning( "[%s]: Couldn't open file for writing, %s. Error = %08x", __FUNCTION__, tempFileName.c_str(), GetLastError() ); 157 file->error = true; 158 callback->errorCode = SAVEGAME_E_UNKNOWN; 159 ret = -1; 160 continue; 161 } 162 163 if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { 164 165 idFile_SaveGamePipelined * inputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); 166 assert( inputFile != NULL ); 167 168 blockForIO_t block; 169 while ( inputFile->NextWriteBlock( & block ) ) { 170 if ( (size_t)outputFile->Write( block.data, block.bytes ) != block.bytes ) { 171 idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); 172 file->error = true; 173 callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; 174 ret = -1; 175 break; 176 } 177 } 178 179 } else { 180 181 if ( ( file->type & SAVEGAMEFILE_BINARY ) || ( file->type & SAVEGAMEFILE_COMPRESSED ) ) { 182 if ( saveGame_checksum.GetBool() ) { 183 unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); 184 size_t size = outputFile->WriteBig( checksum ); 185 if ( size != sizeof( checksum ) ) { 186 idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); 187 file->error = true; 188 callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; 189 ret = -1; 190 } 191 } 192 } 193 194 size_t size = outputFile->Write( file->GetDataPtr(), file->Length() ); 195 if ( size != (size_t)file->Length() ) { 196 idLib::Warning( "[%s]: Write failed. Error = %08x", __FUNCTION__, GetLastError() ); 197 file->error = true; 198 callback->errorCode = SAVEGAME_E_INSUFFICIENT_ROOM; 199 ret = -1; 200 } else { 201 idLib::PrintfIf( saveGame_verbose.GetBool(), "Saved %s (%s)\n", fileName.c_str(), outputFile->GetFullPath() ); 202 } 203 } 204 205 delete outputFile; 206 207 if ( ret == ERROR_SUCCESS ) { 208 // Remove the old file 209 if ( !fileSystem->RenameFile( tempFileName, fileName, "fs_savePath" ) ) { 210 idLib::Warning( "Could not start to rename temporary file %s to %s.", tempFileName.c_str(), fileName.c_str() ); 211 } 212 } else { 213 fileSystem->RemoveFile( tempFileName ); 214 idLib::Warning( "Invalid write to temporary file %s.", tempFileName.c_str() ); 215 } 216 } 217 218 if ( data.saveLoadParms->cancelled ) { 219 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 220 } 221 222 // Removed because it seemed a bit drastic 223 #if 0 224 // If there is an error, delete the partially saved folder 225 if ( callback->errorCode != SAVEGAME_E_NONE ) { 226 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { 227 idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); 228 for ( int i = 0; i < files->GetNumFiles(); i++ ) { 229 fileSystem->RemoveFile( files->GetFile( i ) ); 230 } 231 fileSystem->FreeFileList( files ); 232 fileSystem->RemoveDir( saveFolder ); 233 } 234 } 235 #endif 236 237 return ret; 238 } 239 240 /* 241 ======================== 242 idSessionLocal::LoadGame 243 ======================== 244 */ 245 int idSaveGameThread::Load() { 246 idSaveLoadParms * callback = data.saveLoadParms; 247 idStr saveFolder = "savegame"; 248 249 saveFolder.AppendPath( callback->directory ); 250 251 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) != FOLDER_YES ) { 252 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 253 return -1; 254 } 255 256 int ret = ERROR_SUCCESS; 257 for ( int i = 0; i < callback->files.Num() && ret == ERROR_SUCCESS && !callback->cancelled; i++ ) { 258 idFile_SaveGame * file = callback->files[i]; 259 260 idStr filename = saveFolder; 261 filename.AppendPath( file->GetName() ); 262 263 idFile * inputFile = fileSystem->OpenFileRead( filename.c_str() ); 264 if ( inputFile == NULL ) { 265 file->error = true; 266 if ( !( file->type & SAVEGAMEFILE_OPTIONAL ) ) { 267 callback->errorCode = SAVEGAME_E_CORRUPTED; 268 ret = -1; 269 } 270 continue; 271 } 272 273 if ( ( file->type & SAVEGAMEFILE_PIPELINED ) != 0 ) { 274 275 idFile_SaveGamePipelined * outputFile = dynamic_cast< idFile_SaveGamePipelined * >( file ); 276 assert( outputFile != NULL ); 277 278 size_t lastReadBytes = 0; 279 blockForIO_t block; 280 while ( outputFile->NextReadBlock( &block, lastReadBytes ) && !callback->cancelled ) { 281 lastReadBytes = inputFile->Read( block.data, block.bytes ); 282 if ( lastReadBytes != block.bytes ) { 283 // Notify end-of-file to the save game file which will cause all reads on the 284 // other end of the pipeline to return zero bytes after the pipeline is drained. 285 outputFile->NextReadBlock( NULL, lastReadBytes ); 286 break; 287 } 288 } 289 290 } else { 291 292 size_t size = inputFile->Length(); 293 294 unsigned int originalChecksum = 0; 295 if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { 296 if ( saveGame_checksum.GetBool() ) { 297 if ( size >= sizeof( originalChecksum ) ) { 298 inputFile->ReadBig( originalChecksum ); 299 size -= sizeof( originalChecksum ); 300 } 301 } 302 } 303 304 file->SetLength( size ); 305 306 size_t sizeRead = inputFile->Read( (void *)file->GetDataPtr(), size ); 307 if ( sizeRead != size ) { 308 file->error = true; 309 callback->errorCode = SAVEGAME_E_CORRUPTED; 310 ret = -1; 311 } 312 313 if ( ( file->type & SAVEGAMEFILE_BINARY ) != 0 || ( file->type & SAVEGAMEFILE_COMPRESSED ) != 0 ) { 314 if ( saveGame_checksum.GetBool() ) { 315 unsigned int checksum = MD5_BlockChecksum( file->GetDataPtr(), file->Length() ); 316 if ( checksum != originalChecksum ) { 317 file->error = true; 318 callback->errorCode = SAVEGAME_E_CORRUPTED; 319 ret = -1; 320 } 321 } 322 } 323 } 324 325 delete inputFile; 326 } 327 328 if ( data.saveLoadParms->cancelled ) { 329 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 330 } 331 332 return ret; 333 } 334 335 /* 336 ======================== 337 idSaveGameThread::Delete 338 339 This deletes a complete savegame directory 340 ======================== 341 */ 342 int idSaveGameThread::Delete() { 343 idSaveLoadParms * callback = data.saveLoadParms; 344 idStr saveFolder = "savegame"; 345 346 saveFolder.AppendPath( callback->directory ); 347 348 int ret = ERROR_SUCCESS; 349 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { 350 idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); 351 for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { 352 fileSystem->RemoveFile( files->GetFile( i ) ); 353 } 354 fileSystem->FreeFileList( files ); 355 356 fileSystem->RemoveDir( saveFolder ); 357 } else { 358 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 359 ret = -1; 360 } 361 362 if ( data.saveLoadParms->cancelled ) { 363 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 364 } 365 366 return ret; 367 } 368 369 /* 370 ======================== 371 idSaveGameThread::Enumerate 372 ======================== 373 */ 374 int idSaveGameThread::Enumerate() { 375 idSaveLoadParms * callback = data.saveLoadParms; 376 idStr saveFolder = "savegame"; 377 378 callback->detailList.Clear(); 379 380 int ret = ERROR_SUCCESS; 381 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { 382 idFileList * files = fileSystem->ListFilesTree( saveFolder, SAVEGAME_DETAILS_FILENAME ); 383 const idStrList & fileList = files->GetList(); 384 385 for ( int i = 0; i < fileList.Num() && !callback->cancelled; i++ ) { 386 idSaveGameDetails * details = callback->detailList.Alloc(); 387 // We have more folders on disk than we have room in our save detail list, stop trying to read them in and continue with what we have 388 if ( details == NULL ) { 389 break; 390 } 391 idStr directory = fileList[i]; 392 393 idFile * file = fileSystem->OpenFileRead( directory.c_str() ); 394 395 if ( file != NULL ) { 396 // Read the DETAIL file for the enumerated data 397 if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) { 398 if ( !SavegameReadDetailsFromFile( file, *details ) ) { 399 details->damaged = true; 400 ret = -1; 401 } 402 } 403 404 // Use the date from the directory 405 WIN32_FILE_ATTRIBUTE_DATA attrData; 406 BOOL attrRet = GetFileAttributesEx( file->GetFullPath(), GetFileExInfoStandard, &attrData ); 407 delete file; 408 if ( attrRet == TRUE ) { 409 FILETIME lastWriteTime = attrData.ftLastWriteTime; 410 const ULONGLONG second = 10000000L; // One second = 10,000,000 * 100 nsec 411 SYSTEMTIME base_st = { 1970, 1, 0, 1, 0, 0, 0, 0 }; 412 ULARGE_INTEGER itime; 413 FILETIME base_ft; 414 BOOL success = SystemTimeToFileTime( &base_st, &base_ft ); 415 416 itime.QuadPart = ((ULARGE_INTEGER *)&lastWriteTime)->QuadPart; 417 if ( success ) { 418 itime.QuadPart -= ((ULARGE_INTEGER *)&base_ft)->QuadPart; 419 } else { 420 // Hard coded number of 100-nanosecond units from 1/1/1601 to 1/1/1970 421 itime.QuadPart -= 116444736000000000LL; 422 } 423 itime.QuadPart /= second; 424 details->date = itime.QuadPart; 425 } 426 } else { 427 details->damaged = true; 428 } 429 430 // populate the game details struct 431 directory = directory.StripFilename(); 432 details->slotName = directory.c_str() + saveFolder.Length() + 1; // Strip off the prefix too 433 // JDC: I hit this all the time assert( fileSystem->IsFolder( directory.c_str(), "fs_savePath" ) == FOLDER_YES ); 434 } 435 fileSystem->FreeFileList( files ); 436 } else { 437 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 438 ret = -3; 439 } 440 441 if ( data.saveLoadParms->cancelled ) { 442 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 443 } 444 445 return ret; 446 } 447 448 /* 449 ======================== 450 idSaveGameThread::EnumerateFiles 451 ======================== 452 */ 453 int idSaveGameThread::EnumerateFiles() { 454 idSaveLoadParms * callback = data.saveLoadParms; 455 idStr folder = "savegame"; 456 457 folder.AppendPath( callback->directory ); 458 459 callback->files.Clear(); 460 461 int ret = ERROR_SUCCESS; 462 if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) { 463 // get listing of all the files, but filter out below 464 idFileList * files = fileSystem->ListFilesTree( folder, "*.*" ); 465 466 // look for the instance pattern 467 for ( int i = 0; i < files->GetNumFiles() && ret == 0 && !callback->cancelled; i++ ) { 468 idStr fullFilename = files->GetFile( i ); 469 idStr filename = fullFilename; 470 filename.StripPath(); 471 472 if ( filename.IcmpPrefix( callback->pattern ) != 0 ) { 473 continue; 474 } 475 if ( !callback->postPattern.IsEmpty() && filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) { 476 continue; 477 } 478 479 // Read the DETAIL file for the enumerated data 480 if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) { 481 idSaveGameDetails & details = callback->description; 482 idFile * uncompressed = fileSystem->OpenFileRead( fullFilename.c_str() ); 483 484 if ( uncompressed == NULL ) { 485 details.damaged = true; 486 } else { 487 if ( !SavegameReadDetailsFromFile( uncompressed, details ) ) { 488 ret = -1; 489 } 490 491 delete uncompressed; 492 } 493 494 // populate the game details struct 495 details.slotName = callback->directory; 496 assert( fileSystem->IsFolder( details.slotName, "fs_savePath" ) == FOLDER_YES ); 497 } 498 499 idFile_SaveGame * file = new (TAG_SAVEGAMES) idFile_SaveGame( filename, SAVEGAMEFILE_AUTO_DELETE ); 500 callback->files.Append( file ); 501 } 502 fileSystem->FreeFileList( files ); 503 } else { 504 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 505 ret = -3; 506 } 507 508 if ( data.saveLoadParms->cancelled ) { 509 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 510 } 511 512 return ret; 513 } 514 515 /* 516 ======================== 517 idSaveGameThread::DeleteFiles 518 ======================== 519 */ 520 int idSaveGameThread::DeleteFiles() { 521 idSaveLoadParms * callback = data.saveLoadParms; 522 idStr folder = "savegame"; 523 524 folder.AppendPath( callback->directory ); 525 526 // delete the explicitly requested files first 527 for ( int j = 0; j < callback->files.Num() && !callback->cancelled; ++j ) { 528 idFile_SaveGame * file = callback->files[j]; 529 idStr fullpath = folder; 530 fullpath.AppendPath( file->GetName() ); 531 fileSystem->RemoveFile( fullpath ); 532 } 533 534 int ret = ERROR_SUCCESS; 535 if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) { 536 // get listing of all the files, but filter out below 537 idFileList * files = fileSystem->ListFilesTree( folder, "*.*" ); 538 539 // look for the instance pattern 540 for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { 541 idStr filename = files->GetFile( i ); 542 filename.StripPath(); 543 544 // If there are post/pre patterns to match, make sure we adhere to the patterns 545 if ( callback->pattern.IsEmpty() || ( filename.IcmpPrefix( callback->pattern ) != 0 ) ) { 546 continue; 547 } 548 if ( callback->postPattern.IsEmpty() || ( filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) ) { 549 continue; 550 } 551 552 fileSystem->RemoveFile( files->GetFile( i ) ); 553 } 554 fileSystem->FreeFileList( files ); 555 } else { 556 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 557 ret = -3; 558 } 559 560 if ( data.saveLoadParms->cancelled ) { 561 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 562 } 563 564 return ret; 565 } 566 567 /* 568 ======================== 569 idSaveGameThread::DeleteAll 570 571 This deletes all savegame directories 572 ======================== 573 */ 574 int idSaveGameThread::DeleteAll() { 575 idSaveLoadParms * callback = data.saveLoadParms; 576 idStr saveFolder = "savegame"; 577 int ret = ERROR_SUCCESS; 578 579 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { 580 idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" ); 581 // remove directories after files 582 for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) { 583 // contained files should always be first 584 if ( fileSystem->IsFolder( files->GetFile( i ), "fs_savePath" ) == FOLDER_YES ) { 585 fileSystem->RemoveDir( files->GetFile( i ) ); 586 } else { 587 fileSystem->RemoveFile( files->GetFile( i ) ); 588 } 589 } 590 fileSystem->FreeFileList( files ); 591 } else { 592 callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND; 593 ret = -3; 594 } 595 596 if ( data.saveLoadParms->cancelled ) { 597 data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED; 598 } 599 600 return ret; 601 } 602 603 /* 604 ======================== 605 idSaveGameThread::Run 606 ======================== 607 */ 608 int idSaveGameThread::Run() { 609 int ret = ERROR_SUCCESS; 610 611 try { 612 idLocalUserWin * user = GetLocalUserFromSaveParms( data ); 613 if ( user != NULL && !user->IsStorageDeviceAvailable() ) { 614 data.saveLoadParms->errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE; 615 } 616 617 if ( savegame_winInduceDelay.GetInteger() > 0 ) { 618 Sys_Sleep( savegame_winInduceDelay.GetInteger() ); 619 } 620 621 if ( data.saveLoadParms->mode & SAVEGAME_MBF_SAVE ) { 622 ret = Save(); 623 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_LOAD ) { 624 ret = Load(); 625 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE ) { 626 ret = Enumerate(); 627 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FOLDER ) { 628 ret = Delete(); 629 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_ALL_FOLDERS ) { 630 ret = DeleteAll(); 631 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FILES ) { 632 ret = DeleteFiles(); 633 } else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE_FILES ) { 634 ret = EnumerateFiles(); 635 } 636 637 // if something failed and no one set an error code, do it now. 638 if ( ret != 0 && data.saveLoadParms->errorCode == SAVEGAME_E_NONE ) { 639 data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN; 640 } 641 } catch ( ... ) { 642 // if anything horrible happens, leave it up to the savegame processors to handle in PostProcess(). 643 data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN; 644 } 645 646 // Make sure to cancel any save game file pipelines. 647 if ( data.saveLoadParms->errorCode != SAVEGAME_E_NONE ) { 648 data.saveLoadParms->CancelSaveGameFilePipelines(); 649 } 650 651 // Override error if cvar set 652 if ( savegame_error.GetInteger() != 0 ) { 653 data.saveLoadParms->errorCode = (saveGameError_t)savegame_error.GetInteger(); 654 } 655 656 // Tell the waiting caller that we are done 657 data.saveLoadParms->callbackSignal.Raise(); 658 659 return ret; 660 } 661 662 /* 663 ======================== 664 Sys_SaveGameCheck 665 ======================== 666 */ 667 void Sys_SaveGameCheck( bool & exists, bool & autosaveExists ) { 668 exists = false; 669 autosaveExists = false; 670 671 const idStr autosaveFolderStr = AddSaveFolderPrefix( SAVEGAME_AUTOSAVE_FOLDER, idSaveGameManager::PACKAGE_GAME ); 672 const char * autosaveFolder = autosaveFolderStr.c_str(); 673 const char * saveFolder = "savegame"; 674 675 if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) { 676 idFileList * files = fileSystem->ListFiles( saveFolder, "/" ); 677 const idStrList & fileList = files->GetList(); 678 679 idLib::PrintfIf( saveGame_verbose.GetBool(), "found %d savegames\n", fileList.Num() ); 680 681 for ( int i = 0; i < fileList.Num(); i++ ) { 682 const char * directory = va( "%s/%s", saveFolder, fileList[i].c_str() ); 683 684 if ( fileSystem->IsFolder( directory, "fs_savePath" ) == FOLDER_YES ) { 685 exists = true; 686 687 idLib::PrintfIf( saveGame_verbose.GetBool(), "found savegame: %s\n", fileList[i].c_str() ); 688 689 if ( idStr::Icmp( fileList[i].c_str(), autosaveFolder ) == 0 ) { 690 autosaveExists = true; 691 break; 692 } 693 } 694 } 695 696 fileSystem->FreeFileList( files ); 697 } 698 }