Experimental moddownload
This commit is contained in:
parent
c5bb8547e0
commit
243ddc400d
@ -3,6 +3,229 @@
|
||||
namespace Components
|
||||
{
|
||||
mg_mgr Download::Mgr;
|
||||
Download::ClientDownload Download::CLDownload;
|
||||
|
||||
#pragma region Client
|
||||
|
||||
void Download::InitiateClientDownload(std::string mod)
|
||||
{
|
||||
if (Download::CLDownload.Running) return;
|
||||
|
||||
Download::CLDownload.Mutex.lock();
|
||||
Download::CLDownload.Progress = "Downloading mod";
|
||||
Download::CLDownload.Mutex.unlock();
|
||||
|
||||
Command::Execute("openmenu popup_reconnectingtoparty", true);
|
||||
|
||||
Download::CLDownload.Running = true;
|
||||
Download::CLDownload.Mod = mod;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
fDownload->downloading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev == MG_EV_RECV)
|
||||
{
|
||||
fDownload->receivedBytes += static_cast<size_t>(*reinterpret_cast<int*>(ev_data));
|
||||
|
||||
double progress = (100.0 / fDownload->file.Size) * fDownload->receivedBytes;
|
||||
fDownload->download->Mutex.lock();
|
||||
fDownload->download->Progress = fmt::sprintf("Downloading file (%d/%d) %s %d%%", fDownload->index + 1, fDownload->download->Files.size(), fDownload->file.Name.data(), static_cast<unsigned int>(progress));
|
||||
fDownload->download->Mutex.unlock();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
download->Mutex.lock();
|
||||
download->Progress = fmt::sprintf("Downloading file (%d/%d) %s 0%%", index + 1, download->Files.size(), file.Name.data());
|
||||
download->Mutex.unlock();
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
download->Valid = true;
|
||||
mg_mgr_init(&download->Mgr, &fDownload);
|
||||
mg_connect_http(&download->Mgr, Download::DownloadHandler, url.data(), NULL, NULL);
|
||||
|
||||
while (fDownload.downloading)
|
||||
{
|
||||
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())
|
||||
{
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Party::ConnectError("Failed to download the modlist!");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Download::ParseModList(download, list))
|
||||
{
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Party::ConnectError("Failed to parse the modlist!");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < download->Files.size(); ++i)
|
||||
{
|
||||
if (!Download::DownloadFile(download, i))
|
||||
{
|
||||
Dvar::Var("partyend_reason").Set(fmt::sprintf("Failed to download file: %s!", download->Files[i].Name.data()));
|
||||
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string mod = download->Mod;
|
||||
|
||||
download->Thread.detach();
|
||||
download->Clear();
|
||||
|
||||
// Run this on the main thread
|
||||
QuickPatch::Once([] ()
|
||||
{
|
||||
auto fsGame = Dvar::Var("fs_game");
|
||||
fsGame.Set(mod);
|
||||
fsGame.Get<Game::dvar_t*>()->pad2[0] = 1;
|
||||
|
||||
mod.clear();
|
||||
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty", true);
|
||||
|
||||
if (Dvar::Var("cl_modVidRestart").Get<bool>())
|
||||
{
|
||||
Command::Execute("vid_restart", false);
|
||||
}
|
||||
|
||||
Command::Execute("reconnect", false);
|
||||
});
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Server
|
||||
|
||||
bool Download::IsClient(mg_connection *nc)
|
||||
{
|
||||
@ -283,6 +506,8 @@ namespace Components
|
||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
Download::Download()
|
||||
{
|
||||
if (Dedicated::IsEnabled())
|
||||
@ -308,12 +533,22 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::Hook(0x5AC6E9, [] ()
|
||||
{
|
||||
// TODO: Perform moddownload here
|
||||
// Utils::Hook(0x5AC6E9, [] ()
|
||||
// {
|
||||
// // TODO: Perform moddownload here
|
||||
//
|
||||
// Game::CL_DownloadsComplete(0);
|
||||
// }, HOOK_CALL).Install()->Quick();
|
||||
|
||||
Game::CL_DownloadsComplete(0);
|
||||
}, HOOK_CALL).Install()->Quick();
|
||||
QuickPatch::OnFrame([]
|
||||
{
|
||||
if (Download::CLDownload.Running)
|
||||
{
|
||||
Download::CLDownload.Mutex.lock();
|
||||
Localization::SetTemp("MENU_RECONNECTING_TO_PARTY", Download::CLDownload.Progress);
|
||||
Download::CLDownload.Mutex.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +560,7 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Download::CLDownload.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,16 +10,79 @@ namespace Components
|
||||
const char* GetName() { return "Download"; };
|
||||
#endif
|
||||
|
||||
static void InitiateClientDownload(std::string mod);
|
||||
|
||||
private:
|
||||
class ClientDownload
|
||||
{
|
||||
public:
|
||||
ClientDownload() : Valid(false), Running(false) {}
|
||||
~ClientDownload() { this->Clear(); }
|
||||
|
||||
bool Running;
|
||||
bool Valid;
|
||||
mg_mgr Mgr;
|
||||
Network::Address Target;
|
||||
std::string Mod;
|
||||
std::mutex Mutex;
|
||||
std::thread Thread;
|
||||
std::string Progress;
|
||||
|
||||
class File
|
||||
{
|
||||
public:
|
||||
std::string Name;
|
||||
std::string Hash;
|
||||
size_t Size;
|
||||
};
|
||||
|
||||
std::vector<File> Files;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
this->Running = false;
|
||||
this->Mod.clear();
|
||||
this->Files.clear();
|
||||
|
||||
if (this->Valid)
|
||||
{
|
||||
this->Valid = false;
|
||||
mg_mgr_free(&(this->Mgr));
|
||||
}
|
||||
|
||||
this->Mutex.lock();
|
||||
this->Progress.clear();
|
||||
this->Mutex.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
class FileDownload
|
||||
{
|
||||
public:
|
||||
ClientDownload* download;
|
||||
ClientDownload::File file;
|
||||
|
||||
bool downloading;
|
||||
unsigned int index;
|
||||
std::string buffer;
|
||||
size_t receivedBytes;
|
||||
};
|
||||
|
||||
static mg_mgr Mgr;
|
||||
static ClientDownload CLDownload;
|
||||
|
||||
static void EventHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void ListHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void FileHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void InfoHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
static void DownloadHandler(mg_connection *nc, int ev, void *ev_data);
|
||||
|
||||
static bool IsClient(mg_connection *nc);
|
||||
static Game::client_t* GetClient(mg_connection *nc);
|
||||
static void Forbid(mg_connection *nc);
|
||||
|
||||
static void ModDownloader(ClientDownload* download);
|
||||
static bool ParseModList(ClientDownload* download, std::string list);
|
||||
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
||||
};
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ namespace Components
|
||||
|
||||
void Party::ConnectError(std::string message)
|
||||
{
|
||||
Localization::ClearTemp();
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Dvar::Var("partyend_reason").Set(message);
|
||||
Command::Execute("openmenu menu_xboxlive_partyended");
|
||||
@ -354,8 +355,9 @@ namespace Components
|
||||
{
|
||||
// Invalidate handler for future packets
|
||||
Party::Container.Valid = false;
|
||||
Party::Container.Info = info;
|
||||
|
||||
int matchType = atoi(info.Get("matchtype").data());
|
||||
Party::Container.MatchType = atoi(info.Get("matchtype").data());
|
||||
uint32_t securityLevel = static_cast<uint32_t>(atoi(info.Get("securityLevel").data()));
|
||||
|
||||
if (info.Get("challenge") != Party::Container.Challenge)
|
||||
@ -368,48 +370,55 @@ namespace Components
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Auth::IncreaseSecurityLevel(securityLevel, "reconnect");
|
||||
}
|
||||
else if (!matchType)
|
||||
else if (!Party::Container.MatchType)
|
||||
{
|
||||
Party::ConnectError("Server is not hosting a match.");
|
||||
}
|
||||
// Connect
|
||||
else if (matchType == 1) // Party
|
||||
else if(Party::Container.MatchType > 2 || Party::Container.MatchType < 0)
|
||||
{
|
||||
// Send playlist request
|
||||
Party::Container.RequestTime = Game::Sys_Milliseconds();
|
||||
Party::Container.AwaitingPlaylist = true;
|
||||
Network::SendCommand(address, "getplaylist");
|
||||
|
||||
// This is not a safe method
|
||||
// TODO: Fix actual error!
|
||||
if (Game::CL_IsCgameInitialized())
|
||||
{
|
||||
Command::Execute("disconnect", true);
|
||||
}
|
||||
Party::ConnectError("Invalid join response: Unknown matchtype");
|
||||
}
|
||||
else if (matchType == 2) // Match
|
||||
else if(!info.Get("fs_game").empty() && Dvar::Var("fs_game").Get<std::string>() != info.Get("fs_game"))
|
||||
{
|
||||
if (atoi(info.Get("clients").data()) >= atoi(info.Get("sv_maxclients").data()))
|
||||
{
|
||||
Party::ConnectError("@EXE_SERVERISFULL");
|
||||
}
|
||||
if (info.Get("mapname") == "" || info.Get("gametype") == "")
|
||||
{
|
||||
Party::ConnectError("Invalid map or gametype.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dvar::Var("xblive_privateserver").Set(true);
|
||||
|
||||
Game::Menus_CloseAll(Game::uiContext);
|
||||
|
||||
Game::_XSESSION_INFO hostInfo;
|
||||
Game::CL_ConnectFromParty(0, &hostInfo, *address.Get(), 0, 0, info.Get("mapname").data(), info.Get("gametype").data());
|
||||
}
|
||||
Command::Execute("closemenu popup_reconnectingtoparty");
|
||||
Download::InitiateClientDownload(info.Get("fs_game"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Party::ConnectError("Invalid join response: Unknown matchtype");
|
||||
if (Party::Container.MatchType == 1) // Party
|
||||
{
|
||||
// Send playlist request
|
||||
Party::Container.RequestTime = Game::Sys_Milliseconds();
|
||||
Party::Container.AwaitingPlaylist = true;
|
||||
Network::SendCommand(Party::Container.Target, "getplaylist");
|
||||
|
||||
// This is not a safe method
|
||||
// TODO: Fix actual error!
|
||||
if (Game::CL_IsCgameInitialized())
|
||||
{
|
||||
Command::Execute("disconnect", true);
|
||||
}
|
||||
}
|
||||
else if (Party::Container.MatchType == 2) // Match
|
||||
{
|
||||
if (atoi(Party::Container.Info.Get("clients").data()) >= atoi(Party::Container.Info.Get("sv_maxclients").data()))
|
||||
{
|
||||
Party::ConnectError("@EXE_SERVERISFULL");
|
||||
}
|
||||
if (Party::Container.Info.Get("mapname") == "" || Party::Container.Info.Get("gametype") == "")
|
||||
{
|
||||
Party::ConnectError("Invalid map or gametype.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dvar::Var("xblive_privateserver").Set(true);
|
||||
|
||||
Game::Menus_CloseAll(Game::uiContext);
|
||||
|
||||
Game::_XSESSION_INFO hostInfo;
|
||||
Game::CL_ConnectFromParty(0, &hostInfo, *Party::Container.Target.Get(), 0, 0, Party::Container.Info.Get("mapname").data(), Party::Container.Info.Get("gametype").data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace Components
|
||||
static void PlaylistContinue();
|
||||
static void PlaylistError(std::string error);
|
||||
|
||||
static void ConnectError(std::string message);
|
||||
|
||||
private:
|
||||
class JoinContainer
|
||||
{
|
||||
@ -27,10 +29,13 @@ namespace Components
|
||||
std::string Challenge;
|
||||
DWORD JoinTime;
|
||||
bool Valid;
|
||||
int MatchType;
|
||||
|
||||
Utils::InfoString Info;
|
||||
|
||||
// Party-specific stuff
|
||||
DWORD RequestTime;
|
||||
bool AwaitingPlaylist;
|
||||
bool AwaitingPlaylist;
|
||||
};
|
||||
|
||||
static JoinContainer Container;
|
||||
@ -40,8 +45,6 @@ namespace Components
|
||||
|
||||
static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description);
|
||||
|
||||
static void ConnectError(std::string message);
|
||||
|
||||
static DWORD UIDvarIntStub(char* dvar);
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace Utils
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = static_cast<int>(this->TokenString.size() - 1); i >= 0; i--)
|
||||
for (int i = static_cast<int>(this->TokenString.size() - 1); i >= 0; --i)
|
||||
{
|
||||
if (this->TokenString[i] == 0xFF)
|
||||
{
|
||||
|
@ -47,5 +47,40 @@ namespace Utils
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void CreateDirectory(std::string dir)
|
||||
{
|
||||
char opath[MAX_PATH];
|
||||
char *p;
|
||||
size_t len;
|
||||
|
||||
strncpy_s(opath, dir.data(), sizeof(opath));
|
||||
len = strlen(opath);
|
||||
|
||||
if (opath[len - 1] == L'/')
|
||||
{
|
||||
opath[len - 1] = L'\0';
|
||||
}
|
||||
|
||||
for (p = opath; *p; p++)
|
||||
{
|
||||
if (*p == L'/' || *p == L'\\')
|
||||
{
|
||||
*p = L'\0';
|
||||
|
||||
if (_access(opath, 0))
|
||||
{
|
||||
_mkdir(opath);
|
||||
}
|
||||
|
||||
*p = L'\\';
|
||||
}
|
||||
}
|
||||
|
||||
if (_access(opath, 0))
|
||||
{
|
||||
_mkdir(opath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ namespace Utils
|
||||
bool FileExists(std::string file);
|
||||
void WriteFile(std::string file, std::string data);
|
||||
std::string ReadFile(std::string file);
|
||||
void CreateDirectory(std::string dir);
|
||||
}
|
||||
}
|
||||
|
@ -104,10 +104,26 @@ namespace Utils
|
||||
WebIO::m_sUrl.document = server.substr(pos);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.port.clear();
|
||||
|
||||
pos = WebIO::m_sUrl.server.find(":");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
WebIO::m_sUrl.port = WebIO::m_sUrl.server.substr(pos + 1);
|
||||
WebIO::m_sUrl.server = WebIO::m_sUrl.server.substr(0, pos);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.raw.clear();
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.protocol);
|
||||
WebIO::m_sUrl.raw.append("://");
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.server);
|
||||
|
||||
if (!WebIO::m_sUrl.port.empty())
|
||||
{
|
||||
WebIO::m_sUrl.raw.append(":");
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.port);
|
||||
}
|
||||
|
||||
WebIO::m_sUrl.raw.append(WebIO::m_sUrl.document);
|
||||
|
||||
WebIO::m_isFTP = (WebIO::m_sUrl.protocol == "ftp");
|
||||
@ -217,6 +233,11 @@ namespace Utils
|
||||
wPort = INTERNET_DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
|
||||
if (!WebIO::m_sUrl.port.empty())
|
||||
{
|
||||
wPort = static_cast<WORD>(atoi(WebIO::m_sUrl.port.data()));
|
||||
}
|
||||
|
||||
const char* username = (WebIO::m_username.size() ? WebIO::m_username.data() : NULL);
|
||||
const char* password = (WebIO::m_password.size() ? WebIO::m_password.data() : NULL);
|
||||
WebIO::m_hConnect = InternetConnectA(WebIO::m_hSession, WebIO::m_sUrl.server.data(), wPort, username, password, dwService, dwFlag, 0);
|
||||
|
@ -74,6 +74,7 @@ namespace Utils
|
||||
std::string protocol;
|
||||
std::string server;
|
||||
std::string document;
|
||||
std::string port;
|
||||
std::string raw;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user