mirror of
https://github.com/alterware/master-server.git
synced 2025-06-27 23:01:52 +00:00
init
This commit is contained in:
60
src/services/elimination_handler.cpp
Normal file
60
src/services/elimination_handler.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "elimination_handler.hpp"
|
||||
|
||||
constexpr auto T7_PROTOCOL = 7;
|
||||
|
||||
constexpr size_t MAX_SERVERS_PER_GAME = 15;
|
||||
|
||||
void elimination_handler::run_frame()
|
||||
{
|
||||
std::unordered_map<game_type, std::unordered_map<network::address, size_t>> server_count;
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
this->get_server().get_server_list().iterate([&](server_list::iteration_context& context)
|
||||
{
|
||||
auto& server = context.get();
|
||||
const auto diff = now - server.heartbeat;
|
||||
|
||||
if ((server.state == game_server::state::pinged && diff > 2min) ||
|
||||
(server.state == game_server::state::can_ping && diff > 15min))
|
||||
{
|
||||
context.remove();
|
||||
}
|
||||
|
||||
if (server.game == game_type::unknown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.game == game_type::t7 && server.protocol < T7_PROTOCOL)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("Removing T7 server '%s' because protocol %i is less than %i\n",
|
||||
context.get_address().to_string().data(), server.protocol, T7_PROTOCOL);
|
||||
#endif
|
||||
context.remove();
|
||||
}
|
||||
|
||||
++server_count[server.game][context.get_address()];
|
||||
if (server_count[server.game][context.get_address()] >= MAX_SERVERS_PER_GAME)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
console::info("Removing server '%s' because it exceeds MAX_SERVERS_PER_GAME\n",
|
||||
context.get_address().to_string().data());
|
||||
#endif
|
||||
context.remove();
|
||||
}
|
||||
});
|
||||
|
||||
now = std::chrono::high_resolution_clock::now();
|
||||
this->get_server().get_client_list().iterate([&](client_list::iteration_context& context)
|
||||
{
|
||||
auto& client = context.get();
|
||||
const auto diff = now - client.heartbeat;
|
||||
|
||||
if (diff > 5min || (!client.registered && diff > 20s))
|
||||
{
|
||||
context.remove();
|
||||
}
|
||||
});
|
||||
}
|
11
src/services/elimination_handler.hpp
Normal file
11
src/services/elimination_handler.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class elimination_handler : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
void run_frame() override;
|
||||
};
|
56
src/services/getbots_command.cpp
Normal file
56
src/services/getbots_command.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include <std_include.hpp>
|
||||
#include "getbots_command.hpp"
|
||||
|
||||
#include "../console.hpp"
|
||||
|
||||
const char* getbots_command::get_command() const
|
||||
{
|
||||
return "getbots";
|
||||
}
|
||||
|
||||
void getbots_command::handle_command(const network::address& target, const std::string_view&)
|
||||
{
|
||||
static const std::vector<std::string> bot_names
|
||||
{
|
||||
"aerosoul",
|
||||
"Eldor",
|
||||
"FutureRave",
|
||||
"Girl",
|
||||
"INeedBots",
|
||||
"INeedGames",
|
||||
"Infamous",
|
||||
"Jebus3211",
|
||||
"Joel",
|
||||
"JTAG",
|
||||
"Laupetin",
|
||||
"Louvenarde",
|
||||
"OneFourOne",
|
||||
"PeterG",
|
||||
"quaK",
|
||||
"RezTech",
|
||||
"sass",
|
||||
"Slykuiper",
|
||||
"st0rm",
|
||||
"xensik",
|
||||
"xoxor4d",
|
||||
"Diamante",
|
||||
"Dsso",
|
||||
"Evan",
|
||||
"FragsAreUs",
|
||||
"FryTechTip",
|
||||
"H3X1C",
|
||||
"homura",
|
||||
"Jimbo",
|
||||
"RektInator",
|
||||
"Squirrel",
|
||||
};
|
||||
|
||||
std::stringstream stream{};
|
||||
for (const auto& bot : bot_names)
|
||||
{
|
||||
stream << bot << std::endl;
|
||||
}
|
||||
|
||||
this->get_server().send(target, "getbotsResponse", stream.str());
|
||||
console::log("Sent bot names: %s", target.to_string().data());
|
||||
}
|
12
src/services/getbots_command.hpp
Normal file
12
src/services/getbots_command.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class getbots_command : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
const char* get_command() const override;
|
||||
void handle_command(const network::address& target, const std::string_view& data) override;
|
||||
};
|
96
src/services/getservers_command.cpp
Normal file
96
src/services/getservers_command.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include <std_include.hpp>
|
||||
#include "getservers_command.hpp"
|
||||
|
||||
#include "../console.hpp"
|
||||
|
||||
#include <utils/parameters.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct prepared_server
|
||||
{
|
||||
uint32_t address;
|
||||
uint16_t port;
|
||||
};
|
||||
}
|
||||
|
||||
constexpr auto MTU = 1400; // Real UDP MTU is more like 1500 bytes, but we keep a little wiggle room just in case
|
||||
constexpr auto DPM_PROTOCOL_ADDRESS_LENGTH = sizeof prepared_server::address;
|
||||
constexpr auto DPM_PROTOCOL_PORT_LENGTH = sizeof prepared_server::port;
|
||||
|
||||
static_assert(DPM_PROTOCOL_ADDRESS_LENGTH == 4);
|
||||
static_assert(DPM_PROTOCOL_PORT_LENGTH == 2);
|
||||
|
||||
const char* getservers_command::get_command() const
|
||||
{
|
||||
return "getservers";
|
||||
}
|
||||
|
||||
void getservers_command::handle_command(const network::address& target, const std::string_view& data)
|
||||
{
|
||||
const utils::parameters params(data);
|
||||
if (params.size() < 2)
|
||||
{
|
||||
throw execution_exception("Invalid parameter count");
|
||||
}
|
||||
|
||||
const auto& game = params[0];
|
||||
|
||||
const auto* p = params[1].data();
|
||||
char* end;
|
||||
const auto protocol = strtol(params[1].data(), &end, 10);
|
||||
if (p == end)
|
||||
{
|
||||
throw execution_exception("Invalid protocol");
|
||||
}
|
||||
|
||||
const auto game_type = resolve_game_type(game);
|
||||
if (game_type == game_type::unknown)
|
||||
{
|
||||
throw execution_exception("Invalid game type: " + game);
|
||||
}
|
||||
|
||||
std::queue<prepared_server> prepared_servers{};
|
||||
|
||||
this->get_server().get_server_list() //
|
||||
.find_registered_servers(game_type, protocol,
|
||||
[&prepared_servers](const game_server&, const network::address& address)
|
||||
{
|
||||
const auto addr = address.get_in_addr().sin_addr.s_addr;
|
||||
const auto port = htons(address.get_port());
|
||||
|
||||
prepared_servers.push({ addr, port });
|
||||
});
|
||||
|
||||
size_t packet_count = 0;
|
||||
std::string response{};
|
||||
|
||||
while (!prepared_servers.empty())
|
||||
{
|
||||
const auto& server = prepared_servers.front();
|
||||
response.push_back('\\');
|
||||
response.append(reinterpret_cast<const char*>(&server.address), DPM_PROTOCOL_ADDRESS_LENGTH);
|
||||
response.append(reinterpret_cast<const char*>(&server.port), DPM_PROTOCOL_PORT_LENGTH);
|
||||
prepared_servers.pop();
|
||||
|
||||
if (response.size() >= MTU || prepared_servers.empty())
|
||||
{
|
||||
// Only send EOT if the queue is empty (last packet)
|
||||
if (prepared_servers.empty())
|
||||
{
|
||||
response.push_back('\\');
|
||||
response.append("EOT");
|
||||
response.push_back('\0');
|
||||
response.push_back('\0');
|
||||
response.push_back('\0');
|
||||
}
|
||||
|
||||
this->get_server().send(target, "getserversResponse", response);
|
||||
packet_count++;
|
||||
|
||||
response.clear();
|
||||
}
|
||||
}
|
||||
|
||||
console::log("Sent %zu servers in %zu parts for game %s:\t%s", prepared_servers.size(), packet_count, game.data(), target.to_string().data());
|
||||
}
|
12
src/services/getservers_command.hpp
Normal file
12
src/services/getservers_command.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class getservers_command : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
const char* get_command() const override;
|
||||
void handle_command(const network::address& target, const std::string_view& data) override;
|
||||
};
|
12
src/services/heartbeat_command.cpp
Normal file
12
src/services/heartbeat_command.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <std_include.hpp>
|
||||
#include "heartbeat_command.hpp"
|
||||
|
||||
const char* heartbeat_command::get_command() const
|
||||
{
|
||||
return "heartbeat";
|
||||
}
|
||||
|
||||
void heartbeat_command::handle_command(const network::address& target, [[maybe_unused]] const std::string_view& data)
|
||||
{
|
||||
this->get_server().get_server_list().heartbeat(target);
|
||||
}
|
12
src/services/heartbeat_command.hpp
Normal file
12
src/services/heartbeat_command.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class heartbeat_command : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
const char* get_command() const override;
|
||||
void handle_command(const network::address& target, const std::string_view& data) override;
|
||||
};
|
60
src/services/info_response_command.cpp
Normal file
60
src/services/info_response_command.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "info_response_command.hpp"
|
||||
|
||||
#include "../console.hpp"
|
||||
|
||||
const char* info_response_command::get_command() const
|
||||
{
|
||||
return "infoResponse";
|
||||
}
|
||||
|
||||
void info_response_command::handle_command(const network::address& target, const std::string_view& data)
|
||||
{
|
||||
const auto found = this->get_server().get_server_list().find(
|
||||
target, [&data](game_server& server, const network::address& address)
|
||||
{
|
||||
utils::info_string info{data};
|
||||
const auto game = info.get("gamename");
|
||||
const auto challenge = info.get("challenge");
|
||||
|
||||
const auto game_type = resolve_game_type(game);
|
||||
|
||||
if (game_type == game_type::unknown)
|
||||
{
|
||||
server.state = game_server::state::dead;
|
||||
throw execution_exception{"Invalid game type: " + game};
|
||||
}
|
||||
|
||||
if (server.state != game_server::state::pinged)
|
||||
{
|
||||
throw execution_exception{"Stray info response"};
|
||||
}
|
||||
|
||||
if (challenge != server.challenge)
|
||||
{
|
||||
throw execution_exception{"Invalid challenge"};
|
||||
}
|
||||
|
||||
const auto player_count = atoi(info.get("clients").data());
|
||||
const auto bot_count = atoi(info.get("bots").data());
|
||||
auto real_player_count = player_count - bot_count;
|
||||
real_player_count = std::clamp(real_player_count, 0, 18);
|
||||
|
||||
server.registered = true;
|
||||
server.game = game_type;
|
||||
server.state = game_server::state::can_ping;
|
||||
server.protocol = atoi(info.get("protocol").data());
|
||||
server.clients = static_cast<unsigned int>(real_player_count);
|
||||
server.name = info.get("hostname");
|
||||
server.heartbeat = std::chrono::high_resolution_clock::now();
|
||||
server.info_string = std::move(info);
|
||||
|
||||
console::log("Server registered for game %s (%i):\t%s\t- %s", game.data(), server.protocol,
|
||||
address.to_string().data(), server.name.data());
|
||||
});
|
||||
|
||||
if (!found)
|
||||
{
|
||||
throw execution_exception{"infoResponse without server!"};
|
||||
}
|
||||
}
|
12
src/services/info_response_command.hpp
Normal file
12
src/services/info_response_command.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class info_response_command : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
const char* get_command() const override;
|
||||
void handle_command(const network::address& target, const std::string_view& data) override;
|
||||
};
|
153
src/services/kill_list.cpp
Normal file
153
src/services/kill_list.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#include "std_include.hpp"
|
||||
#include "kill_list.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
|
||||
constexpr auto* kill_file = "./kill.txt";
|
||||
|
||||
kill_list::kill_list_entry::kill_list_entry(std::string ip_address, std::string reason)
|
||||
: ip_address_(std::move(ip_address)), reason_(std::move(reason))
|
||||
{
|
||||
}
|
||||
|
||||
bool kill_list::contains(const network::address& address, std::string& reason)
|
||||
{
|
||||
auto str_address = address.to_string(false);
|
||||
|
||||
return this->entries_container_.access<bool>([&str_address, &reason](const kill_list_entries& entries)
|
||||
{
|
||||
if (const auto itr = entries.find(str_address); itr != entries.end())
|
||||
{
|
||||
reason = itr->second.reason_;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void kill_list::add_to_kill_list(const kill_list_entry& add)
|
||||
{
|
||||
const auto any_change = this->entries_container_.access<bool>([&add](kill_list_entries& entries)
|
||||
{
|
||||
const auto existing_entry = entries.find(add.ip_address_);
|
||||
if (existing_entry == entries.end() || existing_entry->second.reason_ != add.reason_)
|
||||
{
|
||||
console::info("Added %s to kill list (reason: %s)", add.ip_address_.data(), add.reason_.data());
|
||||
entries[add.ip_address_] = add;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!any_change)
|
||||
{
|
||||
console::info("%s already in kill list, doing nothing", add.ip_address_.data());
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_to_disk();
|
||||
}
|
||||
|
||||
void kill_list::remove_from_kill_list(const network::address& remove)
|
||||
{
|
||||
this->remove_from_kill_list(remove.to_string());
|
||||
}
|
||||
|
||||
void kill_list::remove_from_kill_list(const std::string& remove)
|
||||
{
|
||||
const auto any_change = this->entries_container_.access<bool>([&remove](kill_list_entries& entries)
|
||||
{
|
||||
if (entries.erase(remove))
|
||||
{
|
||||
console::info("Removed %s from kill list", remove.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!any_change)
|
||||
{
|
||||
console::info("%s not in kill list, doing nothing", remove.data());
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_to_disk();
|
||||
}
|
||||
|
||||
void kill_list::reload_from_disk()
|
||||
{
|
||||
std::string contents;
|
||||
if (!utils::io::read_file(kill_file, &contents))
|
||||
{
|
||||
console::info("Could not find %s, kill list will not be loaded.", kill_file);
|
||||
return;
|
||||
}
|
||||
|
||||
std::istringstream string_stream(contents);
|
||||
std::string line;
|
||||
|
||||
this->entries_container_.access([&string_stream, &line](kill_list_entries& entries)
|
||||
{
|
||||
entries.clear();
|
||||
while (std::getline(string_stream, line))
|
||||
{
|
||||
if (line[0] == '#')
|
||||
{
|
||||
// comments or ignored line
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string ip;
|
||||
std::string comment;
|
||||
|
||||
const auto index = line.find(' ');
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
ip = line.substr(0, index);
|
||||
comment = line.substr(index + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ip = line;
|
||||
}
|
||||
|
||||
if (ip.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Double line breaks from windows' \r\n
|
||||
if (ip[ip.size() - 1] == '\r')
|
||||
{
|
||||
ip.pop_back();
|
||||
}
|
||||
|
||||
entries.emplace(ip, kill_list_entry(ip, comment));
|
||||
}
|
||||
|
||||
console::info("Loaded %zu kill list entries from %s", entries.size(), kill_file);
|
||||
});
|
||||
}
|
||||
|
||||
void kill_list::write_to_disk()
|
||||
{
|
||||
std::ostringstream stream;
|
||||
this->entries_container_.access([&stream](const kill_list_entries& entries)
|
||||
{
|
||||
for (const auto& [ip, entry] : entries)
|
||||
{
|
||||
stream << entry.ip_address_ << " " << entry.reason_ << "\n";
|
||||
}
|
||||
|
||||
utils::io::write_file(kill_file, stream.str(), false);
|
||||
console::info("Wrote %s to disk (%zu entries)", kill_file, entries.size());
|
||||
});
|
||||
}
|
||||
|
||||
kill_list::kill_list(server& server) : service(server)
|
||||
{
|
||||
this->reload_from_disk();
|
||||
}
|
32
src/services/kill_list.hpp
Normal file
32
src/services/kill_list.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <network/address.hpp>
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class kill_list : public service
|
||||
{
|
||||
public:
|
||||
class kill_list_entry
|
||||
{
|
||||
public:
|
||||
kill_list_entry() = default;
|
||||
kill_list_entry(std::string ip_address, std::string reason);
|
||||
|
||||
std::string ip_address_;
|
||||
std::string reason_;
|
||||
};
|
||||
|
||||
kill_list(server& server);
|
||||
|
||||
bool contains(const network::address& address, std::string& reason);
|
||||
void add_to_kill_list(const kill_list_entry& add);
|
||||
void remove_from_kill_list(const network::address& remove);
|
||||
void remove_from_kill_list(const std::string& remove);
|
||||
|
||||
private:
|
||||
using kill_list_entries = std::unordered_map<std::string, kill_list_entry>;
|
||||
utils::concurrency::container<kill_list_entries> entries_container_;
|
||||
|
||||
void reload_from_disk();
|
||||
void write_to_disk();
|
||||
};
|
65
src/services/patch_kill_list_command.cpp
Normal file
65
src/services/patch_kill_list_command.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include <std_include.hpp>
|
||||
#include "patch_kill_list_command.hpp"
|
||||
|
||||
#include "crypto_key.hpp"
|
||||
#include "services/kill_list.hpp"
|
||||
|
||||
#include <utils/parameters.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
const char* patch_kill_list_command::get_command() const
|
||||
{
|
||||
return "patchkill";
|
||||
}
|
||||
|
||||
// patchkill timestamp signature add/remove target_ip (ban_reason)
|
||||
void patch_kill_list_command::handle_command([[maybe_unused]] const network::address& target, const std::string_view& data)
|
||||
{
|
||||
const utils::parameters params(data);
|
||||
if (params.size() < 3)
|
||||
{
|
||||
throw execution_exception("Invalid parameter count");
|
||||
}
|
||||
|
||||
const auto supplied_timestamp = std::chrono::seconds(std::stoul(params[0]));
|
||||
const auto current_timestamp = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
|
||||
// Abs the duration so that the client can be ahead or behind
|
||||
const auto time_stretch = std::chrono::abs(current_timestamp - supplied_timestamp);
|
||||
|
||||
// not offset by more than 5 minutes in either direction
|
||||
if (time_stretch > 5min)
|
||||
{
|
||||
throw execution_exception(utils::string::va("Invalid timestamp supplied - expected %llu, got %llu, which is more than 5 minutes apart", current_timestamp.count(), supplied_timestamp.count()));
|
||||
}
|
||||
|
||||
const auto& signature = utils::cryptography::base64::decode(params[1]);
|
||||
const auto should_remove = params[2] == "remove"s;
|
||||
|
||||
if (!should_remove && params[2] != "add"s)
|
||||
{
|
||||
throw execution_exception("Invalid parameter #2: should be 'add' or 'remove'");
|
||||
}
|
||||
|
||||
const auto supplied_reason = params.join(4);
|
||||
const auto& crypto_key = crypto_key::get();
|
||||
const auto signature_candidate = std::to_string(supplied_timestamp.count());
|
||||
|
||||
if (!utils::cryptography::ecc::verify_message(crypto_key, signature_candidate, signature))
|
||||
{
|
||||
throw execution_exception("Signature verification of the kill list patch key failed");
|
||||
}
|
||||
|
||||
const auto kill_list_service = this->get_server().get_service<kill_list>();
|
||||
const auto& supplied_address = params[3];
|
||||
|
||||
if (should_remove)
|
||||
{
|
||||
kill_list_service->remove_from_kill_list(supplied_address);
|
||||
}
|
||||
else
|
||||
{
|
||||
kill_list_service->add_to_kill_list(kill_list::kill_list_entry(supplied_address, supplied_reason));
|
||||
}
|
||||
}
|
12
src/services/patch_kill_list_command.hpp
Normal file
12
src/services/patch_kill_list_command.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class patch_kill_list_command : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
const char* get_command() const override;
|
||||
void handle_command(const network::address& target, const std::string_view& data) override;
|
||||
};
|
26
src/services/ping_handler.cpp
Normal file
26
src/services/ping_handler.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include <std_include.hpp>
|
||||
#include "ping_handler.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
void ping_handler::run_frame()
|
||||
{
|
||||
auto count = 0;
|
||||
this->get_server().get_server_list().iterate([&](server_list::iteration_context& context)
|
||||
{
|
||||
auto& server = context.get();
|
||||
if (server.state == game_server::state::needs_ping)
|
||||
{
|
||||
server.state = game_server::state::pinged;
|
||||
server.challenge = utils::cryptography::random::get_challenge();
|
||||
server.heartbeat = std::chrono::high_resolution_clock::now();
|
||||
|
||||
this->get_server().send(context.get_address(), "getinfo", server.challenge);
|
||||
|
||||
if (20 >= ++count)
|
||||
{
|
||||
context.stop_iterating();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
11
src/services/ping_handler.hpp
Normal file
11
src/services/ping_handler.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class ping_handler : public service
|
||||
{
|
||||
public:
|
||||
using service::service;
|
||||
|
||||
void run_frame() override;
|
||||
};
|
86
src/services/statistics_handler.cpp
Normal file
86
src/services/statistics_handler.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include <std_include.hpp>
|
||||
#include "statistics_handler.hpp"
|
||||
#include "../console.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
void write_stats(const std::map<game_type, std::vector<std::pair<std::string, network::address>>>& servers,
|
||||
std::map<game_type, uint32_t>& players)
|
||||
{
|
||||
rapidjson::Document root{};
|
||||
root.SetObject();
|
||||
|
||||
for (const auto& game_servers : servers)
|
||||
{
|
||||
const auto server_count = static_cast<uint32_t>(game_servers.second.size());
|
||||
const auto player_count = players[game_servers.first];
|
||||
|
||||
rapidjson::Value game{};
|
||||
game.SetObject();
|
||||
|
||||
game.AddMember("servers", server_count, root.GetAllocator());
|
||||
game.AddMember("players", player_count, root.GetAllocator());
|
||||
|
||||
auto game_name = resolve_game_type_name(game_servers.first);
|
||||
game_name = utils::string::to_lower(game_name);
|
||||
|
||||
rapidjson::Value game_name_object(game_name, root.GetAllocator());
|
||||
root.AddMember(game_name_object, game, root.GetAllocator());
|
||||
}
|
||||
|
||||
rapidjson::StringBuffer root_buffer{};
|
||||
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||
root_writer(root_buffer);
|
||||
root.Accept(root_writer);
|
||||
|
||||
std::string root_data(root_buffer.GetString(), root_buffer.GetLength());
|
||||
utils::io::write_file("/var/www/master.xlabs.dev/stats.json", root_data);
|
||||
}
|
||||
}
|
||||
|
||||
statistics_handler::statistics_handler(server& server)
|
||||
: service(server)
|
||||
, last_print(std::chrono::high_resolution_clock::now())
|
||||
{
|
||||
}
|
||||
|
||||
void statistics_handler::run_frame()
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (now - this->last_print < 5min)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<game_type, uint32_t> players{};
|
||||
std::map<game_type, std::vector<std::pair<std::string, network::address>>> servers;
|
||||
|
||||
this->last_print = std::chrono::high_resolution_clock::now();
|
||||
this->get_server().get_server_list().iterate([&servers, &players](const server_list::iteration_context& context)
|
||||
{
|
||||
const auto& server = context.get();
|
||||
if (server.registered)
|
||||
{
|
||||
servers[server.game].emplace_back(server.name, context.get_address());
|
||||
players[server.game] += server.clients;
|
||||
}
|
||||
});
|
||||
|
||||
console::lock _{};
|
||||
|
||||
for (const auto& game_servers : servers)
|
||||
{
|
||||
console::log("%s (%d):", resolve_game_type_name(game_servers.first).data(),
|
||||
static_cast<uint32_t>(game_servers.second.size()));
|
||||
|
||||
for (const auto& server : game_servers.second)
|
||||
{
|
||||
console::log("\t%s\t%s", server.second.to_string().data(), server.first.data());
|
||||
}
|
||||
}
|
||||
|
||||
write_stats(servers, players);
|
||||
}
|
14
src/services/statistics_handler.hpp
Normal file
14
src/services/statistics_handler.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "../service.hpp"
|
||||
|
||||
class statistics_handler : public service
|
||||
{
|
||||
public:
|
||||
statistics_handler(server& server);
|
||||
|
||||
void run_frame() override;
|
||||
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point last_print;
|
||||
};
|
Reference in New Issue
Block a user