Merge branch 'diamante_develop' into develop

# Conflicts:
#	.gitmodules
This commit is contained in:
Louvenarde
2023-06-22 19:23:13 +02:00
33 changed files with 472 additions and 650 deletions

View File

@ -371,7 +371,7 @@ namespace Components
MutedList.access([&](muteList& clients)
{
const nlohmann::json::array_t arr = list;
for (auto& entry : arr)
for (const auto& entry : arr)
{
if (entry.is_number_unsigned())
{

View File

@ -243,7 +243,6 @@ namespace Components
// Intercept time wrapping
Utils::Hook(0x62737D, TimeWrapStub, HOOK_CALL).install()->quick();
//Utils::Hook::Set<DWORD>(0x62735C, 50'000); // Time wrap after 50 seconds (for testing - i don't want to wait 3 weeks)
if (!ZoneBuilder::IsEnabled())
{

View File

@ -5,6 +5,7 @@
#include "Download.hpp"
#include "Events.hpp"
#include "MapRotation.hpp"
#include "Node.hpp"
#include "Party.hpp"
#include "ServerInfo.hpp"
@ -40,7 +41,7 @@ namespace Components
void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map)
{
if (CLDownload.running) return;
if (CLDownload.running_) return;
Scheduler::Once([]
{
@ -61,26 +62,26 @@ namespace Components
return;
}
CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
CLDownload.hashedPassword_ = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
}
CLDownload.running = true;
CLDownload.isMap = map;
CLDownload.mod = mod;
CLDownload.terminateThread = false;
CLDownload.totalBytes = 0;
CLDownload.lastTimeStamp = 0;
CLDownload.downBytes = 0;
CLDownload.timeStampBytes = 0;
CLDownload.isPrivate = needPassword;
CLDownload.target = Party::Target();
CLDownload.thread = std::thread(ModDownloader, &CLDownload);
CLDownload.running_ = true;
CLDownload.isMap_ = map;
CLDownload.mod_ = mod;
CLDownload.terminateThread_ = false;
CLDownload.totalBytes_ = 0;
CLDownload.lastTimeStamp_ = 0;
CLDownload.downBytes_ = 0;
CLDownload.timeStampBytes_ = 0;
CLDownload.isPrivate_ = needPassword;
CLDownload.target_ = Party::Target();
CLDownload.thread_ = std::thread(ModDownloader, &CLDownload);
}
bool Download::ParseModList(ClientDownload* download, const std::string& list)
{
if (!download) return false;
download->files.clear();
download->files_.clear();
nlohmann::json listData;
try
@ -98,7 +99,7 @@ namespace Components
return false;
}
download->totalBytes = 0;
download->totalBytes_ = 0;
const nlohmann::json::array_t listDataArray = listData;
for (auto& file : listDataArray)
@ -118,8 +119,8 @@ namespace Components
if (!fileEntry.name.empty())
{
download->files.push_back(fileEntry);
download->totalBytes += fileEntry.size;
download->files_.push_back(fileEntry);
download->totalBytes_ += fileEntry.size;
}
}
catch (const nlohmann::json::exception& ex)
@ -134,12 +135,12 @@ namespace Components
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
{
if (!download || download->files.size() <= index) return false;
if (!download || download->files_.size() <= index) return false;
auto file = download->files[index];
auto file = download->files_[index];
auto path = download->mod + "/" + file.name;
if (download->isMap)
auto path = download->mod_ + "/" + file.name;
if (download->isMap_)
{
path = "usermaps/" + path;
}
@ -149,16 +150,16 @@ namespace Components
auto data = Utils::IO::ReadFile(path);
if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash)
{
download->totalBytes += file.size;
download->totalBytes_ += file.size;
return true;
}
}
auto host = "http://" + download->target.getString();
auto host = "http://" + download->target_.getString();
auto fastHost = SV_wwwBaseUrl.get<std::string>();
if (Utils::String::StartsWith(fastHost, "https://"))
{
download->thread.detach();
download->thread_.detach();
download->clear();
Scheduler::Once([]
@ -197,8 +198,8 @@ namespace Components
}
else
{
url = host + "/file/" + (download->isMap ? "map/" : "") + file.name
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
url = host + "/file/" + (download->isMap_ ? "map/" : "") + file.name
+ (download->isPrivate_ ? ("?password=" + download->hashedPassword_) : "");
}
Logger::Print("Downloading from url {}\n", url);
@ -212,14 +213,14 @@ namespace Components
Utils::String::Replace(url, " ", "%20");
download->valid = true;
download->valid_ = true;
fDownload.downloading = true;
Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](std::size_t bytes, std::size_t)
{
if(!fDownload.downloading || fDownload.download->terminateThread)
if(!fDownload.downloading || fDownload.download->terminateThread_)
{
webIO.cancelDownload();
return;
@ -234,14 +235,14 @@ namespace Components
fDownload.downloading = false;
download->valid = false;
download->valid_ = false;
if (fDownload.buffer.size() != file.size || Utils::Cryptography::SHA256::Compute(fDownload.buffer, true) != file.hash)
{
return false;
}
if (download->isMap) Utils::IO::CreateDir("usermaps/" + download->mod);
if (download->isMap_) Utils::IO::CreateDir("usermaps/" + download->mod_);
Utils::IO::WriteFile(path, fDownload.buffer);
return true;
@ -251,16 +252,16 @@ namespace Components
{
if (!download) download = &CLDownload;
const auto host = "http://" + download->target.getString();
const auto host = "http://" + download->target_.getString();
const auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
const auto listUrl = host + (download->isMap_ ? "/map" : "/list") + (download->isPrivate_ ? ("?password=" + download->hashedPassword_) : "");
const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
if (list.empty())
{
if (download->terminateThread) return;
if (download->terminateThread_) return;
download->thread.detach();
download->thread_.detach();
download->clear();
Scheduler::Once([]
@ -272,13 +273,13 @@ namespace Components
return;
}
if (download->terminateThread) return;
if (download->terminateThread_) return;
if (!ParseModList(download, list))
{
if (download->terminateThread) return;
if (download->terminateThread_) return;
download->thread.detach();
download->thread_.detach();
download->clear();
Scheduler::Once([]
@ -290,21 +291,21 @@ namespace Components
return;
}
if (download->terminateThread) return;
if (download->terminateThread_) return;
static std::string mod;
mod = download->mod;
mod = download->mod_;
for (std::size_t i = 0; i < download->files.size(); ++i)
for (std::size_t i = 0; i < download->files_.size(); ++i)
{
if (download->terminateThread) return;
if (download->terminateThread_) return;
if (!DownloadFile(download, i))
{
if (download->terminateThread) return;
if (download->terminateThread_) return;
mod = std::format("Failed to download file: {}!", download->files[i].name);
download->thread.detach();
mod = std::format("Failed to download file: {}!", download->files_[i].name);
download->thread_.detach();
download->clear();
Scheduler::Once([]
@ -320,12 +321,12 @@ namespace Components
}
}
if (download->terminateThread) return;
if (download->terminateThread_) return;
download->thread.detach();
download->thread_.detach();
download->clear();
if (download->isMap)
if (download->isMap_)
{
Scheduler::Once([]
{
@ -357,22 +358,22 @@ namespace Components
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
{
fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes;
fDownload->download->timeStampBytes += bytes;
fDownload->download->downBytes_ += bytes;
fDownload->download->timeStampBytes_ += bytes;
static volatile bool framePushed = false;
if (!framePushed)
{
double progress = 0;
if (fDownload->download->totalBytes)
if (fDownload->download->totalBytes_)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
progress = (100.0 / fDownload->download->totalBytes_) * fDownload->download->downBytes_;
}
static std::uint32_t dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlSize = fDownload->download->files_.size();
dlProgress = static_cast<std::uint32_t>(progress);
framePushed = true;
@ -383,18 +384,18 @@ namespace Components
}, Scheduler::Pipeline::MAIN);
}
auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp_;
if (delta > 300)
{
const auto doFormat = fDownload->download->lastTimeStamp != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
const auto doFormat = fDownload->download->lastTimeStamp_ != 0;
fDownload->download->lastTimeStamp_ = Game::Sys_Milliseconds();
const auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
const auto dataLeft = fDownload->download->totalBytes_ - fDownload->download->downBytes_;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
if (fDownload->download->timeStampBytes_)
{
const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
const double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes_) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
@ -404,7 +405,7 @@ namespace Components
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
dlTsBytes = fDownload->download->timeStampBytes_;
Scheduler::Once([]
{
@ -413,7 +414,7 @@ namespace Components
}, Scheduler::Pipeline::MAIN);
}
fDownload->download->timeStampBytes = 0;
fDownload->download->timeStampBytes_ = 0;
}
}
@ -434,7 +435,7 @@ namespace Components
MongooseLogBuffer.push_back(c);
}
static std::string InfoHandler()
static std::optional<std::string> InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
const auto status = ServerInfo::GetInfo();
const auto host = ServerInfo::GetHostInfo();
@ -480,10 +481,12 @@ namespace Components
}
info["players"] = players;
return nlohmann::json(info).dump();
std::string out = nlohmann::json(info).dump();
return { out };
}
static std::string ListHandler()
static std::optional<std::string> ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
static nlohmann::json jsonList;
static std::filesystem::path fsGamePre;
@ -526,10 +529,12 @@ namespace Components
jsonList = fileList;
}
return jsonList.dump();
std::string out = jsonList.dump();
return { out };
}
static std::string MapHandler()
static std::optional<std::string> MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
static std::string mapNamePre;
static nlohmann::json jsonList;
@ -570,10 +575,12 @@ namespace Components
jsonList = fileList;
}
return jsonList.dump();
std::string out = jsonList.dump();
return { out };
}
static void FileHandler(mg_connection* c, const mg_http_message* hm)
static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm)
{
std::string url(hm->uri.ptr, hm->uri.len);
@ -602,7 +609,7 @@ namespace Components
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
{
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
return;
return {};
}
url = std::format("usermaps\\{}\\{}", mapName, url);
@ -612,7 +619,7 @@ namespace Components
if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos)
{
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
return;
return {};
}
}
@ -633,10 +640,44 @@ namespace Components
mg_printf(c, "%s", "\r\n");
mg_send(c, file.data(), file.size());
}
return {};
}
static std::optional<std::string> ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
std::vector<std::string> servers;
const auto nodes = Node::GetNodes();
for (const auto& node : nodes)
{
const auto address = node.address.getString();
servers.emplace_back(address);
}
nlohmann::json jsonList = servers;
std::string out = jsonList.dump();
return { out };
}
static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data)
{
using callback = std::function<std::optional<std::string>(mg_connection*, const mg_http_message*)>;
static const auto handlers = []() -> std::unordered_map<std::string, callback>
{
std::unordered_map<std::string, callback> f;
f["/file"] = FileHandler;
f["/info"] = InfoHandler;
f["/list"] = ListHandler;
f["/map"] = MapHandler;
f["/serverlist"] = ServerListHandler;
return f;
}();
if (ev != MG_EV_HTTP_MSG)
{
return;
@ -645,26 +686,24 @@ namespace Components
auto* hm = static_cast<mg_http_message*>(ev_data);
const std::string url(hm->uri.ptr, hm->uri.len);
if (url.starts_with("/info"))
auto handled = false;
for (auto i = handlers.begin(); i != handlers.end();)
{
const auto reply = InfoHandler();
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
if (url.starts_with(i->first))
{
if (const auto reply = i->second(c, hm))
{
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.value().data());
}
handled = true;
break;
}
++i;
}
else if (url.starts_with("/list"))
{
const auto reply = ListHandler();
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
}
else if (url.starts_with("/map"))
{
const auto reply = MapHandler();
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
}
else if (url.starts_with("/file"))
{
FileHandler(c, hm);
}
else
if (!handled)
{
mg_http_serve_opts opts = { .root_dir = "iw4x/html" }; // Serve local dir
mg_http_serve_dir(c, hm, &opts);

View File

@ -24,24 +24,24 @@ namespace Components
class ClientDownload
{
public:
ClientDownload(bool _isMap = false) : running(false), valid(false), terminateThread(false), isMap(_isMap), totalBytes(0), downBytes(0), lastTimeStamp(0), timeStampBytes(0) {}
ClientDownload(bool isMap = false) : running_(false), valid_(false), terminateThread_(false), isMap_(isMap), totalBytes_(0), downBytes_(0), lastTimeStamp_(0), timeStampBytes_(0) {}
~ClientDownload() { this->clear(); }
bool running;
bool valid;
bool terminateThread;
bool isMap;
bool isPrivate;
Network::Address target;
std::string hashedPassword;
std::string mod;
std::thread thread;
bool running_;
bool valid_;
bool terminateThread_;
bool isMap_;
bool isPrivate_;
Network::Address target_;
std::string hashedPassword_;
std::string mod_;
std::thread thread_;
std::size_t totalBytes;
std::size_t downBytes;
std::size_t totalBytes_;
std::size_t downBytes_;
int lastTimeStamp;
std::size_t timeStampBytes;
int lastTimeStamp_;
std::size_t timeStampBytes_;
class File
{
@ -51,24 +51,24 @@ namespace Components
std::size_t size;
};
std::vector<File> files;
std::vector<File> files_;
void clear()
{
this->terminateThread = true;
this->terminateThread_ = true;
if (this->thread.joinable())
if (this->thread_.joinable())
{
this->thread.join();
this->thread_.join();
}
this->running = false;
this->mod.clear();
this->files.clear();
this->running_ = false;
this->mod_.clear();
this->files_.clear();
if (this->valid)
if (this->valid_)
{
this->valid = false;
this->valid_ = false;
}
}
};

View File

@ -767,16 +767,7 @@ namespace Components
UIScript::Add("downloadDLC", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
int dlc = token.get<int>();
for (const auto& pack : Maps::DlcPacks)
{
if (pack.index == dlc)
{
ShellExecuteW(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW);
return;
}
}
const auto dlc = token.get<int>();
Game::ShowMessageBox(Utils::String::VA("DLC %d does not exist!", dlc), "ERROR");
});

View File

@ -14,7 +14,6 @@ namespace Components
static std::thread Thread;
static bool Terminate;
static bool DownloadUpdater();
static const char* GetNewsText();
};

View File

@ -15,6 +15,8 @@ namespace Components
bool Node::WasIngame = false;
const Game::dvar_t* Node::net_natFix;
bool Node::Entry::isValid() const
{
return (this->lastResponse.has_value() && !this->lastResponse->elapsed(NODE_HALFLIFE * 2));
@ -48,7 +50,7 @@ namespace Components
this->lastRequest->update();
Session::Send(this->address, "nodeListRequest");
Node::SendList(this->address);
SendList(this->address);
#ifdef NODE_SYSTEM_DEBUG
Logger::Debug("Sent request to {}", this->address.getString());
#endif
@ -56,7 +58,6 @@ namespace Components
void Node::Entry::reset()
{
// this->lastResponse.reset(); // This would invalidate the node, but maybe we don't want that?
this->lastRequest.reset();
}
@ -69,28 +70,54 @@ namespace Components
for (auto i = 0; i < list.nodes_size(); ++i)
{
const std::string& addr = list.nodes(i);
const auto& addr = list.nodes(i);
if (addr.size() == sizeof(sockaddr))
{
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
Add(reinterpret_cast<sockaddr*>(const_cast<char*>(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)
std::string data;
if (!Utils::IO::ReadFile("players/nodes.json", &data) || data.empty())
{
const std::string& addr = list.nodes(i);
return;
}
if (addr.size() == sizeof(sockaddr))
nlohmann::json nodes;
try
{
nodes = nlohmann::json::parse(data);
}
catch (const std::exception& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "JSON Parse Error: {}\n", ex.what());
return;
}
if (!nodes.contains("nodes"))
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "nodes.json contains invalid data\n");
return;
}
const auto& list = nodes["nodes"];
if (!list.is_array())
{
return;
}
const nlohmann::json::array_t arr = list;
Logger::Print("Parsing {} nodes from nodes.json\n", arr.size());
for (const auto& entry : arr)
{
if (entry.is_string())
{
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
Network::Address address(entry.get<std::string>());
Add(address);
}
}
}
@ -99,29 +126,32 @@ namespace Components
{
if (Dedicated::IsEnabled() && Dedicated::SVLanOnly.get<bool>()) return;
std::vector<std::string> nodes;
static Utils::Time::Interval interval;
if (!force && !interval.elapsed(1min)) return;
interval.update();
Proto::Node::List list;
Mutex.lock();
Node::Mutex.lock();
for (auto& node : Node::Nodes)
for (auto& node : Nodes)
{
if (node.isValid())
if (node.isValid() || force)
{
std::string* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
const auto address = node.address.getString();
nodes.emplace_back(address);
}
}
Node::Mutex.unlock();
Utils::IO::WriteFile("players/nodes.dat", Utils::Compression::ZLib::Compress(list.SerializeAsString()));
Mutex.unlock();
nlohmann::json out;
out["nodes"] = nodes;
Utils::IO::WriteFile("players/nodes.json", out.dump());
}
void Node::Add(Network::Address address)
void Node::Add(const Network::Address& address)
{
#ifndef DEBUG
if (address.isLocal() || address.isSelf()) return;
@ -129,23 +159,23 @@ namespace Components
if (!address.isValid()) return;
std::lock_guard _(Node::Mutex);
for (auto& session : Node::Nodes)
std::lock_guard _(Mutex);
for (auto& session : Nodes)
{
if (session.address == address) return;
}
Node::Entry node;
Entry node;
node.address = address;
Node::Nodes.push_back(node);
Nodes.push_back(node);
}
std::vector<Node::Entry> Node::GetNodes()
{
std::lock_guard _(Node::Mutex);
std::lock_guard _(Mutex);
return Node::Nodes;
return Nodes;
}
void Node::RunFrame()
@ -165,34 +195,34 @@ namespace Components
if (WasIngame) // our last frame we were in-game and now we aren't so touch all nodes
{
for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();++i)
for (auto& entry : Nodes)
{
// 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();
entry.lastRequest.reset();
entry.lastResponse.reset();
}
WasIngame = false;
}
static Utils::Time::Interval frameLimit;
int interval = static_cast<int>(1000.0f / Dvar::Var("net_serverFrames").get<int>());
const auto interval = 1000 / ServerList::NETServerFrames.get<int>();
if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return;
frameLimit.update();
std::lock_guard _(Node::Mutex);
Dvar::Var queryLimit("net_serverQueryLimit");
std::lock_guard _(Mutex);
int sentRequests = 0;
for (auto i = Node::Nodes.begin(); i != Node::Nodes.end();)
for (auto i = Nodes.begin(); i != Nodes.end();)
{
if (i->isDead())
{
i = Node::Nodes.erase(i);
i = Nodes.erase(i);
continue;
}
if (sentRequests < queryLimit.get<int>() && i->requiresRequest())
if (sentRequests < ServerList::NETServerQueryLimit.get<int>() && i->requiresRequest())
{
++sentRequests;
i->sendRequest();
@ -204,17 +234,16 @@ namespace Components
void Node::Synchronize()
{
std::lock_guard _(Node::Mutex);
for (auto& node : Node::Nodes)
std::lock_guard _(Mutex);
for (auto& node : Nodes)
{
//if (node.isValid()) // Comment out to simulate 'syncnodes' behaviour
{
node.reset();
}
}
}
void Node::HandleResponse(Network::Address address, const std::string& data)
void Node::HandleResponse(const Network::Address& address, const std::string& data)
{
Proto::Node::List list;
if (!list.ParseFromString(data)) return;
@ -223,7 +252,7 @@ namespace Components
Logger::Debug("Received response from {}", address.getString());
#endif
std::lock_guard _(Node::Mutex);
std::lock_guard _(Mutex);
for (int i = 0; i < list.nodes_size(); ++i)
{
@ -231,7 +260,7 @@ namespace Components
if (addr.size() == sizeof(sockaddr))
{
Node::Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
Add(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
}
}
@ -251,7 +280,7 @@ namespace Components
#endif
}
for (auto& node : Node::Nodes)
for (auto& node : Nodes)
{
if (address == node.address)
{
@ -263,39 +292,41 @@ namespace Components
}
}
Node::Entry entry;
Entry entry;
entry.address = address;
entry.data.protocol = list.protocol();
entry.lastResponse.emplace(Utils::Time::Point());
Node::Nodes.push_back(entry);
Nodes.push_back(entry);
}
}
void Node::SendList(const Network::Address& address)
{
std::lock_guard _(Node::Mutex);
std::lock_guard _(Mutex);
// need to keep the message size below 1404 bytes else recipient will just drop it
std::vector<std::string> nodeListReponseMessages;
for (std::size_t curNode = 0; curNode < Node::Nodes.size();)
for (std::size_t curNode = 0; curNode < Nodes.size();)
{
Proto::Node::List list;
list.set_isnode(Dedicated::IsEnabled());
list.set_protocol(PROTOCOL);
list.set_port(Node::GetPort());
list.set_port(GetPort());
for (std::size_t i = 0; i < NODE_MAX_NODES_TO_SEND;)
{
if (curNode >= Node::Nodes.size())
if (curNode >= Nodes.size())
{
break;
}
auto node = Node::Nodes.at(curNode++);
auto& node = Nodes.at(curNode++);
if (node.isValid())
{
std::string* str = list.add_nodes();
auto* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
@ -320,65 +351,100 @@ namespace Components
}
}
unsigned short Node::GetPort()
std::uint16_t Node::GetPort()
{
if (Dvar::Var("net_natFix").get<bool>()) return 0;
if (net_natFix->current.enabled) return 0;
return Network::GetPort();
}
void Node::Migrate()
{
Proto::Node::List list;
std::string nodes;
if (!Utils::IO::ReadFile("players/nodes.dat", &nodes) || nodes.empty())
{
return;
}
if (!list.ParseFromString(Utils::Compression::ZLib::Decompress(nodes)))
{
return;
}
std::vector<std::string> data;
for (auto i = 0; i < list.nodes_size(); ++i)
{
const std::string& addr = list.nodes(i);
if (addr.size() == sizeof(sockaddr))
{
Network::Address address(reinterpret_cast<sockaddr*>(const_cast<char*>(addr.data())));
data.emplace_back(address.getString());
}
}
nlohmann::json out;
out["nodes"] = data;
if (!Utils::IO::FileExists("players/nodes.json"))
{
Utils::IO::WriteFile("players/nodes.json", out.dump());
}
Utils::IO::RemoveFile("players/nodes.dat");
}
Node::Node()
{
if (ZoneBuilder::IsEnabled()) return;
Dvar::Register<bool>("net_natFix", false, 0, "Fix node registration for certain firewalls/routers");
net_natFix = Game::Dvar_RegisterBool("net_natFix", false, 0, "Fix node registration for certain firewalls/routers");
Scheduler::Loop([]
{
Node::StoreNodes(false);
}, Scheduler::Pipeline::ASYNC);
StoreNodes(false);
}, Scheduler::Pipeline::ASYNC, 5min);
Scheduler::Loop(Node::RunFrame, Scheduler::Pipeline::MAIN);
Scheduler::Loop(RunFrame, Scheduler::Pipeline::MAIN);
Session::Handle("nodeListResponse", Node::HandleResponse);
Session::Handle("nodeListResponse", HandleResponse);
Session::Handle("nodeListRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{
Node::SendList(address);
SendList(address);
});
// Load stored nodes
auto loadNodes = []
Scheduler::OnGameInitialized([]
{
Node::LoadNodePreset();
Node::LoadNodes();
};
Migrate();
LoadNodePreset();
LoadNodes();
}, Scheduler::Pipeline::MAIN);
Scheduler::OnGameInitialized(loadNodes, Scheduler::Pipeline::MAIN);
Command::Add("listnodes", [](const Command::Params*)
Command::Add("listNodes", [](const Command::Params*)
{
Logger::Print("Nodes: {}\n", Node::Nodes.size());
Logger::Print("Nodes: {}\n", Nodes.size());
std::lock_guard _(Node::Mutex);
for (auto& node : Node::Nodes)
std::lock_guard _(Mutex);
for (const auto& node : Nodes)
{
Logger::Print("{}\t({})\n", node.address.getString(), node.isValid() ? "Valid" : "Invalid");
}
});
Command::Add("addnode", [](const Command::Params* params)
Command::Add("addNode", [](const Command::Params* params)
{
if (params->size() < 2) return;
auto address = Network::Address{ params->get(1) };
if (address.isValid())
{
Node::Add(address);
Add(address);
}
});
}
Node::~Node()
void Node::preDestroy()
{
std::lock_guard _(Node::Mutex);
Node::StoreNodes(true);
Node::Nodes.clear();
std::lock_guard _(Mutex);
StoreNodes(true);
Nodes.clear();
}
}

View File

@ -1,6 +1,6 @@
#pragma once
#define NODE_HALFLIFE (3 * 60 * 1000) //3min
#define NODE_HALFLIFE (3 * 60 * 1000) // 3min
#define NODE_MAX_NODES_TO_SEND 64
#define NODE_SEND_RATE 500ms
@ -12,7 +12,7 @@ namespace Components
class Data
{
public:
uint64_t protocol;
std::uint64_t protocol;
};
class Entry
@ -34,9 +34,9 @@ namespace Components
};
Node();
~Node();
void preDestroy() override;
static void Add(Network::Address address);
static void Add(const Network::Address& address);
static std::vector<Entry> GetNodes();
static void RunFrame();
static void Synchronize();
@ -46,7 +46,9 @@ namespace Components
static std::vector<Entry> Nodes;
static bool WasIngame;
static void HandleResponse(Network::Address address, const std::string& data);
static const Game::dvar_t* net_natFix;
static void HandleResponse(const Network::Address& address, const std::string& data);
static void SendList(const Network::Address& address);
@ -54,6 +56,8 @@ namespace Components
static void LoadNodes();
static void StoreNodes(bool force);
static unsigned short GetPort();
static std::uint16_t GetPort();
static void Migrate();
};
}

View File

@ -245,10 +245,10 @@ namespace Components
}
auto workingDir = std::filesystem::current_path().string();
auto binary = FileSystem::GetAppdataPath() / "data" / "iw4x" / *Game::sys_exitCmdLine;
const std::string binary = *Game::sys_exitCmdLine;
SetEnvironmentVariableA("XLABS_MW2_INSTALL", workingDir.data());
Utils::Library::LaunchProcess(binary.string(), "-singleplayer", workingDir);
SetEnvironmentVariableA("MW2_INSTALL", workingDir.data());
Utils::Library::LaunchProcess(binary, "-singleplayer", workingDir);
}
__declspec(naked) void QuickPatch::SND_GetAliasOffset_Stub()
@ -320,7 +320,7 @@ namespace Components
Utils::Hook::Set<void(*)(Game::XAssetHeader, void*)>(0x51FCDD, QuickPatch::R_AddImageToList_Hk);
Utils::Hook::Set<const char*>(0x41DB8C, "iw4x-sp.exe");
Utils::Hook::Set<const char*>(0x41DB8C, "iw4-sp.exe");
Utils::Hook(0x4D6989, QuickPatch::Sys_SpawnQuitProcess_Hk, HOOK_CALL).install()->quick();
// Fix crash as nullptr goes unchecked

View File

@ -127,10 +127,10 @@ namespace Components
{
Utils::InfoString info;
info.set("admin", Dvar::Var("_Admin").get<const char*>());
info.set("website", Dvar::Var("_Website").get<const char*>());
info.set("email", Dvar::Var("_Email").get<const char*>());
info.set("location", Dvar::Var("_Location").get<const char*>());
info.set("admin", Dvar::Var("_Admin").get<std::string>());
info.set("website", Dvar::Var("_Website").get<std::string>());
info.set("email", Dvar::Var("_Email").get<std::string>());
info.set("location", Dvar::Var("_Location").get<std::string>());
return info;
}

View File

@ -54,6 +54,11 @@ namespace Components
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
static bool UseMasterServer;
static Dvar::Var UIServerSelected;
static Dvar::Var UIServerSelectedMap;
static Dvar::Var NETServerQueryLimit;
static Dvar::Var NETServerFrames;
private:
enum class Column : int
{
@ -150,11 +155,6 @@ namespace Components
static std::vector<unsigned int> VisibleList;
static Dvar::Var UIServerSelected;
static Dvar::Var UIServerSelectedMap;
static Dvar::Var NETServerQueryLimit;
static Dvar::Var NETServerFrames;
static bool IsServerListOpen();
};
}

View File

@ -149,159 +149,10 @@ namespace Components
{
if (Dedicated::IsEnabled()) return;
// Do not execute this when building zones
if (!ZoneBuilder::IsEnabled())
{
// Correctly upgrade stats
Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick();
// Correctly upgrade stats
Utils::Hook(0x42F088, StructuredData::UpdateVersionOffsets, HOOK_CALL).install()->quick();
// 15 or more custom classes
Utils::Hook::Set<BYTE>(0x60A2FE, NUM_CUSTOM_CLASSES);
return;
}
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& filename, bool* /*restrict*/)
{
// Only intercept playerdatadef loading
if (type != Game::ASSET_TYPE_STRUCTURED_DATA_DEF || filename != "mp/playerdata.def") return;
// Store asset
Game::StructuredDataDefSet* data = asset.structuredDataDefSet;
if (!data) return;
if (data->defCount != 1)
{
Logger::Error(Game::ERR_FATAL, "PlayerDataDefSet contains more than 1 definition!");
return;
}
if (data->defs[0].version != 155)
{
Logger::Error(Game::ERR_FATAL, "Initial PlayerDataDef is not version 155, patching not possible!");
return;
}
std::unordered_map<int, std::vector<std::vector<std::string>>> patchDefinitions;
std::unordered_map<int, std::unordered_map<std::string, std::string>> otherPatchDefinitions;
// First check if all versions are present
for (int i = 156;; ++i)
{
// We're on DB thread (OnLoad) so use DB thread for FS
FileSystem::File definition(std::format("{}/{}.json", filename, i), Game::FsThread::FS_THREAD_DATABASE);
if (!definition.exists()) break;
std::vector<std::vector<std::string>> enumContainer;
std::unordered_map<std::string, std::string> otherPatches;
nlohmann::json defData;
try
{
defData = nlohmann::json::parse(definition.getBuffer());
}
catch (const nlohmann::json::parse_error& ex)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "JSON Parse Error: {}\n", ex.what());
return;
}
if (!defData.is_object())
{
Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} is invalid!", i);
return;
}
for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
{
auto enumData = defData[StructuredData::EnumTranslation[pType]];
std::vector<std::string> entryData;
if (enumData.is_array())
{
for (const auto& rawEntry : enumData)
{
if (rawEntry.is_string())
{
entryData.push_back(rawEntry.get<std::string>());
}
}
}
enumContainer.push_back(entryData);
}
auto other = defData["other"];
if (other.is_object())
{
for (auto& item : other.items())
{
if (item.value().is_string())
{
otherPatches[item.key()] = item.value().get<std::string>();
}
}
}
patchDefinitions[i] = enumContainer;
otherPatchDefinitions[i] = otherPatches;
}
// Nothing to patch
if (patchDefinitions.empty()) return;
// Reallocate the definition
auto* newData = StructuredData::MemAllocator.allocateArray<Game::StructuredDataDef>(data->defCount + patchDefinitions.size());
std::memcpy(&newData[patchDefinitions.size()], data->defs, sizeof Game::StructuredDataDef * data->defCount);
// Prepare the buffers
for (unsigned int i = 0; i < patchDefinitions.size(); ++i)
{
std::memcpy(&newData[i], data->defs, sizeof Game::StructuredDataDef);
newData[i].version = (patchDefinitions.size() - i) + 155;
// Reallocate the enum array
auto* newEnums = StructuredData::MemAllocator.allocateArray<Game::StructuredDataEnum>(data->defs->enumCount);
std::memcpy(newEnums, data->defs->enums, sizeof Game::StructuredDataEnum * data->defs->enumCount);
newData[i].enums = newEnums;
}
// Apply new data
data->defs = newData;
data->defCount += patchDefinitions.size();
// Patch the definition
for (unsigned int i = 0; i < data->defCount; ++i)
{
// No need to patch version 155
if (newData[i].version == 155) continue;
if (patchDefinitions.contains(newData[i].version))
{
auto patchData = patchDefinitions[newData[i].version];
auto otherData = otherPatchDefinitions[newData[i].version];
// Invalid patch data
if (patchData.size() != StructuredData::PlayerDataType::COUNT)
{
Logger::Error(Game::ERR_FATAL, "PlayerDataDef patch for version {} wasn't parsed correctly!", newData[i].version);
continue;
}
// Apply the patch data
for (auto pType = 0; pType < StructuredData::PlayerDataType::COUNT; ++pType)
{
if (!patchData[pType].empty())
{
StructuredData::PatchPlayerDataEnum(&newData[i], static_cast<StructuredData::PlayerDataType>(pType), patchData[pType]);
}
}
StructuredData::PatchAdditionalData(&newData[i], otherData);
}
}
});
// 15 or more custom classes
Utils::Hook::Set<BYTE>(0x60A2FE, NUM_CUSTOM_CLASSES);
}
}