Experimental moddownload

This commit is contained in:
momo5502 2016-08-31 17:54:08 +02:00
parent c5bb8547e0
commit 243ddc400d
9 changed files with 412 additions and 44 deletions

View File

@ -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();
}
}
}

View File

@ -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);
};
}

View File

@ -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());
}
}
}
}
}

View File

@ -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);
};
}

View File

@ -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)
{

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -74,6 +74,7 @@ namespace Utils
std::string protocol;
std::string server;
std::string document;
std::string port;
std::string raw;
};