mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-20 09:34:19 +00:00
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:
commit
5987d8d248
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue