mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Update ghost format to v6: Replace CRC with SHA256
To be friendly to other implementors of the format, the CRC field is now zeroed out instead of completely gone. Drop support for v2 and v3 of the ghost format, the ghost files should have been automatically converted by the client already.
This commit is contained in:
parent
6ff53358d3
commit
070504b5c8
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "graphics.h"
|
||||
#include "message.h"
|
||||
#include <base/hash.h>
|
||||
#include <engine/friends.h>
|
||||
|
||||
struct SWarning;
|
||||
|
@ -193,7 +194,8 @@ public:
|
|||
|
||||
virtual const char *GetCurrentMap() = 0;
|
||||
virtual const char *GetCurrentMapPath() = 0;
|
||||
virtual unsigned GetMapCrc() = 0;
|
||||
virtual SHA256_DIGEST GetCurrentMapSha256() = 0;
|
||||
virtual unsigned GetCurrentMapCrc() = 0;
|
||||
|
||||
virtual int GetCurrentRaceTime() = 0;
|
||||
|
||||
|
|
|
@ -4371,7 +4371,12 @@ const char *CClient::GetCurrentMapPath()
|
|||
return m_aCurrentMapPath;
|
||||
}
|
||||
|
||||
unsigned CClient::GetMapCrc()
|
||||
SHA256_DIGEST CClient::GetCurrentMapSha256()
|
||||
{
|
||||
return m_pMap->Sha256();
|
||||
}
|
||||
|
||||
unsigned CClient::GetCurrentMapCrc()
|
||||
{
|
||||
return m_pMap->Crc();
|
||||
}
|
||||
|
|
|
@ -471,7 +471,8 @@ public:
|
|||
|
||||
virtual const char *GetCurrentMap();
|
||||
virtual const char *GetCurrentMapPath();
|
||||
virtual unsigned GetMapCrc();
|
||||
virtual SHA256_DIGEST GetCurrentMapSha256();
|
||||
virtual unsigned GetCurrentMapCrc();
|
||||
|
||||
virtual void RaceRecord_Start(const char *pFilename);
|
||||
virtual void RaceRecord_Stop();
|
||||
|
|
|
@ -1,31 +1,19 @@
|
|||
#ifndef ENGINE_GHOST_H
|
||||
#define ENGINE_GHOST_H
|
||||
|
||||
#include <base/hash.h>
|
||||
#include <engine/map.h>
|
||||
#include <engine/shared/protocol.h>
|
||||
|
||||
#include "kernel.h"
|
||||
|
||||
class CGhostHeader
|
||||
class CGhostInfo
|
||||
{
|
||||
public:
|
||||
unsigned char m_aMarker[8];
|
||||
unsigned char m_Version;
|
||||
char m_aOwner[MAX_NAME_LENGTH];
|
||||
char m_aMap[64];
|
||||
unsigned char m_aCrc[4];
|
||||
unsigned char m_aNumTicks[4];
|
||||
unsigned char m_aTime[4];
|
||||
|
||||
int GetTime() const
|
||||
{
|
||||
return (m_aTime[0] << 24) | (m_aTime[1] << 16) | (m_aTime[2] << 8) | (m_aTime[3]);
|
||||
}
|
||||
|
||||
int GetTicks() const
|
||||
{
|
||||
return (m_aNumTicks[0] << 24) | (m_aNumTicks[1] << 16) | (m_aNumTicks[2] << 8) | (m_aNumTicks[3]);
|
||||
}
|
||||
int m_NumTicks;
|
||||
int m_Time;
|
||||
};
|
||||
|
||||
class IGhostRecorder : public IInterface
|
||||
|
@ -34,7 +22,7 @@ class IGhostRecorder : public IInterface
|
|||
public:
|
||||
virtual ~IGhostRecorder() {}
|
||||
|
||||
virtual int Start(const char *pFilename, const char *pMap, unsigned MapCrc, const char *pName) = 0;
|
||||
virtual int Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName) = 0;
|
||||
virtual int Stop(int Ticks, int Time) = 0;
|
||||
|
||||
virtual void WriteData(int Type, const void *pData, int Size) = 0;
|
||||
|
@ -47,15 +35,15 @@ class IGhostLoader : public IInterface
|
|||
public:
|
||||
virtual ~IGhostLoader() {}
|
||||
|
||||
virtual int Load(const char *pFilename, const char *pMap, unsigned Crc) = 0;
|
||||
virtual int Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) = 0;
|
||||
virtual void Close() = 0;
|
||||
|
||||
virtual const CGhostHeader *GetHeader() const = 0;
|
||||
virtual const CGhostInfo *GetInfo() const = 0;
|
||||
|
||||
virtual bool ReadNextType(int *pType) = 0;
|
||||
virtual bool ReadData(int Type, void *pData, int Size) = 0;
|
||||
|
||||
virtual bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc) = 0;
|
||||
virtual bool GetGhostInfo(const char *pFilename, CGhostInfo *pInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "network.h"
|
||||
|
||||
static const unsigned char gs_aHeaderMarker[8] = {'T', 'W', 'G', 'H', 'O', 'S', 'T', 0};
|
||||
static const unsigned char gs_ActVersion = 5;
|
||||
static const unsigned char gs_CurVersion = 6;
|
||||
static const int gs_NumTicksOffset = 93;
|
||||
|
||||
CGhostRecorder::CGhostRecorder()
|
||||
|
@ -24,7 +24,7 @@ void CGhostRecorder::Init()
|
|||
}
|
||||
|
||||
// Record
|
||||
int CGhostRecorder::Start(const char *pFilename, const char *pMap, unsigned Crc, const char *pName)
|
||||
int CGhostRecorder::Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName)
|
||||
{
|
||||
m_File = m_pStorage->OpenFile(pFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
||||
if(!m_File)
|
||||
|
@ -39,20 +39,17 @@ int CGhostRecorder::Start(const char *pFilename, const char *pMap, unsigned Crc,
|
|||
CGhostHeader Header;
|
||||
mem_zero(&Header, sizeof(Header));
|
||||
mem_copy(Header.m_aMarker, gs_aHeaderMarker, sizeof(Header.m_aMarker));
|
||||
Header.m_Version = gs_ActVersion;
|
||||
Header.m_Version = gs_CurVersion;
|
||||
str_copy(Header.m_aOwner, pName, sizeof(Header.m_aOwner));
|
||||
str_copy(Header.m_aMap, pMap, sizeof(Header.m_aMap));
|
||||
Header.m_aCrc[0] = (Crc >> 24) & 0xff;
|
||||
Header.m_aCrc[1] = (Crc >> 16) & 0xff;
|
||||
Header.m_aCrc[2] = (Crc >> 8) & 0xff;
|
||||
Header.m_aCrc[3] = (Crc)&0xff;
|
||||
Header.m_MapSha256 = MapSha256;
|
||||
io_write(m_File, &Header, sizeof(Header));
|
||||
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "Ghost recording to '%s'", pFilename);
|
||||
str_format(aBuf, sizeof(aBuf), "ghost recording to '%s'", pFilename);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf);
|
||||
return 0;
|
||||
}
|
||||
|
@ -185,7 +182,7 @@ void CGhostLoader::ResetBuffer()
|
|||
m_BufferPrevItem = -1;
|
||||
}
|
||||
|
||||
int CGhostLoader::Load(const char *pFilename, const char *pMap, unsigned Crc)
|
||||
int CGhostLoader::Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc)
|
||||
{
|
||||
m_File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
if(!m_File)
|
||||
|
@ -209,7 +206,7 @@ int CGhostLoader::Load(const char *pFilename, const char *pMap, unsigned Crc)
|
|||
return -1;
|
||||
}
|
||||
|
||||
if(m_Header.m_Version != gs_ActVersion && m_Header.m_Version != 4)
|
||||
if(!(4 <= m_Header.m_Version && m_Header.m_Version <= gs_CurVersion))
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost version %d is not supported", m_Header.m_Version);
|
||||
|
@ -219,14 +216,47 @@ int CGhostLoader::Load(const char *pFilename, const char *pMap, unsigned Crc)
|
|||
return -1;
|
||||
}
|
||||
|
||||
unsigned GhostMapCrc = (m_Header.m_aCrc[0] << 24) | (m_Header.m_aCrc[1] << 16) | (m_Header.m_aCrc[2] << 8) | (m_Header.m_aCrc[3]);
|
||||
if(str_comp(m_Header.m_aMap, pMap) != 0 || GhostMapCrc != Crc)
|
||||
if(str_comp(m_Header.m_aMap, pMap) != 0)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map name '%s' does not match current map '%s'", m_Header.m_aMap, pMap);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
}
|
||||
if(m_Header.m_Version >= 6)
|
||||
{
|
||||
if(m_Header.m_MapSha256 != MapSha256)
|
||||
{
|
||||
char aGhostSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(m_Header.m_MapSha256, aGhostSha256, sizeof(aGhostSha256));
|
||||
char aMapSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(MapSha256, aMapSha256, sizeof(aMapSha256));
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map '%s' sha256 mismatch, wanted=%s ghost=%s", pMap, aMapSha256, aGhostSha256);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
io_skip(m_File, -(int)sizeof(SHA256_DIGEST));
|
||||
unsigned GhostMapCrc = (m_Header.m_aZeroes[0] << 24) | (m_Header.m_aZeroes[1] << 16) | (m_Header.m_aZeroes[2] << 8) | (m_Header.m_aZeroes[3]);
|
||||
if(str_comp(m_Header.m_aMap, pMap) != 0 || GhostMapCrc != MapCrc)
|
||||
{
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "ghost map '%s' crc mismatch, wanted=%08x ghost=%08x", pMap, MapCrc, GhostMapCrc);
|
||||
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_loader", aBuf);
|
||||
io_close(m_File);
|
||||
m_File = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
m_Info = m_Header.ToGhostInfo();
|
||||
m_LastItem.Reset();
|
||||
ResetBuffer();
|
||||
|
||||
|
@ -336,177 +366,41 @@ void CGhostLoader::Close()
|
|||
m_File = 0;
|
||||
}
|
||||
|
||||
bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc)
|
||||
bool CGhostLoader::GetGhostInfo(const char *pFilename, CGhostInfo *pInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc)
|
||||
{
|
||||
if(!pGhostHeader)
|
||||
return false;
|
||||
|
||||
mem_zero(pGhostHeader, sizeof(CGhostHeader));
|
||||
CGhostHeader Header;
|
||||
mem_zero(&Header, sizeof(Header));
|
||||
|
||||
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
if(!File)
|
||||
return false;
|
||||
|
||||
io_read(File, pGhostHeader, sizeof(CGhostHeader));
|
||||
|
||||
if(mem_comp(pGhostHeader->m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) == 0 && (pGhostHeader->m_Version == 2 || pGhostHeader->m_Version == 3))
|
||||
{
|
||||
io_close(File);
|
||||
// old version... try to update
|
||||
IGhostRecorder *pRecorder = Kernel()->RequestInterface<IGhostRecorder>();
|
||||
if(CGhostUpdater::Update(pRecorder, m_pStorage, m_pConsole, pFilename))
|
||||
{
|
||||
// try again
|
||||
File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
io_read(File, pGhostHeader, sizeof(CGhostHeader));
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
io_read(File, &Header, sizeof(Header));
|
||||
io_close(File);
|
||||
|
||||
if(mem_comp(pGhostHeader->m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) || (pGhostHeader->m_Version != gs_ActVersion && pGhostHeader->m_Version != 4))
|
||||
if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) || !(4 <= Header.m_Version && Header.m_Version <= gs_CurVersion))
|
||||
return false;
|
||||
|
||||
unsigned GhostMapCrc = (pGhostHeader->m_aCrc[0] << 24) | (pGhostHeader->m_aCrc[1] << 16) | (pGhostHeader->m_aCrc[2] << 8) | (pGhostHeader->m_aCrc[3]);
|
||||
if(str_comp(pGhostHeader->m_aMap, pMap) != 0 || GhostMapCrc != Crc)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void StrToInts(int *pInts, int Num, const char *pStr)
|
||||
{
|
||||
int Index = 0;
|
||||
while(Num)
|
||||
if(str_comp(Header.m_aMap, pMap) != 0)
|
||||
{
|
||||
char aBuf[4] = {0, 0, 0, 0};
|
||||
for(int c = 0; c < 4 && pStr[Index]; c++, Index++)
|
||||
aBuf[c] = pStr[Index];
|
||||
*pInts = ((aBuf[0] + 128) << 24) | ((aBuf[1] + 128) << 16) | ((aBuf[2] + 128) << 8) | (aBuf[3] + 128);
|
||||
pInts++;
|
||||
Num--;
|
||||
}
|
||||
|
||||
// null terminate
|
||||
pInts[-1] &= 0xffffff00;
|
||||
}
|
||||
|
||||
bool CGhostUpdater::Update(class IGhostRecorder *pRecorder, class IStorage *pStorage, class IConsole *pConsole, const char *pFilename)
|
||||
{
|
||||
pStorage->CreateFolder("ghosts/backup", IStorage::TYPE_SAVE);
|
||||
|
||||
const char *pExtractedName = pFilename;
|
||||
for(const char *pSrc = pFilename; *pSrc; pSrc++)
|
||||
if(*pSrc == '/' || *pSrc == '\\')
|
||||
pExtractedName = pSrc + 1;
|
||||
|
||||
char aBackupFilename[512];
|
||||
str_format(aBackupFilename, sizeof(aBackupFilename), "ghosts/backup/%s", pExtractedName);
|
||||
if(!pStorage->RenameFile(pFilename, aBackupFilename, IStorage::TYPE_SAVE))
|
||||
return false;
|
||||
|
||||
IOHANDLE File = pStorage->OpenFile(aBackupFilename, IOFLAG_READ, IStorage::TYPE_SAVE);
|
||||
if(!File)
|
||||
return false;
|
||||
|
||||
// read header
|
||||
CGhostHeaderMain Header;
|
||||
io_read(File, &Header, sizeof(Header));
|
||||
if(mem_comp(Header.m_aMarker, gs_aHeaderMarker, sizeof(gs_aHeaderMarker)) != 0 || (Header.m_Version != 2 && Header.m_Version != 3))
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "error: no valid ghost file");
|
||||
io_close(File);
|
||||
return false;
|
||||
}
|
||||
|
||||
io_seek(File, 0, IOSEEK_START);
|
||||
|
||||
int Ticks, Time;
|
||||
if(Header.m_Version == 2)
|
||||
if(Header.m_Version >= 6)
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "updating v2 ghost file");
|
||||
CGhostHeaderV2 ExtHeader;
|
||||
char aSkinData[ms_SkinSizeV2];
|
||||
io_read(File, &ExtHeader, sizeof(ExtHeader));
|
||||
io_read(File, aSkinData, sizeof(aSkinData));
|
||||
|
||||
Ticks = ExtHeader.m_NumShots;
|
||||
Time = ExtHeader.m_Time * 1000;
|
||||
|
||||
unsigned Crc = (ExtHeader.m_aCrc[0] << 24) | (ExtHeader.m_aCrc[1] << 16) | (ExtHeader.m_aCrc[2] << 8) | (ExtHeader.m_aCrc[3]);
|
||||
pRecorder->Start(pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner);
|
||||
|
||||
CGhostSkin Skin;
|
||||
mem_copy(&Skin, aSkinData + ms_SkinOffsetV2, sizeof(Skin));
|
||||
pRecorder->WriteData(0 /* GHOSTDATA_TYPE_SKIN */, &Skin, sizeof(Skin));
|
||||
if(Header.m_MapSha256 != MapSha256)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "updating v3 ghost file");
|
||||
CGhostHeaderV3 ExtHeader;
|
||||
io_read(File, &ExtHeader, sizeof(ExtHeader));
|
||||
|
||||
Ticks = ExtHeader.m_NumShots;
|
||||
Time = ExtHeader.m_Time * 1000;
|
||||
|
||||
unsigned Crc = (ExtHeader.m_aCrc[0] << 24) | (ExtHeader.m_aCrc[1] << 16) | (ExtHeader.m_aCrc[2] << 8) | (ExtHeader.m_aCrc[3]);
|
||||
pRecorder->Start(pFilename, ExtHeader.m_aMap, Crc, ExtHeader.m_aOwner);
|
||||
|
||||
CGhostSkin Skin;
|
||||
StrToInts(&Skin.m_Skin0, 6, ExtHeader.m_aSkinName);
|
||||
Skin.m_UseCustomColor = ExtHeader.m_UseCustomColor;
|
||||
Skin.m_ColorBody = ExtHeader.m_ColorBody;
|
||||
Skin.m_ColorFeet = ExtHeader.m_ColorFeet;
|
||||
pRecorder->WriteData(0 /* GHOSTDATA_TYPE_SKIN */, &Skin, sizeof(Skin));
|
||||
}
|
||||
|
||||
// read data
|
||||
int Index = 0;
|
||||
while(Index < Ticks)
|
||||
{
|
||||
static char s_aCompresseddata[100 * 500];
|
||||
static char s_aDecompressed[100 * 500];
|
||||
static char s_aData[100 * 500];
|
||||
|
||||
unsigned char aSize[4];
|
||||
if(io_read(File, aSize, sizeof(aSize)) != sizeof(aSize))
|
||||
break;
|
||||
unsigned Size = (aSize[0] << 24) | (aSize[1] << 16) | (aSize[2] << 8) | aSize[3];
|
||||
|
||||
if(io_read(File, s_aCompresseddata, Size) != Size)
|
||||
unsigned GhostMapCrc = (Header.m_aZeroes[0] << 24) | (Header.m_aZeroes[1] << 16) | (Header.m_aZeroes[2] << 8) | (Header.m_aZeroes[3]);
|
||||
if(GhostMapCrc != MapCrc)
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "error reading chunk");
|
||||
break;
|
||||
}
|
||||
|
||||
int DataSize = CNetBase::Decompress(s_aCompresseddata, Size, s_aDecompressed, sizeof(s_aDecompressed));
|
||||
if(DataSize < 0)
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "error during network decompression");
|
||||
break;
|
||||
}
|
||||
|
||||
DataSize = CVariableInt::Decompress(s_aDecompressed, DataSize, s_aData, sizeof(s_aData));
|
||||
if(DataSize < 0)
|
||||
{
|
||||
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost/updater", "error during intpack decompression");
|
||||
break;
|
||||
}
|
||||
|
||||
char *pTmp = s_aData;
|
||||
for(int i = 0; i < DataSize / ms_GhostCharacterSize; i++)
|
||||
{
|
||||
pRecorder->WriteData(1 /* GHOSTDATA_TYPE_CHARACTER_NO_TICK */, pTmp, ms_GhostCharacterSize);
|
||||
pTmp += ms_GhostCharacterSize;
|
||||
Index++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*pInfo = Header.ToGhostInfo();
|
||||
|
||||
io_close(File);
|
||||
|
||||
bool Error = Ticks != Index;
|
||||
pRecorder->Stop(Index, Error ? 0 : Time);
|
||||
return !Error;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,40 @@ enum
|
|||
NUM_ITEMS_PER_CHUNK = 50,
|
||||
};
|
||||
|
||||
// version 4-6
|
||||
struct CGhostHeader
|
||||
{
|
||||
unsigned char m_aMarker[8];
|
||||
unsigned char m_Version;
|
||||
char m_aOwner[MAX_NAME_LENGTH];
|
||||
char m_aMap[64];
|
||||
unsigned char m_aZeroes[4]; // Crc before version 6
|
||||
unsigned char m_aNumTicks[4];
|
||||
unsigned char m_aTime[4];
|
||||
SHA256_DIGEST m_MapSha256;
|
||||
|
||||
int GetTicks() const
|
||||
{
|
||||
return (m_aNumTicks[0] << 24) | (m_aNumTicks[1] << 16) | (m_aNumTicks[2] << 8) | (m_aNumTicks[3]);
|
||||
}
|
||||
|
||||
int GetTime() const
|
||||
{
|
||||
return (m_aTime[0] << 24) | (m_aTime[1] << 16) | (m_aTime[2] << 8) | (m_aTime[3]);
|
||||
}
|
||||
|
||||
CGhostInfo ToGhostInfo() const
|
||||
{
|
||||
CGhostInfo Result;
|
||||
mem_zero(&Result, sizeof(Result));
|
||||
str_copy(Result.m_aOwner, m_aOwner, sizeof(Result.m_aOwner));
|
||||
str_copy(Result.m_aMap, m_aMap, sizeof(Result.m_aMap));
|
||||
Result.m_NumTicks = GetTicks();
|
||||
Result.m_Time = GetTime();
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
|
||||
class CGhostItem
|
||||
{
|
||||
public:
|
||||
|
@ -42,7 +76,7 @@ public:
|
|||
|
||||
void Init();
|
||||
|
||||
int Start(const char *pFilename, const char *pMap, unsigned MapCrc, const char *pName);
|
||||
int Start(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, const char *pName);
|
||||
int Stop(int Ticks, int Time);
|
||||
|
||||
void WriteData(int Type, const void *pData, int Size);
|
||||
|
@ -56,6 +90,7 @@ class CGhostLoader : public IGhostLoader
|
|||
class IStorage *m_pStorage;
|
||||
|
||||
CGhostHeader m_Header;
|
||||
CGhostInfo m_Info;
|
||||
|
||||
CGhostItem m_LastItem;
|
||||
|
||||
|
@ -73,74 +108,13 @@ public:
|
|||
|
||||
void Init();
|
||||
|
||||
int Load(const char *pFilename, const char *pMap, unsigned Crc);
|
||||
int Load(const char *pFilename, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc);
|
||||
void Close();
|
||||
const CGhostHeader *GetHeader() const { return &m_Header; }
|
||||
const CGhostInfo *GetInfo() const { return &m_Info; }
|
||||
|
||||
bool ReadNextType(int *pType);
|
||||
bool ReadData(int Type, void *pData, int Size);
|
||||
|
||||
bool GetGhostInfo(const char *pFilename, CGhostHeader *pGhostHeader, const char *pMap, unsigned Crc);
|
||||
bool GetGhostInfo(const char *pFilename, CGhostInfo *pGhostInfo, const char *pMap, SHA256_DIGEST MapSha256, unsigned MapCrc);
|
||||
};
|
||||
|
||||
class CGhostUpdater
|
||||
{
|
||||
// all
|
||||
struct CGhostHeaderMain
|
||||
{
|
||||
unsigned char m_aMarker[8];
|
||||
unsigned char m_Version;
|
||||
};
|
||||
|
||||
// version 2
|
||||
struct CGhostHeaderV2
|
||||
{
|
||||
unsigned char m_aMarker[8];
|
||||
unsigned char m_Version;
|
||||
char m_aOwner[MAX_NAME_LENGTH];
|
||||
char m_aMap[64];
|
||||
unsigned char m_aCrc[4];
|
||||
int m_NumShots;
|
||||
float m_Time;
|
||||
};
|
||||
|
||||
static const int ms_SkinSizeV2 = 17 * sizeof(int);
|
||||
static const int ms_SkinOffsetV2 = 8 * sizeof(int);
|
||||
|
||||
// version 3
|
||||
struct CGhostHeaderV3
|
||||
{
|
||||
unsigned char m_aMarker[8];
|
||||
unsigned char m_Version;
|
||||
char m_aOwner[MAX_NAME_LENGTH];
|
||||
char m_aSkinName[64];
|
||||
int m_UseCustomColor;
|
||||
int m_ColorBody;
|
||||
int m_ColorFeet;
|
||||
char m_aMap[64];
|
||||
unsigned char m_aCrc[4];
|
||||
int m_NumShots;
|
||||
float m_Time;
|
||||
};
|
||||
|
||||
// actual version
|
||||
struct CGhostSkin
|
||||
{
|
||||
int m_Skin0;
|
||||
int m_Skin1;
|
||||
int m_Skin2;
|
||||
int m_Skin3;
|
||||
int m_Skin4;
|
||||
int m_Skin5;
|
||||
int m_UseCustomColor;
|
||||
int m_ColorBody;
|
||||
int m_ColorFeet;
|
||||
};
|
||||
|
||||
static const int ms_GhostCharacterSize = 11 * sizeof(int);
|
||||
|
||||
public:
|
||||
static bool Update(class IGhostRecorder *pRecorder, class IStorage *pStorage, class IConsole *pConsole, const char *pFilename);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -119,16 +119,18 @@ CGhostCharacter *CGhost::CGhostPath::Get(int Index)
|
|||
void CGhost::GetPath(char *pBuf, int Size, const char *pPlayerName, int Time) const
|
||||
{
|
||||
const char *pMap = Client()->GetCurrentMap();
|
||||
unsigned Crc = Client()->GetMapCrc();
|
||||
SHA256_DIGEST Sha256 = Client()->GetCurrentMapSha256();
|
||||
char aSha256[SHA256_MAXSTRSIZE];
|
||||
sha256_str(Sha256, aSha256, sizeof(aSha256));
|
||||
|
||||
char aPlayerName[MAX_NAME_LENGTH];
|
||||
str_copy(aPlayerName, pPlayerName, sizeof(aPlayerName));
|
||||
str_sanitize_filename(aPlayerName);
|
||||
|
||||
if(Time < 0)
|
||||
str_format(pBuf, Size, "%s/%s_%s_%08x_tmp_%d.gho", ms_pGhostDir, pMap, aPlayerName, Crc, pid());
|
||||
str_format(pBuf, Size, "%s/%s_%s_%s_tmp_%d.gho", ms_pGhostDir, pMap, aPlayerName, aSha256, pid());
|
||||
else
|
||||
str_format(pBuf, Size, "%s/%s_%s_%d.%03d_%08x.gho", ms_pGhostDir, pMap, aPlayerName, Time / 1000, Time % 1000, Crc);
|
||||
str_format(pBuf, Size, "%s/%s_%s_%d.%03d_%s.gho", ms_pGhostDir, pMap, aPlayerName, Time / 1000, Time % 1000, aSha256);
|
||||
}
|
||||
|
||||
void CGhost::AddInfos(const CNetObj_Character *pChar)
|
||||
|
@ -139,7 +141,7 @@ void CGhost::AddInfos(const CNetObj_Character *pChar)
|
|||
if(g_Config.m_ClRaceSaveGhost && !GhostRecorder()->IsRecording() && NumTicks > 0)
|
||||
{
|
||||
GetPath(m_aTmpFilename, sizeof(m_aTmpFilename), m_CurGhost.m_aPlayer);
|
||||
GhostRecorder()->Start(m_aTmpFilename, Client()->GetCurrentMap(), Client()->GetMapCrc(), m_CurGhost.m_aPlayer);
|
||||
GhostRecorder()->Start(m_aTmpFilename, Client()->GetCurrentMap(), Client()->GetCurrentMapSha256(), m_CurGhost.m_aPlayer);
|
||||
|
||||
GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, &m_CurGhost.m_StartTick, sizeof(int));
|
||||
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &m_CurGhost.m_Skin, sizeof(CGhostSkin));
|
||||
|
@ -445,15 +447,14 @@ int CGhost::Load(const char *pFilename)
|
|||
if(Slot == -1)
|
||||
return -1;
|
||||
|
||||
if(GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetMapCrc()) != 0)
|
||||
if(GhostLoader()->Load(pFilename, Client()->GetCurrentMap(), Client()->GetCurrentMapSha256(), Client()->GetCurrentMapCrc()) != 0)
|
||||
return -1;
|
||||
|
||||
const CGhostHeader *pHeader = GhostLoader()->GetHeader();
|
||||
const CGhostInfo *pInfo = GhostLoader()->GetInfo();
|
||||
|
||||
int NumTicks = pHeader->GetTicks();
|
||||
int Time = pHeader->GetTime();
|
||||
if(NumTicks <= 0 || Time <= 0)
|
||||
if(pInfo->m_NumTicks <= 0 || pInfo->m_Time <= 0)
|
||||
{
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "invalid header info");
|
||||
GhostLoader()->Close();
|
||||
return -1;
|
||||
}
|
||||
|
@ -461,9 +462,9 @@ int CGhost::Load(const char *pFilename)
|
|||
// select ghost
|
||||
CGhostItem *pGhost = &m_aActiveGhosts[Slot];
|
||||
pGhost->Reset();
|
||||
pGhost->m_Path.SetSize(NumTicks);
|
||||
pGhost->m_Path.SetSize(pInfo->m_NumTicks);
|
||||
|
||||
str_copy(pGhost->m_aPlayer, pHeader->m_aOwner, sizeof(pGhost->m_aPlayer));
|
||||
str_copy(pGhost->m_aPlayer, pInfo->m_aOwner, sizeof(pGhost->m_aPlayer));
|
||||
|
||||
int Index = 0;
|
||||
bool FoundSkin = false;
|
||||
|
@ -473,7 +474,7 @@ int CGhost::Load(const char *pFilename)
|
|||
int Type;
|
||||
while(!Error && GhostLoader()->ReadNextType(&Type))
|
||||
{
|
||||
if(Index == NumTicks && (Type == GHOSTDATA_TYPE_CHARACTER || Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK))
|
||||
if(Index == pInfo->m_NumTicks && (Type == GHOSTDATA_TYPE_CHARACTER || Type == GHOSTDATA_TYPE_CHARACTER_NO_TICK))
|
||||
{
|
||||
Error = true;
|
||||
break;
|
||||
|
@ -505,8 +506,9 @@ int CGhost::Load(const char *pFilename)
|
|||
|
||||
GhostLoader()->Close();
|
||||
|
||||
if(Error || Index != NumTicks)
|
||||
if(Error || Index != pInfo->m_NumTicks)
|
||||
{
|
||||
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost", "invalid ghost data");
|
||||
pGhost->Reset();
|
||||
return -1;
|
||||
}
|
||||
|
@ -514,10 +516,10 @@ int CGhost::Load(const char *pFilename)
|
|||
if(NoTick)
|
||||
{
|
||||
int StartTick = 0;
|
||||
for(int i = 1; i < NumTicks; i++) // estimate start tick
|
||||
for(int i = 1; i < pInfo->m_NumTicks; i++) // estimate start tick
|
||||
if(pGhost->m_Path.Get(i)->m_AttackTick != pGhost->m_Path.Get(i - 1)->m_AttackTick)
|
||||
StartTick = pGhost->m_Path.Get(i)->m_AttackTick - i;
|
||||
for(int i = 0; i < NumTicks; i++)
|
||||
for(int i = 0; i < pInfo->m_NumTicks; i++)
|
||||
pGhost->m_Path.Get(i)->m_Tick = StartTick + i;
|
||||
}
|
||||
|
||||
|
@ -552,7 +554,7 @@ void CGhost::SaveGhost(CMenus::CGhostItem *pItem)
|
|||
|
||||
int NumTicks = pGhost->m_Path.Size();
|
||||
GetPath(pItem->m_aFilename, sizeof(pItem->m_aFilename), pItem->m_aPlayer, pItem->m_Time);
|
||||
GhostRecorder()->Start(pItem->m_aFilename, Client()->GetCurrentMap(), Client()->GetMapCrc(), pItem->m_aPlayer);
|
||||
GhostRecorder()->Start(pItem->m_aFilename, Client()->GetCurrentMap(), Client()->GetCurrentMapSha256(), pItem->m_aPlayer);
|
||||
|
||||
GhostRecorder()->WriteData(GHOSTDATA_TYPE_START_TICK, &pGhost->m_StartTick, sizeof(int));
|
||||
GhostRecorder()->WriteData(GHOSTDATA_TYPE_SKIN, &pGhost->m_Skin, sizeof(CGhostSkin));
|
||||
|
|
|
@ -882,14 +882,14 @@ int CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType
|
|||
char aFilename[256];
|
||||
str_format(aFilename, sizeof(aFilename), "%s/%s", pSelf->m_pClient->m_pGhost->GetGhostDir(), pName);
|
||||
|
||||
CGhostHeader Header;
|
||||
if(!pSelf->m_pClient->m_pGhost->GhostLoader()->GetGhostInfo(aFilename, &Header, pMap, pSelf->Client()->GetMapCrc()))
|
||||
CGhostInfo Info;
|
||||
if(!pSelf->m_pClient->m_pGhost->GhostLoader()->GetGhostInfo(aFilename, &Info, pMap, pSelf->Client()->GetCurrentMapSha256(), pSelf->Client()->GetCurrentMapCrc()))
|
||||
return 0;
|
||||
|
||||
CGhostItem Item;
|
||||
str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename));
|
||||
str_copy(Item.m_aPlayer, Header.m_aOwner, sizeof(Item.m_aPlayer));
|
||||
Item.m_Time = Header.GetTime();
|
||||
str_copy(Item.m_aPlayer, Info.m_aOwner, sizeof(Item.m_aPlayer));
|
||||
Item.m_Time = Info.m_Time;
|
||||
if(Item.m_Time > 0)
|
||||
pSelf->m_lGhosts.add(Item);
|
||||
return 0;
|
||||
|
|
Loading…
Reference in a new issue