doom3-bfg/neo/sys/sys_savegame.h
2012-11-26 12:58:24 -06:00

483 lines
21 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_SAVEGAME_H__
#define __SYS_SAVEGAME_H__
#ifdef OUTPUT_FUNC
#undef OUTPUT_FUNC
#endif
#ifdef OUTPUT_FUNC_EXIT
#undef OUTPUT_FUNC_EXIT
#endif
#define OUTPUT_FUNC() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Enter\n", __FUNCTION__ )
#define OUTPUT_FUNC_EXIT() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Exit\n", __FUNCTION__ )
#define DEFINE_CLASS( x ) virtual const char * Name() const { return #x; }
#define MAX_SAVEGAMES 16
#define MAX_FILES_WITHIN_SAVEGAME 10
#define MIN_SAVEGAME_SIZE_BYTES ( 4 * 1024 * 1024 )
#define MAX_SAVEGAME_STRING_TABLE_SIZE 400 * 1024 // 400 kB max string table size
#define MAX_FILENAME_LENGTH 255
#define MAX_FILENAME_LENGTH_PATTERN 8
#define MAX_FOLDER_NAME_LENGTH 64
#define SAVEGAME_DETAILS_FILENAME "game.details"
// PS3 restrictions: The only characters that can be used are 0-9 (numbers), A-Z (uppercase alphabet), "_" (underscore), and "-" (hyphen)
#define SAVEGAME_AUTOSAVE_FOLDER "AUTOSAVE" // auto save slot
// common descriptors for savegame description fields
#define SAVEGAME_DETAIL_FIELD_EXPANSION "expansion"
#define SAVEGAME_DETAIL_FIELD_MAP "mapName"
#define SAVEGAME_DETAIL_FIELD_MAP_LOCATE "mapLocation"
#define SAVEGAME_DETAIL_FIELD_DIFFICULTY "difficulty"
#define SAVEGAME_DETAIL_FIELD_PLAYTIME "playTime"
#define SAVEGAME_DETAIL_FIELD_LANGUAGE "language"
#define SAVEGAME_DETAIL_FIELD_SAVE_VERSION "saveVersion"
#define SAVEGAME_DETAIL_FIELD_CHECKSUM "checksum"
#define SAVEGAME_GAME_DIRECTORY_PREFIX "GAME-"
#define SAVEGAME_PROFILE_DIRECTORY_PREFIX ""
#define SAVEGAME_RAW_DIRECTORY_PREFIX ""
extern idCVar saveGame_verbose;
extern idCVar saveGame_enable;
class idGameSpawnInfo;
class idSession;
class idSessionLocal;
class idSaveGameManager;
// Specific savegame sub-system errors
enum saveGameError_t {
SAVEGAME_E_NONE = 0,
SAVEGAME_E_CANCELLED = BIT( 0 ),
SAVEGAME_E_INSUFFICIENT_ROOM = BIT( 1 ),
SAVEGAME_E_CORRUPTED = BIT( 2 ),
SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE = BIT( 3 ),
SAVEGAME_E_UNKNOWN = BIT( 4 ),
SAVEGAME_E_INVALID_FILENAME = BIT( 5 ),
SAVEGAME_E_STEAM_ERROR = BIT( 6 ),
SAVEGAME_E_FOLDER_NOT_FOUND = BIT( 7 ),
SAVEGAME_E_FILE_NOT_FOUND = BIT( 8 ),
SAVEGAME_E_DLC_NOT_FOUND = BIT( 9 ),
SAVEGAME_E_INVALID_USER = BIT( 10 ),
SAVEGAME_E_PROFILE_TOO_BIG = BIT( 11 ),
SAVEGAME_E_DISC_SWAP = BIT( 12 ),
SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION = BIT( 13 ),
SAVEGAME_E_BITS_USED = 14,
SAVEGAME_E_NUM = SAVEGAME_E_BITS_USED + 1 // because we're counting "none"
};
// Modes to control behavior of savegame manager
enum saveGameModeBitfield_t {
SAVEGAME_MBF_NONE = 0,
SAVEGAME_MBF_LOAD = BIT( 0 ), // standard file load (can be individual/multiple files described in parms)
SAVEGAME_MBF_SAVE = BIT( 1 ), // standard file save (can be individual/multiple files described in parms)
SAVEGAME_MBF_DELETE_FOLDER = BIT( 2 ), // standard package delete
SAVEGAME_MBF_DELETE_ALL_FOLDERS = BIT( 3 ), // deletes all of the savegame folders (should only be used in testing)
SAVEGAME_MBF_ENUMERATE = BIT( 4 ), // gets listing of all savegame folders, typically used with READ_DETAILS to read the description file
SAVEGAME_MBF_NO_COMPRESS = BIT( 5 ), // tells the system the files aren't compressed, usually only needed when reading the descriptors file internally
SAVEGAME_MBF_ENUMERATE_FILES = BIT( 6 ), // enumerates all the files within a particular savegame folder (can be individual/multiple files or pattern described in parms)
SAVEGAME_MBF_DELETE_FILES = BIT( 7 ), // deletes individual files within a particular savegame folder (can be individual/multiple files or pattern described in parms)
SAVEGAME_MBF_READ_DETAILS = BIT( 8 ), // reads the description file (if specified, parms.enumeratedEntry.name & parms.enumeratedEntry.type must be specified)
SAVEGAME_MBF_KEEP_FOLDER = BIT( 9 ) // don't delete the folder before saving
};
typedef interlockedInt_t saveGameHandle_t;
typedef int savegameUserId_t; // [internal] hash of gamer tag for steam
/*
================================================
saveGameCheck_t
================================================
*/
struct saveGameCheck_t {
saveGameCheck_t() {
exists = false;
autosaveExists = false;
autosaveFolder = NULL;
}
bool exists;
bool autosaveExists;
const char * autosaveFolder;
};
/*
================================================
idSaveGameDetails
================================================
*/
class idSaveGameDetails {
public:
idSaveGameDetails();
~idSaveGameDetails() { Clear(); }
void Clear();
bool operator==( const idSaveGameDetails & other ) const { return ( idStr::Icmp( slotName, other.slotName ) == 0 ); }
idSaveGameDetails & operator=( const idSaveGameDetails &other ) {
descriptors.Clear();
descriptors = other.descriptors;
damaged = other.damaged;
date = other.date;
slotName = other.slotName;
return *this;
}
// for std::sort, sort newer (larger date) towards start of list
bool operator<( const idSaveGameDetails & other ) { return date > other.date; }
idStr GetMapName() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP, "" ); }
idStr GetLocation() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP_LOCATE, "" ); }
idStr GetLanguage() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_LANGUAGE, "" ); }
int GetPlaytime() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_PLAYTIME, 0 ); }
int GetExpansion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_EXPANSION, 0 ); }
int GetDifficulty() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_DIFFICULTY, -1 ); }
int GetSaveVersion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_SAVE_VERSION, 0 ); }
public:
idDict descriptors; // [in] Descriptors available to be shown on the save/load screen. Each game can define their own, e.g. Difficulty, level, map, score, time.
bool damaged; // [out]
time_t date; // [out] read from the filesystem, not set by client
idStrStatic< MAX_FOLDER_NAME_LENGTH > slotName; // [out] folder/slot name, e.g. AUTOSAVE
};
typedef idStaticList< idSaveGameDetails, MAX_SAVEGAMES > saveGameDetailsList_t;
// Making a auto_ptr to handle lifetime issues better
typedef idList< idFile_SaveGame *, TAG_SAVEGAMES > saveFileEntryList_t;
/*
================================================
idSaveLoadParms
================================================
*/
class idSaveLoadParms {
public:
idSaveLoadParms();
~idSaveLoadParms();
void ResetCancelled();
void Init();
void SetDefaults( int inputDevice = -1 ); // doesn't clear out things that should be persistent across entire processor
void CancelSaveGameFilePipelines();
void AbortSaveGameFilePipeline();
const int & GetError() const { return errorCode; }
const int & GetHandledErrors() const { return handledErrorCodes; }
const saveGameHandle_t & GetHandle() const { return handle; }
public:
idStrStatic< MAX_FOLDER_NAME_LENGTH > directory; // [in] real directory of the savegame package
idStrStatic< MAX_FILENAME_LENGTH_PATTERN > pattern; // [in] pattern to use while enumerating/deleting files within a savegame folder
idStrStatic< MAX_FILENAME_LENGTH_PATTERN > postPattern; // [in] pattern at the end of the file to use while enumerating/deleting files within a savegame folder
int mode; // [in] SAVE, LOAD, ENUM, DELETE, etc.
idSaveGameDetails description; // [in/out] in: description used to serialize into game.details file, out: if SAVEGAME_MBF_READ_DETAILS used with certain modes, item 0 contains the read details
saveFileEntryList_t files; // [in/out] in: files to be saved, out: objects loaded, for SAVEGAME_MBF_ENUMERATE_FILES, it contains a listing of the filenames only
saveGameDetailsList_t detailList; // [out] listing of the enumerated savegames used only with SAVEGAME_MBF_ENUMERATE
int errorCode; // [out] combination of saveGameError_t bits
int handledErrorCodes; // [out] combination of saveGameError_t bits
int64 requiredSpaceInBytes; // [out] when fails for insufficient space, this is populated with additional space required
int skipErrorDialogMask;
// ----------------------
// Internal vars
// ----------------------
idSysSignal callbackSignal; // [internal] used to signal savegame manager that the Process() call is completed (we still might have more Process() calls to make though...)
volatile bool cancelled; // [internal] while processor is running, this can be set outside of the normal operation of the processor. Each implementation should check this during operation to allow it to shutdown cleanly.
savegameUserId_t userId; // [internal] to get the proper user during every step
int inputDeviceId; // [internal] consoles will use this to segregate each player's files
saveGameHandle_t handle;
private:
// Don't allow copies
idSaveLoadParms( const idSaveLoadParms & s ) {}
void operator=( const idSaveLoadParms & s ) {}
};
// Using function pointers because:
// 1. CompletedCallback methods in processors weren't generic enough, we could use SaveFiles processors
// for profiles/games, but there would be a single completed callback and we'd have to update
// the callback to detect what type of call it was, store the type in the processor, etc.
// 2. Using a functor class would require us to define classes for each callback. The definition of those
// classes could be scattered and a little difficult to follow
// 3. With callback methods, we assign them when needed and know exactly where they are defined/declared.
//typedef void (*saveGameProcessorCallback_t)( idSaveLoadParms & parms );
/*
================================================
saveGameThreadArgs_t
================================================
*/
struct saveGameThreadArgs_t {
saveGameThreadArgs_t() :
saveLoadParms( NULL ) {
}
idSaveLoadParms * saveLoadParms;
};
/*
================================================
idSaveGameThread
================================================
*/
class idSaveGameThread : public idSysThread {
public:
idSaveGameThread() : cancel( false ) {}
int Run();
void CancelOperations() { cancel = true; }
private:
int Save();
int Load();
int Enumerate();
int Delete();
int DeleteAll();
int DeleteFiles();
int EnumerateFiles();
public:
saveGameThreadArgs_t data;
volatile bool cancel;
};
/*
================================================
idSaveGameProcessor
================================================
*/
class idSaveGameProcessor {
friend class idSaveGameManager;
public:
DEFINE_CLASS( idSaveGameProcessor );
static const int MAX_COMPLETED_CALLBACKS = 5;
idSaveGameProcessor();
virtual ~idSaveGameProcessor() { }
//------------------------
// Virtuals
//------------------------
// Basic init
virtual bool Init();
// This method should returns true if the processor has additional sub-states to
// manage. The saveGameManager will retain the current state and Process() will be called again. When this method
// returns false Process() will not be called again. For example, during save, you might want to load other files
// and save them somewhere else, return true until you are done with the entire state.
virtual bool Process() { return false; }
// Gives each processor to validate an error returned from the previous process call.
// This is useful when processors have a multi-stage Process() and expect some benign errors like
// deleting a savegame folder before copying into it.
virtual bool ValidateLastError() { return false; }
// Processors need to override this if they will eventually reset the map.
// If it could possibly reset the map through any of its stages, including kicking off another processor in completed callback, return false.
// We will force non-simple processors to execute last and won't block the map heap reset due if non-simple processors are still executing.
virtual bool IsSimpleProcessor() const { return true; }
// This is a fail-safe to catch a timing issue on the PS3 where the nextmap processor could sometimes hang during a level transition
virtual bool ShouldTimeout() const { return false; }
//------------------------
// Commands
//------------------------
// Cancels this processor in whatever state it's currently in and sets an error code for SAVEGAME_E_CANCELLED
void Cancel() { parms.cancelled = true; parms.errorCode = SAVEGAME_E_CANCELLED; }
//------------------------
// Accessors
//------------------------
// Returns error status
idSysSignal & GetSignal() { return parms.callbackSignal; }
// Returns error status
const int & GetError() const { return parms.errorCode; }
// Returns the processor's save/load parms
const idSaveLoadParms & GetParms() const { return parms; }
// Returns the processor's save/load parms
idSaveLoadParms & GetParmsNonConst() { return parms; }
// Returns if this processor is currently working
bool IsWorking() const { return working; }
// This is a way to tell the processor which errors shouldn't be handled by the processor or system.
void SetSkipSystemErrorDialogMask( const int errorMask ) { parms.skipErrorDialogMask = errorMask; }
int GetSkipSystemErrorDialogMask() const { return parms.skipErrorDialogMask; }
// Returns the handle given by execution
saveGameHandle_t GetHandle() const { return parms.GetHandle(); }
// These can be overridden by game code, like the GUI, when the processor is done executing.
// Game classes like the GUI can create a processor derived from a game's Save processor impl and simply use
// this method to know when everything is done. It eases the burden of constantly checking the working flag.
// This will be called back within the game thread during SaveGameManager::Pump().
void AddCompletedCallback( const idCallback & callback );
private:
// Returns whether or not the thread is finished operating, should only be called by the savegame manager
bool IsThreadFinished();
protected:
idSaveLoadParms parms;
int savegameLogicTestIterator;
private:
bool init;
bool working;
idStaticList< idCallback *, MAX_COMPLETED_CALLBACKS > completedCallbacks;
};
/*
================================================
idSaveGameManager
Why all the object-oriented nonsense?
- Savegames need to be processed asynchronously, saving/loading/deleting files should happen during the game frame
so there is a common way to update the render device.
- When executing commands, if no "strategy"s are used, the pump() method would need to have a switch statement,
extending the manager for other commands would mean modifying the manager itself for various commands.
By making it a strategy, we are able to create custom commands and define the behavior within game code and keep
the manager code in the engine static.
================================================
*/
class idSaveGameManager {
public:
enum packageType_t {
PACKAGE_PROFILE,
PACKAGE_GAME,
PACKAGE_RAW,
PACKAGE_NUM
};
const static int MAX_SAVEGAME_DIRECTORY_DEPTH = 5;
explicit idSaveGameManager();
~idSaveGameManager();
// Called within main game thread
void Pump();
// Has the storage device been selected yet? This is only an issue on the 360, and primarily for development purposes
bool IsStorageAvailable() const { return storageAvailable; }
void SetStorageAvailable( const bool available ) { storageAvailable = available; }
// Check to see if a processor is set within the manager
bool IsWorking() const;
// Assign a processor to the manager. The processor should belong in game-side code
// This queues up processors and executes them serially
// Returns whether or not the processor is immediately executed
saveGameHandle_t ExecuteProcessor( idSaveGameProcessor * processor );
// Synchronous version, CompletedCallback is NOT called.
saveGameHandle_t ExecuteProcessorAndWait( idSaveGameProcessor * processor );
// Lets the currently processing queue finish, but clears the processor queue
void Clear();
void WaitForAllProcessors( bool overrideSimpleProcessorCheck = false );
const bool IsCancelled() const { return cancel; }
void CancelAllProcessors( const bool forceCancelInFlightProcessor );
void CancelToTerminate();
idSaveGameThread & GetSaveGameThread() { return saveThread; }
bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const { return handle <= lastExecutedProcessorHandle || handle == 0; } // last case should never be reached since it would be also be true in first case, this is just to show intent
void Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes );
bool DeviceSelectorWaitingOnSaveRetry();
void ShowRetySaveDialog( const char * folder, const int64 bytes );
void ShowRetySaveDialog();
void ClearRetryInfo();
void RetrySave();
// This will cause the processor to cancel execution, the completion callback will be called
void CancelWithHandle( const saveGameHandle_t & handle );
const saveGameDetailsList_t & GetEnumeratedSavegames() const { return enumeratedSaveGames; }
saveGameDetailsList_t & GetEnumeratedSavegamesNonConst() { return enumeratedSaveGames; }
private:
// These are to make sure that all processors start and finish in the same way without a lot of code duplication.
// We need to make sure that we adhere to PS3 system combination initialization issues.
void StartNextProcessor();
void FinishProcessor( idSaveGameProcessor * processor );
// Calls start on the processor after it's been assigned
void Start();
private:
idSaveGameProcessor * processor;
idStaticList< idSaveGameProcessor *, 4 > processorQueue;
bool cancel;
idSaveGameThread saveThread;
int startTime;
bool continueProcessing;
saveGameHandle_t submittedProcessorHandle;
saveGameHandle_t executingProcessorHandle;
saveGameHandle_t lastExecutedProcessorHandle;
saveGameDetailsList_t enumeratedSaveGames;
bool storageAvailable; // On 360, this is false by default, after the storage device is selected
// it becomes true. This allows us to start the game without a storage device
// selected and pop the selector when necessary.
const char * retryFolder;
int64 retryBytes;
bool retrySave;
idSysSignal deviceRequestedSignal;
};
// Bridge between the session's APIs and the savegame thread
void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms );
// Folder prefix should be NULL for everything except PS3
// Synchronous check, just checks if any savegame exists for master local user and if one is an autosave
void Sys_SaveGameCheck( bool & exists, bool & autosaveExists );
const idStr & GetSaveFolder( idSaveGameManager::packageType_t type );
idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type );
idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type );
bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details );
idStr GetSaveGameErrorString( int errorMask );
#endif // __SYS_SAVEGAME_H__