diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/server | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/server')
-rw-r--r-- | source/server/CMakeLists.txt | 25 | ||||
-rw-r--r-- | source/server/StarServerQueryThread.cpp | 278 | ||||
-rw-r--r-- | source/server/StarServerQueryThread.hpp | 108 | ||||
-rw-r--r-- | source/server/StarServerRconClient.cpp | 145 | ||||
-rw-r--r-- | source/server/StarServerRconClient.hpp | 51 | ||||
-rw-r--r-- | source/server/StarServerRconThread.cpp | 62 | ||||
-rw-r--r-- | source/server/StarServerRconThread.hpp | 36 | ||||
-rw-r--r-- | source/server/main.cpp | 94 |
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; +} |