1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "protocolstatus.h"
23#include "configmanager.h"
24#include "game.h"
25#include "outputmessage.h"
26
27extern ConfigManager g_config;
28extern Game g_game;
29
30std::map<uint32_t, int64_t> ProtocolStatus::ipConnectMap;
31const uint64_t ProtocolStatus::start = OTSYS_TIME();
32
33enum RequestedInfo_t : uint16_t {
34 REQUEST_BASIC_SERVER_INFO = 1 << 0,
35 REQUEST_OWNER_SERVER_INFO = 1 << 1,
36 REQUEST_MISC_SERVER_INFO = 1 << 2,
37 REQUEST_PLAYERS_INFO = 1 << 3,
38 REQUEST_MAP_INFO = 1 << 4,
39 REQUEST_EXT_PLAYERS_INFO = 1 << 5,
40 REQUEST_PLAYER_STATUS_INFO = 1 << 6,
41 REQUEST_SERVER_SOFTWARE_INFO = 1 << 7,
42};
43
44void ProtocolStatus::onRecvFirstMessage(NetworkMessage& msg)
45{
46 uint32_t ip = getIP();
47 if (ip != 0x0100007F) {
48 std::string ipStr = convertIPToString(ip);
49 if (ipStr != g_config.getString(ConfigManager::IP)) {
50 std::map<uint32_t, int64_t>::const_iterator it = ipConnectMap.find(ip);
51 if (it != ipConnectMap.end() && (OTSYS_TIME() < (it->second + g_config.getNumber(ConfigManager::STATUSQUERY_TIMEOUT)))) {
52 disconnect();
53 return;
54 }
55 }
56 }
57
58 ipConnectMap[ip] = OTSYS_TIME();
59
60 switch (msg.getByte()) {
61 //XML info protocol
62 case 0xFF: {
63 if (msg.getString(4) == "info") {
64 g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendStatusString,
65 std::static_pointer_cast<ProtocolStatus>(shared_from_this()))));
66 return;
67 }
68 break;
69 }
70
71 //Another ServerInfo protocol
72 case 0x01: {
73 uint16_t requestedInfo = msg.get<uint16_t>(); // only a Byte is necessary, though we could add new info here
74 std::string characterName;
75 if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) {
76 characterName = msg.getString();
77 }
78 g_dispatcher.addTask(createTask(std::bind(&ProtocolStatus::sendInfo, std::static_pointer_cast<ProtocolStatus>(shared_from_this()),
79 requestedInfo, characterName)));
80 return;
81 }
82
83 default:
84 break;
85 }
86 disconnect();
87}
88
89void ProtocolStatus::sendStatusString()
90{
91 auto output = OutputMessagePool::getOutputMessage();
92
93 setRawMessages(true);
94
95 pugi::xml_document doc;
96
97 pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
98 decl.append_attribute("version") = "1.0";
99
100 pugi::xml_node tsqp = doc.append_child("tsqp");
101 tsqp.append_attribute("version") = "1.0";
102
103 pugi::xml_node serverinfo = tsqp.append_child("serverinfo");
104 uint64_t uptime = (OTSYS_TIME() - ProtocolStatus::start) / 1000;
105 serverinfo.append_attribute("uptime") = std::to_string(uptime).c_str();
106 serverinfo.append_attribute("ip") = g_config.getString(ConfigManager::IP).c_str();
107 serverinfo.append_attribute("servername") = g_config.getString(ConfigManager::SERVER_NAME).c_str();
108 serverinfo.append_attribute("port") = std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)).c_str();
109 serverinfo.append_attribute("location") = g_config.getString(ConfigManager::LOCATION).c_str();
110 serverinfo.append_attribute("url") = g_config.getString(ConfigManager::URL).c_str();
111 serverinfo.append_attribute("server") = STATUS_SERVER_NAME;
112 serverinfo.append_attribute("version") = STATUS_SERVER_VERSION;
113 serverinfo.append_attribute("client") = CLIENT_VERSION_STR;
114
115 pugi::xml_node owner = tsqp.append_child("owner");
116 owner.append_attribute("name") = g_config.getString(ConfigManager::OWNER_NAME).c_str();
117 owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str();
118
119 pugi::xml_node players = tsqp.append_child("players");
120 players.append_attribute("online") = std::to_string(g_game.getPlayersOnline()).c_str();
121 players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str();
122 players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str();
123
124 pugi::xml_node monsters = tsqp.append_child("monsters");
125 monsters.append_attribute("total") = std::to_string(g_game.getMonstersOnline()).c_str();
126
127 pugi::xml_node npcs = tsqp.append_child("npcs");
128 npcs.append_attribute("total") = std::to_string(g_game.getNpcsOnline()).c_str();
129
130 pugi::xml_node rates = tsqp.append_child("rates");
131 rates.append_attribute("experience") = std::to_string(g_config.getNumber(ConfigManager::RATE_EXPERIENCE)).c_str();
132 rates.append_attribute("skill") = std::to_string(g_config.getNumber(ConfigManager::RATE_SKILL)).c_str();
133 rates.append_attribute("loot") = std::to_string(g_config.getNumber(ConfigManager::RATE_LOOT)).c_str();
134 rates.append_attribute("magic") = std::to_string(g_config.getNumber(ConfigManager::RATE_MAGIC)).c_str();
135 rates.append_attribute("spawn") = std::to_string(g_config.getNumber(ConfigManager::RATE_SPAWN)).c_str();
136
137 pugi::xml_node map = tsqp.append_child("map");
138 map.append_attribute("name") = g_config.getString(ConfigManager::MAP_NAME).c_str();
139 map.append_attribute("author") = g_config.getString(ConfigManager::MAP_AUTHOR).c_str();
140
141 uint32_t mapWidth, mapHeight;
142 g_game.getMapDimensions(mapWidth, mapHeight);
143 map.append_attribute("width") = std::to_string(mapWidth).c_str();
144 map.append_attribute("height") = std::to_string(mapHeight).c_str();
145
146 pugi::xml_node motd = tsqp.append_child("motd");
147 motd.text() = g_config.getString(ConfigManager::MOTD).c_str();
148
149 std::ostringstream ss;
150 doc.save(ss, "", pugi::format_raw);
151
152 std::string data = ss.str();
153 output->addBytes(data.c_str(), data.size());
154 send(output);
155 disconnect();
156}
157
158void ProtocolStatus::sendInfo(uint16_t requestedInfo, const std::string& characterName)
159{
160 auto output = OutputMessagePool::getOutputMessage();
161
162 if (requestedInfo & REQUEST_BASIC_SERVER_INFO) {
163 output->addByte(0x10);
164 output->addString(g_config.getString(ConfigManager::SERVER_NAME));
165 output->addString(g_config.getString(ConfigManager::IP));
166 output->addString(std::to_string(g_config.getNumber(ConfigManager::LOGIN_PORT)));
167 }
168
169 if (requestedInfo & REQUEST_OWNER_SERVER_INFO) {
170 output->addByte(0x11);
171 output->addString(g_config.getString(ConfigManager::OWNER_NAME));
172 output->addString(g_config.getString(ConfigManager::OWNER_EMAIL));
173 }
174
175 if (requestedInfo & REQUEST_MISC_SERVER_INFO) {
176 output->addByte(0x12);
177 output->addString(g_config.getString(ConfigManager::MOTD));
178 output->addString(g_config.getString(ConfigManager::LOCATION));
179 output->addString(g_config.getString(ConfigManager::URL));
180 output->add<uint64_t>((OTSYS_TIME() - ProtocolStatus::start) / 1000);
181 }
182
183 if (requestedInfo & REQUEST_PLAYERS_INFO) {
184 output->addByte(0x20);
185 output->add<uint32_t>(g_game.getPlayersOnline());
186 output->add<uint32_t>(g_config.getNumber(ConfigManager::MAX_PLAYERS));
187 output->add<uint32_t>(g_game.getPlayersRecord());
188 }
189
190 if (requestedInfo & REQUEST_MAP_INFO) {
191 output->addByte(0x30);
192 output->addString(g_config.getString(ConfigManager::MAP_NAME));
193 output->addString(g_config.getString(ConfigManager::MAP_AUTHOR));
194 uint32_t mapWidth, mapHeight;
195 g_game.getMapDimensions(mapWidth, mapHeight);
196 output->add<uint16_t>(mapWidth);
197 output->add<uint16_t>(mapHeight);
198 }
199
200 if (requestedInfo & REQUEST_EXT_PLAYERS_INFO) {
201 output->addByte(0x21); // players info - online players list
202
203 const auto& players = g_game.getPlayers();
204 output->add<uint32_t>(players.size());
205 for (const auto& it : players) {
206 output->addString(it.second->getName());
207 output->add<uint32_t>(it.second->getLevel());
208 }
209 }
210
211 if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) {
212 output->addByte(0x22); // players info - online status info of a player
213 if (g_game.getPlayerByName(characterName) != nullptr) {
214 output->addByte(0x01);
215 } else {
216 output->addByte(0x00);
217 }
218 }
219
220 if (requestedInfo & REQUEST_SERVER_SOFTWARE_INFO) {
221 output->addByte(0x23); // server software info
222 output->addString(STATUS_SERVER_NAME);
223 output->addString(STATUS_SERVER_VERSION);
224 output->addString(CLIENT_VERSION_STR);
225 }
226 send(output);
227 disconnect();
228}
229