diff --git a/Config.ini b/Config.ini index 3598451..46c45f1 100644 --- a/Config.ini +++ b/Config.ini @@ -8,10 +8,7 @@ ; ; This block is used to define settings that are global in nature. -; Currently, there is a "Servers" option to specify what sections -; to look at for server configuration options, and a "Plugins" -; option that specifies which plugins to load from the Plugins -; folder. +; "Plugins" specifies which plugins to load from the Plugins directory. ; DO NOT INCLUDE A PLUGIN'S FILE EXTENSION (.dll or .so). ; ; Settings: diff --git a/Release/Plugins/RenX.Core.lib b/Release/Plugins/RenX.Core.lib index 7074fe3..a3fa086 100644 Binary files a/Release/Plugins/RenX.Core.lib and b/Release/Plugins/RenX.Core.lib differ diff --git a/RenX.Commands/RenX_Commands.cpp b/RenX.Commands/RenX_Commands.cpp index de8344d..8f36661 100644 --- a/RenX.Commands/RenX_Commands.cpp +++ b/RenX.Commands/RenX_Commands.cpp @@ -317,7 +317,7 @@ void PlayersIRCCommand::create() const size_t STRING_LENGTH = 240; -void PlayersIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &nick, const Jupiter::ReadableString &) +void PlayersIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString &channel, const Jupiter::ReadableString &, const Jupiter::ReadableString &) { int type = source->getChannel(channel)->getType(); @@ -340,6 +340,12 @@ void PlayersIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString & noServers = false; if (server->players.size() != server->getBotCount()) { + if (server->players.size() == 0) + { + source->sendMessage(channel, Jupiter::StringS::Format("ERROR: NO PLAYERS BUT BOT_COUNT = %u.", server->getBotCount())); + continue; + } + // End string containers Jupiter::DLList gStrings; Jupiter::DLList nStrings; @@ -451,7 +457,8 @@ void PlayersIRCCommand::trigger(IRC_Bot *source, const Jupiter::ReadableString & } source->sendMessage(channel, out); } - else source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("No players are in-game.")); + else + source->sendMessage(channel, STRING_LITERAL_AS_REFERENCE("No players are in-game.")); } } if (noServers) diff --git a/RenX.Core/RenX_Core.h b/RenX.Core/RenX_Core.h index a26e8c9..c1fe2d2 100644 --- a/RenX.Core/RenX_Core.h +++ b/RenX.Core/RenX_Core.h @@ -170,11 +170,6 @@ namespace RenX */ void banCheck(); - /** - * @brief Initializes the Core. - */ - void init(); - /** * Destructor for the Core class. */ diff --git a/RenX.Core/RenX_Server.cpp b/RenX.Core/RenX_Server.cpp index c13e3a5..9c50276 100644 --- a/RenX.Core/RenX_Server.cpp +++ b/RenX.Core/RenX_Server.cpp @@ -931,6 +931,11 @@ void RenX::Server::setCommandPrefix(const Jupiter::ReadableString &prefix) RenX::Server::CommandPrefix = prefix; } +void RenX::Server::setRanked(bool in_value) +{ + RenX::Server::m_ranked = in_value; +} + const Jupiter::ReadableString &RenX::Server::getRules() const { return RenX::Server::rules; @@ -986,6 +991,16 @@ int RenX::Server::getTimeLimit() const return RenX::Server::timeLimit; } +int RenX::Server::getTeamMode() const +{ + return RenX::Server::m_team_mode; +} + +int RenX::Server::getGameType() const +{ + return RenX::Server::m_game_type; +} + double RenX::Server::getCrateRespawnDelay() const { return RenX::Server::crateRespawnAfterPickup; @@ -1006,14 +1021,14 @@ bool RenX::Server::isPrivateMessagingEnabled() const return RenX::Server::allowPrivateMessaging; } -bool RenX::Server::isPassworded() const +bool RenX::Server::isRanked() const { - return RenX::Server::passworded; + return RenX::Server::m_ranked; } -bool RenX::Server::isAutoBalanceEnabled() const +bool RenX::Server::isPassworded() const { - return RenX::Server::autoBalanceTeams; + return RenX::Server::passworded; } bool RenX::Server::isCratesEnabled() const @@ -1021,6 +1036,11 @@ bool RenX::Server::isCratesEnabled() const return RenX::Server::spawnCrates; } +bool RenX::Server::isBotsEnabled() const +{ + return RenX::Server::botsEnabled; +} + const Jupiter::ReadableString &RenX::Server::getPassword() const { return RenX::Server::pass; @@ -1912,7 +1932,7 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) { if (this->lastCommandParams.isEmpty()) { - // "PlayerLimit" | PlayerLimit | "VehicleLimit" | VehicleLimit | "MineLimit" | MineLimit | "TimeLimit" | TimeLimit | "bPassworded" | bPassworded | "bSteamRequired" | bSteamRequired | "bPrivateMessageTeamOnly" | bPrivateMessageTeamOnly | "bAllowPrivateMessaging" | bAllowPrivateMessaging | "bAutoBalanceTeams" | bAutoBalanceTeams | "bSpawnCrates" | bSpawnCrates | "CrateRespawnAfterPickup" | CrateRespawnAfterPickup | bIsCompetitive | "bIsCompetitive" + // "PlayerLimit" | PlayerLimit | "VehicleLimit" | VehicleLimit | "MineLimit" | MineLimit | "TimeLimit" | TimeLimit | "bPassworded" | bPassworded | "bSteamRequired" | bSteamRequired | "bPrivateMessageTeamOnly" | bPrivateMessageTeamOnly | "bAllowPrivateMessaging" | bAllowPrivateMessaging | "TeamMode" | TeamMode | "bSpawnCrates" | bSpawnCrates | "CrateRespawnAfterPickup" | CrateRespawnAfterPickup | bIsCompetitive | "bIsCompetitive" this->playerLimit = tokens.getToken(1).asInt(); this->vehicleLimit = tokens.getToken(3).asInt(); this->mineLimit = tokens.getToken(5).asInt(); @@ -1921,7 +1941,7 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) this->steamRequired = tokens.getToken(11).asBool(); this->privateMessageTeamOnly = tokens.getToken(13).asBool(); this->allowPrivateMessaging = tokens.getToken(15).asBool(); - this->autoBalanceTeams = tokens.getToken(17).asBool(); + this->m_team_mode = this->rconVersion >= 4 ? tokens.getToken(17).asInt() : true; this->spawnCrates = tokens.getToken(19).asBool(); this->crateRespawnAfterPickup = tokens.getToken(21).asDouble(); @@ -1940,6 +1960,9 @@ void RenX::Server::processLine(const Jupiter::ReadableString &line) this->match_state = 3; else // Unknown state -- assume it's in progress this->match_state = 1; + + this->botsEnabled = tokens.getToken(27).asBool(); + this->m_game_type = tokens.getToken(29).asInt(); } } } @@ -3340,6 +3363,10 @@ void RenX::Server::wipeData() delete player; } RenX::Server::reliable = false; + RenX::Server::m_team_mode = 3; + RenX::Server::m_game_type = 1; + RenX::Server::m_ranked = false; + RenX::Server::botsEnabled = true; RenX::Server::match_state = 1; RenX::Server::subscribed = false; RenX::Server::fully_connected = false; diff --git a/RenX.Core/RenX_Server.h b/RenX.Core/RenX_Server.h index f2c2006..bb58ce4 100644 --- a/RenX.Core/RenX_Server.h +++ b/RenX.Core/RenX_Server.h @@ -686,6 +686,8 @@ namespace RenX */ void setCommandPrefix(const Jupiter::ReadableString &prefix); + void setRanked(bool in_value); + /** * @brief Fetches the rules of a server. * @@ -736,28 +738,17 @@ namespace RenX */ std::chrono::milliseconds getDelay() const; - /** - server->getMineLimit(), - json_bool_as_cstring(server->isSteamRequired()), - json_bool_as_cstring(server->isPrivateMessageTeamOnly()), - json_bool_as_cstring(server->isPassworded()), - json_bool_as_cstring(server->isPrivateMessagingEnabled()), - server->getPlayerLimit(), - server->getVehicleLimit(), - json_bool_as_cstring(server->isAutoBalanceTeams()), - json_bool_as_cstring(server->isCratesEnabled()), - server->getCrateRespawnDelay(), - server->getTimeLimit(), - */ - int getMineLimit() const; int getPlayerLimit() const; int getVehicleLimit() const; int getTimeLimit() const; + int getTeamMode() const; + int getGameType() const; double getCrateRespawnDelay() const; bool isSteamRequired() const; bool isPrivateMessageTeamOnly() const; bool isPrivateMessagingEnabled() const; + bool isRanked() const; /** * @brief Checks if the server has a game password. @@ -766,8 +757,8 @@ namespace RenX */ bool isPassworded() const; - bool isAutoBalanceEnabled() const; bool isCratesEnabled() const; + bool isBotsEnabled() const; /** * @brief Fetches the RCON password of a server. @@ -1033,12 +1024,15 @@ namespace RenX bool steamRequired = false; bool privateMessageTeamOnly = false; bool allowPrivateMessaging = true; - bool autoBalanceTeams = true; bool spawnCrates = true; + bool botsEnabled = true; bool competitive = false; bool devBot = false; bool reliable = false; + bool m_ranked = false; + int m_team_mode = 3; /** 0 = static, 1 = swap, 2 = random swap, 3 = shuffle, 4 = traditional (assign as players connect) */ int match_state = 1; /** 0 = pending, 1 = in progress, 2 = over, 3 = travelling */ + int m_game_type = 1; /** < 0 = Invalid, 0 = Main Menu, 1 = Rx_Game, 2 = TS_Game, > 2 = Unassigned */ int attempts = 0; int playerLimit = 0; int vehicleLimit = 0; diff --git a/RenX.Ladder/RenX_Ladder.cpp b/RenX.Ladder/RenX_Ladder.cpp index 5e2c57a..abe2153 100644 --- a/RenX.Ladder/RenX_Ladder.cpp +++ b/RenX.Ladder/RenX_Ladder.cpp @@ -35,14 +35,28 @@ bool RenX_LadderPlugin::initialize() else RenX_LadderPlugin::max_ladder_command_part_name_output = mlcpno; + RenX::Server *server; + for (size_t index = 0; index != RenX::getCore()->getServerCount(); ++index) + { + server = RenX::getCore()->getServer(index); + if (this->only_pure == false || server->isPure()) + server->setRanked(true); + } + return true; } +void RenX_LadderPlugin::RenX_OnServerFullyConnected(RenX::Server *server) +{ + if (this->only_pure == false || server->isPure()) + server->setRanked(true); +} + /** Wait until the client list has been updated to update the ladder */ void RenX_LadderPlugin::RenX_OnGameOver(RenX::Server *server, RenX::WinType winType, const RenX::TeamType &team, int gScore, int nScore) { - if (server->isReliable() && server->players.size() != server->getBotCount()) + if (server->isRanked() && server->isReliable() && server->players.size() != server->getBotCount()) { char chr = static_cast(team); server->varData.set(this->name, "t"_jrs, Jupiter::ReferenceString(&chr, 1)); diff --git a/RenX.Ladder/RenX_Ladder.h b/RenX.Ladder/RenX_Ladder.h index 5b129da..01b24c8 100644 --- a/RenX.Ladder/RenX_Ladder.h +++ b/RenX.Ladder/RenX_Ladder.h @@ -36,6 +36,7 @@ class RenX_LadderPlugin : public RenX::Plugin { public: virtual bool initialize() override; + void RenX_OnServerFullyConnected(RenX::Server *server) override; void RenX_OnGameOver(RenX::Server *server, RenX::WinType winType, const RenX::TeamType &team, int gScore, int nScore) override; void RenX_OnCommand(RenX::Server *server, const Jupiter::ReadableString &) override; diff --git a/RenX.ServerList/RenX_ServerList.cpp b/RenX.ServerList/RenX_ServerList.cpp index 06420db..0145a94 100644 --- a/RenX.ServerList/RenX_ServerList.cpp +++ b/RenX.ServerList/RenX_ServerList.cpp @@ -30,6 +30,9 @@ using namespace Jupiter::literals; static STRING_LITERAL_AS_NAMED_REFERENCE(CONTENT_TYPE_APPLICATION_JSON, "application/json"); +const Jupiter::ReferenceString server_list_game_header = ""_jrs; +const Jupiter::ReferenceString server_list_game_footer = "\n"_jrs; + Jupiter::String jsonify(const Jupiter::ReadableString &in_str) { const char *ptr = in_str.ptr(); @@ -49,7 +52,65 @@ Jupiter::String jsonify(const Jupiter::ReadableString &in_str) result += '\"'; } else if (*ptr < 0x20) // control characters - result += Jupiter::StringS::Format("\\u00%x", *ptr); + result.aformat("\\u00%x", *ptr); + else if ((*ptr & 0x80) != 0) // UTF-8 sequence; copy to bypass above processing + { + result += *ptr; + + if ((*ptr & 0x40) != 0) + { + // this is a 2+ byte sequence + + if ((*ptr & 0x20) != 0) + { + // this is a 3+ byte sequence + + if ((*ptr & 0x10) != 0) + { + // this is a 4 byte sequnce + result += *++ptr; + } + + result += *++ptr; + } + + result += *++ptr; + } + } + else // Character in standard ASCII table + result += *ptr; + + ++ptr; + --str_length; + } + + return result; +} + +Jupiter::String sanitize_game(const Jupiter::ReadableString &in_str) +{ + const char *ptr = in_str.ptr(); + size_t str_length = in_str.size(); + Jupiter::String result(str_length); + + while (str_length != 0) + { + if (*ptr == '\\') // backslash + { + result += '\\'; + result += '\\'; + } + else if (*ptr == '\"') // quotation + { + result += '\\'; + result += '\"'; + } + else if (*ptr < 0x20) // control characters + result.aformat("\\u00%x", *ptr); + else if (*ptr == '~') // Game server list control character + result += "\\u007E"_jrs; + else if (*ptr == ';') // Game server list control character + result += "\\u003B"_jrs; else if ((*ptr & 0x80) != 0) // UTF-8 sequence; copy to bypass above processing { result += *ptr; @@ -91,6 +152,7 @@ bool RenX_ServerListPlugin::initialize() RenX_ServerListPlugin::server_list_page_name = this->config.get(Jupiter::ReferenceString::empty, "ServersPageName"_jrs, "servers.jsp"_jrs); RenX_ServerListPlugin::server_list_long_page_name = this->config.get(Jupiter::ReferenceString::empty, "HumanServersPageName"_jrs, "servers_long.jsp"_jrs); RenX_ServerListPlugin::server_page_name = this->config.get(Jupiter::ReferenceString::empty, "ServerPageName"_jrs, "server.jsp"_jrs); + RenX_ServerListPlugin::game_server_list_page_name = this->config.get(Jupiter::ReferenceString::empty, "ServersGamePageName"_jrs, "browser.jsp"_jrs); /** Initialize content */ Jupiter::HTTP::Server &server = getHTTPServer(); @@ -119,6 +181,14 @@ bool RenX_ServerListPlugin::initialize() content->free_result = true; server.hook(RenX_ServerListPlugin::web_hostname, RenX_ServerListPlugin::web_path, content); + // Game server list page + content = new Jupiter::HTTP::Server::Content(RenX_ServerListPlugin::game_server_list_page_name, handle_game_server_list_page); + content->language = &Jupiter::HTTP::Content::Language::ENGLISH; + content->type = &Jupiter::HTTP::Content::Type::Text::HTML; + content->charset = &Jupiter::HTTP::Content::Type::Text::Charset::ASCII; + content->free_result = false; + server.hook(RenX_ServerListPlugin::web_hostname, RenX_ServerListPlugin::web_path, content); + this->updateServerList(); return true; } @@ -135,11 +205,14 @@ Jupiter::ReadableString *RenX_ServerListPlugin::getServerListJSON() return std::addressof(RenX_ServerListPlugin::server_list_json); } -const char *json_bool_as_cstring(bool in) +Jupiter::ReadableString *RenX_ServerListPlugin::getServerListGame() +{ + return std::addressof(RenX_ServerListPlugin::server_list_game); +} + +constexpr const char *json_bool_as_cstring(bool in) { - if (in) - return "true"; - return "false"; + return in ? "true" : "false"; } Jupiter::StringS server_as_json(const RenX::Server *server) @@ -150,7 +223,7 @@ Jupiter::StringS server_as_json(const RenX::Server *server) Jupiter::String server_map = jsonify(server->getMap().name); Jupiter::String server_version = jsonify(server->getGameVersion()); - server_json_block.format(R"json({"Name":"%.*s","Current Map":"%.*s","Bots":%u,"Players":%u,"Game Version":"%.*s","Variables":{"Mine Limit":%d,"bSteamRequired":%s,"bPrivateMessageTeamOnly":%s,"bPassworded":%s,"bAllowPrivateMessaging":%s,"Player Limit":%d,"Vehicle Limit":%d,"bAutoBalanceTeams":%s,"bSpawnCrates":%s,"CrateRespawnAfterPickup":%f,"Time Limit":%d},"Port":%u,"IP":"%.*s")json", + server_json_block.format(R"json({"Name":"%.*s","Current Map":"%.*s","Bots":%u,"Players":%u,"Game Version":"%.*s","Variables":{"Mine Limit":%d,"bSteamRequired":%s,"bPrivateMessageTeamOnly":%s,"bPassworded":%s,"bAllowPrivateMessaging":%s,"Player Limit":%d,"Vehicle Limit":%d,"bAutoBalanceTeams":%s,"Team Mode":%d,"bSpawnCrates":%s,"CrateRespawnAfterPickup":%f,"Time Limit":%d},"Port":%u,"IP":"%.*s")json", server_name.size(), server_name.ptr(), server_map.size(), server_map.ptr(), server->getBotCount(), @@ -163,7 +236,8 @@ Jupiter::StringS server_as_json(const RenX::Server *server) json_bool_as_cstring(server->isPrivateMessagingEnabled()), server->getPlayerLimit(), server->getVehicleLimit(), - json_bool_as_cstring(server->isAutoBalanceEnabled()), + json_bool_as_cstring(server->getTeamMode() == 3), + server->getTeamMode(), json_bool_as_cstring(server->isCratesEnabled()), server->getCrateRespawnDelay(), server->getTimeLimit(), @@ -175,6 +249,42 @@ Jupiter::StringS server_as_json(const RenX::Server *server) return server_json_block; } +Jupiter::StringS server_as_game(const RenX::Server *server) +{ + Jupiter::String server_game_block(128); + + Jupiter::String server_name = sanitize_game(server->getName()); + Jupiter::String server_map = sanitize_game(server->getMap().name); + Jupiter::String server_version = sanitize_game(server->getGameVersion()); + + server_game_block.format("\n<@>%.*s~%.*s~%u~%s~%.*s~" "%d;%d;%d;%s;%d;%d;%d;%s;%s;%s;%.*s;%s" "~%u~%d~%s~%s", + server_name.size(), server_name.ptr(), + server->getSocketHostname().size(), server->getSocketHostname().ptr(), + server->getPort(), + json_bool_as_cstring(server->isPassworded()), + server_map.size(), server_map.ptr(), + //START OPTIONS + server->getPlayerLimit(), + server->getVehicleLimit(), + server->getMineLimit(), + json_bool_as_cstring(server->isCratesEnabled()), + server->getGameType(), + server->getTeamMode(), + server->getTimeLimit(), + json_bool_as_cstring(server->isPrivateMessagingEnabled()), + json_bool_as_cstring(server->isPrivateMessageTeamOnly()), + json_bool_as_cstring(server->isSteamRequired()), + server_version.size(), server_version.ptr(), + json_bool_as_cstring(server->isBotsEnabled()), + //END OPTIONS + server->players.size() - server->getBotCount(), + server->getPlayerLimit(), + json_bool_as_cstring(server->isRanked()), // json_bool_as_cstring(plugin != nullptr && (reinterpret_cast(plugin)->isOnlyPure() == false || server->isPure())), + json_bool_as_cstring(server->isMatchInProgress())); + + return server_game_block; +} + Jupiter::StringS server_as_long_json(const RenX::Server *server) { Jupiter::String server_json_block(128); @@ -198,6 +308,7 @@ Jupiter::StringS server_as_long_json(const RenX::Server *server) "Player Limit": %d, "Vehicle Limit": %d, "bAutoBalanceTeams": %s, + "Team Mode": %d "bSpawnCrates": %s, "CrateRespawnAfterPickup": %f, "Time Limit": %d @@ -218,7 +329,8 @@ Jupiter::StringS server_as_long_json(const RenX::Server *server) json_bool_as_cstring(server->isPrivateMessagingEnabled()), server->getPlayerLimit(), server->getVehicleLimit(), - json_bool_as_cstring(server->isAutoBalanceEnabled()), + json_bool_as_cstring(server->getTeamMode() == 3), + server->getTeamMode(), json_bool_as_cstring(server->isCratesEnabled()), server->getCrateRespawnDelay(), server->getTimeLimit(), @@ -294,6 +406,21 @@ void RenX_ServerListPlugin::addServerToServerList(RenX::Server *server) RenX_ServerListPlugin::server_list_json += ']'; } + // append to server_list_game + + if (RenX_ServerListPlugin::server_list_game.isEmpty()) + { + RenX_ServerListPlugin::server_list_game = server_list_game_header; + RenX_ServerListPlugin::server_list_game += server_as_game(server); + RenX_ServerListPlugin::server_list_game += server_list_game_footer; + } + else + { + RenX_ServerListPlugin::server_list_game.truncate(server_list_game_footer.size()); // remove trailing "" + RenX_ServerListPlugin::server_list_game += server_as_game(server); + RenX_ServerListPlugin::server_list_game += server_list_game_footer; + } + // add to individual listing server_json_block = '{'; @@ -352,9 +479,10 @@ void RenX_ServerListPlugin::updateServerList() size_t index = 0; RenX::Server *server; - // regenerate server_list_json + // regenerate server_list_json and server_list_Game RenX_ServerListPlugin::server_list_json = '['; + RenX_ServerListPlugin::server_list_game = server_list_game_header; while (index != servers.size()) { @@ -362,6 +490,8 @@ void RenX_ServerListPlugin::updateServerList() if (server->isConnected() && server->isFullyConnected()) { RenX_ServerListPlugin::server_list_json += server_as_json(server); + RenX_ServerListPlugin::server_list_game += server_as_game(server); + ++index; break; } @@ -374,11 +504,14 @@ void RenX_ServerListPlugin::updateServerList() { RenX_ServerListPlugin::server_list_json += ','; RenX_ServerListPlugin::server_list_json += server_as_json(server); + + RenX_ServerListPlugin::server_list_game += server_as_game(server); } ++index; } RenX_ServerListPlugin::server_list_json += ']'; + RenX_ServerListPlugin::server_list_game += server_list_game_footer; } void RenX_ServerListPlugin::RenX_OnServerFullyConnected(RenX::Server *server) @@ -495,6 +628,11 @@ Jupiter::ReadableString *handle_server_page(const Jupiter::ReadableString &query return new Jupiter::ReferenceString(server->varData.get(pluginInstance.getName(), "j"_jrs)); } +Jupiter::ReadableString *handle_game_server_list_page(const Jupiter::ReadableString &query_string) +{ + return pluginInstance.getServerListGame(); +} + extern "C" __declspec(dllexport) Jupiter::Plugin *getPlugin() { return &pluginInstance; diff --git a/RenX.ServerList/RenX_ServerList.h b/RenX.ServerList/RenX_ServerList.h index 3179c98..7bc152c 100644 --- a/RenX.ServerList/RenX_ServerList.h +++ b/RenX.ServerList/RenX_ServerList.h @@ -27,6 +27,7 @@ class RenX_ServerListPlugin : public RenX::Plugin { public: // RenX_ServerListPlugin Jupiter::ReadableString *getServerListJSON(); + Jupiter::ReadableString *getServerListGame(); void addServerToServerList(RenX::Server *server); void updateServerList(); @@ -42,12 +43,13 @@ public: // RenX::Plugin void RenX_OnMapLoad(RenX::Server *server, const Jupiter::ReadableString &map) override; private: - Jupiter::StringS server_list_json; - Jupiter::StringS web_hostname, web_path, server_list_page_name, server_list_long_page_name, server_page_name; + Jupiter::StringS server_list_json, server_list_game; + Jupiter::StringS web_hostname, web_path, server_list_page_name, server_list_long_page_name, server_page_name, game_server_list_page_name; }; Jupiter::ReadableString *handle_server_list_page(const Jupiter::ReadableString &); Jupiter::ReadableString *handle_server_list_long_page(const Jupiter::ReadableString &); Jupiter::ReadableString *handle_server_page(const Jupiter::ReadableString &query_string); +Jupiter::ReadableString *handle_game_server_list_page(const Jupiter::ReadableString &); #endif // _RENX_SERVERLIST_H_HEADER