| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616 |
- /*
- ** Command & Conquer Generals(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/Recorder.h"
- #include "Common/FileSystem.h"
- #include "Common/playerlist.h"
- #include "Common/Player.h"
- #include "Common/GlobalData.h"
- #include "Common/GameEngine.h"
- #include "GameClient/GameWindow.h"
- #include "GameClient/GameWindowManager.h"
- #include "GameClient/InGameUI.h"
- #include "GameClient/Shell.h"
- #include "GameClient/GameText.h"
- #include "GameNetwork/LANAPICallbacks.h"
- #include "GameNetwork/GameMessageParser.h"
- #include "GameNetwork/GameSpy/PeerDefs.h"
- #include "GameNetwork/NetworkUtil.h"
- #include "GameLogic/GameLogic.h"
- #include "Common/RandomValue.h"
- #include "Common/CRCDebug.h"
- #include "Common/Version.h"
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- Int REPLAY_CRC_INTERVAL = 100;
- const char *replayExtention = ".rep";
- const char *lastReplayFileName = "00000000"; // a name the user is unlikely to ever type, but won't cause panic & confusion
- static time_t startTime;
- static const UnsignedInt startTimeOffset = 6;
- static const UnsignedInt endTimeOffset = startTimeOffset + sizeof(time_t);
- static const UnsignedInt framesOffset = endTimeOffset + sizeof(time_t);
- static const UnsignedInt desyncOffset = framesOffset + sizeof(UnsignedInt);
- static const UnsignedInt quitEarlyOffset = desyncOffset + sizeof(Bool);
- static const UnsignedInt disconOffset = quitEarlyOffset + sizeof(Bool);
- void RecorderClass::logGameStart(AsciiString options)
- {
- if (!m_file)
- return;
- time(&startTime);
- UnsignedInt fileSize = ftell(m_file);
- // move to appropriate offset
- if (!fseek(m_file, startTimeOffset, SEEK_SET))
- {
- // save off start time
- fwrite(&startTime, sizeof(time_t), 1, m_file);
- }
- // move back to end of stream
- #ifdef DEBUG_CRASHING
- Int res =
- #endif
- fseek(m_file, fileSize, SEEK_SET);
- DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheNetwork && TheGlobalData->m_saveStats)
- {
- //if (TheLAN)
- {
- unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
- char computerName[MAX_COMPUTERNAME_LENGTH + 1];
- if (!GetComputerName(computerName, &bufSize))
- {
- strcpy(computerName, "unknown");
- }
- AsciiString statsFile = TheGlobalData->m_baseStatsDir;
- TheFileSystem->createDirectory(statsFile);
- statsFile.concat(computerName);
- statsFile.concat(".txt");
- FILE *logFP = fopen(statsFile.str(), "a+");
- if (!logFP)
- {
- // try again locally
- TheWritableGlobalData->m_baseStatsDir = TheGlobalData->getPath_UserData();
- statsFile = TheGlobalData->m_baseStatsDir;
- statsFile.concat(computerName);
- statsFile.concat(".txt");
- logFP = fopen(statsFile.str(), "a+");
- }
- if (logFP)
- {
- struct tm *t2 = localtime(&startTime);
- fprintf(logFP, "\nGame start at %s\tOptions are %s\n", asctime(t2), options.str());
- fclose(logFP);
- }
- }
- }
- #endif
- }
- void RecorderClass::logPlayerDisconnect(UnicodeString player, Int slot)
- {
- if (!m_file)
- return;
- DEBUG_ASSERTCRASH((slot >= 0) && (slot < MAX_SLOTS), ("Attempting to disconnect an invalid slot number"));
- if ((slot < 0) || (slot >= (MAX_SLOTS)))
- {
- return;
- }
- UnsignedInt fileSize = ftell(m_file);
- // move to appropriate offset
- if (!fseek(m_file, disconOffset + slot*sizeof(Bool), SEEK_SET))
- {
- // save off discon status
- Bool b = TRUE;
- fwrite(&b, sizeof(Bool), 1, m_file);
- }
- // move back to end of stream
- #ifdef DEBUG_CRASHING
- Int res =
- #endif
- fseek(m_file, fileSize, SEEK_SET);
- DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheGlobalData->m_saveStats)
- {
- unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
- char computerName[MAX_COMPUTERNAME_LENGTH + 1];
- if (!GetComputerName(computerName, &bufSize))
- {
- strcpy(computerName, "unknown");
- }
- AsciiString statsFile = TheGlobalData->m_baseStatsDir;
- statsFile.concat(computerName);
- statsFile.concat(".txt");
- FILE *logFP = fopen(statsFile.str(), "a+");
- if (logFP)
- {
- time_t t;
- time(&t);
- struct tm *t2 = localtime(&t);
- fprintf(logFP, "\tPlayer %ls dropped at %s", player.str(), asctime(t2));
- fclose(logFP);
- }
- }
- #endif
- }
- void RecorderClass::logCRCMismatch( void )
- {
- if (!m_file)
- return;
- UnsignedInt fileSize = ftell(m_file);
- // move to appropriate offset
- if (!fseek(m_file, desyncOffset, SEEK_SET))
- {
- // save off desync status
- Bool b = TRUE;
- fwrite(&b, sizeof(Bool), 1, m_file);
- }
- // move back to end of stream
- #ifdef DEBUG_CRASHING
- Int res =
- #endif
- fseek(m_file, fileSize, SEEK_SET);
- DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheGlobalData->m_saveStats)
- {
- m_wasDesync = TRUE;
- unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
- char computerName[MAX_COMPUTERNAME_LENGTH + 1];
- if (!GetComputerName(computerName, &bufSize))
- {
- strcpy(computerName, "unknown");
- }
- AsciiString statsFile = TheGlobalData->m_baseStatsDir;
- statsFile.concat(computerName);
- statsFile.concat(".txt");
- FILE *logFP = fopen(statsFile.str(), "a+");
- if (logFP)
- {
- time_t t;
- time(&t);
- struct tm *t2 = localtime(&t);
- fprintf(logFP, "\tCRC mismatch at %s", asctime(t2));
- fclose(logFP);
- }
- }
- #endif
- }
- void RecorderClass::logGameEnd( void )
- {
- if (!m_file)
- return;
- time_t t;
- time(&t);
- UnsignedInt duration = TheGameLogic->getFrame();
- UnsignedInt fileSize = ftell(m_file);
- // move to appropriate offset
- if (!fseek(m_file, endTimeOffset, SEEK_SET))
- {
- // save off end time
- fwrite(&t, sizeof(time_t), 1, m_file);
- }
- // move to appropriate offset
- if (!fseek(m_file, framesOffset, SEEK_SET))
- {
- // save off duration
- fwrite(&duration, sizeof(UnsignedInt), 1, m_file);
- }
- // move back to end of stream
- #ifdef DEBUG_CRASHING
- Int res =
- #endif
- fseek(m_file, fileSize, SEEK_SET);
- DEBUG_ASSERTCRASH(res == 0, ("Could not seek to end of file!"));
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheNetwork && TheGlobalData->m_saveStats)
- {
- //if (TheLAN)
- {
- unsigned long bufSize = MAX_COMPUTERNAME_LENGTH + 1;
- char computerName[MAX_COMPUTERNAME_LENGTH + 1];
- if (!GetComputerName(computerName, &bufSize))
- {
- strcpy(computerName, "unknown");
- }
- AsciiString statsFile = TheGlobalData->m_baseStatsDir;
- statsFile.concat(computerName);
- statsFile.concat(".txt");
- FILE *logFP = fopen(statsFile.str(), "a+");
- if (logFP)
- {
- struct tm *t2 = localtime(&t);
- duration = t - startTime;
- Int minutes = duration/60;
- Int seconds = duration%60;
- fprintf(logFP, "Game end at %s(%d:%2.2d elapsed time)\n", asctime(t2), minutes, seconds);
- fclose(logFP);
- }
- }
- }
- #endif
- }
- #ifdef DEBUG_LOGGING
- #if defined(_INTERNAL)
- #define DEBUG_FILE_NAME "DebugLogFileI.txt"
- #define DEBUG_FILE_NAME_PREV "DebugLogFilePrevI.txt"
- #elif defined(_DEBUG)
- #define DEBUG_FILE_NAME "DebugLogFileD.txt"
- #define DEBUG_FILE_NAME_PREV "DebugLogFilePrevD.txt"
- #else
- #define DEBUG_FILE_NAME "DebugLogFile.txt"
- #define DEBUG_FILE_NAME_PREV "DebugLogFilePrev.txt"
- #endif
- #endif
- void RecorderClass::cleanUpReplayFile( void )
- {
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheGlobalData->m_saveStats)
- {
- char fname[_MAX_PATH+1];
- strncpy(fname, TheGlobalData->m_baseStatsDir.str(), _MAX_PATH);
- strncat(fname, m_fileName.str(), _MAX_PATH - strlen(fname));
- DEBUG_LOG(("Saving replay to %s\n", fname));
- AsciiString oldFname;
- oldFname.format("%s%s", getReplayDir().str(), m_fileName.str());
- CopyFile(oldFname.str(), fname, TRUE);
- #ifdef DEBUG_FILE_NAME
- AsciiString debugFname = fname;
- debugFname.removeLastChar();
- debugFname.removeLastChar();
- debugFname.removeLastChar();
- debugFname.concat("txt");
- UnsignedInt fileSize = 0;
- FILE *fp = fopen(DEBUG_FILE_NAME, "rb");
- if (fp)
- {
- fseek(fp, 0, SEEK_END);
- fileSize = ftell(fp);
- fclose(fp);
- fp = NULL;
- DEBUG_LOG(("Log file size was %d\n", fileSize));
- }
- const int MAX_DEBUG_SIZE = 65536;
- if (fileSize <= MAX_DEBUG_SIZE || TheGlobalData->m_saveAllStats)
- {
- DEBUG_LOG(("Using CopyFile to copy %s\n", DEBUG_FILE_NAME));
- CopyFile(DEBUG_FILE_NAME, debugFname.str(), TRUE);
- }
- else
- {
- DEBUG_LOG(("manual copy of %s\n", DEBUG_FILE_NAME));
- FILE *ifp = fopen(DEBUG_FILE_NAME, "rb");
- FILE *ofp = fopen(debugFname.str(), "wb");
- if (ifp && ofp)
- {
- fseek(ifp, fileSize-MAX_DEBUG_SIZE, SEEK_SET);
- char buf[4096];
- Int len;
- while ( (len=fread(buf, 1, 4096, ifp)) > 0 )
- {
- fwrite(buf, 1, len, ofp);
- }
- fclose(ofp);
- fclose(ifp);
- ifp = NULL;
- ofp = NULL;
- }
- else
- {
- if (ifp) fclose(ifp);
- if (ofp) fclose(ofp);
- ifp = NULL;
- ofp = NULL;
- }
- }
- #endif // DEBUG_FILE_NAME
- }
- #endif
- }
- /**
- * The recorder object.
- */
- RecorderClass *TheRecorder = NULL;
- /**
- * Constructor
- */
- RecorderClass::RecorderClass()
- {
- m_originalGameMode = GAME_NONE;
- m_mode = RECORDERMODETYPE_RECORD;
- m_file = NULL;
- m_fileName.clear();
- m_currentFilePosition = 0;
- //Added By Sadullah Nader
- //Initializtion(s) inserted
- m_doingAnalysis = FALSE;
- m_nextFrame = 0;
- m_wasDesync = FALSE;
- //
- init(); // just for the heck of it.
- }
- /**
- * Destructor
- */
- RecorderClass::~RecorderClass() {
- }
- /**
- * Initialization
- * The recorder will record by default since every game will be recorded.
- * Obviously a game that is being played back will not be recorded.
- * Since the playback is done through a special interface, that interface
- * will set the recorder mode to RECORDERMODETYPE_PLAYBACK.
- */
- void RecorderClass::init() {
- m_originalGameMode = GAME_NONE;
- m_mode = RECORDERMODETYPE_NONE;
- m_file = NULL;
- m_fileName.clear();
- m_currentFilePosition = 0;
- m_gameInfo.clearSlotList();
- m_gameInfo.reset();
- if (TheGlobalData->m_pendingFile.isEmpty())
- m_gameInfo.setMap(TheGlobalData->m_mapName);
- else
- m_gameInfo.setMap(TheGlobalData->m_pendingFile);
- m_gameInfo.setSeed(GetGameLogicRandomSeed());
- m_wasDesync = FALSE;
- m_doingAnalysis = FALSE;
- }
- /**
- * Reset the recorder to the "initialized state."
- */
- void RecorderClass::reset() {
- if (m_file != NULL) {
- fclose(m_file);
- m_file = NULL;
- }
- m_fileName.clear();
- init();
- }
- /**
- * update
- * Do the update for this frame.
- */
- void RecorderClass::update() {
- if (m_mode == RECORDERMODETYPE_RECORD || m_mode == RECORDERMODETYPE_NONE) {
- updateRecord();
- } else if (m_mode == RECORDERMODETYPE_PLAYBACK) {
- updatePlayback();
- }
- }
- /**
- * Do the update for the next frame of this playback.
- */
- void RecorderClass::updatePlayback() {
- cullBadCommands(); // Remove any bad commands that have been inserted by the local user that shouldn't be
- // executed during playback.
- if (m_nextFrame == -1) {
- // This is reached if there are no more commands to be executed.
- return;
- }
- UnsignedInt curFrame = TheGameLogic->getFrame();
- if (m_doingAnalysis)
- curFrame = m_nextFrame;
- // While there are commands to be queued up for this frame, do it.
- while (m_nextFrame == curFrame) {
- appendNextCommand(); // append the next command to TheCommandQueue
- readNextFrame(); // Read the next command's frame number for playback.
- }
- }
- /**
- * Stop the currently running playback. This is probably due either to the user exiting out of the playback or
- * reaching the end of the playback file.
- */
- void RecorderClass::stopPlayback() {
- if (m_file != NULL) {
- fclose(m_file);
- m_file = NULL;
- }
- m_fileName.clear();
- // Don't clear the game data if the replay is over - let things continue
- //#ifdef DEBUG_CRC
- if (!m_doingAnalysis)
- TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA);
- //#endif
- }
- /**
- * Update function for recording a game. Basically all the pertinant logic commands for this frame are written out
- * to a file.
- */
- void RecorderClass::updateRecord()
- {
- Bool needFlush = FALSE;
- static Int lastFrame = -1;
- GameMessage *msg = TheCommandList->getFirstMessage();
- while (msg != NULL) {
- if (msg->getType() == GameMessage::MSG_NEW_GAME &&
- msg->getArgument(0)->integer != GAME_SHELL &&
- msg->getArgument(0)->integer != GAME_NONE) {
- m_originalGameMode = msg->getArgument(0)->integer;
- DEBUG_LOG(("RecorderClass::updateRecord() - original game is mode %d\n", m_originalGameMode));
- lastFrame = 0;
- GameDifficulty diff = DIFFICULTY_NORMAL;
- if (msg->getArgumentCount() >= 2)
- diff = (GameDifficulty)msg->getArgument(1)->integer;
- Int rankPoints = 0;
- if (msg->getArgumentCount() >= 3)
- rankPoints = msg->getArgument(2)->integer;
- Int maxFPS = 0;
- if (msg->getArgumentCount() >= 4)
- maxFPS = msg->getArgument(3)->integer;
- startRecording(diff, m_originalGameMode, rankPoints, maxFPS);
- } else if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) {
- if (m_file != NULL) {
- lastFrame = -1;
- writeToFile(msg);
- stopRecording();
- }
- m_fileName.clear();
- } else {
- if (m_file != NULL) {
- if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
- (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES)) {
- // Only write the important messages to the file.
- writeToFile(msg);
- needFlush = TRUE;
- }
- }
- }
- msg = msg->next();
- }
- if (needFlush) {
- fflush(m_file);
- }
- }
- /**
- * Start a new file for recording. This will always overwrite the "LastReplay.rep" file with the new one.
- * So don't call this unless you really mean it.
- */
- void RecorderClass::startRecording(GameDifficulty diff, Int originalGameMode, Int rankPoints, Int maxFPS) {
- DEBUG_ASSERTCRASH(m_file == NULL, ("Starting to record game while game is in progress."));
- reset();
- m_mode = RECORDERMODETYPE_RECORD;
- AsciiString filepath = getReplayDir();
- // We have to make sure the replay dir exists.
- TheFileSystem->createDirectory(filepath);
- m_fileName = getLastReplayFileName();
- m_fileName.concat(getReplayExtention());
- filepath.concat(m_fileName);
- m_file = fopen(filepath.str(), "wb");
- if (m_file == NULL) {
- DEBUG_ASSERTCRASH(m_file != NULL, ("Failed to create replay file"));
- return;
- }
- fprintf(m_file, "GENREP");
- //
- // save space for stats to be filled in.
- //
- // **** if this changes, change the LAN Playtest code above ****
- //
- time_t t = 0;
- fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for start time
- fwrite(&t, sizeof(time_t), 1, m_file); // reserve space for end time
- UnsignedInt frames = 0;
- fwrite(&frames, sizeof(UnsignedInt), 1, m_file); // reserve space for duration in frames
- Bool b = FALSE;
- fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we desync)
- fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if we quit early)
- for (Int i=0; i<MAX_SLOTS; ++i)
- {
- fwrite(&b, sizeof(Bool), 1, m_file); // reserve space for flag (true if player i disconnects)
- }
- // Print out the name of the replay.
- UnicodeString replayName;
- replayName = TheGameText->fetch("GUI:LastReplay");
- fwprintf(m_file, L"%ws", replayName.str());
- fputwc(0, m_file);
- // Date and Time
- SYSTEMTIME systemTime;
- GetLocalTime( &systemTime );
- fwrite(&systemTime, sizeof(SYSTEMTIME), 1, m_file);
- // write out version info
- UnicodeString versionString = TheVersion->getUnicodeVersion();
- UnicodeString versionTimeString = TheVersion->getUnicodeBuildTime();
- UnsignedInt versionNumber = TheVersion->getVersionNumber();
- fwprintf(m_file, L"%ws", versionString.str());
- fputwc(0, m_file);
- fwprintf(m_file, L"%ws", versionTimeString.str());
- fputwc(0, m_file);
- fwrite(&versionNumber, sizeof(UnsignedInt), 1, m_file);
- fwrite(&(TheGlobalData->m_exeCRC), sizeof(UnsignedInt), 1, m_file);
- fwrite(&(TheGlobalData->m_iniCRC), sizeof(UnsignedInt), 1, m_file);
- // Number of players
- /*
- Int numPlayers = ThePlayerList->getPlayerCount();
- fwrite(&numPlayers, sizeof(numPlayers), 1, m_file);
- */
- // Write the slot list.
- AsciiString theSlotList;
- Int localIndex = -1;
- if (TheNetwork)
- {
- if (TheLAN)
- {
- GameInfo *game = TheLAN->GetMyGame();
- DEBUG_ASSERTCRASH(game, ("Starting a LAN game with no LANGameInfo object!"));
- theSlotList = GameInfoToAsciiString(game);
- for (Int i=0; i<MAX_SLOTS; ++i)
- {
- if (game->getLocalIP() == game->getSlot(i)->getIP())
- {
- localIndex = i;
- break;
- }
- }
- }
- else
- {
- theSlotList = GameInfoToAsciiString(TheGameSpyGame);
- localIndex = TheGameSpyGame->getLocalSlotNum();
- }
- }
- else
- {
- if(TheSkirmishGameInfo)
- {
- TheSkirmishGameInfo->setCRCInterval(REPLAY_CRC_INTERVAL);
- theSlotList = GameInfoToAsciiString(TheSkirmishGameInfo);
- DEBUG_LOG(("GameInfo String: %s\n",theSlotList.str()));
- localIndex = 0;
- }
- else
- {
- // single player. format the generic (empty) slotlist
- m_gameInfo.setCRCInterval(REPLAY_CRC_INTERVAL);
- theSlotList = GameInfoToAsciiString(&m_gameInfo);
- }
- }
- logGameStart(theSlotList);
- DEBUG_LOG(("RecorderClass::startRecording - theSlotList = %s\n", theSlotList.str()));
- // write slot list (starting spots, color, alliances, etc
- fwrite(theSlotList.str(), theSlotList.getLength() + 1, 1, m_file);
- fprintf(m_file, "%d", localIndex);
- fputc(0, m_file);
- /*
- /// @todo fix this to use starting spots and player alliances when those are put in the game.
- for (Int i = 0; i < numPlayers; ++i) {
- Player *player = ThePlayerList->getNthPlayer(i);
- if (player == NULL) {
- continue;
- }
- UnicodeString name = player->getPlayerDisplayName();
- fwprintf(m_file, L"%s", name.str());
- fputwc(0, m_file);
- UnicodeString faction = player->getFaction()->getFactionDisplayName();
- fwprintf(m_file, L"%s", faction.str());
- fputwc(0, m_file);
- Int color = player->getColor()->getAsInt();
- fwrite(&color, sizeof(color), 1, m_file);
- Int team = 0;
- Int startingSpot = 0;
- fwrite(&startingSpot, sizeof(Int), 1, m_file);
- fwrite(&team, sizeof(Int), 1, m_file);
- }
- */
- // Write the game difficulty.
- fwrite(&diff, sizeof(Int), 1, m_file);
- // Write original game mode
- fwrite(&originalGameMode, sizeof(originalGameMode), 1, m_file);
- // Write rank points to add at game start
- fwrite(&rankPoints, sizeof(rankPoints), 1, m_file);
- // Write maxFPS chosen
- fwrite(&maxFPS, sizeof(maxFPS), 1, m_file);
- DEBUG_LOG(("RecorderClass::startRecording() - diff=%d, mode=%d, FPS=%d\n", diff, originalGameMode, maxFPS));
- /*
- // Write the map name.
- fprintf(m_file, "%s", (TheGlobalData->m_mapName).str());
- fputc(0, m_file);
- */
- /// @todo Need to write game options when there are some to be written.
- }
- /**
- * This will stop the current recording session and close the file. This should always be called at the end of
- * every game.
- */
- void RecorderClass::stopRecording() {
- logGameEnd();
- if (TheNetwork)
- {
- //if (TheLAN)
- {
- if (m_wasDesync)
- cleanUpReplayFile();
- m_wasDesync = FALSE;
- }
- }
- if (m_file != NULL) {
- fclose(m_file);
- m_file = NULL;
- }
- m_fileName.clear();
- }
- /**
- * Write this game message to the record file. This also writes the game message's execution frame.
- */
- void RecorderClass::writeToFile(GameMessage * msg) {
- // Write the frame number for this command.
- UnsignedInt frame = TheGameLogic->getFrame();
- fwrite(&frame, sizeof(frame), 1, m_file);
- // Write the command type
- GameMessage::Type type = msg->getType();
- fwrite(&type, sizeof(type), 1, m_file);
- // Write the player index
- Int playerIndex = msg->getPlayerIndex();
- fwrite(&playerIndex, sizeof(playerIndex), 1, m_file);
- #ifdef DEBUG_LOGGING
- AsciiString commandName = msg->getCommandAsAsciiString();
- if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
- {
- commandName.concat(" (Non-Network message!)");
- }
- else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
- {
- AsciiString tmp;
- tmp.format(" (CRC 0x%8.8X)", msg->getArgument(0)->integer);
- commandName.concat(tmp);
- }
- //DEBUG_LOG(("RecorderClass::writeToFile - Adding %s command from player %d to TheCommandList on frame %d\n",
- //commandName.str(), msg->getPlayerIndex(), TheGameLogic->getFrame()));
- #endif // DEBUG_LOGGING
- GameMessageParser *parser = newInstance(GameMessageParser)(msg);
- UnsignedByte numTypes = parser->getNumTypes();
- fwrite(&numTypes, sizeof(numTypes), 1, m_file);
- GameMessageParserArgumentType *argType = parser->getFirstArgumentType();
- while (argType != NULL) {
- UnsignedByte type = (UnsignedByte)(argType->getType());
- fwrite(&type, sizeof(type), 1, m_file);
- UnsignedByte argTypeCount = (UnsignedByte)(argType->getArgCount());
- fwrite(&argTypeCount, sizeof(argTypeCount), 1, m_file);
- argType = argType->getNext();
- }
- // UnsignedByte lasttype = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
- Int numArgs = msg->getArgumentCount();
- for (Int i = 0; i < numArgs; ++i) {
- // UnsignedByte type = (UnsignedByte)(msg->getArgumentDataType(i));
- // if (lasttype != type) {
- // fwrite(&type, sizeof(type), 1, m_file);
- // lasttype = type;
- // }
- writeArgument(msg->getArgumentDataType(i), *(msg->getArgument(i)));
- }
- parser->deleteInstance();
- parser = NULL;
- fflush(m_file); ///< @todo should this be in the final release?
- }
- void RecorderClass::writeArgument(GameMessageArgumentDataType type, const GameMessageArgumentType arg) {
- if (type == ARGUMENTDATATYPE_INTEGER) {
- fwrite(&(arg.integer), sizeof(arg.integer), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_REAL) {
- fwrite(&(arg.real), sizeof(arg.real), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
- fwrite(&(arg.boolean), sizeof(arg.boolean), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_OBJECTID) {
- fwrite(&(arg.objectID), sizeof(arg.objectID), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
- fwrite(&(arg.drawableID), sizeof(arg.drawableID), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_TEAMID) {
- fwrite(&(arg.teamID), sizeof(arg.teamID), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_LOCATION) {
- fwrite(&(arg.location), sizeof(arg.location), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_PIXEL) {
- fwrite(&(arg.pixel), sizeof(arg.pixel), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
- fwrite(&(arg.pixelRegion), sizeof(arg.pixelRegion), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_TIMESTAMP) {
- fwrite(&(arg.timestamp), sizeof(arg.timestamp), 1, m_file);
- } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
- fwrite(&(arg.wChar), sizeof(arg.wChar), 1, m_file);
- }
- }
- /**
- * Read in a replay header, for (1) populating a replay listbox or (2) starting playback. In
- * case (2), set FILE *m_file.
- */
- Bool RecorderClass::readReplayHeader(ReplayHeader& header)
- {
- AsciiString filepath = getReplayDir();
- filepath.concat(header.filename.str());
- m_file = fopen(filepath.str(), "rb");
- if (m_file == NULL)
- {
- DEBUG_LOG(("Can't open %s (%s)\n", filepath.str(), header.filename.str()));
- return FALSE;
- }
- // Read the GENREP header.
- char genrep[7];
- fread(&genrep, sizeof(char), 6, m_file);
- genrep[6] = 0;
- if (strncmp(genrep, "GENREP", 6)) {
- DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have GENREP at the start.\n"));
- fclose(m_file);
- m_file = NULL;
- return FALSE;
- }
- // read in some stats
- fread(&header.startTime, sizeof(time_t), 1, m_file);
- fread(&header.endTime, sizeof(time_t), 1, m_file);
- fread(&header.frameDuration, sizeof(UnsignedInt), 1, m_file);
- fread(&header.desyncGame, sizeof(Bool), 1, m_file);
- fread(&header.quitEarly, sizeof(Bool), 1, m_file);
- for (Int i=0; i<MAX_SLOTS; ++i)
- {
- fread(&(header.playerDiscons[i]), sizeof(Bool), 1, m_file);
- }
- // Read the Replay Name. We don't actually do anything with it. Oh well.
- header.replayName = readUnicodeString();
- // Read the date and time. We don't really do anything with this either. Oh well.
- fread(&header.timeVal, sizeof(SYSTEMTIME), 1, m_file);
- // Read in the Version info
- header.versionString = readUnicodeString();
- header.versionTimeString = readUnicodeString();
- fread(&header.versionNumber, sizeof(UnsignedInt), 1, m_file);
- fread(&header.exeCRC, sizeof(UnsignedInt), 1, m_file);
- fread(&header.iniCRC, sizeof(UnsignedInt), 1, m_file);
- // Read in the GameInfo
- header.gameOptions = readAsciiString();
- m_gameInfo.reset();
- m_gameInfo.enterGame();
- DEBUG_LOG(("RecorderClass::readReplayHeader - GameInfo = %s\n", header.gameOptions.str()));
- if (!ParseAsciiStringToGameInfo(&m_gameInfo, header.gameOptions))
- {
- DEBUG_LOG(("RecorderClass::readReplayHeader - replay file did not have a valid GameInfo string.\n"));
- fclose(m_file);
- m_file = NULL;
- return FALSE;
- }
- m_gameInfo.startGame(0);
- AsciiString playerIndex = readAsciiString();
- header.localPlayerIndex = atoi(playerIndex.str());
- if (header.localPlayerIndex < -1 || header.localPlayerIndex >= MAX_SLOTS)
- {
- DEBUG_LOG(("RecorderClass::readReplayHeader - invalid local slot number.\n"));
- m_gameInfo.endGame();
- m_gameInfo.reset();
- fclose(m_file);
- m_file = NULL;
- return FALSE;
- }
- if (header.localPlayerIndex >= 0)
- {
- Int localIP = m_gameInfo.getSlot(header.localPlayerIndex)->getIP();
- m_gameInfo.setLocalIP(localIP);
- }
- if (!header.forPlayback)
- {
- m_gameInfo.endGame();
- m_gameInfo.reset();
- fclose(m_file);
- m_file = NULL;
- }
- return TRUE;
- }
- #if defined _DEBUG || defined _INTERNAL
- Bool RecorderClass::analyzeReplay( AsciiString filename )
- {
- m_doingAnalysis = TRUE;
- return playbackFile(filename);
- }
- Bool RecorderClass::isAnalysisInProgress( void )
- {
- return m_mode == RECORDERMODETYPE_PLAYBACK && m_nextFrame != -1;
- }
- #endif
- AsciiString RecorderClass::getCurrentReplayFilename( void )
- {
- if (m_mode == RECORDERMODETYPE_PLAYBACK)
- {
- return m_currentReplayFilename;
- }
- return AsciiString::TheEmptyString;
- }
- class CRCInfo
- {
- public:
- CRCInfo();
- void addCRC(UnsignedInt val);
- UnsignedInt readCRC(void);
- void setLocalPlayer(UnsignedInt index) { m_localPlayer = index; }
- UnsignedInt getLocalPlayer(void) { return m_localPlayer; }
- void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; }
- Bool sawCRCMismatch(void) { return m_sawCRCMismatch; }
- protected:
- Bool m_sawCRCMismatch;
- Bool m_skippedOne;
- std::list<UnsignedInt> m_data;
- UnsignedInt m_localPlayer;
- };
- CRCInfo::CRCInfo()
- {
- m_localPlayer = ~0;
- m_skippedOne = FALSE;
- m_sawCRCMismatch = FALSE;
- }
- void CRCInfo::addCRC(UnsignedInt val)
- {
- //if (!m_skippedOne)
- //{
- // m_skippedOne = TRUE;
- // return;
- //}
- m_data.push_back(val);
- //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)\n", val, m_data.size(), !m_data.empty()));
- }
- UnsignedInt CRCInfo::readCRC(void)
- {
- if (m_data.empty())
- {
- //DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d\n", m_data.size()));
- return 0;
- }
- UnsignedInt val = m_data.front();
- m_data.pop_front();
- //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d\n", val, !m_data.empty(), m_data.size()));
- return val;
- }
- void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback)
- {
- if (fromPlayback)
- {
- //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo\n", newCRC, playerIndex));
- m_crcInfo->addCRC(newCRC);
- return;
- }
- Int localPlayerIndex = m_crcInfo->getLocalPlayer();
- Bool samePlayer = FALSE;
- AsciiString playerName;
- playerName.format("player%d", localPlayerIndex);
- const Player *p = ThePlayerList->getNthPlayer(playerIndex);
- if (!p || (p->getPlayerNameKey() == NAMEKEY(playerName)))
- samePlayer = TRUE;
- if (samePlayer || (localPlayerIndex < 0))
- {
- UnsignedInt playbackCRC = m_crcInfo->readCRC();
- //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of %8.8X/%8.8X from %d\n", newCRC, playbackCRC, playerIndex));
- if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch())
- {
- m_crcInfo->setSawCRCMismatch();
- // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning
- // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the
- // tail end of patch season, let's just disable the message, and hope the users believe the
- // problem is fixed. -MDC 3/20/2003
- //TheInGameUI->message("GUI:CRCMismatch");
- DEBUG_CRASH(("Replay has gone out of sync! All bets are off!\nOld:%8.8X New:%8.8X\nFrame:%d",
- playbackCRC, newCRC, TheGameLogic->getFrame()));
- }
- return;
- }
- //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Skipping CRC of %8.8X from %d (our index is %d)\n", newCRC, playerIndex, localPlayerIndex));
- }
- /**
- * Return true if this version of the file is the same as our version of the game
- */
- Bool RecorderClass::testVersionPlayback(AsciiString filename)
- {
- ReplayHeader header;
- header.forPlayback = TRUE;
- header.filename = filename;
- Bool success = readReplayHeader( header );
- if (!success)
- {
- return FALSE;
- }
- Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
- Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
- Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
- Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
- Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
- Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
- if(exeDifferent || iniDifferent)
- {
- return TRUE;
- }
- return FALSE;
- }
- /**
- * Start playback of the file. Return true or false depending on if the file is
- * a valid replay file or not.
- */
- Bool RecorderClass::playbackFile(AsciiString filename)
- {
- if (!m_doingAnalysis)
- {
- if (TheGameLogic->isInGame())
- {
- TheGameLogic->clearGameData();
- }
- }
- m_mode = RECORDERMODETYPE_PLAYBACK;
- ReplayHeader header;
- header.forPlayback = TRUE;
- header.filename = filename;
- Bool success = readReplayHeader( header );
- if (!success)
- {
- return FALSE;
- }
- #ifdef DEBUG_LOGGING
- Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion();
- Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();
- Bool versionNumberDiff = header.versionNumber != TheVersion->getVersionNumber();
- Bool exeCRCDiff = header.exeCRC != TheGlobalData->m_exeCRC;
- Bool exeDifferent = versionStringDiff || versionTimeStringDiff || versionNumberDiff || exeCRCDiff;
- Bool iniDifferent = header.iniCRC != TheGlobalData->m_iniCRC;
- AsciiString debugString;
- AsciiString tempStr;
- if (exeDifferent)
- {
- debugString = "EXE is different:\n";
- if (versionStringDiff)
- {
- tempStr.format(" Version [%ls] vs [%ls]\n", TheVersion->getUnicodeVersion().str(), header.versionString.str());
- debugString.concat(tempStr);
- }
- if (versionTimeStringDiff)
- {
- tempStr.format(" Build Time [%ls] vs [%ls]\n", TheVersion->getUnicodeBuildTime().str(), header.versionTimeString.str());
- debugString.concat(tempStr);
- }
- if (versionNumberDiff)
- {
- tempStr.format(" Version Number %8.8X vs %8.8X\n", TheVersion->getVersionNumber(), header.versionNumber);
- debugString.concat(tempStr);
- }
- if (exeCRCDiff)
- {
- tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_exeCRC, header.exeCRC);
- debugString.concat(tempStr);
- }
- }
- if (iniDifferent)
- {
- debugString.concat("INIs are different:\n");
- tempStr.format(" CRC %8.8X vs %8.8X\n", TheGlobalData->m_iniCRC, header.iniCRC);
- debugString.concat(tempStr);
- }
- DEBUG_ASSERTCRASH(!exeDifferent && !iniDifferent, (debugString.str()));
- #endif
- TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();
- #ifdef DEBUG_LOGGING
- if (header.localPlayerIndex >= 0)
- {
- DEBUG_LOG(("Local player is %ls (slot %d, IP %8.8X)\n",
- m_gameInfo.getSlot(header.localPlayerIndex)->getName().str(), header.localPlayerIndex, m_gameInfo.getSlot(header.localPlayerIndex)->getIP()));
- }
- #endif
- m_crcInfo = NEW CRCInfo;
- m_crcInfo->setLocalPlayer(header.localPlayerIndex);
- REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval();
- DEBUG_LOG(("Player index is %d, replay CRC interval is %d\n", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL));
- Int difficulty = 0;
- fread(&difficulty, sizeof(difficulty), 1, m_file);
- fread(&m_originalGameMode, sizeof(m_originalGameMode), 1, m_file);
- Int rankPoints = 0;
- fread(&rankPoints, sizeof(rankPoints), 1, m_file);
-
- Int maxFPS = 0;
- fread(&maxFPS, sizeof(maxFPS), 1, m_file);
- DEBUG_LOG(("RecorderClass::playbackFile() - original game was mode %d\n", m_originalGameMode));
- readNextFrame();
- // send a message to the logic for a new game
- if (!m_doingAnalysis)
- {
- GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_NEW_GAME );
- msg->appendIntegerArgument(GAME_REPLAY);
- msg->appendIntegerArgument(difficulty);
- msg->appendIntegerArgument(rankPoints);
- if( maxFPS != 0 )
- msg->appendIntegerArgument(maxFPS);
- //InitGameLogicRandom( m_gameInfo.getSeed());
- InitRandom( m_gameInfo.getSeed() );
- }
- m_currentReplayFilename = filename;
- return TRUE;
- }
- /**
- * Read a unicode string from the current file position. The string is assumed to be 0-terminated.
- */
- UnicodeString RecorderClass::readUnicodeString() {
- UnsignedShort str[1024] = L"";
- Int index = 0;
- Int c = fgetwc(m_file);
- if (c == EOF) {
- str[index] = 0;
- }
- str[index] = c;
- while (index < 1024 && str[index] != 0) {
- ++index;
- Int c = fgetwc(m_file);
- if (c == EOF) {
- str[index] = 0;
- break;
- }
- str[index] = c;
- }
- str[1023] = L'\0';
- UnicodeString retval(str);
- return retval;
- }
- /**
- * Read an ascii string from the current file position. The string is assumed to be 0-terminated.
- */
- AsciiString RecorderClass::readAsciiString() {
- char str[1024] = "";
- Int index = 0;
- Int c = fgetc(m_file);
- if (c == EOF) {
- str[index] = 0;
- }
- str[index] = c;
- while (index < 1024 && str[index] != 0) {
- ++index;
- Int c = fgetc(m_file);
- if (c == EOF) {
- str[index] = 0;
- break;
- }
- str[index] = c;
- }
- str[1023] = '\0';
- AsciiString retval(str);
- return retval;
- }
- /**
- * Read the frame number for the next command in the playback file. If the end of the file is reached, the playback
- * is stopped and the next frame is said to be -1.
- */
- void RecorderClass::readNextFrame() {
- Int retcode = fread(&m_nextFrame, sizeof(m_nextFrame), 1, m_file);
- if (retcode != 1) {
- DEBUG_LOG(("RecorderClass::readNextFrame - fread failed on frame %d\n", TheGameLogic->getFrame()));
- m_nextFrame = -1;
- stopPlayback();
- }
- }
- /**
- * This reads the next command from the replay file and appends it to TheCommandList.
- */
- void RecorderClass::appendNextCommand() {
- GameMessage::Type type;
- Int retcode = fread(&type, sizeof(type), 1, m_file);
- if (retcode != 1) {
- DEBUG_LOG(("RecorderClass::appendNextCommand - fread failed on frame %d\n", m_nextFrame/*TheGameLogic->getFrame()*/));
- return;
- }
- GameMessage *msg = newInstance(GameMessage)(type);
- if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type == GameMessage::MSG_CLEAR_GAME_DATA)
- {
- }
- else
- {
- if (!m_doingAnalysis)
- {
- TheCommandList->appendMessage(msg);
- }
- }
- #ifdef DEBUG_LOGGING
- AsciiString commandName = msg->getCommandAsAsciiString();
- if (type < GameMessage::MSG_BEGIN_NETWORK_MESSAGES || type > GameMessage::MSG_END_NETWORK_MESSAGES)
- {
- commandName.concat(" (Non-Network message!)");
- }
- else if (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
- {
- commandName.concat(" (CRC message!)");
- }
- #endif // DEBUG_LOGGING
- Int playerIndex = -1;
- fread(&playerIndex, sizeof(playerIndex), 1, m_file);
- msg->friend_setPlayerIndex(playerIndex);
- // don't debug log this if we're debugging sync errors, as it will cause diff problems between a game and it's replay...
- #ifdef DEBUG_LOGGING
- Bool logCommand = true;
- #ifdef DEBUG_CRC
- if (!m_doingAnalysis)
- logCommand = false;
- #endif
- if (logCommand)
- {
- DEBUG_LOG(("RecorderClass::appendNextCommand - Adding %s command from player %d to TheCommandList on frame %d\n",
- commandName.str(), (type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)?0:msg->getPlayerIndex(), m_nextFrame/*TheGameLogic->getFrame()*/));
- }
- #endif
- UnsignedByte numTypes = 0;
- Int totalArgs = 0;
- fread(&numTypes, sizeof(numTypes), 1, m_file);
- GameMessageParser *parser = newInstance(GameMessageParser)();
- for (UnsignedByte i = 0; i < numTypes; ++i) {
- UnsignedByte type = (UnsignedByte)ARGUMENTDATATYPE_UNKNOWN;
- fread(&type, sizeof(type), 1, m_file);
- UnsignedByte numArgs = 0;
- fread(&numArgs, sizeof(numArgs), 1, m_file);
- parser->addArgType((GameMessageArgumentDataType)type, numArgs);
- totalArgs += numArgs;
- }
- GameMessageParserArgumentType *parserArgType = parser->getFirstArgumentType();
- GameMessageArgumentDataType lasttype = ARGUMENTDATATYPE_UNKNOWN;
- Int argsLeftForType = 0;
- if (parserArgType != NULL) {
- lasttype = parserArgType->getType();
- argsLeftForType = parserArgType->getArgCount();
- }
- for (Int j = 0; j < totalArgs; ++j) {
- readArgument(lasttype, msg);
- --argsLeftForType;
- if (argsLeftForType == 0) {
- DEBUG_ASSERTCRASH(parserArgType != NULL, ("parserArgType was NULL when it shouldn't have been."));
- if (parserArgType == NULL) {
- return;
- }
- parserArgType = parserArgType->getNext();
- // parserArgType is allowed to be NULL here, this is the case if there are no more arguments.
- if (parserArgType != NULL) {
- argsLeftForType = parserArgType->getArgCount();
- lasttype = parserArgType->getType();
- }
- }
- }
- if (type == GameMessage::MSG_CLEAR_GAME_DATA || type == GameMessage::MSG_BEGIN_NETWORK_MESSAGES)
- {
- msg->deleteInstance();
- msg = NULL;
- }
- if (m_doingAnalysis)
- {
- msg->deleteInstance();
- msg = NULL;
- }
- parser->deleteInstance();
- parser = NULL;
- }
- void RecorderClass::readArgument(GameMessageArgumentDataType type, GameMessage *msg) {
- if (type == ARGUMENTDATATYPE_INTEGER) {
- Int theint;
- fread(&theint, sizeof(theint), 1, m_file);
- msg->appendIntegerArgument(theint);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Integer argument: %d (%8.8X)\n", theint, theint));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_REAL) {
- Real thereal;
- fread(&thereal, sizeof(thereal), 1, m_file);
- msg->appendRealArgument(thereal);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Real argument: %g (%8.8X)\n", thereal, *(int *)&thereal));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_BOOLEAN) {
- Bool thebool;
- fread(&thebool, sizeof(thebool), 1, m_file);
- msg->appendBooleanArgument(thebool);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Bool argument: %d\n", thebool));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_OBJECTID) {
- ObjectID theid;
- fread(&theid, sizeof(theid), 1, m_file);
- msg->appendObjectIDArgument(theid);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Object ID argument: %d\n", theid));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_DRAWABLEID) {
- DrawableID theid;
- fread(&theid, sizeof(theid), 1, m_file);
- msg->appendDrawableIDArgument(theid);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Drawable ID argument: %d\n", theid));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_TEAMID) {
- UnsignedInt theid;
- fread(&theid, sizeof(theid), 1, m_file);
- msg->appendTeamIDArgument(theid);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Team ID argument: %d\n", theid));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_LOCATION) {
- Coord3D loc;
- fread(&loc, sizeof(loc), 1, m_file);
- msg->appendLocationArgument(loc);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Coord3D argument: %g %g %g (%8.8X %8.8X %8.8X)\n", loc.x, loc.y, loc.z,
- *(int *)&loc.x, *(int *)&loc.y, *(int *)&loc.z));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_PIXEL) {
- ICoord2D pixel;
- fread(&pixel, sizeof(pixel), 1, m_file);
- msg->appendPixelArgument(pixel);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Pixel argument: %d,%d\n", pixel.x, pixel.y));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_PIXELREGION) {
- IRegion2D reg;
- fread(®, sizeof(reg), 1, m_file);
- msg->appendPixelRegionArgument(reg);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Pixel Region argument: %d,%d -> %d,%d\n", reg.lo.x, reg.lo.y, reg.hi.x, reg.hi.y));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_TIMESTAMP) { // Not to be confused with Terrance Stamp... Kneel before Zod!!!
- UnsignedInt stamp;
- fread(&stamp, sizeof(stamp), 1, m_file);
- msg->appendTimestampArgument(stamp);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("Timestamp argument: %d\n", stamp));
- }
- #endif
- } else if (type == ARGUMENTDATATYPE_WIDECHAR) {
- WideChar theid;
- fread(&theid, sizeof(theid), 1, m_file);
- msg->appendWideCharArgument(theid);
- #ifdef DEBUG_LOGGING
- if (m_doingAnalysis)
- {
- DEBUG_LOG(("WideChar argument: %d (%lc)\n", theid, theid));
- }
- #endif
- }
- }
- /**
- * This needs to be called for every frame during playback. Basically it prevents the user from inserting.
- */
- void RecorderClass::cullBadCommands() {
- if (m_doingAnalysis)
- return;
- GameMessage *msg = TheCommandList->getFirstMessage();
- GameMessage *next = NULL;
- while (msg != NULL) {
- next = msg->next();
- if ((msg->getType() > GameMessage::MSG_BEGIN_NETWORK_MESSAGES) &&
- (msg->getType() < GameMessage::MSG_END_NETWORK_MESSAGES) &&
- (msg->getType() != GameMessage::MSG_LOGIC_CRC)) {
- msg->deleteInstance();
- }
- msg = next;
- }
- }
- /**
- * returns the directory that holds the replay files.
- */
- AsciiString RecorderClass::getReplayDir()
- {
- const char* replayDir = "Replays\\";
- AsciiString tmp = TheGlobalData->getPath_UserData();
- tmp.concat(replayDir);
- return tmp;
- }
- /**
- * returns the file extention for the replay files.
- */
- AsciiString RecorderClass::getReplayExtention() {
- return AsciiString(replayExtention);
- }
- /**
- * returns the file name used for the replay file that is recorded to.
- */
- AsciiString RecorderClass::getLastReplayFileName()
- {
- #if defined(_DEBUG) || defined(_INTERNAL)
- if (TheNetwork && TheGlobalData->m_saveStats)
- {
- GameInfo *game = NULL;
- if (TheLAN)
- game = TheLAN->GetMyGame();
- else if (TheGameSpyInfo)
- game = TheGameSpyGame;
- if (game)
- {
- AsciiString players;
- AsciiString full;
- AsciiString fullPlusNum;
- AsciiString mapName = game->getMap();
- const char *fname = mapName.reverseFind('\\');
- if (fname)
- mapName = fname+1;
- for (Int i=0; i<MAX_SLOTS; ++i)
- {
- GameSlot *slot = game->getSlot(i);
- if (slot && slot->isHuman())
- {
- AsciiString player;
- player.format("%ls_", slot->getName().str());
- players.concat(player);
- }
- }
- full.format("%s%s_%d_%d", players.str(), mapName.str(), game->getSeed(), game->getLocalSlotNum());
- AsciiString testString;
- testString.format("%s%s%s", getReplayDir().str(), full.str(), replayExtention);
- FILE *fp;
- fp = fopen(testString.str(), "rb");
- if (fp)
- {
- fclose(fp);
- }
- else
- {
- return full;
- }
- Int test = 1;
- while (test < 20)
- {
- fullPlusNum.format("%s_%d", full.str(), test);
- testString.format("%s%s%s", getReplayDir().str(), fullPlusNum.str(), replayExtention);
- fp = fopen(testString.str(), "rb");
- if (fp)
- {
- fclose(fp);
- ++test;
- }
- else
- {
- return fullPlusNum;
- }
- }
- return fullPlusNum;
- }
- }
- #endif
- return AsciiString(lastReplayFileName);
- }
- /**
- * return the current operating mode of TheRecorder.
- */
- RecorderModeType RecorderClass::getMode() {
- return m_mode;
- }
- ///< Show or Hide the Replay controls
- void RecorderClass::initControls()
- {
- NameKeyType parentReplayControlID = TheNameKeyGenerator->nameToKey( AsciiString("ReplayControl.wnd:ParentReplayControl") );
- GameWindow *parentReplayControl = TheWindowManager->winGetWindowFromId( NULL, parentReplayControlID );
- Bool show = (getMode() != RECORDERMODETYPE_PLAYBACK);
- if (parentReplayControl)
- {
- parentReplayControl->winHide(show); // show the replay control window.
- }
- }
- ///< is this a multiplayer game (record OR playback)?
- Bool RecorderClass::isMultiplayer( void )
- {
- if (m_mode == RECORDERMODETYPE_PLAYBACK)
- {
- GameSlot *slot;
- for (int i=0; i<MAX_SLOTS; ++i)
- {
- slot = m_gameInfo.getSlot(i);
- if (slot && slot->isOccupied()) ///< slots default to closed for non-networked games
- return true;
- }
- }
- if (TheGameLogic->getGameMode()==GAME_SINGLE_PLAYER) {
- return false; // single player isn't multiplayer.
- }
- if (TheGameLogic->getGameMode()==GAME_SHELL) {
- return false; // shell isn't multiplayer.
- }
- if (TheNetwork || TheSkirmishGameInfo)
- return true;
- return false;
- }
- /**
- * Create a new recorder object.
- */
- RecorderClass * createRecorder() {
- return NEW RecorderClass;
- }
|