2010-11-20 10:37:14 +00:00
|
|
|
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
2011-06-09 20:44:22 +00:00
|
|
|
#include <base/math.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <base/system.h>
|
2011-06-09 20:44:22 +00:00
|
|
|
|
2010-08-17 22:06:00 +00:00
|
|
|
#include <engine/console.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <engine/storage.h>
|
2011-06-09 20:44:22 +00:00
|
|
|
|
2014-08-13 15:32:03 +00:00
|
|
|
#include <engine/shared/config.h>
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <engine/shared/video.h>
|
2016-08-30 23:39:59 +00:00
|
|
|
#endif
|
|
|
|
|
2011-06-09 20:44:22 +00:00
|
|
|
#include "compression.h"
|
2010-09-12 10:16:51 +00:00
|
|
|
#include "demo.h"
|
2010-05-29 07:25:38 +00:00
|
|
|
#include "memheap.h"
|
|
|
|
#include "network.h"
|
2011-06-09 20:44:22 +00:00
|
|
|
#include "snapshot.h"
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2022-03-21 08:01:56 +00:00
|
|
|
const double g_aSpeeds[g_DemoSpeeds] = {0.1, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 12.0, 16.0, 20.0, 24.0, 28.0, 32.0, 40.0, 48.0, 56.0, 64.0};
|
|
|
|
const CUuid SHA256_EXTENSION =
|
|
|
|
{{0x6b, 0xe6, 0xda, 0x4a, 0xce, 0xbd, 0x38, 0x0c,
|
|
|
|
0x9b, 0x5b, 0x12, 0x89, 0xc8, 0x42, 0xd7, 0x80}};
|
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
static const unsigned char s_aHeaderMarker[7] = {'T', 'W', 'D', 'E', 'M', 'O', 0};
|
|
|
|
static const unsigned char s_CurVersion = 6;
|
|
|
|
static const unsigned char s_OldVersion = 3;
|
|
|
|
static const unsigned char s_Sha256Version = 6;
|
|
|
|
static const unsigned char s_VersionTickCompression = 5; // demo files with this version or higher will use `CHUNKTICKFLAG_TICK_COMPRESSED`
|
|
|
|
static const int s_LengthOffset = 152;
|
|
|
|
static const int s_NumMarkersOffset = 176;
|
2010-09-03 19:17:32 +00:00
|
|
|
|
2021-10-23 23:54:18 +00:00
|
|
|
static const ColorRGBA gs_DemoPrintColor{0.75f, 0.7f, 0.7f, 1.0f};
|
2021-03-08 03:41:27 +00:00
|
|
|
|
2017-07-08 11:38:27 +00:00
|
|
|
CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
m_File = 0;
|
Fix uninitialized variable
As reported by valgrind --tool=memcheck:
[201==344082== Conditional jump or move depends on uninitialised value(s)
==344082== at 0x483BC85: strlen (vg_replace_strmem.c:461)
==344082== by 0x5B9E61D: __vfprintf_internal (in /usr/lib/libc-2.30.so)
==344082== by 0x5BB0409: __vsnprintf_internal (in /usr/lib/libc-2.30.so)
==344082== by 0x222AE7: str_format (system.c:2350)
==344082== by 0x2196AB: CStorage::GetPath(int, char const*, char*, unsigned int) (storage.cpp:274)
==344082== by 0x219DDD: CStorage::RemoveFile(char const*, int) (storage.cpp:409)
==344082== by 0x255D3C: CClient::DemoRecorder_Stop(int, bool) (client.cpp:3546)
==344082== by 0x2569E7: CClient::ConchainReplays(IConsole::IResult*, void*, void (*)(IConsole::IResult*, void*), void*) (client.cpp:3727)
==344082== by 0x1F4659: CConsole::Con_Chain(IConsole::IResult*, void*) (console.cpp:1169)
==344082== by 0x1E4C2C: CConsole::ExecuteLineStroked(int, char const*, int, bool) (console.cpp:504)
==344082== by 0x1E4F37: CConsole::ExecuteLine(char const*, int, bool) (console.cpp:558)
==344082== by 0x1E5240: CConsole::ExecuteFile(char const*, int, bool, int) (console.cpp:604)
2019-11-03 17:05:41 +00:00
|
|
|
m_aCurrentFilename[0] = '\0';
|
2017-02-28 16:16:22 +00:00
|
|
|
m_pfnFilter = 0;
|
|
|
|
m_pUser = 0;
|
2010-05-29 07:25:38 +00:00
|
|
|
m_LastTickMarker = -1;
|
|
|
|
m_pSnapshotDelta = pSnapshotDelta;
|
2017-07-08 11:38:27 +00:00
|
|
|
m_NoMapData = NoMapData;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Record
|
2021-11-08 19:21:02 +00:00
|
|
|
int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, SHA256_DIGEST *pSha256, unsigned Crc, const char *pType, unsigned MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2017-02-28 09:08:14 +00:00
|
|
|
m_pfnFilter = pfnFilter;
|
|
|
|
m_pUser = pUser;
|
|
|
|
|
2014-12-02 14:44:54 +00:00
|
|
|
m_pMapData = pMapData;
|
2019-05-24 22:24:13 +00:00
|
|
|
m_pConsole = pConsole;
|
2014-12-02 14:44:54 +00:00
|
|
|
|
|
|
|
IOHANDLE DemoFile = pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
|
|
|
if(!DemoFile)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for recording", pFilename);
|
2021-03-08 03:41:27 +00:00
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
|
2019-05-31 18:42:28 +00:00
|
|
|
}
|
2014-12-02 14:44:54 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
CDemoHeader Header;
|
2013-02-25 23:00:38 +00:00
|
|
|
CTimelineMarkers TimelineMarkers;
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_File)
|
|
|
|
{
|
2017-07-08 11:38:27 +00:00
|
|
|
io_close(DemoFile);
|
2010-05-29 07:25:38 +00:00
|
|
|
return -1;
|
2017-07-08 11:38:27 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2017-07-08 20:09:03 +00:00
|
|
|
bool CloseMapFile = false;
|
2010-09-12 14:56:13 +00:00
|
|
|
|
2017-07-08 20:09:03 +00:00
|
|
|
if(MapFile)
|
|
|
|
io_seek(MapFile, 0, IOSEEK_START);
|
|
|
|
|
2020-01-03 09:12:37 +00:00
|
|
|
char aSha256[SHA256_MAXSTRSIZE];
|
|
|
|
if(pSha256)
|
|
|
|
sha256_str(*pSha256, aSha256, sizeof(aSha256));
|
|
|
|
|
2017-07-08 20:09:03 +00:00
|
|
|
if(!pMapData && !MapFile)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2014-12-02 14:44:54 +00:00
|
|
|
// open mapfile
|
|
|
|
char aMapFilename[128];
|
2019-06-05 20:24:17 +00:00
|
|
|
// try the downloaded maps
|
2020-01-03 09:12:37 +00:00
|
|
|
if(pSha256)
|
|
|
|
{
|
2020-10-10 20:06:47 +00:00
|
|
|
str_format(aMapFilename, sizeof(aMapFilename), "downloadedmaps/%s_%s.map", pMap, aSha256);
|
2020-01-03 09:12:37 +00:00
|
|
|
}
|
2020-10-10 20:06:47 +00:00
|
|
|
else
|
2020-01-03 09:12:37 +00:00
|
|
|
{
|
|
|
|
str_format(aMapFilename, sizeof(aMapFilename), "downloadedmaps/%s_%08x.map", pMap, Crc);
|
|
|
|
}
|
2020-10-10 20:06:47 +00:00
|
|
|
MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_READ, IStorage::TYPE_ALL);
|
2014-12-02 14:44:54 +00:00
|
|
|
if(!MapFile)
|
|
|
|
{
|
2019-06-05 20:24:17 +00:00
|
|
|
// try the normal maps folder
|
|
|
|
str_format(aMapFilename, sizeof(aMapFilename), "maps/%s.map", pMap);
|
2014-12-02 14:44:54 +00:00
|
|
|
MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_READ, IStorage::TYPE_ALL);
|
|
|
|
}
|
|
|
|
if(!MapFile)
|
|
|
|
{
|
|
|
|
// search for the map within subfolders
|
|
|
|
char aBuf[512];
|
|
|
|
str_format(aMapFilename, sizeof(aMapFilename), "%s.map", pMap);
|
|
|
|
if(pStorage->FindFile(aMapFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
|
|
|
|
MapFile = pStorage->OpenFile(aBuf, IOFLAG_READ, IStorage::TYPE_ALL);
|
|
|
|
}
|
|
|
|
if(!MapFile)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "Unable to open mapfile '%s'", pMap);
|
2021-03-08 03:41:27 +00:00
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
|
2019-05-31 18:42:28 +00:00
|
|
|
}
|
2014-12-02 14:44:54 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2017-07-08 20:09:03 +00:00
|
|
|
|
|
|
|
CloseMapFile = true;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-02-19 12:37:31 +00:00
|
|
|
if(MapFile)
|
|
|
|
MapSize = io_length(MapFile);
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// write header
|
|
|
|
mem_zero(&Header, sizeof(Header));
|
2020-10-27 17:57:14 +00:00
|
|
|
mem_copy(Header.m_aMarker, s_aHeaderMarker, sizeof(Header.m_aMarker));
|
|
|
|
Header.m_Version = s_CurVersion;
|
2010-05-29 07:25:38 +00:00
|
|
|
str_copy(Header.m_aNetversion, pNetVersion, sizeof(Header.m_aNetversion));
|
2011-03-13 09:41:10 +00:00
|
|
|
str_copy(Header.m_aMapName, pMap, sizeof(Header.m_aMapName));
|
2021-11-08 19:21:02 +00:00
|
|
|
uint_to_bytes_be(Header.m_aMapSize, MapSize);
|
|
|
|
uint_to_bytes_be(Header.m_aMapCrc, Crc);
|
2010-05-29 07:25:38 +00:00
|
|
|
str_copy(Header.m_aType, pType, sizeof(Header.m_aType));
|
2011-03-13 09:41:10 +00:00
|
|
|
// Header.m_Length - add this on stop
|
|
|
|
str_timestamp(Header.m_aTimestamp, sizeof(Header.m_aTimestamp));
|
2011-03-17 16:58:10 +00:00
|
|
|
io_write(DemoFile, &Header, sizeof(Header));
|
2013-02-25 23:00:38 +00:00
|
|
|
io_write(DemoFile, &TimelineMarkers, sizeof(TimelineMarkers)); // fill this on stop
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-12-17 14:44:54 +00:00
|
|
|
//Write Sha256
|
|
|
|
io_write(DemoFile, SHA256_EXTENSION.m_aData, sizeof(SHA256_EXTENSION.m_aData));
|
2020-01-03 09:12:37 +00:00
|
|
|
io_write(DemoFile, pSha256, sizeof(SHA256_DIGEST));
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2017-07-08 11:38:27 +00:00
|
|
|
if(m_NoMapData)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else if(pMapData)
|
2010-09-12 14:56:13 +00:00
|
|
|
{
|
2017-07-08 11:38:27 +00:00
|
|
|
io_write(DemoFile, pMapData, MapSize);
|
2014-12-02 14:44:54 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// write map data
|
2022-02-14 23:12:52 +00:00
|
|
|
while(true)
|
2014-12-02 14:44:54 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
unsigned char aChunk[1024 * 64];
|
2014-12-02 14:44:54 +00:00
|
|
|
int Bytes = io_read(MapFile, &aChunk, sizeof(aChunk));
|
|
|
|
if(Bytes <= 0)
|
|
|
|
break;
|
|
|
|
io_write(DemoFile, &aChunk, Bytes);
|
|
|
|
}
|
2017-07-08 20:09:03 +00:00
|
|
|
if(CloseMapFile)
|
|
|
|
io_close(MapFile);
|
|
|
|
else
|
|
|
|
io_seek(MapFile, 0, IOSEEK_START);
|
2010-09-03 19:17:32 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_LastKeyFrame = -1;
|
|
|
|
m_LastTickMarker = -1;
|
2010-12-07 23:48:02 +00:00
|
|
|
m_FirstTick = -1;
|
2012-01-10 22:13:19 +00:00
|
|
|
m_NumTimelineMarkers = 0;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "Recording to '%s'", pFilename);
|
2021-03-08 03:41:27 +00:00
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
|
2019-05-31 18:42:28 +00:00
|
|
|
}
|
2011-03-17 16:58:10 +00:00
|
|
|
m_File = DemoFile;
|
2019-05-20 21:55:40 +00:00
|
|
|
str_copy(m_aCurrentFilename, pFilename, sizeof(m_aCurrentFilename));
|
2011-03-17 16:58:10 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Tickmarker
|
2011-04-13 18:37:12 +00:00
|
|
|
7 = Always set
|
|
|
|
6 = Keyframe flag
|
|
|
|
0-5 = Delta tick
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
Normal
|
2011-04-13 18:37:12 +00:00
|
|
|
7 = Not set
|
|
|
|
5-6 = Type
|
|
|
|
0-4 = Size
|
2010-05-29 07:25:38 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CHUNKTYPEFLAG_TICKMARKER = 0x80,
|
|
|
|
CHUNKTICKFLAG_KEYFRAME = 0x40, // only when tickmarker is set
|
2015-02-23 13:26:10 +00:00
|
|
|
CHUNKTICKFLAG_TICK_COMPRESSED = 0x20, // when we store the tick value in the first chunk
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2015-02-23 13:26:10 +00:00
|
|
|
CHUNKMASK_TICK = 0x1f,
|
2015-03-11 16:52:18 +00:00
|
|
|
CHUNKMASK_TICK_LEGACY = 0x3f,
|
2010-05-29 07:25:38 +00:00
|
|
|
CHUNKMASK_TYPE = 0x60,
|
|
|
|
CHUNKMASK_SIZE = 0x1f,
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
CHUNKTYPE_SNAPSHOT = 1,
|
|
|
|
CHUNKTYPE_MESSAGE = 2,
|
|
|
|
CHUNKTYPE_DELTA = 3,
|
|
|
|
|
|
|
|
CHUNKFLAG_BIGSIZE = 0x10
|
|
|
|
};
|
|
|
|
|
|
|
|
void CDemoRecorder::WriteTickMarker(int Tick, int Keyframe)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_LastTickMarker == -1 || Tick - m_LastTickMarker > CHUNKMASK_TICK || Keyframe)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
unsigned char aChunk[5];
|
|
|
|
aChunk[0] = CHUNKTYPEFLAG_TICKMARKER;
|
2021-11-08 19:21:02 +00:00
|
|
|
uint_to_bytes_be(aChunk + 1, Tick);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(Keyframe)
|
|
|
|
aChunk[0] |= CHUNKTICKFLAG_KEYFRAME;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
io_write(m_File, aChunk, sizeof(aChunk));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned char aChunk[1];
|
2020-09-26 19:41:58 +00:00
|
|
|
aChunk[0] = CHUNKTYPEFLAG_TICKMARKER | CHUNKTICKFLAG_TICK_COMPRESSED | (Tick - m_LastTickMarker);
|
2010-05-29 07:25:38 +00:00
|
|
|
io_write(m_File, aChunk, sizeof(aChunk));
|
2011-04-13 18:37:12 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
m_LastTickMarker = Tick;
|
2010-12-07 23:48:02 +00:00
|
|
|
if(m_FirstTick < 0)
|
|
|
|
m_FirstTick = Tick;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoRecorder::Write(int Type, const void *pData, int Size)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
char aBuffer[64 * 1024];
|
|
|
|
char aBuffer2[64 * 1024];
|
2010-05-29 07:25:38 +00:00
|
|
|
unsigned char aChunk[3];
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!m_File)
|
|
|
|
return;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Size > 64 * 1024)
|
2013-08-04 02:24:33 +00:00
|
|
|
return;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
/* pad the data with 0 so we get an alignment of 4,
|
|
|
|
else the compression won't work and miss some bytes */
|
|
|
|
mem_copy(aBuffer2, pData, Size);
|
2020-09-26 19:41:58 +00:00
|
|
|
while(Size & 3)
|
2010-05-29 07:25:38 +00:00
|
|
|
aBuffer2[Size++] = 0;
|
2017-09-16 17:30:08 +00:00
|
|
|
Size = CVariableInt::Compress(aBuffer2, Size, aBuffer, sizeof(aBuffer)); // buffer2 -> buffer
|
|
|
|
if(Size < 0)
|
|
|
|
return;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
Size = CNetBase::Compress(aBuffer, Size, aBuffer2, sizeof(aBuffer2)); // buffer -> buffer2
|
2017-09-16 17:30:08 +00:00
|
|
|
if(Size < 0)
|
|
|
|
return;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
aChunk[0] = ((Type & 0x3) << 5);
|
2010-05-29 07:25:38 +00:00
|
|
|
if(Size < 30)
|
|
|
|
{
|
|
|
|
aChunk[0] |= Size;
|
|
|
|
io_write(m_File, aChunk, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(Size < 256)
|
|
|
|
{
|
|
|
|
aChunk[0] |= 30;
|
2020-09-26 19:41:58 +00:00
|
|
|
aChunk[1] = Size & 0xff;
|
2010-05-29 07:25:38 +00:00
|
|
|
io_write(m_File, aChunk, 2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aChunk[0] |= 31;
|
2020-09-26 19:41:58 +00:00
|
|
|
aChunk[1] = Size & 0xff;
|
|
|
|
aChunk[2] = Size >> 8;
|
2010-05-29 07:25:38 +00:00
|
|
|
io_write(m_File, aChunk, 3);
|
|
|
|
}
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
io_write(m_File, aBuffer2, Size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_LastKeyFrame == -1 || (Tick - m_LastKeyFrame) > SERVER_TICK_SPEED * 5)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
// write full tickmarker
|
|
|
|
WriteTickMarker(Tick, 1);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// write snapshot
|
|
|
|
Write(CHUNKTYPE_SNAPSHOT, pData, Size);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_LastKeyFrame = Tick;
|
|
|
|
mem_copy(m_aLastSnapshotData, pData, Size);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// create delta, prepend tick
|
2020-09-26 19:41:58 +00:00
|
|
|
char aDeltaData[CSnapshot::MAX_SIZE + sizeof(int)];
|
2010-05-29 07:25:38 +00:00
|
|
|
int DeltaSize;
|
|
|
|
|
|
|
|
// write tickmarker
|
|
|
|
WriteTickMarker(Tick, 0);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
DeltaSize = m_pSnapshotDelta->CreateDelta((CSnapshot *)m_aLastSnapshotData, (CSnapshot *)pData, &aDeltaData);
|
2010-05-29 07:25:38 +00:00
|
|
|
if(DeltaSize)
|
|
|
|
{
|
|
|
|
// record delta
|
|
|
|
Write(CHUNKTYPE_DELTA, aDeltaData, DeltaSize);
|
|
|
|
mem_copy(m_aLastSnapshotData, pData, Size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoRecorder::RecordMessage(const void *pData, int Size)
|
|
|
|
{
|
2017-02-28 09:08:14 +00:00
|
|
|
if(m_pfnFilter)
|
2016-04-27 15:05:30 +00:00
|
|
|
{
|
2017-02-28 09:08:14 +00:00
|
|
|
if(m_pfnFilter(pData, Size, m_pUser))
|
|
|
|
{
|
2016-04-27 15:05:30 +00:00
|
|
|
return;
|
2017-02-28 09:08:14 +00:00
|
|
|
}
|
2016-04-27 15:05:30 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
Write(CHUNKTYPE_MESSAGE, pData, Size);
|
|
|
|
}
|
|
|
|
|
2017-07-08 11:38:27 +00:00
|
|
|
int CDemoRecorder::Stop()
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
if(!m_File)
|
|
|
|
return -1;
|
2011-03-13 09:41:10 +00:00
|
|
|
|
|
|
|
// add the demo length to the header
|
2020-10-27 17:57:14 +00:00
|
|
|
io_seek(m_File, s_LengthOffset, IOSEEK_START);
|
2021-11-08 19:21:02 +00:00
|
|
|
unsigned char aLength[4];
|
|
|
|
int_to_bytes_be(aLength, Length());
|
2011-03-13 09:41:10 +00:00
|
|
|
io_write(m_File, aLength, sizeof(aLength));
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2012-01-10 22:13:19 +00:00
|
|
|
// add the timeline markers to the header
|
2020-10-27 17:57:14 +00:00
|
|
|
io_seek(m_File, s_NumMarkersOffset, IOSEEK_START);
|
2021-11-08 19:21:02 +00:00
|
|
|
unsigned char aNumMarkers[4];
|
|
|
|
int_to_bytes_be(aNumMarkers, m_NumTimelineMarkers);
|
2012-01-10 22:13:19 +00:00
|
|
|
io_write(m_File, aNumMarkers, sizeof(aNumMarkers));
|
|
|
|
for(int i = 0; i < m_NumTimelineMarkers; i++)
|
|
|
|
{
|
2021-11-08 19:21:02 +00:00
|
|
|
unsigned char aMarker[4];
|
|
|
|
int_to_bytes_be(aMarker, m_aTimelineMarkers[i]);
|
2012-01-10 22:13:19 +00:00
|
|
|
io_write(m_File, aMarker, sizeof(aMarker));
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
io_close(m_File);
|
|
|
|
m_File = 0;
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
2021-03-08 03:41:27 +00:00
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording", gs_DemoPrintColor);
|
2011-03-17 16:58:10 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-10 22:13:19 +00:00
|
|
|
void CDemoRecorder::AddDemoMarker()
|
|
|
|
{
|
|
|
|
if(m_LastTickMarker < 0 || m_NumTimelineMarkers >= MAX_TIMELINE_MARKERS)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// not more than 1 marker in a second
|
|
|
|
if(m_NumTimelineMarkers > 0)
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
int Diff = m_LastTickMarker - m_aTimelineMarkers[m_NumTimelineMarkers - 1];
|
2022-02-14 16:36:54 +00:00
|
|
|
if(Diff < (float)SERVER_TICK_SPEED)
|
2012-01-10 22:13:19 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_aTimelineMarkers[m_NumTimelineMarkers++] = m_LastTickMarker;
|
|
|
|
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
2021-03-08 03:41:27 +00:00
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Added timeline marker", gs_DemoPrintColor);
|
2012-01-10 22:13:19 +00:00
|
|
|
}
|
|
|
|
|
2021-07-19 18:14:12 +00:00
|
|
|
CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta, TUpdateIntraTimesFunc &&UpdateIntraTimesFunc)
|
|
|
|
{
|
|
|
|
Construct(pSnapshotDelta);
|
|
|
|
|
|
|
|
m_UpdateIntraTimesFunc = UpdateIntraTimesFunc;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta)
|
2021-07-19 18:14:12 +00:00
|
|
|
{
|
|
|
|
Construct(pSnapshotDelta);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::Construct(class CSnapshotDelta *pSnapshotDelta)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
m_File = 0;
|
|
|
|
m_pKeyFrames = 0;
|
2016-10-28 07:31:22 +00:00
|
|
|
m_SpeedIndex = 4;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
m_pSnapshotDelta = pSnapshotDelta;
|
|
|
|
m_LastSnapshotDataSize = -1;
|
|
|
|
}
|
|
|
|
|
2016-04-27 15:05:30 +00:00
|
|
|
void CDemoPlayer::SetListener(IListener *pListener)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2016-04-27 15:05:30 +00:00
|
|
|
m_pListener = pListener;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
|
|
|
|
{
|
|
|
|
unsigned char Chunk = 0;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
*pSize = 0;
|
|
|
|
*pType = 0;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2015-03-13 14:48:30 +00:00
|
|
|
if(m_File == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk))
|
|
|
|
return -1;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(Chunk & CHUNKTYPEFLAG_TICKMARKER)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
// decode tick marker
|
2020-09-26 19:41:58 +00:00
|
|
|
int Tickdelta_legacy = Chunk & (CHUNKMASK_TICK_LEGACY); // compatibility
|
|
|
|
*pType = Chunk & (CHUNKTYPEFLAG_TICKMARKER | CHUNKTICKFLAG_KEYFRAME);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
if(m_Info.m_Header.m_Version < s_VersionTickCompression && Tickdelta_legacy != 0)
|
2015-03-11 16:52:18 +00:00
|
|
|
{
|
|
|
|
*pTick += Tickdelta_legacy;
|
|
|
|
}
|
2020-09-26 19:41:58 +00:00
|
|
|
else if(Chunk & (CHUNKTICKFLAG_TICK_COMPRESSED))
|
2015-02-23 13:26:10 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
int Tickdelta = Chunk & (CHUNKMASK_TICK);
|
2015-02-23 13:26:10 +00:00
|
|
|
*pTick += Tickdelta;
|
|
|
|
}
|
|
|
|
else
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
unsigned char aTickdata[4];
|
|
|
|
if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
|
|
|
|
return -1;
|
2021-11-08 19:21:02 +00:00
|
|
|
*pTick = bytes_be_to_int(aTickdata);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// decode normal chunk
|
2020-09-26 19:41:58 +00:00
|
|
|
*pType = (Chunk & CHUNKMASK_TYPE) >> 5;
|
|
|
|
*pSize = Chunk & CHUNKMASK_SIZE;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(*pSize == 30)
|
|
|
|
{
|
|
|
|
unsigned char aSizedata[1];
|
|
|
|
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
|
|
|
|
return -1;
|
|
|
|
*pSize = aSizedata[0];
|
|
|
|
}
|
|
|
|
else if(*pSize == 31)
|
|
|
|
{
|
|
|
|
unsigned char aSizedata[2];
|
|
|
|
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
|
|
|
|
return -1;
|
2020-09-26 19:41:58 +00:00
|
|
|
*pSize = (aSizedata[1] << 8) | aSizedata[0];
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::ScanFile()
|
|
|
|
{
|
|
|
|
long StartPos;
|
|
|
|
CHeap Heap;
|
|
|
|
CKeyFrameSearch *pFirstKey = 0;
|
|
|
|
CKeyFrameSearch *pCurrentKey = 0;
|
|
|
|
//DEMOREC_CHUNK chunk;
|
|
|
|
int ChunkSize, ChunkType, ChunkTick = 0;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
StartPos = io_tell(m_File);
|
|
|
|
m_Info.m_SeekablePoints = 0;
|
|
|
|
|
2022-02-14 23:12:52 +00:00
|
|
|
while(true)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
long CurrentPos = io_tell(m_File);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
|
|
|
|
break;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// read the chunk
|
2020-09-26 19:41:58 +00:00
|
|
|
if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(ChunkType & CHUNKTICKFLAG_KEYFRAME)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
CKeyFrameSearch *pKey;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// save the position
|
|
|
|
pKey = (CKeyFrameSearch *)Heap.Allocate(sizeof(CKeyFrameSearch));
|
|
|
|
pKey->m_Frame.m_Filepos = CurrentPos;
|
|
|
|
pKey->m_Frame.m_Tick = ChunkTick;
|
|
|
|
pKey->m_pNext = 0;
|
|
|
|
if(pCurrentKey)
|
|
|
|
pCurrentKey->m_pNext = pKey;
|
|
|
|
if(!pFirstKey)
|
|
|
|
pFirstKey = pKey;
|
|
|
|
pCurrentKey = pKey;
|
|
|
|
m_Info.m_SeekablePoints++;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(m_Info.m_Info.m_FirstTick == -1)
|
|
|
|
m_Info.m_Info.m_FirstTick = ChunkTick;
|
|
|
|
m_Info.m_Info.m_LastTick = ChunkTick;
|
|
|
|
}
|
|
|
|
else if(ChunkSize)
|
|
|
|
io_skip(m_File, ChunkSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy all the frames to an array instead for fast access
|
2021-03-07 10:14:37 +00:00
|
|
|
m_pKeyFrames = (CKeyFrame *)calloc(maximum(m_Info.m_SeekablePoints, 1), sizeof(CKeyFrame));
|
2020-12-08 11:29:10 +00:00
|
|
|
for(pCurrentKey = pFirstKey, i = 0; pCurrentKey; pCurrentKey = pCurrentKey->m_pNext, i++)
|
|
|
|
m_pKeyFrames[i] = pCurrentKey->m_Frame;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// destroy the temporary heap and seek back to the start
|
|
|
|
io_seek(m_File, StartPos, IOSEEK_START);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::DoTick()
|
|
|
|
{
|
2020-10-27 17:57:14 +00:00
|
|
|
static char s_aCompresseddata[CSnapshot::MAX_SIZE];
|
|
|
|
static char s_aDecompressed[CSnapshot::MAX_SIZE];
|
|
|
|
static char s_aData[CSnapshot::MAX_SIZE];
|
2010-05-29 07:25:38 +00:00
|
|
|
int ChunkType, ChunkTick, ChunkSize;
|
2010-06-09 16:24:38 +00:00
|
|
|
int DataSize = 0;
|
2010-05-29 07:25:38 +00:00
|
|
|
int GotSnapshot = 0;
|
|
|
|
|
|
|
|
// update ticks
|
|
|
|
m_Info.m_PreviousTick = m_Info.m_Info.m_CurrentTick;
|
|
|
|
m_Info.m_Info.m_CurrentTick = m_Info.m_NextTick;
|
|
|
|
ChunkTick = m_Info.m_Info.m_CurrentTick;
|
|
|
|
|
2021-07-19 18:14:12 +00:00
|
|
|
int64_t Freq = time_freq();
|
|
|
|
int64_t CurtickStart = (m_Info.m_Info.m_CurrentTick) * Freq / SERVER_TICK_SPEED;
|
|
|
|
int64_t PrevtickStart = (m_Info.m_PreviousTick) * Freq / SERVER_TICK_SPEED;
|
|
|
|
m_Info.m_IntraTick = (m_Info.m_CurrentTime - PrevtickStart) / (float)(CurtickStart - PrevtickStart);
|
|
|
|
m_Info.m_IntraTickSincePrev = (m_Info.m_CurrentTime - PrevtickStart) / (float)(Freq / SERVER_TICK_SPEED);
|
|
|
|
m_Info.m_TickTime = (m_Info.m_CurrentTime - PrevtickStart) / (float)Freq;
|
|
|
|
if(m_UpdateIntraTimesFunc)
|
|
|
|
m_UpdateIntraTimesFunc();
|
|
|
|
|
2022-02-14 23:12:52 +00:00
|
|
|
while(true)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
|
|
|
|
{
|
|
|
|
// stop on error or eof
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "end of file");
|
2020-09-26 19:41:58 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
|
|
|
if(IVideo::Current())
|
|
|
|
Stop();
|
|
|
|
#endif
|
2010-10-31 17:51:05 +00:00
|
|
|
if(m_Info.m_PreviousTick == -1)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "empty demo");
|
2010-10-31 17:51:05 +00:00
|
|
|
Stop();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
Pause();
|
2010-05-29 07:25:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// read the chunk
|
|
|
|
if(ChunkSize)
|
|
|
|
{
|
2020-10-27 17:57:14 +00:00
|
|
|
if(io_read(m_File, s_aCompresseddata, ChunkSize) != (unsigned)ChunkSize)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
// stop on error or eof
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error reading chunk");
|
2010-05-29 07:25:38 +00:00
|
|
|
Stop();
|
|
|
|
break;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
DataSize = CNetBase::Decompress(s_aCompresseddata, ChunkSize, s_aDecompressed, sizeof(s_aDecompressed));
|
2010-05-29 07:25:38 +00:00
|
|
|
if(DataSize < 0)
|
|
|
|
{
|
|
|
|
// stop on error or eof
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error during network decompression");
|
2010-05-29 07:25:38 +00:00
|
|
|
Stop();
|
|
|
|
break;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
DataSize = CVariableInt::Decompress(s_aDecompressed, DataSize, s_aData, sizeof(s_aData));
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(DataSize < 0)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error during intpack decompression");
|
2010-05-29 07:25:38 +00:00
|
|
|
Stop();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(ChunkType == CHUNKTYPE_DELTA)
|
|
|
|
{
|
|
|
|
// process delta snapshot
|
2020-10-27 17:57:14 +00:00
|
|
|
static char s_aNewsnap[CSnapshot::MAX_SIZE];
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
GotSnapshot = 1;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, (CSnapshot *)s_aNewsnap, s_aData, DataSize);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(DataSize >= 0)
|
|
|
|
{
|
2016-04-27 15:05:30 +00:00
|
|
|
if(m_pListener)
|
2020-10-27 17:57:14 +00:00
|
|
|
m_pListener->OnDemoPlayerSnapshot(s_aNewsnap, DataSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
m_LastSnapshotDataSize = DataSize;
|
2020-10-27 17:57:14 +00:00
|
|
|
mem_copy(m_aLastSnapshotData, s_aNewsnap, DataSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else
|
2010-08-17 22:06:00 +00:00
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "error during unpacking of delta, err=%d", DataSize);
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
|
|
|
|
}
|
2010-08-17 22:06:00 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else if(ChunkType == CHUNKTYPE_SNAPSHOT)
|
|
|
|
{
|
|
|
|
// process full snapshot
|
|
|
|
GotSnapshot = 1;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_LastSnapshotDataSize = DataSize;
|
2020-10-27 17:57:14 +00:00
|
|
|
mem_copy(m_aLastSnapshotData, s_aData, DataSize);
|
2016-04-27 15:05:30 +00:00
|
|
|
if(m_pListener)
|
2020-10-27 17:57:14 +00:00
|
|
|
m_pListener->OnDemoPlayerSnapshot(s_aData, DataSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// if there were no snapshots in this tick, replay the last one
|
2016-04-27 15:05:30 +00:00
|
|
|
if(!GotSnapshot && m_pListener && m_LastSnapshotDataSize != -1)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
GotSnapshot = 1;
|
2016-04-27 15:05:30 +00:00
|
|
|
m_pListener->OnDemoPlayerSnapshot(m_aLastSnapshotData, m_LastSnapshotDataSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// check the remaining types
|
2020-09-26 19:41:58 +00:00
|
|
|
if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
m_Info.m_NextTick = ChunkTick;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if(ChunkType == CHUNKTYPE_MESSAGE)
|
|
|
|
{
|
2016-04-27 15:05:30 +00:00
|
|
|
if(m_pListener)
|
2020-10-27 17:57:14 +00:00
|
|
|
m_pListener->OnDemoPlayerMessage(s_aData, DataSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::Pause()
|
|
|
|
{
|
2022-02-14 23:12:52 +00:00
|
|
|
m_Info.m_Info.m_Paused = true;
|
2020-02-27 12:32:42 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-02-27 10:50:18 +00:00
|
|
|
if(IVideo::Current() && g_Config.m_ClVideoPauseWithDemo)
|
2020-06-22 21:59:37 +00:00
|
|
|
IVideo::Current()->Pause(true);
|
2020-02-27 12:32:42 +00:00
|
|
|
#endif
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::Unpause()
|
|
|
|
{
|
|
|
|
if(m_Info.m_Info.m_Paused)
|
|
|
|
{
|
|
|
|
/*m_Info.start_tick = m_Info.current_tick;
|
|
|
|
m_Info.start_time = time_get();*/
|
2022-02-14 23:12:52 +00:00
|
|
|
m_Info.m_Info.m_Paused = false;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2020-02-27 12:32:42 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-02-27 10:50:18 +00:00
|
|
|
if(IVideo::Current() && g_Config.m_ClVideoPauseWithDemo)
|
2020-06-22 21:59:37 +00:00
|
|
|
IVideo::Current()->Pause(false);
|
2020-02-27 12:32:42 +00:00
|
|
|
#endif
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2010-10-06 21:07:35 +00:00
|
|
|
int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2010-08-17 22:06:00 +00:00
|
|
|
m_pConsole = pConsole;
|
2010-10-06 21:07:35 +00:00
|
|
|
m_File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!m_File)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "could not open '%s'", pFilename);
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
|
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-09-06 19:36:39 +00:00
|
|
|
// store the filename
|
|
|
|
str_copy(m_aFilename, pFilename, sizeof(m_aFilename));
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// clear the playback info
|
|
|
|
mem_zero(&m_Info, sizeof(m_Info));
|
|
|
|
m_Info.m_Info.m_FirstTick = -1;
|
|
|
|
m_Info.m_Info.m_LastTick = -1;
|
|
|
|
m_Info.m_NextTick = -1;
|
|
|
|
m_Info.m_Info.m_CurrentTick = -1;
|
|
|
|
m_Info.m_PreviousTick = -1;
|
|
|
|
m_Info.m_Info.m_Speed = 1;
|
2016-10-28 07:31:22 +00:00
|
|
|
m_SpeedIndex = 4;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_LastSnapshotDataSize = -1;
|
|
|
|
|
|
|
|
// read the header
|
|
|
|
io_read(m_File, &m_Info.m_Header, sizeof(m_Info.m_Header));
|
2020-10-27 17:57:14 +00:00
|
|
|
if(mem_comp(m_Info.m_Header.m_aMarker, s_aHeaderMarker, sizeof(s_aHeaderMarker)) != 0)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "'%s' is not a demo file", pFilename);
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
|
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
io_close(m_File);
|
|
|
|
m_File = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
2011-03-13 09:41:10 +00:00
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
if(m_Info.m_Header.m_Version < s_OldVersion)
|
2011-03-13 09:41:10 +00:00
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "demo version %d is not supported", m_Info.m_Header.m_Version);
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
|
|
|
|
}
|
2011-03-13 09:41:10 +00:00
|
|
|
io_close(m_File);
|
|
|
|
m_File = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
2020-10-27 17:57:14 +00:00
|
|
|
else if(m_Info.m_Header.m_Version > s_OldVersion)
|
2013-02-25 23:00:38 +00:00
|
|
|
io_read(m_File, &m_Info.m_TimelineMarkers, sizeof(m_Info.m_TimelineMarkers));
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-12-17 14:44:54 +00:00
|
|
|
SHA256_DIGEST Sha256 = SHA256_ZEROED;
|
2020-10-27 17:57:14 +00:00
|
|
|
if(m_Info.m_Header.m_Version >= s_Sha256Version)
|
2019-12-17 14:44:54 +00:00
|
|
|
{
|
|
|
|
CUuid ExtensionUuid = {};
|
|
|
|
io_read(m_File, &ExtensionUuid.m_aData, sizeof(ExtensionUuid.m_aData));
|
|
|
|
|
|
|
|
if(ExtensionUuid == SHA256_EXTENSION)
|
|
|
|
{
|
|
|
|
io_read(m_File, &Sha256, sizeof(SHA256_DIGEST)); // need a safe read
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This hopes whatever happened during the version increment didn't add something here
|
|
|
|
dbg_msg("demo", "demo version incremented, but not by ddnet");
|
|
|
|
io_seek(m_File, -(int)sizeof(ExtensionUuid.m_aData), IOSEEK_CUR);
|
|
|
|
}
|
|
|
|
}
|
2019-10-14 00:27:08 +00:00
|
|
|
|
2011-03-12 17:07:57 +00:00
|
|
|
// get demo type
|
|
|
|
if(!str_comp(m_Info.m_Header.m_aType, "client"))
|
2011-03-28 20:08:52 +00:00
|
|
|
m_DemoType = DEMOTYPE_CLIENT;
|
2011-03-12 17:07:57 +00:00
|
|
|
else if(!str_comp(m_Info.m_Header.m_aType, "server"))
|
|
|
|
m_DemoType = DEMOTYPE_SERVER;
|
2011-03-28 20:08:52 +00:00
|
|
|
else
|
|
|
|
m_DemoType = DEMOTYPE_INVALID;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-03-13 09:41:10 +00:00
|
|
|
// read map
|
2021-11-08 19:21:02 +00:00
|
|
|
unsigned MapSize = bytes_be_to_uint(m_Info.m_Header.m_aMapSize);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-03-13 09:41:10 +00:00
|
|
|
// check if we already have the map
|
|
|
|
// TODO: improve map checking (maps folder, check crc)
|
2021-11-08 19:21:02 +00:00
|
|
|
unsigned Crc = bytes_be_to_uint(m_Info.m_Header.m_aMapCrc);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-10-13 16:46:28 +00:00
|
|
|
// save byte offset of map for later use
|
|
|
|
m_MapOffset = io_tell(m_File);
|
|
|
|
io_skip(m_File, MapSize);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2018-07-10 09:29:02 +00:00
|
|
|
// store map information
|
2014-08-12 14:21:06 +00:00
|
|
|
m_MapInfo.m_Crc = Crc;
|
2019-12-17 14:44:54 +00:00
|
|
|
m_MapInfo.m_Sha256 = Sha256;
|
2014-08-12 14:21:06 +00:00
|
|
|
m_MapInfo.m_Size = MapSize;
|
|
|
|
str_copy(m_MapInfo.m_aName, m_Info.m_Header.m_aMapName, sizeof(m_MapInfo.m_aName));
|
|
|
|
|
2020-10-27 17:57:14 +00:00
|
|
|
if(m_Info.m_Header.m_Version > s_OldVersion)
|
2012-01-10 22:13:19 +00:00
|
|
|
{
|
2013-02-25 23:00:38 +00:00
|
|
|
// get timeline markers
|
2021-11-08 19:21:02 +00:00
|
|
|
int Num = bytes_be_to_int(m_Info.m_TimelineMarkers.m_aNumTimelineMarkers);
|
2019-04-26 19:36:49 +00:00
|
|
|
m_Info.m_Info.m_NumTimelineMarkers = minimum(Num, (int)MAX_TIMELINE_MARKERS);
|
2018-01-23 15:04:24 +00:00
|
|
|
for(int i = 0; i < m_Info.m_Info.m_NumTimelineMarkers; i++)
|
2013-02-25 23:00:38 +00:00
|
|
|
{
|
2021-11-08 19:21:02 +00:00
|
|
|
m_Info.m_Info.m_aTimelineMarkers[i] = bytes_be_to_int(m_Info.m_TimelineMarkers.m_aTimelineMarkers[i]);
|
2013-02-25 23:00:38 +00:00
|
|
|
}
|
2012-01-10 22:13:19 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2018-07-10 09:29:02 +00:00
|
|
|
// scan the file for interesting points
|
2010-05-29 07:25:38 +00:00
|
|
|
ScanFile();
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2014-08-13 15:32:03 +00:00
|
|
|
// reset slice markers
|
|
|
|
g_Config.m_ClDemoSliceBegin = -1;
|
|
|
|
g_Config.m_ClDemoSliceEnd = -1;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// ready for playback
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-11-15 13:31:05 +00:00
|
|
|
unsigned char *CDemoPlayer::GetMapData(class IStorage *pStorage)
|
2019-10-13 16:46:28 +00:00
|
|
|
{
|
|
|
|
if(!m_MapInfo.m_Size)
|
2021-11-15 13:31:05 +00:00
|
|
|
return 0;
|
2019-10-13 16:46:28 +00:00
|
|
|
|
|
|
|
long CurSeek = io_tell(m_File);
|
|
|
|
|
|
|
|
// get map data
|
|
|
|
io_seek(m_File, m_MapOffset, IOSEEK_START);
|
|
|
|
unsigned char *pMapData = (unsigned char *)malloc(m_MapInfo.m_Size);
|
|
|
|
io_read(m_File, pMapData, m_MapInfo.m_Size);
|
|
|
|
io_seek(m_File, CurSeek, IOSEEK_START);
|
2021-11-15 13:31:05 +00:00
|
|
|
return pMapData;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDemoPlayer::ExtractMap(class IStorage *pStorage)
|
|
|
|
{
|
|
|
|
unsigned char *pMapData = GetMapData(pStorage);
|
|
|
|
if(!pMapData)
|
|
|
|
return false;
|
2019-10-13 16:46:28 +00:00
|
|
|
|
2019-10-14 00:27:08 +00:00
|
|
|
// handle sha256
|
2019-12-17 14:44:54 +00:00
|
|
|
SHA256_DIGEST Sha256 = SHA256_ZEROED;
|
2020-10-27 17:57:14 +00:00
|
|
|
if(m_Info.m_Header.m_Version >= s_Sha256Version)
|
2019-12-17 14:44:54 +00:00
|
|
|
Sha256 = m_MapInfo.m_Sha256;
|
2019-10-14 00:27:08 +00:00
|
|
|
else
|
|
|
|
{
|
2019-12-17 14:44:54 +00:00
|
|
|
Sha256 = sha256(pMapData, m_MapInfo.m_Size);
|
|
|
|
m_MapInfo.m_Sha256 = Sha256;
|
2019-10-14 00:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// construct name
|
2019-10-13 16:46:28 +00:00
|
|
|
char aSha[SHA256_MAXSTRSIZE], aMapFilename[128];
|
2019-12-17 14:44:54 +00:00
|
|
|
sha256_str(Sha256, aSha, sizeof(aSha));
|
2020-10-10 20:06:47 +00:00
|
|
|
str_format(aMapFilename, sizeof(aMapFilename), "downloadedmaps/%s_%s.map", m_Info.m_Header.m_aMapName, aSha);
|
2019-10-13 16:46:28 +00:00
|
|
|
|
|
|
|
// save map
|
|
|
|
IOHANDLE MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
2019-12-18 12:51:08 +00:00
|
|
|
if(!MapFile)
|
|
|
|
return false;
|
|
|
|
|
2019-10-13 16:46:28 +00:00
|
|
|
io_write(MapFile, pMapData, m_MapInfo.m_Size);
|
|
|
|
io_close(MapFile);
|
|
|
|
|
|
|
|
// free data
|
|
|
|
free(pMapData);
|
2019-12-18 12:51:08 +00:00
|
|
|
return true;
|
2019-10-13 16:46:28 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
int CDemoPlayer::NextFrame()
|
|
|
|
{
|
|
|
|
DoTick();
|
|
|
|
return IsPlaying();
|
|
|
|
}
|
|
|
|
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t CDemoPlayer::time()
|
2016-08-30 23:39:59 +00:00
|
|
|
{
|
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2016-08-31 15:22:15 +00:00
|
|
|
static bool s_Recording = false;
|
2020-09-26 19:41:58 +00:00
|
|
|
if(IVideo::Current())
|
2016-08-31 15:22:15 +00:00
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
if(!s_Recording)
|
2016-08-31 15:22:15 +00:00
|
|
|
{
|
|
|
|
s_Recording = true;
|
2020-06-22 21:59:37 +00:00
|
|
|
m_Info.m_LastUpdate = IVideo::Time();
|
2016-08-31 15:22:15 +00:00
|
|
|
}
|
2020-06-22 21:59:37 +00:00
|
|
|
return IVideo::Time();
|
2016-08-31 15:22:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t Now = time_get();
|
2020-09-26 19:41:58 +00:00
|
|
|
if(s_Recording)
|
2016-08-31 15:22:15 +00:00
|
|
|
{
|
|
|
|
s_Recording = false;
|
|
|
|
m_Info.m_LastUpdate = Now;
|
|
|
|
}
|
|
|
|
return Now;
|
|
|
|
}
|
2016-08-30 23:39:59 +00:00
|
|
|
#else
|
|
|
|
return time_get();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
int CDemoPlayer::Play()
|
|
|
|
{
|
|
|
|
// fill in previous and next tick
|
|
|
|
while(m_Info.m_PreviousTick == -1 && IsPlaying())
|
|
|
|
DoTick();
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// set start info
|
|
|
|
/*m_Info.start_tick = m_Info.previous_tick;
|
|
|
|
m_Info.start_time = time_get();*/
|
2020-09-26 19:41:58 +00:00
|
|
|
m_Info.m_CurrentTime = m_Info.m_PreviousTick * time_freq() / SERVER_TICK_SPEED;
|
2016-08-30 23:39:59 +00:00
|
|
|
m_Info.m_LastUpdate = time();
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-15 18:39:39 +00:00
|
|
|
int CDemoPlayer::SeekPercent(float Percent)
|
|
|
|
{
|
|
|
|
int WantedTick = m_Info.m_Info.m_FirstTick + ((m_Info.m_Info.m_LastTick - m_Info.m_Info.m_FirstTick) * Percent);
|
|
|
|
return SetPos(WantedTick);
|
|
|
|
}
|
|
|
|
|
|
|
|
int CDemoPlayer::SeekTime(float Seconds)
|
|
|
|
{
|
2022-02-14 16:36:54 +00:00
|
|
|
int WantedTick = m_Info.m_Info.m_CurrentTick + (Seconds * (float)SERVER_TICK_SPEED);
|
2019-04-15 18:39:39 +00:00
|
|
|
return SetPos(WantedTick);
|
|
|
|
}
|
|
|
|
|
|
|
|
int CDemoPlayer::SetPos(int WantedTick)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
if(!m_File)
|
|
|
|
return -1;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// -5 because we have to have a current tick and previous tick when we do the playback
|
2019-04-15 18:39:39 +00:00
|
|
|
WantedTick = clamp(WantedTick, m_Info.m_Info.m_FirstTick, m_Info.m_Info.m_LastTick) - 5;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// get correct key frame
|
2019-04-15 18:39:39 +00:00
|
|
|
int KeyFrame = 0;
|
|
|
|
while(KeyFrame < m_Info.m_SeekablePoints - 1 && m_pKeyFrames[KeyFrame].m_Tick < WantedTick)
|
|
|
|
{
|
|
|
|
KeyFrame++;
|
|
|
|
}
|
|
|
|
while(KeyFrame > 0 && m_pKeyFrames[KeyFrame].m_Tick > WantedTick)
|
|
|
|
{
|
|
|
|
KeyFrame--;
|
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-04-15 18:39:39 +00:00
|
|
|
// seek to the correct key frame
|
|
|
|
io_seek(m_File, m_pKeyFrames[KeyFrame].m_Filepos, IOSEEK_START);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
m_Info.m_NextTick = -1;
|
|
|
|
m_Info.m_Info.m_CurrentTick = -1;
|
|
|
|
m_Info.m_PreviousTick = -1;
|
|
|
|
|
|
|
|
// playback everything until we hit our tick
|
2014-08-31 01:13:42 +00:00
|
|
|
while(m_Info.m_PreviousTick < WantedTick && IsPlaying())
|
2010-05-29 07:25:38 +00:00
|
|
|
DoTick();
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
Play();
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoPlayer::SetSpeed(float Speed)
|
|
|
|
{
|
2019-04-02 21:16:01 +00:00
|
|
|
m_Info.m_Info.m_Speed = clamp(Speed, 0.f, 256.f);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2016-10-28 07:31:22 +00:00
|
|
|
void CDemoPlayer::SetSpeedIndex(int Offset)
|
|
|
|
{
|
2022-03-30 13:16:19 +00:00
|
|
|
m_SpeedIndex = clamp(m_SpeedIndex + Offset, 0, (int)(std::size(g_aSpeeds) - 1));
|
2016-10-28 07:31:22 +00:00
|
|
|
SetSpeed(g_aSpeeds[m_SpeedIndex]);
|
|
|
|
}
|
|
|
|
|
2014-08-12 14:21:06 +00:00
|
|
|
int CDemoPlayer::Update(bool RealTime)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t Now = time();
|
|
|
|
int64_t Deltatime = Now - m_Info.m_LastUpdate;
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Info.m_LastUpdate = Now;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!IsPlaying())
|
|
|
|
return 0;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(m_Info.m_Info.m_Paused)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t Freq = time_freq();
|
|
|
|
m_Info.m_CurrentTime += (int64_t)(Deltatime * (double)m_Info.m_Info.m_Speed);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2022-02-14 23:12:52 +00:00
|
|
|
while(true)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t CurtickStart = (m_Info.m_Info.m_CurrentTick) * Freq / SERVER_TICK_SPEED;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
// break if we are ready
|
2014-08-12 14:21:06 +00:00
|
|
|
if(RealTime && CurtickStart > m_Info.m_CurrentTime)
|
2010-05-29 07:25:38 +00:00
|
|
|
break;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// do one more tick
|
|
|
|
DoTick();
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(m_Info.m_Info.m_Paused)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// update intratick
|
2011-04-13 18:37:12 +00:00
|
|
|
{
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t CurtickStart = (m_Info.m_Info.m_CurrentTick) * Freq / SERVER_TICK_SPEED;
|
|
|
|
int64_t PrevtickStart = (m_Info.m_PreviousTick) * Freq / SERVER_TICK_SPEED;
|
2020-09-26 19:41:58 +00:00
|
|
|
m_Info.m_IntraTick = (m_Info.m_CurrentTime - PrevtickStart) / (float)(CurtickStart - PrevtickStart);
|
2021-07-19 18:14:12 +00:00
|
|
|
m_Info.m_IntraTickSincePrev = (m_Info.m_CurrentTime - PrevtickStart) / (float)(Freq / SERVER_TICK_SPEED);
|
2010-05-29 07:25:38 +00:00
|
|
|
m_Info.m_TickTime = (m_Info.m_CurrentTime - PrevtickStart) / (float)Freq;
|
2021-07-19 18:14:12 +00:00
|
|
|
if(m_UpdateIntraTimesFunc)
|
|
|
|
m_UpdateIntraTimesFunc();
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(m_Info.m_Info.m_CurrentTick == m_Info.m_PreviousTick ||
|
|
|
|
m_Info.m_Info.m_CurrentTick == m_Info.m_NextTick)
|
|
|
|
{
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
{
|
|
|
|
char aBuf[256];
|
|
|
|
str_format(aBuf, sizeof(aBuf), "tick error prev=%d cur=%d next=%d",
|
|
|
|
m_Info.m_PreviousTick, m_Info.m_Info.m_CurrentTick, m_Info.m_NextTick);
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
|
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CDemoPlayer::Stop()
|
|
|
|
{
|
2016-08-30 23:39:59 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-09-26 19:41:58 +00:00
|
|
|
if(IVideo::Current())
|
|
|
|
IVideo::Current()->Stop();
|
2016-08-30 23:39:59 +00:00
|
|
|
#endif
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!m_File)
|
|
|
|
return -1;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2019-05-31 18:42:28 +00:00
|
|
|
if(m_pConsole)
|
|
|
|
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "Stopped playback");
|
2010-05-29 07:25:38 +00:00
|
|
|
io_close(m_File);
|
|
|
|
m_File = 0;
|
2020-12-08 11:29:10 +00:00
|
|
|
free(m_pKeyFrames);
|
|
|
|
m_pKeyFrames = 0;
|
2010-09-06 19:36:39 +00:00
|
|
|
str_copy(m_aFilename, "", sizeof(m_aFilename));
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-06-09 20:44:22 +00:00
|
|
|
void CDemoPlayer::GetDemoName(char *pBuffer, int BufferSize) const
|
2010-09-06 19:36:39 +00:00
|
|
|
{
|
2011-06-09 20:44:22 +00:00
|
|
|
const char *pFileName = m_aFilename;
|
|
|
|
const char *pExtractedName = pFileName;
|
|
|
|
const char *pEnd = 0;
|
|
|
|
for(; *pFileName; ++pFileName)
|
2010-09-06 19:36:39 +00:00
|
|
|
{
|
2011-06-09 20:44:22 +00:00
|
|
|
if(*pFileName == '/' || *pFileName == '\\')
|
2020-09-26 19:41:58 +00:00
|
|
|
pExtractedName = pFileName + 1;
|
2011-06-09 20:44:22 +00:00
|
|
|
else if(*pFileName == '.')
|
|
|
|
pEnd = pFileName;
|
2010-09-06 19:36:39 +00:00
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
int Length = pEnd > pExtractedName ? minimum(BufferSize, (int)(pEnd - pExtractedName + 1)) : BufferSize;
|
2011-06-09 20:44:22 +00:00
|
|
|
str_copy(pBuffer, pExtractedName, Length);
|
2010-09-06 19:36:39 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2019-10-14 00:27:08 +00:00
|
|
|
bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo) const
|
2010-10-09 11:27:21 +00:00
|
|
|
{
|
2019-10-14 00:27:08 +00:00
|
|
|
if(!pDemoHeader || !pTimelineMarkers || !pMapInfo)
|
2011-03-13 09:41:10 +00:00
|
|
|
return false;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-03-13 09:41:10 +00:00
|
|
|
mem_zero(pDemoHeader, sizeof(CDemoHeader));
|
2019-01-08 19:12:21 +00:00
|
|
|
mem_zero(pTimelineMarkers, sizeof(CTimelineMarkers));
|
2011-03-13 09:41:10 +00:00
|
|
|
|
2010-10-09 11:27:21 +00:00
|
|
|
IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
|
|
|
|
if(!File)
|
|
|
|
return false;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2011-03-13 09:41:10 +00:00
|
|
|
io_read(File, pDemoHeader, sizeof(CDemoHeader));
|
2019-01-08 19:12:21 +00:00
|
|
|
io_read(File, pTimelineMarkers, sizeof(CTimelineMarkers));
|
2019-10-14 00:27:08 +00:00
|
|
|
|
|
|
|
str_copy(pMapInfo->m_aName, pDemoHeader->m_aMapName, sizeof(pMapInfo->m_aName));
|
2021-11-08 19:21:02 +00:00
|
|
|
pMapInfo->m_Crc = bytes_be_to_int(pDemoHeader->m_aMapCrc);
|
2019-10-14 00:27:08 +00:00
|
|
|
|
2019-12-22 16:09:14 +00:00
|
|
|
SHA256_DIGEST Sha256 = SHA256_ZEROED;
|
2020-10-27 17:57:14 +00:00
|
|
|
if(pDemoHeader->m_Version >= s_Sha256Version)
|
2019-12-22 16:09:14 +00:00
|
|
|
{
|
|
|
|
CUuid ExtensionUuid = {};
|
|
|
|
io_read(File, &ExtensionUuid.m_aData, sizeof(ExtensionUuid.m_aData));
|
|
|
|
|
|
|
|
if(ExtensionUuid == SHA256_EXTENSION)
|
|
|
|
{
|
|
|
|
io_read(File, &Sha256, sizeof(SHA256_DIGEST)); // need a safe read
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This hopes whatever happened during the version increment didn't add something here
|
|
|
|
dbg_msg("demo", "demo version incremented, but not by ddnet");
|
|
|
|
io_seek(File, -(int)sizeof(ExtensionUuid.m_aData), IOSEEK_CUR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pMapInfo->m_Sha256 = Sha256;
|
2019-10-14 00:27:08 +00:00
|
|
|
|
2021-11-08 19:21:02 +00:00
|
|
|
pMapInfo->m_Size = bytes_be_to_int(pDemoHeader->m_aMapSize);
|
2019-10-14 00:27:08 +00:00
|
|
|
|
2010-10-09 11:27:21 +00:00
|
|
|
io_close(File);
|
2020-10-27 17:57:14 +00:00
|
|
|
return !(mem_comp(pDemoHeader->m_aMarker, s_aHeaderMarker, sizeof(s_aHeaderMarker)) || pDemoHeader->m_Version < s_OldVersion);
|
2010-10-09 11:27:21 +00:00
|
|
|
}
|
2011-03-12 17:07:57 +00:00
|
|
|
|
|
|
|
int CDemoPlayer::GetDemoType() const
|
|
|
|
{
|
|
|
|
if(m_File)
|
|
|
|
return m_DemoType;
|
|
|
|
return DEMOTYPE_INVALID;
|
|
|
|
}
|
2014-08-12 14:21:06 +00:00
|
|
|
|
|
|
|
void CDemoEditor::Init(const char *pNetVersion, class CSnapshotDelta *pSnapshotDelta, class IConsole *pConsole, class IStorage *pStorage)
|
|
|
|
{
|
|
|
|
m_pNetVersion = pNetVersion;
|
|
|
|
m_pSnapshotDelta = pSnapshotDelta;
|
|
|
|
m_pConsole = pConsole;
|
|
|
|
m_pStorage = pStorage;
|
|
|
|
}
|
|
|
|
|
2017-02-28 09:08:14 +00:00
|
|
|
void CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int EndTick, DEMOFUNC_FILTER pfnFilter, void *pUser)
|
2014-08-12 14:21:06 +00:00
|
|
|
{
|
|
|
|
class CDemoPlayer DemoPlayer(m_pSnapshotDelta);
|
|
|
|
class CDemoRecorder DemoRecorder(m_pSnapshotDelta);
|
|
|
|
|
|
|
|
m_pDemoPlayer = &DemoPlayer;
|
|
|
|
m_pDemoRecorder = &DemoRecorder;
|
|
|
|
|
2016-04-27 15:05:30 +00:00
|
|
|
m_pDemoPlayer->SetListener(this);
|
2014-08-12 14:21:06 +00:00
|
|
|
|
2015-07-09 00:08:14 +00:00
|
|
|
m_SliceFrom = StartTick;
|
2014-08-12 14:21:06 +00:00
|
|
|
m_SliceTo = EndTick;
|
|
|
|
m_Stop = false;
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_pDemoPlayer->Load(m_pStorage, m_pConsole, pDemo, IStorage::TYPE_ALL) == -1)
|
2014-08-12 14:21:06 +00:00
|
|
|
return;
|
2015-07-09 00:08:14 +00:00
|
|
|
|
2019-10-14 00:27:08 +00:00
|
|
|
const CMapInfo *pMapInfo = m_pDemoPlayer->GetMapInfo();
|
2019-12-22 16:09:14 +00:00
|
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_pDemoPlayer->Info();
|
|
|
|
|
|
|
|
SHA256_DIGEST Sha256 = pMapInfo->m_Sha256;
|
2020-10-27 17:57:14 +00:00
|
|
|
if(pInfo->m_Header.m_Version < s_Sha256Version)
|
2018-06-05 19:22:40 +00:00
|
|
|
{
|
2019-12-22 16:09:14 +00:00
|
|
|
if(m_pDemoPlayer->ExtractMap(m_pStorage))
|
|
|
|
Sha256 = pMapInfo->m_Sha256;
|
2018-06-05 19:22:40 +00:00
|
|
|
}
|
2019-12-22 16:09:14 +00:00
|
|
|
|
2021-11-15 13:31:05 +00:00
|
|
|
unsigned char *pMapData = m_pDemoPlayer->GetMapData(m_pStorage);
|
|
|
|
const int Result = m_pDemoRecorder->Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, &Sha256, pMapInfo->m_Crc, "client", pMapInfo->m_Size, pMapData, NULL, pfnFilter, pUser) == -1;
|
2022-03-01 22:19:49 +00:00
|
|
|
free(pMapData);
|
2021-11-15 13:31:05 +00:00
|
|
|
if(Result != 0)
|
2014-08-12 14:21:06 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
m_pDemoPlayer->Play();
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
while(m_pDemoPlayer->IsPlaying() && !m_Stop)
|
|
|
|
{
|
2014-08-12 14:21:06 +00:00
|
|
|
m_pDemoPlayer->Update(false);
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(pInfo->m_Info.m_Paused)
|
2014-08-12 14:21:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-05-20 21:55:40 +00:00
|
|
|
m_pDemoPlayer->Stop();
|
2014-08-12 14:21:06 +00:00
|
|
|
m_pDemoRecorder->Stop();
|
2020-12-22 11:17:50 +00:00
|
|
|
} // NOLINT(clang-analyzer-unix.Malloc)
|
2014-08-12 14:21:06 +00:00
|
|
|
|
|
|
|
void CDemoEditor::OnDemoPlayerSnapshot(void *pData, int Size)
|
|
|
|
{
|
|
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_pDemoPlayer->Info();
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_SliceTo != -1 && pInfo->m_Info.m_CurrentTick > m_SliceTo)
|
2014-08-12 14:21:06 +00:00
|
|
|
m_Stop = true;
|
2020-09-26 19:41:58 +00:00
|
|
|
else if(m_SliceFrom == -1 || pInfo->m_Info.m_CurrentTick >= m_SliceFrom)
|
2014-08-12 14:21:06 +00:00
|
|
|
m_pDemoRecorder->RecordSnapshot(pInfo->m_Info.m_CurrentTick, pData, Size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDemoEditor::OnDemoPlayerMessage(void *pData, int Size)
|
|
|
|
{
|
|
|
|
const CDemoPlayer::CPlaybackInfo *pInfo = m_pDemoPlayer->Info();
|
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_SliceTo != -1 && pInfo->m_Info.m_CurrentTick > m_SliceTo)
|
2014-08-12 14:21:06 +00:00
|
|
|
m_Stop = true;
|
2020-09-26 19:41:58 +00:00
|
|
|
else if(m_SliceFrom == -1 || pInfo->m_Info.m_CurrentTick >= m_SliceFrom)
|
2014-08-12 14:21:06 +00:00
|
|
|
m_pDemoRecorder->RecordMessage(pData, Size);
|
|
|
|
}
|