ddnet/src/engine/shared/demo.cpp

1224 lines
32 KiB
C++
Raw Normal View History

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. */
#include <base/math.h>
2010-05-29 07:25:38 +00:00
#include <base/system.h>
#include <engine/console.h>
2010-05-29 07:25:38 +00:00
#include <engine/storage.h>
#include <engine/shared/config.h>
#if defined(CONF_VIDEORECORDER)
#include <engine/shared/video.h>
#endif
#include "compression.h"
2010-09-12 10:16:51 +00:00
#include "demo.h"
2010-05-29 07:25:38 +00:00
#include "network.h"
#include "snapshot.h"
2010-05-29 07:25:38 +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}};
static const unsigned char gs_CurVersion = 6;
static const unsigned char gs_OldVersion = 3;
static const unsigned char gs_Sha256Version = 6;
static const unsigned char gs_VersionTickCompression = 5; // demo files with this version or higher will use `CHUNKTICKFLAG_TICK_COMPRESSED`
static const int gs_LengthOffset = 152;
static const int gs_NumMarkersOffset = 176;
2021-10-23 23:54:18 +00:00
static const ColorRGBA gs_DemoPrintColor{0.75f, 0.7f, 0.7f, 1.0f};
CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData)
2010-05-29 07:25:38 +00:00
{
m_File = 0;
m_aCurrentFilename[0] = '\0';
m_pfnFilter = nullptr;
m_pUser = nullptr;
2010-05-29 07:25:38 +00:00
m_LastTickMarker = -1;
m_pSnapshotDelta = pSnapshotDelta;
m_NoMapData = NoMapData;
2010-05-29 07:25:38 +00:00
}
CDemoRecorder::~CDemoRecorder()
{
dbg_assert(m_File == 0, "Demo recorder was not stopped");
}
2010-05-29 07:25:38 +00:00
// Record
int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, const SHA256_DIGEST &Sha256, 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
{
dbg_assert(m_File == 0, "Demo recorder already recording");
m_pfnFilter = pfnFilter;
m_pUser = pUser;
m_pMapData = pMapData;
m_pConsole = pConsole;
IOHANDLE DemoFile = pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
if(!DemoFile)
{
if(m_pConsole)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for recording", pFilename);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
}
return -1;
}
bool CloseMapFile = false;
2010-09-12 14:56:13 +00:00
if(MapFile)
io_seek(MapFile, 0, IOSEEK_START);
2020-01-03 09:12:37 +00:00
char aSha256[SHA256_MAXSTRSIZE];
sha256_str(Sha256, aSha256, sizeof(aSha256));
2020-01-03 09:12:37 +00:00
if(!pMapData && !MapFile)
2010-05-29 07:25:38 +00:00
{
// open mapfile
char aMapFilename[IO_MAX_PATH_LENGTH];
// try the downloaded maps
str_format(aMapFilename, sizeof(aMapFilename), "downloadedmaps/%s_%s.map", pMap, aSha256);
MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_READ, IStorage::TYPE_ALL);
if(!MapFile)
{
// try the normal maps folder
str_format(aMapFilename, sizeof(aMapFilename), "maps/%s.map", pMap);
MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_READ, IStorage::TYPE_ALL);
}
if(!MapFile)
{
// search for the map within subfolders
char aBuf[IO_MAX_PATH_LENGTH];
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)
{
if(m_pConsole)
{
char aBuf[32 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "Unable to open mapfile '%s'", pMap);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
}
return -1;
}
CloseMapFile = true;
2010-05-29 07:25:38 +00:00
}
if(m_NoMapData)
MapSize = 0;
else if(MapFile)
2020-02-19 12:37:31 +00:00
MapSize = io_length(MapFile);
2010-05-29 07:25:38 +00:00
// write header
CDemoHeader Header;
2010-05-29 07:25:38 +00:00
mem_zero(&Header, sizeof(Header));
mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker));
Header.m_Version = gs_CurVersion;
2022-07-09 16:14:56 +00:00
str_copy(Header.m_aNetversion, pNetVersion);
str_copy(Header.m_aMapName, pMap);
uint_to_bytes_be(Header.m_aMapSize, MapSize);
uint_to_bytes_be(Header.m_aMapCrc, Crc);
2022-07-09 16:14:56 +00:00
str_copy(Header.m_aType, pType);
// Header.m_Length - add this on stop
str_timestamp(Header.m_aTimestamp, sizeof(Header.m_aTimestamp));
io_write(DemoFile, &Header, sizeof(Header));
CTimelineMarkers TimelineMarkers;
mem_zero(&TimelineMarkers, sizeof(TimelineMarkers));
io_write(DemoFile, &TimelineMarkers, sizeof(TimelineMarkers)); // fill this on stop
// Write Sha256
2019-12-17 14:44:54 +00:00
io_write(DemoFile, SHA256_EXTENSION.m_aData, sizeof(SHA256_EXTENSION.m_aData));
io_write(DemoFile, &Sha256, sizeof(SHA256_DIGEST));
if(m_NoMapData)
{
}
else if(pMapData)
2010-09-12 14:56:13 +00:00
{
io_write(DemoFile, pMapData, MapSize);
}
else
{
// write map data
2022-02-14 23:12:52 +00:00
while(true)
{
unsigned char aChunk[1024 * 64];
mem_zero(aChunk, sizeof(aChunk));
int Bytes = io_read(MapFile, &aChunk, sizeof(aChunk));
if(Bytes <= 0)
break;
io_write(DemoFile, &aChunk, Bytes);
}
if(CloseMapFile)
io_close(MapFile);
else
io_seek(MapFile, 0, IOSEEK_START);
}
2010-05-29 07:25:38 +00:00
m_LastKeyFrame = -1;
m_LastTickMarker = -1;
m_FirstTick = -1;
m_NumTimelineMarkers = 0;
if(m_pConsole)
{
char aBuf[32 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "Recording to '%s'", pFilename);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor);
}
m_File = DemoFile;
2022-07-09 16:14:56 +00:00
str_copy(m_aCurrentFilename, pFilename);
2010-05-29 07:25:38 +00:00
return 0;
}
/*
Tickmarker
7 = Always set
6 = Keyframe flag
0-5 = Delta tick
2010-05-29 07:25:38 +00:00
Normal
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
CHUNKTICKFLAG_TICK_COMPRESSED = 0x20, // when we store the tick value in the first chunk
CHUNKMASK_TICK = 0x1f,
CHUNKMASK_TICK_LEGACY = 0x3f,
2010-05-29 07:25:38 +00:00
CHUNKMASK_TYPE = 0x60,
CHUNKMASK_SIZE = 0x1f,
2010-05-29 07:25:38 +00:00
CHUNKTYPE_SNAPSHOT = 1,
CHUNKTYPE_MESSAGE = 2,
CHUNKTYPE_DELTA = 3,
};
2023-10-15 13:00:59 +00:00
void CDemoRecorder::WriteTickMarker(int Tick, bool Keyframe)
2010-05-29 07:25:38 +00:00
{
if(m_LastTickMarker == -1 || Tick - m_LastTickMarker > CHUNKMASK_TICK || Keyframe)
2010-05-29 07:25:38 +00:00
{
2023-02-04 00:22:49 +00:00
unsigned char aChunk[sizeof(int32_t) + 1];
2010-05-29 07:25:38 +00:00
aChunk[0] = CHUNKTYPEFLAG_TICKMARKER;
uint_to_bytes_be(aChunk + 1, Tick);
2010-05-29 07:25:38 +00:00
if(Keyframe)
aChunk[0] |= CHUNKTICKFLAG_KEYFRAME;
2010-05-29 07:25:38 +00:00
io_write(m_File, aChunk, sizeof(aChunk));
}
else
{
unsigned char aChunk[1];
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));
}
2010-05-29 07:25:38 +00:00
m_LastTickMarker = Tick;
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)
{
if(!m_File)
return;
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 */
char aBuffer[64 * 1024];
char aBuffer2[64 * 1024];
2010-05-29 07:25:38 +00:00
mem_copy(aBuffer2, pData, Size);
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;
unsigned char aChunk[3];
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;
aChunk[1] = Size & 0xff;
2010-05-29 07:25:38 +00:00
io_write(m_File, aChunk, 2);
}
else
{
aChunk[0] |= 31;
aChunk[1] = Size & 0xff;
aChunk[2] = Size >> 8;
2010-05-29 07:25:38 +00:00
io_write(m_File, aChunk, 3);
}
}
2010-05-29 07:25:38 +00:00
io_write(m_File, aBuffer2, Size);
}
void CDemoRecorder::RecordSnapshot(int Tick, const void *pData, int Size)
{
if(m_LastKeyFrame == -1 || (Tick - m_LastKeyFrame) > SERVER_TICK_SPEED * 5)
2010-05-29 07:25:38 +00:00
{
// write full tickmarker
2023-10-15 13:00:59 +00:00
WriteTickMarker(Tick, true);
2010-05-29 07:25:38 +00:00
// write snapshot
Write(CHUNKTYPE_SNAPSHOT, pData, Size);
2010-05-29 07:25:38 +00:00
m_LastKeyFrame = Tick;
mem_copy(m_aLastSnapshotData, pData, Size);
}
else
{
// write tickmarker
2023-10-15 13:00:59 +00:00
WriteTickMarker(Tick, false);
// create delta
char aDeltaData[CSnapshot::MAX_SIZE + sizeof(int)];
const int 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)
{
if(m_pfnFilter)
{
if(m_pfnFilter(pData, Size, m_pUser))
{
return;
}
}
2010-05-29 07:25:38 +00:00
Write(CHUNKTYPE_MESSAGE, pData, Size);
}
int CDemoRecorder::Stop()
2010-05-29 07:25:38 +00:00
{
if(!m_File)
return -1;
// add the demo length to the header
io_seek(m_File, gs_LengthOffset, IOSEEK_START);
2023-02-04 00:22:49 +00:00
unsigned char aLength[sizeof(int32_t)];
uint_to_bytes_be(aLength, Length());
io_write(m_File, aLength, sizeof(aLength));
// add the timeline markers to the header
io_seek(m_File, gs_NumMarkersOffset, IOSEEK_START);
2023-02-04 00:22:49 +00:00
unsigned char aNumMarkers[sizeof(int32_t)];
uint_to_bytes_be(aNumMarkers, m_NumTimelineMarkers);
io_write(m_File, aNumMarkers, sizeof(aNumMarkers));
for(int i = 0; i < m_NumTimelineMarkers; i++)
{
2023-02-04 00:22:49 +00:00
unsigned char aMarker[sizeof(int32_t)];
uint_to_bytes_be(aMarker, m_aTimelineMarkers[i]);
io_write(m_File, aMarker, sizeof(aMarker));
}
2010-05-29 07:25:38 +00:00
io_close(m_File);
m_File = 0;
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording", gs_DemoPrintColor);
2010-05-29 07:25:38 +00:00
return 0;
}
void CDemoRecorder::AddDemoMarker()
{
if(m_LastTickMarker < 0)
return;
AddDemoMarker(m_LastTickMarker);
}
void CDemoRecorder::AddDemoMarker(int Tick)
{
dbg_assert(Tick >= 0, "invalid marker tick");
if(m_NumTimelineMarkers >= MAX_TIMELINE_MARKERS)
return;
// not more than 1 marker in a second
if(m_NumTimelineMarkers > 0)
{
int Diff = Tick - m_aTimelineMarkers[m_NumTimelineMarkers - 1];
if(Diff < (float)SERVER_TICK_SPEED)
return;
}
m_aTimelineMarkers[m_NumTimelineMarkers++] = Tick;
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Added timeline marker", gs_DemoPrintColor);
}
CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta, bool UseVideo, TUpdateIntraTimesFunc &&UpdateIntraTimesFunc)
2021-07-19 18:14:12 +00:00
{
Construct(pSnapshotDelta, UseVideo);
2021-07-19 18:14:12 +00:00
m_UpdateIntraTimesFunc = UpdateIntraTimesFunc;
}
CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta, bool UseVideo)
2021-07-19 18:14:12 +00:00
{
Construct(pSnapshotDelta, UseVideo);
2021-07-19 18:14:12 +00:00
}
CDemoPlayer::~CDemoPlayer()
{
dbg_assert(m_File == 0, "Demo player not stopped");
}
void CDemoPlayer::Construct(class CSnapshotDelta *pSnapshotDelta, bool UseVideo)
2010-05-29 07:25:38 +00:00
{
m_File = 0;
m_SpeedIndex = 4;
2010-05-29 07:25:38 +00:00
m_pSnapshotDelta = pSnapshotDelta;
m_LastSnapshotDataSize = -1;
m_pListener = nullptr;
m_UseVideo = UseVideo;
m_aFilename[0] = '\0';
m_aErrorMessage[0] = '\0';
2010-05-29 07:25:38 +00:00
}
void CDemoPlayer::SetListener(IListener *pListener)
2010-05-29 07:25:38 +00:00
{
m_pListener = pListener;
2010-05-29 07:25:38 +00:00
}
CDemoPlayer::EReadChunkHeaderResult CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
2010-05-29 07:25:38 +00:00
{
*pSize = 0;
*pType = 0;
unsigned char Chunk = 0;
2010-05-29 07:25:38 +00:00
if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk))
return CHUNKHEADER_EOF;
if(Chunk & CHUNKTYPEFLAG_TICKMARKER)
2010-05-29 07:25:38 +00:00
{
// decode tick marker
2023-10-15 10:52:31 +00:00
int Tickdelta_legacy = Chunk & CHUNKMASK_TICK_LEGACY; // compatibility
*pType = Chunk & (CHUNKTYPEFLAG_TICKMARKER | CHUNKTICKFLAG_KEYFRAME);
int NewTick;
if(m_Info.m_Header.m_Version < gs_VersionTickCompression && Tickdelta_legacy != 0)
{
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
NewTick = *pTick + Tickdelta_legacy;
}
2023-10-15 10:52:31 +00:00
else if(Chunk & CHUNKTICKFLAG_TICK_COMPRESSED)
{
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
2023-10-15 10:52:31 +00:00
int Tickdelta = Chunk & CHUNKMASK_TICK;
NewTick = *pTick + Tickdelta;
}
else
2010-05-29 07:25:38 +00:00
{
2023-02-04 00:22:49 +00:00
unsigned char aTickdata[sizeof(int32_t)];
2010-05-29 07:25:38 +00:00
if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
return CHUNKHEADER_ERROR;
NewTick = bytes_be_to_uint(aTickdata);
2010-05-29 07:25:38 +00:00
}
if(NewTick < MIN_TICK || NewTick >= MAX_TICK) // invalid tick
return CHUNKHEADER_ERROR;
*pTick = NewTick;
2010-05-29 07:25:38 +00:00
}
else
{
// decode normal chunk
*pType = (Chunk & CHUNKMASK_TYPE) >> 5;
*pSize = Chunk & CHUNKMASK_SIZE;
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 CHUNKHEADER_ERROR;
2010-05-29 07:25:38 +00:00
*pSize = aSizedata[0];
}
else if(*pSize == 31)
{
unsigned char aSizedata[2];
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
return CHUNKHEADER_ERROR;
*pSize = (aSizedata[1] << 8) | aSizedata[0];
2010-05-29 07:25:38 +00:00
}
}
return CHUNKHEADER_SUCCESS;
2010-05-29 07:25:38 +00:00
}
bool CDemoPlayer::ScanFile()
2010-05-29 07:25:38 +00:00
{
const long StartPos = io_tell(m_File);
m_vKeyFrames.clear();
if(StartPos < 0)
return false;
2010-05-29 07:25:38 +00:00
int ChunkTick = -1;
2022-02-14 23:12:52 +00:00
while(true)
2010-05-29 07:25:38 +00:00
{
const long CurrentPos = io_tell(m_File);
if(CurrentPos < 0)
{
m_vKeyFrames.clear();
return false;
}
int ChunkType, ChunkSize;
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result == CHUNKHEADER_EOF)
{
2010-05-29 07:25:38 +00:00
break;
}
else if(Result == CHUNKHEADER_ERROR)
{
m_vKeyFrames.clear();
return false;
}
if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
2010-05-29 07:25:38 +00:00
{
if(ChunkType & CHUNKTICKFLAG_KEYFRAME)
2010-05-29 07:25:38 +00:00
{
m_vKeyFrames.emplace_back(CurrentPos, ChunkTick);
2010-05-29 07:25:38 +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)
{
if(io_skip(m_File, ChunkSize) != 0)
{
m_vKeyFrames.clear();
return false;
}
}
2010-05-29 07:25:38 +00:00
}
if(io_seek(m_File, StartPos, IOSEEK_START) != 0)
{
m_vKeyFrames.clear();
return false;
}
return true;
2010-05-29 07:25:38 +00:00
}
void CDemoPlayer::DoTick()
{
// update ticks
m_Info.m_PreviousTick = m_Info.m_Info.m_CurrentTick;
m_Info.m_Info.m_CurrentTick = m_Info.m_NextTick;
int ChunkTick = m_Info.m_Info.m_CurrentTick;
2010-05-29 07:25:38 +00:00
2021-07-19 18:14:12 +00:00
int64_t Freq = time_freq();
2023-10-15 10:52:31 +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;
2021-07-19 18:14:12 +00:00
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();
bool GotSnapshot = false;
2022-02-14 23:12:52 +00:00
while(true)
2010-05-29 07:25:38 +00:00
{
int ChunkType, ChunkSize;
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result == CHUNKHEADER_EOF)
2010-05-29 07:25:38 +00:00
{
if(m_Info.m_PreviousTick == -1)
{
Stop("Empty demo");
}
else
{
Pause();
// Stop rendering when reaching end of file
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current())
Stop();
#endif
}
break;
}
else if(Result == CHUNKHEADER_ERROR)
{
Stop("Error reading chunk header");
2010-05-29 07:25:38 +00:00
break;
}
2010-05-29 07:25:38 +00:00
// read the chunk
int DataSize = 0;
2010-05-29 07:25:38 +00:00
if(ChunkSize)
{
if(io_read(m_File, m_aCompressedSnapshotData, ChunkSize) != (unsigned)ChunkSize)
2010-05-29 07:25:38 +00:00
{
Stop("Error reading chunk data");
2010-05-29 07:25:38 +00:00
break;
}
DataSize = CNetBase::Decompress(m_aCompressedSnapshotData, ChunkSize, m_aDecompressedSnapshotData, sizeof(m_aDecompressedSnapshotData));
2010-05-29 07:25:38 +00:00
if(DataSize < 0)
{
Stop("Error during network decompression");
2010-05-29 07:25:38 +00:00
break;
}
DataSize = CVariableInt::Decompress(m_aDecompressedSnapshotData, DataSize, m_aCurrentSnapshotData, sizeof(m_aCurrentSnapshotData));
2010-05-29 07:25:38 +00:00
if(DataSize < 0)
{
Stop("Error during intpack decompression");
2010-05-29 07:25:38 +00:00
break;
}
}
2010-05-29 07:25:38 +00:00
if(ChunkType == CHUNKTYPE_DELTA)
{
// process delta snapshot
CSnapshot *pNewsnap = (CSnapshot *)m_aDeltaSnapshotData;
DataSize = m_pSnapshotDelta->UnpackDelta((CSnapshot *)m_aLastSnapshotData, pNewsnap, m_aCurrentSnapshotData, DataSize);
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
if(DataSize < 0)
{
if(m_pConsole)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Error unpacking snapshot delta. DataSize=%d", DataSize);
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
else if(!pNewsnap->IsValid(DataSize))
{
if(m_pConsole)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Snapshot delta invalid. DataSize=%d", DataSize);
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
else
2010-05-29 07:25:38 +00:00
{
if(m_pListener)
m_pListener->OnDemoPlayerSnapshot(m_aDeltaSnapshotData, DataSize);
2010-05-29 07:25:38 +00:00
m_LastSnapshotDataSize = DataSize;
mem_copy(m_aLastSnapshotData, m_aDeltaSnapshotData, DataSize);
GotSnapshot = true;
2010-05-29 07:25:38 +00:00
}
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
}
else if(ChunkType == CHUNKTYPE_SNAPSHOT)
{
// process full snapshot
CSnapshot *pSnap = (CSnapshot *)m_aCurrentSnapshotData;
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
if(!pSnap->IsValid(DataSize))
{
if(m_pConsole)
{
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Snapshot invalid. DataSize=%d", DataSize);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
else
{
GotSnapshot = true;
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
m_LastSnapshotDataSize = DataSize;
mem_copy(m_aLastSnapshotData, m_aCurrentSnapshotData, DataSize);
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
if(m_pListener)
m_pListener->OnDemoPlayerSnapshot(m_aCurrentSnapshotData, DataSize);
Validate snapshot size and member variables and demo snapshots Add `CSnapshot::IsValid` to check if a snapshot unpacked from a snapshot delta or demo is valid: - ensure number of items and data size are not negative - ensure that the actual size of the snapshot matches the size derived from its member variables - ensure item offsets are within the valid range - ensure item sizes are not negative Add `CSnapshot::TotalSize` and `CSnapshot::OffsetSize` utility functions. Minor improvements to related error messages. Fixes buffer overflow: ``` ==47744==ERROR: AddressSanitizer: global-buffer-overflow on address 0x558618e3767f at pc 0x558614b9bdfb bp 0x7ffe58a32cd0 sp 0x7ffe58a32cc0 READ of size 4 at 0x558618e3767f thread T0 0x558614b9bdfa in CSnapshotItem::Type() const src/engine/shared/snapshot.h:16 0x558615c3c911 in CSnapshot::GetItemType(int) const src/engine/shared/snapshot.cpp:29 0x558614aebaba in CClient::UnpackAndValidateSnapshot(CSnapshot*, CSnapshot*) src/engine/client/client.cpp:2264 0x558614af87cb in CClient::OnDemoPlayerSnapshot(void*, int) src/engine/client/client.cpp:2598 0x558615b9db1a in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:659 0x558615babd3f in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x558614afb08b in CClient::Update() src/engine/client/client.cpp:2686 0x558614b1d9eb in CClient::Run() src/engine/client/client.cpp:3296 0x558614b8e64f in main src/engine/client/client.cpp:4761 ``` And fixes a buffer overflow that manifests itself as an internal ASan error: ``` ================================================================= ==4755==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) 0x7f0bf5f368be in AsanCheckFailed ../../../../src/libsanitizer/asan/asan_rtl.cc:72 0x7f0bf5f54eee in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) ../../../../src/libsanitizer/sanitizer_common/sanitizer_termination.cc:77 0x7f0bf5e4cb6f in GetShadowKind ../../../../src/libsanitizer/asan/asan_descriptions.cc:79 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:95 0x7f0bf5e4cb6f in __asan::GetShadowAddressInformation(unsigned long, __asan::ShadowAddressDescription*) ../../../../src/libsanitizer/asan/asan_descriptions.cc:92 0x7f0bf5e4e386 in __asan::AddressDescription::AddressDescription(unsigned long, unsigned long, bool) ../../../../src/libsanitizer/asan/asan_descriptions.cc:440 0x7f0bf5e50e94 in __asan::ErrorGeneric::ErrorGeneric(unsigned int, unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long) ../../../../src/libsanitizer/asan/asan_errors.cc:380 0x7f0bf5f35f4d in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ../../../../src/libsanitizer/asan/asan_report.cc:460 0x7f0bf5e86f5e in __interceptor_memset ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:762 0x558234873f1d in mem_zero src/base/system.cpp:213 0x55823481fc27 in CSnapshotBuilder::NewItem(int, int, int) src/engine/shared/snapshot.cpp:675 0x55823481be65 in CSnapshotDelta::UnpackDelta(CSnapshot*, CSnapshot*, void const*, int) src/engine/shared/snapshot.cpp:380 0x558234776641 in CDemoPlayer::DoTick() src/engine/shared/demo.cpp:631 0x5582347861a9 in CDemoPlayer::Update(bool) src/engine/shared/demo.cpp:1007 0x5582336d4c7d in CClient::Update() src/engine/client/client.cpp:2695 0x5582336f75dd in CClient::Run() src/engine/client/client.cpp:3305 0x558233768241 in main src/engine/client/client.cpp:4770 ```
2022-07-22 22:01:46 +00:00
}
2010-05-29 07:25:38 +00:00
}
else
{
// if there were no snapshots in this tick, replay the last one
if(!GotSnapshot && m_pListener && m_LastSnapshotDataSize != -1)
2010-05-29 07:25:38 +00:00
{
GotSnapshot = true;
m_pListener->OnDemoPlayerSnapshot(m_aLastSnapshotData, m_LastSnapshotDataSize);
2010-05-29 07:25:38 +00:00
}
2010-05-29 07:25:38 +00:00
// check the remaining types
if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
2010-05-29 07:25:38 +00:00
{
m_Info.m_NextTick = ChunkTick;
break;
}
else if(ChunkType == CHUNKTYPE_MESSAGE)
{
if(m_pListener)
m_pListener->OnDemoPlayerMessage(m_aCurrentSnapshotData, 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;
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current() && g_Config.m_ClVideoPauseWithDemo)
IVideo::Current()->Pause(true);
#endif
2010-05-29 07:25:38 +00:00
}
void CDemoPlayer::Unpause()
{
2022-05-30 20:44:12 +00:00
m_Info.m_Info.m_Paused = false;
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current() && g_Config.m_ClVideoPauseWithDemo)
IVideo::Current()->Pause(false);
#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
{
dbg_assert(m_File == 0, "Demo player already playing");
m_pConsole = pConsole;
str_copy(m_aFilename, pFilename);
str_copy(m_aErrorMessage, "");
if(m_pConsole)
2010-05-29 07:25:38 +00:00
{
char aBuf[32 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "Loading demo '%s'", pFilename);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
2010-05-29 07:25:38 +00:00
}
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;
m_SpeedIndex = 4;
2010-05-29 07:25:38 +00:00
m_LastSnapshotDataSize = -1;
if(!GetDemoInfo(pStorage, m_pConsole, pFilename, StorageType, &m_Info.m_Header, &m_Info.m_TimelineMarkers, &m_MapInfo, &m_File, m_aErrorMessage, sizeof(m_aErrorMessage)))
2010-05-29 07:25:38 +00:00
{
str_copy(m_aFilename, "");
2010-05-29 07:25:38 +00:00
return -1;
}
// save byte offset of map for later use
m_MapOffset = io_tell(m_File);
if(m_MapOffset < 0 || io_skip(m_File, m_MapInfo.m_Size) != 0)
{
Stop("Error skipping map data");
return -1;
}
2014-08-12 14:21:06 +00:00
if(m_Info.m_Header.m_Version > gs_OldVersion)
{
// get timeline markers
int Num = bytes_be_to_uint(m_Info.m_TimelineMarkers.m_aNumTimelineMarkers);
m_Info.m_Info.m_NumTimelineMarkers = clamp<int>(Num, 0, MAX_TIMELINE_MARKERS);
for(int i = 0; i < m_Info.m_Info.m_NumTimelineMarkers; i++)
{
m_Info.m_Info.m_aTimelineMarkers[i] = bytes_be_to_uint(m_Info.m_TimelineMarkers.m_aTimelineMarkers[i]);
}
}
2018-07-10 09:29:02 +00:00
// scan the file for interesting points
if(!ScanFile())
{
Stop("Error scanning demo file");
return -1;
}
// 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;
}
unsigned char *CDemoPlayer::GetMapData(class IStorage *pStorage)
{
if(!m_MapInfo.m_Size)
return nullptr;
const long CurSeek = io_tell(m_File);
if(CurSeek < 0 || io_seek(m_File, m_MapOffset, IOSEEK_START) != 0)
return nullptr;
unsigned char *pMapData = (unsigned char *)malloc(m_MapInfo.m_Size);
if(io_read(m_File, pMapData, m_MapInfo.m_Size) != m_MapInfo.m_Size ||
io_seek(m_File, CurSeek, IOSEEK_START) != 0)
{
free(pMapData);
return nullptr;
}
return pMapData;
}
bool CDemoPlayer::ExtractMap(class IStorage *pStorage)
{
unsigned char *pMapData = GetMapData(pStorage);
if(!pMapData)
return false;
2019-10-14 00:27:08 +00:00
// handle sha256
2019-12-17 14:44:54 +00:00
SHA256_DIGEST Sha256 = SHA256_ZEROED;
if(m_Info.m_Header.m_Version >= gs_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
char aSha[SHA256_MAXSTRSIZE], aMapFilename[IO_MAX_PATH_LENGTH];
2019-12-17 14:44:54 +00:00
sha256_str(Sha256, aSha, sizeof(aSha));
str_format(aMapFilename, sizeof(aMapFilename), "downloadedmaps/%s_%s.map", m_Info.m_Header.m_aMapName, aSha);
// save map
IOHANDLE MapFile = pStorage->OpenFile(aMapFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
if(!MapFile)
{
free(pMapData);
return false;
}
io_write(MapFile, pMapData, m_MapInfo.m_Size);
io_close(MapFile);
// free data
free(pMapData);
return true;
}
int64_t CDemoPlayer::Time()
{
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current())
{
if(!m_WasRecording)
{
m_WasRecording = true;
m_Info.m_LastUpdate = IVideo::Time();
}
return IVideo::Time();
}
else
{
const int64_t Now = time_get();
if(m_WasRecording)
{
m_WasRecording = false;
m_Info.m_LastUpdate = Now;
}
return Now;
}
#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();
2010-05-29 07:25:38 +00:00
// set start info
m_Info.m_CurrentTime = m_Info.m_PreviousTick * time_freq() / SERVER_TICK_SPEED;
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 + round_truncate((m_Info.m_Info.m_LastTick - m_Info.m_Info.m_FirstTick) * Percent);
2019-04-15 18:39:39 +00:00
return SetPos(WantedTick);
}
int CDemoPlayer::SeekTime(float Seconds)
{
int WantedTick = m_Info.m_Info.m_CurrentTick + round_truncate(Seconds * (float)SERVER_TICK_SPEED);
2019-04-15 18:39:39 +00:00
return SetPos(WantedTick);
}
int CDemoPlayer::SeekTick(ETickOffset TickOffset)
{
int WantedTick;
switch(TickOffset)
{
case TICK_CURRENT:
WantedTick = m_Info.m_Info.m_CurrentTick;
break;
case TICK_PREVIOUS:
WantedTick = m_Info.m_PreviousTick;
break;
case TICK_NEXT:
WantedTick = m_Info.m_NextTick;
break;
default:
dbg_assert(false, "Invalid TickOffset");
WantedTick = -1;
break;
}
// +1 because SetPos will seek until the given tick is the next tick that
// will be played back, whereas we want the wanted tick to be played now.
return SetPos(WantedTick + 1);
}
2019-04-15 18:39:39 +00:00
int CDemoPlayer::SetPos(int WantedTick)
2010-05-29 07:25:38 +00:00
{
if(!m_File)
return -1;
WantedTick = clamp(WantedTick, m_Info.m_Info.m_FirstTick, m_Info.m_Info.m_LastTick);
const int KeyFrameWantedTick = WantedTick - 5; // -5 because we have to have a current tick and previous tick when we do the playback
const float Percent = (KeyFrameWantedTick - m_Info.m_Info.m_FirstTick) / (float)(m_Info.m_Info.m_LastTick - m_Info.m_Info.m_FirstTick);
2010-05-29 07:25:38 +00:00
// get correct key frame
size_t KeyFrame = clamp<size_t>(m_vKeyFrames.size() * Percent, 0, m_vKeyFrames.size() - 1);
while(KeyFrame < m_vKeyFrames.size() - 1 && m_vKeyFrames[KeyFrame].m_Tick < KeyFrameWantedTick)
2019-04-15 18:39:39 +00:00
KeyFrame++;
while(KeyFrame > 0 && m_vKeyFrames[KeyFrame].m_Tick > KeyFrameWantedTick)
2019-04-15 18:39:39 +00:00
KeyFrame--;
2019-04-15 18:39:39 +00:00
// seek to the correct key frame
if(io_seek(m_File, m_vKeyFrames[KeyFrame].m_Filepos, IOSEEK_START) != 0)
{
Stop("Error seeking keyframe position");
return -1;
}
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
while(m_Info.m_NextTick < WantedTick && IsPlaying())
2010-05-29 07:25:38 +00:00
DoTick();
2010-05-29 07:25:38 +00:00
Play();
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
}
void CDemoPlayer::SetSpeedIndex(int SpeedIndex)
{
dbg_assert(SpeedIndex >= 0 && SpeedIndex < g_DemoSpeeds, "invalid SpeedIndex");
m_SpeedIndex = SpeedIndex;
SetSpeed(g_aSpeeds[m_SpeedIndex]);
}
void CDemoPlayer::AdjustSpeedIndex(int Offset)
{
SetSpeedIndex(clamp(m_SpeedIndex + Offset, 0, (int)(std::size(g_aSpeeds) - 1)));
}
2014-08-12 14:21:06 +00:00
int CDemoPlayer::Update(bool RealTime)
2010-05-29 07:25:38 +00:00
{
int64_t Now = Time();
2021-06-23 05:05:49 +00:00
int64_t Deltatime = Now - m_Info.m_LastUpdate;
2010-05-29 07:25:38 +00:00
m_Info.m_LastUpdate = Now;
2010-05-29 07:25:38 +00:00
if(!IsPlaying())
return 0;
const int64_t Freq = time_freq();
if(!m_Info.m_Info.m_Paused)
2010-05-29 07:25:38 +00:00
{
2021-06-23 05:05:49 +00:00
m_Info.m_CurrentTime += (int64_t)(Deltatime * (double)m_Info.m_Info.m_Speed);
while(!m_Info.m_Info.m_Paused && IsPlaying())
2010-05-29 07:25:38 +00:00
{
2023-10-15 10:52:31 +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;
2010-05-29 07:25:38 +00:00
// do one more tick
DoTick();
}
}
2010-05-29 07:25:38 +00:00
// update intratick
{
2023-10-15 10:52:31 +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;
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();
}
2010-05-29 07:25:38 +00:00
return 0;
}
void CDemoPlayer::Stop(const char *pErrorMessage)
2010-05-29 07:25:38 +00:00
{
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current())
IVideo::Current()->Stop();
#endif
2010-05-29 07:25:38 +00:00
if(!m_File)
return;
if(m_pConsole)
{
char aBuf[256];
if(pErrorMessage[0] == '\0')
str_copy(aBuf, "Stopped playback");
else
str_format(aBuf, sizeof(aBuf), "Stopped playback due to error: %s", pErrorMessage);
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;
m_vKeyFrames.clear();
2022-07-09 16:14:56 +00:00
str_copy(m_aFilename, "");
str_copy(m_aErrorMessage, pErrorMessage);
2010-05-29 07:25:38 +00:00
}
void CDemoPlayer::GetDemoName(char *pBuffer, size_t BufferSize) const
{
IStorage::StripPathAndExtension(m_aFilename, pBuffer, BufferSize);
}
2010-05-29 07:25:38 +00:00
bool CDemoPlayer::GetDemoInfo(IStorage *pStorage, IConsole *pConsole, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo, IOHANDLE *pFile, char *pErrorMessage, size_t ErrorMessageSize) const
{
mem_zero(pDemoHeader, sizeof(CDemoHeader));
mem_zero(pTimelineMarkers, sizeof(CTimelineMarkers));
mem_zero(pMapInfo, sizeof(CMapInfo));
IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
{
if(pErrorMessage != nullptr)
str_copy(pErrorMessage, "Could not open demo file", ErrorMessageSize);
return false;
}
if(io_read(File, pDemoHeader, sizeof(CDemoHeader)) != sizeof(CDemoHeader) || !pDemoHeader->Valid())
{
if(pErrorMessage != nullptr)
str_copy(pErrorMessage, "Error reading demo header", ErrorMessageSize);
mem_zero(pDemoHeader, sizeof(CDemoHeader));
io_close(File);
return false;
}
if(pDemoHeader->m_Version < gs_OldVersion)
{
if(pErrorMessage != nullptr)
str_format(pErrorMessage, ErrorMessageSize, "Demo version '%d' is not supported", pDemoHeader->m_Version);
mem_zero(pDemoHeader, sizeof(CDemoHeader));
io_close(File);
return false;
}
else if(pDemoHeader->m_Version > gs_OldVersion)
{
if(io_read(File, pTimelineMarkers, sizeof(CTimelineMarkers)) != sizeof(CTimelineMarkers))
{
if(pErrorMessage != nullptr)
str_copy(pErrorMessage, "Error reading timeline markers", ErrorMessageSize);
mem_zero(pDemoHeader, sizeof(CDemoHeader));
io_close(File);
return false;
}
}
2019-10-14 00:27:08 +00:00
2019-12-22 16:09:14 +00:00
SHA256_DIGEST Sha256 = SHA256_ZEROED;
if(pDemoHeader->m_Version >= gs_Sha256Version)
2019-12-22 16:09:14 +00:00
{
CUuid ExtensionUuid = {};
const unsigned ExtensionUuidSize = io_read(File, &ExtensionUuid.m_aData, sizeof(ExtensionUuid.m_aData));
if(ExtensionUuidSize == sizeof(ExtensionUuid.m_aData) && ExtensionUuid == SHA256_EXTENSION)
2019-12-22 16:09:14 +00:00
{
if(io_read(File, &Sha256, sizeof(SHA256_DIGEST)) != sizeof(SHA256_DIGEST))
{
if(pErrorMessage != nullptr)
str_copy(pErrorMessage, "Error reading SHA256", ErrorMessageSize);
mem_zero(pDemoHeader, sizeof(CDemoHeader));
mem_zero(pTimelineMarkers, sizeof(CTimelineMarkers));
io_close(File);
return false;
}
2019-12-22 16:09:14 +00:00
}
else
{
// This hopes whatever happened during the version increment didn't add something here
if(pConsole)
{
pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "Demo version incremented, but not by DDNet");
}
if(io_seek(File, -(int)ExtensionUuidSize, IOSEEK_CUR) != 0)
{
if(pErrorMessage != nullptr)
str_copy(pErrorMessage, "Error rewinding SHA256 extension UUID", ErrorMessageSize);
mem_zero(pDemoHeader, sizeof(CDemoHeader));
mem_zero(pTimelineMarkers, sizeof(CTimelineMarkers));
io_close(File);
return false;
}
2019-12-22 16:09:14 +00:00
}
}
2019-10-14 00:27:08 +00:00
str_copy(pMapInfo->m_aName, pDemoHeader->m_aMapName);
pMapInfo->m_Sha256 = Sha256;
pMapInfo->m_Crc = bytes_be_to_uint(pDemoHeader->m_aMapCrc);
pMapInfo->m_Size = bytes_be_to_uint(pDemoHeader->m_aMapSize);
2019-10-14 00:27:08 +00:00
if(pFile == nullptr)
io_close(File);
else
*pFile = File;
return true;
}
class CDemoRecordingListener : public CDemoPlayer::IListener
{
public:
CDemoRecorder *m_pDemoRecorder;
CDemoPlayer *m_pDemoPlayer;
bool m_Stop;
int m_StartTick;
int m_EndTick;
void OnDemoPlayerSnapshot(void *pData, int Size) override
{
const CDemoPlayer::CPlaybackInfo *pInfo = m_pDemoPlayer->Info();
if(m_EndTick != -1 && pInfo->m_Info.m_CurrentTick > m_EndTick)
m_Stop = true;
else if(m_StartTick == -1 || pInfo->m_Info.m_CurrentTick >= m_StartTick)
m_pDemoRecorder->RecordSnapshot(pInfo->m_Info.m_CurrentTick, pData, Size);
}
void OnDemoPlayerMessage(void *pData, int Size) override
{
const CDemoPlayer::CPlaybackInfo *pInfo = m_pDemoPlayer->Info();
if(m_EndTick != -1 && pInfo->m_Info.m_CurrentTick > m_EndTick)
m_Stop = true;
else if(m_StartTick == -1 || pInfo->m_Info.m_CurrentTick >= m_StartTick)
m_pDemoRecorder->RecordMessage(pData, Size);
}
};
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;
}
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
{
CDemoPlayer DemoPlayer(m_pSnapshotDelta, false);
if(DemoPlayer.Load(m_pStorage, m_pConsole, pDemo, IStorage::TYPE_ALL_OR_ABSOLUTE) == -1)
2014-08-12 14:21:06 +00:00
return;
2015-07-09 00:08:14 +00:00
const CMapInfo *pMapInfo = DemoPlayer.GetMapInfo();
const CDemoPlayer::CPlaybackInfo *pInfo = DemoPlayer.Info();
2019-12-22 16:09:14 +00:00
SHA256_DIGEST Sha256 = pMapInfo->m_Sha256;
if(pInfo->m_Header.m_Version < gs_Sha256Version)
{
if(DemoPlayer.ExtractMap(m_pStorage))
2019-12-22 16:09:14 +00:00
Sha256 = pMapInfo->m_Sha256;
}
2019-12-22 16:09:14 +00:00
CDemoRecorder DemoRecorder(m_pSnapshotDelta);
unsigned char *pMapData = DemoPlayer.GetMapData(m_pStorage);
const int Result = DemoRecorder.Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, Sha256, pMapInfo->m_Crc, pInfo->m_Header.m_aType, pMapInfo->m_Size, pMapData, nullptr, pfnFilter, pUser) == -1;
free(pMapData);
if(Result != 0)
{
DemoPlayer.Stop();
2014-08-12 14:21:06 +00:00
return;
}
CDemoRecordingListener Listener;
Listener.m_pDemoRecorder = &DemoRecorder;
Listener.m_pDemoPlayer = &DemoPlayer;
Listener.m_Stop = false;
Listener.m_StartTick = StartTick;
Listener.m_EndTick = EndTick;
DemoPlayer.SetListener(&Listener);
2014-08-12 14:21:06 +00:00
DemoPlayer.Play();
2014-08-12 14:21:06 +00:00
while(DemoPlayer.IsPlaying() && !Listener.m_Stop)
{
DemoPlayer.Update(false);
2014-08-12 14:21:06 +00:00
if(pInfo->m_Info.m_Paused)
2014-08-12 14:21:06 +00:00
break;
}
// Copy timeline markers to sliced demo
for(int i = 0; i < pInfo->m_Info.m_NumTimelineMarkers; i++)
{
if((StartTick == -1 || pInfo->m_Info.m_aTimelineMarkers[i] >= StartTick) && (EndTick == -1 || pInfo->m_Info.m_aTimelineMarkers[i] <= EndTick))
{
DemoRecorder.AddDemoMarker(pInfo->m_Info.m_aTimelineMarkers[i]);
}
}
DemoPlayer.Stop();
DemoRecorder.Stop();
2014-08-12 14:21:06 +00:00
}