From ab0fc28c26a34ed0651f9d920aa1db96aad9d2ab Mon Sep 17 00:00:00 2001 From: rtldg <55846624+rtldg@users.noreply.github.com> Date: Fri, 8 Oct 2021 10:59:56 +0000 Subject: [PATCH] make replay reading/writing functions stocks --- addons/sourcemod/scripting/include/shavit.inc | 2 + .../include/shavit/shavit-replay-file.inc | 332 ++++++++++++++++++ .../include/shavit/shavit-replay-stocks.inc | 14 - .../scripting/shavit-replay-recorder.sp | 65 +--- addons/sourcemod/scripting/shavit-replay.sp | 292 ++------------- 5 files changed, 360 insertions(+), 345 deletions(-) create mode 100644 addons/sourcemod/scripting/include/shavit/shavit-replay-file.inc diff --git a/addons/sourcemod/scripting/include/shavit.inc b/addons/sourcemod/scripting/include/shavit.inc index e28ded68..fd274d5d 100644 --- a/addons/sourcemod/scripting/include/shavit.inc +++ b/addons/sourcemod/scripting/include/shavit.inc @@ -269,6 +269,8 @@ enum struct frame_cache_t // iReplayVersion >= 0x05 int iPostFrames; float fTickrate; + // blah blah not affected by iReplayVersion + int iSteamID; } stock void Shavit_LogQuery(const char[] query) diff --git a/addons/sourcemod/scripting/include/shavit/shavit-replay-file.inc b/addons/sourcemod/scripting/include/shavit/shavit-replay-file.inc new file mode 100644 index 00000000..0f92ef1b --- /dev/null +++ b/addons/sourcemod/scripting/include/shavit/shavit-replay-file.inc @@ -0,0 +1,332 @@ + +// History of REPLAY_FORMAT_SUBVERSION: +// 0x01: standard origin[3], angles[2], and buttons +// 0x02: flags added movetype added +// 0x03: integrity stuff: style, track, and map added to header. preframe count added (unimplemented until later though) +// 0x04: steamid/accountid written as a 32-bit int instead of a string +// 0x05: postframes & fTickrate added +// 0x06: mousexy and vel added +// 0x07: fixed iFrameCount because postframes were included in the value when they shouldn't be +// 0x08: added zone-offsets to header + +#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}" +#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}" +#define REPLAY_FORMAT_SUBVERSION 0x08 + +#define REPLAY_FRAMES_PER_WRITE 100 // amounts of frames to write per read/write call + +enum struct replay_header_t +{ + char sReplayFormat[40]; + int iReplayVersion; + char sMap[PLATFORM_MAX_PATH]; + int iStyle; + int iTrack; + int iPreFrames; + int iFrameCount; + float fTime; + int iSteamID; + int iPostFrames; + float fTickrate; + float fZoneOffset[2]; +} + +stock bool LoadReplayCache(frame_cache_t cache, int style, int track, const char[] path, const char[] mapname) +{ + bool success = false; + replay_header_t header; + File fFile = ReadReplayHeader(path, header, style, track); + + if (fFile != null) + { + if (header.iReplayVersion > REPLAY_FORMAT_SUBVERSION) + { + // not going to try and read it + } + else if (header.iReplayVersion < 0x03 || (StrEqual(header.sMap, mapname, false) && header.iStyle == style && header.iTrack == track)) + { + success = ReadReplayFrames(fFile, header, cache); + } + + delete fFile; + } + + return success; +} + +stock bool ReadReplayFrames(File file, replay_header_t header, frame_cache_t cache) +{ + int total_cells = 6; + int used_cells = 6; + bool is_btimes = false; + + if (header.iReplayVersion > 0x01) + { + total_cells = 8; + used_cells = 8; + } + + // We have differing total_cells & used_cells because we want to save memory during playback since the latest two cells added (vel & mousexy) aren't needed and are only useful for replay file anticheat usage stuff.... + if (header.iReplayVersion >= 0x06) + { + total_cells = 10; + used_cells = 8; + } + + any aReplayData[sizeof(frame_t)]; + + delete cache.aFrames; + int iTotalSize = header.iFrameCount + header.iPreFrames + header.iPostFrames; + cache.aFrames = new ArrayList(used_cells, iTotalSize); + + if (!header.sReplayFormat[0]) // old replay format. no header. + { + char sLine[320]; + char sExplodedLine[6][64]; + + if(!file.Seek(0, SEEK_SET)) + { + return false; + } + + while (!file.EndOfFile()) + { + file.ReadLine(sLine, 320); + int iStrings = ExplodeString(sLine, "|", sExplodedLine, 6, 64); + + aReplayData[0] = StringToFloat(sExplodedLine[0]); + aReplayData[1] = StringToFloat(sExplodedLine[1]); + aReplayData[2] = StringToFloat(sExplodedLine[2]); + aReplayData[3] = StringToFloat(sExplodedLine[3]); + aReplayData[4] = StringToFloat(sExplodedLine[4]); + aReplayData[5] = (iStrings == 6) ? StringToInt(sExplodedLine[5]) : 0; + + cache.aFrames.PushArray(aReplayData, 6); + } + + cache.iFrameCount = cache.aFrames.Length; + } + else // assumes the file position will be at the start of the frames + { + is_btimes = StrEqual(header.sReplayFormat, "btimes"); + + for (int i = 0; i < iTotalSize; i++) + { + if(file.Read(aReplayData, total_cells, 4) >= 0) + { + cache.aFrames.SetArray(i, aReplayData, used_cells); + + if (is_btimes && (aReplayData[5] & IN_BULLRUSH)) + { + if (!header.iPreFrames) + { + header.iPreFrames = i; + header.iFrameCount -= i; + } + else if (!header.iPostFrames) + { + header.iPostFrames = header.iFrameCount + header.iPreFrames - i; + header.iFrameCount -= header.iPostFrames; + } + } + } + } + } + + cache.iFrameCount = header.iFrameCount; + cache.fTime = header.fTime; + cache.iReplayVersion = header.iReplayVersion; + cache.bNewFormat = StrEqual(header.sReplayFormat, REPLAY_FORMAT_FINAL) || is_btimes; + cache.sReplayName = "unknown"; + cache.iPreFrames = header.iPreFrames; + cache.iPostFrames = header.iPostFrames; + cache.fTickrate = header.fTickrate; + cache.iSteamID = header.iSteamID; + + return true; +} + +stock File ReadReplayHeader(const char[] path, replay_header_t header, int style, int track) +{ + replay_header_t empty_header; + header = empty_header; + + if (!FileExists(path)) + { + return null; + } + + File file = OpenFile(path, "rb"); + + if (file == null) + { + return null; + } + + char sHeader[64]; + + if(!file.ReadLine(sHeader, 64)) + { + delete file; + return null; + } + + TrimString(sHeader); + char sExplodedHeader[2][64]; + ExplodeString(sHeader, ":", sExplodedHeader, 2, 64); + + strcopy(header.sReplayFormat, sizeof(header.sReplayFormat), sExplodedHeader[1]); + + if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_FINAL)) // hopefully, the last of them + { + int version = StringToInt(sExplodedHeader[0]); + + header.iReplayVersion = version; + + // replay file integrity and PreFrames + if(version >= 0x03) + { + file.ReadString(header.sMap, PLATFORM_MAX_PATH); + file.ReadUint8(header.iStyle); + file.ReadUint8(header.iTrack); + + file.ReadInt32(header.iPreFrames); + + // In case the replay was from when there could still be negative preframes + if(header.iPreFrames < 0) + { + header.iPreFrames = 0; + } + } + + file.ReadInt32(header.iFrameCount); + file.ReadInt32(view_as(header.fTime)); + + if (header.iReplayVersion < 0x07) + { + header.iFrameCount -= header.iPreFrames; + } + + if(version >= 0x04) + { + file.ReadInt32(header.iSteamID); + } + else + { + char sAuthID[32]; + file.ReadString(sAuthID, 32); + ReplaceString(sAuthID, 32, "[U:1:", ""); + ReplaceString(sAuthID, 32, "]", ""); + header.iSteamID = StringToInt(sAuthID); + } + + if (version >= 0x05) + { + file.ReadInt32(header.iPostFrames); + file.ReadInt32(view_as(header.fTickrate)); + + if (header.iReplayVersion < 0x07) + { + header.iFrameCount -= header.iPostFrames; + } + } + + if (version >= 0x08) + { + file.ReadInt32(view_as(header.fZoneOffset[0])); + file.ReadInt32(view_as(header.fZoneOffset[1])); + } + } + else if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_V2)) + { + header.iFrameCount = StringToInt(sExplodedHeader[0]); + } + else // old, outdated and slow - only used for ancient replays + { + // check for btimes replays + file.Seek(0, SEEK_SET); + any stuff[2]; + file.Read(stuff, 2, 4); + + int btimes_player_id = stuff[0]; + float run_time = stuff[1]; + + if (btimes_player_id >= 0 && run_time > 0.0 && run_time < (10.0 * 60.0 * 60.0)) + { + header.sReplayFormat = "btimes"; + header.fTime = run_time; + + file.Seek(0, SEEK_END); + header.iFrameCount = (file.Position / 4 - 2) / 6; + file.Seek(2*4, SEEK_SET); + } + } + + if (header.iReplayVersion < 0x03) + { + header.iStyle = style; + header.iTrack = track; + } + + if (header.iReplayVersion < 0x05) + { + header.fTickrate = (1.0 / GetTickInterval()); // just assume it's our own tickrate... + } + + return file; +} + +stock void WriteReplayHeader(File fFile, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int iSize, float tickrate, const char[] sMap) +{ + fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION); + + fFile.WriteString(sMap, true); + fFile.WriteInt8(style); + fFile.WriteInt8(track); + fFile.WriteInt32(preframes); + + fFile.WriteInt32(iSize - preframes - postframes); + fFile.WriteInt32(view_as(time)); + fFile.WriteInt32(steamid); + + fFile.WriteInt32(postframes); + fFile.WriteInt32(view_as(tickrate)); + + fFile.WriteInt32(view_as(fZoneOffset[0])); + fFile.WriteInt32(view_as(fZoneOffset[1])); +} + +// file_a is usually used as the wr replay file. +// file_b is usually used as the duplicate/backup replay file. +stock void WriteReplayFrames(ArrayList playerrecording, int iSize, File file_a, File file_b) +{ + any aFrameData[sizeof(frame_t)]; + any aWriteData[sizeof(frame_t) * REPLAY_FRAMES_PER_WRITE]; + int iFramesWritten = 0; + + for(int i = 0; i < iSize; i++) + { + playerrecording.GetArray(i, aFrameData, sizeof(frame_t)); + + for(int j = 0; j < sizeof(frame_t); j++) + { + aWriteData[(sizeof(frame_t) * iFramesWritten) + j] = aFrameData[j]; + } + + if(++iFramesWritten == REPLAY_FRAMES_PER_WRITE || i == iSize - 1) + { + if (file_a) + { + file_a.Write(aWriteData, sizeof(frame_t) * iFramesWritten, 4); + } + + if (file_b) + { + file_b.Write(aWriteData, sizeof(frame_t) * iFramesWritten, 4); + } + + iFramesWritten = 0; + } + } +} + diff --git a/addons/sourcemod/scripting/include/shavit/shavit-replay-stocks.inc b/addons/sourcemod/scripting/include/shavit/shavit-replay-stocks.inc index 55fd34c6..9ddc48d9 100644 --- a/addons/sourcemod/scripting/include/shavit/shavit-replay-stocks.inc +++ b/addons/sourcemod/scripting/include/shavit/shavit-replay-stocks.inc @@ -1,18 +1,4 @@ -// History of REPLAY_FORMAT_SUBVERSION: -// 0x01: standard origin[3], angles[2], and buttons -// 0x02: flags added movetype added -// 0x03: integrity stuff: style, track, and map added to header. preframe count added (unimplemented until later though) -// 0x04: steamid/accountid written as a 32-bit int instead of a string -// 0x05: postframes & fTickrate added -// 0x06: mousexy and vel added -// 0x07: fixed iFrameCount because postframes were included in the value when they shouldn't be -// 0x08: added zone-offsets to header - -#define REPLAY_FORMAT_V2 "{SHAVITREPLAYFORMAT}{V2}" -#define REPLAY_FORMAT_FINAL "{SHAVITREPLAYFORMAT}{FINAL}" -#define REPLAY_FORMAT_SUBVERSION 0x08 - stock bool Shavit_ReplayEnabledStyle(int style) { return !Shavit_GetStyleSettingBool(style, "unranked") && !Shavit_GetStyleSettingBool(style, "noreplay"); diff --git a/addons/sourcemod/scripting/shavit-replay-recorder.sp b/addons/sourcemod/scripting/shavit-replay-recorder.sp index 413e12ab..bc8fd0d7 100644 --- a/addons/sourcemod/scripting/shavit-replay-recorder.sp +++ b/addons/sourcemod/scripting/shavit-replay-recorder.sp @@ -25,6 +25,7 @@ #include #include +#include public Plugin myinfo = { @@ -35,8 +36,6 @@ public Plugin myinfo = url = "https://github.com/shavitush/bhoptimer" } -#define FRAMES_PER_WRITE 100 // amounts of frames to write per read/write call - enum struct finished_run_info { int iSteamID; @@ -424,72 +423,20 @@ void SaveReplay(int style, int track, float time, int steamid, char[] name, int fCopy = OpenFile(sPath, "wb"); } - if (saveWR) + if (fWR) { - WriteReplayHeader(fWR, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize); + WriteReplayHeader(fWR, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map); } - if (saveCopy) + if (fCopy) { - WriteReplayHeader(fCopy, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize); + WriteReplayHeader(fCopy, style, track, time, steamid, preframes, postframes, fZoneOffset, iSize, gF_Tickrate, gS_Map); } - any aFrameData[sizeof(frame_t)]; - any aWriteData[sizeof(frame_t) * FRAMES_PER_WRITE]; - int iFramesWritten = 0; - - for(int i = 0; i < iSize; i++) - { - playerrecording.GetArray(i, aFrameData, sizeof(frame_t)); - - for(int j = 0; j < sizeof(frame_t); j++) - { - aWriteData[(sizeof(frame_t) * iFramesWritten) + j] = aFrameData[j]; - } - - if(++iFramesWritten == FRAMES_PER_WRITE || i == iSize - 1) - { - if (saveWR) - { - fWR.Write(aWriteData, sizeof(frame_t) * iFramesWritten, 4); - } - - if (saveCopy) - { - fCopy.Write(aWriteData, sizeof(frame_t) * iFramesWritten, 4); - } - - iFramesWritten = 0; - } - } + WriteReplayFrames(playerrecording, iSize, fWR, fCopy); delete fWR; delete fCopy; - - if (!saveWR) - { - return; - } -} - -void WriteReplayHeader(File fFile, int style, int track, float time, int steamid, int preframes, int postframes, float fZoneOffset[2], int iSize) -{ - fFile.WriteLine("%d:" ... REPLAY_FORMAT_FINAL, REPLAY_FORMAT_SUBVERSION); - - fFile.WriteString(gS_Map, true); - fFile.WriteInt8(style); - fFile.WriteInt8(track); - fFile.WriteInt32(preframes); - - fFile.WriteInt32(iSize - preframes - postframes); - fFile.WriteInt32(view_as(time)); - fFile.WriteInt32(steamid); - - fFile.WriteInt32(postframes); - fFile.WriteInt32(view_as(gF_Tickrate)); - - fFile.WriteInt32(view_as(fZoneOffset[0])); - fFile.WriteInt32(view_as(fZoneOffset[1])); } public Action Shavit_OnUserCmdPre(int client, int &buttons, int &impulse, float vel[3], float angles[3], TimerStatus status, int track, int style, int mouse[2]) diff --git a/addons/sourcemod/scripting/shavit-replay.sp b/addons/sourcemod/scripting/shavit-replay.sp index 73f1ea24..9692014d 100644 --- a/addons/sourcemod/scripting/shavit-replay.sp +++ b/addons/sourcemod/scripting/shavit-replay.sp @@ -30,6 +30,7 @@ #include #include +#include #undef REQUIRE_EXTENSIONS #include @@ -66,22 +67,6 @@ enum struct loopingbot_config_t char sName[MAX_NAME_LENGTH]; } -enum struct replay_header_t -{ - char sReplayFormat[40]; - int iReplayVersion; - char sMap[PLATFORM_MAX_PATH]; - int iStyle; - int iTrack; - int iPreFrames; - int iFrameCount; - float fTime; - int iSteamID; - int iPostFrames; - float fTickrate; - float fZoneOffset[2]; -} - enum struct bot_info_t { int iEnt; @@ -786,6 +771,25 @@ void StopOrRestartBots(int style, int track, bool restart) } } +bool LoadReplay(frame_cache_t cache, int style, int track, const char[] path, const char[] mapname) +{ + bool ret = LoadReplayCache(cache, style, track, path, mapname); + + if (ret && cache.iSteamID > 0) + { + char sQuery[192]; + FormatEx(sQuery, 192, "SELECT name FROM %susers WHERE auth = %d;", gS_MySQLPrefix, cache.iSteamID); + + DataPack hPack = new DataPack(); + hPack.WriteCell(style); + hPack.WriteCell(track); + + gH_SQL.Query(SQL_GetUserName_Callback, sQuery, hPack, DBPrio_High); + } + + return ret; +} + bool UnloadReplay(int style, int track, bool reload, bool restart, const char[] path = "") { ClearFrameCache(gA_FrameCache[style][track]); @@ -1859,262 +1863,6 @@ bool DefaultLoadReplay(frame_cache_t cache, int style, int track) return true; } -bool LoadReplay(frame_cache_t cache, int style, int track, const char[] path, const char[] mapname) -{ - bool success = false; - replay_header_t header; - File fFile = ReadReplayHeader(path, header, style, track); - - if (fFile != null) - { - if (header.iReplayVersion > REPLAY_FORMAT_SUBVERSION) - { - // not going to try and read it - } - else if (header.iReplayVersion < 0x03 || (StrEqual(header.sMap, mapname, false) && header.iStyle == style && header.iTrack == track)) - { - success = ReadReplayFrames(fFile, header, cache); - } - - delete fFile; - } - - return success; -} - -bool ReadReplayFrames(File file, replay_header_t header, frame_cache_t cache) -{ - int total_cells = 6; - int used_cells = 6; - bool is_btimes = false; - - if (header.iReplayVersion > 0x01) - { - total_cells = 8; - used_cells = 8; - } - - // We have differing total_cells & used_cells because we want to save memory during playback since the latest two cells added (vel & mousexy) aren't needed and are only useful for replay file anticheat usage stuff.... - if (header.iReplayVersion >= 0x06) - { - total_cells = 10; - used_cells = 8; - } - - any aReplayData[sizeof(frame_t)]; - - delete cache.aFrames; - int iTotalSize = header.iFrameCount + header.iPreFrames + header.iPostFrames; - cache.aFrames = new ArrayList(used_cells, iTotalSize); - - if (!header.sReplayFormat[0]) // old replay format. no header. - { - char sLine[320]; - char sExplodedLine[6][64]; - - if(!file.Seek(0, SEEK_SET)) - { - return false; - } - - while (!file.EndOfFile()) - { - file.ReadLine(sLine, 320); - int iStrings = ExplodeString(sLine, "|", sExplodedLine, 6, 64); - - aReplayData[0] = StringToFloat(sExplodedLine[0]); - aReplayData[1] = StringToFloat(sExplodedLine[1]); - aReplayData[2] = StringToFloat(sExplodedLine[2]); - aReplayData[3] = StringToFloat(sExplodedLine[3]); - aReplayData[4] = StringToFloat(sExplodedLine[4]); - aReplayData[5] = (iStrings == 6) ? StringToInt(sExplodedLine[5]) : 0; - - cache.aFrames.PushArray(aReplayData, 6); - } - - cache.iFrameCount = cache.aFrames.Length; - } - else // assumes the file position will be at the start of the frames - { - is_btimes = StrEqual(header.sReplayFormat, "btimes"); - - for (int i = 0; i < iTotalSize; i++) - { - if(file.Read(aReplayData, total_cells, 4) >= 0) - { - cache.aFrames.SetArray(i, aReplayData, used_cells); - - if (is_btimes && (aReplayData[5] & IN_BULLRUSH)) - { - if (!header.iPreFrames) - { - header.iPreFrames = i; - header.iFrameCount -= i; - } - else if (!header.iPostFrames) - { - header.iPostFrames = header.iFrameCount + header.iPreFrames - i; - header.iFrameCount -= header.iPostFrames; - } - } - } - } - - if (StrEqual(header.sReplayFormat, REPLAY_FORMAT_FINAL)) - { - char sQuery[192]; - FormatEx(sQuery, 192, "SELECT name FROM %susers WHERE auth = %d;", gS_MySQLPrefix, header.iSteamID); - - DataPack hPack = new DataPack(); - hPack.WriteCell(header.iStyle); - hPack.WriteCell(header.iTrack); - - gH_SQL.Query(SQL_GetUserName_Callback, sQuery, hPack, DBPrio_High); - } - } - - cache.iFrameCount = header.iFrameCount; - cache.fTime = header.fTime; - cache.iReplayVersion = header.iReplayVersion; - cache.bNewFormat = StrEqual(header.sReplayFormat, REPLAY_FORMAT_FINAL) || is_btimes; - cache.sReplayName = "unknown"; - cache.iPreFrames = header.iPreFrames; - cache.iPostFrames = header.iPostFrames; - cache.fTickrate = header.fTickrate; - - return true; -} - -File ReadReplayHeader(const char[] path, replay_header_t header, int style, int track) -{ - replay_header_t empty_header; - header = empty_header; - - if (!FileExists(path)) - { - return null; - } - - File file = OpenFile(path, "rb"); - - if (file == null) - { - return null; - } - - char sHeader[64]; - - if(!file.ReadLine(sHeader, 64)) - { - delete file; - return null; - } - - TrimString(sHeader); - char sExplodedHeader[2][64]; - ExplodeString(sHeader, ":", sExplodedHeader, 2, 64); - - strcopy(header.sReplayFormat, sizeof(header.sReplayFormat), sExplodedHeader[1]); - - if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_FINAL)) // hopefully, the last of them - { - int version = StringToInt(sExplodedHeader[0]); - - header.iReplayVersion = version; - - // replay file integrity and PreFrames - if(version >= 0x03) - { - file.ReadString(header.sMap, PLATFORM_MAX_PATH); - file.ReadUint8(header.iStyle); - file.ReadUint8(header.iTrack); - - file.ReadInt32(header.iPreFrames); - - // In case the replay was from when there could still be negative preframes - if(header.iPreFrames < 0) - { - header.iPreFrames = 0; - } - } - - file.ReadInt32(header.iFrameCount); - file.ReadInt32(view_as(header.fTime)); - - if (header.iReplayVersion < 0x07) - { - header.iFrameCount -= header.iPreFrames; - } - - if(version >= 0x04) - { - file.ReadInt32(header.iSteamID); - } - else - { - char sAuthID[32]; - file.ReadString(sAuthID, 32); - ReplaceString(sAuthID, 32, "[U:1:", ""); - ReplaceString(sAuthID, 32, "]", ""); - header.iSteamID = StringToInt(sAuthID); - } - - if (version >= 0x05) - { - file.ReadInt32(header.iPostFrames); - file.ReadInt32(view_as(header.fTickrate)); - - if (header.iReplayVersion < 0x07) - { - header.iFrameCount -= header.iPostFrames; - } - } - - if (version >= 0x08) - { - file.ReadInt32(view_as(header.fZoneOffset[0])); - file.ReadInt32(view_as(header.fZoneOffset[1])); - } - } - else if(StrEqual(header.sReplayFormat, REPLAY_FORMAT_V2)) - { - header.iFrameCount = StringToInt(sExplodedHeader[0]); - } - else // old, outdated and slow - only used for ancient replays - { - // check for btimes replays - file.Seek(0, SEEK_SET); - any stuff[2]; - file.Read(stuff, 2, 4); - - int btimes_player_id = stuff[0]; - float run_time = stuff[1]; - - if (btimes_player_id >= 0 && run_time > 0.0 && run_time < (10.0 * 60.0 * 60.0)) - { - header.sReplayFormat = "btimes"; - header.fTime = run_time; - - file.Seek(0, SEEK_END); - header.iFrameCount = (file.Position / 4 - 2) / 6; - file.Seek(2*4, SEEK_SET); - } - } - - if (header.iReplayVersion < 0x03) - { - header.iStyle = style; - header.iTrack = track; - } - - if (header.iReplayVersion < 0x05) - { - header.fTickrate = gF_Tickrate; - } - - return file; -} - bool DeleteReplay(int style, int track, int accountid, const char[] mapname) { char sPath[PLATFORM_MAX_PATH];