Start implementing moddownload

This commit is contained in:
momo5502 2016-06-04 17:06:49 +02:00
parent 843580f164
commit d59e70ff2a
9 changed files with 81 additions and 490 deletions

6
.gitmodules vendored
View File

@ -5,9 +5,6 @@
[submodule "deps/json11"]
path = deps/json11
url = https://github.com/dropbox/json11.git
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio.git
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
@ -24,3 +21,6 @@
[submodule "deps/pdcurses"]
path = deps/pdcurses
url = https://github.com/wmcbrine/PDCurses.git
[submodule "deps/mongoose"]
path = deps/mongoose
url = https://github.com/cesanta/mongoose

1
deps/asio vendored

@ -1 +0,0 @@
Subproject commit 722f7e2be05a51c69644662ec514d6149b2b7ef8

1
deps/mongoose vendored Submodule

@ -0,0 +1 @@
Subproject commit dfde5785a62bc4b59f504a5545ce8476ad09f88d

View File

@ -148,13 +148,13 @@ workspace "iw4x"
filter {}
-- Dependency libraries
links { "zlib", "json11", "pdcurses", "libtomcrypt", "libtommath", "protobuf" }
links { "zlib", "json11", "pdcurses", "libtomcrypt", "libtommath", "protobuf", "mongoose" }
includedirs
{
"./deps/zlib",
"./deps/json11",
"./deps/pdcurses",
"./deps/asio/asio/include",
"./deps/mongoose",
"./deps/libtomcrypt/src/headers",
"./deps/libtommath",
"./deps/protobuf/src",
@ -261,6 +261,23 @@ workspace "iw4x"
kind "StaticLib"
-- json11
project "mongoose"
language "C"
files
{
"./deps/mongoose/*.c",
"./deps/mongoose/*.h"
}
-- not our code, ignore POSIX usage warnings for now
warnings "Off"
-- always build as static lib, as json11 doesn't export anything
kind "StaticLib"
-- pdcurses
project "pdcurses"
language "C"

View File

@ -2,426 +2,74 @@
namespace Components
{
Download::Container Download::DataContainer;
mg_mgr Download::Mgr;
Download::Container::DownloadCL* Download::FindClientDownload(int id)
void Download::EventHandler(mg_connection *nc, int ev, void *ev_data)
{
for (auto &download : Download::DataContainer.ClientDownloads)
// Only handle http requests
if (ev != MG_EV_HTTP_REQUEST) return;
http_message* message = reinterpret_cast<http_message*>(ev_data);
if (std::string(message->uri.p, message->uri.len) == "/")
{
if (download.id == id)
{
return &download;
}
mg_printf(nc, "%s",
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"Hi fella!");
}
return nullptr;
}
Download::Container::DownloadSV* Download::FindServerDownload(int id)
{
for (auto &download : Download::DataContainer.ServerDownloads)
else
{
if (download.id == id)
{
return &download;
}
mg_printf(nc, "%s",
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<h1>404 - Not Found</h1>");
}
return nullptr;
}
void Download::RemoveClientDownload(int id)
{
for (auto i = Download::DataContainer.ClientDownloads.begin(); i != Download::DataContainer.ClientDownloads.end(); ++i)
{
if (i->id == id)
{
Download::DataContainer.ClientDownloads.erase(i);
return;
}
}
}
void Download::RemoveServerDownload(int id)
{
for (auto i = Download::DataContainer.ServerDownloads.begin(); i != Download::DataContainer.ServerDownloads.end(); ++i)
{
if (i->id == id)
{
Download::DataContainer.ServerDownloads.erase(i);
return;
}
}
}
bool Download::HasSentPacket(Download::Container::DownloadSV* download, int packet)
{
for (auto sentPacket : download->sentParts)
{
if (packet == sentPacket)
{
return true;
}
}
return false;
}
bool Download::HasReceivedPacket(Download::Container::DownloadCL* download, int packet)
{
if (!download->parts.empty())
{
for (auto i = download->parts.begin(); i != download->parts.end(); ++i)
{
if (i->first == packet)
{
return true;
}
}
}
return false;
}
bool Download::HasReceivedAllPackets(Download::Container::DownloadCL* download)
{
for (int i = 0; i < download->maxParts; ++i)
{
if (!Download::HasReceivedPacket(download, i))
{
return false;
}
}
return true;
}
int Download::ReadPacketId(std::string &data)
{
int id = *(int*)data.data();
data = std::string(data.data() + sizeof(int), data.size() - sizeof(int));
return id;
}
// Client handlers
void Download::AckRequest(Network::Address target, std::string data)
{
if (data.size() < sizeof(Download::Container::AckRequest)) return; // Drop invalid packets, if they were important, we'll re-request them later
Download::Container::AckRequest* request = (Download::Container::AckRequest*)data.data();
if (data.size() < (sizeof(Download::Container::AckRequest) + request->length)) return; // Again, drop invalid packets
auto download = Download::FindClientDownload(request->id);
if (download && download->target == target && !download->acknowledged)
{
std::string challenge(data.data() + sizeof(Download::Container::AckRequest), request->length);
download->acknowledged = true;
download->lastPing = Game::Com_Milliseconds();
download->maxParts = request->maxPackets;
std::string packet;
packet.append(reinterpret_cast<char*>(&download->id), sizeof(int));
packet.append(challenge);
Network::SendCommand(target, "dlAckResponse", packet);
}
}
void Download::PacketResponse(Network::Address target, std::string data)
{
//Logger::Print("Packet incoming!\n");
if (data.size() < sizeof(Download::Container::Packet)) return; // Drop invalid packets, if they were important, we'll re-request them later
Download::Container::Packet* packet = (Download::Container::Packet*)data.data();
//Logger::Print("Reading data!\n");
if (data.size() < (sizeof(Download::Container::Packet) + packet->length)) return; // Again, drop invalid packets
//Logger::Print("Finding corresponding download!\n");
auto download = Download::FindClientDownload(packet->id);
if (download && download->target == target)
{
//Logger::Print("Parsing packet!\n");
download->lastPing = Game::Com_Milliseconds();
std::string packetData(data.data() + sizeof(Download::Container::Packet), packet->length);
if (packet->hash == Utils::Cryptography::JenkinsOneAtATime::Compute(packetData.data(), packetData.size()))
{
//Logger::Print("Packet added!\n");
download->parts[packet->partId] = packetData;
if (Download::HasReceivedAllPackets(download))
{
download->successCallback(download->id, Download::AssembleBuffer(download));
Download::RemoveClientDownload(download->id);
}
}
else
{
Logger::Print("Hash invalid!\n");
}
}
}
// Server handlers
void Download::AckResponse(Network::Address target, std::string data)
{
int id = Download::ReadPacketId(data);
std::string challenge = Utils::ParseChallenge(data); // TODO: Maybe optimize this to ensure length matches
auto download = Download::FindServerDownload(id);
if (download && download->target == target)
{
if (download->challenge != challenge)
{
Logger::Print("Invalid download challenge!\n");
Download::RemoveServerDownload(id);
}
else
{
download->lastPing = Game::Com_Milliseconds();
download->acknowledged = true;
Logger::Print("Client acknowledged!\n");
}
}
}
void Download::MissingRequest(Network::Address target, std::string data)
{
int id = Download::ReadPacketId(data);
auto download = Download::FindServerDownload(id);
if (download && download->target == target)
{
while ((data.size() % 4) >= 4)
{
Download::MarkPacketAsDirty(download, *reinterpret_cast<int*>(const_cast<char*>(data.data())));
data = data.substr(4);
}
}
}
void Download::DownloadRequest(Network::Address target, std::string data)
{
int id = Download::ReadPacketId(data);
Download::Container::DownloadSV download;
download.id = id;
download.target = target;
download.acknowledged = false;
download.startTime = Game::Com_Milliseconds();
download.lastPing = Game::Com_Milliseconds();
download.maxParts = 0;
for (int i = 0; i < 1000000; ++i)
{
download.buffer.append("1234567890");
}
download.maxParts = download.buffer.size() / PACKET_SIZE;
if (download.buffer.size() % PACKET_SIZE) download.maxParts++;
download.challenge = Utils::VA("%X", Utils::Cryptography::Rand::GenerateInt());
Download::Container::AckRequest request;
request.id = id;
request.maxPackets = download.maxParts;
request.length = download.challenge.size();
std::string packet;
packet.append(reinterpret_cast<char*>(&request), sizeof(request));
packet.append(download.challenge);
Download::DataContainer.ServerDownloads.push_back(download);
Network::SendCommand(target, "dlAckRequest", packet);
}
std::string Download::AssembleBuffer(Download::Container::DownloadCL* download)
{
std::string buffer;
for (int i = 0; i < download->maxParts; ++i)
{
if (!Download::HasReceivedPacket(download, i)) return "";
buffer.append(download->parts[i]);
}
return buffer;
}
void Download::RequestMissingPackets(Download::Container::DownloadCL* download, std::vector<int> packets)
{
if (!packets.empty())
{
download->lastPing = Game::Com_Milliseconds();
std::string data;
data.append(reinterpret_cast<char*>(&download->id), sizeof(int));
for (auto &packet : packets)
{
data.append(reinterpret_cast<char*>(&packet), sizeof(int));
}
Network::SendCommand(download->target, "dlMissRequest", data);
}
}
void Download::MarkPacketAsDirty(Download::Container::DownloadSV* download, int packet)
{
if (!download->sentParts.empty())
{
for (auto i = download->sentParts.begin(); i != download->sentParts.end(); ++i)
{
if (*i == packet)
{
download->sentParts.erase(i);
i = download->sentParts.begin();
}
}
}
}
void Download::SendPacket(Download::Container::DownloadSV* download, int packet)
{
if (!download || packet >= download->maxParts) return;
download->lastPing = Game::Com_Milliseconds();
download->sentParts.push_back(packet);
Download::Container::Packet packetContainer;
packetContainer.id = download->id;
packetContainer.partId = packet;
int size = ((packet + 1) == download->maxParts ? (download->buffer.size() % PACKET_SIZE) : PACKET_SIZE);
size = (size == 0 ? PACKET_SIZE : size); // If remaining data equals packet PACKET_SIZE, size would be 0, so adjust it.
std::string data(download->buffer.data() + (packet * PACKET_SIZE), size);
packetContainer.length = data.size();
packetContainer.hash = Utils::Cryptography::JenkinsOneAtATime::Compute(data.data(), data.size());
std::string response = "dlPacketResponse\n";
response.append(reinterpret_cast<char*>(&packetContainer), sizeof(packetContainer));
response.append(data);
Network::SendCommand(download->target, "dlPacketResponse", response);
}
void Download::Frame()
{
if (!Download::DataContainer.ClientDownloads.empty())
{
for (auto i = Download::DataContainer.ClientDownloads.begin(); i != Download::DataContainer.ClientDownloads.end(); ++i)
{
if ((Game::Com_Milliseconds() - i->lastPing) > (DOWNLOAD_TIMEOUT * 2))
{
i->failureCallback(i->id);
Download::DataContainer.ClientDownloads.erase(i);
return;
}
// Request missing parts
if (i->acknowledged && (Game::Com_Milliseconds() - i->lastPing) > DOWNLOAD_TIMEOUT)
{
std::vector<int> missingPackets;
for (int j = 0; j < i->maxParts; ++j)
{
if (!Download::HasReceivedPacket(&(*i), j))
{
missingPackets.push_back(j);
}
}
Download::RequestMissingPackets(&(*i), missingPackets);
}
}
}
if (!Download::DataContainer.ServerDownloads.empty())
{
for (auto i = Download::DataContainer.ServerDownloads.begin(); i != Download::DataContainer.ServerDownloads.end(); ++i)
{
if ((Game::Com_Milliseconds() - i->lastPing) > (DOWNLOAD_TIMEOUT * 3))
{
Download::DataContainer.ServerDownloads.erase(i);
return;
}
int packets = 0;
for (int j = 0; j < i->maxParts && packets <= FRAME_PACKET_LIMIT && i->acknowledged; ++j)
{
if (!Download::HasSentPacket(&(*i), j))
{
//Logger::Print("Sending packet...\n");
Download::SendPacket(&(*i), j);
packets++;
}
}
}
}
}
int Download::Get(Network::Address target, std::string file, std::function<void(int, std::string)> successCallback, std::function<void(int)> failureCallback)
{
Download::Container::DownloadCL download;
download.id = Game::Com_Milliseconds();
download.target = target;
download.acknowledged = false;
download.startTime = Game::Com_Milliseconds();
download.lastPing = Game::Com_Milliseconds();
download.maxParts = 0;
download.failureCallback = failureCallback;
download.successCallback = successCallback;
Download::DataContainer.ClientDownloads.push_back(download);
std::string response = "dlRequest\n";
response.append(reinterpret_cast<char*>(&download.id), sizeof(int));
response.append(file);
Network::SendCommand(target, "dlRequest", response);
return download.id;
nc->flags |= MG_F_SEND_AND_CLOSE;
}
Download::Download()
{
#ifdef ENABLE_EXPERIMENTAL_UDP_DOWNLOAD
// Frame handlers
QuickPatch::OnFrame(Download::Frame);
// Register client handlers
Network::Handle("dlAckRequest", Download::AckRequest);
Network::Handle("dlPacketResponse", Download::PacketResponse);
// Register server handlers
Network::Handle("dlAckResponse", Download::AckResponse);
Network::Handle("dlMissRequest", Download::MissingRequest);
Network::Handle("dlAckResponse", Download::AckResponse);
Network::Handle("dlRequest", Download::DownloadRequest);
Command::Add("zob", [] (Command::Params params)
if (Dedicated::IsDedicated())
{
Logger::Print("Requesting!\n");
Download::Get(Network::Address("192.168.0.23:28960"), "test", [] (int id, std::string data)
mg_mgr_init(&Download::Mgr, NULL);
Network::OnStart([] ()
{
Logger::Print("Download succeeded %d!\n", Game::Com_Milliseconds() - (Download::FindClientDownload(id)->startTime));
}, [] (int id)
{
Logger::Print("Download failed!\n");
mg_connection* nc = mg_bind(&Download::Mgr, Utils::VA("%hu", (Dvar::Var("net_port").Get<int>() & 0xFFFF)), Download::EventHandler);
mg_set_protocol_http_websocket(nc);
});
});
#endif
QuickPatch::OnFrame([]
{
mg_mgr_poll(&Download::Mgr, 0);
});
}
else
{
Utils::Hook(0x5AC6E9, [] ()
{
// TODO: Perform moddownload here
Game::CL_DownloadsComplete(0);
}, HOOK_CALL).Install()->Quick();
}
}
Download::~Download()
{
Download::DataContainer.ServerDownloads.clear();
Download::DataContainer.ClientDownloads.clear();
if (Dedicated::IsDedicated())
{
mg_mgr_free(&Download::Mgr);
}
else
{
}
}
}

View File

@ -1,93 +1,15 @@
#define FRAME_PACKET_LIMIT 20
#define DOWNLOAD_TIMEOUT 2500
#define PACKET_SIZE 1000
namespace Components
{
class Download : public Component
{
public:
struct Container
{
struct DownloadCL
{
int id;
int startTime;
int lastPing;
bool acknowledged;
int maxParts;
Network::Address target;
std::map<int, std::string> parts;
std::function<void(int)> failureCallback;
std::function<void(int, std::string)> successCallback;
};
struct DownloadSV
{
int id;
int startTime;
int lastPing;
bool acknowledged;
std::vector<int> sentParts;
int maxParts;
Network::Address target;
std::string challenge;
std::string buffer;
};
struct Packet
{
int id;
int partId;
int length;
unsigned int hash;
};
struct AckRequest
{
int id;
int maxPackets;
int length;
};
std::vector<DownloadCL> ClientDownloads;
std::vector<DownloadSV> ServerDownloads;
};
Download();
~Download();
const char* GetName() { return "Download"; };
static int Get(Network::Address target, std::string file, std::function<void(int, std::string)> successCallback, std::function<void(int)> failureCallback);
static Container::DownloadCL* FindClientDownload(int id);
static Container::DownloadSV* FindServerDownload(int id);
private:
static void Frame();
static Container DataContainer;
static mg_mgr Mgr;
// Client handlers
static void AckRequest(Network::Address target, std::string data);
static void PacketResponse(Network::Address target, std::string data);
// Server handlers
static void AckResponse(Network::Address target, std::string data);
static void MissingRequest(Network::Address target, std::string data);
static void DownloadRequest(Network::Address target, std::string data);
// Helper functions
static void RemoveClientDownload(int id);
static void RemoveServerDownload(int id);
static bool HasSentPacket(Container::DownloadSV* download, int packet);
static bool HasReceivedPacket(Container::DownloadCL* download, int packet);
static bool HasReceivedAllPackets(Container::DownloadCL* download);
static std::string AssembleBuffer(Container::DownloadCL* download);
static void RequestMissingPackets(Container::DownloadCL* download, std::vector<int> packets);
static void MarkPacketAsDirty(Container::DownloadSV* download, int packet);
static void SendPacket(Container::DownloadSV* download, int packet);
static int ReadPacketId(std::string &data);
static void EventHandler(mg_connection *nc, int ev, void *ev_data);
};
}

View File

@ -11,6 +11,7 @@ namespace Game
CL_GetClientName_t CL_GetClientName = (CL_GetClientName_t)0x4563D0;
CL_IsCgameInitialized_t CL_IsCgameInitialized = (CL_IsCgameInitialized_t)0x43EB20;
CL_ConnectFromParty_t CL_ConnectFromParty = (CL_ConnectFromParty_t)0x433D30;
CL_DownloadsComplete_t CL_DownloadsComplete = (CL_DownloadsComplete_t)0x42CE90;
Cmd_AddCommand_t Cmd_AddCommand = (Cmd_AddCommand_t)0x470090;
Cmd_AddServerCommand_t Cmd_AddServerCommand = (Cmd_AddServerCommand_t)0x4DCE00;

View File

@ -18,6 +18,9 @@ namespace Game
typedef void(__cdecl * CL_ConnectFromParty_t)(int controllerIndex, _XSESSION_INFO *hostInfo, netadr_t addr, int numPublicSlots, int numPrivateSlots, const char *mapname, const char *gametype);
extern CL_ConnectFromParty_t CL_ConnectFromParty;
typedef void(__cdecl * CL_DownloadsComplete_t)(int controller);
extern CL_DownloadsComplete_t CL_DownloadsComplete;
typedef void(__cdecl * Cmd_AddCommand_t)(const char* name, void(*callback), cmd_function_t* data, char);
extern Cmd_AddCommand_t Cmd_AddCommand;

View File

@ -63,7 +63,7 @@
#include <zlib.h>
#include <curses.h>
//#include <asio.hpp>
#include <mongoose.h>
#include <json11.hpp>
#include <tomcrypt.h>
#include <wink/signal.hpp>