GameStateMap.cpp 16 KB

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