From c7cd281aff683e03fe8bf1850208b280dbe956f1 Mon Sep 17 00:00:00 2001 From: heinrich5991 Date: Sat, 18 Nov 2023 21:52:51 +0100 Subject: [PATCH] Add icon URL, remove `servers-key`/`ranks-key` from community JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously: ```json { "ddnet": { "name": "DDraceNetwork", "servers-key": "servers", "ranks-key": "maps", "icon-sha256": "162313eeb954e34495ad03066fee1418ed3c963e76cc83dff993e1c7856c742b" } } ``` Now: ```json [ { "id": "ddnet", "name": "DDraceNetwork", "icon": { "sha256": "162313eeb954e34495ad03066fee1418ed3c963e76cc83dff993e1c7856c742b", "url": "https://info.ddnet.org/icons/ddnet.png" }, "ranks": [ … ], "servers": { … } } ] ``` Special case `ddnet` to look for the `servers`, `maps` in the top-level and `kog` to look for `servers-kog` in the top-level. --- src/engine/client.h | 2 - src/engine/client/client.cpp | 7 - src/engine/client/serverbrowser.cpp | 253 +++++++++++-------- src/engine/client/serverbrowser.h | 3 + src/engine/serverbrowser.h | 16 +- src/game/client/components/menus_browser.cpp | 8 +- 6 files changed, 157 insertions(+), 132 deletions(-) diff --git a/src/engine/client.h b/src/engine/client.h index fdaf199b7..5028a167f 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -94,7 +94,6 @@ protected: TMapLoadingCallbackFunc m_MapLoadingCBFunc; char m_aNews[3000]; - char m_aCommunityIconsDownloadUrl[256]; int m_Points; int64_t m_ReconnectTime; @@ -258,7 +257,6 @@ public: virtual unsigned GetCurrentMapCrc() const = 0; const char *News() const { return m_aNews; } - const char *CommunityIconsDownloadUrl() const { return m_aCommunityIconsDownloadUrl; } int Points() const { return m_Points; } int64_t ReconnectTime() const { return m_ReconnectTime; } void SetReconnectTime(int64_t ReconnectTime) { m_ReconnectTime = ReconnectTime; } diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index d6f365abc..708b39e3d 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -135,7 +135,6 @@ CClient::CClient() : m_pDDNetInfoTask = NULL; m_aNews[0] = '\0'; m_aMapDownloadUrl[0] = '\0'; - m_aCommunityIconsDownloadUrl[0] = '\0'; m_Points = -1; m_CurrentServerInfoRequestTime = -1; @@ -2245,12 +2244,6 @@ void CClient::LoadDDNetInfo() str_copy(m_aMapDownloadUrl, MapDownloadUrl); } - const json_value &CommunityIconsDownloadUrl = DDNetInfo["community-icons-download-url"]; - if(CommunityIconsDownloadUrl.type == json_string) - { - str_copy(m_aCommunityIconsDownloadUrl, CommunityIconsDownloadUrl); - } - const json_value &Points = DDNetInfo["points"]; if(Points.type == json_integer) { diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 5b99ab19e..ce29a4da0 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -1141,6 +1141,91 @@ void CServerBrowser::LoadDDNetLocation() } } +bool CServerBrowser::ParseCommunityServers(CCommunity *pCommunity, const json_value &Servers) +{ + for(unsigned ServerIndex = 0; ServerIndex < Servers.u.array.length; ++ServerIndex) + { + // pServer - { name, flagId, servers } + const json_value &Server = Servers[ServerIndex]; + if(Server.type != json_object) + { + log_error("serverbrowser", "invalid server (ServerIndex=%u)", ServerIndex); + return false; + } + + const json_value &Name = Server["name"]; + const json_value &FlagId = Server["flagId"]; + const json_value &Types = Server["servers"]; + if(Name.type != json_string || FlagId.type != json_integer || Types.type != json_object) + { + log_error("serverbrowser", "invalid server attribute (ServerIndex=%u)", ServerIndex); + return false; + } + if(Types.u.object.length == 0) + continue; + + pCommunity->m_vCountries.emplace_back(Name.u.string.ptr, FlagId.u.integer); + CCommunityCountry *pCountry = &pCommunity->m_vCountries.back(); + + for(unsigned TypeIndex = 0; TypeIndex < Types.u.object.length; ++TypeIndex) + { + const json_value &Addresses = *Types.u.object.values[TypeIndex].value; + if(Addresses.type != json_array) + { + log_error("serverbrowser", "invalid addresses (ServerIndex=%u, TypeIndex=%u)", ServerIndex, TypeIndex); + return false; + } + if(Addresses.u.array.length == 0) + continue; + + const char *pTypeName = Types.u.object.values[TypeIndex].name; + + // add type if it doesn't exist already + const auto CommunityType = std::find_if(pCommunity->m_vTypes.begin(), pCommunity->m_vTypes.end(), [pTypeName](const auto &Elem) { + return str_comp(Elem.Name(), pTypeName) == 0; + }); + if(CommunityType == pCommunity->m_vTypes.end()) + { + pCommunity->m_vTypes.emplace_back(pTypeName); + } + + // add addresses + for(unsigned AddressIndex = 0; AddressIndex < Addresses.u.array.length; ++AddressIndex) + { + const json_value &Address = Addresses[AddressIndex]; + if(Address.type != json_string) + { + log_error("serverbrowser", "invalid address (ServerIndex=%u, TypeIndex=%u, AddressIndex=%u)", ServerIndex, TypeIndex, AddressIndex); + return false; + } + NETADDR NetAddr; + if(net_addr_from_str(&NetAddr, Address.u.string.ptr)) + { + log_error("serverbrowser", "invalid address (ServerIndex=%u, TypeIndex=%u, AddressIndex=%u)", ServerIndex, TypeIndex, AddressIndex); + continue; + } + pCountry->m_vServers.emplace_back(NetAddr, pTypeName); + } + } + } + return true; +} + +bool CServerBrowser::ParseCommunityFinishes(CCommunity *pCommunity, const json_value &Finishes) +{ + for(unsigned FinishIndex = 0; FinishIndex < Finishes.u.array.length; ++FinishIndex) + { + const json_value &Finish = Finishes[FinishIndex]; + if(Finish.type != json_string) + { + log_error("serverbrowser", "invalid rank (FinishIndex=%u)", FinishIndex); + return false; + } + pCommunity->m_FinishedMaps.emplace((const char *)Finish); + } + return true; +} + void CServerBrowser::LoadDDNetServers() { // Parse communities @@ -1151,136 +1236,90 @@ void CServerBrowser::LoadDDNetServers() return; const json_value &Communities = (*m_pDDNetInfo)["communities"]; - if(Communities.type != json_object) + if(Communities.type != json_array) return; - for(unsigned CommunityIndex = 0; CommunityIndex < Communities.u.object.length; ++CommunityIndex) + for(unsigned CommunityIndex = 0; CommunityIndex < Communities.u.array.length; ++CommunityIndex) { - const char *pCommunityId = Communities.u.object.values[CommunityIndex].name; - const json_value &Community = *Communities.u.object.values[CommunityIndex].value; + const json_value &Community = Communities[CommunityIndex]; if(Community.type != json_object) { - log_error("serverbrowser", "invalid community (CommunityId=%s)", pCommunityId); + log_error("serverbrowser", "invalid community (CommunityIndex=%d)", (int)CommunityIndex); continue; } - const json_value &Name = Community["name"]; - const json_value &JsonServersKey = Community["servers-key"]; - const json_value &JsonRanksKey = Community["ranks-key"]; - const json_value &IconSha256 = Community["icon-sha256"]; - if(Name.type != json_string || JsonServersKey.type != json_string || JsonRanksKey.type != json_string || IconSha256.type != json_string) + const json_value &Id = Community["id"]; + if(Id.type != json_string) { - log_error("serverbrowser", "invalid community attribute (CommunityId=%s)", pCommunityId); + log_error("serverbrowser", "invalid community id (CommunityIndex=%d)", (int)CommunityIndex); + continue; + } + const json_value &Icon = Community["icon"]; + const json_value &IconSha256 = Icon["sha256"]; + const json_value &IconUrl = Icon["url"]; + const json_value &Name = Community["name"]; + const json_value *pFinishes = &Icon["finishes"]; + const json_value *pServers = &Icon["servers"]; + if(pFinishes->type == json_none) + { + if(str_comp(Id, COMMUNITY_DDNET) == 0) + { + pFinishes = &(*m_pDDNetInfo)["maps"]; + } + } + // Backward compatibility. + if(pServers->type == json_none) + { + if(str_comp(Id, COMMUNITY_DDNET) == 0) + { + pServers = &(*m_pDDNetInfo)["servers"]; + } + else if(str_comp(Id, "kog") == 0) + { + pServers = &(*m_pDDNetInfo)["servers-kog"]; + } + } + if(false || + Icon.type != json_object || + IconSha256.type != json_string || + IconUrl.type != json_string || + Name.type != json_string || + (pFinishes->type != json_array && pFinishes->type != json_none) || + pServers->type != json_array) + { + log_error("serverbrowser", "invalid community attribute (CommunityId=%s)", (const char *)Id); continue; } SHA256_DIGEST ParsedIconSha256; - if(sha256_from_str(&ParsedIconSha256, IconSha256.u.string.ptr) != 0) + if(sha256_from_str(&ParsedIconSha256, IconSha256) != 0) { - log_error("serverbrowser", "invalid community icon sha256 (CommunityId=%s)", pCommunityId); + log_error("serverbrowser", "invalid community icon sha256 (CommunityId=%s)", (const char *)Id); continue; } - m_vCommunities.emplace_back(pCommunityId, Name.u.string.ptr, JsonServersKey.u.string.ptr, JsonRanksKey.u.string.ptr, ParsedIconSha256); - } - - // Parse finishes for each community - for(auto &Community : m_vCommunities) - { - if(!Community.HasRanks()) - continue; - const json_value &Ranks = (*m_pDDNetInfo)[Community.JsonRanksKey()]; - if(Ranks.type != json_array) + CCommunity NewCommunity(Id, Name, ParsedIconSha256, IconUrl); + if(ParseCommunityServers(&NewCommunity, *pServers)) { - log_error("serverbrowser", "invalid community ranks (CommunityId=%s)", Community.Id()); + log_error("serverbrowser", "invalid community servers (CommunityId=%s)", NewCommunity.Id()); + continue; + } + NewCommunity.m_HasFinishes = pFinishes->type == json_array; + if(NewCommunity.m_HasFinishes && ParseCommunityFinishes(&NewCommunity, *pFinishes)) + { + log_error("serverbrowser", "invalid community finishes (CommunityId=%s)", NewCommunity.Id()); continue; } - for(unsigned RankIndex = 0; RankIndex < Ranks.u.array.length; ++RankIndex) + for(const auto &Country : NewCommunity.Countries()) { - const json_value &Rank = *Ranks.u.array.values[RankIndex]; - if(Rank.type != json_string) + for(const auto &Server : Country.Servers()) { - log_error("serverbrowser", "invalid rank (RankIndex=%u)", RankIndex); - continue; - } - Community.m_RankedMaps.emplace(Rank.u.string.ptr); - } - } - - // parse servers for each community - for(auto &Community : m_vCommunities) - { - const json_value &Servers = (*m_pDDNetInfo)[Community.JsonServersKey()]; - if(Servers.type != json_array) - { - log_error("serverbrowser", "invalid community servers (CommunityId=%s)", Community.Id()); - continue; - } - - for(unsigned ServerIndex = 0; ServerIndex < Servers.u.array.length; ++ServerIndex) - { - // pServer - { name, flagId, servers } - const json_value &Server = *Servers.u.array.values[ServerIndex]; - if(Server.type != json_object) - { - log_error("serverbrowser", "invalid server (ServerIndex=%u)", ServerIndex); - continue; - } - - const json_value &Name = Server["name"]; - const json_value &FlagId = Server["flagId"]; - const json_value &Types = Server["servers"]; - if(Name.type != json_string || FlagId.type != json_integer || Types.type != json_object) - { - log_error("serverbrowser", "invalid server attribute (ServerIndex=%u)", ServerIndex); - continue; - } - if(Types.u.object.length == 0) - continue; - - Community.m_vCountries.emplace_back(Name.u.string.ptr, FlagId.u.integer); - CCommunityCountry *pCountry = &Community.m_vCountries.back(); - - for(unsigned TypeIndex = 0; TypeIndex < Types.u.object.length; ++TypeIndex) - { - const json_value &Addresses = *Types.u.object.values[TypeIndex].value; - if(Addresses.type != json_array) - { - log_error("serverbrowser", "invalid addresses (ServerIndex=%u, TypeIndex=%u)", ServerIndex, TypeIndex); - continue; - } - if(Addresses.u.array.length == 0) - continue; - - const char *pTypeName = Types.u.object.values[TypeIndex].name; - - // add type if it doesn't exist already - const auto CommunityType = std::find_if(Community.m_vTypes.begin(), Community.m_vTypes.end(), [pTypeName](const auto &Elem) { - return str_comp(Elem.Name(), pTypeName) == 0; - }); - if(CommunityType == Community.m_vTypes.end()) - { - Community.m_vTypes.emplace_back(pTypeName); - } - - // add addresses - for(unsigned AddressIndex = 0; AddressIndex < Addresses.u.array.length; ++AddressIndex) - { - const json_value &Address = *Addresses.u.array.values[AddressIndex]; - if(Address.type != json_string) - { - log_error("serverbrowser", "invalid address (ServerIndex=%u, TypeIndex=%u, AddressIndex=%u)", ServerIndex, TypeIndex, AddressIndex); - continue; - } - NETADDR NetAddr; - net_addr_from_str(&NetAddr, Address.u.string.ptr); - pCountry->m_vServers.emplace_back(NetAddr, pTypeName); - m_CommunityServersByAddr.emplace(std::make_pair(NetAddr, CCommunityServer(Community.Id(), pCountry->Name(), pTypeName))); - } + m_CommunityServersByAddr.emplace(Server.Address(), CCommunityServer(NewCommunity.Id(), Country.Name(), Server.TypeName())); } } + m_vCommunities.push_back(std::move(NewCommunity)); } // Add default none community - m_vCommunities.emplace_back(COMMUNITY_NONE, "None", "", "", SHA256_ZEROED); + m_vCommunities.emplace_back(COMMUNITY_NONE, "None", SHA256_ZEROED, ""); // Remove unknown elements from exclude lists CleanFilters(); @@ -1391,10 +1430,10 @@ int CServerBrowser::LoadingProgression() const CServerInfo::ERankState CCommunity::HasRank(const char *pMap) const { - if(!HasRanks() || pMap[0] == '\0') + if(!HasRanks()) return CServerInfo::RANK_UNAVAILABLE; const CCommunityMap Needle(pMap); - return m_RankedMaps.count(Needle) == 0 ? CServerInfo::RANK_UNRANKED : CServerInfo::RANK_RANKED; + return m_FinishedMaps.count(Needle) == 0 ? CServerInfo::RANK_UNRANKED : CServerInfo::RANK_RANKED; } const std::vector &CServerBrowser::Communities() const diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index 6e0b63cfb..854907c91 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -219,6 +219,9 @@ private: void SetInfo(CServerEntry *pEntry, const CServerInfo &Info); void SetLatency(NETADDR Addr, int Latency); + + static bool ParseCommunityFinishes(CCommunity *pCommunity, const json_value &Finishes); + static bool ParseCommunityServers(CCommunity *pCommunity, const json_value &Servers); }; #endif diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 5c124f48f..a365c9e44 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -209,31 +209,29 @@ class CCommunity char m_aId[CServerInfo::MAX_COMMUNITY_ID_LENGTH]; char m_aName[64]; - char m_aJsonServersKey[32]; - char m_aJsonRanksKey[32]; SHA256_DIGEST m_IconSha256; + char m_aIconUrl[128]; std::vector m_vCountries; std::vector m_vTypes; - std::unordered_set m_RankedMaps; + bool m_HasFinishes = false; + std::unordered_set m_FinishedMaps; public: - CCommunity(const char *pId, const char *pName, const char *pJsonServersKey, const char *pJsonRanksKey, SHA256_DIGEST IconSha256) : + CCommunity(const char *pId, const char *pName, SHA256_DIGEST IconSha256, const char *pIconUrl) : m_IconSha256(IconSha256) { str_copy(m_aId, pId); str_copy(m_aName, pName); - str_copy(m_aJsonServersKey, pJsonServersKey); - str_copy(m_aJsonRanksKey, pJsonRanksKey); + str_copy(m_aIconUrl, pIconUrl); } const char *Id() const { return m_aId; } const char *Name() const { return m_aName; } - const char *JsonServersKey() const { return m_aJsonServersKey; } - const char *JsonRanksKey() const { return m_aJsonRanksKey; } + const char *IconUrl() const { return m_aIconUrl; } const SHA256_DIGEST &IconSha256() const { return m_IconSha256; } const std::vector &Countries() const { return m_vCountries; } const std::vector &Types() const { return m_vTypes; } - bool HasRanks() const { return m_aJsonRanksKey[0] != '\0'; } + bool HasRanks() const { return m_HasFinishes; } CServerInfo::ERankState HasRank(const char *pMap) const; }; diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 6f75c58dc..15f4db9ab 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1983,10 +1983,6 @@ void CMenus::UpdateCommunityIcons() } } - const char *pDownloadUrl = Client()->CommunityIconsDownloadUrl(); - if(pDownloadUrl[0] == '\0') - return; - // Find added and updated community icons for(const auto &Community : ServerBrowser()->Communities()) { @@ -2000,9 +1996,7 @@ void CMenus::UpdateCommunityIcons() }); if(pExistingDownload == m_CommunityIconDownloadJobs.end() && (ExistingIcon == m_vCommunityIcons.end() || ExistingIcon->m_Sha256 != Community.IconSha256())) { - char aUrl[256]; - str_format(aUrl, sizeof(aUrl), "%s/%s.png", pDownloadUrl, Community.Id()); - std::shared_ptr pJob = std::make_shared(this, Community.Id(), aUrl); + std::shared_ptr pJob = std::make_shared(this, Community.Id(), Community.IconUrl()); Engine()->AddJob(pJob); m_CommunityIconDownloadJobs.push_back(pJob); }