Use more secure hash function for map downloads

SHA256 was chosen because it is reasonably standard, the file names
don't explode in length (this rules out SHA512) and it is supported by
basically all versions of OpenSSL (this rules out SHA512/256 and SHA3).

The protocol is changed in a backward compatible way: The supporting
server sends the SHA256 corresponding to the map in the `MAP_DETAILS`
message prior to sending the `MAP_CHANGE` message. The client saves the
SHA256 obtained from the `MAP_DETAILS` message until the next
`MAP_CHANGE` message.

For servers not supporting this protocol, the client falls back to
simply opening maps like in the previous scheme.

Remove the `map_version` tool, it is not being used and would have been
a little bit effort to update.

Use the OpenSSL implementation of SHA256 if it is supported, otherwise
fall back to a public domain one.

Fix #1127.
This commit is contained in:
heinrich5991 2018-06-05 21:22:40 +02:00
parent 125377bf2d
commit ca8fcc823c
26 changed files with 626 additions and 144 deletions

View file

@ -262,6 +262,7 @@ if(NOT CMAKE_CROSSCOMPILING)
find_package(PkgConfig) find_package(PkgConfig)
endif() endif()
find_package(ZLIB) find_package(ZLIB)
find_package(Crypto)
find_package(Curl) find_package(Curl)
find_package(Freetype) find_package(Freetype)
if(DOWNLOAD_GTEST) if(DOWNLOAD_GTEST)
@ -338,6 +339,7 @@ if(MYSQL)
show_dependency_status("MySQL" MYSQL) show_dependency_status("MySQL" MYSQL)
endif() endif()
show_dependency_status("Ogg" OGG) show_dependency_status("Ogg" OGG)
show_dependency_status("OpenSSL Crypto" CRYPTO)
show_dependency_status("Opus" OPUS) show_dependency_status("Opus" OPUS)
show_dependency_status("Opusfile" OPUSFILE) show_dependency_status("Opusfile" OPUSFILE)
show_dependency_status("Pnglite" PNGLITE) show_dependency_status("Pnglite" PNGLITE)
@ -554,6 +556,11 @@ set_glob(BASE GLOB_RECURSE src/base
confusables.c confusables.c
confusables_data.h confusables_data.h
detect.h detect.h
hash.c
hash.h
hash_ctxt.h
hash_libtomcrypt.c
hash_openssl.c
math.h math.h
system.c system.c
system.h system.h
@ -695,7 +702,7 @@ list(APPEND TARGETS_DEP md5)
set(DEPS ${DEP_MD5} ${ZLIB_DEP}) set(DEPS ${DEP_MD5} ${ZLIB_DEP})
# Libraries # Libraries
set(LIBS ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS}) set(LIBS ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ${CRYPTO_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS})
# Targets # Targets
add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_GENERATED_SHARED} ${BASE}) add_library(engine-shared EXCLUDE_FROM_ALL OBJECT ${ENGINE_INTERFACE} ${ENGINE_SHARED} ${ENGINE_GENERATED_SHARED} ${BASE})
@ -1051,7 +1058,6 @@ set_glob(TOOLS GLOB src/tools
map_extract.cpp map_extract.cpp
map_replace_image.cpp map_replace_image.cpp
map_resave.cpp map_resave.cpp
map_version.cpp
packetgen.cpp packetgen.cpp
tileset_borderadd.cpp tileset_borderadd.cpp
tileset_borderfix.cpp tileset_borderfix.cpp
@ -1104,6 +1110,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
aio.cpp aio.cpp
fs.cpp fs.cpp
git_revision.cpp git_revision.cpp
hash.cpp
jobs.cpp jobs.cpp
json.cpp json.cpp
mapbugs.cpp mapbugs.cpp
@ -1511,6 +1518,9 @@ foreach(target ${TARGETS_OWN})
target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS})
target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_compile_definitions(${target} PRIVATE GLEW_STATIC) target_compile_definitions(${target} PRIVATE GLEW_STATIC)
if(CRYPTO_FOUND)
target_compile_definitions(${target} PRIVATE CONF_OPENSSL)
endif()
if(WEBSOCKETS) if(WEBSOCKETS)
target_compile_definitions(${target} PRIVATE CONF_WEBSOCKETS) target_compile_definitions(${target} PRIVATE CONF_WEBSOCKETS)
target_include_directories(${target} PRIVATE ${WEBSOCKETS_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${WEBSOCKETS_INCLUDE_DIRS})

19
cmake/FindCrypto.cmake Normal file
View file

@ -0,0 +1,19 @@
if(NOT PREFER_BUNDLED_LIBS)
find_package(OpenSSL)
if(OPENSSL_FOUND)
set(CRYPTO_FOUND ON)
set(CRYPTO_BUNDLED OFF)
set(CRYPTO_LIBRARY ${OPENSSL_CRYPTO_LIBRARY})
set(CRYPTO_INCLUDEDIR ${OPENSSL_INCLUDE_DIR})
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Crypto DEFAULT_MSG CRYPTO_LIBRARY CRYPTO_INCLUDEDIR)
mark_as_advanced(CRYPTO_LIBRARY CRYPTO_INCLUDEDIR)
if(CRYPTO_FOUND)
set(CRYPTO_LIBRARIES ${CRYPTO_LIBRARY})
set(CRYPTO_INCLUDE_DIRS ${CRYPTO_INCLUDEDIR})
endif()

41
src/base/hash.c Normal file
View file

@ -0,0 +1,41 @@
#include "hash.h"
#include "hash_ctxt.h"
#include "system.h"
SHA256_DIGEST sha256(const void *message, size_t message_len)
{
SHA256_CTX ctxt;
sha256_init(&ctxt);
sha256_update(&ctxt, message, message_len);
return sha256_finish(&ctxt);
}
void sha256_str(SHA256_DIGEST digest, char *str, size_t max_len)
{
unsigned i;
if(max_len > SHA256_MAXSTRSIZE)
{
max_len = SHA256_MAXSTRSIZE;
}
str[max_len - 1] = 0;
max_len -= 1;
for(i = 0; i < max_len; i++)
{
static const char HEX[] = "0123456789abcdef";
int index = i / 2;
if(i % 2 == 0)
{
str[i] = HEX[digest.data[index] >> 4];
}
else
{
str[i] = HEX[digest.data[index] & 0xf];
}
}
}
int sha256_comp(SHA256_DIGEST digest1, SHA256_DIGEST digest2)
{
return mem_comp(digest1.data, digest2.data, sizeof(digest1.data));
}

43
src/base/hash.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef BASE_HASH_H
#define BASE_HASH_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
enum
{
SHA256_DIGEST_LENGTH=256/8,
SHA256_MAXSTRSIZE=2*SHA256_DIGEST_LENGTH+1,
};
typedef struct
{
unsigned char data[SHA256_DIGEST_LENGTH];
} SHA256_DIGEST;
SHA256_DIGEST sha256(const void *message, size_t message_len);
void sha256_str(SHA256_DIGEST digest, char *str, size_t max_len);
int sha256_from_str(SHA256_DIGEST *out, const char *str);
int sha256_comp(SHA256_DIGEST digest1, SHA256_DIGEST digest2);
static const SHA256_DIGEST SHA256_ZEROED = {{0}};
#ifdef __cplusplus
}
#endif
#ifdef __cplusplus
inline bool operator==(const SHA256_DIGEST &that, const SHA256_DIGEST &other)
{
return sha256_comp(that, other) == 0;
}
inline bool operator!=(const SHA256_DIGEST &that, const SHA256_DIGEST &other)
{
return !(that == other);
}
#endif
#endif // BASE_HASH_H

35
src/base/hash_ctxt.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef BASE_HASH_CTXT_H
#define BASE_HASH_CTXT_H
#include "hash.h"
#include <stdint.h>
#if defined(CONF_OPENSSL)
#include <openssl/sha.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(CONF_OPENSSL)
// SHA256_CTX is defined in <openssl/sha.h>
#else
typedef struct
{
uint64_t length;
uint32_t state[8];
uint32_t curlen;
unsigned char buf[64];
} SHA256_CTX;
#endif
void sha256_init(SHA256_CTX *ctxt);
void sha256_update(SHA256_CTX *ctxt, const void *data, size_t data_len);
SHA256_DIGEST sha256_finish(SHA256_CTX *ctxt);
#ifdef __cplusplus
}
#endif
#endif // BASE_HASH_CTXT_H

197
src/base/hash_libtomcrypt.c Executable file
View file

@ -0,0 +1,197 @@
// SHA-256. Adapted from https://github.com/kalven/sha-2, which was adapted
// from LibTomCrypt. This code is Public Domain.
#if !defined(CONF_OPENSSL)
#include "hash_ctxt.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
typedef uint32_t u32;
typedef uint64_t u64;
typedef SHA256_CTX sha256_state;
static const u32 K[64] =
{
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL,
0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL,
0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL,
0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL,
0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL,
0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL,
0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL,
0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
};
static u32 min(u32 x, u32 y)
{
return x < y ? x : y;
}
static u32 load32(const unsigned char* y)
{
return ((u32)y[0] << 24) | ((u32)y[1] << 16) | ((u32)y[2] << 8) | ((u32)y[3] << 0);
}
static void store64(u64 x, unsigned char* y)
{
for(int i = 0; i != 8; ++i)
y[i] = (x >> ((7-i) * 8)) & 255;
}
static void store32(u32 x, unsigned char* y)
{
for(int i = 0; i != 4; ++i)
y[i] = (x >> ((3-i) * 8)) & 255;
}
static u32 Ch(u32 x, u32 y, u32 z) { return z ^ (x & (y ^ z)); }
static u32 Maj(u32 x, u32 y, u32 z) { return ((x | y) & z) | (x & y); }
static u32 Rot(u32 x, u32 n) { return (x >> (n & 31)) | (x << (32 - (n & 31))); }
static u32 Sh(u32 x, u32 n) { return x >> n; }
static u32 Sigma0(u32 x) { return Rot(x, 2) ^ Rot(x, 13) ^ Rot(x, 22); }
static u32 Sigma1(u32 x) { return Rot(x, 6) ^ Rot(x, 11) ^ Rot(x, 25); }
static u32 Gamma0(u32 x) { return Rot(x, 7) ^ Rot(x, 18) ^ Sh(x, 3); }
static u32 Gamma1(u32 x) { return Rot(x, 17) ^ Rot(x, 19) ^ Sh(x, 10); }
static void sha_compress(sha256_state* md, const unsigned char* buf)
{
u32 S[8], W[64], t0, t1, t;
// Copy state into S
for(int i = 0; i < 8; i++)
S[i] = md->state[i];
// Copy the state into 512-bits into W[0..15]
for(int i = 0; i < 16; i++)
W[i] = load32(buf + (4*i));
// Fill W[16..63]
for(int i = 16; i < 64; i++)
W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
// Compress
#define RND(a, b, c, d, e, f, g, h, i) \
{ \
t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
t1 = Sigma0(a) + Maj(a, b, c); \
d += t0; \
h = t0 + t1; \
}
for(int i = 0; i < 64; ++i)
{
RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i);
t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4];
S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t;
}
// Feedback
for(int i = 0; i < 8; i++)
md->state[i] = md->state[i] + S[i];
}
// Public interface
static void sha_init(sha256_state* md)
{
md->curlen = 0;
md->length = 0;
md->state[0] = 0x6A09E667UL;
md->state[1] = 0xBB67AE85UL;
md->state[2] = 0x3C6EF372UL;
md->state[3] = 0xA54FF53AUL;
md->state[4] = 0x510E527FUL;
md->state[5] = 0x9B05688CUL;
md->state[6] = 0x1F83D9ABUL;
md->state[7] = 0x5BE0CD19UL;
}
static void sha_process(sha256_state* md, const void* src, u32 inlen)
{
const u32 block_size = 64;
const unsigned char* in = src;
while(inlen > 0)
{
if(md->curlen == 0 && inlen >= block_size)
{
sha_compress(md, in);
md->length += block_size * 8;
in += block_size;
inlen -= block_size;
}
else
{
u32 n = min(inlen, (block_size - md->curlen));
memcpy(md->buf + md->curlen, in, n);
md->curlen += n;
in += n;
inlen -= n;
if(md->curlen == block_size)
{
sha_compress(md, md->buf);
md->length += 8*block_size;
md->curlen = 0;
}
}
}
}
static void sha_done(sha256_state* md, void* out)
{
// Increase the length of the message
md->length += md->curlen * 8;
// Append the '1' bit
md->buf[md->curlen++] = (unsigned char)0x80;
// If the length is currently above 56 bytes we append zeros then compress.
// Then we can fall back to padding zeros and length encoding like normal.
if(md->curlen > 56)
{
while(md->curlen < 64)
md->buf[md->curlen++] = 0;
sha_compress(md, md->buf);
md->curlen = 0;
}
// Pad upto 56 bytes of zeroes
while(md->curlen < 56)
md->buf[md->curlen++] = 0;
// Store length
store64(md->length, md->buf+56);
sha_compress(md, md->buf);
// Copy output
for(int i = 0; i < 8; i++)
store32(md->state[i], (unsigned char *)out+(4*i));
}
void sha256_init(SHA256_CTX *ctxt)
{
sha_init(ctxt);
}
void sha256_update(SHA256_CTX *ctxt, const void *data, size_t data_len)
{
sha_process(ctxt, data, data_len);
}
SHA256_DIGEST sha256_finish(SHA256_CTX *ctxt)
{
SHA256_DIGEST result;
sha_done(ctxt, result.data);
return result;
}
#endif

20
src/base/hash_openssl.c Normal file
View file

@ -0,0 +1,20 @@
#if defined(CONF_OPENSSL)
#include "hash_ctxt.h"
void sha256_init(SHA256_CTX *ctxt)
{
SHA256_Init(ctxt);
}
void sha256_update(SHA256_CTX *ctxt, const void *data, size_t data_len)
{
SHA256_Update(ctxt, data, data_len);
}
SHA256_DIGEST sha256_finish(SHA256_CTX *ctxt)
{
SHA256_DIGEST result;
SHA256_Final(result.data, ctxt);
return result;
}
#endif

View file

@ -316,10 +316,17 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
m_pMapdownloadTask = NULL; m_pMapdownloadTask = NULL;
m_MapdownloadFile = 0; m_MapdownloadFile = 0;
m_MapdownloadChunk = 0; m_MapdownloadChunk = 0;
m_MapdownloadSha256Present = false;
m_MapdownloadSha256 = SHA256_ZEROED;
m_MapdownloadCrc = 0; m_MapdownloadCrc = 0;
m_MapdownloadAmount = -1; m_MapdownloadAmount = -1;
m_MapdownloadTotalsize = -1; m_MapdownloadTotalsize = -1;
m_MapDetailsPresent = false;
m_aMapDetailsName[0] = 0;
m_MapDetailsSha256 = SHA256_ZEROED;
m_MapDetailsCrc = 0;
m_pDDNetInfoTask = NULL; m_pDDNetInfoTask = NULL;
m_aNews[0] = '\0'; m_aNews[0] = '\0';
@ -742,6 +749,8 @@ void CClient::DisconnectWithReason(const char *pReason)
if(m_MapdownloadFile) if(m_MapdownloadFile)
io_close(m_MapdownloadFile); io_close(m_MapdownloadFile);
m_MapdownloadFile = 0; m_MapdownloadFile = 0;
m_MapdownloadSha256Present = false;
m_MapdownloadSha256 = SHA256_ZEROED;
m_MapdownloadCrc = 0; m_MapdownloadCrc = 0;
m_MapdownloadTotalsize = -1; m_MapdownloadTotalsize = -1;
m_MapdownloadAmount = 0; m_MapdownloadAmount = 0;
@ -1052,7 +1061,7 @@ vec3 CClient::GetColorV3(int v)
return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f));
} }
const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc) const char *CClient::LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc)
{ {
static char s_aErrorMsg[128]; static char s_aErrorMsg[128];
@ -1064,6 +1073,18 @@ const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned
return s_aErrorMsg; return s_aErrorMsg;
} }
if(pWantedSha256 && m_pMap->Sha256() != *pWantedSha256)
{
char aWanted[SHA256_MAXSTRSIZE];
char aGot[SHA256_MAXSTRSIZE];
sha256_str(*pWantedSha256, aWanted, sizeof(aWanted));
sha256_str(m_pMap->Sha256(), aGot, sizeof(aWanted));
str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map differs from the server. %s != %s", aGot, aWanted);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", s_aErrorMsg);
m_pMap->Unload();
return s_aErrorMsg;
}
// get the crc of the map // get the crc of the map
if(m_pMap->Crc() != WantedCrc) if(m_pMap->Crc() != WantedCrc)
{ {
@ -1085,28 +1106,37 @@ const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned
str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap)); str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
str_copy(m_aCurrentMapPath, pFilename, sizeof(m_aCurrentMapPath)); str_copy(m_aCurrentMapPath, pFilename, sizeof(m_aCurrentMapPath));
return 0x0; return 0;
} }
const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc)
const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
{ {
const char *pError = 0; const char *pError = 0;
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc); char aWanted[256];
char aWantedSha256[SHA256_MAXSTRSIZE];
aWanted[0] = 0;
if(pWantedSha256)
{
sha256_str(*pWantedSha256, aWantedSha256, sizeof(aWantedSha256));
str_format(aWanted, sizeof(aWanted), "sha256=%s ", aWantedSha256);
}
str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted%s crc=%08x", pMapName, aWanted, WantedCrc);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
SetState(IClient::STATE_LOADING); SetState(IClient::STATE_LOADING);
// try the normal maps folder // try the normal maps folder
str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
pError = LoadMap(pMapName, aBuf, WantedCrc); pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError) if(!pError)
return pError; return pError;
// try the downloaded maps // try the downloaded maps
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc); str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
pError = LoadMap(pMapName, aBuf, WantedCrc); pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError) if(!pError)
return pError; return pError;
@ -1114,7 +1144,7 @@ const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
char aFilename[128]; char aFilename[128];
str_format(aFilename, sizeof(aFilename), "%s.map", pMapName); str_format(aFilename, sizeof(aFilename), "%s.map", pMapName);
if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf))) if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
pError = LoadMap(pMapName, aBuf, WantedCrc); pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
return pError; return pError;
} }
@ -1428,6 +1458,23 @@ void CClient::ProcessServerInfo(int RawType, NETADDR *pFrom, const void *pData,
#undef GET_INT #undef GET_INT
} }
static void FormatMapDownloadFilename(const char *pName, SHA256_DIGEST *pSha256, int Crc, bool Temp, char *pBuffer, int BufferSize)
{
char aSha256[SHA256_MAXSTRSIZE + 1];
aSha256[0] = 0;
if(pSha256)
{
aSha256[0] = '_';
sha256_str(*pSha256, aSha256 + 1, sizeof(aSha256) - 1);
}
str_format(pBuffer, BufferSize, "%s_%08x%s.map%s",
pName,
Crc,
aSha256,
Temp ? ".tmp" : "");
}
void CClient::ProcessServerPacket(CNetChunk *pPacket) void CClient::ProcessServerPacket(CNetChunk *pPacket)
{ {
CUnpacker Unpacker; CUnpacker Unpacker;
@ -1452,8 +1499,27 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
if(Sys) if(Sys)
{ {
// system message // system message
if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE) if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_DETAILS)
{ {
const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);
SHA256_DIGEST *pMapSha256 = (SHA256_DIGEST *)Unpacker.GetRaw(sizeof(*pMapSha256));
int MapCrc = Unpacker.GetInt();
if(Unpacker.Error())
{
return;
}
m_MapDetailsPresent = true;
str_copy(m_aMapDetailsName, pMap, sizeof(m_aMapDetailsName));
m_MapDetailsSha256 = *pMapSha256;
m_MapDetailsCrc = MapCrc;
}
else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE)
{
bool MapDetailsWerePresent = m_MapDetailsPresent;
m_MapDetailsPresent = false;
const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES); const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);
int MapCrc = Unpacker.GetInt(); int MapCrc = Unpacker.GetInt();
int MapSize = Unpacker.GetInt(); int MapSize = Unpacker.GetInt();
@ -1478,7 +1544,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
DisconnectWithReason(pError); DisconnectWithReason(pError);
else else
{ {
pError = LoadMapSearch(pMap, MapCrc); SHA256_DIGEST *pMapSha256 = 0;
if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc)
{
pMapSha256 = &m_MapDetailsSha256;
}
pError = LoadMapSearch(pMap, pMapSha256, MapCrc);
if(!pError) if(!pError)
{ {
@ -1487,7 +1558,9 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
} }
else else
{ {
str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc); char aFilename[256];
FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, aFilename, sizeof(aFilename));
str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s", aFilename);
char aBuf[256]; char aBuf[256];
str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename); str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename);
@ -1496,21 +1569,20 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
m_MapdownloadChunk = 0; m_MapdownloadChunk = 0;
str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName)); str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
m_MapdownloadSha256Present = (bool)pMapSha256;
m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED;
m_MapdownloadCrc = MapCrc; m_MapdownloadCrc = MapCrc;
m_MapdownloadTotalsize = MapSize; m_MapdownloadTotalsize = MapSize;
m_MapdownloadAmount = 0; m_MapdownloadAmount = 0;
ResetMapDownload(); ResetMapDownload();
if(g_Config.m_ClHttpMapDownload) if(pMapSha256 && g_Config.m_ClHttpMapDownload)
{ {
char aUrl[256]; char aUrl[256];
char aFilename[64]; char aEscaped[256];
char aEscaped[128];
str_format(aFilename, sizeof(aFilename), "%s_%08x.map", pMap, MapCrc);
EscapeUrl(aEscaped, sizeof(aEscaped), aFilename); EscapeUrl(aEscaped, sizeof(aEscaped), aFilename);
str_format(aUrl, sizeof(aUrl), "%s/", g_Config.m_ClDDNetMapDownloadUrl); str_format(aUrl, sizeof(aUrl), "%s/%s", g_Config.m_ClDDNetMapDownloadUrl, aEscaped);
// We only trust our own custom-selected CAs for our own servers. // We only trust our own custom-selected CAs for our own servers.
// Other servers can use any CA trusted by the system. // Other servers can use any CA trusted by the system.
@ -1519,7 +1591,6 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
str_comp_nocase_num("http://maps.ddnet.tw/", aUrl, 21) == 0 || str_comp_nocase_num("http://maps.ddnet.tw/", aUrl, 21) == 0 ||
str_comp_nocase_num("https://maps.ddnet.tw/", aUrl, 22) == 0; str_comp_nocase_num("https://maps.ddnet.tw/", aUrl, 22) == 0;
str_append(aUrl, aEscaped, sizeof(aUrl));
m_pMapdownloadTask = std::make_shared<CGetFile>(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true); m_pMapdownloadTask = std::make_shared<CGetFile>(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true);
Engine()->AddJob(m_pMapdownloadTask); Engine()->AddJob(m_pMapdownloadTask);
@ -2106,9 +2177,20 @@ void CClient::FinishMapDownload()
int Prev = m_MapdownloadTotalsize; int Prev = m_MapdownloadTotalsize;
m_MapdownloadTotalsize = -1; m_MapdownloadTotalsize = -1;
SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : 0;
char aTmp[256];
char aMapFileTemp[256];
char aMapFile[256];
FormatMapDownloadFilename(m_aMapdownloadName, pSha256, m_MapdownloadCrc, true, aTmp, sizeof(aTmp));
str_format(aMapFileTemp, sizeof(aMapFileTemp), "downloadedmaps/%s", aTmp);
FormatMapDownloadFilename(m_aMapdownloadName, pSha256, m_MapdownloadCrc, false, aTmp, sizeof(aTmp));
str_format(aMapFile, sizeof(aMapFileTemp), "downloadedmaps/%s", aTmp);
Storage()->RenameFile(aMapFileTemp, aMapFile, IStorage::TYPE_SAVE);
// load map // load map
pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc); pError = LoadMap(m_aMapdownloadName, aMapFile, pSha256, m_MapdownloadCrc);
if(!pError) if(!pError)
{ {
ResetMapDownload(); ResetMapDownload();
@ -3144,7 +3226,7 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)| (m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)|
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)| (m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)|
(m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]); (m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]);
pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Crc); pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, 0, Crc);
if(pError) if(pError)
{ {
DisconnectWithReason(pError); DisconnectWithReason(pError);
@ -3221,7 +3303,7 @@ void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int
} }
else else
str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename); str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename);
m_DemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File()); m_DemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
} }
} }
@ -3652,7 +3734,7 @@ void CClient::RaceRecord_Start(const char *pFilename)
if(State() != IClient::STATE_ONLINE) if(State() != IClient::STATE_ONLINE)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
else else
m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File()); m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File());
} }
void CClient::RaceRecord_Stop() void CClient::RaceRecord_Stop()

View file

@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <base/hash.h>
#include <engine/shared/fetcher.h> #include <engine/shared/fetcher.h>
class CGraph class CGraph
@ -139,6 +140,13 @@ class CClient : public IClient, public CDemoPlayer::IListener
int m_MapdownloadCrc; int m_MapdownloadCrc;
int m_MapdownloadAmount; int m_MapdownloadAmount;
int m_MapdownloadTotalsize; int m_MapdownloadTotalsize;
bool m_MapdownloadSha256Present;
SHA256_DIGEST m_MapdownloadSha256;
bool m_MapDetailsPresent;
char m_aMapDetailsName[256];
int m_MapDetailsCrc;
SHA256_DIGEST m_MapDetailsSha256;
std::shared_ptr<CGetFile> m_pDDNetInfoTask; std::shared_ptr<CGetFile> m_pDDNetInfoTask;
@ -286,8 +294,8 @@ public:
virtual const char *ErrorString(); virtual const char *ErrorString();
const char *LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc); const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc);
const char *LoadMapSearch(const char *pMapName, int WantedCrc); const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc);
static int PlayerScoreNameComp(const void *a, const void *b); static int PlayerScoreNameComp(const void *a, const void *b);

View file

@ -3,6 +3,7 @@
#ifndef ENGINE_MAP_H #ifndef ENGINE_MAP_H
#define ENGINE_MAP_H #define ENGINE_MAP_H
#include <base/hash.h>
#include "kernel.h" #include "kernel.h"
class IMap : public IInterface class IMap : public IInterface
@ -28,6 +29,7 @@ public:
virtual bool Load(const char *pMapName) = 0; virtual bool Load(const char *pMapName) = 0;
virtual bool IsLoaded() = 0; virtual bool IsLoaded() = 0;
virtual void Unload() = 0; virtual void Unload() = 0;
virtual SHA256_DIGEST Sha256() = 0;
virtual unsigned Crc() = 0; virtual unsigned Crc() = 0;
virtual int MapSize() = 0; virtual int MapSize() = 0;
virtual IOHANDLE File() = 0; virtual IOHANDLE File() = 0;

View file

@ -2,6 +2,9 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef ENGINE_SERVER_H #ifndef ENGINE_SERVER_H
#define ENGINE_SERVER_H #define ENGINE_SERVER_H
#include <base/hash.h>
#include "kernel.h" #include "kernel.h"
#include "message.h" #include "message.h"
#include <game/generated/protocol.h> #include <game/generated/protocol.h>
@ -133,7 +136,7 @@ public:
return true; return true;
} }
virtual void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, int *pMapCrc) = 0; virtual void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pSha256, int *pMapCrc) = 0;
virtual void SetClientName(int ClientID, char const *pName) = 0; virtual void SetClientName(int ClientID, char const *pName) = 0;
virtual void SetClientClan(int ClientID, char const *pClan) = 0; virtual void SetClientClan(int ClientID, char const *pClan) = 0;

View file

@ -777,6 +777,7 @@ int CServer::NewClientNoAuthCallback(int ClientID, void *pUser)
pThis->m_aClients[ClientID].m_DnsblState = CClient::DNSBL_STATE_NONE; pThis->m_aClients[ClientID].m_DnsblState = CClient::DNSBL_STATE_NONE;
pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTING; pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTING;
pThis->m_aClients[ClientID].m_SupportsMapSha256 = false;
pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aName[0] = 0;
pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0;
pThis->m_aClients[ClientID].m_Country = -1; pThis->m_aClients[ClientID].m_Country = -1;
@ -797,6 +798,7 @@ int CServer::NewClientCallback(int ClientID, void *pUser)
{ {
CServer *pThis = (CServer *)pUser; CServer *pThis = (CServer *)pUser;
pThis->m_aClients[ClientID].m_State = CClient::STATE_AUTH; pThis->m_aClients[ClientID].m_State = CClient::STATE_AUTH;
pThis->m_aClients[ClientID].m_SupportsMapSha256 = false;
pThis->m_aClients[ClientID].m_DnsblState = CClient::DNSBL_STATE_NONE; pThis->m_aClients[ClientID].m_DnsblState = CClient::DNSBL_STATE_NONE;
pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aName[0] = 0;
pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0;
@ -875,6 +877,7 @@ int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
pThis->GameServer()->OnClientDrop(ClientID, pReason); pThis->GameServer()->OnClientDrop(ClientID, pReason);
pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY; pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
pThis->m_aClients[ClientID].m_SupportsMapSha256 = false;
pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aName[0] = 0;
pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0;
pThis->m_aClients[ClientID].m_Country = -1; pThis->m_aClients[ClientID].m_Country = -1;
@ -901,20 +904,31 @@ void CServer::SendRconType(int ClientID, bool UsernameReq)
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true); SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
} }
void CServer::GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, int *pMapCrc) void CServer::GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pMapSha256, int *pMapCrc)
{ {
str_copy(pMapName, GetMapName(), MapNameSize); str_copy(pMapName, GetMapName(), MapNameSize);
*pMapSize = m_CurrentMapSize; *pMapSize = m_CurrentMapSize;
*pMapSha256 = m_CurrentMapSha256;
*pMapCrc = m_CurrentMapCrc; *pMapCrc = m_CurrentMapCrc;
} }
void CServer::SendMap(int ClientID) void CServer::SendMap(int ClientID)
{
{
CMsgPacker Msg(NETMSG_MAP_DETAILS);
Msg.AddString(GetMapName(), 0);
Msg.AddRaw(&m_CurrentMapSha256.data, sizeof(m_CurrentMapSha256.data));
Msg.AddInt(m_CurrentMapCrc);
Msg.AddInt(m_CurrentMapSize);
SendMsgEx(&Msg, MSGFLAG_VITAL, ClientID, true);
}
{ {
CMsgPacker Msg(NETMSG_MAP_CHANGE); CMsgPacker Msg(NETMSG_MAP_CHANGE);
Msg.AddString(GetMapName(), 0); Msg.AddString(GetMapName(), 0);
Msg.AddInt(m_CurrentMapCrc); Msg.AddInt(m_CurrentMapCrc);
Msg.AddInt(m_CurrentMapSize); Msg.AddInt(m_CurrentMapSize);
SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true);
}
m_aClients[ClientID].m_NextMapChunk = 0; m_aClients[ClientID].m_NextMapChunk = 0;
} }
@ -1692,8 +1706,13 @@ int CServer::LoadMap(const char *pMapName)
m_IDPool.TimeoutIDs(); m_IDPool.TimeoutIDs();
// get the crc of the map // get the crc of the map
m_CurrentMapSha256 = m_pMap->Sha256();
m_CurrentMapCrc = m_pMap->Crc(); m_CurrentMapCrc = m_pMap->Crc();
char aBufMsg[256]; char aBufMsg[256];
char aSha256[SHA256_MAXSTRSIZE];
sha256_str(m_CurrentMapSha256, aSha256, sizeof(aSha256));
str_format(aBufMsg, sizeof(aBufMsg), "%s sha256 is %s", aBuf, aSha256);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg);
str_format(aBufMsg, sizeof(aBufMsg), "%s crc is %08x", aBuf, m_CurrentMapCrc); str_format(aBufMsg, sizeof(aBufMsg), "%s crc is %08x", aBuf, m_CurrentMapCrc);
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg);
@ -2385,7 +2404,7 @@ void CServer::DemoRecorder_HandleAutoStart()
char aDate[20]; char aDate[20];
str_timestamp(aDate, sizeof(aDate)); str_timestamp(aDate, sizeof(aDate));
str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate); str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", "auto/autorecord", aDate);
m_aDemoRecorder[MAX_CLIENTS].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server", m_CurrentMapSize, m_pCurrentMapData); m_aDemoRecorder[MAX_CLIENTS].Start(Storage(), m_pConsole, aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapSha256, m_CurrentMapCrc, "server", m_CurrentMapSize, m_pCurrentMapData);
if(g_Config.m_SvAutoDemoMax) if(g_Config.m_SvAutoDemoMax)
{ {
// clean up auto recorded demos // clean up auto recorded demos
@ -2421,7 +2440,7 @@ void CServer::StartRecord(int ClientID)
{ {
char aFilename[128]; char aFilename[128];
str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID); str_format(aFilename, sizeof(aFilename), "demos/%s_%d_%d_tmp.demo", m_aCurrentMap, g_Config.m_SvPort, ClientID);
m_aDemoRecorder[ClientID].Start(Storage(), Console(), aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "server", m_CurrentMapSize, m_pCurrentMapData); m_aDemoRecorder[ClientID].Start(Storage(), Console(), aFilename, GameServer()->NetVersion(), m_aCurrentMap, m_CurrentMapSha256, m_CurrentMapCrc, "server", m_CurrentMapSize, m_pCurrentMapData);
} }
} }
@ -2455,7 +2474,7 @@ void CServer::ConRecord(IConsole::IResult *pResult, void *pUser)
str_timestamp(aDate, sizeof(aDate)); str_timestamp(aDate, sizeof(aDate));
str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aDate); str_format(aFilename, sizeof(aFilename), "demos/demo_%s.demo", aDate);
} }
pServer->m_aDemoRecorder[MAX_CLIENTS].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_CurrentMapCrc, "server", pServer->m_CurrentMapSize, pServer->m_pCurrentMapData); pServer->m_aDemoRecorder[MAX_CLIENTS].Start(pServer->Storage(), pServer->Console(), aFilename, pServer->GameServer()->NetVersion(), pServer->m_aCurrentMap, pServer->m_CurrentMapSha256, pServer->m_CurrentMapCrc, "server", pServer->m_CurrentMapSize, pServer->m_pCurrentMapData);
} }
void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser) void CServer::ConStopRecord(IConsole::IResult *pResult, void *pUser)

View file

@ -3,6 +3,9 @@
#ifndef ENGINE_SERVER_SERVER_H #ifndef ENGINE_SERVER_SERVER_H
#define ENGINE_SERVER_SERVER_H #define ENGINE_SERVER_SERVER_H
#include <base/hash.h>
#include <base/math.h>
#include <engine/engine.h> #include <engine/engine.h>
#include <engine/server.h> #include <engine/server.h>
@ -13,7 +16,6 @@
#include <engine/shared/network.h> #include <engine/shared/network.h>
#include <engine/server/register.h> #include <engine/server/register.h>
#include <engine/shared/console.h> #include <engine/shared/console.h>
#include <base/math.h>
#include <engine/shared/econ.h> #include <engine/shared/econ.h>
#include <engine/shared/fifo.h> #include <engine/shared/fifo.h>
#include <engine/shared/netban.h> #include <engine/shared/netban.h>
@ -140,6 +142,7 @@ public:
// connection state info // connection state info
int m_State; int m_State;
bool m_SupportsMapSha256;
int m_Latency; int m_Latency;
int m_SnapRate; int m_SnapRate;
@ -204,6 +207,7 @@ public:
//static NETADDR4 master_server; //static NETADDR4 master_server;
char m_aCurrentMap[64]; char m_aCurrentMap[64];
SHA256_DIGEST m_CurrentMapSha256;
unsigned m_CurrentMapCrc; unsigned m_CurrentMapCrc;
unsigned char *m_pCurrentMapData; unsigned char *m_pCurrentMapData;
unsigned int m_CurrentMapSize; unsigned int m_CurrentMapSize;
@ -245,7 +249,7 @@ public:
void SetRconCID(int ClientID); void SetRconCID(int ClientID);
int GetAuthedState(int ClientID); int GetAuthedState(int ClientID);
const char *GetAuthName(int ClientID); const char *GetAuthName(int ClientID);
void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, int *pMapCrc); void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pMapSha256, int *pMapCrc);
int GetClientInfo(int ClientID, CClientInfo *pInfo); int GetClientInfo(int ClientID, CClientInfo *pInfo);
void GetClientAddr(int ClientID, char *pAddrStr, int Size); void GetClientAddr(int ClientID, char *pAddrStr, int Size);
const char *ClientName(int ClientID); const char *ClientName(int ClientID);

View file

@ -1,6 +1,7 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* (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. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/math.h> #include <base/math.h>
#include <base/hash_ctxt.h>
#include <base/system.h> #include <base/system.h>
#include <engine/storage.h> #include <engine/storage.h>
#include "datafile.h" #include "datafile.h"
@ -58,6 +59,7 @@ struct CDatafileInfo
struct CDatafile struct CDatafile
{ {
IOHANDLE m_File; IOHANDLE m_File;
SHA256_DIGEST m_Sha256;
unsigned m_Crc; unsigned m_Crc;
CDatafileInfo m_Info; CDatafileInfo m_Info;
CDatafileHeader m_Header; CDatafileHeader m_Header;
@ -80,12 +82,15 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
// take the CRC of the file and store it // take the CRC of the file and store it
unsigned Crc = 0; unsigned Crc = 0;
SHA256_DIGEST Sha256;
{ {
enum enum
{ {
BUFFER_SIZE = 64*1024 BUFFER_SIZE = 64*1024
}; };
SHA256_CTX Sha256Ctxt;
sha256_init(&Sha256Ctxt);
unsigned char aBuffer[BUFFER_SIZE]; unsigned char aBuffer[BUFFER_SIZE];
while(1) while(1)
@ -94,7 +99,9 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
if(Bytes <= 0) if(Bytes <= 0)
break; break;
Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention
sha256_update(&Sha256Ctxt, aBuffer, Bytes);
} }
Sha256 = sha256_finish(&Sha256Ctxt);
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
} }
@ -143,6 +150,7 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
pTmpDataFile->m_ppDataPtrs = (char **)(pTmpDataFile+1); pTmpDataFile->m_ppDataPtrs = (char **)(pTmpDataFile+1);
pTmpDataFile->m_pData = (char *)(pTmpDataFile+1)+Header.m_NumRawData*sizeof(char *); pTmpDataFile->m_pData = (char *)(pTmpDataFile+1)+Header.m_NumRawData*sizeof(char *);
pTmpDataFile->m_File = File; pTmpDataFile->m_File = File;
pTmpDataFile->m_Sha256 = Sha256;
pTmpDataFile->m_Crc = Crc; pTmpDataFile->m_Crc = Crc;
// clear the data pointers // clear the data pointers
@ -226,32 +234,6 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int
return true; return true;
} }
bool CDataFileReader::GetCrcSize(class IStorage *pStorage, const char *pFilename, int StorageType, unsigned *pCrc, unsigned *pSize)
{
IOHANDLE File = pStorage->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
return false;
// get crc and size
unsigned Crc = 0;
unsigned Size = 0;
unsigned char aBuffer[64*1024];
while(1)
{
unsigned Bytes = io_read(File, aBuffer, sizeof(aBuffer));
if(Bytes <= 0)
break;
Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention
Size += Bytes;
}
io_close(File);
*pCrc = Crc;
*pSize = Size;
return true;
}
int CDataFileReader::NumData() int CDataFileReader::NumData()
{ {
if(!m_pDataFile) { return 0; } if(!m_pDataFile) { return 0; }
@ -432,6 +414,20 @@ bool CDataFileReader::Close()
return true; return true;
} }
SHA256_DIGEST CDataFileReader::Sha256()
{
if(!m_pDataFile)
{
SHA256_DIGEST Result;
for(unsigned i = 0; i < sizeof(Result.data); i++)
{
Result.data[i] = 0xff;
}
return Result;
}
return m_pDataFile->m_Sha256;
}
unsigned CDataFileReader::Crc() unsigned CDataFileReader::Crc()
{ {
if(!m_pDataFile) return 0xFFFFFFFF; if(!m_pDataFile) return 0xFFFFFFFF;

View file

@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_DATAFILE_H #ifndef ENGINE_SHARED_DATAFILE_H
#define ENGINE_SHARED_DATAFILE_H #define ENGINE_SHARED_DATAFILE_H
#include <base/hash.h>
// raw datafile access // raw datafile access
class CDataFileReader class CDataFileReader
{ {
@ -18,8 +20,6 @@ public:
bool Open(class IStorage *pStorage, const char *pFilename, int StorageType); bool Open(class IStorage *pStorage, const char *pFilename, int StorageType);
bool Close(); bool Close();
static bool GetCrcSize(class IStorage *pStorage, const char *pFilename, int StorageType, unsigned *pCrc, unsigned *pSize);
void *GetData(int Index); void *GetData(int Index);
void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved
int GetDataSize(int Index); int GetDataSize(int Index);
@ -32,6 +32,7 @@ public:
int NumData(); int NumData();
void Unload(); void Unload();
SHA256_DIGEST Sha256();
unsigned Crc(); unsigned Crc();
int MapSize(); int MapSize();
IOHANDLE File(); IOHANDLE File();

View file

@ -33,7 +33,7 @@ CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapDat
} }
// Record // Record
int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, unsigned Crc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser) int CDemoRecorder::Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetVersion, const char *pMap, SHA256_DIGEST Sha256, unsigned Crc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile, DEMOFUNC_FILTER pfnFilter, void *pUser)
{ {
m_pfnFilter = pfnFilter; m_pfnFilter = pfnFilter;
m_pUser = pUser; m_pUser = pUser;
@ -963,7 +963,12 @@ void CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int
return; return;
const CDemoPlayer::CMapInfo *pMapInfo = m_pDemoPlayer->GetMapInfo(); const CDemoPlayer::CMapInfo *pMapInfo = m_pDemoPlayer->GetMapInfo();
if (m_pDemoRecorder->Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, pMapInfo->m_Crc, "client", pMapInfo->m_Size, NULL, NULL, pfnFilter, pUser) == -1) SHA256_DIGEST Fake;
for(unsigned i = 0; i < sizeof(Fake.data); i++)
{
Fake.data[i] = 0xff;
}
if (m_pDemoRecorder->Start(m_pStorage, m_pConsole, pDst, m_pNetVersion, pMapInfo->m_aName, Fake, pMapInfo->m_Crc, "client", pMapInfo->m_Size, NULL, NULL, pfnFilter, pUser) == -1)
return; return;

View file

@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_DEMO_H #ifndef ENGINE_SHARED_DEMO_H
#define ENGINE_SHARED_DEMO_H #define ENGINE_SHARED_DEMO_H
#include <base/hash.h>
#include <engine/demo.h> #include <engine/demo.h>
#include <engine/shared/protocol.h> #include <engine/shared/protocol.h>
@ -32,7 +34,7 @@ public:
CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData = false); CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData = false);
CDemoRecorder() {} CDemoRecorder() {}
int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, unsigned MapCrc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile = 0, DEMOFUNC_FILTER pfnFilter = 0, void *pUser = 0); int Start(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, const char *pNetversion, const char *pMap, SHA256_DIGEST Sha256, unsigned MapCrc, const char *pType, unsigned int MapSize, unsigned char *pMapData, IOHANDLE MapFile = 0, DEMOFUNC_FILTER pfnFilter = 0, void *pUser = 0);
int Stop(); int Stop();
void AddDemoMarker(); void AddDemoMarker();
@ -77,6 +79,7 @@ public:
struct CMapInfo struct CMapInfo
{ {
char m_aName[128]; char m_aName[128];
SHA256_DIGEST m_Sha256;
int m_Crc; int m_Crc;
int m_Size; int m_Size;
}; };

View file

@ -20,7 +20,7 @@ private:
IStorage *m_pStorage; IStorage *m_pStorage;
char m_aUrl[256]; char m_aUrl[256];
char m_aDest[128]; char m_aDest[256];
int m_StorageType; int m_StorageType;
bool m_UseDDNetCA; bool m_UseDDNetCA;
bool m_CanTimeout; bool m_CanTimeout;

View file

@ -39,6 +39,11 @@ public:
return m_DataFile.IsOpen(); return m_DataFile.IsOpen();
} }
virtual SHA256_DIGEST Sha256()
{
return m_DataFile.Sha256();
}
virtual unsigned Crc() virtual unsigned Crc()
{ {
return m_DataFile.Crc(); return m_DataFile.Crc();

View file

@ -22,3 +22,4 @@ UUID(NETMSG_ITIS, "it-is@ddnet.tw")
UUID(NETMSG_IDONTKNOW, "i-dont-know@ddnet.tw") UUID(NETMSG_IDONTKNOW, "i-dont-know@ddnet.tw")
UUID(NETMSG_RCONTYPE, "rcon-type@ddnet.tw") UUID(NETMSG_RCONTYPE, "rcon-type@ddnet.tw")
UUID(NETMSG_MAP_DETAILS, "map-details@ddnet.tw")

View file

@ -2632,8 +2632,9 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
char aMapName[128]; char aMapName[128];
int MapSize; int MapSize;
SHA256_DIGEST MapSha256;
int MapCrc; int MapCrc;
Server()->GetMapInfo(aMapName, sizeof(aMapName), &MapSize, &MapCrc); Server()->GetMapInfo(aMapName, sizeof(aMapName), &MapSize, &MapSha256, &MapCrc);
m_MapBugs = GetMapBugs(aMapName, MapSize, MapCrc); m_MapBugs = GetMapBugs(aMapName, MapSize, MapCrc);
// reset everything here // reset everything here
@ -2743,6 +2744,7 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
GameInfo.m_pMapName = aMapName; GameInfo.m_pMapName = aMapName;
GameInfo.m_MapSize = MapSize; GameInfo.m_MapSize = MapSize;
GameInfo.m_MapSha256 = MapSha256;
GameInfo.m_MapCrc = MapCrc; GameInfo.m_MapCrc = MapCrc;
m_TeeHistorian.Reset(&GameInfo, TeeHistorianWrite, this); m_TeeHistorian.Reset(&GameInfo, TeeHistorianWrite, this);

View file

@ -66,9 +66,11 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
char aGameUuid[UUID_MAXSTRSIZE]; char aGameUuid[UUID_MAXSTRSIZE];
char aStartTime[128]; char aStartTime[128];
char aMapSha256[SHA256_MAXSTRSIZE];
FormatUuid(pGameInfo->m_GameUuid, aGameUuid, sizeof(aGameUuid)); FormatUuid(pGameInfo->m_GameUuid, aGameUuid, sizeof(aGameUuid));
str_timestamp_ex(pGameInfo->m_StartTime, aStartTime, sizeof(aStartTime), "%Y-%m-%dT%H:%M:%S%z"); str_timestamp_ex(pGameInfo->m_StartTime, aStartTime, sizeof(aStartTime), "%Y-%m-%dT%H:%M:%S%z");
sha256_str(pGameInfo->m_MapSha256, aMapSha256, sizeof(aMapSha256));
char aCommentBuffer[128]; char aCommentBuffer[128];
char aServerVersionBuffer[128]; char aServerVersionBuffer[128];
@ -76,12 +78,13 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
char aServerNameBuffer[128]; char aServerNameBuffer[128];
char aGameTypeBuffer[128]; char aGameTypeBuffer[128];
char aMapNameBuffer[128]; char aMapNameBuffer[128];
char aMapSha256Buffer[256];
char aJson[2048]; char aJson[2048];
#define E(buf, str) EscapeJson(buf, sizeof(buf), str) #define E(buf, str) EscapeJson(buf, sizeof(buf), str)
str_format(aJson, sizeof(aJson), "{\"comment\":\"%s\",\"version\":\"%s\",\"game_uuid\":\"%s\",\"server_version\":\"%s\",\"start_time\":\"%s\",\"server_name\":\"%s\",\"server_port\":\"%d\",\"game_type\":\"%s\",\"map_name\":\"%s\",\"map_size\":\"%d\",\"map_crc\":\"%08x\",\"config\":{", str_format(aJson, sizeof(aJson), "{\"comment\":\"%s\",\"version\":\"%s\",\"game_uuid\":\"%s\",\"server_version\":\"%s\",\"start_time\":\"%s\",\"server_name\":\"%s\",\"server_port\":\"%d\",\"game_type\":\"%s\",\"map_name\":\"%s\",\"map_size\":\"%d\",\"map_sha256\":\"%s\",\"map_crc\":\"%08x\",\"config\":{",
E(aCommentBuffer, TEEHISTORIAN_NAME), E(aCommentBuffer, TEEHISTORIAN_NAME),
TEEHISTORIAN_VERSION, TEEHISTORIAN_VERSION,
aGameUuid, aGameUuid,
@ -92,6 +95,7 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
E(aGameTypeBuffer, pGameInfo->m_pGameType), E(aGameTypeBuffer, pGameInfo->m_pGameType),
E(aMapNameBuffer, pGameInfo->m_pMapName), E(aMapNameBuffer, pGameInfo->m_pMapName),
pGameInfo->m_MapSize, pGameInfo->m_MapSize,
E(aMapSha256Buffer, aMapSha256),
pGameInfo->m_MapCrc); pGameInfo->m_MapCrc);
Write(aJson, str_length(aJson)); Write(aJson, str_length(aJson));

View file

@ -1,3 +1,4 @@
#include <base/hash.h>
#include <engine/console.h> #include <engine/console.h>
#include <engine/shared/fetcher.h> #include <engine/shared/fetcher.h>
#include <engine/shared/packer.h> #include <engine/shared/packer.h>
@ -27,6 +28,7 @@ public:
const char *m_pMapName; const char *m_pMapName;
int m_MapSize; int m_MapSize;
SHA256_DIGEST m_MapSha256;
int m_MapCrc; int m_MapCrc;
CConfiguration *m_pConfig; CConfiguration *m_pConfig;

42
src/test/hash.cpp Normal file
View file

@ -0,0 +1,42 @@
#include <gtest/gtest.h>
#include <base/hash_ctxt.h>
#include <base/system.h>
static void Expect(SHA256_DIGEST Actual, const char *pWanted)
{
char aActual[SHA256_MAXSTRSIZE];
sha256_str(Actual, aActual, sizeof(aActual));
EXPECT_STREQ(aActual, pWanted);
}
TEST(Hash, Sha256)
{
// https://en.wikipedia.org/w/index.php?title=SHA-2&oldid=840187620#Test_vectors
Expect(sha256("", 0), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
SHA256_CTX ctxt;
sha256_init(&ctxt);
Expect(sha256_finish(&ctxt), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
// printf 'The quick brown fox jumps over the lazy dog.' | sha256sum
char QUICK_BROWN_FOX[] = "The quick brown fox jumps over the lazy dog.";
Expect(sha256(QUICK_BROWN_FOX, str_length(QUICK_BROWN_FOX)), "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c");
sha256_init(&ctxt);
sha256_update(&ctxt, "The ", 4);
sha256_update(&ctxt, "quick ", 6);
sha256_update(&ctxt, "brown ", 6);
sha256_update(&ctxt, "fox ", 4);
sha256_update(&ctxt, "jumps ", 6);
sha256_update(&ctxt, "over ", 5);
sha256_update(&ctxt, "the ", 4);
sha256_update(&ctxt, "lazy ", 5);
sha256_update(&ctxt, "dog.", 4);
Expect(sha256_finish(&ctxt), "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c");
}
TEST(Hash, Sha256Eq)
{
EXPECT_EQ(sha256("", 0), sha256("", 0));
}

View file

@ -43,6 +43,13 @@ protected:
RegisterTeehistorianUuids(&m_UuidManager); RegisterTeehistorianUuids(&m_UuidManager);
RegisterGameUuids(&m_UuidManager); RegisterGameUuids(&m_UuidManager);
SHA256_DIGEST Sha256 = {{
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
0x01, 0x23,
}};
mem_zero(&m_GameInfo, sizeof(m_GameInfo)); mem_zero(&m_GameInfo, sizeof(m_GameInfo));
m_GameInfo.m_GameUuid = CalculateUuid("test@ddnet.tw"); m_GameInfo.m_GameUuid = CalculateUuid("test@ddnet.tw");
@ -55,6 +62,7 @@ protected:
m_GameInfo.m_pMapName = "Kobra 3 Solo"; m_GameInfo.m_pMapName = "Kobra 3 Solo";
m_GameInfo.m_MapSize = 903514; m_GameInfo.m_MapSize = 903514;
m_GameInfo.m_MapSha256 = Sha256;
m_GameInfo.m_MapCrc = 0xeceaf25c; m_GameInfo.m_MapCrc = 0xeceaf25c;
m_GameInfo.m_pConfig = &m_Config; m_GameInfo.m_pConfig = &m_Config;
@ -81,7 +89,7 @@ protected:
{ {
static CUuid TEEHISTORIAN_UUID = CalculateUuid("teehistorian@ddnet.tw"); static CUuid TEEHISTORIAN_UUID = CalculateUuid("teehistorian@ddnet.tw");
static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\""; static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\"";
static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_crc\":\"eceaf25c\",\"config\":{},\"tuning\":{},\"uuids\":["; static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"config\":{},\"tuning\":{},\"uuids\":[";
static const char PREFIX3[] = "]}"; static const char PREFIX3[] = "]}";
char aTimeBuf[64]; char aTimeBuf[64];

View file

@ -1,70 +0,0 @@
/* (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>
#include <base/system.h>
#include <engine/kernel.h>
#include <engine/map.h>
#include <engine/storage.h>
static IOHANDLE s_File = 0;
static IStorage *s_pStorage = 0;
static IEngineMap *s_pEngineMap = 0;
int MaplistCallback(const char *pName, int IsDir, int DirType, void *pUser)
{
int l = str_length(pName);
if(l < 4 || IsDir || str_comp(pName+l-4, ".map") != 0)
return 0;
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "maps/%s", pName);
if(!s_pEngineMap->Load(aBuf))
return 0;
unsigned MapCrc = s_pEngineMap->Crc();
s_pEngineMap->Unload();
IOHANDLE MapFile = s_pStorage->OpenFile(aBuf, IOFLAG_READ, DirType);
unsigned MapSize = io_length(MapFile);
io_close(MapFile);
char aMapName[8];
str_copy(aMapName, pName, min((int)sizeof(aMapName),l-3));
str_format(aBuf, sizeof(aBuf), "\t{\"%s\", {0x%02x, 0x%02x, 0x%02x, 0x%02x}, {0x%02x, 0x%02x, 0x%02x, 0x%02x}},\n", aMapName,
(MapCrc>>24)&0xff, (MapCrc>>16)&0xff, (MapCrc>>8)&0xff, MapCrc&0xff,
(MapSize>>24)&0xff, (MapSize>>16)&0xff, (MapSize>>8)&0xff, MapSize&0xff);
io_write(s_File, aBuf, str_length(aBuf));
return 0;
}
int main(int argc, const char **argv) // ignore_convention
{
IKernel *pKernel = IKernel::Create();
s_pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_BASIC, argc, argv);
s_pEngineMap = CreateEngineMap();
bool RegisterFail = !pKernel->RegisterInterface(s_pStorage);
RegisterFail |= !pKernel->RegisterInterface(s_pEngineMap);
if(RegisterFail)
{
delete pKernel;
return -1;
}
s_File = s_pStorage->OpenFile("map_version.txt", IOFLAG_WRITE, 1);
if(s_File)
{
io_write(s_File, "static CMapVersion s_aMapVersionList[] = {\n", str_length("static CMapVersion s_aMapVersionList[] = {\n"));
s_pStorage->ListDirectory(1, "maps", MaplistCallback, 0);
io_write(s_File, "};\n", str_length("};\n"));
io_close(s_File);
}
delete pKernel;
return 0;
}