2022-02-27 07:53:44 -05:00
|
|
|
#include <STDInclude.hpp>
|
2023-01-03 07:16:44 -05:00
|
|
|
#include <Utils/InfoString.hpp>
|
|
|
|
|
2022-12-26 07:07:24 -05:00
|
|
|
#include "Download.hpp"
|
2023-01-03 07:16:44 -05:00
|
|
|
#include "Party.hpp"
|
2022-12-26 07:07:24 -05:00
|
|
|
#include "ServerInfo.hpp"
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
#include <mongoose.h>
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
namespace Components
|
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
static mg_mgr Mgr;
|
|
|
|
|
2022-12-31 09:03:33 -05:00
|
|
|
Dvar::Var Download::SV_wwwDownload;
|
|
|
|
Dvar::Var Download::SV_wwwBaseUrl;
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
Download::ClientDownload Download::CLDownload;
|
|
|
|
|
2017-05-25 14:18:20 -04:00
|
|
|
std::thread Download::ServerThread;
|
2022-12-01 13:24:44 -05:00
|
|
|
volatile bool Download::Terminate;
|
2022-06-28 03:26:43 -04:00
|
|
|
bool Download::ServerRunning;
|
2017-05-25 14:18:20 -04:00
|
|
|
|
2017-01-20 08:36:13 -05: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
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
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)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
if (CLDownload.running) return;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
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");
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::MAIN);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Command::Execute("openmenu mod_download_popmenu", false);
|
|
|
|
|
2017-06-19 15:39:48 -04:00
|
|
|
if (needPassword)
|
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
const auto password = Dvar::Var("password").get<std::string>();
|
|
|
|
if (password.empty())
|
2017-06-19 15:39:48 -04:00
|
|
|
{
|
|
|
|
// shouldn't ever happen but this is safe
|
|
|
|
Party::ConnectError("A password is required to connect to this server!");
|
|
|
|
return;
|
|
|
|
}
|
2017-07-02 08:16:06 -04:00
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
CLDownload.hashedPassword = Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(password), "");
|
2017-06-19 15:39:48 -04:00
|
|
|
}
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
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);
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2018-12-17 08:29:18 -05:00
|
|
|
bool Download::ParseModList(ClientDownload* download, const std::string& list)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (!download) return false;
|
|
|
|
download->files.clear();
|
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
nlohmann::json listData;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
listData = nlohmann::json::parse(list);
|
|
|
|
}
|
|
|
|
catch (const nlohmann::json::parse_error& ex)
|
|
|
|
{
|
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Parse Error: {}\n", ex.what());
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
if (!listData.is_array())
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
download->totalBytes = 0;
|
2022-10-16 11:17:42 -04:00
|
|
|
const nlohmann::json::array_t listDataArray = listData;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-07-29 15:54:18 -04:00
|
|
|
for (auto& file : listDataArray)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (!file.is_object()) return false;
|
|
|
|
|
2022-10-16 11:17:42 -04:00
|
|
|
try
|
|
|
|
{
|
|
|
|
const auto hash = file.at("hash").get<std::string>();
|
|
|
|
const auto name = file.at("name").get<std::string>();
|
|
|
|
const auto size = file.at("size").get<std::size_t>();
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
ClientDownload::File fileEntry;
|
2022-10-16 11:17:42 -04:00
|
|
|
fileEntry.name = name;
|
|
|
|
fileEntry.hash = hash;
|
|
|
|
fileEntry.size = size;
|
|
|
|
|
|
|
|
if (!fileEntry.name.empty())
|
|
|
|
{
|
|
|
|
download->files.push_back(fileEntry);
|
|
|
|
download->totalBytes += fileEntry.size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const nlohmann::json::exception& ex)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-16 11:17:42 -04:00
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Json Error: {}\n", ex.what());
|
|
|
|
return false;
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Download::DownloadFile(ClientDownload* download, unsigned int index)
|
|
|
|
{
|
|
|
|
if (!download || download->files.size() <= index) return false;
|
|
|
|
|
|
|
|
auto file = download->files[index];
|
|
|
|
|
2022-12-01 13:33:32 -05:00
|
|
|
auto path = download->mod + "/" + file.name;
|
2017-04-06 16:22:47 -04:00
|
|
|
if (download->isMap)
|
|
|
|
{
|
|
|
|
path = "usermaps/" + path;
|
|
|
|
}
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
if (Utils::IO::FileExists(path))
|
|
|
|
{
|
2022-12-01 13:33:32 -05:00
|
|
|
auto data = Utils::IO::ReadFile(path);
|
2017-01-20 08:36:13 -05:00
|
|
|
if (data.size() == file.size && Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(data), "") == file.hash)
|
|
|
|
{
|
|
|
|
download->totalBytes += file.size;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
auto host = "http://" + download->target.getString();
|
2022-12-31 09:03:33 -05:00
|
|
|
auto fastHost = SV_wwwBaseUrl.get<std::string>();
|
2017-07-05 03:13:51 -04:00
|
|
|
if (Utils::String::StartsWith(fastHost, "https://"))
|
|
|
|
{
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-07-05 03:13:51 -04:00
|
|
|
{
|
|
|
|
Command::Execute("closemenu mod_download_popmenu");
|
|
|
|
Party::ConnectError("HTTPS not supported for downloading!");
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2017-07-05 03:13:51 -04:00
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
return false;
|
2017-07-05 03:13:51 -04:00
|
|
|
}
|
2022-06-13 13:59:32 -04:00
|
|
|
|
|
|
|
if (!Utils::String::StartsWith(fastHost, "http://"))
|
2017-07-05 03:13:51 -04:00
|
|
|
{
|
|
|
|
fastHost = "http://" + fastHost;
|
|
|
|
}
|
2017-06-12 19:22:39 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
// ...
|
2022-12-31 09:03:33 -05:00
|
|
|
if (SV_wwwDownload.get<bool>())
|
2017-06-12 19:22:39 -04:00
|
|
|
{
|
2017-07-05 03:13:51 -04:00
|
|
|
if (!Utils::String::EndsWith(fastHost, "/")) fastHost.append("/");
|
2017-06-12 19:22:39 -04:00
|
|
|
url = fastHost + path;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-06-19 16:31:58 -04:00
|
|
|
url = host + "/file/" + (download->isMap ? "map/" : "") + file.name
|
2017-07-02 08:16:06 -04:00
|
|
|
+ (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
|
2017-06-12 19:22:39 -04:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-06-12 17:07:53 -04:00
|
|
|
Logger::Print("Downloading from url {}\n", url);
|
2017-07-05 03:13:51 -04:00
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
FileDownload fDownload;
|
2017-01-20 08:36:13 -05:00
|
|
|
fDownload.file = file;
|
|
|
|
fDownload.index = index;
|
|
|
|
fDownload.download = download;
|
|
|
|
fDownload.downloading = true;
|
|
|
|
fDownload.receivedBytes = 0;
|
|
|
|
|
|
|
|
Utils::String::Replace(url, " ", "%20");
|
|
|
|
|
|
|
|
download->valid = true;
|
2019-12-25 17:03:43 -05:00
|
|
|
|
|
|
|
fDownload.downloading = true;
|
|
|
|
|
|
|
|
Utils::WebIO webIO;
|
2022-12-01 13:24:44 -05:00
|
|
|
webIO.setProgressCallback([&fDownload, &webIO](std::size_t bytes, std::size_t)
|
2019-12-25 17:03:43 -05:00
|
|
|
{
|
|
|
|
if(!fDownload.downloading || fDownload.download->terminateThread)
|
|
|
|
{
|
|
|
|
webIO.cancelDownload();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
DownloadProgress(&fDownload, bytes - fDownload.receivedBytes);
|
2019-12-25 17:03:43 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
fDownload.buffer = webIO.get(url, &result);
|
|
|
|
if (!result) fDownload.buffer.clear();
|
|
|
|
|
|
|
|
fDownload.downloading = false;
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
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)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-30 15:07:05 -04:00
|
|
|
if (download->isMap) Utils::IO::CreateDir("usermaps/" + download->mod);
|
2017-01-20 08:36:13 -05:00
|
|
|
Utils::IO::WriteFile(path, fDownload.buffer);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Download::ModDownloader(ClientDownload* download)
|
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
if (!download) download = &CLDownload;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-12-18 16:47:59 -05:00
|
|
|
const auto host = "http://" + download->target.getString();
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-12-18 16:47:59 -05:00
|
|
|
const auto listUrl = host + (download->isMap ? "/map" : "/list") + (download->isPrivate ? ("?password=" + download->hashedPassword) : "");
|
2017-06-19 15:39:48 -04:00
|
|
|
|
2022-12-18 16:47:59 -05:00
|
|
|
const auto list = Utils::WebIO("IW4x", listUrl).setTimeout(5000)->get();
|
2017-01-20 08:36:13 -05:00
|
|
|
if (list.empty())
|
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-04-06 16:22:47 -04:00
|
|
|
Command::Execute("closemenu mod_download_popmenu");
|
2017-01-20 08:36:13 -05:00
|
|
|
Party::ConnectError("Failed to download the modlist!");
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
if (!ParseModList(download, list))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-04-06 16:22:47 -04:00
|
|
|
Command::Execute("closemenu mod_download_popmenu");
|
2017-01-20 08:36:13 -05:00
|
|
|
Party::ConnectError("Failed to parse the modlist!");
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
2017-01-22 07:44:14 -05:00
|
|
|
static std::string mod;
|
|
|
|
mod = download->mod;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-12-18 16:47:59 -05:00
|
|
|
for (std::size_t i = 0; i < download->files.size(); ++i)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
if (!DownloadFile(download, i))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
2022-12-11 12:54:24 -05:00
|
|
|
mod = std::format("Failed to download file: {}!", download->files[i].name);
|
2017-01-20 08:36:13 -05:00
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
Dvar::Var("partyend_reason").set(mod);
|
2017-01-22 07:44:14 -05:00
|
|
|
mod.clear();
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Command::Execute("closemenu mod_download_popmenu");
|
|
|
|
Command::Execute("openmenu menu_xboxlive_partyended");
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2017-06-14 06:06:04 -04:00
|
|
|
if (download->isMap)
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
|
|
|
Command::Execute("reconnect", false);
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
|
|
|
else
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-04-06 16:22:47 -04:00
|
|
|
// Run this on the main thread
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-08-10 17:03:26 -04:00
|
|
|
Game::Dvar_SetString(*Game::fs_gameDirVar, mod.data());
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
mod.clear();
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
Command::Execute("closemenu mod_download_popmenu", false);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
if (Dvar::Var("cl_modVidRestart").get<bool>())
|
|
|
|
{
|
|
|
|
Command::Execute("vid_restart", false);
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
Command::Execute("reconnect", false);
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::MAIN);
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
#pragma region Server
|
2022-10-28 06:52:34 -04:00
|
|
|
|
|
|
|
void Download::DownloadProgress(FileDownload* fDownload, std::size_t bytes)
|
2019-12-25 17:03:43 -05:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-31 10:49:30 -04:00
|
|
|
static std::uint32_t dlIndex, dlSize, dlProgress;
|
2019-12-25 17:03:43 -05:00
|
|
|
dlIndex = fDownload->index + 1;
|
|
|
|
dlSize = fDownload->download->files.size();
|
2022-10-31 10:49:30 -04:00
|
|
|
dlProgress = static_cast<std::uint32_t>(progress);
|
2019-12-25 17:03:43 -05:00
|
|
|
|
|
|
|
framePushed = true;
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2019-12-25 17:03:43 -05:00
|
|
|
{
|
|
|
|
framePushed = false;
|
|
|
|
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::CLIENT);
|
2019-12-25 17:03:43 -05:00
|
|
|
}
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
auto delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
|
2019-12-25 17:03:43 -05:00
|
|
|
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)
|
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
static std::size_t dlTsBytes;
|
2019-12-25 17:03:43 -05:00
|
|
|
static int dlDelta, dlTimeLeft;
|
|
|
|
dlTimeLeft = timeLeft;
|
|
|
|
dlDelta = delta;
|
|
|
|
dlTsBytes = fDownload->download->timeStampBytes;
|
|
|
|
|
2022-06-13 13:59:32 -04:00
|
|
|
Scheduler::Once([]
|
2019-12-25 17:03:43 -05:00
|
|
|
{
|
|
|
|
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
|
|
|
|
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
|
2022-06-13 13:59:32 -04:00
|
|
|
}, Scheduler::Pipeline::MAIN);
|
2019-12-25 17:03:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fDownload->download->timeStampBytes = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static std::string InfoHandler()
|
2017-06-19 15:39:48 -04:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
const auto status = ServerInfo::GetInfo();
|
|
|
|
const auto host = ServerInfo::GetHostInfo();
|
2017-06-19 15:39:48 -04:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
std::unordered_map<std::string, nlohmann::json> info;
|
|
|
|
info["status"] = status.to_json();
|
|
|
|
info["host"] = host.to_json();
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
std::vector<nlohmann::json> players;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
// Build player list
|
|
|
|
for (auto i = 0; i < Game::MAX_CLIENTS; ++i)
|
|
|
|
{
|
|
|
|
std::unordered_map<std::string, nlohmann::json> playerInfo;
|
|
|
|
playerInfo["score"] = 0;
|
|
|
|
playerInfo["ping"] = 0;
|
|
|
|
playerInfo["name"] = "";
|
2018-12-02 12:17:45 -05:00
|
|
|
|
2022-11-15 17:18:00 -05:00
|
|
|
if (Dedicated::IsRunning())
|
2022-10-28 06:52:34 -04:00
|
|
|
{
|
|
|
|
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
2018-12-02 12:17:45 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
playerInfo["score"] = Game::SV_GameClientNum_Score(i);
|
|
|
|
playerInfo["ping"] = Game::svs_clients[i].ping;
|
|
|
|
playerInfo["name"] = Game::svs_clients[i].name;
|
|
|
|
}
|
|
|
|
else
|
2018-12-02 12:17:45 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
// Score and ping are irrelevant
|
|
|
|
const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
2022-12-01 13:24:44 -05:00
|
|
|
if (name == nullptr || *name == '\0') continue;
|
2022-10-28 06:52:34 -04:00
|
|
|
|
|
|
|
playerInfo["name"] = name;
|
2018-12-02 12:17:45 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
players.emplace_back(playerInfo);
|
|
|
|
}
|
2018-12-02 12:17:45 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
info["players"] = players;
|
|
|
|
return {nlohmann::json(info).dump()};
|
2018-12-02 12:17:45 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static std::string ListHandler()
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-07-29 15:54:18 -04:00
|
|
|
static nlohmann::json jsonList;
|
2023-01-06 07:51:41 -05:00
|
|
|
static std::filesystem::path fsGamePre;
|
2017-04-06 16:22:47 -04:00
|
|
|
|
2023-01-06 07:51:41 -05:00
|
|
|
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
|
2022-10-28 06:52:34 -04:00
|
|
|
|
2023-01-06 07:51:41 -05:00
|
|
|
if (!fsGame.empty() && (fsGamePre != fsGame))
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2023-01-06 07:51:41 -05:00
|
|
|
fsGamePre = fsGame;
|
2017-04-06 16:22:47 -04:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
std::vector<nlohmann::json> fileList;
|
2017-04-06 16:22:47 -04:00
|
|
|
|
2023-01-06 07:51:41 -05:00
|
|
|
const auto path = (*Game::fs_basepath)->current.string / fsGame;
|
2023-01-06 08:09:17 -05:00
|
|
|
auto list = FileSystem::GetSysFileList(path.generic_string(), "iwd", false);
|
2022-10-28 06:52:34 -04:00
|
|
|
list.emplace_back("mod.ff");
|
2017-04-06 16:22:47 -04:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
for (const auto& file : list)
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
auto filename = path / file;
|
2023-01-06 07:51:41 -05:00
|
|
|
|
|
|
|
if (file.find("_svr_") != std::string::npos) // Files that are 'server only' are skipped
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
continue;
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
2022-12-01 13:24:44 -05:00
|
|
|
|
2022-12-01 13:33:32 -05:00
|
|
|
auto fileBuffer = Utils::IO::ReadFile(filename.generic_string());
|
2022-12-01 13:24:44 -05:00
|
|
|
if (fileBuffer.empty())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-01-06 07:51:41 -05:00
|
|
|
std::unordered_map<std::string, nlohmann::json> jsonFileList;
|
2022-12-01 13:24:44 -05:00
|
|
|
jsonFileList["name"] = file;
|
|
|
|
jsonFileList["size"] = fileBuffer.size();
|
|
|
|
jsonFileList["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
|
|
|
|
|
|
|
fileList.emplace_back(jsonFileList);
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
jsonList = fileList;
|
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
return {jsonList.dump()};
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static std::string MapHandler()
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
static std::string mapNamePre;
|
|
|
|
static nlohmann::json jsonList;
|
2017-06-19 15:39:48 -04:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
const auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
|
|
|
if (!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mapNamePre.clear();
|
|
|
|
jsonList = {};
|
|
|
|
}
|
|
|
|
else if (!mapName.empty() && mapName != mapNamePre)
|
|
|
|
{
|
|
|
|
std::vector<nlohmann::json> fileList;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
mapNamePre = mapName;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2023-01-05 04:59:09 -05:00
|
|
|
const std::filesystem::path basePath = (*Game::fs_basepath)->current.string;
|
2022-12-01 13:24:44 -05:00
|
|
|
const auto path = basePath / "usermaps" / mapName;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
2022-10-28 06:52:34 -04:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
const auto filename = std::format("{}\\{}{}", path.generic_string(), mapName, Maps::UserMapFiles[i]);
|
|
|
|
|
|
|
|
std::unordered_map<std::string, nlohmann::json> file;
|
2022-12-01 13:33:32 -05:00
|
|
|
auto fileBuffer = Utils::IO::ReadFile(filename);
|
2022-10-28 06:52:34 -04:00
|
|
|
if (fileBuffer.empty())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
continue;
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
file["name"] = mapName + Maps::UserMapFiles[i];
|
|
|
|
file["size"] = fileBuffer.size();
|
|
|
|
file["hash"] = Utils::Cryptography::SHA256::Compute(fileBuffer, true);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
fileList.emplace_back(file);
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
jsonList = fileList;
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
2022-10-28 06:52:34 -04:00
|
|
|
|
|
|
|
return {jsonList.dump()};
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static void FileHandler(mg_connection* c, const mg_http_message* hm)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
std::string url(hm->uri.ptr, hm->uri.len);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
Utils::String::Replace(url, "\\", "/");
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
// Strip /file
|
|
|
|
url = url.substr(6);
|
|
|
|
Utils::String::Replace(url, "%20", " ");
|
2017-06-19 15:39:48 -04:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
auto isMap = false;
|
|
|
|
if (url.starts_with("map/"))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
isMap = true;
|
|
|
|
url = url.substr(4);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
auto mapName = (Party::IsInUserMapLobby() ? Dvar::Var("ui_mapname").get<std::string>() : Maps::GetUserMap()->getName());
|
|
|
|
auto isValidFile = false;
|
2022-12-01 13:24:44 -05:00
|
|
|
for (std::size_t i = 0; i < ARRAYSIZE(Maps::UserMapFiles); ++i)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
if (url == (mapName + Maps::UserMapFiles[i]))
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
isValidFile = true;
|
|
|
|
break;
|
2017-04-06 16:22:47 -04:00
|
|
|
}
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
|
|
|
|
return;
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
url = std::format("usermaps\\{}\\{}", mapName, url);
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
2022-10-28 06:52:34 -04:00
|
|
|
else
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden");
|
|
|
|
return;
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
std::string file;
|
2023-01-05 04:59:09 -05:00
|
|
|
const std::string fsGame = (*Game::fs_gameDirVar)->current.string;
|
|
|
|
const auto path = std::format("{}\\{}{}", (*Game::fs_basepath)->current.string, isMap ? ""s : (fsGame + "\\"s), url);
|
2022-10-28 06:52:34 -04:00
|
|
|
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
|
|
|
|
{
|
|
|
|
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found %s", path.data());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
|
|
|
mg_printf(c, "%s", "Content-Type: application/octet-stream\r\n");
|
|
|
|
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.size()));
|
|
|
|
mg_printf(c, "%s", "Connection: close\r\n");
|
|
|
|
mg_printf(c, "%s", "\r\n");
|
2022-10-31 10:49:30 -04:00
|
|
|
mg_send(c, file.data(), file.size());
|
2022-10-28 06:52:34 -04:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static void HTMLHandler(mg_connection* c, mg_http_message* hm)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
auto url = "html" + std::string(hm->uri.ptr, hm->uri.len);
|
|
|
|
FileSystem::File file;
|
|
|
|
|
|
|
|
if (url.ends_with("/"))
|
|
|
|
{
|
|
|
|
url.append("index.html");
|
|
|
|
file = FileSystem::File(url);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
file = FileSystem::File(url);
|
|
|
|
if (!file.exists())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
url.append("/index.html");
|
2017-01-20 08:36:13 -05:00
|
|
|
file = FileSystem::File(url);
|
|
|
|
}
|
2022-10-28 06:52:34 -04:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
const auto mimeType = Utils::GetMimeType(url);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
if (file.exists())
|
|
|
|
{
|
|
|
|
mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n");
|
|
|
|
mg_printf(c, "Content-Type: %s\r\n", mimeType.data());
|
|
|
|
mg_printf(c, "Content-Length: %d\r\n", static_cast<int>(file.getBuffer().size()));
|
|
|
|
mg_printf(c, "%s", "Connection: close\r\n");
|
|
|
|
mg_printf(c, "%s", "\r\n");
|
2022-10-31 10:49:30 -04:00
|
|
|
mg_send(c, file.getBuffer().data(), file.getBuffer().size());
|
2022-10-28 06:52:34 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found");
|
|
|
|
}
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
static void EventHandler(mg_connection* c, int ev, void* ev_data, [[maybe_unused]] void* fn_data)
|
|
|
|
{
|
|
|
|
if (ev != MG_EV_HTTP_MSG)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
auto* hm = static_cast<mg_http_message*>(ev_data);
|
|
|
|
std::string url(hm->uri.ptr, hm->uri.len);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
if (url.starts_with("/info"))
|
|
|
|
{
|
|
|
|
const auto reply = InfoHandler();
|
|
|
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.data());
|
|
|
|
}
|
|
|
|
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
|
|
|
|
{
|
|
|
|
HTMLHandler(c, hm);
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
Download::Download()
|
|
|
|
{
|
2022-12-18 16:47:59 -05:00
|
|
|
AssertSize(Game::va_info_t, 0x804);
|
|
|
|
AssertSize(jmp_buf, 0x40);
|
|
|
|
AssertSize(Game::TraceThreadInfo, 0x8);
|
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
if (Dedicated::IsEnabled())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mg_mgr_init(&Mgr);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2022-10-28 06:52:34 -04:00
|
|
|
Network::OnStart([]
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
const auto* nc = mg_http_listen(&Mgr, Utils::String::VA(":%hu", Network::GetPort()), &EventHandler, &Mgr);
|
|
|
|
if (!nc)
|
2017-02-02 15:07:40 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
Logger::PrintError(Game::CON_CHANNEL_ERROR, "Failed to bind TCP socket, mod download won't work!\n");
|
2017-02-02 15:07:40 -05:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
});
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
ServerRunning = true;
|
|
|
|
Terminate = false;
|
2022-12-17 12:54:41 -05:00
|
|
|
ServerThread = Utils::Thread::CreateNamedThread("Mongoose", []
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-12-18 16:47:59 -05:00
|
|
|
Com_InitThreadData();
|
|
|
|
|
2022-12-01 13:24:44 -05:00
|
|
|
while (!Terminate)
|
2017-05-25 14:18:20 -04:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mg_mgr_poll(&Mgr, 100);
|
2017-05-25 14:18:20 -04:00
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-05-27 06:19:28 -04:00
|
|
|
Scheduler::Once([]
|
2017-01-22 07:44:14 -05:00
|
|
|
{
|
2022-07-02 13:52:57 -04: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, "");
|
2022-05-27 06:19:28 -04:00
|
|
|
}, Scheduler::Pipeline::MAIN);
|
2017-01-22 07:44:14 -05:00
|
|
|
|
2022-08-24 10:38:14 -04:00
|
|
|
UIScript::Add("mod_download_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
CLDownload.clear();
|
2017-01-20 08:36:13 -05:00
|
|
|
});
|
|
|
|
}
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2022-05-27 06:19:28 -04:00
|
|
|
Scheduler::Once([]
|
2017-06-26 07:04:30 -04:00
|
|
|
{
|
2022-12-31 09:03:33 -05:00
|
|
|
SV_wwwDownload = Dvar::Register<bool>("sv_wwwDownload", false, Game::DVAR_NONE, "Set to true to enable downloading maps/mods from an external server.");
|
|
|
|
SV_wwwBaseUrl = Dvar::Register<const char*>("sv_wwwBaseUrl", "", Game::DVAR_NONE, "Set to the base url for the external map download.");
|
2022-05-27 06:19:28 -04:00
|
|
|
}, Scheduler::Pipeline::MAIN);
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Download::~Download()
|
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
if (ServerRunning)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-10-28 06:52:34 -04:00
|
|
|
mg_mgr_free(&Mgr);
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
2017-01-23 16:06:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void Download::preDestroy()
|
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
Terminate = true;
|
|
|
|
if (ServerThread.joinable())
|
2017-06-02 06:26:08 -04:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
ServerThread.join();
|
2017-06-02 06:26:08 -04:00
|
|
|
}
|
|
|
|
|
2017-01-23 16:06:50 -05:00
|
|
|
if (!Dedicated::IsEnabled())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2022-12-01 13:24:44 -05:00
|
|
|
CLDownload.clear();
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|