#include "STDInclude.hpp" namespace Components { std::recursive_mutex Node::Mutex; std::vector Node::Nodes; bool Node::wasIngame = false; bool Node::Entry::isValid() { return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2)); } bool Node::Entry::isDead() { if (!this->lastResponse.has_value()) { if (this->lastRequest.has_value() && this->lastRequest->elapsed(NODE_HALFLIFE)) { return true; } } else if(this->lastResponse->elapsed(NODE_HALFLIFE * 2) && this->lastRequest.has_value() && this->lastRequest->after(*this->lastResponse)) { return true; } return false; } bool Node::Entry::requiresRequest() { return (!this->isDead() && (!this->lastRequest.has_value() || this->lastRequest->elapsed(NODE_HALFLIFE))); } void Node::Entry::sendRequest() { if (!this->lastRequest.has_value()) this->lastRequest.emplace(Utils::Time::Point()); this->lastRequest->update(); Session::Send(this->address, "nodeListRequest"); Node::SendList(this->address); NODE_LOG("Sent request to %s\n", this->address.getCString()); } void Node::Entry::reset() { // this->lastResponse.reset(); // This would invalidate the node, but maybe we don't want that? this->lastRequest.reset(); } json11::Json Node::Entry::to_json() const { return this->address.getString(); } void Node::LoadNodeRemotePreset() { std::string nodes = Utils::Cache::GetFile("/iw4/nodes.txt"); if (nodes.empty()) return; auto nodeList = Utils::String::Explode(nodes, '\n'); for (auto& node : nodeList) { Utils::String::Replace(node, "\r", ""); node = Utils::String::Trim(node); Node::Add(node); } } void Node::LoadNodePreset() { Proto::Node::List list; if (Monitor::IsEnabled()) { std::string nodes = Utils::IO::ReadFile("players/nodes_default.dat"); if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; } else { FileSystem::File defaultNodes("nodes_default.dat"); if (!defaultNodes.exists() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(defaultNodes.getBuffer()))) return; } for (int i = 0; i < list.nodes_size(); ++i) { const std::string& addr = list.nodes(i); if (addr.size() == sizeof(sockaddr)) { Node::Add(reinterpret_cast(const_cast(addr.data()))); } } } void Node::LoadNodes() { Proto::Node::List list; std::string nodes = Utils::IO::ReadFile("players/nodes.dat"); if (nodes.empty() || !list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes))) return; for (int i = 0; i < list.nodes_size(); ++i) { const std::string& addr = list.nodes(i); if (addr.size() == sizeof(sockaddr)) { Node::Add(reinterpret_cast(const_cast(addr.data()))); } } } void Node::StoreNodes(bool force) { if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; static Utils::Time::Interval interval; if (!force && !interval.elapsed(1min)) return; interval.update(); Proto::Node::List list; Node::Mutex.lock(); for (auto& node : Node::Nodes) { if (node.isValid()) { std::string* str = list.add_nodes(); sockaddr addr = node.address.getSockAddr(); str->append(reinterpret_cast(&addr), sizeof(addr)); } } Node::Mutex.unlock(); Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString())); } void Node::Add(Network::Address address) { #ifndef DEBUG if (address.isLocal() || address.isSelf()) return; #endif if (!address.isValid()) return; std::lock_guard _(Node::Mutex); for (auto& session : Node::Nodes) { if (session.address == address) return; } Node::Entry node; node.address = address; Node::Nodes.push_back(node); } std::vector Node::GetNodes() { std::lock_guard _(Node::Mutex); return Node::Nodes; } void Node::RunFrame() { if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; if (!Dedicated::IsEnabled() && Game::CL_IsCgameInitialized()) { wasIngame = true; return; // don't run while ingame because it can still cause lag spikes on lower end PCs } if (wasIngame) // our last frame we were ingame and now we aren't so touch all nodes { for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();++i) { // clearing the last request and response times makes the // dispatcher think its a new node and will force a refresh i->lastRequest.reset(); i->lastResponse.reset(); } wasIngame = false; } static Utils::Time::Interval frameLimit; int interval = static_cast(1000.0f / Dvar::Var("net_serverFrames").get()); if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; frameLimit.update(); std::lock_guard _(Node::Mutex); Dvar::Var queryLimit("net_serverQueryLimit"); int sentRequests = 0; for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();) { if (i->isDead()) { i = Node::Nodes.erase(i); continue; } else if (sentRequests < queryLimit.get() && i->requiresRequest()) { ++sentRequests; i->sendRequest(); } ++i; } } void Node::Synchronize() { std::lock_guard _(Node::Mutex); for (auto& node : Node::Nodes) { //if (node.isValid()) // Comment out to simulate 'syncnodes' behaviour { node.reset(); } } } void Node::HandleResponse(Network::Address address, const std::string& data) { Proto::Node::List list; if (!list.ParseFromString(data)) return; NODE_LOG("Received response from %s\n", address.getCString()); std::lock_guard _(Node::Mutex); for (int i = 0; i < list.nodes_size(); ++i) { const std::string& addr = list.nodes(i); if (addr.size() == sizeof(sockaddr)) { Node::Add(reinterpret_cast(const_cast(addr.data()))); } } if (list.isnode() && (!list.port() || list.port() == address.getPort())) { if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && list.protocol() == PROTOCOL) { NODE_LOG("Inserting %s into the serverlist\n", address.getCString()); ServerList::InsertRequest(address); } else { NODE_LOG("Dropping serverlist insertion for %s\n", address.getCString()); } for (auto& node : Node::Nodes) { if (address == node.address) { if (!node.lastResponse.has_value()) node.lastResponse.emplace(Utils::Time::Point()); node.lastResponse->update(); node.data.protocol = list.protocol(); return; } } Node::Entry entry; entry.address = address; entry.data.protocol = list.protocol(); entry.lastResponse.emplace(Utils::Time::Point()); Node::Nodes.push_back(entry); } } void Node::SendList(Network::Address address) { Proto::Node::List list; list.set_isnode(Dedicated::IsEnabled()); list.set_protocol(PROTOCOL); list.set_port(Node::GetPort()); std::lock_guard _(Node::Mutex); for (auto& node : Node::Nodes) { if (node.isValid()) { std::string* str = list.add_nodes(); sockaddr addr = node.address.getSockAddr(); str->append(reinterpret_cast(&addr), sizeof(addr)); } } Session::Send(address, "nodeListResponse", list.SerializeAsString()); } unsigned short Node::GetPort() { if (Dvar::Var("net_natFix").get()) return 0; return Network::GetPort(); } Node::Node() { if (ZoneBuilder::IsEnabled()) return; Dvar::Register("net_natFix", false, 0, "Fix node registration for certain firewalls/routers"); Scheduler::OnFrameAsync([]() { Node::StoreNodes(false); }); Scheduler::OnFrame(Node::RunFrame); Session::Handle("nodeListResponse", Node::HandleResponse); Session::Handle("nodeListRequest", [](Network::Address address, const std::string&) { Node::SendList(address); }); // Load stored nodes auto loadNodes = []() { Node::LoadNodePreset(); Node::LoadNodes(); }; if (Monitor::IsEnabled()) Network::OnStart(loadNodes); else Dvar::OnInit(loadNodes); Network::OnStart([]() { std::thread([]() { Node::LoadNodeRemotePreset(); }).detach(); }); Command::Add("listnodes", [](Command::Params*) { Logger::Print("Nodes: %d\n", Node::Nodes.size()); std::lock_guard _(Node::Mutex); for (auto& node : Node::Nodes) { Logger::Print("%s\t(%s)\n", node.address.getCString(), node.isValid() ? "Valid" : "Invalid"); } }); Command::Add("addnode", [](Command::Params* params) { if (params->length() < 2) return; Node::Add({ params->get(1) }); }); } Node::~Node() { std::lock_guard _(Node::Mutex); Node::StoreNodes(true); Node::Nodes.clear(); } }