Recorder.cpp 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619
  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. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  24. #include "Common/Recorder.h"
  25. #include "Common/FileSystem.h"
  26. #include "Common/playerlist.h"
  27. #include "Common/Player.h"
  28. #include "Common/GlobalData.h"
  29. #include "Common/GameEngine.h"
  30. #include "GameClient/GameWindow.h"
  31. #include "GameClient/GameWindowManager.h"
  32. #include "GameClient/InGameUI.h"
  33. #include "GameClient/Shell.h"
  34. #include "GameClient/GameText.h"
  35. #include "GameNetwork/LANAPICallbacks.h"
  36. #include "GameNetwork/GameMessageParser.h"
  37. #include "GameNetwork/GameSpy/PeerDefs.h"
  38. #include "GameNetwork/NetworkUtil.h"
  39. #include "GameLogic/GameLogic.h"
  40. #include "Common/RandomValue.h"
  41. #include "Common/CRCDebug.h"
  42. #include "Common/Version.h"
  43. #ifdef _INTERNAL
  44. // for occasional debugging...
  45. //#pragma optimize("", off)
  46. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  47. #endif
  48. Int REPLAY_CRC_INTERVAL = 100;
  49. const char *replayExtention = ".rep";
  50. const char *lastReplayFileName = "00000000"; // a name the user is unlikely to ever type, but won't cause panic & confusion
  51. static time_t startTime;
  52. static const UnsignedInt startTimeOffset = 6;
  53. static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(time_t);
  54. static const UnsignedInt framesOffset = endTimeOffset + sizeof(time_t);
  55. static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt);
  56. static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool);
  57. static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool);
  58. void RecorderClass::logGameStart(AsciiString options)
  59. {
  60. if (!m_file)
  61. return;
  62. time(&startTime);
  63. UnsignedInt fileSize = ftell(m_file);
  64. // move to appropriate offset
  65. if (!fseek(m_file, startTimeOffset, SEEK_SET))
  66. {
  67. // save off start time
  68. fwrite(&startTime, sizeof(time_t), 1, m_file);
  69. }
  70. // move back to end of stream
  71. #ifdef DEBUG_CRASHING
  72. Int res =
  73. #endif
  74. fseek(m_file, fileSize, SEEK_SET);
  75. DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
  76. #if defined(_DEBUG) || defined(_INTERNAL)
  77. if (TheNetwork && TheGlobalData->m_saveStats)
  78. {
  79. //if (TheLAN)
  80. {
  81. unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
  82. char computerName[MAX_COMPUTERNAME_LENGTH + 1];
  83. if (!GetComputerName(computerName, &bufSize))
  84. {
  85. strcpy(computerName, "unknown");
  86. }
  87. AsciiString statsFile = TheGlobalData->m_baseStatsDir;
  88. TheFileSystem->createDirectory(statsFile);
  89. statsFile.concat(computerName);
  90. statsFile.concat(".txt");
  91. FILE *logFP = fopen(statsFile.str(), "a+");
  92. if (!logFP)
  93. {
  94. // try again locally
  95. TheWritableGlobalData->m_baseStatsDir = TheGlobalData->getPath_UserData();
  96. statsFile = TheGlobalData->m_baseStatsDir;
  97. statsFile.concat(computerName);
  98. statsFile.concat(".txt");
  99. logFP = fopen(statsFile.str(), "a+");
  100. }
  101. if (logFP)
  102. {
  103. struct tm *t2 = localtime(&startTime);
  104. fprintf(logFP, "\nGame start at %s\tOptions are %s\n", asctime(t2), options.str());
  105. fclose(logFP);
  106. }
  107. }
  108. }
  109. #endif
  110. }
  111. void RecorderClass::logPlayerDisconnect(UnicodeString player, Int slot)
  112. {
  113. if (!m_file)
  114. return;
  115. DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to disconnect an invalid slot number"));
  116. if ((slot < 0) || (slot >= (MAX_SLOTS)))
  117. {
  118. return;
  119. }
  120. UnsignedInt fileSize = ftell(m_file);
  121. // move to appropriate offset
  122. if (!fseek(m_file, disconOffset + slot*sizeof(Bool), SEEK_SET))
  123. {
  124. // save off discon status
  125. Bool b = TRUE;
  126. fwrite(&b, sizeof(Bool), 1, m_file);
  127. }
  128. // move back to end of stream
  129. #ifdef DEBUG_CRASHING
  130. Int res =
  131. #endif
  132. fseek(m_file, fileSize, SEEK_SET);
  133. DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
  134. #if defined(_DEBUG) || defined(_INTERNAL)
  135. if (TheGlobalData->m_saveStats)
  136. {
  137. unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
  138. char computerName[MAX_COMPUTERNAME_LENGTH + 1];
  139. if (!GetComputerName(computerName, &bufSize))
  140. {
  141. strcpy(computerName, "unknown");
  142. }
  143. AsciiString statsFile = TheGlobalData->m_baseStatsDir;
  144. statsFile.concat(computerName);
  145. statsFile.concat(".txt");
  146. FILE *logFP = fopen(statsFile.str(), "a+");
  147. if (logFP)
  148. {
  149. time_t t;
  150. time(&t);
  151. struct tm *t2 = localtime(&t);
  152. fprintf(logFP, "\tPlayer %ls dropped at %s", player.str(), asctime(t2));
  153. fclose(logFP);
  154. }
  155. }
  156. #endif
  157. }
  158. void RecorderClass::logCRCMismatch( void )
  159. {
  160. if (!m_file)
  161. return;
  162. UnsignedInt fileSize = ftell(m_file);
  163. // move to appropriate offset
  164. if (!fseek(m_file, desyncOffset, SEEK_SET))
  165. {
  166. // save off desync status
  167. Bool b = TRUE;
  168. fwrite(&b, sizeof(Bool), 1, m_file);
  169. }
  170. // move back to end of stream
  171. #ifdef DEBUG_CRASHING
  172. Int res =
  173. #endif
  174. fseek(m_file, fileSize, SEEK_SET);
  175. DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
  176. #if defined(_DEBUG) || defined(_INTERNAL)
  177. if (TheGlobalData->m_saveStats)
  178. {
  179. m_wasDesync = TRUE;
  180. unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
  181. char computerName[MAX_COMPUTERNAME_LENGTH + 1];
  182. if (!GetComputerName(computerName, &bufSize))
  183. {
  184. strcpy(computerName, "unknown");
  185. }
  186. AsciiString statsFile = TheGlobalData->m_baseStatsDir;
  187. statsFile.concat(computerName);
  188. statsFile.concat(".txt");
  189. FILE *logFP = fopen(statsFile.str(), "a+");
  190. if (logFP)
  191. {
  192. time_t t;
  193. time(&t);
  194. struct tm *t2 = localtime(&t);
  195. fprintf(logFP, "\tCRC mismatch at %s", asctime(t2));
  196. fclose(logFP);
  197. }
  198. }
  199. #endif
  200. }
  201. void RecorderClass::logGameEnd( void )
  202. {
  203. if (!m_file)
  204. return;
  205. time_t t;
  206. time(&t);
  207. UnsignedInt duration = TheGameLogic->getFrame();
  208. UnsignedInt fileSize = ftell(m_file);
  209. // move to appropriate offset
  210. if (!fseek(m_file, endTimeOffset, SEEK_SET))
  211. {
  212. // save off end time
  213. fwrite(&t, sizeof(time_t), 1, m_file);
  214. }
  215. // move to appropriate offset
  216. if (!fseek(m_file, framesOffset, SEEK_SET))
  217. {
  218. // save off duration
  219. fwrite(&duration, sizeof(UnsignedInt), 1, m_file);
  220. }
  221. // move back to end of stream
  222. #ifdef DEBUG_CRASHING
  223. Int res =
  224. #endif
  225. fseek(m_file, fileSize, SEEK_SET);
  226. DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
  227. #if defined(_DEBUG) || defined(_INTERNAL)
  228. if (TheNetwork && TheGlobalData->m_saveStats)
  229. {
  230. //if (TheLAN)
  231. {
  232. unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
  233. char computerName[MAX_COMPUTERNAME_LENGTH + 1];
  234. if (!GetComputerName(computerName, &bufSize))
  235. {
  236. strcpy(computerName, "unknown");
  237. }
  238. AsciiString statsFile = TheGlobalData->m_baseStatsDir;
  239. statsFile.concat(computerName);
  240. statsFile.concat(".txt");
  241. FILE *logFP = fopen(statsFile.str(), "a+");
  242. if (logFP)
  243. {
  244. struct tm *t2 = localtime(&t);
  245. duration = t - startTime;
  246. Int minutes = duration/60;
  247. Int seconds = duration%60;
  248. fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds);
  249. fclose(logFP);
  250. }
  251. }
  252. }
  253. #endif
  254. }
  255. #ifdef DEBUG_LOGGING
  256. #if defined(_INTERNAL)
  257. #define DEBUG_FILE_NAME "DebugLogFileI.txt"
  258. #define DEBUG_FILE_NAME_PREV "DebugLogFilePrevI.txt"
  259. #elif defined(_DEBUG)
  260. #define DEBUG_FILE_NAME "DebugLogFileD.txt"
  261. #define DEBUG_FILE_NAME_PREV "DebugLogFilePrevD.txt"
  262. #else
  263. #define DEBUG_FILE_NAME "DebugLogFile.txt"
  264. #define DEBUG_FILE_NAME_PREV "DebugLogFilePrev.txt"
  265. #endif
  266. #endif
  267. void RecorderClass::cleanUpReplayFile( void )
  268. {
  269. #if defined(_DEBUG) || defined(_INTERNAL)
  270. if (TheGlobalData->m_saveStats)
  271. {
  272. char fname[_MAX_PATH+1];
  273. strncpy(fname, TheGlobalData->m_baseStatsDir.str(), _MAX_PATH);
  274. strncat(fname, m_fileName.str(), _MAX_PATH - strlen(fname));
  275. DEBUG_LOG(("Saving replay to %s\n", fname));
  276. AsciiString oldFname;
  277. oldFname.format("%s%s", getReplayDir().str(), m_fileName.str());
  278. CopyFile(oldFname.str(), fname, TRUE);
  279. #ifdef DEBUG_FILE_NAME
  280. AsciiString debugFname = fname;
  281. debugFname.removeLastChar();
  282. debugFname.removeLastChar();
  283. debugFname.removeLastChar();
  284. debugFname.concat("txt");
  285. UnsignedInt fileSize = 0;
  286. FILE *fp = fopen(DEBUG_FILE_NAME, "rb");
  287. if (fp)
  288. {
  289. fseek(fp, 0, SEEK_END);
  290. fileSize = ftell(fp);
  291. fclose(fp);
  292. fp = NULL;
  293. DEBUG_LOG(("Log file size was %d\n", fileSize));
  294. }
  295. const int MAX_DEBUG_SIZE = 65536;
  296. if (fileSize <= MAX_DEBUG_SIZE || TheGlobalData->m_saveAllStats)
  297. {
  298. DEBUG_LOG(("Using CopyFile to copy %s\n", DEBUG_FILE_NAME));
  299. CopyFile(DEBUG_FILE_NAME, debugFname.str(), TRUE);
  300. }
  301. else
  302. {
  303. DEBUG_LOG(("manual copy of %s\n", DEBUG_FILE_NAME));
  304. FILE *ifp = fopen(DEBUG_FILE_NAME, "rb");
  305. FILE *ofp = fopen(debugFname.str(), "wb");
  306. if (ifp && ofp)
  307. {
  308. fseek(ifp, fileSize-MAX_DEBUG_SIZE, SEEK_SET);
  309. char buf[4096];
  310. Int len;
  311. while ( (len=fread(buf, 1, 4096, ifp)) > 0 )
  312. {
  313. fwrite(buf, 1, len, ofp);
  314. }
  315. fclose(ofp);
  316. fclose(ifp);
  317. ifp = NULL;
  318. ofp = NULL;
  319. }
  320. else
  321. {
  322. if (ifp) fclose(ifp);
  323. if (ofp) fclose(ofp);
  324. ifp = NULL;
  325. ofp = NULL;
  326. }
  327. }
  328. #endif // DEBUG_FILE_NAME
  329. }
  330. #endif
  331. }
  332. /**
  333. * The recorder object.
  334. */
  335. RecorderClass *TheRecorder = NULL;
  336. /**
  337. * Constructor
  338. */
  339. RecorderClass::RecorderClass()
  340. {
  341. m_originalGameMode = GAME_NONE;
  342. m_mode = RECORDERMODETYPE_RECORD;
  343. m_file = NULL;
  344. m_fileName.clear();
  345. m_currentFilePosition = 0;
  346. //Added By Sadullah Nader
  347. //Initializtion(s) inserted
  348. m_doingAnalysis = FALSE;
  349. m_nextFrame = 0;
  350. m_wasDesync = FALSE;
  351. //
  352. init(); // just for the heck of it.
  353. }
  354. /**
  355. * Destructor
  356. */
  357. RecorderClass::~RecorderClass() {
  358. }
  359. /**
  360. * Initialization
  361. * The recorder will record by default since every game will be recorded.
  362. * Obviously a game that is being played back will not be recorded.
  363. * Since the playback is done through a special interface, that interface
  364. * will set the recorder mode to RECORDERMODETYPE_PLAYBACK.
  365. */
  366. void RecorderClass::init() {
  367. m_originalGameMode = GAME_NONE;
  368. m_mode = RECORDERMODETYPE_NONE;
  369. m_file = NULL;
  370. m_fileName.clear();
  371. m_currentFilePosition = 0;
  372. m_gameInfo.clearSlotList();
  373. m_gameInfo.reset();
  374. if (TheGlobalData->m_pendingFile.isEmpty())
  375. m_gameInfo.setMap(TheGlobalData->m_mapName);
  376. else
  377. m_gameInfo.setMap(TheGlobalData->m_pendingFile);
  378. m_gameInfo.setSeed(GetGameLogicRandomSeed());
  379. m_wasDesync = FALSE;
  380. m_doingAnalysis = FALSE;
  381. }
  382. /**
  383. * Reset the recorder to the "initialized state."
  384. */
  385. void RecorderClass::reset() {
  386. if (m_file != NULL) {
  387. fclose(m_file);
  388. m_file = NULL;
  389. }
  390. m_fileName.clear();
  391. init();
  392. }
  393. /**
  394. * update
  395. * Do the update for this frame.
  396. */
  397. void RecorderClass::update() {
  398. if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) {
  399. updateRecord();
  400. } else if (m_mode == RECORDERMODETYPE_PLAYBACK) {
  401. updatePlayback();
  402. }
  403. }
  404. /**
  405. * Do the update for the next frame of this playback.
  406. */
  407. void RecorderClass::updatePlayback() {
  408. cullBadCommands(); // Remove any bad commands that have been inserted by the local user that shouldn't be
  409. // executed during playback.
  410. if (m_nextFrame == -1) {
  411. // This is reached if there are no more commands to be executed.
  412. return;
  413. }
  414. UnsignedInt curFrame = TheGameLogic->getFrame();
  415. if (m_doingAnalysis)
  416. curFrame = m_nextFrame;
  417. // While there are commands to be queued up for this frame, do it.
  418. while (m_nextFrame == curFrame) {
  419. appendNextCommand(); // append the next command to TheCommandQueue
  420. readNextFrame(); // Read the next command's frame number for playback.
  421. }
  422. }
  423. /**
  424. * Stop the currently running playback. This is probably due either to the user exiting out of the playback or
  425. * reaching the end of the playback file.
  426. */
  427. void RecorderClass::stopPlayback() {
  428. if (m_file != NULL) {
  429. fclose(m_file);
  430. m_file = NULL;
  431. }
  432. m_fileName.clear();
  433. // Don't clear the game data if the replay is over - let things continue
  434. //#ifdef DEBUG_CRC
  435. if (!m_doingAnalysis)
  436. TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA);
  437. //#endif
  438. }
  439. /**
  440. * Update function for recording a game. Basically all the pertinant logic commands for this frame are written out
  441. * to a file.
  442. */
  443. void RecorderClass::updateRecord()
  444. {
  445. Bool needFlush = FALSE;
  446. static Int lastFrame = -1;
  447. GameMessage *msg = TheCommandList->getFirstMessage();
  448. while (msg != NULL) {
  449. if (msg->getType() == GameMessage::MSG_NEW_GAME &&
  450. msg->getArgument(0)->integer != GAME_SHELL &&
  451. msg->getArgument(0)->integer != GAME_SINGLE_PLAYER && // Due to the massive amount of scripts that use <local player> in GC and single player, replays have been cut for them.
  452. msg->getArgument(0)->integer != GAME_NONE)
  453. {
  454. m_originalGameMode = msg->getArgument(0)->integer;
  455. DEBUG_LOG(("RecorderClass::updateRecord() - original game is mode %d\n", m_originalGameMode));
  456. lastFrame = 0;
  457. GameDifficulty diff = DIFFICULTY_NORMAL;
  458. if (msg->getArgumentCount() >= 2)
  459. diff = (GameDifficulty)msg->getArgument(1)->integer;
  460. Int rankPoints = 0;
  461. if (msg->getArgumentCount() >= 3)
  462. rankPoints = msg->getArgument(2)->integer;
  463. Int maxFPS = 0;
  464. if (msg->getArgumentCount() >= 4)
  465. maxFPS = msg->getArgument(3)->integer;
  466. startRecording(diff, m_originalGameMode, rankPoints, maxFPS);
  467. } else if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) {
  468. if (m_file != NULL) {
  469. lastFrame = -1;
  470. writeToFile(msg);
  471. stopRecording();
  472. }
  473. m_fileName.clear();
  474. } else {
  475. if (m_file != NULL) {
  476. if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
  477. (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES)) {
  478. // Only write the important messages to the file.
  479. writeToFile(msg);
  480. needFlush = TRUE;
  481. }
  482. }
  483. }
  484. msg = msg->next();
  485. }
  486. if (needFlush) {
  487. fflush(m_file);
  488. }
  489. }
  490. /**
  491. * Start a new file for recording. This will always overwrite the "LastReplay.rep" file with the new one.
  492. * So don't call this unless you really mean it.
  493. */
  494. void RecorderClass::startRecording(GameDifficulty diff, Int originalGameMode, Int rankPoints, Int maxFPS) {
  495. DEBUG_ASSERTCRASH(m_file == NULL, ("Starting to record game while game is in progress."));
  496. reset();
  497. m_mode = RECORDERMODETYPE_RECORD;
  498. AsciiString filepath = getReplayDir();
  499. // We have to make sure the replay dir exists.
  500. TheFileSystem->createDirectory(filepath);
  501. m_fileName = getLastReplayFileName();
  502. m_fileName.concat(getReplayExtention());
  503. filepath.concat(m_fileName);
  504. m_file = fopen(filepath.str(), "wb");
  505. if (m_file == NULL) {
  506. DEBUG_ASSERTCRASH(m_file != NULL, ("Failed to create replay file"));
  507. return;
  508. }
  509. fprintf(m_file, "GENREP");
  510. //
  511. // save space for stats to be filled in.
  512. //
  513. // **** if this changes, change the LAN code above ****
  514. //
  515. time_t t = 0;
  516. fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for start time
  517. fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for end time
  518. UnsignedInt frames = 0;
  519. fwrite(&frames, sizeof(UnsignedInt), 1, m_file); // reserve space for duration in frames
  520. Bool b = FALSE;
  521. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we desync)
  522. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we quit early)
  523. for (Int i=0; i<MAX_SLOTS; ++i)
  524. {
  525. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if player i disconnects)
  526. }
  527. // Print out the name of the replay.
  528. UnicodeString replayName;
  529. replayName = TheGameText->fetch("GUI:LastReplay");
  530. fwprintf(m_file, L"%ws", replayName.str());
  531. fputwc(0, m_file);
  532. // Date and Time
  533. SYSTEMTIME systemTime;
  534. GetLocalTime( &systemTime );
  535. fwrite(&systemTime, sizeof(SYSTEMTIME), 1, m_file);
  536. // write out version info
  537. UnicodeString versionString = TheVersion->getUnicodeVersion();
  538. UnicodeString versionTimeString = TheVersion->getUnicodeBuildTime();
  539. UnsignedInt versionNumber = TheVersion->getVersionNumber();
  540. fwprintf(m_file, L"%ws", versionString.str());
  541. fputwc(0, m_file);
  542. fwprintf(m_file, L"%ws", versionTimeString.str());
  543. fputwc(0, m_file);
  544. fwrite(&versionNumber, sizeof(UnsignedInt), 1, m_file);
  545. fwrite(&(TheGlobalData->m_exeCRC), sizeof(UnsignedInt), 1, m_file);
  546. fwrite(&(TheGlobalData->m_iniCRC), sizeof(UnsignedInt), 1, m_file);
  547. // Number of players
  548. /*
  549. Int numPlayers = ThePlayerList->getPlayerCount();
  550. fwrite(&numPlayers, sizeof(numPlayers), 1, m_file);
  551. */
  552. // Write the slot list.
  553. AsciiString theSlotList;
  554. Int localIndex = -1;
  555. if (TheNetwork)
  556. {
  557. if (TheLAN)
  558. {
  559. GameInfo *game = TheLAN->GetMyGame();
  560. DEBUG_ASSERTCRASH(game, ("Starting a LAN game with no LANGameInfo object!"));
  561. theSlotList = GameInfoToAsciiString(game);
  562. for (Int i=0; i<MAX_SLOTS; ++i)
  563. {
  564. if (game->getLocalIP() == game->getSlot(i)->getIP())
  565. {
  566. localIndex = i;
  567. break;
  568. }
  569. }
  570. }
  571. else
  572. {
  573. theSlotList = GameInfoToAsciiString(TheGameSpyGame);
  574. localIndex = TheGameSpyGame->getLocalSlotNum();
  575. }
  576. }
  577. else
  578. {
  579. if(TheSkirmishGameInfo)
  580. {
  581. TheSkirmishGameInfo->setCRCInterval(REPLAY_CRC_INTERVAL);
  582. theSlotList = GameInfoToAsciiString(TheSkirmishGameInfo);
  583. DEBUG_LOG(("GameInfo String: %s\n",theSlotList.str()));
  584. localIndex = 0;
  585. }
  586. else
  587. {
  588. // single player. format the generic (empty) slotlist
  589. m_gameInfo.setCRCInterval(REPLAY_CRC_INTERVAL);
  590. theSlotList = GameInfoToAsciiString(&m_gameInfo);
  591. }
  592. }
  593. logGameStart(theSlotList);
  594. DEBUG_LOG(("RecorderClass::startRecording - theSlotList = %s\n", theSlotList.str()));
  595. // write slot list (starting spots, color, alliances, etc
  596. fwrite(theSlotList.str(), theSlotList.getLength() + 1, 1, m_file);
  597. fprintf(m_file, "%d", localIndex);
  598. fputc(0, m_file);
  599. /*
  600. /// @todo fix this to use starting spots and player alliances when those are put in the game.
  601. for (Int i = 0; i < numPlayers; ++i) {
  602. Player *player = ThePlayerList->getNthPlayer(i);
  603. if (player == NULL) {
  604. continue;
  605. }
  606. UnicodeString name = player->getPlayerDisplayName();
  607. fwprintf(m_file, L"%s", name.str());
  608. fputwc(0, m_file);
  609. UnicodeString faction = player->getFaction()->getFactionDisplayName();
  610. fwprintf(m_file, L"%s", faction.str());
  611. fputwc(0, m_file);
  612. Int color = player->getColor()->getAsInt();
  613. fwrite(&color, sizeof(color), 1, m_file);
  614. Int team = 0;
  615. Int startingSpot = 0;
  616. fwrite(&startingSpot, sizeof(Int), 1, m_file);
  617. fwrite(&team, sizeof(Int), 1, m_file);
  618. }
  619. */
  620. // Write the game difficulty.
  621. fwrite(&diff, sizeof(Int), 1, m_file);
  622. // Write original game mode
  623. fwrite(&originalGameMode, sizeof(originalGameMode), 1, m_file);
  624. // Write rank points to add at game start
  625. fwrite(&rankPoints, sizeof(rankPoints), 1, m_file);
  626. // Write maxFPS chosen
  627. fwrite(&maxFPS, sizeof(maxFPS), 1, m_file);
  628. DEBUG_LOG(("RecorderClass::startRecording() - diff=%d, mode=%d, FPS=%d\n", diff, originalGameMode, maxFPS));
  629. /*
  630. // Write the map name.
  631. fprintf(m_file, "%s", (TheGlobalData->m_mapName).str());
  632. fputc(0, m_file);
  633. */
  634. /// @todo Need to write game options when there are some to be written.
  635. }
  636. /**
  637. * This will stop the current recording session and close the file. This should always be called at the end of
  638. * every game.
  639. */
  640. void RecorderClass::stopRecording() {
  641. logGameEnd();
  642. if (TheNetwork)
  643. {
  644. //if (TheLAN)
  645. {
  646. if (m_wasDesync)
  647. cleanUpReplayFile();
  648. m_wasDesync = FALSE;
  649. }
  650. }
  651. if (m_file != NULL) {
  652. fclose(m_file);
  653. m_file = NULL;
  654. }
  655. m_fileName.clear();
  656. }
  657. /**
  658. * Write this game message to the record file. This also writes the game message's execution frame.
  659. */
  660. void RecorderClass::writeToFile(GameMessage * msg) {
  661. // Write the frame number for this command.
  662. UnsignedInt frame = TheGameLogic->getFrame();
  663. fwrite(&frame, sizeof(frame), 1, m_file);
  664. // Write the command type
  665. GameMessage::Type type = msg->getType();
  666. fwrite(&type, sizeof(type), 1, m_file);
  667. // Write the player index
  668. Int playerIndex = msg->getPlayerIndex();
  669. fwrite(&playerIndex, sizeof(playerIndex), 1, m_file);
  670. #ifdef DEBUG_LOGGING
  671. AsciiString commandName = msg->getCommandAsAsciiString();
  672. if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
  673. {
  674. commandName.concat(" (Non-Network message!)");
  675. }
  676. else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  677. {
  678. AsciiString tmp;
  679. tmp.format(" (CRC 0x%8.8X)", msg->getArgument(0)->integer);
  680. commandName.concat(tmp);
  681. }
  682. //DEBUG_LOG(("RecorderClass::writeToFile - Adding %s command from player %d to TheCommandList on frame %d\n",
  683. //commandName.str(), msg->getPlayerIndex(), TheGameLogic->getFrame()));
  684. #endif // DEBUG_LOGGING
  685. GameMessageParser *parser = newInstance(GameMessageParser)(msg);
  686. UnsignedByte numTypes = parser->getNumTypes();
  687. fwrite(&numTypes, sizeof(numTypes), 1, m_file);
  688. GameMessageParserArgumentType *argType = parser->getFirstArgumentType();
  689. while (argType != NULL) {
  690. UnsignedByte type = (UnsignedByte)(argType->getType());
  691. fwrite(&type, sizeof(type), 1, m_file);
  692. UnsignedByte argTypeCount = (UnsignedByte)(argType->getArgCount());
  693. fwrite(&argTypeCount, sizeof(argTypeCount), 1, m_file);
  694. argType = argType->getNext();
  695. }
  696. // UnsignedByte lasttype = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
  697. Int numArgs = msg->getArgumentCount();
  698. for (Int i = 0; i < numArgs; ++i) {
  699. // UnsignedByte type = (UnsignedByte)(msg->getArgumentDataType(i));
  700. // if (lasttype != type) {
  701. // fwrite(&type, sizeof(type), 1, m_file);
  702. // lasttype = type;
  703. // }
  704. writeArgument(msg->getArgumentDataType(i), *(msg->getArgument(i)));
  705. }
  706. parser->deleteInstance();
  707. parser = NULL;
  708. fflush(m_file); ///< @todo should this be in the final release?
  709. }
  710. void RecorderClass::writeArgument(GameMessageArgumentDataType type, const GameMessageArgumentType arg) {
  711. if (type == ARGUMENTDATATYPE_INTEGER) {
  712. fwrite(&(arg.integer), sizeof(arg.integer), 1, m_file);
  713. } else if (type == ARGUMENTDATATYPE_REAL) {
  714. fwrite(&(arg.real), sizeof(arg.real), 1, m_file);
  715. } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
  716. fwrite(&(arg.boolean), sizeof(arg.boolean), 1, m_file);
  717. } else if (type == ARGUMENTDATATYPE_OBJECTID) {
  718. fwrite(&(arg.objectID), sizeof(arg.objectID), 1, m_file);
  719. } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
  720. fwrite(&(arg.drawableID), sizeof(arg.drawableID), 1, m_file);
  721. } else if (type == ARGUMENTDATATYPE_TEAMID) {
  722. fwrite(&(arg.teamID), sizeof(arg.teamID), 1, m_file);
  723. } else if (type == ARGUMENTDATATYPE_LOCATION) {
  724. fwrite(&(arg.location), sizeof(arg.location), 1, m_file);
  725. } else if (type == ARGUMENTDATATYPE_PIXEL) {
  726. fwrite(&(arg.pixel), sizeof(arg.pixel), 1, m_file);
  727. } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
  728. fwrite(&(arg.pixelRegion), sizeof(arg.pixelRegion), 1, m_file);
  729. } else if (type == ARGUMENTDATATYPE_TIMESTAMP) {
  730. fwrite(&(arg.timestamp), sizeof(arg.timestamp), 1, m_file);
  731. } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
  732. fwrite(&(arg.wChar), sizeof(arg.wChar), 1, m_file);
  733. }
  734. }
  735. /**
  736. * Read in a replay header, for (1) populating a replay listbox or (2) starting playback. In
  737. * case (2), set FILE *m_file.
  738. */
  739. Bool RecorderClass::readReplayHeader(ReplayHeader& header)
  740. {
  741. AsciiString filepath = getReplayDir();
  742. filepath.concat(header.filename.str());
  743. m_file = fopen(filepath.str(), "rb");
  744. if (m_file == NULL)
  745. {
  746. DEBUG_LOG(("Can't open %s (%s)\n", filepath.str(), header.filename.str()));
  747. return FALSE;
  748. }
  749. // Read the GENREP header.
  750. char genrep[7];
  751. fread(&genrep, sizeof(char), 6, m_file);
  752. genrep[6] = 0;
  753. if (strncmp(genrep, "GENREP", 6)) {
  754. DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have GENREP at the start.\n"));
  755. fclose(m_file);
  756. m_file = NULL;
  757. return FALSE;
  758. }
  759. // read in some stats
  760. fread(&header.startTime, sizeof(time_t), 1, m_file);
  761. fread(&header.endTime, sizeof(time_t), 1, m_file);
  762. fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file);
  763. fread(&header.desyncGame, sizeof(Bool), 1, m_file);
  764. fread(&header.quitEarly, sizeof(Bool), 1, m_file);
  765. for (Int i=0; i<MAX_SLOTS; ++i)
  766. {
  767. fread(&(header.playerDiscons[i]), sizeof(Bool), 1, m_file);
  768. }
  769. // Read the Replay Name. We don't actually do anything with it. Oh well.
  770. header.replayName = readUnicodeString();
  771. // Read the date and time. We don't really do anything with this either. Oh well.
  772. fread(&header.timeVal, sizeof(SYSTEMTIME), 1, m_file);
  773. // Read in the Version info
  774. header.versionString = readUnicodeString();
  775. header.versionTimeString = readUnicodeString();
  776. fread(&header.versionNumber, sizeof(UnsignedInt), 1, m_file);
  777. fread(&header.exeCRC, sizeof(UnsignedInt), 1, m_file);
  778. fread(&header.iniCRC, sizeof(UnsignedInt), 1, m_file);
  779. // Read in the GameInfo
  780. header.gameOptions = readAsciiString();
  781. m_gameInfo.reset();
  782. m_gameInfo.enterGame();
  783. DEBUG_LOG(("RecorderClass::readReplayHeader - GameInfo = %s\n", header.gameOptions.str()));
  784. if (!ParseAsciiStringToGameInfo(&m_gameInfo, header.gameOptions))
  785. {
  786. DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have a valid GameInfo string.\n"));
  787. fclose(m_file);
  788. m_file = NULL;
  789. return FALSE;
  790. }
  791. m_gameInfo.startGame(0);
  792. AsciiString playerIndex = readAsciiString();
  793. header.localPlayerIndex = atoi(playerIndex.str());
  794. if (header.localPlayerIndex < -1 || header.localPlayerIndex >= MAX_SLOTS)
  795. {
  796. DEBUG_LOG(("RecorderClass::readReplayHeader - invalid local slot number.\n"));
  797. m_gameInfo.endGame();
  798. m_gameInfo.reset();
  799. fclose(m_file);
  800. m_file = NULL;
  801. return FALSE;
  802. }
  803. if (header.localPlayerIndex >= 0)
  804. {
  805. Int localIP = m_gameInfo.getSlot(header.localPlayerIndex)->getIP();
  806. m_gameInfo.setLocalIP(localIP);
  807. }
  808. if (!header.forPlayback)
  809. {
  810. m_gameInfo.endGame();
  811. m_gameInfo.reset();
  812. fclose(m_file);
  813. m_file = NULL;
  814. }
  815. return TRUE;
  816. }
  817. #if defined _DEBUG || defined _INTERNAL
  818. Bool RecorderClass::analyzeReplay( AsciiString filename )
  819. {
  820. m_doingAnalysis = TRUE;
  821. return playbackFile(filename);
  822. }
  823. Bool RecorderClass::isAnalysisInProgress( void )
  824. {
  825. return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1;
  826. }
  827. #endif
  828. AsciiString RecorderClass::getCurrentReplayFilename( void )
  829. {
  830. if (m_mode == RECORDERMODETYPE_PLAYBACK)
  831. {
  832. return m_currentReplayFilename;
  833. }
  834. return AsciiString::TheEmptyString;
  835. }
  836. class CRCInfo
  837. {
  838. public:
  839. CRCInfo();
  840. void addCRC(UnsignedInt val);
  841. UnsignedInt readCRC(void);
  842. void setLocalPlayer(UnsignedInt index) { m_localPlayer = index; }
  843. UnsignedInt getLocalPlayer(void) { return m_localPlayer; }
  844. void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; }
  845. Bool sawCRCMismatch(void) { return m_sawCRCMismatch; }
  846. protected:
  847. Bool m_sawCRCMismatch;
  848. Bool m_skippedOne;
  849. std::list<UnsignedInt> m_data;
  850. UnsignedInt m_localPlayer;
  851. };
  852. CRCInfo::CRCInfo()
  853. {
  854. m_localPlayer = ~0;
  855. m_skippedOne = FALSE;
  856. m_sawCRCMismatch = FALSE;
  857. }
  858. void CRCInfo::addCRC(UnsignedInt val)
  859. {
  860. //if (!m_skippedOne)
  861. //{
  862. // m_skippedOne = TRUE;
  863. // return;
  864. //}
  865. m_data.push_back(val);
  866. //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)\n", val, m_data.size(), !m_data.empty()));
  867. }
  868. UnsignedInt CRCInfo::readCRC(void)
  869. {
  870. if (m_data.empty())
  871. {
  872. //DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d\n", m_data.size()));
  873. return 0;
  874. }
  875. UnsignedInt val = m_data.front();
  876. m_data.pop_front();
  877. //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d\n", val, !m_data.empty(), m_data.size()));
  878. return val;
  879. }
  880. void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback)
  881. {
  882. if (fromPlayback)
  883. {
  884. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo\n", newCRC, playerIndex));
  885. m_crcInfo->addCRC(newCRC);
  886. return;
  887. }
  888. Int localPlayerIndex = m_crcInfo->getLocalPlayer();
  889. Bool samePlayer = FALSE;
  890. AsciiString playerName;
  891. playerName.format("player%d", localPlayerIndex);
  892. const Player *p = ThePlayerList->getNthPlayer(playerIndex);
  893. if (!p || (p->getPlayerNameKey() == NAMEKEY(playerName)))
  894. samePlayer = TRUE;
  895. if (samePlayer || (localPlayerIndex < 0))
  896. {
  897. UnsignedInt playbackCRC = m_crcInfo->readCRC();
  898. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of %8.8X/%8.8X from %d\n", newCRC, playbackCRC, playerIndex));
  899. if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch())
  900. {
  901. m_crcInfo->setSawCRCMismatch();
  902. //Kris: Patch 1.01 November 10, 2003 (integrated changes from Matt Campbell)
  903. // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning
  904. // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the
  905. // tail end of patch season, let's just disable the message, and hope the users believe the
  906. // problem is fixed. -MDC 3/20/2003
  907. //TheInGameUI->message("GUI:CRCMismatch");
  908. DEBUG_CRASH(("Replay has gone out of sync! All bets are off!\nOld:%8.8X New:%8.8X\nFrame:%d",
  909. playbackCRC, newCRC, TheGameLogic->getFrame()));
  910. }
  911. return;
  912. }
  913. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Skipping CRC of %8.8X from %d (our index is %d)\n", newCRC, playerIndex, localPlayerIndex));
  914. }
  915. /**
  916. * Return true if this version of the file is the same as our version of the game
  917. */
  918. Bool RecorderClass::testVersionPlayback(AsciiString filename)
  919. {
  920. ReplayHeader header;
  921. header.forPlayback = TRUE;
  922. header.filename = filename;
  923. Bool success = readReplayHeader( header );
  924. if (!success)
  925. {
  926. return FALSE;
  927. }
  928. Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
  929. Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
  930. Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
  931. Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
  932. Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
  933. Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
  934. if(exeDifferent || iniDifferent)
  935. {
  936. return TRUE;
  937. }
  938. return FALSE;
  939. }
  940. /**
  941. * Start playback of the file. Return true or false depending on if the file is
  942. * a valid replay file or not.
  943. */
  944. Bool RecorderClass::playbackFile(AsciiString filename)
  945. {
  946. if (!m_doingAnalysis)
  947. {
  948. if (TheGameLogic->isInGame())
  949. {
  950. TheGameLogic->clearGameData();
  951. }
  952. }
  953. m_mode = RECORDERMODETYPE_PLAYBACK;
  954. ReplayHeader header;
  955. header.forPlayback = TRUE;
  956. header.filename = filename;
  957. Bool success = readReplayHeader( header );
  958. if (!success)
  959. {
  960. return FALSE;
  961. }
  962. #ifdef DEBUG_LOGGING
  963. Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
  964. Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
  965. Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
  966. Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
  967. Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
  968. Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
  969. AsciiString debugString;
  970. AsciiString tempStr;
  971. if (exeDifferent)
  972. {
  973. debugString = "EXE is different:\n";
  974. if (versionStringDiff)
  975. {
  976. tempStr.format(" Version [%ls] vs [%ls]\n", TheVersion->getUnicodeVersion().str(), header.versionString.str());
  977. debugString.concat(tempStr);
  978. }
  979. if (versionTimeStringDiff)
  980. {
  981. tempStr.format(" Build Time [%ls] vs [%ls]\n", TheVersion->getUnicodeBuildTime().str(), header.versionTimeString.str());
  982. debugString.concat(tempStr);
  983. }
  984. if (versionNumberDiff)
  985. {
  986. tempStr.format(" Version Number %8.8X vs %8.8X\n", TheVersion->getVersionNumber(), header.versionNumber);
  987. debugString.concat(tempStr);
  988. }
  989. if (exeCRCDiff)
  990. {
  991. tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_exeCRC, header.exeCRC);
  992. debugString.concat(tempStr);
  993. }
  994. }
  995. if (iniDifferent)
  996. {
  997. debugString.concat("INIs are different:\n");
  998. tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_iniCRC, header.iniCRC);
  999. debugString.concat(tempStr);
  1000. }
  1001. DEBUG_ASSERTCRASH(!exeDifferent && !iniDifferent, (debugString.str()));
  1002. #endif
  1003. TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();
  1004. #ifdef DEBUG_LOGGING
  1005. if (header.localPlayerIndex >= 0)
  1006. {
  1007. DEBUG_LOG(("Local player is %ls (slot %d, IP %8.8X)\n",
  1008. m_gameInfo.getSlot(header.localPlayerIndex)->getName().str(), header.localPlayerIndex, m_gameInfo.getSlot(header.localPlayerIndex)->getIP()));
  1009. }
  1010. #endif
  1011. m_crcInfo = NEW CRCInfo;
  1012. m_crcInfo->setLocalPlayer(header.localPlayerIndex);
  1013. REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval();
  1014. DEBUG_LOG(("Player index is %d, replay CRC interval is %d\n", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL));
  1015. Int difficulty = 0;
  1016. fread(&difficulty, sizeof(difficulty), 1, m_file);
  1017. fread(&m_originalGameMode, sizeof(m_originalGameMode), 1, m_file);
  1018. Int rankPoints = 0;
  1019. fread(&rankPoints, sizeof(rankPoints), 1, m_file);
  1020. Int maxFPS = 0;
  1021. fread(&maxFPS, sizeof(maxFPS), 1, m_file);
  1022. DEBUG_LOG(("RecorderClass::playbackFile() - original game was mode %d\n", m_originalGameMode));
  1023. readNextFrame();
  1024. // send a message to the logic for a new game
  1025. if (!m_doingAnalysis)
  1026. {
  1027. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
  1028. msg->appendIntegerArgument(GAME_REPLAY);
  1029. msg->appendIntegerArgument(difficulty);
  1030. msg->appendIntegerArgument(rankPoints);
  1031. if( maxFPS != 0 )
  1032. msg->appendIntegerArgument(maxFPS);
  1033. //InitGameLogicRandom( m_gameInfo.getSeed());
  1034. InitRandom( m_gameInfo.getSeed() );
  1035. }
  1036. m_currentReplayFilename = filename;
  1037. return TRUE;
  1038. }
  1039. /**
  1040. * Read a unicode string from the current file position. The string is assumed to be 0-terminated.
  1041. */
  1042. UnicodeString RecorderClass::readUnicodeString() {
  1043. UnsignedShort str[1024] = L"";
  1044. Int index = 0;
  1045. Int c = fgetwc(m_file);
  1046. if (c == EOF) {
  1047. str[index] = 0;
  1048. }
  1049. str[index] = c;
  1050. while (index < 1024 && str[index] != 0) {
  1051. ++index;
  1052. Int c = fgetwc(m_file);
  1053. if (c == EOF) {
  1054. str[index] = 0;
  1055. break;
  1056. }
  1057. str[index] = c;
  1058. }
  1059. str[1023] = L'\0';
  1060. UnicodeString retval(str);
  1061. return retval;
  1062. }
  1063. /**
  1064. * Read an ascii string from the current file position. The string is assumed to be 0-terminated.
  1065. */
  1066. AsciiString RecorderClass::readAsciiString() {
  1067. char str[1024] = "";
  1068. Int index = 0;
  1069. Int c = fgetc(m_file);
  1070. if (c == EOF) {
  1071. str[index] = 0;
  1072. }
  1073. str[index] = c;
  1074. while (index < 1024 && str[index] != 0) {
  1075. ++index;
  1076. Int c = fgetc(m_file);
  1077. if (c == EOF) {
  1078. str[index] = 0;
  1079. break;
  1080. }
  1081. str[index] = c;
  1082. }
  1083. str[1023] = '\0';
  1084. AsciiString retval(str);
  1085. return retval;
  1086. }
  1087. /**
  1088. * Read the frame number for the next command in the playback file. If the end of the file is reached, the playback
  1089. * is stopped and the next frame is said to be -1.
  1090. */
  1091. void RecorderClass::readNextFrame() {
  1092. Int retcode = fread(&m_nextFrame, sizeof(m_nextFrame), 1, m_file);
  1093. if (retcode != 1) {
  1094. DEBUG_LOG(("RecorderClass::readNextFrame - fread failed on frame %d\n", TheGameLogic->getFrame()));
  1095. m_nextFrame = -1;
  1096. stopPlayback();
  1097. }
  1098. }
  1099. /**
  1100. * This reads the next command from the replay file and appends it to TheCommandList.
  1101. */
  1102. void RecorderClass::appendNextCommand() {
  1103. GameMessage::Type type;
  1104. Int retcode = fread(&type, sizeof(type), 1, m_file);
  1105. if (retcode != 1) {
  1106. DEBUG_LOG(("RecorderClass::appendNextCommand - fread failed on frame %d\n", m_nextFrame/*TheGameLogic->getFrame()*/));
  1107. return;
  1108. }
  1109. GameMessage *msg = newInstance(GameMessage)(type);
  1110. if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type == GameMessage::MSG_CLEAR_GAME_DATA)
  1111. {
  1112. }
  1113. else
  1114. {
  1115. if (!m_doingAnalysis)
  1116. {
  1117. TheCommandList->appendMessage(msg);
  1118. }
  1119. }
  1120. #ifdef DEBUG_LOGGING
  1121. AsciiString commandName = msg->getCommandAsAsciiString();
  1122. if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
  1123. {
  1124. commandName.concat(" (Non-Network message!)");
  1125. }
  1126. else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  1127. {
  1128. commandName.concat(" (CRC message!)");
  1129. }
  1130. #endif // DEBUG_LOGGING
  1131. Int playerIndex = -1;
  1132. fread(&playerIndex, sizeof(playerIndex), 1, m_file);
  1133. msg->friend_setPlayerIndex(playerIndex);
  1134. // don't debug log this if we're debugging sync errors, as it will cause diff problems between a game and it's replay...
  1135. #ifdef DEBUG_LOGGING
  1136. Bool logCommand = true;
  1137. #ifdef DEBUG_CRC
  1138. if (!m_doingAnalysis)
  1139. logCommand = false;
  1140. #endif
  1141. if (logCommand)
  1142. {
  1143. DEBUG_LOG(("RecorderClass::appendNextCommand - Adding %s command from player %d to TheCommandList on frame %d\n",
  1144. commandName.str(), (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)?0:msg->getPlayerIndex(), m_nextFrame/*TheGameLogic->getFrame()*/));
  1145. }
  1146. #endif
  1147. UnsignedByte numTypes = 0;
  1148. Int totalArgs = 0;
  1149. fread(&numTypes, sizeof(numTypes), 1, m_file);
  1150. GameMessageParser *parser = newInstance(GameMessageParser)();
  1151. for (UnsignedByte i = 0; i < numTypes; ++i) {
  1152. UnsignedByte type = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
  1153. fread(&type, sizeof(type), 1, m_file);
  1154. UnsignedByte numArgs = 0;
  1155. fread(&numArgs, sizeof(numArgs), 1, m_file);
  1156. parser->addArgType((GameMessageArgumentDataType)type, numArgs);
  1157. totalArgs += numArgs;
  1158. }
  1159. GameMessageParserArgumentType *parserArgType = parser->getFirstArgumentType();
  1160. GameMessageArgumentDataType lasttype = ARGUMENTDATATYPE_UNKNOWN;
  1161. Int argsLeftForType = 0;
  1162. if (parserArgType != NULL) {
  1163. lasttype = parserArgType->getType();
  1164. argsLeftForType = parserArgType->getArgCount();
  1165. }
  1166. for (Int j = 0; j < totalArgs; ++j) {
  1167. readArgument(lasttype, msg);
  1168. --argsLeftForType;
  1169. if (argsLeftForType == 0) {
  1170. DEBUG_ASSERTCRASH(parserArgType != NULL, ("parserArgType was NULL when it shouldn't have been."));
  1171. if (parserArgType == NULL) {
  1172. return;
  1173. }
  1174. parserArgType = parserArgType->getNext();
  1175. // parserArgType is allowed to be NULL here, this is the case if there are no more arguments.
  1176. if (parserArgType != NULL) {
  1177. argsLeftForType = parserArgType->getArgCount();
  1178. lasttype = parserArgType->getType();
  1179. }
  1180. }
  1181. }
  1182. if (type == GameMessage::MSG_CLEAR_GAME_DATA || type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  1183. {
  1184. msg->deleteInstance();
  1185. msg = NULL;
  1186. }
  1187. if (m_doingAnalysis)
  1188. {
  1189. msg->deleteInstance();
  1190. msg = NULL;
  1191. }
  1192. parser->deleteInstance();
  1193. parser = NULL;
  1194. }
  1195. void RecorderClass::readArgument(GameMessageArgumentDataType type, GameMessage *msg) {
  1196. if (type == ARGUMENTDATATYPE_INTEGER) {
  1197. Int theint;
  1198. fread(&theint, sizeof(theint), 1, m_file);
  1199. msg->appendIntegerArgument(theint);
  1200. #ifdef DEBUG_LOGGING
  1201. if (m_doingAnalysis)
  1202. {
  1203. DEBUG_LOG(("Integer argument: %d (%8.8X)\n", theint, theint));
  1204. }
  1205. #endif
  1206. } else if (type == ARGUMENTDATATYPE_REAL) {
  1207. Real thereal;
  1208. fread(&thereal, sizeof(thereal), 1, m_file);
  1209. msg->appendRealArgument(thereal);
  1210. #ifdef DEBUG_LOGGING
  1211. if (m_doingAnalysis)
  1212. {
  1213. DEBUG_LOG(("Real argument: %g (%8.8X)\n", thereal, *(int *)&thereal));
  1214. }
  1215. #endif
  1216. } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
  1217. Bool thebool;
  1218. fread(&thebool, sizeof(thebool), 1, m_file);
  1219. msg->appendBooleanArgument(thebool);
  1220. #ifdef DEBUG_LOGGING
  1221. if (m_doingAnalysis)
  1222. {
  1223. DEBUG_LOG(("Bool argument: %d\n", thebool));
  1224. }
  1225. #endif
  1226. } else if (type == ARGUMENTDATATYPE_OBJECTID) {
  1227. ObjectID theid;
  1228. fread(&theid, sizeof(theid), 1, m_file);
  1229. msg->appendObjectIDArgument(theid);
  1230. #ifdef DEBUG_LOGGING
  1231. if (m_doingAnalysis)
  1232. {
  1233. DEBUG_LOG(("Object ID argument: %d\n", theid));
  1234. }
  1235. #endif
  1236. } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
  1237. DrawableID theid;
  1238. fread(&theid, sizeof(theid), 1, m_file);
  1239. msg->appendDrawableIDArgument(theid);
  1240. #ifdef DEBUG_LOGGING
  1241. if (m_doingAnalysis)
  1242. {
  1243. DEBUG_LOG(("Drawable ID argument: %d\n", theid));
  1244. }
  1245. #endif
  1246. } else if (type == ARGUMENTDATATYPE_TEAMID) {
  1247. UnsignedInt theid;
  1248. fread(&theid, sizeof(theid), 1, m_file);
  1249. msg->appendTeamIDArgument(theid);
  1250. #ifdef DEBUG_LOGGING
  1251. if (m_doingAnalysis)
  1252. {
  1253. DEBUG_LOG(("Team ID argument: %d\n", theid));
  1254. }
  1255. #endif
  1256. } else if (type == ARGUMENTDATATYPE_LOCATION) {
  1257. Coord3D loc;
  1258. fread(&loc, sizeof(loc), 1, m_file);
  1259. msg->appendLocationArgument(loc);
  1260. #ifdef DEBUG_LOGGING
  1261. if (m_doingAnalysis)
  1262. {
  1263. DEBUG_LOG(("Coord3D argument: %g %g %g (%8.8X %8.8X %8.8X)\n", loc.x, loc.y, loc.z,
  1264. *(int *)&loc.x, *(int *)&loc.y, *(int *)&loc.z));
  1265. }
  1266. #endif
  1267. } else if (type == ARGUMENTDATATYPE_PIXEL) {
  1268. ICoord2D pixel;
  1269. fread(&pixel, sizeof(pixel), 1, m_file);
  1270. msg->appendPixelArgument(pixel);
  1271. #ifdef DEBUG_LOGGING
  1272. if (m_doingAnalysis)
  1273. {
  1274. DEBUG_LOG(("Pixel argument: %d,%d\n", pixel.x, pixel.y));
  1275. }
  1276. #endif
  1277. } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
  1278. IRegion2D reg;
  1279. fread(&reg, sizeof(reg), 1, m_file);
  1280. msg->appendPixelRegionArgument(reg);
  1281. #ifdef DEBUG_LOGGING
  1282. if (m_doingAnalysis)
  1283. {
  1284. DEBUG_LOG(("Pixel Region argument: %d,%d -> %d,%d\n", reg.lo.x, reg.lo.y, reg.hi.x, reg.hi.y));
  1285. }
  1286. #endif
  1287. } else if (type == ARGUMENTDATATYPE_TIMESTAMP) { // Not to be confused with Terrance Stamp... Kneel before Zod!!!
  1288. UnsignedInt stamp;
  1289. fread(&stamp, sizeof(stamp), 1, m_file);
  1290. msg->appendTimestampArgument(stamp);
  1291. #ifdef DEBUG_LOGGING
  1292. if (m_doingAnalysis)
  1293. {
  1294. DEBUG_LOG(("Timestamp argument: %d\n", stamp));
  1295. }
  1296. #endif
  1297. } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
  1298. WideChar theid;
  1299. fread(&theid, sizeof(theid), 1, m_file);
  1300. msg->appendWideCharArgument(theid);
  1301. #ifdef DEBUG_LOGGING
  1302. if (m_doingAnalysis)
  1303. {
  1304. DEBUG_LOG(("WideChar argument: %d (%lc)\n", theid, theid));
  1305. }
  1306. #endif
  1307. }
  1308. }
  1309. /**
  1310. * This needs to be called for every frame during playback. Basically it prevents the user from inserting.
  1311. */
  1312. void RecorderClass::cullBadCommands() {
  1313. if (m_doingAnalysis)
  1314. return;
  1315. GameMessage *msg = TheCommandList->getFirstMessage();
  1316. GameMessage *next = NULL;
  1317. while (msg != NULL) {
  1318. next = msg->next();
  1319. if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
  1320. (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES) &&
  1321. (msg->getType() != GameMessage::MSG_LOGIC_CRC)) {
  1322. msg->deleteInstance();
  1323. }
  1324. msg = next;
  1325. }
  1326. }
  1327. /**
  1328. * returns the directory that holds the replay files.
  1329. */
  1330. AsciiString RecorderClass::getReplayDir()
  1331. {
  1332. const char* replayDir = "Replays\\";
  1333. AsciiString tmp = TheGlobalData->getPath_UserData();
  1334. tmp.concat(replayDir);
  1335. return tmp;
  1336. }
  1337. /**
  1338. * returns the file extention for the replay files.
  1339. */
  1340. AsciiString RecorderClass::getReplayExtention() {
  1341. return AsciiString(replayExtention);
  1342. }
  1343. /**
  1344. * returns the file name used for the replay file that is recorded to.
  1345. */
  1346. AsciiString RecorderClass::getLastReplayFileName()
  1347. {
  1348. #if defined(_DEBUG) || defined(_INTERNAL)
  1349. if (TheNetwork && TheGlobalData->m_saveStats)
  1350. {
  1351. GameInfo *game = NULL;
  1352. if (TheLAN)
  1353. game = TheLAN->GetMyGame();
  1354. else if (TheGameSpyInfo)
  1355. game = TheGameSpyGame;
  1356. if (game)
  1357. {
  1358. AsciiString players;
  1359. AsciiString full;
  1360. AsciiString fullPlusNum;
  1361. AsciiString mapName = game->getMap();
  1362. const char *fname = mapName.reverseFind('\\');
  1363. if (fname)
  1364. mapName = fname+1;
  1365. for (Int i=0; i<MAX_SLOTS; ++i)
  1366. {
  1367. GameSlot *slot = game->getSlot(i);
  1368. if (slot && slot->isHuman())
  1369. {
  1370. AsciiString player;
  1371. player.format("%ls_", slot->getName().str());
  1372. players.concat(player);
  1373. }
  1374. }
  1375. full.format("%s%s_%d_%d", players.str(), mapName.str(), game->getSeed(), game->getLocalSlotNum());
  1376. AsciiString testString;
  1377. testString.format("%s%s%s", getReplayDir().str(), full.str(), replayExtention);
  1378. FILE *fp;
  1379. fp = fopen(testString.str(), "rb");
  1380. if (fp)
  1381. {
  1382. fclose(fp);
  1383. }
  1384. else
  1385. {
  1386. return full;
  1387. }
  1388. Int test = 1;
  1389. while (test < 20)
  1390. {
  1391. fullPlusNum.format("%s_%d", full.str(), test);
  1392. testString.format("%s%s%s", getReplayDir().str(), fullPlusNum.str(), replayExtention);
  1393. fp = fopen(testString.str(), "rb");
  1394. if (fp)
  1395. {
  1396. fclose(fp);
  1397. ++test;
  1398. }
  1399. else
  1400. {
  1401. return fullPlusNum;
  1402. }
  1403. }
  1404. return fullPlusNum;
  1405. }
  1406. }
  1407. #endif
  1408. return AsciiString(lastReplayFileName);
  1409. }
  1410. /**
  1411. * return the current operating mode of TheRecorder.
  1412. */
  1413. RecorderModeType RecorderClass::getMode() {
  1414. return m_mode;
  1415. }
  1416. ///< Show or Hide the Replay controls
  1417. void RecorderClass::initControls()
  1418. {
  1419. NameKeyType parentReplayControlID = TheNameKeyGenerator->nameToKey( AsciiString("ReplayControl.wnd:ParentReplayControl") );
  1420. GameWindow *parentReplayControl = TheWindowManager->winGetWindowFromId( NULL, parentReplayControlID );
  1421. Bool show = (getMode() != RECORDERMODETYPE_PLAYBACK);
  1422. if (parentReplayControl)
  1423. {
  1424. parentReplayControl->winHide(show); // show the replay control window.
  1425. }
  1426. }
  1427. ///< is this a multiplayer game (record OR playback)?
  1428. Bool RecorderClass::isMultiplayer( void )
  1429. {
  1430. if (m_mode == RECORDERMODETYPE_PLAYBACK)
  1431. {
  1432. GameSlot *slot;
  1433. for (int i=0; i<MAX_SLOTS; ++i)
  1434. {
  1435. slot = m_gameInfo.getSlot(i);
  1436. if (slot && slot->isOccupied()) ///< slots default to closed for non-networked games
  1437. return true;
  1438. }
  1439. }
  1440. if (TheGameLogic->getGameMode()==GAME_SINGLE_PLAYER) {
  1441. return false; // single player isn't multiplayer.
  1442. }
  1443. if (TheGameLogic->getGameMode()==GAME_SHELL) {
  1444. return false; // shell isn't multiplayer.
  1445. }
  1446. if (TheNetwork || TheSkirmishGameInfo)
  1447. return true;
  1448. return false;
  1449. }
  1450. /**
  1451. * Create a new recorder object.
  1452. */
  1453. RecorderClass * createRecorder() {
  1454. return NEW RecorderClass;
  1455. }