diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 33307df9..333a3ebe 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -51,6 +51,7 @@ namespace Components void Loader::Uninitialize() { + std::reverse(Loader::Components.begin(), Loader::Components.end()); for (auto component : Loader::Components) { Logger::Print("Unregistering component: %s\n", component->GetName()); diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 8a580252..4d0d04e2 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -7,46 +7,50 @@ namespace Components void Node::LoadNodes() { - std::string nodes = Utils::ReadFile("nodes.txt"); - auto list = Utils::Explode(nodes, '\n'); + std::string nodes = Utils::ReadFile("players/nodes.dat"); - for (auto entry : list) + // Invalid + if (!nodes.size() || nodes.size() % 6) return; + + unsigned int size = (nodes.size() / sizeof(Node::AddressEntry)); + + Node::AddressEntry* addresses = reinterpret_cast(const_cast(nodes.data())); + for (unsigned int i = 0; i < size; ++i) { - Network::Address addr(entry); - Node::AddNode(addr); + Node::AddNode(addresses[i].toNetAddress()); } } - void Node::LoadDedis() - { - - } - void Node::StoreNodes() { - std::string nodes; + std::vector entries; - for (auto node : Node::Nodes) + for (auto entry : Node::Nodes) { - nodes.append(node.address.GetString()); - nodes.append("\n"); + Node::AddressEntry thisAddress; + thisAddress.fromNetAddress(entry.address); + + entries.push_back(thisAddress); } - Utils::WriteFile("nodes.txt", nodes); - } - - void Node::StoreDedis() - { + std::string nodeStream(reinterpret_cast(entries.data()), entries.size() * sizeof(Node::AddressEntry)); + CreateDirectoryW(L"players", NULL); + Utils::WriteFile("players/nodes.dat", nodeStream); } void Node::AddNode(Network::Address address, bool valid) { +#ifdef DEBUG + if (address.IsSelf()) return; +#else if (address.IsLocal() || address.IsSelf()) return; +#endif Node::NodeEntry entry; entry.startTime = 0; + entry.lastHeartbeat = 0; entry.endTime = (valid ? Game::Com_Milliseconds() : 0); entry.state = (valid ? Node::STATE_VALID : Node::STATE_UNKNOWN); entry.address = address; @@ -76,7 +80,7 @@ namespace Components } } - void Node::AddDedi(Network::Address address) + void Node::AddDedi(Network::Address address, bool dirty) { Node::DediEntry entry; @@ -87,10 +91,16 @@ namespace Components // Search if we already know that node bool duplicate = false; - for (auto ourEntry : Node::Nodes) + for (auto &ourEntry : Node::Dedis) { if (ourEntry.address == entry.address) { + if (dirty) + { + ourEntry.endTime = Game::Com_Milliseconds(); + ourEntry.state = Node::STATE_UNKNOWN; + } + duplicate = true; break; } @@ -159,6 +169,24 @@ namespace Components Network::SendRaw(target, packet); } + void Node::ValidateDedi(Network::Address address, Utils::InfoString info) + { + for (auto &dedi : Node::Dedis) + { + if (dedi.address == address) + { + dedi.state = (info.Get("challenge") == dedi.challenge ? Node::STATE_VALID : Node::STATE_INVALID); + dedi.endTime = Game::Com_Milliseconds(); + + if (dedi.state == Node::STATE_VALID) + { + Logger::Print("Validated dedi %s\n", address.GetString()); + } + break; + } + } + } + Node::Node() { //#ifdef USE_NODE_STUFF @@ -166,13 +194,22 @@ namespace Components Dvar::OnInit([] () { + Node::Dedis.clear(); Node::Nodes.clear(); Node::LoadNodes(); - - Node::Dedis.clear(); - Node::LoadDedis(); }); + if (Dedicated::IsDedicated()) + { + QuickPatch::OnShutdown([] () + { + for (auto node : Node::Nodes) + { + Network::Send(node.address, "heartbeatDeadline\n"); + } + }); + } + Network::Handle("nodeRequestLists", [] (Network::Address address, std::string data) { Logger::Print("Sending our lists to %s\n", address.GetString()); @@ -181,7 +218,7 @@ namespace Components //Node::AddNode(address, true); Node::SendNodeList(address); - //Node::SendDediList(address); + Node::SendDediList(address); }); Network::Handle("nodeNodeList", [] (Network::Address address, std::string data) @@ -228,17 +265,35 @@ namespace Components } }); + Network::Handle("heartbeat", [] (Network::Address address, std::string data) + { + Logger::Print("Received heartbeat from %s\n", address.GetString()); + Node::AddDedi(address, true); + }); + + Network::Handle("heartbeatDeadline", [] (Network::Address address, std::string data) + { + for (auto &dedi : Node::Dedis) + { + if (dedi.address == address) + { + Logger::Print("Dedi invalidation message received from %s\n", address.GetString()); + + dedi.state = Node::STATE_INVALID; + dedi.endTime = Game::Com_Milliseconds(); + } + } + }); + Dedicated::OnFrame([] () { + int heartbeatCount = 0; int count = 0; // Send requests for (auto &node : Node::Nodes) { - // Frame limit - if (count >= 1) break; // Query only 1 node per frame (-> 3 packets sent per frame) - - if (node.state == Node::STATE_UNKNOWN || (/*node.state != Node::STATE_INVALID && */node.state != Node::STATE_QUERYING && (Game::Com_Milliseconds() - node.endTime) > (1000 * 30))) + if (count < NODE_FRAME_QUERY_LIMIT && (node.state == Node::STATE_UNKNOWN || (/*node.state != Node::STATE_INVALID && */node.state != Node::STATE_QUERYING && (Game::Com_Milliseconds() - node.endTime) > (NODE_VALIDITY_EXPIRE)))) { count++; @@ -253,18 +308,60 @@ namespace Components // Send our lists Node::SendNodeList(node.address); - //Node::SendDediList(node.address); + Node::SendDediList(node.address); } - } - // Mark invalid nodes - for (auto &node : Node::Nodes) - { - if (node.state == Node::STATE_QUERYING && (Game::Com_Milliseconds() - node.startTime) > (1000 * 10)) + if (node.state == Node::STATE_QUERYING && (Game::Com_Milliseconds() - node.startTime) > (NODE_QUERY_TIMEOUT)) { node.state = Node::STATE_INVALID; node.endTime = Game::Com_Milliseconds(); } + + if (node.state == Node::STATE_VALID) + { + if (heartbeatCount < HEARTBEATS_FRAME_LIMIT && (!node.lastHeartbeat || (Game::Com_Milliseconds() - node.lastHeartbeat) > (HEARTBEAT_INTERVAL))) + { + heartbeatCount++; + + Logger::Print("Sending heartbeat to node %s...\n", node.address.GetString()); + node.lastHeartbeat = Game::Com_Milliseconds(); + Network::Send(node.address, "heartbeat\n"); + } + } + } + + count = 0; + + for (auto &dedi : Node::Dedis) + { + if (count < DEDI_FRAME_QUERY_LIMIT && (dedi.state == Node::STATE_UNKNOWN || (/*node.state != Node::STATE_INVALID && */dedi.state != Node::STATE_QUERYING && (Game::Com_Milliseconds() - dedi.endTime) > (DEDI_VALIDITY_EXPIRE)))) + { + count++; + + dedi.startTime = Game::Com_Milliseconds(); + dedi.endTime = 0; + dedi.challenge = Utils::VA("%d", dedi.startTime); + dedi.state = Node::STATE_QUERYING; + + Logger::Print("Verifying dedi %s...\n", dedi.address.GetString()); + + // Request new lists + Network::Send(dedi.address, Utils::VA("getinfo %s\n", dedi.challenge.data())); + } + + // No query response + if (dedi.state == Node::STATE_QUERYING && (Game::Com_Milliseconds() - dedi.startTime) > (DEDI_QUERY_TIMEOUT)) + { + dedi.state = Node::STATE_INVALID; + dedi.endTime = Game::Com_Milliseconds(); + } + + // Lack of heartbeats + if (dedi.state == Node::STATE_VALID && (Game::Com_Milliseconds() - dedi.startTime) > (HEARTBEAT_DEADLINE)) + { + Logger::Print("Invalidating dedi %s\n", dedi.address.GetString()); + dedi.state = Node::STATE_INVALID; + } } count = 0; @@ -280,6 +377,16 @@ namespace Components } }); + Command::Add("listdedis", [] (Command::Params params) + { + Logger::Print("Dedi: %d\n", Node::Dedis.size()); + + for (auto dedi : Node::Dedis) + { + Logger::Print("%s\n", dedi.address.GetString()); + } + }); + Command::Add("addnode", [](Command::Params params) { if (params.Length() < 2) return; @@ -293,8 +400,5 @@ namespace Components { Node::StoreNodes(); Node::Nodes.clear(); - - Node::StoreDedis(); - Node::Dedis.clear(); } } diff --git a/src/Components/Modules/Node.hpp b/src/Components/Modules/Node.hpp index e4733ab2..d9103b7a 100644 --- a/src/Components/Modules/Node.hpp +++ b/src/Components/Modules/Node.hpp @@ -1,3 +1,16 @@ +#define HEARTBEAT_DEADLINE 1000 * 3 * 10 // Invalidate servers after 10 minutes without heartbeat +#define HEARTBEAT_INTERVAL 1000 * 10 * 1 // Send heartbeats to each node every 3 minutes + +#define NODE_VALIDITY_EXPIRE 1000 * 60 * 2 // Revalidate nodes after 2 minutes +#define DEDI_VALIDITY_EXPIRE 1000 * 60 * 2 // Revalidate dedis after 2 minutes + +#define NODE_QUERY_TIMEOUT 1000 * 30 * 1 // Invalidate nodes after 30 seconds without query response +#define DEDI_QUERY_TIMEOUT 1000 * 10 * 1 // Invalidate dedis after 10 seconds without query response + +#define HEARTBEATS_FRAME_LIMIT 1 // Limit of heartbeats sent to nodes per frame +#define NODE_FRAME_QUERY_LIMIT 1 // Limit of nodes to be queried per frame +#define DEDI_FRAME_QUERY_LIMIT 1 // Limit of dedis to be queried per frame + namespace Components { class Node : public Component @@ -7,6 +20,8 @@ namespace Components ~Node(); const char* GetName() { return "Node"; }; + static void ValidateDedi(Network::Address address, Utils::InfoString info); + private: enum EntryState { @@ -22,11 +37,13 @@ namespace Components EntryState state; int startTime; int endTime; + int lastHeartbeat; }; struct DediEntry { Network::Address address; + std::string challenge; EntryState state; int startTime; int endTime; @@ -61,13 +78,10 @@ namespace Components static std::vector Dedis; static void LoadNodes(); - static void LoadDedis(); - static void StoreNodes(); - static void StoreDedis(); static void AddNode(Network::Address address, bool valid = false); - static void AddDedi(Network::Address address); + static void AddDedi(Network::Address address, bool dirty = false); static void SendNodeList(Network::Address target); static void SendDediList(Network::Address target); diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 382ea702..4244f91b 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -375,6 +375,7 @@ namespace Components } } + Node::ValidateDedi(address, info); ServerList::Insert(address, info); }); } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 8a822f5a..35e512ce 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -2,12 +2,29 @@ namespace Components { - __int64* QuickPatch::GetStatsID() + std::vector QuickPatch::ShutdownCallbacks; + + int64_t* QuickPatch::GetStatsID() { - static __int64 id = 0x110000100001337; + static int64_t id = 0x110000100001337; return &id; } + void QuickPatch::OnShutdown(QuickPatch::Callback callback) + { + QuickPatch::ShutdownCallbacks.push_back(callback); + } + + void QuickPatch::ShutdownStub(int channel, const char* message) + { + Game::Com_Printf(0, message); + + for (auto callback : QuickPatch::ShutdownCallbacks) + { + if (callback) callback(); + } + } + void QuickPatch::UnlockStats() { Command::Execute("setPlayerData prestige 10"); @@ -171,6 +188,8 @@ namespace Components Utils::Hook::Set(0x60BED2, "unskippablecinematic IW_logo\n"); Utils::Hook::Nop(0x60BEF6, 5); // Don't reset intro dvar + Utils::Hook(0x4D4007, QuickPatch::ShutdownStub, HOOK_CALL).Install()->Quick(); + // Rename stat file - TODO: beautify Utils::Hook::SetString(0x71C048, "iw4x.stat"); @@ -185,4 +204,9 @@ namespace Components QuickPatch::UnlockStats(); }); } + + QuickPatch::~QuickPatch() + { + QuickPatch::ShutdownCallbacks.clear(); + } } diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 98e44cad..b464b1ce 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -3,12 +3,19 @@ namespace Components class QuickPatch : public Component { public: + typedef void(*Callback)(); + QuickPatch(); + ~QuickPatch(); const char* GetName() { return "QuickPatch"; }; static void UnlockStats(); + static void OnShutdown(Callback callback); private: - static _int64* GetStatsID(); + static std::vector ShutdownCallbacks; + + static int64_t* GetStatsID(); + static void ShutdownStub(int channel, const char* message); }; } diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index e62bf029..8b476430 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -138,8 +138,12 @@ namespace Utils void WriteFile(std::string file, std::string data) { std::ofstream stream(file, std::ios::binary); - stream.write(data.data(), data.size()); - stream.close(); + + if (stream.is_open()) + { + stream.write(data.data(), data.size()); + stream.close(); + } } std::string ReadFile(std::string file)