Merge pull request #7358 from Robyt3/Engine-Demo-Validation

Validate ticks when reading demo chunk headers, check for all file errors in demo player, show demo error popup
This commit is contained in:
Dennis Felsing 2023-10-19 21:48:47 +00:00 committed by GitHub
commit 5987d8d248
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 173 deletions

View file

@ -389,7 +389,7 @@ char *io_read_all_str(IOHANDLE io)
return (char *)buffer;
}
unsigned io_skip(IOHANDLE io, int size)
int io_skip(IOHANDLE io, int size)
{
return io_seek(io, size, IOSEEK_CUR);
}

View file

@ -292,9 +292,9 @@ char *io_read_all_str(IOHANDLE io);
* @param io Handle to the file.
* @param size Number of bytes to skip.
*
* @return Number of bytes skipped.
* @return 0 on success.
*/
unsigned io_skip(IOHANDLE io, int size);
int io_skip(IOHANDLE io, int size);
/**
* Writes data from a buffer to file.

View file

@ -609,6 +609,9 @@ void CClient::Connect(const char *pAddress, const char *pPassword)
void CClient::DisconnectWithReason(const char *pReason)
{
if(pReason != nullptr && pReason[0] == '\0')
pReason = nullptr;
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason ? pReason : "unknown");
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, gs_ClientNetworkPrintColor);
@ -2437,7 +2440,13 @@ void CClient::Update()
else
{
// disconnect on error
Disconnect();
DisconnectWithReason(m_DemoPlayer.ErrorMessage());
if(m_DemoPlayer.ErrorMessage()[0] != '\0')
{
SWarning Warning(Localize("Error playing demo"), m_DemoPlayer.ErrorMessage());
Warning.m_AutoHide = false;
m_vWarnings.emplace_back(Warning);
}
}
}
else if(State() == IClient::STATE_ONLINE)
@ -3581,27 +3590,23 @@ void CClient::DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void
{
if(m_DemoPlayer.IsPlaying())
{
const char *pDemoFileName = m_DemoPlayer.GetDemoFileName();
m_DemoEditor.Slice(pDemoFileName, pDstPath, g_Config.m_ClDemoSliceBegin, g_Config.m_ClDemoSliceEnd, pfnFilter, pUser);
m_DemoEditor.Slice(m_DemoPlayer.Filename(), pDstPath, g_Config.m_ClDemoSliceBegin, g_Config.m_ClDemoSliceEnd, pfnFilter, pUser);
}
}
const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
{
IOHANDLE File = Storage()->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
return "error opening demo file";
io_close(File);
// Don't disconnect unless the file exists (only for play command)
if(!Storage()->FileExists(pFilename, StorageType))
return "No demo with this filename exists";
Disconnect();
m_aNetClient[CONN_MAIN].ResetErrorString();
// try to start playback
m_DemoPlayer.SetListener(this);
if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType))
return "error loading demo";
return m_DemoPlayer.ErrorMessage();
// load map
const CMapInfo *pMapInfo = m_DemoPlayer.GetMapInfo();
@ -3669,8 +3674,7 @@ const char *CClient::DemoPlayer_Render(const char *pFilename, int StorageType, c
{
m_DemoPlayer.Pause();
}
//m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "demo_recorder", "demo eof");
return 0;
return nullptr;
}
#endif

View file

@ -58,8 +58,8 @@ struct CMapInfo
{
char m_aName[MAX_MAP_LENGTH];
SHA256_DIGEST m_Sha256;
int m_Crc;
int m_Size;
unsigned m_Crc;
unsigned m_Size;
};
class IDemoPlayer : public IInterface
@ -100,7 +100,7 @@ public:
virtual bool IsPlaying() const = 0;
virtual const CInfo *BaseInfo() const = 0;
virtual void GetDemoName(char *pBuffer, size_t BufferSize) const = 0;
virtual bool GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo) const = 0;
virtual bool GetDemoInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo, IOHANDLE *pFile = nullptr, char *pErrorMessage = nullptr, size_t ErrorMessageSize = 0) const = 0;
};
class IDemoRecorder : public IInterface

View file

@ -411,6 +411,9 @@ void CDemoPlayer::Construct(class CSnapshotDelta *pSnapshotDelta, bool UseVideo)
m_LastSnapshotDataSize = -1;
m_pListener = nullptr;
m_UseVideo = UseVideo;
m_aFilename[0] = '\0';
m_aErrorMessage[0] = '\0';
}
void CDemoPlayer::SetListener(IListener *pListener)
@ -418,14 +421,14 @@ void CDemoPlayer::SetListener(IListener *pListener)
m_pListener = pListener;
}
int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
CDemoPlayer::EReadChunkHeaderResult CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
{
*pSize = 0;
*pType = 0;
unsigned char Chunk = 0;
if(io_read(m_File, &Chunk, sizeof(Chunk)) != sizeof(Chunk))
return -1;
return CHUNKHEADER_EOF;
if(Chunk & CHUNKTYPEFLAG_TICKMARKER)
{
@ -433,22 +436,30 @@ int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
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)
{
*pTick += Tickdelta_legacy;
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
NewTick = *pTick + Tickdelta_legacy;
}
else if(Chunk & CHUNKTICKFLAG_TICK_COMPRESSED)
{
if(*pTick < 0) // initial tick not initialized before a tick delta
return CHUNKHEADER_ERROR;
int Tickdelta = Chunk & CHUNKMASK_TICK;
*pTick += Tickdelta;
NewTick = *pTick + Tickdelta;
}
else
{
unsigned char aTickdata[sizeof(int32_t)];
if(io_read(m_File, aTickdata, sizeof(aTickdata)) != sizeof(aTickdata))
return -1;
*pTick = bytes_be_to_uint(aTickdata);
return CHUNKHEADER_ERROR;
NewTick = bytes_be_to_uint(aTickdata);
}
if(NewTick < MIN_TICK || NewTick >= MAX_TICK) // invalid tick
return CHUNKHEADER_ERROR;
*pTick = NewTick;
}
else
{
@ -460,34 +471,49 @@ int CDemoPlayer::ReadChunkHeader(int *pType, int *pSize, int *pTick)
{
unsigned char aSizedata[1];
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
return -1;
return CHUNKHEADER_ERROR;
*pSize = aSizedata[0];
}
else if(*pSize == 31)
{
unsigned char aSizedata[2];
if(io_read(m_File, aSizedata, sizeof(aSizedata)) != sizeof(aSizedata))
return -1;
return CHUNKHEADER_ERROR;
*pSize = (aSizedata[1] << 8) | aSizedata[0];
}
}
return 0;
return CHUNKHEADER_SUCCESS;
}
void CDemoPlayer::ScanFile()
bool CDemoPlayer::ScanFile()
{
const long StartPos = io_tell(m_File);
m_vKeyFrames.clear();
if(StartPos < 0)
return false;
int ChunkTick = 0;
int ChunkTick = -1;
while(true)
{
const long CurrentPos = io_tell(m_File);
if(CurrentPos < 0)
{
m_vKeyFrames.clear();
return false;
}
int ChunkType, ChunkSize;
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result == CHUNKHEADER_EOF)
{
break;
}
else if(Result == CHUNKHEADER_ERROR)
{
m_vKeyFrames.clear();
return false;
}
if(ChunkType & CHUNKTYPEFLAG_TICKMARKER)
{
@ -501,10 +527,21 @@ void CDemoPlayer::ScanFile()
m_Info.m_Info.m_LastTick = ChunkTick;
}
else if(ChunkSize)
io_skip(m_File, ChunkSize);
{
if(io_skip(m_File, ChunkSize) != 0)
{
m_vKeyFrames.clear();
return false;
}
}
}
io_seek(m_File, StartPos, IOSEEK_START);
if(io_seek(m_File, StartPos, IOSEEK_START) != 0)
{
m_vKeyFrames.clear();
return false;
}
return true;
}
void CDemoPlayer::DoTick()
@ -527,23 +564,27 @@ void CDemoPlayer::DoTick()
while(true)
{
int ChunkType, ChunkSize;
if(ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick))
const EReadChunkHeaderResult Result = ReadChunkHeader(&ChunkType, &ChunkSize, &ChunkTick);
if(Result == CHUNKHEADER_EOF)
{
// stop on error or eof
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "end of file");
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
if(m_Info.m_PreviousTick == -1)
{
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "empty demo");
Stop();
}
else
Pause();
break;
}
else if(Result == CHUNKHEADER_ERROR)
{
Stop("Error reading chunk header");
break;
}
@ -553,30 +594,21 @@ void CDemoPlayer::DoTick()
{
if(io_read(m_File, m_aCompressedSnapshotData, ChunkSize) != (unsigned)ChunkSize)
{
// stop on error or eof
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error reading chunk");
Stop();
Stop("Error reading chunk data");
break;
}
DataSize = CNetBase::Decompress(m_aCompressedSnapshotData, ChunkSize, m_aDecompressedSnapshotData, sizeof(m_aDecompressedSnapshotData));
if(DataSize < 0)
{
// stop on error or eof
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error during network decompression");
Stop();
Stop("Error during network decompression");
break;
}
DataSize = CVariableInt::Decompress(m_aDecompressedSnapshotData, DataSize, m_aCurrentSnapshotData, sizeof(m_aCurrentSnapshotData));
if(DataSize < 0)
{
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", "error during intpack decompression");
Stop();
Stop("Error during intpack decompression");
break;
}
}
@ -591,8 +623,8 @@ void CDemoPlayer::DoTick()
{
if(m_pConsole)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "error during unpacking of delta, err=%d", DataSize);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Error unpacking snapshot delta. DataSize=%d", DataSize);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
@ -600,8 +632,8 @@ void CDemoPlayer::DoTick()
{
if(m_pConsole)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "snapshot delta invalid. DataSize=%d", DataSize);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Snapshot delta invalid. DataSize=%d", DataSize);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
@ -623,8 +655,8 @@ void CDemoPlayer::DoTick()
{
if(m_pConsole)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "snapshot invalid. DataSize=%d", DataSize);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "Snapshot invalid. DataSize=%d", DataSize);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "demo_player", aBuf);
}
}
@ -685,20 +717,15 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
dbg_assert(m_File == 0, "Demo player already playing");
m_pConsole = pConsole;
m_File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!m_File)
{
str_copy(m_aFilename, pFilename);
str_copy(m_aErrorMessage, "");
if(m_pConsole)
{
char aBuf[32 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "could not open '%s'", pFilename);
str_format(aBuf, sizeof(aBuf), "Loading demo '%s'", pFilename);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
}
return -1;
}
// store the filename
str_copy(m_aFilename, pFilename);
// clear the playback info
mem_zero(&m_Info, sizeof(m_Info));
@ -709,66 +736,21 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
m_Info.m_PreviousTick = -1;
m_Info.m_Info.m_Speed = 1;
m_SpeedIndex = 4;
m_LastSnapshotDataSize = -1;
// read the header
if(io_read(m_File, &m_Info.m_Header, sizeof(m_Info.m_Header)) != sizeof(m_Info.m_Header) || !m_Info.m_Header.Valid())
if(!GetDemoInfo(pStorage, m_pConsole, pFilename, StorageType, &m_Info.m_Header, &m_Info.m_TimelineMarkers, &m_MapInfo, &m_File, m_aErrorMessage, sizeof(m_aErrorMessage)))
{
if(m_pConsole)
{
char aBuf[32 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "'%s' is not a valid demo file", pFilename);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", aBuf);
}
io_close(m_File);
m_File = 0;
str_copy(m_aFilename, "");
return -1;
}
if(m_Info.m_Header.m_Version < gs_OldVersion)
{
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);
}
io_close(m_File);
m_File = 0;
return -1;
}
else if(m_Info.m_Header.m_Version > gs_OldVersion)
io_read(m_File, &m_Info.m_TimelineMarkers, sizeof(m_Info.m_TimelineMarkers));
SHA256_DIGEST Sha256 = SHA256_ZEROED;
if(m_Info.m_Header.m_Version >= gs_Sha256Version)
{
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);
}
}
// save byte offset of map for later use
const unsigned MapSize = bytes_be_to_uint(m_Info.m_Header.m_aMapSize);
m_MapOffset = io_tell(m_File);
io_skip(m_File, MapSize);
// store map information
m_MapInfo.m_Crc = bytes_be_to_uint(m_Info.m_Header.m_aMapCrc);
m_MapInfo.m_Sha256 = Sha256;
m_MapInfo.m_Size = MapSize;
str_copy(m_MapInfo.m_aName, m_Info.m_Header.m_aMapName);
if(m_MapOffset < 0 || io_skip(m_File, m_MapInfo.m_Size) != 0)
{
Stop("Error skipping map data");
return -1;
}
if(m_Info.m_Header.m_Version > gs_OldVersion)
{
@ -782,7 +764,11 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
}
// scan the file for interesting points
ScanFile();
if(!ScanFile())
{
Stop("Error scanning demo file");
return -1;
}
// reset slice markers
g_Config.m_ClDemoSliceBegin = -1;
@ -795,15 +781,18 @@ int CDemoPlayer::Load(class IStorage *pStorage, class IConsole *pConsole, const
unsigned char *CDemoPlayer::GetMapData(class IStorage *pStorage)
{
if(!m_MapInfo.m_Size)
return 0;
return nullptr;
long CurSeek = io_tell(m_File);
// get map data
io_seek(m_File, m_MapOffset, IOSEEK_START);
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);
io_read(m_File, pMapData, m_MapInfo.m_Size);
io_seek(m_File, CurSeek, IOSEEK_START);
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;
}
@ -937,7 +926,11 @@ int CDemoPlayer::SetPos(int WantedTick)
KeyFrame--;
// seek to the correct key frame
io_seek(m_File, m_vKeyFrames[KeyFrame].m_Filepos, IOSEEK_START);
if(io_seek(m_File, m_vKeyFrames[KeyFrame].m_Filepos, IOSEEK_START) != 0)
{
Stop("Error seeking keyframe position");
return -1;
}
m_Info.m_NextTick = -1;
m_Info.m_Info.m_CurrentTick = -1;
@ -1010,7 +1003,7 @@ int CDemoPlayer::Update(bool RealTime)
return 0;
}
int CDemoPlayer::Stop()
void CDemoPlayer::Stop(const char *pErrorMessage)
{
#if defined(CONF_VIDEORECORDER)
if(m_UseVideo && IVideo::Current())
@ -1018,15 +1011,23 @@ int CDemoPlayer::Stop()
#endif
if(!m_File)
return -1;
return;
if(m_pConsole)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", "Stopped playback");
{
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);
}
io_close(m_File);
m_File = 0;
m_vKeyFrames.clear();
str_copy(m_aFilename, "");
return 0;
str_copy(m_aErrorMessage, pErrorMessage);
}
void CDemoPlayer::GetDemoName(char *pBuffer, size_t BufferSize) const
@ -1034,7 +1035,7 @@ void CDemoPlayer::GetDemoName(char *pBuffer, size_t BufferSize) const
IStorage::StripPathAndExtension(m_aFilename, pBuffer, BufferSize);
}
bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo) const
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));
@ -1042,45 +1043,87 @@ bool CDemoPlayer::GetDemoInfo(class IStorage *pStorage, const char *pFilename, i
IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
return false;
if(io_read(File, pDemoHeader, sizeof(CDemoHeader)) != sizeof(CDemoHeader) || !pDemoHeader->Valid() || pDemoHeader->m_Version < gs_OldVersion)
{
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(pDemoHeader->m_Version < gs_OldVersion)
{
io_read(File, pTimelineMarkers, sizeof(CTimelineMarkers));
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;
}
}
str_copy(pMapInfo->m_aName, pDemoHeader->m_aMapName);
pMapInfo->m_Crc = bytes_be_to_uint(pDemoHeader->m_aMapCrc);
SHA256_DIGEST Sha256 = SHA256_ZEROED;
if(pDemoHeader->m_Version >= gs_Sha256Version)
{
CUuid ExtensionUuid = {};
io_read(File, &ExtensionUuid.m_aData, sizeof(ExtensionUuid.m_aData));
if(ExtensionUuid == SHA256_EXTENSION)
const unsigned ExtensionUuidSize = io_read(File, &ExtensionUuid.m_aData, sizeof(ExtensionUuid.m_aData));
if(ExtensionUuidSize == sizeof(ExtensionUuid.m_aData) && ExtensionUuid == SHA256_EXTENSION)
{
io_read(File, &Sha256, sizeof(SHA256_DIGEST)); // need a safe read
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;
}
}
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);
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;
}
}
}
pMapInfo->m_Sha256 = Sha256;
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);
if(pFile == nullptr)
io_close(File);
else
*pFile = File;
return true;
}

View file

@ -107,6 +107,7 @@ private:
IOHANDLE m_File;
long m_MapOffset;
char m_aFilename[IO_MAX_PATH_LENGTH];
char m_aErrorMessage[256];
std::vector<SKeyFrame> m_vKeyFrames;
CMapInfo m_MapInfo;
int m_SpeedIndex;
@ -123,9 +124,15 @@ private:
bool m_UseVideo;
bool m_WasRecording = false;
int ReadChunkHeader(int *pType, int *pSize, int *pTick);
enum EReadChunkHeaderResult
{
CHUNKHEADER_SUCCESS,
CHUNKHEADER_ERROR,
CHUNKHEADER_EOF,
};
EReadChunkHeaderResult ReadChunkHeader(int *pType, int *pSize, int *pTick);
void DoTick();
void ScanFile();
bool ScanFile();
int64_t Time();
@ -144,7 +151,7 @@ public:
int Play();
void Pause() override;
void Unpause() override;
int Stop();
void Stop(const char *pErrorMessage = "");
void SetSpeed(float Speed) override;
void SetSpeedIndex(int SpeedIndex) override;
void AdjustSpeedIndex(int Offset) override;
@ -154,8 +161,9 @@ public:
int SetPos(int WantedTick) override;
const CInfo *BaseInfo() const override { return &m_Info.m_Info; }
void GetDemoName(char *pBuffer, size_t BufferSize) const override;
bool GetDemoInfo(class IStorage *pStorage, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo) const override;
const char *GetDemoFileName() { return m_aFilename; }
bool GetDemoInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo, IOHANDLE *pFile = nullptr, char *pErrorMessage = nullptr, size_t ErrorMessageSize = 0) const override;
const char *Filename() { return m_aFilename; }
const char *ErrorMessage() { return m_aErrorMessage; }
int Update(bool RealTime = true);

View file

@ -3,15 +3,21 @@
struct SWarning
{
SWarning() :
m_WasShown(false) {}
SWarning(const char *pMsg) :
m_WasShown(false)
SWarning() {}
SWarning(const char *pMsg)
{
str_copy(m_aWarningMsg, pMsg, sizeof(m_aWarningMsg));
str_copy(m_aWarningTitle, "");
str_copy(m_aWarningMsg, pMsg);
}
SWarning(const char *pTitle, const char *pMsg)
{
str_copy(m_aWarningTitle, pTitle);
str_copy(m_aWarningMsg, pMsg);
}
char m_aWarningTitle[128];
char m_aWarningMsg[256];
bool m_WasShown;
bool m_WasShown = false;
bool m_AutoHide = true;
};
#endif

View file

@ -1786,7 +1786,7 @@ int CMenus::Render()
Part.VMargin(120.0f, &Part);
static CButtonContainer s_Button;
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (time_get_nanoseconds() - m_PopupWarningLastTime >= m_PopupWarningDuration))
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (m_PopupWarningDuration > 0s && time_get_nanoseconds() - m_PopupWarningLastTime >= m_PopupWarningDuration))
{
m_Popup = POPUP_NONE;
SetActive(false);
@ -1839,7 +1839,7 @@ void CMenus::PopupConfirmDemoReplaceVideo()
if(pError)
{
m_DemoRenderInput.Clear();
PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok"));
PopupMessage(Localize("Error loading demo"), pError, Localize("Ok"));
}
}
#endif

View file

@ -1041,7 +1041,7 @@ bool CMenus::FetchHeader(CDemoItem &Item)
{
char aBuffer[IO_MAX_PATH_LENGTH];
str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_aCurrentDemoFolder, Item.m_aFilename);
Item.m_Valid = DemoPlayer()->GetDemoInfo(Storage(), aBuffer, Item.m_StorageType, &Item.m_Info, &Item.m_TimelineMarkers, &Item.m_MapInfo);
Item.m_Valid = DemoPlayer()->GetDemoInfo(Storage(), nullptr, aBuffer, Item.m_StorageType, &Item.m_Info, &Item.m_TimelineMarkers, &Item.m_MapInfo);
Item.m_InfosLoaded = true;
}
return Item.m_Valid;
@ -1505,7 +1505,9 @@ void CMenus::RenderDemoBrowserButtons(CUIRect ButtonsView, bool WasListboxItemAc
m_LastPauseChange = -1.0f;
m_LastSpeedChange = -1.0f;
if(pError)
PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok"));
{
PopupMessage(Localize("Error loading demo"), pError, Localize("Ok"));
}
else
{
UI()->SetActiveItem(nullptr);

View file

@ -673,9 +673,9 @@ void CGameClient::OnRender()
// display gfx & client warnings
for(SWarning *pWarning : {Graphics()->GetCurWarning(), Client()->GetCurWarning()})
{
if(pWarning != NULL && m_Menus.CanDisplayWarning())
if(pWarning != nullptr && m_Menus.CanDisplayWarning())
{
m_Menus.PopupWarning(Localize("Warning"), pWarning->m_aWarningMsg, "Ok", 10s);
m_Menus.PopupWarning(pWarning->m_aWarningTitle[0] == '\0' ? Localize("Warning") : pWarning->m_aWarningTitle, pWarning->m_aWarningMsg, "Ok", pWarning->m_AutoHide ? 10s : 0s);
pWarning->m_WasShown = true;
}
}