Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/server
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/server
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/server')
-rw-r--r--source/server/CMakeLists.txt25
-rw-r--r--source/server/StarServerQueryThread.cpp278
-rw-r--r--source/server/StarServerQueryThread.hpp108
-rw-r--r--source/server/StarServerRconClient.cpp145
-rw-r--r--source/server/StarServerRconClient.hpp51
-rw-r--r--source/server/StarServerRconThread.cpp62
-rw-r--r--source/server/StarServerRconThread.hpp36
-rw-r--r--source/server/main.cpp94
8 files changed, 799 insertions, 0 deletions
diff --git a/source/server/CMakeLists.txt b/source/server/CMakeLists.txt
new file mode 100644
index 0000000..d6a22e6
--- /dev/null
+++ b/source/server/CMakeLists.txt
@@ -0,0 +1,25 @@
+INCLUDE_DIRECTORIES (
+ ${STAR_EXTERN_INCLUDES}
+ ${STAR_CORE_INCLUDES}
+ ${STAR_BASE_INCLUDES}
+ ${STAR_PLATFORM_INCLUDES}
+ ${STAR_GAME_INCLUDES}
+ )
+
+SET (star_server_HEADERS
+ StarServerQueryThread.hpp
+ StarServerRconClient.hpp
+ StarServerRconThread.hpp
+ )
+
+SET (star_server_SOURCES
+ StarServerQueryThread.cpp
+ StarServerRconClient.cpp
+ StarServerRconThread.cpp
+ main.cpp
+ )
+
+ADD_EXECUTABLE (starbound_server
+ $<TARGET_OBJECTS:star_extern> $<TARGET_OBJECTS:star_core> $<TARGET_OBJECTS:star_base> $<TARGET_OBJECTS:star_game>
+ ${star_server_HEADERS} ${star_server_SOURCES})
+TARGET_LINK_LIBRARIES (starbound_server ${STAR_EXT_LIBS})
diff --git a/source/server/StarServerQueryThread.cpp b/source/server/StarServerQueryThread.cpp
new file mode 100644
index 0000000..446ecbb
--- /dev/null
+++ b/source/server/StarServerQueryThread.cpp
@@ -0,0 +1,278 @@
+#include "StarServerQueryThread.hpp"
+#include "StarLogging.hpp"
+#include "StarRoot.hpp"
+#include "StarConfiguration.hpp"
+#include "StarVersion.hpp"
+#include "StarUniverseServer.hpp"
+#include "StarIterator.hpp"
+
+namespace Star {
+
+ServerQueryThread::ServerQueryThread(UniverseServer* universe, HostAddressWithPort const& bindAddress)
+ : Thread("QueryServer"),
+ m_universe(universe),
+ m_queryServer(bindAddress),
+ m_stop(true),
+ m_lastChallengeCheck(Time::monotonicMilliseconds()) {
+ m_playersResponse.resize(A2S_PACKET_SIZE);
+ m_playersResponse.setByteOrder(ByteOrder::LittleEndian);
+ m_playersResponse.setNullTerminatedStrings(true);
+
+ m_rulesResponse.resize(A2S_PACKET_SIZE);
+ m_rulesResponse.setByteOrder(ByteOrder::LittleEndian);
+ m_rulesResponse.setNullTerminatedStrings(true);
+
+ m_generalResponse.resize(A2S_PACKET_SIZE);
+ m_generalResponse.setByteOrder(ByteOrder::LittleEndian);
+ m_generalResponse.setNullTerminatedStrings(true);
+
+ m_serverPort = 0;
+ m_lastActiveTime = 0;
+
+ auto& root = Root::singleton();
+ auto cfg = root.configuration();
+
+ m_maxPlayers = cfg->get("maxPlayers").toUInt();
+ m_serverName = cfg->get("serverName").toString();
+
+ m_lastPlayersResponse = 0;
+ m_lastRulesResponse = 0;
+}
+
+ServerQueryThread::~ServerQueryThread() {
+ stop();
+ join();
+}
+
+void ServerQueryThread::start() {
+ m_stop = false;
+ Thread::start();
+ m_lastActiveTime = Time::monotonicMilliseconds();
+}
+
+void ServerQueryThread::stop() {
+ m_stop = true;
+ m_queryServer.close();
+}
+
+void ServerQueryThread::sendTo(HostAddressWithPort const& address, DataStreamBuffer* ds) {
+ m_queryServer.send(address, ds->ptr(), ds->size());
+}
+
+uint8_t ServerQueryThread::serverPlayerCount() {
+ return m_universe->numberOfClients();
+}
+
+bool ServerQueryThread::serverPassworded() {
+ // TODO: implement
+ return false;
+}
+
+String ServerQueryThread::serverWorldNames() {
+ auto activeWorlds = m_universe->activeWorlds();
+ if (activeWorlds.empty())
+ return String("Unknown");
+
+ return StringList(activeWorlds.transformed(printWorldId)).join(",");
+}
+
+const char* ServerQueryThread::serverPlugins() {
+ // TODO: implement
+ return "none";
+}
+
+bool ServerQueryThread::processPacket(HostAddressWithPort const& address, char const* data, size_t length) {
+ uint8_t* buf = (uint8_t*)data;
+ if (length < 5 || buf[0] != 0xff || buf[1] != 0xff || buf[2] != 0xff || buf[3] != 0xff) {
+ // short packet or missing header
+ return false;
+ }
+
+ // Process packet
+ switch (buf[4]) {
+ case A2S_INFO_REQUEST: {
+ // We use -6 and not -5 as the string should be NULL terminated
+ // but instead of the std::string constructor stopping at the NULL
+ // it includes it :(
+ std::string str((const char*)(buf + 5), length - 6);
+ if (str.compare(A2S_INFO_REQUEST_STRING) != 0) {
+ // Invalid request
+ return false;
+ }
+
+ m_generalResponse.clear();
+ m_generalResponse << A2S_HEAD_INT << A2S_INFO_REPLY << A2S_VERSION << m_serverName << serverWorldNames()
+ << GAME_DIR << GAME_DESC << A2S_APPID // Should be SteamAppId but this isn't a short :(
+ << serverPlayerCount() << m_maxPlayers << (uint8_t)0x00 // bots
+ << A2S_TYPE_DEDICATED // dedicated
+#ifdef STAR_SYSTEM_FAMILY_WINDOWS
+ << A2S_ENV_WINDOWS // os
+#else
+ << A2S_ENV_LINUX // os
+#endif
+ << serverPassworded() << A2S_VAC_OFF // secure
+ << StarVersionString << A2S_EDF_PORT // EDF
+ << m_serverPort;
+
+ sendTo(address, &m_generalResponse);
+ return true;
+ }
+ case A2S_CHALLENGE_REQUEST:
+ sendChallenge(address);
+ return true;
+
+ case A2S_PLAYER_REQUEST:
+ if (challengeRequest(address, data, length))
+ return true;
+
+ if (!validChallenge(address, data, length))
+ return false;
+
+ buildPlayerResponse();
+ sendTo(address, &m_playersResponse);
+ return true;
+
+ case A2S_RULES_REQUEST:
+ if (challengeRequest(address, data, length))
+ return true;
+
+ if (!validChallenge(address, data, length))
+ return false;
+
+ buildRuleResponse();
+ sendTo(address, &m_rulesResponse);
+ return true;
+ }
+
+ return false;
+}
+
+void ServerQueryThread::buildPlayerResponse() {
+ int64_t now = Time::monotonicMilliseconds();
+ if (now < m_lastPlayersResponse + responseCacheTime) {
+ return;
+ }
+
+ auto clientIds = m_universe->clientIds();
+ uint8_t cnt = (uint8_t)clientIds.count();
+ int32_t kills = 0; // Not currently supported
+ float timeConnected = 60; // Not supported defaults to 1min
+
+ m_playersResponse.clear();
+ m_playersResponse << A2S_HEAD_INT << A2S_PLAYER_REPLY << cnt;
+
+ uint8_t i = 0;
+ for (auto clientId : clientIds) {
+ m_playersResponse << i++ << m_universe->clientNick(clientId) << kills << timeConnected;
+ }
+
+ m_lastPlayersResponse = now;
+}
+
+void ServerQueryThread::buildRuleResponse() {
+ int64_t now = Time::monotonicMilliseconds();
+ if (now < m_lastRulesResponse + responseCacheTime) {
+ return;
+ }
+
+ uint16_t cnt = 1;
+ m_rulesResponse.clear();
+ m_rulesResponse << A2S_HEAD_INT << A2S_RULES_REPLY << cnt << "plugins" << serverPlugins();
+
+ m_lastRulesResponse = now;
+}
+
+void ServerQueryThread::sendChallenge(HostAddressWithPort const& address) {
+ auto challenge = make_shared<RequestChallenge>();
+
+ m_validChallenges[address.address()] = challenge;
+ m_generalResponse.clear();
+ m_generalResponse << A2S_HEAD_INT << A2S_CHALLENGE_RESPONSE << challenge->getChallenge();
+
+ sendTo(address, &m_generalResponse);
+}
+
+void ServerQueryThread::pruneChallenges() {
+ int64_t now = Time::monotonicMilliseconds();
+ if (now < m_lastChallengeCheck + challengeCheckInterval) {
+ return;
+ }
+
+ auto expire = now - challengeCheckInterval;
+ auto it = makeSMutableMapIterator(m_validChallenges);
+ while (it.hasNext()) {
+ auto const& pair = it.next();
+ if (pair.second->before(expire)) {
+ it.remove();
+ }
+ }
+ m_lastChallengeCheck = now;
+}
+
+void ServerQueryThread::run() {
+ HostAddressWithPort udpAddress;
+ char udpData[MaxUdpData];
+ while (!m_stop) {
+ try {
+ auto len = m_queryServer.receive(&udpAddress, udpData, MaxUdpData, 100);
+ pruneChallenges();
+ if (len != 0)
+ processPacket(udpAddress, udpData, len);
+ } catch (SocketClosedException const&) {
+ } catch (std::exception const& e) {
+ Logger::error("ServerQueryThread exception caught: %s", outputException(e, true));
+ }
+ }
+}
+
+ServerQueryThread::RequestChallenge::RequestChallenge()
+ : m_time(Time::monotonicMilliseconds()), m_challenge(Random::randi32()) {}
+
+bool ServerQueryThread::RequestChallenge::before(uint64_t time) {
+ return m_time < time;
+}
+
+int ServerQueryThread::RequestChallenge::getChallenge() {
+ return m_challenge;
+}
+
+bool ServerQueryThread::validChallenge(HostAddressWithPort const& address, char const* data, size_t len) {
+ if (len != 9) {
+ // too much or too little data
+ return false;
+ }
+
+ if (m_validChallenges.count(address.address()) == 0) {
+ // Don't know this source address ignore
+ return false;
+ }
+
+ uint8_t const* b = (uint8_t const*)data;
+ int32_t challenge = ((int32_t)b[8] & 0xff) << 24 | ((int32_t)b[7] & 0xff) << 16 | ((int32_t)b[6] & 0xff) << 8
+ | ((int32_t)b[5] & 0xff);
+ // Note: No byte order swapping needed as protcol performs no conversion
+ if (m_validChallenges.get(address.address())->getChallenge() != challenge) {
+ // Challenges didnt match ignore
+ return false;
+ }
+
+ // All good
+ return true;
+}
+
+bool ServerQueryThread::challengeRequest(HostAddressWithPort const& address, char const* data, size_t len) {
+ if (len != 9) {
+ // too much or too little data
+ return false;
+ }
+
+ uint8_t const* buf = (uint8_t const*)data;
+ if ((buf[5] == 0xff) && (buf[6] == 0xff) && (buf[7] == 0xff) && (buf[8] == 0xff)) {
+ sendChallenge(address);
+ return true;
+ }
+
+ return false;
+}
+
+}
diff --git a/source/server/StarServerQueryThread.hpp b/source/server/StarServerQueryThread.hpp
new file mode 100644
index 0000000..3fb3339
--- /dev/null
+++ b/source/server/StarServerQueryThread.hpp
@@ -0,0 +1,108 @@
+#ifndef STAR_SERVER_QUERY_THREAD_HPP
+#define STAR_SERVER_QUERY_THREAD_HPP
+
+#include "StarThread.hpp"
+#include "StarHostAddress.hpp"
+#include "StarUdp.hpp"
+#include "StarMap.hpp"
+#include "StarDataStreamDevices.hpp"
+
+#include <random>
+
+namespace Star {
+
+STAR_CLASS(UniverseServer);
+STAR_CLASS(ServerQueryThread);
+
+class ServerQueryThread : public Thread {
+public:
+ ServerQueryThread(UniverseServer* universe, HostAddressWithPort const& bindAddress);
+ ~ServerQueryThread();
+
+ void start();
+ void stop();
+
+protected:
+ virtual void run();
+
+private:
+ static const uint8_t A2A_PING_REQUEST = 0x69;
+ static const uint8_t A2A_PING_REPLY = 0x6a;
+ static const uint8_t A2S_CHALLENGE_REQUEST = 0x57;
+ static const uint8_t A2S_CHALLENGE_RESPONSE = 0x41;
+ static const uint8_t A2S_INFO_REQUEST = 0x54;
+ static const uint8_t A2S_INFO_REPLY = 0x49;
+ static const uint8_t A2S_PLAYER_REQUEST = 0x55;
+ static const uint8_t A2S_PLAYER_REPLY = 0x44;
+ static const uint8_t A2S_RULES_REQUEST = 0x56;
+ static const uint8_t A2S_RULES_REPLY = 0x45;
+ static const uint8_t A2S_VERSION = 0x07;
+ static const uint8_t A2S_STR_TERM = 0x00;
+ static const uint8_t A2S_EDF_GID = 0x01;
+ static const uint8_t A2S_EDF_SID = 0x10;
+ static const uint8_t A2S_EDF_TAGS = 0x20;
+ static const uint8_t A2S_EDF_STV = 0x40;
+ static const uint8_t A2S_EDF_PORT = 0x80;
+ static const uint8_t A2S_ENV_WINDOWS = 'W';
+ static const uint8_t A2S_ENV_LINUX = 'L';
+ static const uint8_t A2S_TYPE_DEDICATED = 'D';
+ static const uint8_t A2S_TYPE_LISTEN = 'L';
+ static const uint8_t A2S_TYPE_TV = 'P';
+ static const uint8_t A2S_VAC_OFF = 0x00;
+ static const uint8_t A2S_VAC_ON = 0x01;
+ static constexpr const char* A2S_INFO_REQUEST_STRING = "Source Engine Query";
+ static const uint16_t A2S_APPID = (uint16_t)0xfffe;
+ static const uint16_t A2S_PACKET_SIZE = (uint16_t)0x4e0;
+ static const uint32_t A2S_HEAD_INT = 0xffffffff;
+ static constexpr const char* GAME_DIR = "starbound";
+ static constexpr const char* GAME_DESC = "Starbound";
+ static constexpr const char* GAME_TYPE = "SMP";
+ static const int32_t challengeCheckInterval = 30000;
+ static const int32_t responseCacheTime = 5000;
+
+ void sendTo(HostAddressWithPort const& address, DataStreamBuffer* ds);
+ bool processPacket(HostAddressWithPort const& address, char const* data, size_t length);
+ void buildPlayerResponse();
+ void buildRuleResponse();
+ bool validChallenge(HostAddressWithPort const& address, char const* data, size_t length);
+ void sendChallenge(HostAddressWithPort const& address);
+ void pruneChallenges();
+ bool challengeRequest(HostAddressWithPort const& address, char const* data, size_t length);
+
+ // Server API
+ uint8_t serverPlayerCount();
+ bool serverPassworded();
+ const char* serverPlugins();
+ String serverWorldNames();
+
+ UniverseServer* m_universe;
+ UdpServer m_queryServer;
+ bool m_stop;
+ DataStreamBuffer m_playersResponse;
+ DataStreamBuffer m_rulesResponse;
+ DataStreamBuffer m_generalResponse;
+
+ class RequestChallenge {
+ public:
+ RequestChallenge();
+ bool before(uint64_t time);
+ int32_t getChallenge();
+
+ private:
+ uint64_t m_time;
+ int32_t m_challenge;
+ };
+
+ uint16_t m_serverPort;
+ uint8_t m_maxPlayers;
+ String m_serverName;
+ HashMap<HostAddress, shared_ptr<RequestChallenge>> m_validChallenges;
+ int64_t m_lastChallengeCheck;
+ int64_t m_lastPlayersResponse;
+ int64_t m_lastRulesResponse;
+ int64_t m_lastActiveTime;
+};
+
+}
+
+#endif
diff --git a/source/server/StarServerRconClient.cpp b/source/server/StarServerRconClient.cpp
new file mode 100644
index 0000000..a42a8ea
--- /dev/null
+++ b/source/server/StarServerRconClient.cpp
@@ -0,0 +1,145 @@
+#include "StarServerRconThread.hpp"
+#include "StarServerRconClient.hpp"
+#include "StarLogging.hpp"
+#include "StarRoot.hpp"
+#include "StarConfiguration.hpp"
+#include "StarUniverseServer.hpp"
+#include "StarLexicalCast.hpp"
+
+namespace Star {
+
+ServerRconClient::ServerRconClient(UniverseServer* universe, TcpSocketPtr socket)
+ : Thread("RconClient"),
+ m_universe(universe),
+ m_socket(socket),
+ m_packetBuffer(MaxPacketSize),
+ m_stop(true),
+ m_authed(false) {
+ auto& root = Root::singleton();
+ auto cfg = root.configuration();
+
+ m_packetBuffer.setByteOrder(ByteOrder::LittleEndian);
+ m_packetBuffer.setNullTerminatedStrings(true);
+
+ m_rconPassword = cfg->get("rconServerPassword").toString();
+}
+
+ServerRconClient::~ServerRconClient() {
+ stop();
+ join();
+}
+
+String ServerRconClient::handleCommand(String commandLine) {
+ String command = commandLine.extract();
+
+ if (command == "echo") {
+ return commandLine;
+ } else if (command == "broadcast" || command == "say") {
+ m_universe->adminBroadcast(commandLine);
+ return strf("OK: said %s", commandLine);
+ } else if (command == "stop") {
+ m_universe->stop();
+ return "OK: shutting down";
+ } else {
+ return m_universe->adminCommand(strf("%s %s", command, commandLine));
+ }
+}
+
+void ServerRconClient::receive(size_t size) {
+ m_packetBuffer.reset(size);
+ auto ptr = m_packetBuffer.ptr();
+ while (size > 0) {
+ size_t r = m_socket->receive(ptr, size);
+ if (r == 0)
+ throw NoMoreRequests();
+ size -= r;
+ ptr += r;
+ }
+}
+
+void ServerRconClient::send(uint32_t requestId, uint32_t cmd, String str) {
+ m_packetBuffer.clear();
+ m_packetBuffer << (uint32_t)(str.utf8Size() + 10) << requestId << cmd << str << (uint8_t)0x00;
+ m_socket->send(m_packetBuffer.ptr(), m_packetBuffer.size());
+}
+
+void ServerRconClient::sendAuthFailure() {
+ send(SERVERDATA_AUTH_FAILURE, SERVERDATA_AUTH_RESPONSE, "");
+}
+
+void ServerRconClient::sendCmdResponse(uint32_t requestId, String response) {
+ size_t len = response.length();
+ // Always send at least one packet even if the response was blank
+ do {
+ auto dataLen = (len >= MaxPacketSize) ? MaxPacketSize : len;
+ send(requestId, SERVERDATA_RESPONSE_VALUE, response.substr(0, dataLen));
+ response = response.substr(dataLen);
+ len = response.length();
+ } while (len > 0);
+}
+
+void ServerRconClient::start() {
+ m_stop = false;
+ Thread::start();
+}
+
+void ServerRconClient::stop() {
+ m_stop = true;
+ m_socket->close();
+}
+
+void ServerRconClient::processRequest() {
+ receive(4);
+ uint32_t size = m_packetBuffer.read<uint32_t>();
+
+ receive(size);
+ uint32_t requestId;
+ m_packetBuffer >> requestId;
+
+ uint32_t cmd;
+ m_packetBuffer >> cmd;
+
+ switch (cmd) {
+ case SERVERDATA_AUTH: {
+ String password;
+ m_packetBuffer >> password;
+ if (!m_rconPassword.empty() && m_rconPassword.equals(password)) {
+ m_authed = true;
+ send(requestId, SERVERDATA_RESPONSE_VALUE);
+ send(requestId, SERVERDATA_AUTH_RESPONSE);
+ } else {
+ m_authed = false;
+ sendAuthFailure();
+ }
+ break;
+ }
+ case SERVERDATA_EXECCOMMAND:
+ if (m_authed) {
+ String command;
+ m_packetBuffer >> command;
+ try {
+ Logger::info("RCON %s: %s", m_socket->remoteAddress(), command);
+ sendCmdResponse(requestId, handleCommand(command));
+ } catch (std::exception const& e) {
+ sendCmdResponse(requestId, strf("RCON: Error executing: %s: %s", command, outputException(e, true)));
+ }
+ } else {
+ sendAuthFailure();
+ }
+ break;
+ default:
+ sendCmdResponse(requestId, strf("Unknown request %06x", cmd));
+ }
+}
+
+void ServerRconClient::run() {
+ try {
+ while (!m_stop)
+ processRequest();
+ } catch (NoMoreRequests const&) {
+ } catch (std::exception const& e) {
+ Logger::error("ServerRconClient exception caught: %s", outputException(e, false));
+ }
+}
+
+}
diff --git a/source/server/StarServerRconClient.hpp b/source/server/StarServerRconClient.hpp
new file mode 100644
index 0000000..8c9bb25
--- /dev/null
+++ b/source/server/StarServerRconClient.hpp
@@ -0,0 +1,51 @@
+#ifndef STAR_SERVER_RCON_CLIENT_HPP
+#define STAR_SERVER_RCON_CLIENT_HPP
+
+#include "StarThread.hpp"
+#include "StarTcp.hpp"
+#include "StarMap.hpp"
+#include "StarDataStreamDevices.hpp"
+
+namespace Star {
+
+class UniverseServer;
+
+class ServerRconClient : public Thread {
+public:
+ static const uint32_t SERVERDATA_AUTH = 0x03;
+ static const uint32_t SERVERDATA_EXECCOMMAND = 0x02;
+ static const uint32_t SERVERDATA_RESPONSE_VALUE = 0x00;
+ static const uint32_t SERVERDATA_AUTH_RESPONSE = 0x02;
+ static const uint32_t SERVERDATA_AUTH_FAILURE = 0xffffffff;
+ ServerRconClient(UniverseServer* universe, TcpSocketPtr socket);
+ ~ServerRconClient();
+
+ void start();
+ void stop();
+
+protected:
+ virtual void run();
+
+private:
+ static size_t const MaxPacketSize = 4096;
+ STAR_EXCEPTION(NoMoreRequests, StarException);
+
+ void receive(size_t size);
+ void send(uint32_t requestId, uint32_t cmd, String str = "");
+ void sendAuthFailure();
+ void sendCmdResponse(uint32_t requestId, String response);
+ void closeSocket();
+ void processRequest();
+ String handleCommand(String commandLine);
+
+ UniverseServer* m_universe;
+ TcpSocketPtr m_socket;
+ DataStreamBuffer m_packetBuffer;
+ bool m_stop;
+ bool m_authed;
+ String m_rconPassword;
+};
+typedef shared_ptr<ServerRconClient> ServerRconClientPtr;
+}
+
+#endif
diff --git a/source/server/StarServerRconThread.cpp b/source/server/StarServerRconThread.cpp
new file mode 100644
index 0000000..45f94b3
--- /dev/null
+++ b/source/server/StarServerRconThread.cpp
@@ -0,0 +1,62 @@
+#include "StarServerRconThread.hpp"
+#include "StarLogging.hpp"
+#include "StarRoot.hpp"
+#include "StarConfiguration.hpp"
+#include "StarUniverseServer.hpp"
+#include "StarServerRconClient.hpp"
+#include "StarIterator.hpp"
+
+namespace Star {
+
+ServerRconThread::ServerRconThread(UniverseServer* universe, HostAddressWithPort const& address)
+ : Thread("RconServer"), m_universe(universe), m_rconServer(address), m_stop(true) {
+ if (Root::singleton().configuration()->get("rconServerPassword").toString().empty())
+ Logger::warn("rconServerPassword is not configured requests will NOT be processed");
+}
+
+ServerRconThread::~ServerRconThread() {
+ stop();
+ join();
+}
+
+void ServerRconThread::clearClients(bool all) {
+ auto it = makeSMutableMapIterator(m_clients);
+ while (it.hasNext()) {
+ auto const& pair = it.next();
+ auto client = pair.second;
+ if (all)
+ client->stop();
+ else if (!client->isRunning())
+ it.remove();
+ }
+}
+
+void ServerRconThread::start() {
+ m_stop = false;
+ Thread::start();
+}
+
+void ServerRconThread::stop() {
+ m_stop = true;
+ m_rconServer.stop();
+ clearClients(true);
+}
+
+void ServerRconThread::run() {
+ try {
+ auto timeout = Root::singleton().configuration()->get("rconServerTimeout").toInt();
+ while (!m_stop) {
+ if (auto client = m_rconServer.accept(100)) {
+ client->setTimeout(timeout);
+ auto rconClient = make_shared<ServerRconClient>(m_universe, client);
+ rconClient->start();
+ m_clients[client->remoteAddress().address()] = rconClient;
+ clearClients();
+ }
+ }
+ } catch (std::exception const& e) {
+ Logger::error("ServerRconThread exception caught: %s", e.what());
+ }
+}
+
+}
diff --git a/source/server/StarServerRconThread.hpp b/source/server/StarServerRconThread.hpp
new file mode 100644
index 0000000..ea1b649
--- /dev/null
+++ b/source/server/StarServerRconThread.hpp
@@ -0,0 +1,36 @@
+#ifndef STAR_SERVER_RCON_THREAD_HPP
+#define STAR_SERVER_RCON_THREAD_HPP
+
+#include "StarThread.hpp"
+#include "StarTcp.hpp"
+#include "StarMap.hpp"
+#include "StarServerRconClient.hpp"
+
+namespace Star {
+
+STAR_CLASS(UniverseServer);
+STAR_CLASS(ServerRconThread);
+
+class ServerRconThread : public Thread {
+public:
+ ServerRconThread(UniverseServer* universe, HostAddressWithPort const& address);
+ ~ServerRconThread();
+
+ void start();
+ void stop();
+
+protected:
+ virtual void run();
+
+private:
+ void clearClients(bool all = false);
+
+ UniverseServer* m_universe;
+ TcpServer m_rconServer;
+ bool m_stop;
+ HashMap<HostAddress, ServerRconClientPtr> m_clients;
+};
+
+}
+
+#endif
diff --git a/source/server/main.cpp b/source/server/main.cpp
new file mode 100644
index 0000000..6522acf
--- /dev/null
+++ b/source/server/main.cpp
@@ -0,0 +1,94 @@
+#include "StarFile.hpp"
+#include "StarRandom.hpp"
+#include "StarLexicalCast.hpp"
+#include "StarLogging.hpp"
+#include "StarUniverseServer.hpp"
+#include "StarRootLoader.hpp"
+#include "StarConfiguration.hpp"
+#include "StarVersion.hpp"
+#include "StarServerQueryThread.hpp"
+#include "StarServerRconThread.hpp"
+#include "StarSignalHandler.hpp"
+
+using namespace Star;
+
+Json const AdditionalDefaultConfiguration = Json::parseJson(R"JSON(
+ {
+ "configurationVersion" : {
+ "server" : 4
+ },
+
+ "runQueryServer" : false,
+ "queryServerPort" : 21025,
+ "queryServerBind" : "::",
+
+ "runRconServer" : false,
+ "rconServerPort" : 21026,
+ "rconServerBind" : "::",
+ "rconServerPassword" : "",
+ "rconServerTimeout" : 1000,
+
+ "allowAssetsMismatch" : true,
+ "serverOverrideAssetsDigest" : null
+ }
+ )JSON");
+
+int main(int argc, char** argv) {
+ try {
+ RootLoader rootLoader({{}, AdditionalDefaultConfiguration, String("starbound_server.log"), LogLevel::Info, false, String("starbound_server.config")});
+ RootUPtr root = rootLoader.commandInitOrDie(argc, argv).first;
+ root->fullyLoad();
+
+ SignalHandler signalHandler;
+ signalHandler.setHandleFatal(true);
+ signalHandler.setHandleInterrupt(true);
+
+ auto configuration = root->configuration();
+ {
+ Logger::info("Server Version %s (%s) Source ID: %s Protocol: %s", StarVersionString, StarArchitectureString, StarSourceIdentifierString, StarProtocolVersion);
+
+ UniverseServerUPtr server = make_unique<UniverseServer>(root->toStoragePath("universe"));
+ server->setListeningTcp(true);
+ server->start();
+
+ ServerQueryThreadUPtr queryServer;
+ if (configuration->get("runQueryServer").toBool()) {
+ queryServer = make_unique<ServerQueryThread>(server.get(), HostAddressWithPort(configuration->get("queryServerBind").toString(), configuration->get("queryServerPort").toInt()));
+ queryServer->start();
+ }
+
+ ServerRconThreadUPtr rconServer;
+ if (configuration->get("runRconServer").toBool()) {
+ rconServer = make_unique<ServerRconThread>(server.get(), HostAddressWithPort(configuration->get("rconServerBind").toString(), configuration->get("rconServerPort").toInt()));
+ rconServer->start();
+ }
+
+ while (server->isRunning()) {
+ if (signalHandler.interruptCaught()) {
+ Logger::info("Interrupt caught!");
+ server->stop();
+ break;
+ }
+ Thread::sleep(100);
+ }
+
+ server->join();
+
+ if (queryServer) {
+ queryServer->stop();
+ queryServer->join();
+ }
+
+ if (rconServer) {
+ rconServer->stop();
+ rconServer->join();
+ }
+ }
+
+ Logger::info("Server shutdown gracefully");
+ } catch (std::exception const& e) {
+ fatalException(e, true);
+ }
+
+ return 0;
+}