Recorder.cpp 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616
  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. #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_NONE) {
  452. m_originalGameMode = msg->getArgument(0)->integer;
  453. DEBUG_LOG(("RecorderClass::updateRecord() - original game is mode %d\n", m_originalGameMode));
  454. lastFrame = 0;
  455. GameDifficulty diff = DIFFICULTY_NORMAL;
  456. if (msg->getArgumentCount() >= 2)
  457. diff = (GameDifficulty)msg->getArgument(1)->integer;
  458. Int rankPoints = 0;
  459. if (msg->getArgumentCount() >= 3)
  460. rankPoints = msg->getArgument(2)->integer;
  461. Int maxFPS = 0;
  462. if (msg->getArgumentCount() >= 4)
  463. maxFPS = msg->getArgument(3)->integer;
  464. startRecording(diff, m_originalGameMode, rankPoints, maxFPS);
  465. } else if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) {
  466. if (m_file != NULL) {
  467. lastFrame = -1;
  468. writeToFile(msg);
  469. stopRecording();
  470. }
  471. m_fileName.clear();
  472. } else {
  473. if (m_file != NULL) {
  474. if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
  475. (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES)) {
  476. // Only write the important messages to the file.
  477. writeToFile(msg);
  478. needFlush = TRUE;
  479. }
  480. }
  481. }
  482. msg = msg->next();
  483. }
  484. if (needFlush) {
  485. fflush(m_file);
  486. }
  487. }
  488. /**
  489. * Start a new file for recording. This will always overwrite the "LastReplay.rep" file with the new one.
  490. * So don't call this unless you really mean it.
  491. */
  492. void RecorderClass::startRecording(GameDifficulty diff, Int originalGameMode, Int rankPoints, Int maxFPS) {
  493. DEBUG_ASSERTCRASH(m_file == NULL, ("Starting to record game while game is in progress."));
  494. reset();
  495. m_mode = RECORDERMODETYPE_RECORD;
  496. AsciiString filepath = getReplayDir();
  497. // We have to make sure the replay dir exists.
  498. TheFileSystem->createDirectory(filepath);
  499. m_fileName = getLastReplayFileName();
  500. m_fileName.concat(getReplayExtention());
  501. filepath.concat(m_fileName);
  502. m_file = fopen(filepath.str(), "wb");
  503. if (m_file == NULL) {
  504. DEBUG_ASSERTCRASH(m_file != NULL, ("Failed to create replay file"));
  505. return;
  506. }
  507. fprintf(m_file, "GENREP");
  508. //
  509. // save space for stats to be filled in.
  510. //
  511. // **** if this changes, change the LAN Playtest code above ****
  512. //
  513. time_t t = 0;
  514. fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for start time
  515. fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for end time
  516. UnsignedInt frames = 0;
  517. fwrite(&frames, sizeof(UnsignedInt), 1, m_file); // reserve space for duration in frames
  518. Bool b = FALSE;
  519. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we desync)
  520. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we quit early)
  521. for (Int i=0; i<MAX_SLOTS; ++i)
  522. {
  523. fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if player i disconnects)
  524. }
  525. // Print out the name of the replay.
  526. UnicodeString replayName;
  527. replayName = TheGameText->fetch("GUI:LastReplay");
  528. fwprintf(m_file, L"%ws", replayName.str());
  529. fputwc(0, m_file);
  530. // Date and Time
  531. SYSTEMTIME systemTime;
  532. GetLocalTime( &systemTime );
  533. fwrite(&systemTime, sizeof(SYSTEMTIME), 1, m_file);
  534. // write out version info
  535. UnicodeString versionString = TheVersion->getUnicodeVersion();
  536. UnicodeString versionTimeString = TheVersion->getUnicodeBuildTime();
  537. UnsignedInt versionNumber = TheVersion->getVersionNumber();
  538. fwprintf(m_file, L"%ws", versionString.str());
  539. fputwc(0, m_file);
  540. fwprintf(m_file, L"%ws", versionTimeString.str());
  541. fputwc(0, m_file);
  542. fwrite(&versionNumber, sizeof(UnsignedInt), 1, m_file);
  543. fwrite(&(TheGlobalData->m_exeCRC), sizeof(UnsignedInt), 1, m_file);
  544. fwrite(&(TheGlobalData->m_iniCRC), sizeof(UnsignedInt), 1, m_file);
  545. // Number of players
  546. /*
  547. Int numPlayers = ThePlayerList->getPlayerCount();
  548. fwrite(&numPlayers, sizeof(numPlayers), 1, m_file);
  549. */
  550. // Write the slot list.
  551. AsciiString theSlotList;
  552. Int localIndex = -1;
  553. if (TheNetwork)
  554. {
  555. if (TheLAN)
  556. {
  557. GameInfo *game = TheLAN->GetMyGame();
  558. DEBUG_ASSERTCRASH(game, ("Starting a LAN game with no LANGameInfo object!"));
  559. theSlotList = GameInfoToAsciiString(game);
  560. for (Int i=0; i<MAX_SLOTS; ++i)
  561. {
  562. if (game->getLocalIP() == game->getSlot(i)->getIP())
  563. {
  564. localIndex = i;
  565. break;
  566. }
  567. }
  568. }
  569. else
  570. {
  571. theSlotList = GameInfoToAsciiString(TheGameSpyGame);
  572. localIndex = TheGameSpyGame->getLocalSlotNum();
  573. }
  574. }
  575. else
  576. {
  577. if(TheSkirmishGameInfo)
  578. {
  579. TheSkirmishGameInfo->setCRCInterval(REPLAY_CRC_INTERVAL);
  580. theSlotList = GameInfoToAsciiString(TheSkirmishGameInfo);
  581. DEBUG_LOG(("GameInfo String: %s\n",theSlotList.str()));
  582. localIndex = 0;
  583. }
  584. else
  585. {
  586. // single player. format the generic (empty) slotlist
  587. m_gameInfo.setCRCInterval(REPLAY_CRC_INTERVAL);
  588. theSlotList = GameInfoToAsciiString(&m_gameInfo);
  589. }
  590. }
  591. logGameStart(theSlotList);
  592. DEBUG_LOG(("RecorderClass::startRecording - theSlotList = %s\n", theSlotList.str()));
  593. // write slot list (starting spots, color, alliances, etc
  594. fwrite(theSlotList.str(), theSlotList.getLength() + 1, 1, m_file);
  595. fprintf(m_file, "%d", localIndex);
  596. fputc(0, m_file);
  597. /*
  598. /// @todo fix this to use starting spots and player alliances when those are put in the game.
  599. for (Int i = 0; i < numPlayers; ++i) {
  600. Player *player = ThePlayerList->getNthPlayer(i);
  601. if (player == NULL) {
  602. continue;
  603. }
  604. UnicodeString name = player->getPlayerDisplayName();
  605. fwprintf(m_file, L"%s", name.str());
  606. fputwc(0, m_file);
  607. UnicodeString faction = player->getFaction()->getFactionDisplayName();
  608. fwprintf(m_file, L"%s", faction.str());
  609. fputwc(0, m_file);
  610. Int color = player->getColor()->getAsInt();
  611. fwrite(&color, sizeof(color), 1, m_file);
  612. Int team = 0;
  613. Int startingSpot = 0;
  614. fwrite(&startingSpot, sizeof(Int), 1, m_file);
  615. fwrite(&team, sizeof(Int), 1, m_file);
  616. }
  617. */
  618. // Write the game difficulty.
  619. fwrite(&diff, sizeof(Int), 1, m_file);
  620. // Write original game mode
  621. fwrite(&originalGameMode, sizeof(originalGameMode), 1, m_file);
  622. // Write rank points to add at game start
  623. fwrite(&rankPoints, sizeof(rankPoints), 1, m_file);
  624. // Write maxFPS chosen
  625. fwrite(&maxFPS, sizeof(maxFPS), 1, m_file);
  626. DEBUG_LOG(("RecorderClass::startRecording() - diff=%d, mode=%d, FPS=%d\n", diff, originalGameMode, maxFPS));
  627. /*
  628. // Write the map name.
  629. fprintf(m_file, "%s", (TheGlobalData->m_mapName).str());
  630. fputc(0, m_file);
  631. */
  632. /// @todo Need to write game options when there are some to be written.
  633. }
  634. /**
  635. * This will stop the current recording session and close the file. This should always be called at the end of
  636. * every game.
  637. */
  638. void RecorderClass::stopRecording() {
  639. logGameEnd();
  640. if (TheNetwork)
  641. {
  642. //if (TheLAN)
  643. {
  644. if (m_wasDesync)
  645. cleanUpReplayFile();
  646. m_wasDesync = FALSE;
  647. }
  648. }
  649. if (m_file != NULL) {
  650. fclose(m_file);
  651. m_file = NULL;
  652. }
  653. m_fileName.clear();
  654. }
  655. /**
  656. * Write this game message to the record file. This also writes the game message's execution frame.
  657. */
  658. void RecorderClass::writeToFile(GameMessage * msg) {
  659. // Write the frame number for this command.
  660. UnsignedInt frame = TheGameLogic->getFrame();
  661. fwrite(&frame, sizeof(frame), 1, m_file);
  662. // Write the command type
  663. GameMessage::Type type = msg->getType();
  664. fwrite(&type, sizeof(type), 1, m_file);
  665. // Write the player index
  666. Int playerIndex = msg->getPlayerIndex();
  667. fwrite(&playerIndex, sizeof(playerIndex), 1, m_file);
  668. #ifdef DEBUG_LOGGING
  669. AsciiString commandName = msg->getCommandAsAsciiString();
  670. if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
  671. {
  672. commandName.concat(" (Non-Network message!)");
  673. }
  674. else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  675. {
  676. AsciiString tmp;
  677. tmp.format(" (CRC 0x%8.8X)", msg->getArgument(0)->integer);
  678. commandName.concat(tmp);
  679. }
  680. //DEBUG_LOG(("RecorderClass::writeToFile - Adding %s command from player %d to TheCommandList on frame %d\n",
  681. //commandName.str(), msg->getPlayerIndex(), TheGameLogic->getFrame()));
  682. #endif // DEBUG_LOGGING
  683. GameMessageParser *parser = newInstance(GameMessageParser)(msg);
  684. UnsignedByte numTypes = parser->getNumTypes();
  685. fwrite(&numTypes, sizeof(numTypes), 1, m_file);
  686. GameMessageParserArgumentType *argType = parser->getFirstArgumentType();
  687. while (argType != NULL) {
  688. UnsignedByte type = (UnsignedByte)(argType->getType());
  689. fwrite(&type, sizeof(type), 1, m_file);
  690. UnsignedByte argTypeCount = (UnsignedByte)(argType->getArgCount());
  691. fwrite(&argTypeCount, sizeof(argTypeCount), 1, m_file);
  692. argType = argType->getNext();
  693. }
  694. // UnsignedByte lasttype = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
  695. Int numArgs = msg->getArgumentCount();
  696. for (Int i = 0; i < numArgs; ++i) {
  697. // UnsignedByte type = (UnsignedByte)(msg->getArgumentDataType(i));
  698. // if (lasttype != type) {
  699. // fwrite(&type, sizeof(type), 1, m_file);
  700. // lasttype = type;
  701. // }
  702. writeArgument(msg->getArgumentDataType(i), *(msg->getArgument(i)));
  703. }
  704. parser->deleteInstance();
  705. parser = NULL;
  706. fflush(m_file); ///< @todo should this be in the final release?
  707. }
  708. void RecorderClass::writeArgument(GameMessageArgumentDataType type, const GameMessageArgumentType arg) {
  709. if (type == ARGUMENTDATATYPE_INTEGER) {
  710. fwrite(&(arg.integer), sizeof(arg.integer), 1, m_file);
  711. } else if (type == ARGUMENTDATATYPE_REAL) {
  712. fwrite(&(arg.real), sizeof(arg.real), 1, m_file);
  713. } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
  714. fwrite(&(arg.boolean), sizeof(arg.boolean), 1, m_file);
  715. } else if (type == ARGUMENTDATATYPE_OBJECTID) {
  716. fwrite(&(arg.objectID), sizeof(arg.objectID), 1, m_file);
  717. } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
  718. fwrite(&(arg.drawableID), sizeof(arg.drawableID), 1, m_file);
  719. } else if (type == ARGUMENTDATATYPE_TEAMID) {
  720. fwrite(&(arg.teamID), sizeof(arg.teamID), 1, m_file);
  721. } else if (type == ARGUMENTDATATYPE_LOCATION) {
  722. fwrite(&(arg.location), sizeof(arg.location), 1, m_file);
  723. } else if (type == ARGUMENTDATATYPE_PIXEL) {
  724. fwrite(&(arg.pixel), sizeof(arg.pixel), 1, m_file);
  725. } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
  726. fwrite(&(arg.pixelRegion), sizeof(arg.pixelRegion), 1, m_file);
  727. } else if (type == ARGUMENTDATATYPE_TIMESTAMP) {
  728. fwrite(&(arg.timestamp), sizeof(arg.timestamp), 1, m_file);
  729. } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
  730. fwrite(&(arg.wChar), sizeof(arg.wChar), 1, m_file);
  731. }
  732. }
  733. /**
  734. * Read in a replay header, for (1) populating a replay listbox or (2) starting playback. In
  735. * case (2), set FILE *m_file.
  736. */
  737. Bool RecorderClass::readReplayHeader(ReplayHeader& header)
  738. {
  739. AsciiString filepath = getReplayDir();
  740. filepath.concat(header.filename.str());
  741. m_file = fopen(filepath.str(), "rb");
  742. if (m_file == NULL)
  743. {
  744. DEBUG_LOG(("Can't open %s (%s)\n", filepath.str(), header.filename.str()));
  745. return FALSE;
  746. }
  747. // Read the GENREP header.
  748. char genrep[7];
  749. fread(&genrep, sizeof(char), 6, m_file);
  750. genrep[6] = 0;
  751. if (strncmp(genrep, "GENREP", 6)) {
  752. DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have GENREP at the start.\n"));
  753. fclose(m_file);
  754. m_file = NULL;
  755. return FALSE;
  756. }
  757. // read in some stats
  758. fread(&header.startTime, sizeof(time_t), 1, m_file);
  759. fread(&header.endTime, sizeof(time_t), 1, m_file);
  760. fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file);
  761. fread(&header.desyncGame, sizeof(Bool), 1, m_file);
  762. fread(&header.quitEarly, sizeof(Bool), 1, m_file);
  763. for (Int i=0; i<MAX_SLOTS; ++i)
  764. {
  765. fread(&(header.playerDiscons[i]), sizeof(Bool), 1, m_file);
  766. }
  767. // Read the Replay Name. We don't actually do anything with it. Oh well.
  768. header.replayName = readUnicodeString();
  769. // Read the date and time. We don't really do anything with this either. Oh well.
  770. fread(&header.timeVal, sizeof(SYSTEMTIME), 1, m_file);
  771. // Read in the Version info
  772. header.versionString = readUnicodeString();
  773. header.versionTimeString = readUnicodeString();
  774. fread(&header.versionNumber, sizeof(UnsignedInt), 1, m_file);
  775. fread(&header.exeCRC, sizeof(UnsignedInt), 1, m_file);
  776. fread(&header.iniCRC, sizeof(UnsignedInt), 1, m_file);
  777. // Read in the GameInfo
  778. header.gameOptions = readAsciiString();
  779. m_gameInfo.reset();
  780. m_gameInfo.enterGame();
  781. DEBUG_LOG(("RecorderClass::readReplayHeader - GameInfo = %s\n", header.gameOptions.str()));
  782. if (!ParseAsciiStringToGameInfo(&m_gameInfo, header.gameOptions))
  783. {
  784. DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have a valid GameInfo string.\n"));
  785. fclose(m_file);
  786. m_file = NULL;
  787. return FALSE;
  788. }
  789. m_gameInfo.startGame(0);
  790. AsciiString playerIndex = readAsciiString();
  791. header.localPlayerIndex = atoi(playerIndex.str());
  792. if (header.localPlayerIndex < -1 || header.localPlayerIndex >= MAX_SLOTS)
  793. {
  794. DEBUG_LOG(("RecorderClass::readReplayHeader - invalid local slot number.\n"));
  795. m_gameInfo.endGame();
  796. m_gameInfo.reset();
  797. fclose(m_file);
  798. m_file = NULL;
  799. return FALSE;
  800. }
  801. if (header.localPlayerIndex >= 0)
  802. {
  803. Int localIP = m_gameInfo.getSlot(header.localPlayerIndex)->getIP();
  804. m_gameInfo.setLocalIP(localIP);
  805. }
  806. if (!header.forPlayback)
  807. {
  808. m_gameInfo.endGame();
  809. m_gameInfo.reset();
  810. fclose(m_file);
  811. m_file = NULL;
  812. }
  813. return TRUE;
  814. }
  815. #if defined _DEBUG || defined _INTERNAL
  816. Bool RecorderClass::analyzeReplay( AsciiString filename )
  817. {
  818. m_doingAnalysis = TRUE;
  819. return playbackFile(filename);
  820. }
  821. Bool RecorderClass::isAnalysisInProgress( void )
  822. {
  823. return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1;
  824. }
  825. #endif
  826. AsciiString RecorderClass::getCurrentReplayFilename( void )
  827. {
  828. if (m_mode == RECORDERMODETYPE_PLAYBACK)
  829. {
  830. return m_currentReplayFilename;
  831. }
  832. return AsciiString::TheEmptyString;
  833. }
  834. class CRCInfo
  835. {
  836. public:
  837. CRCInfo();
  838. void addCRC(UnsignedInt val);
  839. UnsignedInt readCRC(void);
  840. void setLocalPlayer(UnsignedInt index) { m_localPlayer = index; }
  841. UnsignedInt getLocalPlayer(void) { return m_localPlayer; }
  842. void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; }
  843. Bool sawCRCMismatch(void) { return m_sawCRCMismatch; }
  844. protected:
  845. Bool m_sawCRCMismatch;
  846. Bool m_skippedOne;
  847. std::list<UnsignedInt> m_data;
  848. UnsignedInt m_localPlayer;
  849. };
  850. CRCInfo::CRCInfo()
  851. {
  852. m_localPlayer = ~0;
  853. m_skippedOne = FALSE;
  854. m_sawCRCMismatch = FALSE;
  855. }
  856. void CRCInfo::addCRC(UnsignedInt val)
  857. {
  858. //if (!m_skippedOne)
  859. //{
  860. // m_skippedOne = TRUE;
  861. // return;
  862. //}
  863. m_data.push_back(val);
  864. //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)\n", val, m_data.size(), !m_data.empty()));
  865. }
  866. UnsignedInt CRCInfo::readCRC(void)
  867. {
  868. if (m_data.empty())
  869. {
  870. //DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d\n", m_data.size()));
  871. return 0;
  872. }
  873. UnsignedInt val = m_data.front();
  874. m_data.pop_front();
  875. //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d\n", val, !m_data.empty(), m_data.size()));
  876. return val;
  877. }
  878. void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback)
  879. {
  880. if (fromPlayback)
  881. {
  882. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo\n", newCRC, playerIndex));
  883. m_crcInfo->addCRC(newCRC);
  884. return;
  885. }
  886. Int localPlayerIndex = m_crcInfo->getLocalPlayer();
  887. Bool samePlayer = FALSE;
  888. AsciiString playerName;
  889. playerName.format("player%d", localPlayerIndex);
  890. const Player *p = ThePlayerList->getNthPlayer(playerIndex);
  891. if (!p || (p->getPlayerNameKey() == NAMEKEY(playerName)))
  892. samePlayer = TRUE;
  893. if (samePlayer || (localPlayerIndex < 0))
  894. {
  895. UnsignedInt playbackCRC = m_crcInfo->readCRC();
  896. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of %8.8X/%8.8X from %d\n", newCRC, playbackCRC, playerIndex));
  897. if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch())
  898. {
  899. m_crcInfo->setSawCRCMismatch();
  900. // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning
  901. // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the
  902. // tail end of patch season, let's just disable the message, and hope the users believe the
  903. // problem is fixed. -MDC 3/20/2003
  904. //TheInGameUI->message("GUI:CRCMismatch");
  905. DEBUG_CRASH(("Replay has gone out of sync! All bets are off!\nOld:%8.8X New:%8.8X\nFrame:%d",
  906. playbackCRC, newCRC, TheGameLogic->getFrame()));
  907. }
  908. return;
  909. }
  910. //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Skipping CRC of %8.8X from %d (our index is %d)\n", newCRC, playerIndex, localPlayerIndex));
  911. }
  912. /**
  913. * Return true if this version of the file is the same as our version of the game
  914. */
  915. Bool RecorderClass::testVersionPlayback(AsciiString filename)
  916. {
  917. ReplayHeader header;
  918. header.forPlayback = TRUE;
  919. header.filename = filename;
  920. Bool success = readReplayHeader( header );
  921. if (!success)
  922. {
  923. return FALSE;
  924. }
  925. Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
  926. Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
  927. Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
  928. Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
  929. Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
  930. Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
  931. if(exeDifferent || iniDifferent)
  932. {
  933. return TRUE;
  934. }
  935. return FALSE;
  936. }
  937. /**
  938. * Start playback of the file. Return true or false depending on if the file is
  939. * a valid replay file or not.
  940. */
  941. Bool RecorderClass::playbackFile(AsciiString filename)
  942. {
  943. if (!m_doingAnalysis)
  944. {
  945. if (TheGameLogic->isInGame())
  946. {
  947. TheGameLogic->clearGameData();
  948. }
  949. }
  950. m_mode = RECORDERMODETYPE_PLAYBACK;
  951. ReplayHeader header;
  952. header.forPlayback = TRUE;
  953. header.filename = filename;
  954. Bool success = readReplayHeader( header );
  955. if (!success)
  956. {
  957. return FALSE;
  958. }
  959. #ifdef DEBUG_LOGGING
  960. Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
  961. Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
  962. Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
  963. Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
  964. Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
  965. Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
  966. AsciiString debugString;
  967. AsciiString tempStr;
  968. if (exeDifferent)
  969. {
  970. debugString = "EXE is different:\n";
  971. if (versionStringDiff)
  972. {
  973. tempStr.format(" Version [%ls] vs [%ls]\n", TheVersion->getUnicodeVersion().str(), header.versionString.str());
  974. debugString.concat(tempStr);
  975. }
  976. if (versionTimeStringDiff)
  977. {
  978. tempStr.format(" Build Time [%ls] vs [%ls]\n", TheVersion->getUnicodeBuildTime().str(), header.versionTimeString.str());
  979. debugString.concat(tempStr);
  980. }
  981. if (versionNumberDiff)
  982. {
  983. tempStr.format(" Version Number %8.8X vs %8.8X\n", TheVersion->getVersionNumber(), header.versionNumber);
  984. debugString.concat(tempStr);
  985. }
  986. if (exeCRCDiff)
  987. {
  988. tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_exeCRC, header.exeCRC);
  989. debugString.concat(tempStr);
  990. }
  991. }
  992. if (iniDifferent)
  993. {
  994. debugString.concat("INIs are different:\n");
  995. tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_iniCRC, header.iniCRC);
  996. debugString.concat(tempStr);
  997. }
  998. DEBUG_ASSERTCRASH(!exeDifferent && !iniDifferent, (debugString.str()));
  999. #endif
  1000. TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();
  1001. #ifdef DEBUG_LOGGING
  1002. if (header.localPlayerIndex >= 0)
  1003. {
  1004. DEBUG_LOG(("Local player is %ls (slot %d, IP %8.8X)\n",
  1005. m_gameInfo.getSlot(header.localPlayerIndex)->getName().str(), header.localPlayerIndex, m_gameInfo.getSlot(header.localPlayerIndex)->getIP()));
  1006. }
  1007. #endif
  1008. m_crcInfo = NEW CRCInfo;
  1009. m_crcInfo->setLocalPlayer(header.localPlayerIndex);
  1010. REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval();
  1011. DEBUG_LOG(("Player index is %d, replay CRC interval is %d\n", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL));
  1012. Int difficulty = 0;
  1013. fread(&difficulty, sizeof(difficulty), 1, m_file);
  1014. fread(&m_originalGameMode, sizeof(m_originalGameMode), 1, m_file);
  1015. Int rankPoints = 0;
  1016. fread(&rankPoints, sizeof(rankPoints), 1, m_file);
  1017. Int maxFPS = 0;
  1018. fread(&maxFPS, sizeof(maxFPS), 1, m_file);
  1019. DEBUG_LOG(("RecorderClass::playbackFile() - original game was mode %d\n", m_originalGameMode));
  1020. readNextFrame();
  1021. // send a message to the logic for a new game
  1022. if (!m_doingAnalysis)
  1023. {
  1024. GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
  1025. msg->appendIntegerArgument(GAME_REPLAY);
  1026. msg->appendIntegerArgument(difficulty);
  1027. msg->appendIntegerArgument(rankPoints);
  1028. if( maxFPS != 0 )
  1029. msg->appendIntegerArgument(maxFPS);
  1030. //InitGameLogicRandom( m_gameInfo.getSeed());
  1031. InitRandom( m_gameInfo.getSeed() );
  1032. }
  1033. m_currentReplayFilename = filename;
  1034. return TRUE;
  1035. }
  1036. /**
  1037. * Read a unicode string from the current file position. The string is assumed to be 0-terminated.
  1038. */
  1039. UnicodeString RecorderClass::readUnicodeString() {
  1040. UnsignedShort str[1024] = L"";
  1041. Int index = 0;
  1042. Int c = fgetwc(m_file);
  1043. if (c == EOF) {
  1044. str[index] = 0;
  1045. }
  1046. str[index] = c;
  1047. while (index < 1024 && str[index] != 0) {
  1048. ++index;
  1049. Int c = fgetwc(m_file);
  1050. if (c == EOF) {
  1051. str[index] = 0;
  1052. break;
  1053. }
  1054. str[index] = c;
  1055. }
  1056. str[1023] = L'\0';
  1057. UnicodeString retval(str);
  1058. return retval;
  1059. }
  1060. /**
  1061. * Read an ascii string from the current file position. The string is assumed to be 0-terminated.
  1062. */
  1063. AsciiString RecorderClass::readAsciiString() {
  1064. char str[1024] = "";
  1065. Int index = 0;
  1066. Int c = fgetc(m_file);
  1067. if (c == EOF) {
  1068. str[index] = 0;
  1069. }
  1070. str[index] = c;
  1071. while (index < 1024 && str[index] != 0) {
  1072. ++index;
  1073. Int c = fgetc(m_file);
  1074. if (c == EOF) {
  1075. str[index] = 0;
  1076. break;
  1077. }
  1078. str[index] = c;
  1079. }
  1080. str[1023] = '\0';
  1081. AsciiString retval(str);
  1082. return retval;
  1083. }
  1084. /**
  1085. * Read the frame number for the next command in the playback file. If the end of the file is reached, the playback
  1086. * is stopped and the next frame is said to be -1.
  1087. */
  1088. void RecorderClass::readNextFrame() {
  1089. Int retcode = fread(&m_nextFrame, sizeof(m_nextFrame), 1, m_file);
  1090. if (retcode != 1) {
  1091. DEBUG_LOG(("RecorderClass::readNextFrame - fread failed on frame %d\n", TheGameLogic->getFrame()));
  1092. m_nextFrame = -1;
  1093. stopPlayback();
  1094. }
  1095. }
  1096. /**
  1097. * This reads the next command from the replay file and appends it to TheCommandList.
  1098. */
  1099. void RecorderClass::appendNextCommand() {
  1100. GameMessage::Type type;
  1101. Int retcode = fread(&type, sizeof(type), 1, m_file);
  1102. if (retcode != 1) {
  1103. DEBUG_LOG(("RecorderClass::appendNextCommand - fread failed on frame %d\n", m_nextFrame/*TheGameLogic->getFrame()*/));
  1104. return;
  1105. }
  1106. GameMessage *msg = newInstance(GameMessage)(type);
  1107. if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type == GameMessage::MSG_CLEAR_GAME_DATA)
  1108. {
  1109. }
  1110. else
  1111. {
  1112. if (!m_doingAnalysis)
  1113. {
  1114. TheCommandList->appendMessage(msg);
  1115. }
  1116. }
  1117. #ifdef DEBUG_LOGGING
  1118. AsciiString commandName = msg->getCommandAsAsciiString();
  1119. if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
  1120. {
  1121. commandName.concat(" (Non-Network message!)");
  1122. }
  1123. else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  1124. {
  1125. commandName.concat(" (CRC message!)");
  1126. }
  1127. #endif // DEBUG_LOGGING
  1128. Int playerIndex = -1;
  1129. fread(&playerIndex, sizeof(playerIndex), 1, m_file);
  1130. msg->friend_setPlayerIndex(playerIndex);
  1131. // don't debug log this if we're debugging sync errors, as it will cause diff problems between a game and it's replay...
  1132. #ifdef DEBUG_LOGGING
  1133. Bool logCommand = true;
  1134. #ifdef DEBUG_CRC
  1135. if (!m_doingAnalysis)
  1136. logCommand = false;
  1137. #endif
  1138. if (logCommand)
  1139. {
  1140. DEBUG_LOG(("RecorderClass::appendNextCommand - Adding %s command from player %d to TheCommandList on frame %d\n",
  1141. commandName.str(), (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)?0:msg->getPlayerIndex(), m_nextFrame/*TheGameLogic->getFrame()*/));
  1142. }
  1143. #endif
  1144. UnsignedByte numTypes = 0;
  1145. Int totalArgs = 0;
  1146. fread(&numTypes, sizeof(numTypes), 1, m_file);
  1147. GameMessageParser *parser = newInstance(GameMessageParser)();
  1148. for (UnsignedByte i = 0; i < numTypes; ++i) {
  1149. UnsignedByte type = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
  1150. fread(&type, sizeof(type), 1, m_file);
  1151. UnsignedByte numArgs = 0;
  1152. fread(&numArgs, sizeof(numArgs), 1, m_file);
  1153. parser->addArgType((GameMessageArgumentDataType)type, numArgs);
  1154. totalArgs += numArgs;
  1155. }
  1156. GameMessageParserArgumentType *parserArgType = parser->getFirstArgumentType();
  1157. GameMessageArgumentDataType lasttype = ARGUMENTDATATYPE_UNKNOWN;
  1158. Int argsLeftForType = 0;
  1159. if (parserArgType != NULL) {
  1160. lasttype = parserArgType->getType();
  1161. argsLeftForType = parserArgType->getArgCount();
  1162. }
  1163. for (Int j = 0; j < totalArgs; ++j) {
  1164. readArgument(lasttype, msg);
  1165. --argsLeftForType;
  1166. if (argsLeftForType == 0) {
  1167. DEBUG_ASSERTCRASH(parserArgType != NULL, ("parserArgType was NULL when it shouldn't have been."));
  1168. if (parserArgType == NULL) {
  1169. return;
  1170. }
  1171. parserArgType = parserArgType->getNext();
  1172. // parserArgType is allowed to be NULL here, this is the case if there are no more arguments.
  1173. if (parserArgType != NULL) {
  1174. argsLeftForType = parserArgType->getArgCount();
  1175. lasttype = parserArgType->getType();
  1176. }
  1177. }
  1178. }
  1179. if (type == GameMessage::MSG_CLEAR_GAME_DATA || type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
  1180. {
  1181. msg->deleteInstance();
  1182. msg = NULL;
  1183. }
  1184. if (m_doingAnalysis)
  1185. {
  1186. msg->deleteInstance();
  1187. msg = NULL;
  1188. }
  1189. parser->deleteInstance();
  1190. parser = NULL;
  1191. }
  1192. void RecorderClass::readArgument(GameMessageArgumentDataType type, GameMessage *msg) {
  1193. if (type == ARGUMENTDATATYPE_INTEGER) {
  1194. Int theint;
  1195. fread(&theint, sizeof(theint), 1, m_file);
  1196. msg->appendIntegerArgument(theint);
  1197. #ifdef DEBUG_LOGGING
  1198. if (m_doingAnalysis)
  1199. {
  1200. DEBUG_LOG(("Integer argument: %d (%8.8X)\n", theint, theint));
  1201. }
  1202. #endif
  1203. } else if (type == ARGUMENTDATATYPE_REAL) {
  1204. Real thereal;
  1205. fread(&thereal, sizeof(thereal), 1, m_file);
  1206. msg->appendRealArgument(thereal);
  1207. #ifdef DEBUG_LOGGING
  1208. if (m_doingAnalysis)
  1209. {
  1210. DEBUG_LOG(("Real argument: %g (%8.8X)\n", thereal, *(int *)&thereal));
  1211. }
  1212. #endif
  1213. } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
  1214. Bool thebool;
  1215. fread(&thebool, sizeof(thebool), 1, m_file);
  1216. msg->appendBooleanArgument(thebool);
  1217. #ifdef DEBUG_LOGGING
  1218. if (m_doingAnalysis)
  1219. {
  1220. DEBUG_LOG(("Bool argument: %d\n", thebool));
  1221. }
  1222. #endif
  1223. } else if (type == ARGUMENTDATATYPE_OBJECTID) {
  1224. ObjectID theid;
  1225. fread(&theid, sizeof(theid), 1, m_file);
  1226. msg->appendObjectIDArgument(theid);
  1227. #ifdef DEBUG_LOGGING
  1228. if (m_doingAnalysis)
  1229. {
  1230. DEBUG_LOG(("Object ID argument: %d\n", theid));
  1231. }
  1232. #endif
  1233. } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
  1234. DrawableID theid;
  1235. fread(&theid, sizeof(theid), 1, m_file);
  1236. msg->appendDrawableIDArgument(theid);
  1237. #ifdef DEBUG_LOGGING
  1238. if (m_doingAnalysis)
  1239. {
  1240. DEBUG_LOG(("Drawable ID argument: %d\n", theid));
  1241. }
  1242. #endif
  1243. } else if (type == ARGUMENTDATATYPE_TEAMID) {
  1244. UnsignedInt theid;
  1245. fread(&theid, sizeof(theid), 1, m_file);
  1246. msg->appendTeamIDArgument(theid);
  1247. #ifdef DEBUG_LOGGING
  1248. if (m_doingAnalysis)
  1249. {
  1250. DEBUG_LOG(("Team ID argument: %d\n", theid));
  1251. }
  1252. #endif
  1253. } else if (type == ARGUMENTDATATYPE_LOCATION) {
  1254. Coord3D loc;
  1255. fread(&loc, sizeof(loc), 1, m_file);
  1256. msg->appendLocationArgument(loc);
  1257. #ifdef DEBUG_LOGGING
  1258. if (m_doingAnalysis)
  1259. {
  1260. DEBUG_LOG(("Coord3D argument: %g %g %g (%8.8X %8.8X %8.8X)\n", loc.x, loc.y, loc.z,
  1261. *(int *)&loc.x, *(int *)&loc.y, *(int *)&loc.z));
  1262. }
  1263. #endif
  1264. } else if (type == ARGUMENTDATATYPE_PIXEL) {
  1265. ICoord2D pixel;
  1266. fread(&pixel, sizeof(pixel), 1, m_file);
  1267. msg->appendPixelArgument(pixel);
  1268. #ifdef DEBUG_LOGGING
  1269. if (m_doingAnalysis)
  1270. {
  1271. DEBUG_LOG(("Pixel argument: %d,%d\n", pixel.x, pixel.y));
  1272. }
  1273. #endif
  1274. } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
  1275. IRegion2D reg;
  1276. fread(&reg, sizeof(reg), 1, m_file);
  1277. msg->appendPixelRegionArgument(reg);
  1278. #ifdef DEBUG_LOGGING
  1279. if (m_doingAnalysis)
  1280. {
  1281. DEBUG_LOG(("Pixel Region argument: %d,%d -> %d,%d\n", reg.lo.x, reg.lo.y, reg.hi.x, reg.hi.y));
  1282. }
  1283. #endif
  1284. } else if (type == ARGUMENTDATATYPE_TIMESTAMP) { // Not to be confused with Terrance Stamp... Kneel before Zod!!!
  1285. UnsignedInt stamp;
  1286. fread(&stamp, sizeof(stamp), 1, m_file);
  1287. msg->appendTimestampArgument(stamp);
  1288. #ifdef DEBUG_LOGGING
  1289. if (m_doingAnalysis)
  1290. {
  1291. DEBUG_LOG(("Timestamp argument: %d\n", stamp));
  1292. }
  1293. #endif
  1294. } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
  1295. WideChar theid;
  1296. fread(&theid, sizeof(theid), 1, m_file);
  1297. msg->appendWideCharArgument(theid);
  1298. #ifdef DEBUG_LOGGING
  1299. if (m_doingAnalysis)
  1300. {
  1301. DEBUG_LOG(("WideChar argument: %d (%lc)\n", theid, theid));
  1302. }
  1303. #endif
  1304. }
  1305. }
  1306. /**
  1307. * This needs to be called for every frame during playback. Basically it prevents the user from inserting.
  1308. */
  1309. void RecorderClass::cullBadCommands() {
  1310. if (m_doingAnalysis)
  1311. return;
  1312. GameMessage *msg = TheCommandList->getFirstMessage();
  1313. GameMessage *next = NULL;
  1314. while (msg != NULL) {
  1315. next = msg->next();
  1316. if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
  1317. (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES) &&
  1318. (msg->getType() != GameMessage::MSG_LOGIC_CRC)) {
  1319. msg->deleteInstance();
  1320. }
  1321. msg = next;
  1322. }
  1323. }
  1324. /**
  1325. * returns the directory that holds the replay files.
  1326. */
  1327. AsciiString RecorderClass::getReplayDir()
  1328. {
  1329. const char* replayDir = "Replays\\";
  1330. AsciiString tmp = TheGlobalData->getPath_UserData();
  1331. tmp.concat(replayDir);
  1332. return tmp;
  1333. }
  1334. /**
  1335. * returns the file extention for the replay files.
  1336. */
  1337. AsciiString RecorderClass::getReplayExtention() {
  1338. return AsciiString(replayExtention);
  1339. }
  1340. /**
  1341. * returns the file name used for the replay file that is recorded to.
  1342. */
  1343. AsciiString RecorderClass::getLastReplayFileName()
  1344. {
  1345. #if defined(_DEBUG) || defined(_INTERNAL)
  1346. if (TheNetwork && TheGlobalData->m_saveStats)
  1347. {
  1348. GameInfo *game = NULL;
  1349. if (TheLAN)
  1350. game = TheLAN->GetMyGame();
  1351. else if (TheGameSpyInfo)
  1352. game = TheGameSpyGame;
  1353. if (game)
  1354. {
  1355. AsciiString players;
  1356. AsciiString full;
  1357. AsciiString fullPlusNum;
  1358. AsciiString mapName = game->getMap();
  1359. const char *fname = mapName.reverseFind('\\');
  1360. if (fname)
  1361. mapName = fname+1;
  1362. for (Int i=0; i<MAX_SLOTS; ++i)
  1363. {
  1364. GameSlot *slot = game->getSlot(i);
  1365. if (slot && slot->isHuman())
  1366. {
  1367. AsciiString player;
  1368. player.format("%ls_", slot->getName().str());
  1369. players.concat(player);
  1370. }
  1371. }
  1372. full.format("%s%s_%d_%d", players.str(), mapName.str(), game->getSeed(), game->getLocalSlotNum());
  1373. AsciiString testString;
  1374. testString.format("%s%s%s", getReplayDir().str(), full.str(), replayExtention);
  1375. FILE *fp;
  1376. fp = fopen(testString.str(), "rb");
  1377. if (fp)
  1378. {
  1379. fclose(fp);
  1380. }
  1381. else
  1382. {
  1383. return full;
  1384. }
  1385. Int test = 1;
  1386. while (test < 20)
  1387. {
  1388. fullPlusNum.format("%s_%d", full.str(), test);
  1389. testString.format("%s%s%s", getReplayDir().str(), fullPlusNum.str(), replayExtention);
  1390. fp = fopen(testString.str(), "rb");
  1391. if (fp)
  1392. {
  1393. fclose(fp);
  1394. ++test;
  1395. }
  1396. else
  1397. {
  1398. return fullPlusNum;
  1399. }
  1400. }
  1401. return fullPlusNum;
  1402. }
  1403. }
  1404. #endif
  1405. return AsciiString(lastReplayFileName);
  1406. }
  1407. /**
  1408. * return the current operating mode of TheRecorder.
  1409. */
  1410. RecorderModeType RecorderClass::getMode() {
  1411. return m_mode;
  1412. }
  1413. ///< Show or Hide the Replay controls
  1414. void RecorderClass::initControls()
  1415. {
  1416. NameKeyType parentReplayControlID = TheNameKeyGenerator->nameToKey( AsciiString("ReplayControl.wnd:ParentReplayControl") );
  1417. GameWindow *parentReplayControl = TheWindowManager->winGetWindowFromId( NULL, parentReplayControlID );
  1418. Bool show = (getMode() != RECORDERMODETYPE_PLAYBACK);
  1419. if (parentReplayControl)
  1420. {
  1421. parentReplayControl->winHide(show); // show the replay control window.
  1422. }
  1423. }
  1424. ///< is this a multiplayer game (record OR playback)?
  1425. Bool RecorderClass::isMultiplayer( void )
  1426. {
  1427. if (m_mode == RECORDERMODETYPE_PLAYBACK)
  1428. {
  1429. GameSlot *slot;
  1430. for (int i=0; i<MAX_SLOTS; ++i)
  1431. {
  1432. slot = m_gameInfo.getSlot(i);
  1433. if (slot && slot->isOccupied()) ///< slots default to closed for non-networked games
  1434. return true;
  1435. }
  1436. }
  1437. if (TheGameLogic->getGameMode()==GAME_SINGLE_PLAYER) {
  1438. return false; // single player isn't multiplayer.
  1439. }
  1440. if (TheGameLogic->getGameMode()==GAME_SHELL) {
  1441. return false; // shell isn't multiplayer.
  1442. }
  1443. if (TheNetwork || TheSkirmishGameInfo)
  1444. return true;
  1445. return false;
  1446. }
  1447. /**
  1448. * Create a new recorder object.
  1449. */
  1450. RecorderClass * createRecorder() {
  1451. return NEW RecorderClass;
  1452. }