From ca8fcc823cabb769760d39f0b93d0de23552cfc4 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Tue, 5 Jun 2018 21:22:40 +0200 Subject: [PATCH] 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. --- CMakeLists.txt | 14 +- cmake/FindCrypto.cmake | 19 +++ src/base/hash.c | 41 ++++++ src/base/hash.h | 43 ++++++ src/base/hash_ctxt.h | 35 +++++ src/base/hash_libtomcrypt.c | 197 +++++++++++++++++++++++++++ src/base/hash_openssl.c | 20 +++ src/engine/client/client.cpp | 128 +++++++++++++---- src/engine/client/client.h | 12 +- src/engine/map.h | 2 + src/engine/server.h | 5 +- src/engine/server/server.cpp | 37 +++-- src/engine/server/server.h | 8 +- src/engine/shared/datafile.cpp | 48 +++---- src/engine/shared/datafile.h | 5 +- src/engine/shared/demo.cpp | 9 +- src/engine/shared/demo.h | 5 +- src/engine/shared/fetcher.h | 2 +- src/engine/shared/map.cpp | 5 + src/engine/shared/protocol_ex_msgs.h | 1 + src/game/server/gamecontext.cpp | 4 +- src/game/server/teehistorian.cpp | 6 +- src/game/server/teehistorian.h | 2 + src/test/hash.cpp | 42 ++++++ src/test/teehistorian.cpp | 10 +- src/tools/map_version.cpp | 70 ---------- 26 files changed, 626 insertions(+), 144 deletions(-) create mode 100644 cmake/FindCrypto.cmake create mode 100644 src/base/hash.c create mode 100644 src/base/hash.h create mode 100644 src/base/hash_ctxt.h create mode 100755 src/base/hash_libtomcrypt.c create mode 100644 src/base/hash_openssl.c create mode 100644 src/test/hash.cpp delete mode 100644 src/tools/map_version.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 31193db9d..2ec06e528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,6 +262,7 @@ if(NOT CMAKE_CROSSCOMPILING) find_package(PkgConfig) endif() find_package(ZLIB) +find_package(Crypto) find_package(Curl) find_package(Freetype) if(DOWNLOAD_GTEST) @@ -338,6 +339,7 @@ if(MYSQL) show_dependency_status("MySQL" MYSQL) endif() show_dependency_status("Ogg" OGG) +show_dependency_status("OpenSSL Crypto" CRYPTO) show_dependency_status("Opus" OPUS) show_dependency_status("Opusfile" OPUSFILE) show_dependency_status("Pnglite" PNGLITE) @@ -554,6 +556,11 @@ set_glob(BASE GLOB_RECURSE src/base confusables.c confusables_data.h detect.h + hash.c + hash.h + hash_ctxt.h + hash_libtomcrypt.c + hash_openssl.c math.h system.c system.h @@ -695,7 +702,7 @@ list(APPEND TARGETS_DEP md5) set(DEPS ${DEP_MD5} ${ZLIB_DEP}) # 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 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_replace_image.cpp map_resave.cpp - map_version.cpp packetgen.cpp tileset_borderadd.cpp tileset_borderfix.cpp @@ -1104,6 +1110,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) aio.cpp fs.cpp git_revision.cpp + hash.cpp jobs.cpp json.cpp mapbugs.cpp @@ -1511,6 +1518,9 @@ foreach(target ${TARGETS_OWN}) target_include_directories(${target} PRIVATE ${CURL_INCLUDE_DIRS}) target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS}) target_compile_definitions(${target} PRIVATE GLEW_STATIC) + if(CRYPTO_FOUND) + target_compile_definitions(${target} PRIVATE CONF_OPENSSL) + endif() if(WEBSOCKETS) target_compile_definitions(${target} PRIVATE CONF_WEBSOCKETS) target_include_directories(${target} PRIVATE ${WEBSOCKETS_INCLUDE_DIRS}) diff --git a/cmake/FindCrypto.cmake b/cmake/FindCrypto.cmake new file mode 100644 index 000000000..f765e7157 --- /dev/null +++ b/cmake/FindCrypto.cmake @@ -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() diff --git a/src/base/hash.c b/src/base/hash.c new file mode 100644 index 000000000..eb2f20587 --- /dev/null +++ b/src/base/hash.c @@ -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)); +} diff --git a/src/base/hash.h b/src/base/hash.h new file mode 100644 index 000000000..cdb947bd1 --- /dev/null +++ b/src/base/hash.h @@ -0,0 +1,43 @@ +#ifndef BASE_HASH_H +#define BASE_HASH_H + +#include + +#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 diff --git a/src/base/hash_ctxt.h b/src/base/hash_ctxt.h new file mode 100644 index 000000000..636ed6741 --- /dev/null +++ b/src/base/hash_ctxt.h @@ -0,0 +1,35 @@ +#ifndef BASE_HASH_CTXT_H +#define BASE_HASH_CTXT_H + +#include "hash.h" +#include + +#if defined(CONF_OPENSSL) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(CONF_OPENSSL) +// SHA256_CTX is defined in +#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 diff --git a/src/base/hash_libtomcrypt.c b/src/base/hash_libtomcrypt.c new file mode 100755 index 000000000..b249f27e0 --- /dev/null +++ b/src/base/hash_libtomcrypt.c @@ -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 +#include +#include + +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 diff --git a/src/base/hash_openssl.c b/src/base/hash_openssl.c new file mode 100644 index 000000000..6d2240abb --- /dev/null +++ b/src/base/hash_openssl.c @@ -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 diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index dd9997999..c6cbb87da 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -316,10 +316,17 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) m_pMapdownloadTask = NULL; m_MapdownloadFile = 0; m_MapdownloadChunk = 0; + m_MapdownloadSha256Present = false; + m_MapdownloadSha256 = SHA256_ZEROED; m_MapdownloadCrc = 0; m_MapdownloadAmount = -1; m_MapdownloadTotalsize = -1; + m_MapDetailsPresent = false; + m_aMapDetailsName[0] = 0; + m_MapDetailsSha256 = SHA256_ZEROED; + m_MapDetailsCrc = 0; + m_pDDNetInfoTask = NULL; m_aNews[0] = '\0'; @@ -742,6 +749,8 @@ void CClient::DisconnectWithReason(const char *pReason) if(m_MapdownloadFile) io_close(m_MapdownloadFile); m_MapdownloadFile = 0; + m_MapdownloadSha256Present = false; + m_MapdownloadSha256 = SHA256_ZEROED; m_MapdownloadCrc = 0; m_MapdownloadTotalsize = -1; 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)); } -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]; @@ -1064,6 +1073,18 @@ const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned 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 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_aCurrentMapPath, pFilename, sizeof(m_aCurrentMapPath)); - return 0x0; + return 0; } - - -const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc) +const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc) { const char *pError = 0; 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); SetState(IClient::STATE_LOADING); // try the normal maps folder str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); - pError = LoadMap(pMapName, aBuf, WantedCrc); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); if(!pError) return pError; // try the downloaded maps str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc); - pError = LoadMap(pMapName, aBuf, WantedCrc); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); if(!pError) return pError; @@ -1114,7 +1144,7 @@ const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc) char aFilename[128]; str_format(aFilename, sizeof(aFilename), "%s.map", pMapName); if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf))) - pError = LoadMap(pMapName, aBuf, WantedCrc); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); return pError; } @@ -1428,6 +1458,23 @@ void CClient::ProcessServerInfo(int RawType, NETADDR *pFrom, const void *pData, #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) { CUnpacker Unpacker; @@ -1452,8 +1499,27 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) if(Sys) { // 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); int MapCrc = Unpacker.GetInt(); int MapSize = Unpacker.GetInt(); @@ -1478,7 +1544,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) DisconnectWithReason(pError); 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) { @@ -1487,7 +1558,9 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) } 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]; 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; str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName)); + m_MapdownloadSha256Present = (bool)pMapSha256; + m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED; m_MapdownloadCrc = MapCrc; m_MapdownloadTotalsize = MapSize; m_MapdownloadAmount = 0; ResetMapDownload(); - if(g_Config.m_ClHttpMapDownload) + if(pMapSha256 && g_Config.m_ClHttpMapDownload) { char aUrl[256]; - char aFilename[64]; - char aEscaped[128]; - str_format(aFilename, sizeof(aFilename), "%s_%08x.map", pMap, MapCrc); - + char aEscaped[256]; 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. // 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("https://maps.ddnet.tw/", aUrl, 22) == 0; - str_append(aUrl, aEscaped, sizeof(aUrl)); m_pMapdownloadTask = std::make_shared(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, UseDDNetCA, true); Engine()->AddJob(m_pMapdownloadTask); @@ -2106,9 +2177,20 @@ void CClient::FinishMapDownload() int Prev = m_MapdownloadTotalsize; 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 - pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc); + pError = LoadMap(m_aMapdownloadName, aMapFile, pSha256, m_MapdownloadCrc); if(!pError) { 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[2]<<8)| (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) { DisconnectWithReason(pError); @@ -3221,7 +3303,7 @@ void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int } else 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) m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); 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() diff --git a/src/engine/client/client.h b/src/engine/client/client.h index e8004a948..544ab3489 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -5,6 +5,7 @@ #include +#include #include class CGraph @@ -139,6 +140,13 @@ class CClient : public IClient, public CDemoPlayer::IListener int m_MapdownloadCrc; int m_MapdownloadAmount; 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 m_pDDNetInfoTask; @@ -286,8 +294,8 @@ public: virtual const char *ErrorString(); - const char *LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc); - const char *LoadMapSearch(const char *pMapName, int WantedCrc); + const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc); + const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc); static int PlayerScoreNameComp(const void *a, const void *b); diff --git a/src/engine/map.h b/src/engine/map.h index f63777d3b..f2c55ffc0 100644 --- a/src/engine/map.h +++ b/src/engine/map.h @@ -3,6 +3,7 @@ #ifndef ENGINE_MAP_H #define ENGINE_MAP_H +#include #include "kernel.h" class IMap : public IInterface @@ -28,6 +29,7 @@ public: virtual bool Load(const char *pMapName) = 0; virtual bool IsLoaded() = 0; virtual void Unload() = 0; + virtual SHA256_DIGEST Sha256() = 0; virtual unsigned Crc() = 0; virtual int MapSize() = 0; virtual IOHANDLE File() = 0; diff --git a/src/engine/server.h b/src/engine/server.h index db3a2a9ca..b468c6e99 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -2,6 +2,9 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef ENGINE_SERVER_H #define ENGINE_SERVER_H + +#include + #include "kernel.h" #include "message.h" #include @@ -133,7 +136,7 @@ public: 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 SetClientClan(int ClientID, char const *pClan) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index b79e15d29..ef2b66659 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -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_State = CClient::STATE_CONNECTING; + pThis->m_aClients[ClientID].m_SupportsMapSha256 = false; pThis->m_aClients[ClientID].m_aName[0] = 0; pThis->m_aClients[ClientID].m_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; @@ -797,6 +798,7 @@ int CServer::NewClientCallback(int ClientID, void *pUser) { CServer *pThis = (CServer *)pUser; 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_aName[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->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_aClan[0] = 0; pThis->m_aClients[ClientID].m_Country = -1; @@ -901,20 +904,31 @@ void CServer::SendRconType(int ClientID, bool UsernameReq) 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); *pMapSize = m_CurrentMapSize; + *pMapSha256 = m_CurrentMapSha256; *pMapCrc = m_CurrentMapCrc; } void CServer::SendMap(int ClientID) { - CMsgPacker Msg(NETMSG_MAP_CHANGE); - Msg.AddString(GetMapName(), 0); - Msg.AddInt(m_CurrentMapCrc); - Msg.AddInt(m_CurrentMapSize); - SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); + { + 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); + Msg.AddString(GetMapName(), 0); + Msg.AddInt(m_CurrentMapCrc); + Msg.AddInt(m_CurrentMapSize); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID, true); + } m_aClients[ClientID].m_NextMapChunk = 0; } @@ -1692,8 +1706,13 @@ int CServer::LoadMap(const char *pMapName) m_IDPool.TimeoutIDs(); // get the crc of the map + m_CurrentMapSha256 = m_pMap->Sha256(); m_CurrentMapCrc = m_pMap->Crc(); 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); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBufMsg); @@ -2385,7 +2404,7 @@ void CServer::DemoRecorder_HandleAutoStart() char aDate[20]; str_timestamp(aDate, sizeof(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) { // clean up auto recorded demos @@ -2421,7 +2440,7 @@ void CServer::StartRecord(int ClientID) { char aFilename[128]; 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_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) diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 529ef1794..6017cf9be 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -3,6 +3,9 @@ #ifndef ENGINE_SERVER_SERVER_H #define ENGINE_SERVER_SERVER_H +#include +#include + #include #include @@ -13,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -140,6 +142,7 @@ public: // connection state info int m_State; + bool m_SupportsMapSha256; int m_Latency; int m_SnapRate; @@ -204,6 +207,7 @@ public: //static NETADDR4 master_server; char m_aCurrentMap[64]; + SHA256_DIGEST m_CurrentMapSha256; unsigned m_CurrentMapCrc; unsigned char *m_pCurrentMapData; unsigned int m_CurrentMapSize; @@ -245,7 +249,7 @@ public: void SetRconCID(int ClientID); int GetAuthedState(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); void GetClientAddr(int ClientID, char *pAddrStr, int Size); const char *ClientName(int ClientID); diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp index 9d5b43896..7ce66d4d1 100644 --- a/src/engine/shared/datafile.cpp +++ b/src/engine/shared/datafile.cpp @@ -1,6 +1,7 @@ /* (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 +#include #include #include #include "datafile.h" @@ -58,6 +59,7 @@ struct CDatafileInfo struct CDatafile { IOHANDLE m_File; + SHA256_DIGEST m_Sha256; unsigned m_Crc; CDatafileInfo m_Info; 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 unsigned Crc = 0; + SHA256_DIGEST Sha256; { enum { BUFFER_SIZE = 64*1024 }; + SHA256_CTX Sha256Ctxt; + sha256_init(&Sha256Ctxt); unsigned char aBuffer[BUFFER_SIZE]; while(1) @@ -94,7 +99,9 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int if(Bytes <= 0) break; Crc = crc32(Crc, aBuffer, Bytes); // ignore_convention + sha256_update(&Sha256Ctxt, aBuffer, Bytes); } + Sha256 = sha256_finish(&Sha256Ctxt); 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_pData = (char *)(pTmpDataFile+1)+Header.m_NumRawData*sizeof(char *); pTmpDataFile->m_File = File; + pTmpDataFile->m_Sha256 = Sha256; pTmpDataFile->m_Crc = Crc; // clear the data pointers @@ -226,32 +234,6 @@ bool CDataFileReader::Open(class IStorage *pStorage, const char *pFilename, int 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() { if(!m_pDataFile) { return 0; } @@ -432,6 +414,20 @@ bool CDataFileReader::Close() 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() { if(!m_pDataFile) return 0xFFFFFFFF; diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h index d194a60c0..41de5a01f 100644 --- a/src/engine/shared/datafile.h +++ b/src/engine/shared/datafile.h @@ -3,6 +3,8 @@ #ifndef ENGINE_SHARED_DATAFILE_H #define ENGINE_SHARED_DATAFILE_H +#include + // raw datafile access class CDataFileReader { @@ -18,8 +20,6 @@ public: bool Open(class IStorage *pStorage, const char *pFilename, int StorageType); bool Close(); - static bool GetCrcSize(class IStorage *pStorage, const char *pFilename, int StorageType, unsigned *pCrc, unsigned *pSize); - void *GetData(int Index); void *GetDataSwapped(int Index); // makes sure that the data is 32bit LE ints when saved int GetDataSize(int Index); @@ -32,6 +32,7 @@ public: int NumData(); void Unload(); + SHA256_DIGEST Sha256(); unsigned Crc(); int MapSize(); IOHANDLE File(); diff --git a/src/engine/shared/demo.cpp b/src/engine/shared/demo.cpp index f409346b5..2c0ffc734 100644 --- a/src/engine/shared/demo.cpp +++ b/src/engine/shared/demo.cpp @@ -33,7 +33,7 @@ CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapDat } // 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_pUser = pUser; @@ -963,7 +963,12 @@ void CDemoEditor::Slice(const char *pDemo, const char *pDst, int StartTick, int return; 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; diff --git a/src/engine/shared/demo.h b/src/engine/shared/demo.h index 1e78eb402..5de604686 100644 --- a/src/engine/shared/demo.h +++ b/src/engine/shared/demo.h @@ -3,6 +3,8 @@ #ifndef ENGINE_SHARED_DEMO_H #define ENGINE_SHARED_DEMO_H +#include + #include #include @@ -32,7 +34,7 @@ public: CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData = false); 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(); void AddDemoMarker(); @@ -77,6 +79,7 @@ public: struct CMapInfo { char m_aName[128]; + SHA256_DIGEST m_Sha256; int m_Crc; int m_Size; }; diff --git a/src/engine/shared/fetcher.h b/src/engine/shared/fetcher.h index 0beda5175..bb64fd00b 100644 --- a/src/engine/shared/fetcher.h +++ b/src/engine/shared/fetcher.h @@ -20,7 +20,7 @@ private: IStorage *m_pStorage; char m_aUrl[256]; - char m_aDest[128]; + char m_aDest[256]; int m_StorageType; bool m_UseDDNetCA; bool m_CanTimeout; diff --git a/src/engine/shared/map.cpp b/src/engine/shared/map.cpp index 912c5e232..a6da6c98e 100644 --- a/src/engine/shared/map.cpp +++ b/src/engine/shared/map.cpp @@ -39,6 +39,11 @@ public: return m_DataFile.IsOpen(); } + virtual SHA256_DIGEST Sha256() + { + return m_DataFile.Sha256(); + } + virtual unsigned Crc() { return m_DataFile.Crc(); diff --git a/src/engine/shared/protocol_ex_msgs.h b/src/engine/shared/protocol_ex_msgs.h index cac1c1198..f85881a30 100644 --- a/src/engine/shared/protocol_ex_msgs.h +++ b/src/engine/shared/protocol_ex_msgs.h @@ -22,3 +22,4 @@ UUID(NETMSG_ITIS, "it-is@ddnet.tw") UUID(NETMSG_IDONTKNOW, "i-dont-know@ddnet.tw") UUID(NETMSG_RCONTYPE, "rcon-type@ddnet.tw") +UUID(NETMSG_MAP_DETAILS, "map-details@ddnet.tw") diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 1b558fdbe..ddc7858d2 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -2632,8 +2632,9 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/) char aMapName[128]; int MapSize; + SHA256_DIGEST MapSha256; int MapCrc; - Server()->GetMapInfo(aMapName, sizeof(aMapName), &MapSize, &MapCrc); + Server()->GetMapInfo(aMapName, sizeof(aMapName), &MapSize, &MapSha256, &MapCrc); m_MapBugs = GetMapBugs(aMapName, MapSize, MapCrc); // reset everything here @@ -2743,6 +2744,7 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/) GameInfo.m_pMapName = aMapName; GameInfo.m_MapSize = MapSize; + GameInfo.m_MapSha256 = MapSha256; GameInfo.m_MapCrc = MapCrc; m_TeeHistorian.Reset(&GameInfo, TeeHistorianWrite, this); diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index 81a3673f6..5ecbb2c68 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -66,9 +66,11 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) char aGameUuid[UUID_MAXSTRSIZE]; char aStartTime[128]; + char aMapSha256[SHA256_MAXSTRSIZE]; FormatUuid(pGameInfo->m_GameUuid, aGameUuid, sizeof(aGameUuid)); 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 aServerVersionBuffer[128]; @@ -76,12 +78,13 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) char aServerNameBuffer[128]; char aGameTypeBuffer[128]; char aMapNameBuffer[128]; + char aMapSha256Buffer[256]; char aJson[2048]; #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), TEEHISTORIAN_VERSION, aGameUuid, @@ -92,6 +95,7 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) E(aGameTypeBuffer, pGameInfo->m_pGameType), E(aMapNameBuffer, pGameInfo->m_pMapName), pGameInfo->m_MapSize, + E(aMapSha256Buffer, aMapSha256), pGameInfo->m_MapCrc); Write(aJson, str_length(aJson)); diff --git a/src/game/server/teehistorian.h b/src/game/server/teehistorian.h index 943f3ebd9..36bf083ac 100644 --- a/src/game/server/teehistorian.h +++ b/src/game/server/teehistorian.h @@ -1,3 +1,4 @@ +#include #include #include #include @@ -27,6 +28,7 @@ public: const char *m_pMapName; int m_MapSize; + SHA256_DIGEST m_MapSha256; int m_MapCrc; CConfiguration *m_pConfig; diff --git a/src/test/hash.cpp b/src/test/hash.cpp new file mode 100644 index 000000000..7a018e215 --- /dev/null +++ b/src/test/hash.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +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)); +} diff --git a/src/test/teehistorian.cpp b/src/test/teehistorian.cpp index ce05043c3..4a2c87d2d 100644 --- a/src/test/teehistorian.cpp +++ b/src/test/teehistorian.cpp @@ -43,6 +43,13 @@ protected: RegisterTeehistorianUuids(&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)); m_GameInfo.m_GameUuid = CalculateUuid("test@ddnet.tw"); @@ -55,6 +62,7 @@ protected: m_GameInfo.m_pMapName = "Kobra 3 Solo"; m_GameInfo.m_MapSize = 903514; + m_GameInfo.m_MapSha256 = Sha256; m_GameInfo.m_MapCrc = 0xeceaf25c; m_GameInfo.m_pConfig = &m_Config; @@ -81,7 +89,7 @@ protected: { 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 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[] = "]}"; char aTimeBuf[64]; diff --git a/src/tools/map_version.cpp b/src/tools/map_version.cpp deleted file mode 100644 index 6de367069..000000000 --- a/src/tools/map_version.cpp +++ /dev/null @@ -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 -#include - -#include -#include -#include - - -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; -}