123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /*
- ===========================================================================
- 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__
|