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; -}