diff --git a/Server/Components/LegacyNetwork/Query/query.cpp b/Server/Components/LegacyNetwork/Query/query.cpp index fc62520c6..47901c056 100644 --- a/Server/Components/LegacyNetwork/Query/query.cpp +++ b/Server/Components/LegacyNetwork/Query/query.cpp @@ -12,6 +12,8 @@ const size_t MAX_ACCEPTABLE_HOSTNAME_SIZE = 63; const size_t MAX_ACCEPTABLE_LANGUAGE_SIZE = 39; const size_t MAX_ACCEPTABLE_GMTEXT_SIZE = 39; +const size_t MAX_ACCEPTABLE_DISCORD_LINK_SIZE = 50; +const size_t MAX_ACCEPTABLE_BANNER_URL_SIZE = 120; template void writeToBuffer(char* output, size_t& offset, T value) @@ -122,6 +124,39 @@ void Query::buildServerInfoBuffer() writeToBuffer(output, language.c_str(), offset, languageLength); } +void Query::buildExtraServerInfoBuffer() +{ + if (core == nullptr) + { + return; + } + + // Set discord link length to 0 if it's over acceptable length (max size defined by discord itself) + uint32_t discordLinkLength = MAX_ACCEPTABLE_DISCORD_LINK_SIZE < discordLink.length() ? 0 : discordLink.length(); + uint32_t lightBannerUrlLength = std::min(lightBannerUrl.length(), MAX_ACCEPTABLE_BANNER_URL_SIZE); + uint32_t darkBannerUrlLength = std::min(darkBannerUrl.length(), MAX_ACCEPTABLE_BANNER_URL_SIZE); + + extraInfoBufferLength = BASE_QUERY_SIZE + sizeof(discordLinkLength) + discordLinkLength + sizeof(lightBannerUrlLength) + lightBannerUrlLength + sizeof(darkBannerUrlLength) + darkBannerUrlLength; + extraInfoBuffer.reset(new char[extraInfoBufferLength]); + + size_t offset = QUERY_TYPE_INDEX; + char* output = extraInfoBuffer.get(); + + writeToBuffer(output, offset, static_cast('o')); + + // Write discord server invite link + writeToBuffer(output, offset, discordLinkLength); + writeToBuffer(output, discordLink.c_str(), offset, discordLinkLength); + + // Write light banner url + writeToBuffer(output, offset, lightBannerUrlLength); + writeToBuffer(output, lightBannerUrl.c_str(), offset, lightBannerUrlLength); + + // Write dark banner url + writeToBuffer(output, offset, darkBannerUrlLength); + writeToBuffer(output, darkBannerUrl.c_str(), offset, darkBannerUrlLength); +} + void Query::updateServerInfoBufferPlayerCount(IPlayer* except) { if (core == nullptr) @@ -260,16 +295,6 @@ Span Query::handleQuery(Span buffer, uint32_t sock, cons PeerAddress::ToString(addr, addrString); core->printLn("[query:%c] from %.*s", buffer[QUERY_TYPE_INDEX], PRINT_VIEW(addrString)); } - - // This is how we detect open.mp, just resend the buffer - if (buffer[QUERY_TYPE_INDEX] == 'o') - { - if (buffer.size() != BASE_QUERY_SIZE + sizeof(uint32_t)) - { - return Span(); - } - return buffer; - } // Ping else if (buffer[QUERY_TYPE_INDEX] == 'p') { @@ -281,8 +306,14 @@ Span Query::handleQuery(Span buffer, uint32_t sock, cons } else if (buffer.size() == BASE_QUERY_SIZE) { + // This is how we detect open.mp, but also let's send some extra data + if (buffer[QUERY_TYPE_INDEX] == 'o') + { + return getBuffer(buffer, extraInfoBuffer, extraInfoBufferLength); + } + // Server info - if (buffer[QUERY_TYPE_INDEX] == 'i') + else if (buffer[QUERY_TYPE_INDEX] == 'i') { return getBuffer(buffer, serverInfoBuffer, serverInfoBufferLength); } diff --git a/Server/Components/LegacyNetwork/Query/query.hpp b/Server/Components/LegacyNetwork/Query/query.hpp index ec81e7603..b02e3aa81 100644 --- a/Server/Components/LegacyNetwork/Query/query.hpp +++ b/Server/Components/LegacyNetwork/Query/query.hpp @@ -49,6 +49,7 @@ class Query : NoCopy { buildServerInfoBuffer(); buildRulesBuffer(); + buildExtraServerInfoBuffer(); } void setMaxPlayers(uint16_t value) @@ -143,6 +144,21 @@ class Query : NoCopy language = String(value); } + void setDiscordLink(StringView value) + { + discordLink = String(value); + } + + void setLightBannerUrl(StringView value) + { + lightBannerUrl = String(value); + } + + void setDarkBannerUrl(StringView value) + { + darkBannerUrl = String(value); + } + private: ICore* core = nullptr; IConsoleComponent* console = nullptr; @@ -151,6 +167,9 @@ class Query : NoCopy String gameModeName = "Unknown"; String language = "EN"; String rconPassword; + String discordLink = ""; + String lightBannerUrl = ""; + String darkBannerUrl = ""; bool passworded = false; bool logQueries = false; bool rconEnabled = false; @@ -167,7 +186,11 @@ class Query : NoCopy std::unique_ptr rulesBuffer; size_t rulesBufferLength = 0; + std::unique_ptr extraInfoBuffer; + size_t extraInfoBufferLength = 0; + void buildPlayerInfoBuffer(IPlayer* except = nullptr); void updateServerInfoBufferPlayerCount(IPlayer* except = nullptr); void buildServerInfoBuffer(); + void buildExtraServerInfoBuffer(); }; diff --git a/Server/Components/LegacyNetwork/legacy_network_impl.cpp b/Server/Components/LegacyNetwork/legacy_network_impl.cpp index c4412b682..bb41f675f 100644 --- a/Server/Components/LegacyNetwork/legacy_network_impl.cpp +++ b/Server/Components/LegacyNetwork/legacy_network_impl.cpp @@ -755,6 +755,24 @@ void RakNetLegacyNetwork::update() query.setRuleValue("weburl", String(website)); } + StringView discordLink = config.getString("discord.invite"); + if (!discordLink.empty()) + { + query.setDiscordLink(discordLink); + } + + StringView bannerUrl = config.getString("banners.light"); + if (!bannerUrl.empty()) + { + query.setLightBannerUrl(bannerUrl); + } + + bannerUrl = config.getString("banners.dark"); + if (!bannerUrl.empty()) + { + query.setDarkBannerUrl(bannerUrl); + } + query.setRuleValue("worldtime", String(std::to_string(*config.getInt("game.time")) + ":00")); StringView rconPassword = config.getString("rcon.password"); diff --git a/Server/Source/core_impl.hpp b/Server/Source/core_impl.hpp index bab8296b9..e7c49df08 100644 --- a/Server/Source/core_impl.hpp +++ b/Server/Source/core_impl.hpp @@ -127,6 +127,11 @@ static const std::map Defaults { { "rcon.allow_teleport", false }, { "rcon.enable", false }, { "rcon.password", String("") }, // Set default to empty instead of changeme, so server starts with disabled rcon without config file + // banners + { "banners.light", String("") }, + { "banners.dark", String("") }, + // discord + { "discord.invite", String("") }, }; // Provide automatic Defaults → JSON conversion in Config