DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

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 }