#include "STDInclude.hpp" namespace Components { mg_mgr Download::Mgr; Download::ClientDownload Download::CLDownload; #pragma region Client void Download::InitiateClientDownload(std::string mod) { if (Download::CLDownload.running) return; Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(0)); Localization::SetTemp("MPUI_PROGRESS_DL", "(0/0) %"); Localization::SetTemp("MPUI_TRANS_RATE", "0.0 MB/s"); Command::Execute("openmenu mod_download_popmenu", false); Download::CLDownload.running = true; Download::CLDownload.mod = mod; Download::CLDownload.terminateThread = false; 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); if (!error.empty() || !listData.is_array()) return false; 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.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(ev_data); Download::FileDownload* fDownload = reinterpret_cast(nc->mgr->user_data); if (ev == MG_EV_CONNECT) { if (hm->message.p) { fDownload->downloading = false; return; } } if (ev == MG_EV_RECV) { size_t bytes = static_cast(*reinterpret_cast(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; } Localization::SetTemp("MPUI_PROGRESS_DL", Utils::String::VA("(%d/%d) %d%%", fDownload->index + 1, fDownload->download->files.size(), static_cast(progress))); 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(timeLeftD); } if (doFormat) { Localization::SetTemp("MPUI_EST_TIME_LEFT", Utils::String::FormatTimeSpan(timeLeft)); Localization::SetTemp("MPUI_TRANS_RATE", Utils::String::FormatBandwidth(fDownload->download->timeStampBytes, delta)); } 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; 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; } } std::string url = "http://" + download->target.getString() + "/file/" + file.name; 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; mg_mgr_init(&download->mgr, &fDownload); mg_connect_http(&download->mgr, Download::DownloadHandler, url.data(), NULL, NULL); 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; } Utils::IO::CreateDirectory(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 list = Utils::WebIO("IW4x", host + "/list").setTimeout(5000)->get(); if (list.empty()) { if (download->terminateThread) return; download->thread.detach(); download->clear(); QuickPatch::Once([] () { 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(); QuickPatch::Once([] () { Party::ConnectError("Failed to parse the modlist!"); }); return; } if (download->terminateThread) return; std::string 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(); QuickPatch::Once([mod] () { Dvar::Var("partyend_reason").set(mod); Localization::ClearTemp(); Command::Execute("closemenu mod_download_popmenu"); Command::Execute("openmenu menu_xboxlive_partyended"); }); return; } } if (download->terminateThread) return; download->thread.detach(); download->clear(); // Run this on the main thread QuickPatch::Once([mod] () { auto fsGame = Dvar::Var("fs_game"); fsGame.set(mod); fsGame.get()->modified = true; Localization::ClearTemp(); Command::Execute("closemenu mod_download_popmenu", false); if (Dvar::Var("cl_modVidRestart").get()) { Command::Execute("vid_restart", false); } Command::Execute("reconnect", false); }); } #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; } 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(); if (!fsGame.empty() && fsGame != fsGamePre) { std::vector fileList; fsGamePre = fsGame; std::string path = Dvar::Var("fs_basepath").get() + "\\" + 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_") == NULL && Utils::IO::FileExists(filename)) { std::map file; std::string fileBuffer = Utils::IO::ReadFile(path + "\\" + *i); file["name"] = *i; file["size"] = static_cast(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(ev_data); // if (!Download::IsClient(nc)) // { // Download::Forbid(nc); // } // else { std::string url(message->uri.p, message->uri.len); Utils::String::Replace(url, "\\", "/"); url = url.substr(6); Utils::String::Replace(url, "%20", " "); if (url.find_first_of("/") != std::string::npos || (!Utils::String::EndsWith(url, ".iwd") && url != "mod.ff") || strstr(url.data(), "_svr_") != NULL) { Download::Forbid(nc); return; } std::string fsGame = Dvar::Var("fs_game").get(); std::string path = Dvar::Var("fs_basepath").get() + "\\" + fsGame + "\\" + url; if (fsGame.empty() || !Utils::IO::FileExists(path)) { 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 { std::string file = Utils::IO::ReadFile(path); 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(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(ev_data); Utils::InfoString status = ServerInfo::GetInfo(); std::map info; info["status"] = status.to_json(); std::vector players; // Build player list for (int i = 0; i < atoi(status.get("sv_maxclients").data()); ++i) // Maybe choose 18 here? { std::map playerInfo; playerInfo["score"] = 0; playerInfo["ping"] = 0; playerInfo["name"] = ""; if (Dvar::Var("sv_running").get()) { 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(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(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!
You are%s connected to this server!", (Download::IsClient(nc) ? " " : " not")); // // Game::client_t* client = Download::GetClient(nc); // // if (client) // { // mg_printf(nc, "
Hello %s!", client->name); // } // } // else { //std::string path = (Dvar::Var("fs_basepath").get() + "\\" 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(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()) { mg_mgr_init(&Download::Mgr, NULL); Network::OnStart([] () { mg_connection* nc = mg_bind(&Download::Mgr, Utils::String::VA("%hu", (Dvar::Var("net_port").get() & 0xFFFF)), Download::EventHandler); // Handle special requests mg_register_http_endpoint(nc, "/info", Download::InfoHandler); mg_register_http_endpoint(nc, "/list", Download::ListHandler); mg_register_http_endpoint(nc, "/file", Download::FileHandler); mg_set_protocol_http_websocket(nc); }); QuickPatch::OnFrame([] { mg_mgr_poll(&Download::Mgr, 0); }); } else { UIScript::Add("mod_download_cancel", [] (UIScript::Token) { Download::CLDownload.clear(); }); } } Download::~Download() { if (Dedicated::IsEnabled()) { mg_mgr_free(&Download::Mgr); } else { Download::CLDownload.clear(); } } }