iw4x-client/src/Components/Modules/Download.cpp

1027 lines
26 KiB
C++
Raw Normal View History

2022-02-27 07:53:44 -05:00
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{
mg_mgr Download::Mgr;
Download::ClientDownload Download::CLDownload;
2017-05-15 15:57:45 -04:00
std::vector<std::shared_ptr<Download::ScriptDownload>> Download::ScriptDownloads;
2017-05-25 14:18:20 -04:00
std::thread Download::ServerThread;
bool Download::Terminate;
2022-06-28 03:26:43 -04:00
bool Download::ServerRunning;
2017-05-25 14:18:20 -04:00
#pragma region Client
2018-12-17 08:29:18 -05:00
void Download::InitiateMapDownload(const std::string& map, bool needPassword)
2017-04-06 16:22:47 -04:00
{
Download::InitiateClientDownload(map, needPassword, true);
2017-04-06 16:22:47 -04:00
}
2018-12-17 08:29:18 -05:00
void Download::InitiateClientDownload(const std::string& mod, bool needPassword, bool map)
{
if (Download::CLDownload.running) return;
Scheduler::Once([]
2017-01-22 07:44:14 -05:00
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(0));
Dvar::Var("ui_dl_progress").set("(0/0) %");
Dvar::Var("ui_dl_transRate").set("0.0 MB/s");
}, Scheduler::Pipeline::MAIN);
Command::Execute("openmenu mod_download_popmenu", false);
if (needPassword)
{
std::string pass = Dvar::Var("password").get<std::string>();
if (pass.empty())
{
// shouldn't ever happen but this is safe
Party::ConnectError("A password is required to connect to this server!");
return;
}
2017-07-02 13:00:28 -04:00
Download::CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(pass), "");
}
Download::CLDownload.running = true;
2017-04-06 16:22:47 -04:00
Download::CLDownload.isMap = map;
Download::CLDownload.mod = mod;
Download::CLDownload.terminateThread = false;
Download::CLDownload.totalBytes = 0;
Download::CLDownload.lastTimeStamp = 0;
Download::CLDownload.downBytes = 0;
Download::CLDownload.timeStampBytes = 0;
2017-06-19 15:44:59 -04:00
Download::CLDownload.isPrivate = needPassword;
Download::CLDownload.target = Party::Target();
Download::CLDownload.thread = std::thread(Download::ModDownloader, &Download::CLDownload);
}
2018-12-17 08:29:18 -05:00
bool Download::ParseModList(ClientDownload* download, const std::string& list)
{
if (!download) return false;
download->files.clear();
std::string error;
nlohmann::json listData = nlohmann::json::parse(list);
2017-04-06 16:22:47 -04:00
if (!error.empty() || !listData.is_array())
{
2022-06-12 17:07:53 -04:00
Logger::Print("Error: {}\n", error);
2017-04-06 16:22:47 -04:00
return false;
}
download->totalBytes = 0;
nlohmann::json::array_t listDataArray = listData;
for (auto& file : listDataArray)
{
if (!file.is_object()) return false;
auto hash = file["hash"];
auto name = file["name"];
auto size = file["size"];
if (!hash.is_string() || !name.is_string() || !size.is_number()) return false;
Download::ClientDownload::File fileEntry;
fileEntry.name = name.get<std::string>();
fileEntry.hash = hash.get<std::string>();
fileEntry.size = size.get<size_t>();
if (!fileEntry.name.empty())
{
download->files.push_back(fileEntry);
download->totalBytes += fileEntry.size;
}
}
return true;
}
void Download::DownloadHandler(mg_connection *nc, int ev, void* ev_data)
{
http_message* hm = reinterpret_cast<http_message*>(ev_data);
Download::FileDownload* fDownload = reinterpret_cast<Download::FileDownload*>(nc->mgr->user_data);
if (ev == MG_EV_CONNECT)
{
if (hm->message.p)
{
2017-05-14 14:14:52 -04:00
fDownload->downloading = true;
return;
}
}
2017-07-05 03:13:51 -04:00
if (ev == MG_EV_CLOSE)
{
fDownload->downloading = false;
return;
}
if (ev == MG_EV_RECV)
{
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
Download::DownloadProgress(fDownload, bytes);
}
if (ev == MG_EV_HTTP_REPLY)
{
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
fDownload->buffer = std::string(hm->body.p, hm->body.len);
fDownload->downloading = false;
return;
}
}
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
{
if (!download || download->files.size() <= index) return false;
auto file = download->files[index];
std::string path = download->mod + "/" + file.name;
2017-04-06 16:22:47 -04:00
if (download->isMap)
{
path = "usermaps/" + path;
}
if (Utils::IO::FileExists(path))
{
std::string data = Utils::IO::ReadFile(path);
if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash)
{
download->totalBytes += file.size;
return true;
}
}
auto host = "http://" + download->target.getString();
auto fastHost = Dvar::Var("sv_wwwBaseUrl").get<std::string>();
2017-07-05 03:13:51 -04:00
if (Utils::String::StartsWith(fastHost, "https://"))
{
download->thread.detach();
download->clear();
Scheduler::Once([]
2017-07-05 03:13:51 -04:00
{
Command::Execute("closemenu mod_download_popmenu");
Party::ConnectError("HTTPS not supported for downloading!");
}, Scheduler::Pipeline::CLIENT);
2017-07-05 03:13:51 -04:00
return false;
2017-07-05 03:13:51 -04:00
}
if (!Utils::String::StartsWith(fastHost, "http://"))
2017-07-05 03:13:51 -04:00
{
fastHost = "http://" + fastHost;
}
std::string url;
// file directory for fasthost looks like this
// /-usermaps
// /-mp_test
// -mp_test.ff
// -mp_test.iwd
// /-mp_whatever
// /-mp_whatever.ff
// /-mods
// /-mod1
// -mod1.iwd
// -mod.ff
// /-mod2
// ...
if (Dvar::Var("sv_wwwDownload").get<bool>())
{
2017-07-05 03:13:51 -04:00
if (!Utils::String::EndsWith(fastHost, "/")) fastHost.append("/");
url = fastHost + path;
}
else
{
url = host + "/file/" + (download->isMap ? "map/" : "") + file.name
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
}
2022-06-12 17:07:53 -04:00
Logger::Print("Downloading from url {}\n", url);
2017-07-05 03:13:51 -04:00
Download::FileDownload fDownload;
fDownload.file = file;
fDownload.index = index;
fDownload.download = download;
fDownload.downloading = true;
fDownload.receivedBytes = 0;
Utils::String::Replace(url, " ", "%20");
// Just a speedtest ;)
//download->totalBytes = 1048576000;
//url = "http://speed.hetzner.de/1GB.bin";
download->valid = true;
/*ZeroMemory(&download->mgr, sizeof download->mgr);
mg_mgr_init(&download->mgr, &fDownload);
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
while (fDownload.downloading && !fDownload.download->terminateThread)
{
2019-12-25 15:53:15 -05:00
mg_mgr_poll(&download->mgr, 100);
}
mg_mgr_free(&download->mgr);*/
fDownload.downloading = true;
Utils::WebIO webIO;
webIO.setProgressCallback([&fDownload, &webIO](size_t bytes, size_t)
{
if(!fDownload.downloading || fDownload.download->terminateThread)
{
webIO.cancelDownload();
return;
}
Download::DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
});
bool result = false;
fDownload.buffer = webIO.get(url, &result);
if (!result) fDownload.buffer.clear();
fDownload.downloading = false;
download->valid = false;
2017-07-05 03:13:51 -04:00
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);
Utils::IO::WriteFile(path, fDownload.buffer);
return true;
}
void Download::ModDownloader(ClientDownload* download)
{
if (!download) download = &Download::CLDownload;
std::string host = "http://" + download->target.getString();
std::string listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
std::string list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
if (list.empty())
{
if (download->terminateThread) return;
download->thread.detach();
download->clear();
Scheduler::Once([]
{
2017-04-06 16:22:47 -04:00
Command::Execute("closemenu mod_download_popmenu");
Party::ConnectError("Failed to download the modlist!");
}, Scheduler::Pipeline::CLIENT);
return;
}
if (download->terminateThread) return;
if (!Download::ParseModList(download, list))
{
if (download->terminateThread) return;
download->thread.detach();
download->clear();
Scheduler::Once([]
{
2017-04-06 16:22:47 -04:00
Command::Execute("closemenu mod_download_popmenu");
Party::ConnectError("Failed to parse the modlist!");
}, Scheduler::Pipeline::CLIENT);
return;
}
if (download->terminateThread) return;
2017-01-22 07:44:14 -05:00
static std::string mod;
mod = download->mod;
for (unsigned int i = 0; i < download->files.size(); ++i)
{
if (download->terminateThread) return;
if (!Download::DownloadFile(download, i))
{
if (download->terminateThread) return;
mod = Utils::String::VA("Failed to download file: %s!", download->files[i].name.data());
download->thread.detach();
download->clear();
Scheduler::Once([]
{
Dvar::Var("partyend_reason").set(mod);
2017-01-22 07:44:14 -05:00
mod.clear();
Command::Execute("closemenu mod_download_popmenu");
Command::Execute("openmenu menu_xboxlive_partyended");
}, Scheduler::Pipeline::CLIENT);
return;
}
}
if (download->terminateThread) return;
download->thread.detach();
download->clear();
if (download->isMap)
2017-04-06 16:22:47 -04:00
{
Scheduler::Once([]
2017-04-06 16:22:47 -04:00
{
Command::Execute("reconnect", false);
}, Scheduler::Pipeline::CLIENT);
2017-04-06 16:22:47 -04:00
}
else
{
2017-04-06 16:22:47 -04:00
// Run this on the main thread
Scheduler::Once([]
2017-04-06 16:22:47 -04:00
{
auto fsGame = Dvar::Var("fs_game");
fsGame.set(mod);
fsGame.get<Game::dvar_t*>()->modified = true;
mod.clear();
2017-04-06 16:22:47 -04:00
Command::Execute("closemenu mod_download_popmenu", false);
2017-04-06 16:22:47 -04:00
if (Dvar::Var("cl_modVidRestart").get<bool>())
{
Command::Execute("vid_restart", false);
}
2017-04-06 16:22:47 -04:00
Command::Execute("reconnect", false);
}, Scheduler::Pipeline::MAIN);
2017-04-06 16:22:47 -04:00
}
}
#pragma endregion
#pragma region Server
bool Download::IsClient(mg_connection *nc)
{
return (Download::GetClient(nc) != nullptr);
}
Game::client_t* Download::GetClient(mg_connection *nc)
{
Network::Address address(nc->sa.sa);
2022-04-12 08:34:51 -04:00
for (int i = 0; i < *Game::svs_clientCount; ++i)
{
Game::client_t* client = &Game::svs_clients[i];
if (client->state >= 3)
{
2021-09-08 05:19:30 -04:00
if (address.getIP().full == Network::Address(client->netchan.remoteAddress).getIP().full)
{
return client;
}
}
}
return nullptr;
}
void Download::DownloadProgress(FileDownload* fDownload, size_t bytes)
{
fDownload->receivedBytes += bytes;
fDownload->download->downBytes += bytes;
fDownload->download->timeStampBytes += bytes;
static volatile bool framePushed = false;
if (!framePushed)
{
double progress = 0;
if (fDownload->download->totalBytes)
{
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
}
static unsigned int dlIndex, dlSize, dlProgress;
dlIndex = fDownload->index + 1;
dlSize = fDownload->download->files.size();
dlProgress = static_cast<unsigned int>(progress);
framePushed = true;
Scheduler::Once([]
{
framePushed = false;
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
}, Scheduler::Pipeline::CLIENT);
}
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
if (delta > 300)
{
bool doFormat = fDownload->download->lastTimeStamp != 0;
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
auto dataLeft = fDownload->download->totalBytes - fDownload->download->downBytes;
int timeLeft = 0;
if (fDownload->download->timeStampBytes)
{
double timeLeftD = ((1.0 * dataLeft) / fDownload->download->timeStampBytes) * delta;
timeLeft = static_cast<int>(timeLeftD);
}
if (doFormat)
{
static size_t dlTsBytes;
static int dlDelta, dlTimeLeft;
dlTimeLeft = timeLeft;
dlDelta = delta;
dlTsBytes = fDownload->download->timeStampBytes;
Scheduler::Once([]
{
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
}, Scheduler::Pipeline::MAIN);
}
fDownload->download->timeStampBytes = 0;
}
}
bool Download::VerifyPassword(mg_connection *nc, http_message* message)
{
std::string g_password = Dvar::Var("g_password").get<std::string>();
if (g_password.empty()) return true;
// sha256 hashes are 64 chars long but we're gonna be safe here
char buffer[128] = { 0 };
int passLen = mg_get_http_var(&message->query_string, "password", buffer, sizeof buffer);
2017-07-05 03:13:51 -04:00
if (passLen <= 0 || std::string(buffer, passLen) != Utils::Cryptography::SHA256::Compute(g_password, true))
{
mg_printf(nc, ("HTTP/1.1 403 Forbidden\r\n"s +
"Content-Type: text/html\r\n"s +
"Connection: close\r\n"s +
"\r\n"s +
((passLen == 0) ? "Password Required"s : "Invalid Password"s)).c_str());
nc->flags |= MG_F_SEND_AND_CLOSE;
return false;
}
return true;
}
void Download::Forbid(mg_connection *nc)
{
mg_printf(nc, "HTTP/1.1 403 Forbidden\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"403 - Forbidden");
nc->flags |= MG_F_SEND_AND_CLOSE;
}
2018-12-02 12:17:45 -05:00
void Download::ServerlistHandler(mg_connection* nc, int ev, void* /*ev_data*/)
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
std::vector<nlohmann::json> servers;
2018-12-02 12:17:45 -05:00
// Build server list
for (auto& node : Node::GetNodes())
{
if (node.isValid())
{
servers.push_back(nlohmann::json{ node.to_json()});
2018-12-02 12:17:45 -05:00
}
}
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"%s", nlohmann::json(servers).dump().data());
2018-12-02 12:17:45 -05:00
nc->flags |= MG_F_SEND_AND_CLOSE;
}
void Download::MapHandler(mg_connection *nc, int ev, void* ev_data)
2017-04-06 16:22:47 -04:00
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
2017-04-06 16:22:47 -04:00
static std::string mapnamePre;
static nlohmann::json jsonList;
2017-04-06 16:22:47 -04:00
std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
2017-04-06 16:22:47 -04:00
{
mapnamePre.clear();
jsonList = std::vector<nlohmann::json>();
2017-04-06 16:22:47 -04:00
}
else if (!mapname.empty() && mapname != mapnamePre)
{
std::vector<nlohmann::json> fileList;
2017-04-06 16:22:47 -04:00
mapnamePre = mapname;
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\usermaps\\" + mapname;
2017-04-08 09:32:51 -04:00
for (int i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
2017-04-06 16:22:47 -04:00
{
2017-04-08 09:32:51 -04:00
std::string filename = path + "\\" + mapname + Maps::UserMapFiles[i];
if (Utils::IO::FileExists(filename))
2017-04-06 16:22:47 -04:00
{
std::map<std::string, nlohmann::json> file;
2017-04-06 16:22:47 -04:00
std::string fileBuffer = Utils::IO::ReadFile(filename);
2017-04-08 09:32:51 -04:00
file["name"] = mapname + Maps::UserMapFiles[i];
2017-04-06 16:22:47 -04:00
file["size"] = static_cast<int>(fileBuffer.size());
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
fileList.push_back(file);
}
}
jsonList = fileList;
}
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"\r\n"
"%s", jsonList.dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
void Download::ListHandler(mg_connection* nc, int ev, void* ev_data)
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
// if (!Download::IsClient(nc))
// {
// Download::Forbid(nc);
// }
// else
{
static std::string fsGamePre;
static nlohmann::json jsonList;
std::string fsGame = Dvar::Var("fs_game").get<std::string>();
if (!fsGame.empty() && fsGame != fsGamePre)
{
std::vector<nlohmann::json> fileList;
fsGamePre = fsGame;
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + fsGame;
auto list = FileSystem::GetSysFileList(path, "iwd", false);
list.push_back("mod.ff");
for (auto i = list.begin(); i != list.end(); ++i)
{
std::string filename = path + "\\" + *i;
if (strstr(i->data(), "_svr_") == nullptr && Utils::IO::FileExists(filename))
{
std::map<std::string, nlohmann::json> file;
2017-04-06 16:22:47 -04:00
std::string fileBuffer = Utils::IO::ReadFile(filename);
file["name"] = *i;
file["size"] = static_cast<int>(fileBuffer.size());
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
fileList.push_back(file);
}
}
jsonList = fileList;
}
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"\r\n"
"%s", jsonList.dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
}
void Download::FileHandler(mg_connection *nc, int ev, void *ev_data)
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
http_message* message = reinterpret_cast<http_message*>(ev_data);
//if (!Download::VerifyPassword(nc, message)) return;
// if (!Download::IsClient(nc))
// {
// Download::Forbid(nc);
// }
// else
{
std::string url(message->uri.p, message->uri.len);
Utils::String::Replace(url, "\\", "/");
2017-04-06 16:22:47 -04:00
2017-02-14 14:53:55 -05:00
if (url.size() >= 6)
{
url = url.substr(6);
}
Utils::String::Replace(url, "%20", " ");
2017-04-06 16:22:47 -04:00
bool isMap = false;
if (Utils::String::StartsWith(url, "map/"))
{
2017-04-06 16:22:47 -04:00
isMap = true;
url = url.substr(4);
std::string mapname = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
2017-04-08 09:32:51 -04:00
bool isValidFile = false;
for (int i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
{
if (url == (mapname + Maps::UserMapFiles[i]))
2017-04-08 09:32:51 -04:00
{
isValidFile = true;
break;
}
}
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
2017-04-06 16:22:47 -04:00
{
Download::Forbid(nc);
return;
}
url = Utils::String::VA("usermaps\\%s\\%s", mapname.data(), url.data());
}
else
{
if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != nullptr)
{
Download::Forbid(nc);
return;
}
}
2017-02-10 09:18:11 -05:00
std::string file;
std::string fsGame = Dvar::Var("fs_game").get<std::string>();
2017-04-06 16:22:47 -04:00
std::string path = Dvar::Var("fs_basepath").get<std::string>() + "\\" + (isMap ? "" : fsGame + "\\") + url;
2017-04-06 16:22:47 -04:00
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
{
mg_printf(nc,
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"404 - Not Found %s", path.data());
}
else
{
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n", file.size());
mg_send(nc, file.data(), static_cast<int>(file.size()));
}
nc->flags |= MG_F_SEND_AND_CLOSE;
}
}
2017-06-22 04:35:45 -04:00
void Download::InfoHandler(mg_connection* nc, int ev, void* /*ev_data*/)
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
//if (!Download::VerifyPassword(nc, reinterpret_cast<http_message*>(ev_data))) return;
Utils::InfoString status = ServerInfo::GetInfo();
2019-01-09 15:40:56 -05:00
Utils::InfoString host = ServerInfo::GetHostInfo();
std::map<std::string, nlohmann::json> info;
info["status"] = status.to_json();
2019-01-09 15:40:56 -05:00
info["host"] = host.to_json();
std::vector<nlohmann::json> players;
// Build player list
for (int i = 0; i < atoi(status.get("sv_maxclients").data()); ++i) // Maybe choose 18 here?
{
std::map<std::string, nlohmann::json> playerInfo;
playerInfo["score"] = 0;
playerInfo["ping"] = 0;
playerInfo["name"] = "";
if (Dvar::Var("sv_running").get<bool>())
{
if (Game::svs_clients[i].state < 3) continue;
playerInfo["score"] = Game::SV_GameClientNum_Score(i);
playerInfo["ping"] = Game::svs_clients[i].ping;
playerInfo["name"] = Game::svs_clients[i].name;
}
else
{
// Score and ping are irrelevant
const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast<Game::PartyData*>(0x1081C00), i);
if (!namePtr || !namePtr[0]) continue;
playerInfo["name"] = namePtr;
}
players.push_back(playerInfo);
}
info["players"] = players;
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"%s", nlohmann::json(info).dump().data());
nc->flags |= MG_F_SEND_AND_CLOSE;
}
void Download::EventHandler(mg_connection *nc, int ev, void *ev_data)
{
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
http_message* message = reinterpret_cast<http_message*>(ev_data);
// if (message->uri.p, message->uri.len == "/"s)
// {
// mg_printf(nc,
// "HTTP/1.1 200 OK\r\n"
// "Content-Type: text/html\r\n"
// "Connection: close\r\n"
// "\r\n"
// "Hi fella!<br>You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not"));
//
// Game::client_t* client = Download::GetClient(nc);
//
// if (client)
// {
// mg_printf(nc, "<br>Hello %s!", client->name);
// }
// }
// else
{
//std::string path = (Dvar::Var("fs_basepath").get<std::string>() + "\\" BASEGAME "\\html");
//mg_serve_http_opts opts = { 0 };
//opts.document_root = path.data();
//mg_serve_http(nc, message, opts);
FileSystem::File file;
std::string url = "html" + std::string(message->uri.p, message->uri.len);
if (Utils::String::EndsWith(url, "/"))
{
url.append("index.html");
file = FileSystem::File(url);
}
else
{
file = FileSystem::File(url);
if (!file.exists())
{
url.append("/index.html");
file = FileSystem::File(url);
}
}
std::string mimeType = Utils::GetMimeType(url);
if (file.exists())
{
std::string buffer = file.getBuffer();
mg_printf(nc,
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n", mimeType.data(), buffer.size());
mg_send(nc, buffer.data(), static_cast<int>(buffer.size()));
}
else
{
mg_printf(nc,
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"404 - Not Found");
}
}
nc->flags |= MG_F_SEND_AND_CLOSE;
}
#pragma endregion
Download::Download()
{
if (Dedicated::IsEnabled() /*|| Dvar::Var("mod_force_download_server").get<bool>()*/)
{
2017-05-14 14:14:52 -04:00
ZeroMemory(&Download::Mgr, sizeof Download::Mgr);
mg_mgr_init(&Download::Mgr, nullptr);
Network::OnStart([]()
{
mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", Network::GetPort()), Download::EventHandler);
if (nc)
{
// Handle special requests
mg_register_http_endpoint(nc, "/info", Download::InfoHandler);
mg_register_http_endpoint(nc, "/list", Download::ListHandler);
mg_register_http_endpoint(nc, "/map", Download::MapHandler);
mg_register_http_endpoint(nc, "/file/", Download::FileHandler);
2018-12-02 12:17:45 -05:00
mg_register_http_endpoint(nc, "/serverlist", Download::ServerlistHandler);
mg_set_protocol_http_websocket(nc);
}
else
{
Logger::Print("Failed to bind TCP socket, moddownload won't work!\n");
}
});
2022-06-28 03:26:43 -04:00
Download::ServerRunning = true;
2017-05-25 14:18:20 -04:00
Download::Terminate = false;
Download::ServerThread = std::thread([]
{
2017-05-25 14:18:20 -04:00
while (!Download::Terminate)
{
mg_mgr_poll(&Download::Mgr, 100);
}
});
}
else
{
Scheduler::Once([]
2017-01-22 07:44:14 -05:00
{
Dvar::Register<const char*>("ui_dl_timeLeft", "", Game::DVAR_NONE, "");
Dvar::Register<const char*>("ui_dl_progress", "", Game::DVAR_NONE, "");
Dvar::Register<const char*>("ui_dl_transRate", "", Game::DVAR_NONE, "");
}, Scheduler::Pipeline::MAIN);
2017-01-22 07:44:14 -05:00
UIScript::Add("mod_download_cancel", [](UIScript::Token)
{
Download::CLDownload.clear();
});
}
2017-05-14 14:14:52 -04:00
Scheduler::Once([]
{
Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_ARCHIVE, "Set to true to enable downloading maps/mods from an external server.");
Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_ARCHIVE, "Set to the base url for the external map download.");
2022-03-08 09:42:41 -05:00
// Force users to enable this because we don't want to accidentally turn everyone's pc into a http server into all their files again
// not saying we are but ya know... accidents happen
// by having it saved we force the user to enable it in config_mp because it only checks the dvar on startup to see if we should init download or not
Dvar::Register<bool>("mod_force_download_server", false, Game::DVAR_ARCHIVE, "Set to true to force the client to run the download server for mods (for mods in private matches).");
}, Scheduler::Pipeline::MAIN);
Scheduler::Loop([]
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
int workingCount = 0;
for (auto i = Download::ScriptDownloads.begin(); i != Download::ScriptDownloads.end();)
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
auto download = *i;
2017-05-14 14:14:52 -04:00
if (download->isDone())
2017-05-14 14:14:52 -04:00
{
2017-05-15 15:57:45 -04:00
download->notifyDone();
2017-05-14 14:14:52 -04:00
i = Download::ScriptDownloads.erase(i);
2017-05-15 15:57:45 -04:00
continue;
}
if (download->isWorking())
{
download->notifyProgress();
++workingCount;
}
++i;
}
for (auto& download : Download::ScriptDownloads)
2017-05-15 15:57:45 -04:00
{
if (workingCount > 5) break;
if (!download->isWorking())
2017-05-15 15:57:45 -04:00
{
download->startWorking();
++workingCount;
2017-05-14 14:14:52 -04:00
}
}
}, Scheduler::Pipeline::MAIN);
2017-05-14 14:14:52 -04:00
2022-06-16 10:15:26 -04:00
Events::OnVMShutdown([]
2017-05-14 14:14:52 -04:00
{
Download::ScriptDownloads.clear();
});
2022-05-04 07:44:45 -04:00
Script::AddFunction("HttpGet", []
2017-05-14 14:14:52 -04:00
{
2022-02-01 07:15:59 -05:00
const auto* url = Game::Scr_GetString(0);
2022-01-23 14:32:20 -05:00
2022-02-01 07:15:59 -05:00
if (url == nullptr)
2022-01-23 14:32:20 -05:00
{
2022-02-09 18:40:27 -05:00
Game::Scr_ParamError(0, "^1HttpGet: Illegal parameter!\n");
2022-01-23 14:32:20 -05:00
return;
}
auto object = Game::AllocObject();
2017-05-14 14:14:52 -04:00
Game::Scr_AddObject(object);
2017-05-14 14:14:52 -04:00
Download::ScriptDownloads.push_back(std::make_shared<ScriptDownload>(url, object));
Game::RemoveRefToObject(object);
});
2017-05-14 14:14:52 -04:00
2022-05-04 07:44:45 -04:00
Script::AddFunction("HttpCancel", []
{
2022-02-01 07:15:59 -05:00
const auto object = Game::Scr_GetObject(0);
for (const auto& download : Download::ScriptDownloads)
{
if (object == download->getObject())
2017-05-14 14:14:52 -04:00
{
download->cancel();
break;
2017-05-14 14:14:52 -04:00
}
}
});
}
Download::~Download()
{
if (Download::ServerRunning)
{
mg_mgr_free(&Download::Mgr);
}
}
void Download::preDestroy()
{
2017-06-02 06:26:08 -04:00
Download::Terminate = true;
if (Download::ServerThread.joinable())
{
Download::ServerThread.join();
}
if (!Dedicated::IsEnabled())
{
Download::CLDownload.clear();
}
2017-05-14 14:14:52 -04:00
Download::ScriptDownloads.clear();
}
}