diff --git a/CHANGELOG.md b/CHANGELOG.md index c520efcf..9386cb43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,21 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Show friend avatars when they play IW4x (request) - Cod4 style fast download for usermaps +- Display a toast when an update is available. +- Use the hourglass cursor while loading assets (with the native cursor feature). +- Show bots in parenthesis after the number of players in the serverlist (request). ### Changed - +- Show friend avatars when they play IW4x (request). ### Fixed - Fix lags and frame drops caused by server sorting - Fix demos on custom maps - Can no longer join a lobby with an incorrect password +- Fix lags and frame drops caused by server sorting. +- Fix demos on custom maps. ## [0.5.0] - 2017-06-04 diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 85e6f59a..1ff8cf7e 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -11,12 +11,12 @@ namespace Components #pragma region Client - void Download::InitiateMapDownload(std::string map) + void Download::InitiateMapDownload(std::string map, bool needPassword) { - Download::InitiateClientDownload(map, true); + Download::InitiateClientDownload(map, needPassword, true); } - void Download::InitiateClientDownload(std::string mod, bool map) + void Download::InitiateClientDownload(std::string mod, bool needPassword, bool map) { if (Download::CLDownload.running) return; @@ -29,6 +29,18 @@ namespace Components Command::Execute("openmenu mod_download_popmenu", false); + if (needPassword) + { + std::string pass = Dvar::Var("password").get(); + if (!pass.length()) + { + // shouldn't ever happen but this is safe + Party::ConnectError("A password is required to connect to this server!"); + return; + } + Download::CLDownload.hashedPassword = Utils::Cryptography::SHA256::Compute(pass); + } + Download::CLDownload.running = true; Download::CLDownload.isMap = map; Download::CLDownload.mod = mod; @@ -37,6 +49,7 @@ namespace Components Download::CLDownload.lastTimeStamp = 0; Download::CLDownload.downBytes = 0; Download::CLDownload.timeStampBytes = 0; + Download::CLDownload.isPrivate = needPassword; Download::CLDownload.target = Party::Target(); Download::CLDownload.thread = std::thread(Download::ModDownloader, &Download::CLDownload); } @@ -192,7 +205,33 @@ namespace Components } } - std::string url = "http://" + download->target.getString() + "/file/" + (download->isMap ? "map/" : "") + file.name; + std::string host = "http://" + download->target.getString(); + std::string fastHost = "http://" + Dvar::Var("sv_wwwBaseUrl").get(); + + 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()) + { + url = fastHost + path; + } + else + { + url = host + "/file/" + (download->isMap ? "map/" : "") + file.name + + (download->isPrivate ? ("?password=" + download->hashedPassword) : ""); + } Download::FileDownload fDownload; fDownload.file = file; @@ -233,7 +272,9 @@ namespace Components std::string host = "http://" + download->target.getString(); - std::string list = Utils::WebIO("IW4x", host + (download->isMap ? "/map" : "/list")).setTimeout(5000)->get(); + 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; @@ -361,6 +402,33 @@ namespace Components return nullptr; } + bool Download::VerifyPassword(mg_connection *nc, http_message* message) + { + std::string g_password = Dvar::Var("g_password").get(); + + if (!g_password.size()) return true; + + Utils::Memory::Allocator* alloc = Utils::Memory::GetAllocator(); + + // sha256 hashes are 64 chars long but we're gonna be safe here + char* buffer = alloc->allocateArray(128); + int passLen = mg_get_http_var(&message->query_string, "password", buffer, 128); + + if (passLen <= 0 || std::string(buffer, passLen) != g_password)//Utils::Cryptography::SHA256::Compute(g_password)) + { + 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" @@ -372,11 +440,13 @@ namespace Components nc->flags |= MG_F_SEND_AND_CLOSE; } - void Download::MapHandler(mg_connection *nc, int ev, void* /*ev_data*/) + void Download::MapHandler(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(ev_data))) return; + static std::string mapnamePre; static json11::Json jsonList; @@ -423,11 +493,13 @@ namespace Components nc->flags |= MG_F_SEND_AND_CLOSE; } - void Download::ListHandler(mg_connection* nc, int ev, void* /*ev_data*/) + 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(ev_data))) return; + // if (!Download::IsClient(nc)) // { // Download::Forbid(nc); @@ -487,6 +559,8 @@ namespace Components http_message* message = reinterpret_cast(ev_data); + //if (!Download::VerifyPassword(nc, message)) return; + // if (!Download::IsClient(nc)) // { // Download::Forbid(nc); @@ -567,12 +641,12 @@ namespace Components } } - void Download::InfoHandler(mg_connection* nc, int ev, void* /*ev_data*/) + 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); + //if (!Download::VerifyPassword(nc, reinterpret_cast(ev_data))) return; Utils::InfoString status = ServerInfo::GetInfo(); @@ -738,6 +812,12 @@ namespace Components mg_mgr_poll(&Download::Mgr, 100); } }); + + Dvar::OnInit([]() + { + Dvar::Register("sv_wwwDownload", false, Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to true to enable downloading maps/mods from an external server."); + Dvar::Register("sv_wwwBaseUrl", "", Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to the base url for the external map download."); + }); } else { @@ -746,6 +826,8 @@ namespace Components Dvar::Register("ui_dl_timeLeft", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); Dvar::Register("ui_dl_progress", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); Dvar::Register("ui_dl_transRate", "", Game::dvar_flag::DVAR_FLAG_NONE, ""); + Dvar::Register("sv_wwwDownload", false, Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to true to enable downloading maps/mods from an external server."); + Dvar::Register("sv_wwwBaseUrl", "", Game::dvar_flag::DVAR_FLAG_DEDISAVED, "Set to the base url for the external map download."); }); UIScript::Add("mod_download_cancel", [](UIScript::Token) diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index 186f237e..911e983e 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -11,8 +11,8 @@ namespace Components void preDestroy() override; - static void InitiateClientDownload(std::string mod, bool map = false); - static void InitiateMapDownload(std::string map); + static void InitiateClientDownload(std::string mod, bool needPassword, bool map = false); + static void InitiateMapDownload(std::string map, bool needPassword); private: class ClientDownload @@ -25,8 +25,10 @@ namespace Components bool valid; bool terminateThread; bool isMap; + bool isPrivate; mg_mgr mgr; Network::Address target; + std::string hashedPassword; std::string mod; std::thread thread; @@ -209,6 +211,8 @@ namespace Components static std::thread ServerThread; static bool Terminate; + static bool VerifyPassword(mg_connection *nc, http_message* message); + static void EventHandler(mg_connection *nc, int ev, void *ev_data); static void ListHandler(mg_connection *nc, int ev, void *ev_data); static void MapHandler(mg_connection *nc, int ev, void *ev_data); diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 51b3ef2b..3b5a23b2 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -154,7 +154,7 @@ namespace Components Utils::Hook::Set(0x5AC2CF, 0xEB); // CL_ParseGamestate Utils::Hook::Set(0x5AC2C3, 0xEB); // CL_ParseGamestate - // AnonymousAddRequest + // AnonymousAddRequest Utils::Hook::Set(0x5B5E18, 0xEB); Utils::Hook::Set(0x5B5E64, 0xEB); Utils::Hook::Nop(0x5B5E5C, 2); @@ -217,7 +217,7 @@ namespace Components Utils::Hook::Set(0x4D6171, 0); Utils::Hook::Nop(0x4077A1, 5); // PartyMigrate_Frame - // Patch playlist stuff for non-party behavior + // Patch playlist stuff for non-party behavior Utils::Hook::Set(0x4A4093, &partyEnable); Utils::Hook::Set(0x4573F1, &partyEnable); Utils::Hook::Set(0x5B1A0C, &partyEnable); @@ -370,6 +370,9 @@ namespace Components info.set("matchtype", "0"); } + info.set("wwwDownload", (Dvar::Var("sv_wwwDownload").get() ? "1" : "0")); + info.set("wwwUrl", Dvar::Var("sv_wwwBaseUrl").get()); + Network::SendCommand(address, "infoResponse", "\\" + info.build()); }); @@ -393,6 +396,18 @@ namespace Components std::string mod = Dvar::Var("fs_game").get(); + // set fast server stuff here so its updated when we go to download stuff + if (info.get("wwwDownload") == "1"s) + { + Dvar::Var("sv_wwwDownload").set(true); + Dvar::Var("sv_wwwBaseUrl").set(info.get("wwwUrl")); + } + else + { + Dvar::Var("sv_wwwDownload").set(false); + Dvar::Var("sv_wwwBaseUrl").set(""); + } + if (info.get("challenge") != Party::Container.challenge) { Party::ConnectError("Invalid join response: Challenge mismatch."); @@ -415,15 +430,19 @@ namespace Components { Party::ConnectError("Invalid map or gametype."); } + else if (Party::Container.info.get("isPrivate") == "1"s && !Dvar::Var("password").get().length()) + { + Party::ConnectError("A password is required to join this server! Set it at the bottom of the serverlist."); + } else if (isUsermap && usermapHash != Maps::GetUsermapHash(info.get("mapname"))) { Command::Execute("closemenu popup_reconnectingtoparty"); - Download::InitiateMapDownload(info.get("mapname")); + Download::InitiateMapDownload(info.get("mapname"), info.get("isPrivate") == "1"); } else if (!info.get("fs_game").empty() && Utils::String::ToLower(mod) != Utils::String::ToLower(info.get("fs_game"))) { Command::Execute("closemenu popup_reconnectingtoparty"); - Download::InitiateClientDownload(info.get("fs_game")); + Download::InitiateClientDownload(info.get("fs_game"), info.get("isPrivate") == "1"s); } else if (!Dvar::Var("fs_game").get().empty() && info.get("fs_game").empty()) {