| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- /*
- ** Command & Conquer Generals(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program 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.
- **
- ** This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: GameStateMap.cpp /////////////////////////////////////////////////////////////////////////
- // Author: Colin Day, October 2002
- // Desc: Chunk in the save game file that will hold a pristine version of the map file
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h"
- #include "Common/File.h"
- #include "Common/FileSystem.h"
- #include "Common/GameState.h"
- #include "Common/GameStateMap.h"
- #include "Common/GlobalData.h"
- #include "Common/Xfer.h"
- #include "GameClient/GameClient.h"
- #include "GameClient/MapUtil.h"
- #include "GameLogic/GameLogic.h"
- #include "GameNetwork/GameInfo.h"
- // GLOBALS ////////////////////////////////////////////////////////////////////////////////////////
- GameStateMap *TheGameStateMap = NULL;
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- // METHODS ////////////////////////////////////////////////////////////////////////////////////////
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- GameStateMap::GameStateMap( void )
- {
- } // end GameStateMap
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- GameStateMap::~GameStateMap( void )
- {
- //
- // clear the save directory of any temporary "scratch pad" maps that were extracted
- // from any previously loaded save game files
- //
- clearScratchPadMaps();
- } // end ~GameStateMap
- // ------------------------------------------------------------------------------------------------
- /** Embed the pristine map into the xfer stream */
- // ------------------------------------------------------------------------------------------------
- static void embedPristineMap( AsciiString map, Xfer *xfer )
- {
-
- // open the map file
- File *file = TheFileSystem->openFile( map.str(), File::READ | File::BINARY );
- if( file == NULL )
- {
- DEBUG_CRASH(( "embedPristineMap - Error opening source file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
-
- // how big is the map file
- Int fileSize = file->seek( 0, File::END );
-
- // rewind to beginning of file
- file->seek( 0, File::START );
- // allocate buffer big enough to hold the entire map file
- char *buffer = new char[ fileSize ];
- if( buffer == NULL )
- {
- DEBUG_CRASH(( "embedPristineMap - Unable to allocate buffer for file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
-
- // copy the file to the buffer
- if( file->read( buffer, fileSize ) != fileSize )
- {
- DEBUG_CRASH(( "embeddPristineMap - Error reading from file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
-
- // close the BIG file
- file->close();
-
- // write the contents to the save file
- DEBUG_ASSERTCRASH( xfer->getXferMode() == XFER_SAVE, ("embedPristineMap - Unsupposed xfer mode\n") );
- xfer->beginBlock();
- xfer->xferUser( buffer, fileSize );
- xfer->endBlock();
- // delete the buffer
- delete [] buffer;
-
- } // end embedPristineMap
- // ------------------------------------------------------------------------------------------------
- /** Embed an "in use" map into the xfer stream. An "in use" map is one that has already
- * been pulled out of a save game file and parked in a temporary file in the save directory */
- // ------------------------------------------------------------------------------------------------
- static void embedInUseMap( AsciiString map, Xfer *xfer )
- {
- FILE *fp = fopen( map.str(), "rb" );
- // sanity
- if( fp == NULL )
- {
- DEBUG_CRASH(( "embedInUseMap - Unable to open file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
- // how big is the file
- fseek( fp, 0, SEEK_END );
- Int fileSize = ftell( fp );
- // rewind file back to start
- fseek( fp, 0, SEEK_SET );
- // allocate a buffer big enough for the entire file
- char *buffer = new char[ fileSize ];
- if( buffer == NULL )
- {
- DEBUG_CRASH(( "embedInUseMap - Unable to allocate buffer for file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
- // read the entire file
- if( fread( buffer, 1, fileSize, fp ) != fileSize )
- {
- DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'\n", map.str() ));
- throw SC_INVALID_DATA;
- } // end if
- // embed file into xfer stream
- xfer->beginBlock();
- xfer->xferUser( buffer, fileSize );
- xfer->endBlock();
- // close the file
- fclose( fp );
- // delete buffer
- delete [] buffer;
- } // embedInUseMap
- // ------------------------------------------------------------------------------------------------
- /** Extract the map from the xfer stream and save as a file with filename 'mapToSave' */
- // ------------------------------------------------------------------------------------------------
- static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer )
- {
- UnsignedInt dataSize;
- // open handle to output file
- FILE *fp = fopen( mapToSave.str(), "w+b" );
- if( fp == NULL )
- {
- DEBUG_CRASH(( "extractAndSaveMap - Unable to open file '%s'\n", mapToSave.str() ));
- throw SC_INVALID_DATA;
- } // en
- // read data size from file
- dataSize = xfer->beginBlock();
- // allocate buffer big enough for the entire map file
- char *buffer = new char[ dataSize ];
- if( buffer == NULL )
- {
- DEBUG_CRASH(( "extractAndSaveMap - Unable to allocate buffer for file '%s'\n", mapToSave.str() ));
- throw SC_INVALID_DATA;
- } // end if
- // read map file
- xfer->xferUser( buffer, dataSize );
- // write contents of buffer to new file
- if( fwrite( buffer, 1, dataSize, fp ) != dataSize )
- {
- DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'\n", mapToSave.str() ));
- throw SC_INVALID_DATA;
- } // end if
- // close the new file
- fclose( fp );
- // end of data block
- xfer->endBlock();
- // delete the buffer
- delete [] buffer;
- } // end extractAndSaveMap
- // ------------------------------------------------------------------------------------------------
- /** Xfer method
- * Version Info:
- * 1: Initial version
- * 2: Now storing the game mode from logic. Storing that here cause TheGameLogic->startNewGame
- * needs to set up the player list based on it.
- */
- // ------------------------------------------------------------------------------------------------
- void GameStateMap::xfer( Xfer *xfer )
- {
- // version
- const XferVersion currentVersion = 2;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
- // get save game info
- SaveGameInfo *saveGameInfo = TheGameState->getSaveGameInfo();
- //
- // map filename, for purposes of saving we will always be saving a with a map filename
- // that refers to map in the save directory so we must always save a filename into
- // the file that is in the save directory
- //
- Bool firstSave = FALSE; // TRUE if we haven't yet saved a pristine load of a new map
- if( xfer->getXferMode() == XFER_SAVE )
- {
- AsciiString mapLeafName = TheGameState->getMapLeafName(TheGlobalData->m_mapName);
- // construct filename to map in the save directory
- saveGameInfo->saveGameMapName = TheGameState->getFilePathInSaveDirectory(mapLeafName);
- // write map name. For cross-machine compatibility, we always write
- // it as just "Save\filename", not a full path.
- {
- AsciiString tmp = TheGameState->realMapPathToPortableMapPath(saveGameInfo->saveGameMapName);
- xfer->xferAsciiString( &tmp );
- }
- //
- // write pristine map name which is already in the member 'pristineMapName' from
- // a previous load, or in the instance where we are saving for the first time
- // and the global data map name refers to a pristine map we will copy it in there first
- //
- if (!TheGameState->isInSaveDirectory(TheGlobalData->m_mapName))
- {
- // copy the pristine name
- saveGameInfo->pristineMapName = TheGlobalData->m_mapName;
- //
- // this is also an indication that we are saving for the first time a brand new
- // map that has never been saved into this save file before (a save is also considered
- // to be a first save as long as we are writing data to disk without having loaded
- // this particluar map from the save file ... so if you load USA01 for the first
- // time and save, that is a first save ... then, without quitting, if you save
- // again that is *also* considered a first save). First save just determines
- // whether the map file we embed in the save file is taken from the maps directory
- // or from the temporary map extracted to the save directory from a load
- //
- firstSave = TRUE;
- } // end if
- // save the pristine name
- // For cross-machine compatibility, we always write
- // it as just "Save\filename", not a full path.
- {
- AsciiString tmp = TheGameState->realMapPathToPortableMapPath(saveGameInfo->pristineMapName);
- xfer->xferAsciiString( &tmp );
- }
- if (currentVersion >= 2)
- {
- // save the game mode.
- Int gameMode = TheGameLogic->getGameMode();
- xfer->xferInt( &gameMode);
- }
- } // end if, save
- else
- {
- // read the save game map name
- AsciiString tmp;
- xfer->xferAsciiString( &tmp );
- saveGameInfo->saveGameMapName = TheGameState->portableMapPathToRealMapPath(tmp);
- if (!TheGameState->isInSaveDirectory(saveGameInfo->saveGameMapName))
- {
- DEBUG_CRASH(("GameState::xfer - The map filename read from the file '%s' is not in the SAVE directory, but should be\n",
- saveGameInfo->saveGameMapName.str()) );
- throw SC_INVALID_DATA;
- }
- // set this map as the map to load in the global data
- TheWritableGlobalData->m_mapName = saveGameInfo->saveGameMapName;
- // read the pristine map filename
- xfer->xferAsciiString( &saveGameInfo->pristineMapName );
- saveGameInfo->pristineMapName = TheGameState->portableMapPathToRealMapPath(saveGameInfo->pristineMapName);
- if (currentVersion >= 2)
- {
- // get the game mode.
- Int gameMode;
- xfer->xferInt(&gameMode);
- TheGameLogic->setGameMode(gameMode);
- }
- } // end else, load
- // map data
- if( xfer->getXferMode() == XFER_SAVE )
- {
- //
- // if this is a first save from a pristine map load, we need to copy the pristine
- // map into the save game file
- //
- if( firstSave == TRUE )
- {
- embedPristineMap( saveGameInfo->pristineMapName, xfer );
- } // end if, first save
- else
- {
- //
- // this is *NOT* a first save from a pristine map, just read the map file
- // that was extracted from the save game file during the last load and embedd
- // that into the save game file
- //
- embedInUseMap( saveGameInfo->saveGameMapName, xfer );
- } // end else
- } // end if, save
- else
- {
- //
- // take the embedded map file out of the save file, and save as its own .map file
- // in the save directory temporarily
- //
- extractAndSaveMap( saveGameInfo->saveGameMapName, xfer );
- } // end else
- //
- // it's important that early in the load process, we xfer the object ID counter
- // in the game logic ... this is necessary because there are flows of code that
- // create objects during the load of a save game that is *NOT FROM THE OBJECT BLOCK* of
- // code, such as loading bridges (which creates the bridge and tower objects). We
- // are fancy and "do the right thing" in those situations, but we don't want to run
- // the risk of these objects being created and having overlapping IDs of anything
- // that we will load from the save file
- //
- ObjectID highObjectID = TheGameLogic->getObjectIDCounter();
- xfer->xferObjectID( &highObjectID );
- TheGameLogic->setObjectIDCounter( highObjectID );
- //
- // it is also equally important to xfer the drawable id counter early in the load process
- // because the act of creating objects also creates drawables, so when it comes time
- // to load the block of drawables we want to make sure that newly created drawables
- // at that time will never overlap IDs with any drawable created as part of an object
- // which then had its ID transferred in from the save file
- //
- DrawableID highDrawableID = TheGameClient->getDrawableIDCounter();
- xfer->xferDrawableID( &highDrawableID );
- TheGameClient->setDrawableIDCounter( highDrawableID );
- if (TheGameLogic->getGameMode()==GAME_SKIRMISH) {
- if (TheSkirmishGameInfo==NULL) {
- TheSkirmishGameInfo = NEW SkirmishGameInfo;
- TheSkirmishGameInfo->init();
- TheSkirmishGameInfo->clearSlotList();
- TheSkirmishGameInfo->reset();
- }
- xfer->xferSnapshot(TheSkirmishGameInfo);
- } else {
- if (TheSkirmishGameInfo) {
- delete TheSkirmishGameInfo;
- TheSkirmishGameInfo = NULL;
- }
- }
- //
- // for loading, start a new game (flagged from a save game) ... this will load the map and all the
- // things in the map file that don't don't change (terrain, triggers, teams, script
- // definitions) etc
- //
- if( xfer->getXferMode() == XFER_LOAD ) {
- TheGameLogic->startNewGame( TRUE );
- }
- } // end xfer
- // ------------------------------------------------------------------------------------------------
- /** Delete any scratch pad maps in the save directory. Scratch pad maps are maps that
- * were embedded in previously loaded save game files and temporarily written out as
- * their own file so that those map files could be loaded as a part of the load game
- * process */
- // ------------------------------------------------------------------------------------------------
- void GameStateMap::clearScratchPadMaps( void )
- {
- // remember the current directory
- char currentDirectory[ _MAX_PATH ];
- GetCurrentDirectory( _MAX_PATH, currentDirectory );
- // switch into the save directory
- SetCurrentDirectory( TheGameState->getSaveDirectory().str() );
- // iterate all items in the directory
- AsciiString fileToDelete;
- WIN32_FIND_DATA item; // search item
- HANDLE hFile = INVALID_HANDLE_VALUE; // handle for search resources
- Bool done = FALSE;
- Bool first = TRUE;
- while( done == FALSE )
- {
- // first, clear flag for deleting file
- fileToDelete.clear();
- // if our first time through we need to start the search
- if( first )
- {
- // start search
- hFile = FindFirstFile( "*", &item );
- if( hFile == INVALID_HANDLE_VALUE )
- return;
- // we are no longer on our first item
- first = FALSE;
- } // end if, first
- // see if this is a file, and therefore a possible .map file
- if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
- {
- // see if there is a ".map" at end of this filename
- Char *c = strrchr( item.cFileName, '.' );
- if( c && stricmp( c, ".map" ) == 0 )
- fileToDelete.set( item.cFileName ); // we want to delete this one
- } // end if
- //
- // find the next file before we delete this one, this is probably not necessary
- // to strcuture things this way so that the find next occurs before the file
- // delete, but it seems more correct to do so
- //
- if( FindNextFile( hFile, &item ) == 0 )
- done = TRUE;
- // delete file if set
- if( fileToDelete.isEmpty() == FALSE )
- DeleteFile( fileToDelete.str() );
- } // end while
- // close search resources
- FindClose( hFile );
- // restore our directory to the current directory
- SetCurrentDirectory( currentDirectory );
- } // end clearScratchPadMaps
|