GameStateMap.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: GameStateMap.cpp /////////////////////////////////////////////////////////////////////////
  24. // Author: Colin Day, October 2002
  25. // Desc: Chunk in the save game file that will hold a pristine version of the map file
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h"
  29. #include "Common/File.h"
  30. #include "Common/FileSystem.h"
  31. #include "Common/GameState.h"
  32. #include "Common/GameStateMap.h"
  33. #include "Common/GlobalData.h"
  34. #include "Common/Xfer.h"
  35. #include "GameClient/GameClient.h"
  36. #include "GameClient/MapUtil.h"
  37. #include "GameLogic/GameLogic.h"
  38. #include "GameNetwork/GameInfo.h"
  39. // GLOBALS ////////////////////////////////////////////////////////////////////////////////////////
  40. GameStateMap *TheGameStateMap = NULL;
  41. #ifdef _INTERNAL
  42. // for occasional debugging...
  43. //#pragma optimize("", off)
  44. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  45. #endif
  46. // METHODS ////////////////////////////////////////////////////////////////////////////////////////
  47. // ------------------------------------------------------------------------------------------------
  48. // ------------------------------------------------------------------------------------------------
  49. GameStateMap::GameStateMap( void )
  50. {
  51. } // end GameStateMap
  52. // ------------------------------------------------------------------------------------------------
  53. // ------------------------------------------------------------------------------------------------
  54. GameStateMap::~GameStateMap( void )
  55. {
  56. //
  57. // clear the save directory of any temporary "scratch pad" maps that were extracted
  58. // from any previously loaded save game files
  59. //
  60. clearScratchPadMaps();
  61. } // end ~GameStateMap
  62. // ------------------------------------------------------------------------------------------------
  63. /** Embed the pristine map into the xfer stream */
  64. // ------------------------------------------------------------------------------------------------
  65. static void embedPristineMap( AsciiString map, Xfer *xfer )
  66. {
  67. // open the map file
  68. File *file = TheFileSystem->openFile( map.str(), File::READ | File::BINARY );
  69. if( file == NULL )
  70. {
  71. DEBUG_CRASH(( "embedPristineMap - Error opening source file '%s'\n", map.str() ));
  72. throw SC_INVALID_DATA;
  73. } // end if
  74. // how big is the map file
  75. Int fileSize = file->seek( 0, File::END );
  76. // rewind to beginning of file
  77. file->seek( 0, File::START );
  78. // allocate buffer big enough to hold the entire map file
  79. char *buffer = new char[ fileSize ];
  80. if( buffer == NULL )
  81. {
  82. DEBUG_CRASH(( "embedPristineMap - Unable to allocate buffer for file '%s'\n", map.str() ));
  83. throw SC_INVALID_DATA;
  84. } // end if
  85. // copy the file to the buffer
  86. if( file->read( buffer, fileSize ) != fileSize )
  87. {
  88. DEBUG_CRASH(( "embeddPristineMap - Error reading from file '%s'\n", map.str() ));
  89. throw SC_INVALID_DATA;
  90. } // end if
  91. // close the BIG file
  92. file->close();
  93. // write the contents to the save file
  94. DEBUG_ASSERTCRASH( xfer->getXferMode() == XFER_SAVE, ("embedPristineMap - Unsupposed xfer mode\n") );
  95. xfer->beginBlock();
  96. xfer->xferUser( buffer, fileSize );
  97. xfer->endBlock();
  98. // delete the buffer
  99. delete [] buffer;
  100. } // end embedPristineMap
  101. // ------------------------------------------------------------------------------------------------
  102. /** Embed an "in use" map into the xfer stream. An "in use" map is one that has already
  103. * been pulled out of a save game file and parked in a temporary file in the save directory */
  104. // ------------------------------------------------------------------------------------------------
  105. static void embedInUseMap( AsciiString map, Xfer *xfer )
  106. {
  107. FILE *fp = fopen( map.str(), "rb" );
  108. // sanity
  109. if( fp == NULL )
  110. {
  111. DEBUG_CRASH(( "embedInUseMap - Unable to open file '%s'\n", map.str() ));
  112. throw SC_INVALID_DATA;
  113. } // end if
  114. // how big is the file
  115. fseek( fp, 0, SEEK_END );
  116. Int fileSize = ftell( fp );
  117. // rewind file back to start
  118. fseek( fp, 0, SEEK_SET );
  119. // allocate a buffer big enough for the entire file
  120. char *buffer = new char[ fileSize ];
  121. if( buffer == NULL )
  122. {
  123. DEBUG_CRASH(( "embedInUseMap - Unable to allocate buffer for file '%s'\n", map.str() ));
  124. throw SC_INVALID_DATA;
  125. } // end if
  126. // read the entire file
  127. if( fread( buffer, 1, fileSize, fp ) != fileSize )
  128. {
  129. DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'\n", map.str() ));
  130. throw SC_INVALID_DATA;
  131. } // end if
  132. // embed file into xfer stream
  133. xfer->beginBlock();
  134. xfer->xferUser( buffer, fileSize );
  135. xfer->endBlock();
  136. // close the file
  137. fclose( fp );
  138. // delete buffer
  139. delete [] buffer;
  140. } // embedInUseMap
  141. // ------------------------------------------------------------------------------------------------
  142. /** Extract the map from the xfer stream and save as a file with filename 'mapToSave' */
  143. // ------------------------------------------------------------------------------------------------
  144. static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer )
  145. {
  146. UnsignedInt dataSize;
  147. // open handle to output file
  148. FILE *fp = fopen( mapToSave.str(), "w+b" );
  149. if( fp == NULL )
  150. {
  151. DEBUG_CRASH(( "extractAndSaveMap - Unable to open file '%s'\n", mapToSave.str() ));
  152. throw SC_INVALID_DATA;
  153. } // en
  154. // read data size from file
  155. dataSize = xfer->beginBlock();
  156. // allocate buffer big enough for the entire map file
  157. char *buffer = new char[ dataSize ];
  158. if( buffer == NULL )
  159. {
  160. DEBUG_CRASH(( "extractAndSaveMap - Unable to allocate buffer for file '%s'\n", mapToSave.str() ));
  161. throw SC_INVALID_DATA;
  162. } // end if
  163. // read map file
  164. xfer->xferUser( buffer, dataSize );
  165. // write contents of buffer to new file
  166. if( fwrite( buffer, 1, dataSize, fp ) != dataSize )
  167. {
  168. DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'\n", mapToSave.str() ));
  169. throw SC_INVALID_DATA;
  170. } // end if
  171. // close the new file
  172. fclose( fp );
  173. // end of data block
  174. xfer->endBlock();
  175. // delete the buffer
  176. delete [] buffer;
  177. } // end extractAndSaveMap
  178. // ------------------------------------------------------------------------------------------------
  179. /** Xfer method
  180. * Version Info:
  181. * 1: Initial version
  182. * 2: Now storing the game mode from logic. Storing that here cause TheGameLogic->startNewGame
  183. * needs to set up the player list based on it.
  184. */
  185. // ------------------------------------------------------------------------------------------------
  186. void GameStateMap::xfer( Xfer *xfer )
  187. {
  188. // version
  189. const XferVersion currentVersion = 2;
  190. XferVersion version = currentVersion;
  191. xfer->xferVersion( &version, currentVersion );
  192. // get save game info
  193. SaveGameInfo *saveGameInfo = TheGameState->getSaveGameInfo();
  194. //
  195. // map filename, for purposes of saving we will always be saving a with a map filename
  196. // that refers to map in the save directory so we must always save a filename into
  197. // the file that is in the save directory
  198. //
  199. Bool firstSave = FALSE; // TRUE if we haven't yet saved a pristine load of a new map
  200. if( xfer->getXferMode() == XFER_SAVE )
  201. {
  202. AsciiString mapLeafName = TheGameState->getMapLeafName(TheGlobalData->m_mapName);
  203. // construct filename to map in the save directory
  204. saveGameInfo->saveGameMapName = TheGameState->getFilePathInSaveDirectory(mapLeafName);
  205. // write map name. For cross-machine compatibility, we always write
  206. // it as just "Save\filename", not a full path.
  207. {
  208. AsciiString tmp = TheGameState->realMapPathToPortableMapPath(saveGameInfo->saveGameMapName);
  209. xfer->xferAsciiString( &tmp );
  210. }
  211. //
  212. // write pristine map name which is already in the member 'pristineMapName' from
  213. // a previous load, or in the instance where we are saving for the first time
  214. // and the global data map name refers to a pristine map we will copy it in there first
  215. //
  216. if (!TheGameState->isInSaveDirectory(TheGlobalData->m_mapName))
  217. {
  218. // copy the pristine name
  219. saveGameInfo->pristineMapName = TheGlobalData->m_mapName;
  220. //
  221. // this is also an indication that we are saving for the first time a brand new
  222. // map that has never been saved into this save file before (a save is also considered
  223. // to be a first save as long as we are writing data to disk without having loaded
  224. // this particluar map from the save file ... so if you load USA01 for the first
  225. // time and save, that is a first save ... then, without quitting, if you save
  226. // again that is *also* considered a first save). First save just determines
  227. // whether the map file we embed in the save file is taken from the maps directory
  228. // or from the temporary map extracted to the save directory from a load
  229. //
  230. firstSave = TRUE;
  231. } // end if
  232. // save the pristine name
  233. // For cross-machine compatibility, we always write
  234. // it as just "Save\filename", not a full path.
  235. {
  236. AsciiString tmp = TheGameState->realMapPathToPortableMapPath(saveGameInfo->pristineMapName);
  237. xfer->xferAsciiString( &tmp );
  238. }
  239. if (currentVersion >= 2)
  240. {
  241. // save the game mode.
  242. Int gameMode = TheGameLogic->getGameMode();
  243. xfer->xferInt( &gameMode);
  244. }
  245. } // end if, save
  246. else
  247. {
  248. // read the save game map name
  249. AsciiString tmp;
  250. xfer->xferAsciiString( &tmp );
  251. saveGameInfo->saveGameMapName = TheGameState->portableMapPathToRealMapPath(tmp);
  252. if (!TheGameState->isInSaveDirectory(saveGameInfo->saveGameMapName))
  253. {
  254. DEBUG_CRASH(("GameState::xfer - The map filename read from the file '%s' is not in the SAVE directory, but should be\n",
  255. saveGameInfo->saveGameMapName.str()) );
  256. throw SC_INVALID_DATA;
  257. }
  258. // set this map as the map to load in the global data
  259. TheWritableGlobalData->m_mapName = saveGameInfo->saveGameMapName;
  260. // read the pristine map filename
  261. xfer->xferAsciiString( &saveGameInfo->pristineMapName );
  262. saveGameInfo->pristineMapName = TheGameState->portableMapPathToRealMapPath(saveGameInfo->pristineMapName);
  263. if (currentVersion >= 2)
  264. {
  265. // get the game mode.
  266. Int gameMode;
  267. xfer->xferInt(&gameMode);
  268. TheGameLogic->setGameMode(gameMode);
  269. }
  270. } // end else, load
  271. // map data
  272. if( xfer->getXferMode() == XFER_SAVE )
  273. {
  274. //
  275. // if this is a first save from a pristine map load, we need to copy the pristine
  276. // map into the save game file
  277. //
  278. if( firstSave == TRUE )
  279. {
  280. embedPristineMap( saveGameInfo->pristineMapName, xfer );
  281. } // end if, first save
  282. else
  283. {
  284. //
  285. // this is *NOT* a first save from a pristine map, just read the map file
  286. // that was extracted from the save game file during the last load and embedd
  287. // that into the save game file
  288. //
  289. embedInUseMap( saveGameInfo->saveGameMapName, xfer );
  290. } // end else
  291. } // end if, save
  292. else
  293. {
  294. //
  295. // take the embedded map file out of the save file, and save as its own .map file
  296. // in the save directory temporarily
  297. //
  298. extractAndSaveMap( saveGameInfo->saveGameMapName, xfer );
  299. } // end else
  300. //
  301. // it's important that early in the load process, we xfer the object ID counter
  302. // in the game logic ... this is necessary because there are flows of code that
  303. // create objects during the load of a save game that is *NOT FROM THE OBJECT BLOCK* of
  304. // code, such as loading bridges (which creates the bridge and tower objects). We
  305. // are fancy and "do the right thing" in those situations, but we don't want to run
  306. // the risk of these objects being created and having overlapping IDs of anything
  307. // that we will load from the save file
  308. //
  309. ObjectID highObjectID = TheGameLogic->getObjectIDCounter();
  310. xfer->xferObjectID( &highObjectID );
  311. TheGameLogic->setObjectIDCounter( highObjectID );
  312. //
  313. // it is also equally important to xfer the drawable id counter early in the load process
  314. // because the act of creating objects also creates drawables, so when it comes time
  315. // to load the block of drawables we want to make sure that newly created drawables
  316. // at that time will never overlap IDs with any drawable created as part of an object
  317. // which then had its ID transferred in from the save file
  318. //
  319. DrawableID highDrawableID = TheGameClient->getDrawableIDCounter();
  320. xfer->xferDrawableID( &highDrawableID );
  321. TheGameClient->setDrawableIDCounter( highDrawableID );
  322. if (TheGameLogic->getGameMode()==GAME_SKIRMISH) {
  323. if (TheSkirmishGameInfo==NULL) {
  324. TheSkirmishGameInfo = NEW SkirmishGameInfo;
  325. TheSkirmishGameInfo->init();
  326. TheSkirmishGameInfo->clearSlotList();
  327. TheSkirmishGameInfo->reset();
  328. }
  329. xfer->xferSnapshot(TheSkirmishGameInfo);
  330. } else {
  331. if (TheSkirmishGameInfo) {
  332. delete TheSkirmishGameInfo;
  333. TheSkirmishGameInfo = NULL;
  334. }
  335. }
  336. //
  337. // for loading, start a new game (flagged from a save game) ... this will load the map and all the
  338. // things in the map file that don't don't change (terrain, triggers, teams, script
  339. // definitions) etc
  340. //
  341. if( xfer->getXferMode() == XFER_LOAD ) {
  342. TheGameLogic->startNewGame( TRUE );
  343. }
  344. } // end xfer
  345. // ------------------------------------------------------------------------------------------------
  346. /** Delete any scratch pad maps in the save directory. Scratch pad maps are maps that
  347. * were embedded in previously loaded save game files and temporarily written out as
  348. * their own file so that those map files could be loaded as a part of the load game
  349. * process */
  350. // ------------------------------------------------------------------------------------------------
  351. void GameStateMap::clearScratchPadMaps( void )
  352. {
  353. // remember the current directory
  354. char currentDirectory[ _MAX_PATH ];
  355. GetCurrentDirectory( _MAX_PATH, currentDirectory );
  356. // switch into the save directory
  357. SetCurrentDirectory( TheGameState->getSaveDirectory().str() );
  358. // iterate all items in the directory
  359. AsciiString fileToDelete;
  360. WIN32_FIND_DATA item; // search item
  361. HANDLE hFile = INVALID_HANDLE_VALUE; // handle for search resources
  362. Bool done = FALSE;
  363. Bool first = TRUE;
  364. while( done == FALSE )
  365. {
  366. // first, clear flag for deleting file
  367. fileToDelete.clear();
  368. // if our first time through we need to start the search
  369. if( first )
  370. {
  371. // start search
  372. hFile = FindFirstFile( "*", &item );
  373. if( hFile == INVALID_HANDLE_VALUE )
  374. return;
  375. // we are no longer on our first item
  376. first = FALSE;
  377. } // end if, first
  378. // see if this is a file, and therefore a possible .map file
  379. if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
  380. {
  381. // see if there is a ".map" at end of this filename
  382. Char *c = strrchr( item.cFileName, '.' );
  383. if( c && stricmp( c, ".map" ) == 0 )
  384. fileToDelete.set( item.cFileName ); // we want to delete this one
  385. } // end if
  386. //
  387. // find the next file before we delete this one, this is probably not necessary
  388. // to strcuture things this way so that the find next occurs before the file
  389. // delete, but it seems more correct to do so
  390. //
  391. if( FindNextFile( hFile, &item ) == 0 )
  392. done = TRUE;
  393. // delete file if set
  394. if( fileToDelete.isEmpty() == FALSE )
  395. DeleteFile( fileToDelete.str() );
  396. } // end while
  397. // close search resources
  398. FindClose( hFile );
  399. // restore our directory to the current directory
  400. SetCurrentDirectory( currentDirectory );
  401. } // end clearScratchPadMaps