2017-01-20 08:36:13 -05:00
|
|
|
#include "STDInclude.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-01-20 08:36:13 -05:00
|
|
|
|
2017-05-25 14:18:20 -04:00
|
|
|
std::thread Download::ServerThread;
|
|
|
|
bool Download::Terminate;
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
#pragma region Client
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
void Download::InitiateMapDownload(std::string map)
|
|
|
|
{
|
|
|
|
Download::InitiateClientDownload(map, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Download::InitiateClientDownload(std::string mod, bool map)
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
if (Download::CLDownload.running) return;
|
|
|
|
|
2017-05-31 09:45:12 -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");
|
|
|
|
});
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Command::Execute("openmenu mod_download_popmenu", false);
|
|
|
|
|
|
|
|
Download::CLDownload.running = true;
|
2017-04-06 16:22:47 -04:00
|
|
|
Download::CLDownload.isMap = map;
|
2017-01-20 08:36:13 -05:00
|
|
|
Download::CLDownload.mod = mod;
|
|
|
|
Download::CLDownload.terminateThread = false;
|
|
|
|
Download::CLDownload.totalBytes = 0;
|
|
|
|
Download::CLDownload.lastTimeStamp = 0;
|
|
|
|
Download::CLDownload.downBytes = 0;
|
|
|
|
Download::CLDownload.timeStampBytes = 0;
|
|
|
|
Download::CLDownload.target = Party::Target();
|
|
|
|
Download::CLDownload.thread = std::thread(Download::ModDownloader, &Download::CLDownload);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Download::ParseModList(ClientDownload* download, std::string list)
|
|
|
|
{
|
|
|
|
if (!download) return false;
|
|
|
|
download->files.clear();
|
|
|
|
|
|
|
|
std::string error;
|
|
|
|
json11::Json listData = json11::Json::parse(list, error);
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
if (!error.empty() || !listData.is_array())
|
|
|
|
{
|
|
|
|
Logger::Print("Error: %s\n", error.data());
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
download->totalBytes = 0;
|
|
|
|
|
|
|
|
for (auto& file : listData.array_items())
|
|
|
|
{
|
|
|
|
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.string_value();
|
|
|
|
fileEntry.hash = hash.string_value();
|
|
|
|
fileEntry.size = static_cast<size_t>(size.number_value());
|
|
|
|
|
|
|
|
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;
|
2017-01-20 08:36:13 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ev == MG_EV_RECV)
|
|
|
|
{
|
|
|
|
size_t bytes = static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
|
|
|
|
fDownload->receivedBytes += bytes;
|
|
|
|
fDownload->download->downBytes += bytes;
|
|
|
|
fDownload->download->timeStampBytes += bytes;
|
|
|
|
|
|
|
|
double progress = 0;
|
|
|
|
if (fDownload->download->totalBytes)
|
|
|
|
{
|
|
|
|
progress = (100.0 / fDownload->download->totalBytes) * fDownload->download->downBytes;
|
|
|
|
}
|
|
|
|
|
2017-01-22 07:44:14 -05:00
|
|
|
static unsigned int dlIndex, dlSize, dlProgress;
|
|
|
|
dlIndex = fDownload->index + 1;
|
|
|
|
dlSize = fDownload->download->files.size();
|
|
|
|
dlProgress = static_cast<unsigned int>(progress);
|
|
|
|
|
|
|
|
static bool framePushed = false;
|
|
|
|
|
|
|
|
if (!framePushed)
|
|
|
|
{
|
|
|
|
framePushed = true;
|
2017-05-31 09:45:12 -04:00
|
|
|
Scheduler::Once([]()
|
2017-01-22 07:44:14 -05:00
|
|
|
{
|
|
|
|
framePushed = false;
|
|
|
|
Dvar::Var("ui_dl_progress").set(Utils::String::VA("(%d/%d) %d%%", dlIndex, dlSize, dlProgress));
|
|
|
|
});
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
int delta = Game::Sys_Milliseconds() - fDownload->download->lastTimeStamp;
|
|
|
|
if (delta > 300)
|
|
|
|
{
|
|
|
|
bool doFormat = fDownload->download->lastTimeStamp != 0;
|
|
|
|
fDownload->download->lastTimeStamp = Game::Sys_Milliseconds();
|
|
|
|
|
|
|
|
size_t 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)
|
|
|
|
{
|
2017-01-22 07:44:14 -05:00
|
|
|
static size_t dlTsBytes;
|
|
|
|
static int dlDelta, dlTimeLeft;
|
|
|
|
dlTimeLeft = timeLeft;
|
|
|
|
dlDelta = delta;
|
|
|
|
dlTsBytes = fDownload->download->timeStampBytes;
|
|
|
|
|
2017-05-31 09:45:12 -04:00
|
|
|
Scheduler::Once([]()
|
2017-01-22 07:44:14 -05:00
|
|
|
{
|
|
|
|
Dvar::Var("ui_dl_timeLeft").set(Utils::String::FormatTimeSpan(dlTimeLeft));
|
|
|
|
Dvar::Var("ui_dl_transRate").set(Utils::String::FormatBandwidth(dlTsBytes, dlDelta));
|
|
|
|
});
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fDownload->download->timeStampBytes = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
std::string url = "http://" + download->target.getString() + "/file/" + (download->isMap ? "map/" : "") + file.name;
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Download::FileDownload fDownload;
|
|
|
|
fDownload.file = file;
|
|
|
|
fDownload.index = index;
|
|
|
|
fDownload.download = download;
|
|
|
|
fDownload.downloading = true;
|
|
|
|
fDownload.receivedBytes = 0;
|
|
|
|
|
|
|
|
Utils::String::Replace(url, " ", "%20");
|
|
|
|
|
|
|
|
download->valid = true;
|
2017-05-14 14:14:52 -04:00
|
|
|
ZeroMemory(&download->mgr, sizeof download->mgr);
|
2017-01-20 08:36:13 -05:00
|
|
|
mg_mgr_init(&download->mgr, &fDownload);
|
2017-01-20 16:41:03 -05:00
|
|
|
mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), nullptr, nullptr);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
while (fDownload.downloading && !fDownload.download->terminateThread)
|
|
|
|
{
|
|
|
|
mg_mgr_poll(&download->mgr, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
mg_mgr_free(&download->mgr);
|
|
|
|
download->valid = false;
|
|
|
|
|
|
|
|
if (fDownload.buffer.size() != file.size || Utils::String::DumpHex(Utils::Cryptography::SHA256::Compute(fDownload.buffer), "") != file.hash)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (!download) download = &Download::CLDownload;
|
|
|
|
|
|
|
|
std::string host = "http://" + download->target.getString();
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
std::string list = Utils::WebIO("IW4x", host + (download->isMap ? "/map" : "/list")).setTimeout(5000)->get();
|
2017-01-20 08:36:13 -05:00
|
|
|
if (list.empty())
|
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2017-05-31 09:45:12 -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!");
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
if (!Download::ParseModList(download, list))
|
|
|
|
{
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2017-05-31 09:45:12 -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!");
|
|
|
|
});
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
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();
|
|
|
|
|
2017-05-31 09:45:12 -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");
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (download->terminateThread) return;
|
|
|
|
|
|
|
|
download->thread.detach();
|
|
|
|
download->clear();
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
if(download->isMap)
|
|
|
|
{
|
2017-05-31 09:45:12 -04:00
|
|
|
Scheduler::Once([]()
|
2017-04-06 16:22:47 -04:00
|
|
|
{
|
|
|
|
Command::Execute("reconnect", false);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-04-06 16:22:47 -04:00
|
|
|
// Run this on the main thread
|
2017-05-31 09:45:12 -04:00
|
|
|
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-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);
|
|
|
|
});
|
|
|
|
}
|
2017-01-20 08:36:13 -05: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);
|
|
|
|
|
|
|
|
for (int i = 0; i < *Game::svs_numclients; ++i)
|
|
|
|
{
|
|
|
|
Game::client_t* client = &Game::svs_clients[i];
|
|
|
|
|
|
|
|
if (client->state >= 3)
|
|
|
|
{
|
|
|
|
if (address.getIP().full == Network::Address(client->addr).getIP().full)
|
|
|
|
{
|
|
|
|
return client;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
void Download::MapHandler(mg_connection *nc, int ev, void* /*ev_data*/)
|
|
|
|
{
|
|
|
|
// Only handle http requests
|
|
|
|
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
|
|
|
|
|
|
static std::string mapnamePre;
|
|
|
|
static json11::Json jsonList;
|
|
|
|
|
|
|
|
std::string mapname = Maps::GetUserMap()->getName();
|
|
|
|
if(!Maps::GetUserMap()->isValid())
|
|
|
|
{
|
|
|
|
mapnamePre.clear();
|
|
|
|
jsonList = std::vector<json11::Json>();
|
|
|
|
}
|
|
|
|
else if (!mapname.empty() && mapname != mapnamePre)
|
|
|
|
{
|
|
|
|
std::vector<json11::Json> fileList;
|
|
|
|
|
|
|
|
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, json11::Json> file;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
void Download::ListHandler(mg_connection* nc, int ev, void* /*ev_data*/)
|
|
|
|
{
|
|
|
|
// Only handle http requests
|
|
|
|
if (ev != MG_EV_HTTP_REQUEST) return;
|
|
|
|
|
|
|
|
// if (!Download::IsClient(nc))
|
|
|
|
// {
|
|
|
|
// Download::Forbid(nc);
|
|
|
|
// }
|
|
|
|
// else
|
|
|
|
{
|
|
|
|
static std::string fsGamePre;
|
|
|
|
static json11::Json jsonList;
|
|
|
|
|
|
|
|
std::string fsGame = Dvar::Var("fs_game").get<std::string>();
|
|
|
|
|
|
|
|
if (!fsGame.empty() && fsGame != fsGamePre)
|
|
|
|
{
|
|
|
|
std::vector<json11::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;
|
2017-01-20 16:41:03 -05:00
|
|
|
if (strstr(i->data(), "_svr_") == nullptr && Utils::IO::FileExists(filename))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
std::map<std::string, json11::Json> file;
|
2017-04-06 16:22:47 -04:00
|
|
|
std::string fileBuffer = Utils::IO::ReadFile(filename);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
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::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);
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Utils::String::Replace(url, "%20", " ");
|
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
bool isMap = false;
|
|
|
|
if (Utils::String::StartsWith(url, "map/"))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-04-06 16:22:47 -04:00
|
|
|
isMap = true;
|
|
|
|
url = url.substr(4);
|
|
|
|
|
|
|
|
std::string mapname = 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]))
|
|
|
|
{
|
|
|
|
isValidFile = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!Maps::GetUserMap()->isValid() || !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-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
2017-02-10 09:18:11 -05:00
|
|
|
std::string file;
|
2017-01-20 08:36:13 -05:00
|
|
|
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-01-20 08:36:13 -05:00
|
|
|
|
2017-04-06 16:22:47 -04:00
|
|
|
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Download::InfoHandler(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);
|
|
|
|
|
|
|
|
Utils::InfoString status = ServerInfo::GetInfo();
|
|
|
|
|
|
|
|
std::map<std::string, json11::Json> info;
|
|
|
|
info["status"] = status.to_json();
|
|
|
|
|
|
|
|
std::vector<json11::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, json11::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_t*>(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", json11::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())
|
|
|
|
{
|
2017-05-25 14:18:20 -04:00
|
|
|
Download::Terminate = false;
|
2017-05-14 14:14:52 -04:00
|
|
|
ZeroMemory(&Download::Mgr, sizeof Download::Mgr);
|
2017-01-20 16:41:03 -05:00
|
|
|
mg_mgr_init(&Download::Mgr, nullptr);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
|
|
|
Network::OnStart([] ()
|
|
|
|
{
|
2017-05-31 12:03:05 -04:00
|
|
|
mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", Network::GetPort()), Download::EventHandler);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2017-02-02 15:07:40 -05:00
|
|
|
if (nc)
|
|
|
|
{
|
|
|
|
// Handle special requests
|
|
|
|
mg_register_http_endpoint(nc, "/info", Download::InfoHandler);
|
|
|
|
mg_register_http_endpoint(nc, "/list", Download::ListHandler);
|
2017-04-08 09:32:51 -04:00
|
|
|
mg_register_http_endpoint(nc, "/map", Download::MapHandler);
|
2017-02-14 11:12:07 -05:00
|
|
|
mg_register_http_endpoint(nc, "/file/", Download::FileHandler);
|
2017-01-20 08:36:13 -05:00
|
|
|
|
2017-02-02 15:07:40 -05:00
|
|
|
mg_set_protocol_http_websocket(nc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Logger::Print("Failed to bind TCP socket, moddownload won't work!\n");
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
});
|
|
|
|
|
2017-05-25 14:18:20 -04:00
|
|
|
Download::Terminate = false;
|
|
|
|
Download::ServerThread = std::thread([]
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
2017-05-25 14:18:20 -04:00
|
|
|
while (!Download::Terminate)
|
|
|
|
{
|
|
|
|
mg_mgr_poll(&Download::Mgr, 100);
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-01-22 07:44:14 -05:00
|
|
|
Dvar::OnInit([]()
|
|
|
|
{
|
|
|
|
Dvar::Register<const char*>("ui_dl_timeLeft", "", Game::dvar_flag::DVAR_FLAG_NONE, "");
|
|
|
|
Dvar::Register<const char*>("ui_dl_progress", "", Game::dvar_flag::DVAR_FLAG_NONE, "");
|
|
|
|
Dvar::Register<const char*>("ui_dl_transRate", "", Game::dvar_flag::DVAR_FLAG_NONE, "");
|
|
|
|
});
|
|
|
|
|
2017-01-20 08:36:13 -05:00
|
|
|
UIScript::Add("mod_download_cancel", [] (UIScript::Token)
|
|
|
|
{
|
|
|
|
Download::CLDownload.clear();
|
|
|
|
});
|
|
|
|
}
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-31 09:45:12 -04:00
|
|
|
Scheduler::OnFrame([]()
|
2017-05-14 14:14:52 -04:00
|
|
|
{
|
2017-05-15 15:57:45 -04:00
|
|
|
int workingCount = 0;
|
|
|
|
|
2017-05-14 14:14:52 -04:00
|
|
|
for(auto i = Download::ScriptDownloads.begin(); i != Download::ScriptDownloads.end();)
|
|
|
|
{
|
2017-05-15 15:57:45 -04:00
|
|
|
auto download = *i;
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-15 15:57:45 -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)
|
|
|
|
{
|
|
|
|
if (workingCount > 5) break;
|
|
|
|
if(!download->isWorking())
|
|
|
|
{
|
|
|
|
download->startWorking();
|
|
|
|
++workingCount;
|
2017-05-14 14:14:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Script::OnVMShutdown([]()
|
|
|
|
{
|
|
|
|
Download::ScriptDownloads.clear();
|
|
|
|
});
|
|
|
|
|
2017-05-15 15:57:45 -04:00
|
|
|
if (Dedicated::IsEnabled() || Flags::HasFlag("scriptablehttp"))
|
2017-05-14 14:14:52 -04:00
|
|
|
{
|
2017-05-15 15:57:45 -04:00
|
|
|
Script::AddFunction("httpGet", [](Game::scr_entref_t)
|
|
|
|
{
|
|
|
|
if (Game::Scr_GetNumParam() < 1) return;
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-15 15:57:45 -04:00
|
|
|
std::string url = Game::Scr_GetString(0);
|
|
|
|
unsigned int object = Game::AllocObject();
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-15 15:57:45 -04:00
|
|
|
Game::Scr_AddObject(object);
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-15 15:57:45 -04:00
|
|
|
Download::ScriptDownloads.push_back(std::make_shared<ScriptDownload>(url, object));
|
|
|
|
Game::RemoveRefToObject(object);
|
|
|
|
});
|
2017-05-14 14:14:52 -04:00
|
|
|
|
2017-05-15 15:57:45 -04:00
|
|
|
Script::AddFunction("httpCancel", [](Game::scr_entref_t)
|
2017-05-14 14:14:52 -04:00
|
|
|
{
|
2017-05-15 15:57:45 -04:00
|
|
|
if (Game::Scr_GetNumParam() < 1) return;
|
|
|
|
|
|
|
|
unsigned int object = Game::Scr_GetObject(0);
|
|
|
|
for (auto& download : Download::ScriptDownloads)
|
2017-05-14 14:14:52 -04:00
|
|
|
{
|
2017-05-15 15:57:45 -04:00
|
|
|
if (object == download->getObject())
|
|
|
|
{
|
|
|
|
download->cancel();
|
|
|
|
break;
|
|
|
|
}
|
2017-05-14 14:14:52 -04:00
|
|
|
}
|
2017-05-15 15:57:45 -04:00
|
|
|
});
|
|
|
|
}
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Download::~Download()
|
|
|
|
{
|
|
|
|
if (Dedicated::IsEnabled())
|
|
|
|
{
|
|
|
|
mg_mgr_free(&Download::Mgr);
|
|
|
|
}
|
2017-01-23 16:06:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void Download::preDestroy()
|
|
|
|
{
|
2017-06-02 06:26:08 -04:00
|
|
|
Download::Terminate = true;
|
|
|
|
if (Download::ServerThread.joinable())
|
|
|
|
{
|
|
|
|
Download::ServerThread.join();
|
|
|
|
}
|
|
|
|
|
2017-01-23 16:06:50 -05:00
|
|
|
if (!Dedicated::IsEnabled())
|
2017-01-20 08:36:13 -05:00
|
|
|
{
|
|
|
|
Download::CLDownload.clear();
|
|
|
|
}
|
2017-05-14 14:14:52 -04:00
|
|
|
|
|
|
|
Download::ScriptDownloads.clear();
|
2017-01-20 08:36:13 -05:00
|
|
|
}
|
|
|
|
}
|