diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index c11b57b09..cdd8d90db 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -84,7 +84,6 @@ CClient::CClient() : for(auto &DemoRecorder : m_aDemoRecorder) DemoRecorder = CDemoRecorder(&m_SnapshotDelta); m_LastRenderTime = time_get(); - IStorage::FormatTmpPath(m_aDDNetInfoTmp, sizeof(m_aDDNetInfoTmp), DDNET_INFO_FILE); mem_zero(m_aInputs, sizeof(m_aInputs)); mem_zero(m_aapSnapshots, sizeof(m_aapSnapshots)); for(auto &SnapshotStorage : m_aSnapshotStorage) @@ -2054,7 +2053,7 @@ void CClient::FinishMapDownload() } } -void CClient::ResetDDNetInfo() +void CClient::ResetDDNetInfoTask() { if(m_pDDNetInfoTask) { @@ -2063,56 +2062,50 @@ void CClient::ResetDDNetInfo() } } -bool CClient::IsDDNetInfoChanged() -{ - IOHANDLE OldFile = m_pStorage->OpenFile(DDNET_INFO_FILE, IOFLAG_READ | IOFLAG_SKIP_BOM, IStorage::TYPE_SAVE); - - if(!OldFile) - return true; - - IOHANDLE NewFile = m_pStorage->OpenFile(m_aDDNetInfoTmp, IOFLAG_READ | IOFLAG_SKIP_BOM, IStorage::TYPE_SAVE); - - if(NewFile) - { - char aOldData[4096]; - char aNewData[4096]; - unsigned OldBytes; - unsigned NewBytes; - - do - { - OldBytes = io_read(OldFile, aOldData, sizeof(aOldData)); - NewBytes = io_read(NewFile, aNewData, sizeof(aNewData)); - - if(OldBytes != NewBytes || mem_comp(aOldData, aNewData, OldBytes) != 0) - { - io_close(NewFile); - io_close(OldFile); - return true; - } - } while(OldBytes > 0); - - io_close(NewFile); - } - - io_close(OldFile); - return false; -} - void CClient::FinishDDNetInfo() { - ResetDDNetInfo(); - if(IsDDNetInfoChanged()) + if(m_ServerBrowser.DDNetInfoSha256() == m_pDDNetInfoTask->Sha256()) { - m_pStorage->RenameFile(m_aDDNetInfoTmp, DDNET_INFO_FILE, IStorage::TYPE_SAVE); - LoadDDNetInfo(); - if(m_ServerBrowser.GetCurrentType() == IServerBrowser::TYPE_INTERNET || m_ServerBrowser.GetCurrentType() == IServerBrowser::TYPE_FAVORITES) - m_ServerBrowser.Refresh(m_ServerBrowser.GetCurrentType()); + log_debug("client/info", "DDNet info already up-to-date"); + return; } - else + + char aTempFilename[IO_MAX_PATH_LENGTH]; + IStorage::FormatTmpPath(aTempFilename, sizeof(aTempFilename), DDNET_INFO_FILE); + IOHANDLE File = Storage()->OpenFile(aTempFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); + if(!File) { - m_pStorage->RemoveFile(m_aDDNetInfoTmp, IStorage::TYPE_SAVE); + log_error("client/info", "Failed to open temporary DDNet info '%s' for writing", aTempFilename); + return; } + + unsigned char *pResult; + size_t ResultLength; + m_pDDNetInfoTask->Result(&pResult, &ResultLength); + dbg_assert(pResult != nullptr, "Invalid info task state"); + bool Error = io_write(File, pResult, ResultLength) != ResultLength; + Error |= io_close(File) != 0; + if(Error) + { + log_error("client/info", "Error writing temporary DDNet info to file '%s'", aTempFilename); + return; + } + + if(Storage()->FileExists(DDNET_INFO_FILE, IStorage::TYPE_SAVE) && !Storage()->RemoveFile(DDNET_INFO_FILE, IStorage::TYPE_SAVE)) + { + log_error("client/info", "Failed to remove old DDNet info '%s'", DDNET_INFO_FILE); + Storage()->RemoveFile(aTempFilename, IStorage::TYPE_SAVE); + return; + } + if(!Storage()->RenameFile(aTempFilename, DDNET_INFO_FILE, IStorage::TYPE_SAVE)) + { + log_error("client/info", "Failed to rename temporary DDNet info '%s' to '%s'", aTempFilename, DDNET_INFO_FILE); + Storage()->RemoveFile(aTempFilename, IStorage::TYPE_SAVE); + return; + } + + log_debug("client/info", "Loading new DDNet info"); + LoadDDNetInfo(); } typedef std::tuple TVersion; @@ -2590,16 +2583,13 @@ void CClient::Update() if(m_pDDNetInfoTask) { if(m_pDDNetInfoTask->State() == EHttpState::DONE) + { FinishDDNetInfo(); - else if(m_pDDNetInfoTask->State() == EHttpState::ERROR) - { - Storage()->RemoveFile(m_aDDNetInfoTmp, IStorage::TYPE_SAVE); - ResetDDNetInfo(); + ResetDDNetInfoTask(); } - else if(m_pDDNetInfoTask->State() == EHttpState::ABORTED) + else if(m_pDDNetInfoTask->State() == EHttpState::ERROR || m_pDDNetInfoTask->State() == EHttpState::ABORTED) { - Storage()->RemoveFile(m_aDDNetInfoTmp, IStorage::TYPE_SAVE); - m_pDDNetInfoTask = NULL; + ResetDDNetInfoTask(); } } @@ -2685,8 +2675,6 @@ void CClient::InitInterfaces() m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage); - m_Http.Init(std::chrono::seconds{1}); - m_ServerBrowser.SetBaseInfo(&m_aNetClient[CONN_CONTACT], m_pGameClient->NetVersion()); #if defined(CONF_AUTOUPDATE) @@ -2757,6 +2745,14 @@ void CClient::Run() } #endif + if(!m_Http.Init(std::chrono::seconds{1})) + { + const char *pErrorMessage = "Failed to initialize the HTTP client."; + log_error("client", "%s", pErrorMessage); + ShowMessageBox("HTTP Error", pErrorMessage); + return; + } + // init text render m_pTextRender = Kernel()->RequestInterface(); m_pTextRender->Init(); @@ -3002,11 +2998,6 @@ void CClient::Run() s_SavedConfig = true; } - if(m_pStorage->FileExists(m_aDDNetInfoTmp, IStorage::TYPE_SAVE)) - { - m_pStorage->RemoveFile(m_aDDNetInfoTmp, IStorage::TYPE_SAVE); - } - if(m_vWarnings.empty() && !GameClient()->IsDisplayingWarning()) break; } @@ -4572,7 +4563,7 @@ void CClient::RequestDDNetInfo() } // Use ipv4 so we can know the ingame ip addresses of players before they join game servers - m_pDDNetInfoTask = HttpGetFile(aUrl, Storage(), m_aDDNetInfoTmp, IStorage::TYPE_SAVE); + m_pDDNetInfoTask = HttpGet(aUrl); m_pDDNetInfoTask->Timeout(CTimeout{10000, 0, 500, 10}); m_pDDNetInfoTask->IpResolve(IPRESOLVE::V4); Http()->Run(m_pDDNetInfoTask); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 848351c23..e392f55c3 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -157,7 +157,6 @@ class CClient : public IClient, public CDemoPlayer::IListener SHA256_DIGEST m_MapDetailsSha256 = SHA256_ZEROED; char m_aMapDetailsUrl[256] = ""; - char m_aDDNetInfoTmp[64]; std::shared_ptr m_pDDNetInfoTask = nullptr; // time @@ -353,8 +352,7 @@ public: void FinishMapDownload(); void RequestDDNetInfo() override; - void ResetDDNetInfo(); - bool IsDDNetInfoChanged(); + void ResetDDNetInfoTask(); void FinishDDNetInfo(); void LoadDDNetInfo(); diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index ad51d29c6..71c8f14f0 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -66,9 +66,6 @@ CServerBrowser::CServerBrowser() : m_BroadcastTime = 0; secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed)); - m_pDDNetInfo = nullptr; - m_DDNetInfoUpdateTime = 0; - CleanUp(); } @@ -1272,7 +1269,6 @@ const json_value *CServerBrowser::LoadDDNetInfo() UpdateServerCommunity(&m_ppServerlist[i]->m_Info); UpdateServerRank(&m_ppServerlist[i]->m_Info); } - m_DDNetInfoUpdateTime = time_get(); return m_pDDNetInfo; } @@ -1281,7 +1277,12 @@ void CServerBrowser::LoadDDNetInfoJson() void *pBuf; unsigned Length; if(!m_pStorage->ReadFile(DDNET_INFO_FILE, IStorage::TYPE_SAVE, &pBuf, &Length)) + { + m_DDNetInfoSha256 = SHA256_ZEROED; return; + } + + m_DDNetInfoSha256 = sha256(pBuf, Length); json_value_free(m_pDDNetInfo); json_settings JsonSettings{}; diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index ce40d86f5..b2c57d402 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -3,6 +3,7 @@ #ifndef ENGINE_CLIENT_SERVERBROWSER_H #define ENGINE_CLIENT_SERVERBROWSER_H +#include #include #include @@ -252,7 +253,7 @@ public: unsigned CurrentCommunitiesHash() const override; bool DDNetInfoAvailable() const override { return m_pDDNetInfo != nullptr; } - int64_t DDNetInfoUpdateTime() const override { return m_DDNetInfoUpdateTime; } + SHA256_DIGEST DDNetInfoSha256() const override { return m_DDNetInfoSha256; } CFavoriteCommunityFilterList &FavoriteCommunitiesFilter() override { return m_FavoriteCommunitiesFilter; } CExcludedCommunityFilterList &CommunitiesFilter() override { return m_CommunitiesFilter; } @@ -311,8 +312,8 @@ private: CExcludedCommunityCountryFilterList m_CountriesFilter; CExcludedCommunityTypeFilterList m_TypesFilter; - json_value *m_pDDNetInfo; - int64_t m_DDNetInfoUpdateTime; + json_value *m_pDDNetInfo = nullptr; + SHA256_DIGEST m_DDNetInfoSha256 = SHA256_ZEROED; CServerEntry *m_pFirstReqServer; // request list CServerEntry *m_pLastReqServer; diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 2e9cb6351..f358764d1 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -306,7 +306,7 @@ public: virtual unsigned CurrentCommunitiesHash() const = 0; virtual bool DDNetInfoAvailable() const = 0; - virtual int64_t DDNetInfoUpdateTime() const = 0; + virtual SHA256_DIGEST DDNetInfoSha256() const = 0; virtual IFilterList &FavoriteCommunitiesFilter() = 0; virtual IFilterList &CommunitiesFilter() = 0; diff --git a/src/engine/shared/http.cpp b/src/engine/shared/http.cpp index 771cf2052..bb25822f9 100644 --- a/src/engine/shared/http.cpp +++ b/src/engine/shared/http.cpp @@ -67,26 +67,15 @@ bool HttpHasIpresolveBug() CHttpRequest::CHttpRequest(const char *pUrl) { str_copy(m_aUrl, pUrl); - sha256_init(&m_ActualSha256); + sha256_init(&m_ActualSha256Ctx); } CHttpRequest::~CHttpRequest() { - m_ResponseLength = 0; - if(!m_WriteToFile) - { - m_BufferSize = 0; - free(m_pBuffer); - m_pBuffer = nullptr; - } + dbg_assert(m_File == nullptr, "HTTP request file was not closed"); + free(m_pBuffer); curl_slist_free_all((curl_slist *)m_pHeaders); - m_pHeaders = nullptr; - if(m_pBody) - { - m_BodyLength = 0; - free(m_pBody); - m_pBody = nullptr; - } + free(m_pBody); } bool CHttpRequest::BeforeInit() @@ -95,14 +84,14 @@ bool CHttpRequest::BeforeInit() { if(fs_makedir_rec_for(m_aDestAbsolute) < 0) { - dbg_msg("http", "i/o error, cannot create folder for: %s", m_aDest); + log_error("http", "i/o error, cannot create folder for: %s", m_aDest); return false; } m_File = io_open(m_aDestAbsolute, IOFLAG_WRITE); if(!m_File) { - dbg_msg("http", "i/o error, cannot open file: %s", m_aDest); + log_error("http", "i/o error, cannot open file: %s", m_aDest); return false; } } @@ -224,7 +213,7 @@ size_t CHttpRequest::OnData(char *pData, size_t DataSize) return 0; } - sha256_update(&m_ActualSha256, pData, DataSize); + sha256_update(&m_ActualSha256Ctx, pData, DataSize); if(!m_WriteToFile) { @@ -277,34 +266,38 @@ void CHttpRequest::OnCompletionInternal(std::optional Result) if(Code != CURLE_OK) { if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE) - dbg_msg("http", "%s failed. libcurl error (%u): %s", m_aUrl, Code, m_aErr); + { + log_error("http", "%s failed. libcurl error (%u): %s", m_aUrl, Code, m_aErr); + } State = (Code == CURLE_ABORTED_BY_CALLBACK) ? EHttpState::ABORTED : EHttpState::ERROR; } else { if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL) - dbg_msg("http", "task done: %s", m_aUrl); + { + log_info("http", "task done: %s", m_aUrl); + } State = EHttpState::DONE; } } else { - dbg_msg("http", "%s failed. internal error: %s", m_aUrl, m_aErr); + log_error("http", "%s failed. internal error: %s", m_aUrl, m_aErr); State = EHttpState::ERROR; } - if(State == EHttpState::DONE && m_ExpectedSha256 != SHA256_ZEROED) + if(State == EHttpState::DONE) { - const SHA256_DIGEST ActualSha256 = sha256_finish(&m_ActualSha256); - if(ActualSha256 != m_ExpectedSha256) + m_ActualSha256 = sha256_finish(&m_ActualSha256Ctx); + if(m_ExpectedSha256 != SHA256_ZEROED && m_ActualSha256 != m_ExpectedSha256) { if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE) { char aActualSha256[SHA256_MAXSTRSIZE]; - sha256_str(ActualSha256, aActualSha256, sizeof(aActualSha256)); + sha256_str(m_ActualSha256, aActualSha256, sizeof(aActualSha256)); char aExpectedSha256[SHA256_MAXSTRSIZE]; sha256_str(m_ExpectedSha256, aExpectedSha256, sizeof(aExpectedSha256)); - dbg_msg("http", "SHA256 mismatch: got=%s, expected=%s, url=%s", aActualSha256, aExpectedSha256, m_aUrl); + log_error("http", "SHA256 mismatch: got=%s, expected=%s, url=%s", aActualSha256, aExpectedSha256, m_aUrl); } State = EHttpState::ERROR; } @@ -314,9 +307,10 @@ void CHttpRequest::OnCompletionInternal(std::optional Result) { if(m_File && io_close(m_File) != 0) { - dbg_msg("http", "i/o error, cannot close file: %s", m_aDest); + log_error("http", "i/o error, cannot close file: %s", m_aDest); State = EHttpState::ERROR; } + m_File = nullptr; if(State == EHttpState::ERROR || State == EHttpState::ABORTED) { @@ -422,7 +416,7 @@ void CHttp::RunLoop() std::unique_lock Lock(m_Lock); if(curl_global_init(CURL_GLOBAL_DEFAULT)) { - dbg_msg("http", "curl_global_init failed"); + log_error("http", "curl_global_init failed"); m_State = CHttp::ERROR; m_Cv.notify_all(); return; @@ -431,7 +425,7 @@ void CHttp::RunLoop() m_pMultiH = curl_multi_init(); if(!m_pMultiH) { - dbg_msg("http", "curl_multi_init failed"); + log_error("http", "curl_multi_init failed"); m_State = CHttp::ERROR; m_Cv.notify_all(); return; @@ -440,19 +434,18 @@ void CHttp::RunLoop() // print curl version { curl_version_info_data *pVersion = curl_version_info(CURLVERSION_NOW); - dbg_msg("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version); + log_info("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version); } m_State = CHttp::RUNNING; m_Cv.notify_all(); - dbg_msg("http", "running"); Lock.unlock(); while(m_State == CHttp::RUNNING) { - static int NextTimeout = std::numeric_limits::max(); + static int s_NextTimeout = std::numeric_limits::max(); int Events = 0; - CURLMcode mc = curl_multi_poll(m_pMultiH, NULL, 0, NextTimeout, &Events); + const CURLMcode PollCode = curl_multi_poll(m_pMultiH, nullptr, 0, s_NextTimeout, &Events); // We may have been woken up for a shutdown if(m_Shutdown) @@ -461,7 +454,7 @@ void CHttp::RunLoop() if(!m_ShutdownTime.has_value()) { m_ShutdownTime = Now + m_ShutdownDelay; - NextTimeout = m_ShutdownDelay.count(); + s_NextTimeout = m_ShutdownDelay.count(); } else if(m_ShutdownTime < Now || m_RunningRequests.empty()) { @@ -469,36 +462,36 @@ void CHttp::RunLoop() } } - if(mc != CURLM_OK) + if(PollCode != CURLM_OK) { Lock.lock(); - dbg_msg("http", "Failed multi wait: %s", curl_multi_strerror(mc)); + log_error("http", "Failed multi wait: %s", curl_multi_strerror(PollCode)); m_State = CHttp::ERROR; break; } - mc = curl_multi_perform(m_pMultiH, &Events); - if(mc != CURLM_OK) + const CURLMcode PerformCode = curl_multi_perform(m_pMultiH, &Events); + if(PerformCode != CURLM_OK) { Lock.lock(); - dbg_msg("http", "Failed multi perform: %s", curl_multi_strerror(mc)); + log_error("http", "Failed multi perform: %s", curl_multi_strerror(PerformCode)); m_State = CHttp::ERROR; break; } - struct CURLMsg *m; - while((m = curl_multi_info_read(m_pMultiH, &Events))) + struct CURLMsg *pMsg; + while((pMsg = curl_multi_info_read(m_pMultiH, &Events))) { - if(m->msg == CURLMSG_DONE) + if(pMsg->msg == CURLMSG_DONE) { - auto RequestIt = m_RunningRequests.find(m->easy_handle); + auto RequestIt = m_RunningRequests.find(pMsg->easy_handle); dbg_assert(RequestIt != m_RunningRequests.end(), "Running handle not added to map"); auto pRequest = std::move(RequestIt->second); m_RunningRequests.erase(RequestIt); - pRequest->OnCompletionInternal(m->data.result); - curl_multi_remove_handle(m_pMultiH, m->easy_handle); - curl_easy_cleanup(m->easy_handle); + pRequest->OnCompletionInternal(pMsg->data.result); + curl_multi_remove_handle(m_pMultiH, pMsg->easy_handle); + curl_easy_cleanup(pMsg->easy_handle); } } @@ -511,7 +504,7 @@ void CHttp::RunLoop() { auto &pRequest = NewRequests.front(); if(g_Config.m_DbgCurl) - dbg_msg("http", "task: %s %s", CHttpRequest::GetRequestType(pRequest->m_Type), pRequest->m_aUrl); + log_debug("http", "task: %s %s", CHttpRequest::GetRequestType(pRequest->m_Type), pRequest->m_aUrl); CURL *pEH = curl_easy_init(); if(!pEH) @@ -520,8 +513,7 @@ void CHttp::RunLoop() if(!pRequest->ConfigureHandle(pEH)) goto error_configure; - mc = curl_multi_add_handle(m_pMultiH, pEH); - if(mc != CURLM_OK) + if(curl_multi_add_handle(m_pMultiH, pEH) != CURLM_OK) goto error_configure; m_RunningRequests.emplace(pEH, std::move(pRequest)); @@ -532,7 +524,7 @@ void CHttp::RunLoop() error_configure: curl_easy_cleanup(pEH); error_init: - dbg_msg("http", "failed to start new request"); + log_error("http", "failed to start new request"); Lock.lock(); m_State = CHttp::ERROR; break; diff --git a/src/engine/shared/http.h b/src/engine/shared/http.h index 7d9bcaa8c..90fd201d1 100644 --- a/src/engine/shared/http.h +++ b/src/engine/shared/http.h @@ -88,7 +88,8 @@ class CHttpRequest : public IHttpRequest int64_t m_MaxResponseSize = -1; REQUEST m_Type = REQUEST::GET; - SHA256_CTX m_ActualSha256; + SHA256_DIGEST m_ActualSha256 = SHA256_ZEROED; + SHA256_CTX m_ActualSha256Ctx; SHA256_DIGEST m_ExpectedSha256 = SHA256_ZEROED; bool m_WriteToFile = false; @@ -197,6 +198,8 @@ public: void Result(unsigned char **ppResult, size_t *pResultLength) const; json_value *ResultJson() const; + + const SHA256_DIGEST &Sha256() const { return m_ActualSha256; } }; inline std::unique_ptr HttpHead(const char *pUrl) diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 0018464f8..49639c5b9 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -506,7 +506,7 @@ protected: static void ConchainUiPageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); struct SCommunityCache { - int64_t m_UpdateTime = 0; + SHA256_DIGEST m_InfoSha256 = SHA256_ZEROED; int m_LastPage = 0; unsigned m_SelectedCommunitiesHash; std::vector m_vpSelectedCommunities; @@ -560,7 +560,7 @@ protected: std::vector m_vCommunityIcons; std::deque> m_CommunityIconLoadJobs; std::deque> m_CommunityIconDownloadJobs; - int64_t m_CommunityIconsUpdateTime = 0; + SHA256_DIGEST m_CommunityIconsInfoSha256 = SHA256_ZEROED; static int CommunityIconScan(const char *pName, int IsDir, int DirType, void *pUser); const SCommunityIcon *FindCommunityIcon(const char *pCommunityId); bool LoadCommunityIconFile(const char *pPath, int DirType, CImageInfo &Info, SHA256_DIGEST &Sha256); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 7d07e3e79..5d826e289 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1840,8 +1840,8 @@ void CMenus::UpdateCommunityCache(bool Force) ServerBrowser()->Refresh(g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1 + IServerBrowser::TYPE_FAVORITE_COMMUNITY_1, true); } - if(!Force && m_CommunityCache.m_UpdateTime != 0 && - m_CommunityCache.m_UpdateTime == ServerBrowser()->DDNetInfoUpdateTime() && + if(!Force && m_CommunityCache.m_InfoSha256 != SHA256_ZEROED && + m_CommunityCache.m_InfoSha256 == ServerBrowser()->DDNetInfoSha256() && !CurrentCommunitiesChanged && !PageChanged) { return; @@ -1849,7 +1849,7 @@ void CMenus::UpdateCommunityCache(bool Force) ServerBrowser()->CleanFilters(); - m_CommunityCache.m_UpdateTime = ServerBrowser()->DDNetInfoUpdateTime(); + m_CommunityCache.m_InfoSha256 = ServerBrowser()->DDNetInfoSha256(); m_CommunityCache.m_LastPage = g_Config.m_UiPage; m_CommunityCache.m_SelectedCommunitiesHash = CommunitiesHash; m_CommunityCache.m_vpSelectedCommunities = ServerBrowser()->CurrentCommunities(); @@ -2055,9 +2055,9 @@ void CMenus::UpdateCommunityIcons() } // Rescan for changed communities only when necessary - if(!ServerBrowser()->DDNetInfoAvailable() || (m_CommunityIconsUpdateTime != 0 && m_CommunityIconsUpdateTime == ServerBrowser()->DDNetInfoUpdateTime())) + if(!ServerBrowser()->DDNetInfoAvailable() || (m_CommunityIconsInfoSha256 != SHA256_ZEROED && m_CommunityIconsInfoSha256 == ServerBrowser()->DDNetInfoSha256())) return; - m_CommunityIconsUpdateTime = ServerBrowser()->DDNetInfoUpdateTime(); + m_CommunityIconsInfoSha256 = ServerBrowser()->DDNetInfoSha256(); // Remove icons for removed communities auto RemovalIterator = m_vCommunityIcons.begin();