mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2026-04-09 01:06:31 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc6167cd2e | ||
|
|
e216b6ec06 | ||
|
|
6597fe5e26 | ||
|
|
2fa5d69369 | ||
|
|
fec80e2c67 | ||
|
|
fa8627a22b | ||
|
|
dd5256ae22 | ||
|
|
e24cbf61bb | ||
|
|
472e2d16b6 | ||
|
|
ae650cc142 | ||
|
|
ad7177bec8 | ||
|
|
a4005c5876 | ||
|
|
a3ad6f8700 | ||
|
|
d3bddb0203 | ||
|
|
9e93fa35fa | ||
|
|
8373a70c4b | ||
|
|
d52a791dd9 | ||
|
|
08a6f9a093 | ||
|
|
e5e40e186b | ||
|
|
bfbff52cb1 | ||
|
|
8d4ba6f158 | ||
|
|
a5d450b680 | ||
|
|
f4e985976f | ||
|
|
db9ec53a6e | ||
|
|
f9b2edd410 | ||
|
|
333a95262b | ||
|
|
e53885a8a8 | ||
|
|
c22ea1e85d | ||
|
|
f8ea9bd8a3 | ||
|
|
ad8eab3d66 | ||
|
|
e880da5cf9 | ||
|
|
d14b64c652 | ||
|
|
649514ca1a | ||
|
|
7149075d53 | ||
|
|
c485fba26b | ||
|
|
d35567dd47 |
52
README.md
52
README.md
@@ -2,57 +2,7 @@
|
|||||||
|
|
||||||
The launcher is the way we communitcate to outside the game, it does a few automated actions such as but not limited to: downloading the mod, launching the game, and create a connection to a server.
|
The launcher is the way we communitcate to outside the game, it does a few automated actions such as but not limited to: downloading the mod, launching the game, and create a connection to a server.
|
||||||
|
|
||||||
**To clone this repository**: `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Launcher.git`
|
## [Getting started](https://docs.beammp.com/game/getting-started/)
|
||||||
|
|
||||||
## How to build for Windows
|
|
||||||
|
|
||||||
Make sure you have the necessary development tools installed:
|
|
||||||
|
|
||||||
[vcpkg](https://vcpkg.io/en/)
|
|
||||||
|
|
||||||
### Release
|
|
||||||
|
|
||||||
In the root directory of the project,
|
|
||||||
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
|
|
||||||
2. `cmake --build bin --parallel --config Release`
|
|
||||||
|
|
||||||
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
|
|
||||||
|
|
||||||
### Debug
|
|
||||||
|
|
||||||
In the root directory of the project,
|
|
||||||
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
|
|
||||||
2. `cmake --build bin --parallel`
|
|
||||||
|
|
||||||
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
|
|
||||||
|
|
||||||
## How to build for Linux
|
|
||||||
|
|
||||||
Make sure you have `vcpkg` installed, as well as basic development tools, often found in packages, for example:
|
|
||||||
|
|
||||||
- Debian: `sudo apt install build-essential`
|
|
||||||
- Fedora: `sudo dnf groupinstall "Development Tools"`
|
|
||||||
- Arch: `sudo pacman -S base-devel`
|
|
||||||
- openSUSE: `zypper in -t pattern devel-basis`
|
|
||||||
|
|
||||||
### Release
|
|
||||||
|
|
||||||
In the root directory of the project,
|
|
||||||
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
|
|
||||||
2. `cmake --build bin --parallel --config Release`
|
|
||||||
|
|
||||||
### Debug
|
|
||||||
|
|
||||||
In the root directory of the project,
|
|
||||||
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
|
|
||||||
2. `cmake --build bin --parallel`
|
|
||||||
|
|
||||||
## Running out of RAM while building
|
|
||||||
|
|
||||||
Should you run out of RAM while building, you can ommit the `--parallel` intruction, it will then use less RAM due to building only on one CPU thread.
|
|
||||||
|
|
||||||
You can also specify a number of threads to use, for example `--parallel 4` will use four CPU threads, but due to the small project size, you may be faster just omitting `--parallel` instead of trying to find the highest possible multithread number
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
class HTTP {
|
class HTTP {
|
||||||
public:
|
public:
|
||||||
static bool Download(const std::string& IP, const std::string& Fields, const std::string& Path);
|
static bool Download(const std::string& IP, const std::string& Path);
|
||||||
static std::string Post(const std::string& IP, const std::string& Fields);
|
static std::string Post(const std::string& IP, const std::string& Fields);
|
||||||
static std::string Get(const std::string& IP, const std::string& Fields = "");
|
static std::string Get(const std::string& IP);
|
||||||
static bool ProgressBar(size_t c, size_t t);
|
static bool ProgressBar(size_t c, size_t t);
|
||||||
static void StartProxy();
|
static void StartProxy();
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <bits/types/siginfo_t.h>
|
#include <bits/types/siginfo_t.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <sys/ucontext.h>
|
#include <sys/ucontext.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void NetReset();
|
void NetReset();
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ void StartGame(std::string Dir) {
|
|||||||
gameArgs += options.game_arguments[i];
|
gameArgs += options.game_arguments[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug("BeamNG executable path: " + Dir);
|
||||||
|
|
||||||
bSuccess = CreateProcessA(nullptr, (LPSTR)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
|
bSuccess = CreateProcessA(nullptr, (LPSTR)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
|
||||||
if (bSuccess) {
|
if (bSuccess) {
|
||||||
info("Game Launched!");
|
info("Game Launched!");
|
||||||
@@ -97,7 +99,19 @@ void StartGame(std::string Dir) {
|
|||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||||
error("Game Closed! launcher closing soon");
|
error("Game Closed! launcher closing soon");
|
||||||
} else {
|
} else {
|
||||||
error("Failed to Launch the game! launcher closing soon");
|
std::string err = "";
|
||||||
|
|
||||||
|
DWORD dw = GetLastError();
|
||||||
|
LPVOID lpErrorMsgBuffer;
|
||||||
|
|
||||||
|
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw,
|
||||||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpErrorMsgBuffer, 0, nullptr) == 0) {
|
||||||
|
err = "Unknown error code: " + std::to_string(dw);
|
||||||
|
} else {
|
||||||
|
err = "Error " + std::to_string(dw) + ": " + (char*)lpErrorMsgBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
error("Failed to Launch the game! launcher closing soon. " + err);
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
exit(2);
|
exit(2);
|
||||||
|
|||||||
@@ -89,6 +89,98 @@ void StartSync(const std::string& Data) {
|
|||||||
info("Connecting to server");
|
info("Connecting to server");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetServerInfo(std::string Data) {
|
||||||
|
debug("Fetching server info of " + Data.substr(1));
|
||||||
|
|
||||||
|
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
|
||||||
|
if (IP.find('.') == -1) {
|
||||||
|
if (IP == "DNS")
|
||||||
|
warn("Connection Failed! (DNS Lookup Failed) for " + Data);
|
||||||
|
else
|
||||||
|
warn("Connection Failed! (WSA failed to start) for " + Data);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKET ISock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
SOCKADDR_IN ServerAddr;
|
||||||
|
if (ISock < 1) {
|
||||||
|
debug("Socket creation failed with error: " + std::to_string(WSAGetLastError()));
|
||||||
|
KillSocket(ISock);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerAddr.sin_family = AF_INET;
|
||||||
|
|
||||||
|
int port = std::stoi(Data.substr(Data.find(':') + 1));
|
||||||
|
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
|
debug("Invalid port number: " + std::to_string(port));
|
||||||
|
KillSocket(ISock);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerAddr.sin_port = htons(port);
|
||||||
|
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
|
||||||
|
if (connect(ISock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
|
||||||
|
debug("Connection to server failed with error: " + std::to_string(WSAGetLastError()));
|
||||||
|
KillSocket(ISock);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char Code[1] = { 'I' };
|
||||||
|
if (send(ISock, Code, 1, 0) != 1) {
|
||||||
|
debug("Sending data to server failed with error: " + std::to_string(WSAGetLastError()));
|
||||||
|
KillSocket(ISock);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string buffer = ([&]() -> std::string {
|
||||||
|
int32_t Header;
|
||||||
|
std::vector<char> data(sizeof(Header));
|
||||||
|
int Temp = recv(ISock, data.data(), sizeof(Header), MSG_WAITALL);
|
||||||
|
|
||||||
|
auto checkBytes = ([&](const int32_t bytes) -> bool {
|
||||||
|
if (bytes == 0) {
|
||||||
|
return false;
|
||||||
|
} else if (bytes < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!checkBytes(Temp)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
memcpy(&Header, data.data(), sizeof(Header));
|
||||||
|
|
||||||
|
if (!checkBytes(Temp)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
data.resize(Header, 0);
|
||||||
|
Temp = recv(ISock, data.data(), Header, MSG_WAITALL);
|
||||||
|
if (!checkBytes(Temp)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return std::string(data.data(), Header);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!buffer.empty()) {
|
||||||
|
debug("Server Info: " + buffer);
|
||||||
|
|
||||||
|
CoreSend("I" + Data + ";" + buffer);
|
||||||
|
} else {
|
||||||
|
debug("Receiving data from server failed with error: " + std::to_string(WSAGetLastError()));
|
||||||
|
debug("Failed to receive server info from " + Data);
|
||||||
|
CoreSend("I" + Data + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
KillSocket(ISock);
|
||||||
|
}
|
||||||
std::mutex sendMutex;
|
std::mutex sendMutex;
|
||||||
|
|
||||||
void CoreSend(std::string data) {
|
void CoreSend(std::string data) {
|
||||||
@@ -108,7 +200,17 @@ bool IsAllowedLink(const std::string& Link) {
|
|||||||
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
|
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
|
||||||
void Parse(std::string Data, SOCKET CSocket) {
|
void Parse(std::string Data, SOCKET CSocket) {
|
||||||
|
std::erase_if(futures, [](const std::future<void>& f) {
|
||||||
|
if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
char Code = Data.at(0), SubCode = 0;
|
char Code = Data.at(0), SubCode = 0;
|
||||||
if (Data.length() > 1)
|
if (Data.length() > 1)
|
||||||
SubCode = Data.at(1);
|
SubCode = Data.at(1);
|
||||||
@@ -121,9 +223,9 @@ void Parse(std::string Data, SOCKET CSocket) {
|
|||||||
Terminate = true;
|
Terminate = true;
|
||||||
TCPTerminate = true;
|
TCPTerminate = true;
|
||||||
Data.clear();
|
Data.clear();
|
||||||
auto future = std::async(std::launch::async, []() {
|
futures.push_back(std::async(std::launch::async, []() {
|
||||||
CoreSend("B" + HTTP::Get("https://backend.beammp.com/servers-info"));
|
CoreSend("B" + HTTP::Get("https://backend.beammp.com/servers-info"));
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
@@ -220,9 +322,9 @@ void Parse(std::string Data, SOCKET CSocket) {
|
|||||||
}
|
}
|
||||||
Data = "N" + Auth.dump();
|
Data = "N" + Auth.dump();
|
||||||
} else {
|
} else {
|
||||||
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
|
futures.push_back(std::async(std::launch::async, [data = std::move(Data)]() {
|
||||||
CoreSend("N" + Login(data.substr(data.find(':') + 1)));
|
CoreSend("N" + Login(data.substr(data.find(':') + 1)));
|
||||||
});
|
}));
|
||||||
Data.clear();
|
Data.clear();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -235,6 +337,12 @@ void Parse(std::string Data, SOCKET CSocket) {
|
|||||||
|
|
||||||
Data.clear();
|
Data.clear();
|
||||||
break;
|
break;
|
||||||
|
case 'I': {
|
||||||
|
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
|
||||||
|
GetServerInfo(data);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
Data.clear();
|
Data.clear();
|
||||||
break;
|
break;
|
||||||
@@ -244,7 +352,8 @@ void Parse(std::string Data, SOCKET CSocket) {
|
|||||||
}
|
}
|
||||||
void GameHandler(SOCKET Client) {
|
void GameHandler(SOCKET Client) {
|
||||||
CoreSocket = Client;
|
CoreSocket = Client;
|
||||||
int32_t Size, Temp, Rcv;
|
int32_t Size, Rcv;
|
||||||
|
int Temp;
|
||||||
char Header[10] = { 0 };
|
char Header[10] = { 0 };
|
||||||
do {
|
do {
|
||||||
Rcv = 0;
|
Rcv = 0;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "Options.h"
|
#include "Options.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
|
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
|
||||||
bool GConnected = false;
|
bool GConnected = false;
|
||||||
@@ -261,7 +262,8 @@ void TCPGameServer(const std::string& IP, int Port) {
|
|||||||
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
|
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
|
||||||
CServer = false;
|
CServer = false;
|
||||||
}
|
}
|
||||||
int32_t Size, Temp, Rcv;
|
int32_t Size, Rcv;
|
||||||
|
int Temp;
|
||||||
char Header[10] = { 0 };
|
char Header[10] = { 0 };
|
||||||
|
|
||||||
// Read byte by byte until '>' is rcved then get the size and read based on it
|
// Read byte by byte until '>' is rcved then get the size and read based on it
|
||||||
|
|||||||
@@ -68,24 +68,23 @@ static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void*
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool HTTP::isDownload = false;
|
bool HTTP::isDownload = false;
|
||||||
std::string HTTP::Get(const std::string& IP, const std::string& Fields) {
|
std::string HTTP::Get(const std::string& IP) {
|
||||||
std::string Ret;
|
std::string Ret;
|
||||||
static thread_local CURL* curl = curl_easy_init();
|
static thread_local CURL* curl = curl_easy_init();
|
||||||
if (curl) {
|
if (curl) {
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
|
char errbuf[CURL_ERROR_SIZE];
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
if (!Fields.empty()) {
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
errbuf[0] = 0;
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
|
|
||||||
}
|
|
||||||
res = curl_easy_perform(curl);
|
res = curl_easy_perform(curl);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
|
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
|
||||||
|
error("Curl error: " + std::string(errbuf));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -100,6 +99,7 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
|||||||
static thread_local CURL* curl = curl_easy_init();
|
static thread_local CURL* curl = curl_easy_init();
|
||||||
if (curl) {
|
if (curl) {
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
|
char errbuf[CURL_ERROR_SIZE];
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||||
@@ -109,12 +109,15 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
|||||||
struct curl_slist* list = nullptr;
|
struct curl_slist* list = nullptr;
|
||||||
list = curl_slist_append(list, "Content-Type: application/json");
|
list = curl_slist_append(list, "Content-Type: application/json");
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
|
errbuf[0] = 0;
|
||||||
res = curl_easy_perform(curl);
|
res = curl_easy_perform(curl);
|
||||||
curl_slist_free_all(list);
|
curl_slist_free_all(list);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
|
error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
|
||||||
|
error("Curl error: " + std::string(errbuf));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -124,12 +127,12 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
|||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HTTP::Download(const std::string& IP, const std::string& Fields, const std::string& Path) {
|
bool HTTP::Download(const std::string& IP, const std::string& Path) {
|
||||||
static std::mutex Lock;
|
static std::mutex Lock;
|
||||||
std::scoped_lock Guard(Lock);
|
std::scoped_lock Guard(Lock);
|
||||||
|
|
||||||
info("Downloading an update (this may take a while)");
|
info("Downloading an update (this may take a while)");
|
||||||
std::string Ret = Get(IP, Fields);
|
std::string Ret = Get(IP);
|
||||||
|
|
||||||
if (Ret.empty()) {
|
if (Ret.empty()) {
|
||||||
error("Download failed");
|
error("Download failed");
|
||||||
|
|||||||
@@ -164,9 +164,12 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
|||||||
do {
|
do {
|
||||||
// receive at most some MB at a time
|
// receive at most some MB at a time
|
||||||
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
|
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
|
||||||
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
int Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
||||||
if (Temp < 1) {
|
if (Temp == -1 || Temp == 0) {
|
||||||
info(std::to_string(Temp));
|
debug("Recv returned: " + std::to_string(Temp));
|
||||||
|
if (Temp == -1) {
|
||||||
|
error("Socket error during download: " + std::to_string(WSAGetLastError()));
|
||||||
|
}
|
||||||
UUl("Socket Closed Code 1");
|
UUl("Socket Closed Code 1");
|
||||||
KillSocket(Sock);
|
KillSocket(Sock);
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
@@ -177,8 +180,8 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
|||||||
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
auto difference = end - start;
|
auto difference = end - start;
|
||||||
float bits_per_s = float(Rcv * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
|
double bits_per_s = double(Rcv * 8) / double(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
|
||||||
float megabits_per_s = bits_per_s / 1000;
|
double megabits_per_s = bits_per_s / 1000;
|
||||||
DownloadSpeed = megabits_per_s;
|
DownloadSpeed = megabits_per_s;
|
||||||
// every 8th iteration print the speed
|
// every 8th iteration print the speed
|
||||||
if (i % 8 == 0) {
|
if (i % 8 == 0) {
|
||||||
@@ -382,7 +385,7 @@ struct ModInfo {
|
|||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
debug(std::string("Failed to receive mod list: ") + e.what());
|
debug(std::string("Failed to receive mod list: ") + e.what());
|
||||||
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
|
debug("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
|
||||||
}
|
}
|
||||||
return std::make_pair(success, modInfos);
|
return std::make_pair(success, modInfos);
|
||||||
}
|
}
|
||||||
@@ -392,6 +395,52 @@ struct ModInfo {
|
|||||||
std::string HashAlgorithm;
|
std::string HashAlgorithm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nlohmann::json modUsage = {};
|
||||||
|
|
||||||
|
void UpdateModUsage(const std::string& fileName) {
|
||||||
|
try {
|
||||||
|
fs::path usageFile = fs::path(CachingDirectory) / "mods.json";
|
||||||
|
|
||||||
|
if (!fs::exists(usageFile)) {
|
||||||
|
if (std::ofstream file(usageFile); !file.is_open()) {
|
||||||
|
error("Failed to create mods.json");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fstream file(usageFile, std::ios::in | std::ios::out);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
error("Failed to open or create mods.json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modUsage.empty()) {
|
||||||
|
auto Size = fs::file_size(fs::path(CachingDirectory) / "mods.json");
|
||||||
|
std::string modsJson(Size, 0);
|
||||||
|
file.read(&modsJson[0], Size);
|
||||||
|
|
||||||
|
if (!modsJson.empty()) {
|
||||||
|
auto parsedModJson = nlohmann::json::parse(modsJson, nullptr, false);
|
||||||
|
|
||||||
|
if (parsedModJson.is_object())
|
||||||
|
modUsage = parsedModJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modUsage[fileName] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
|
file.clear();
|
||||||
|
file.seekp(0, std::ios::beg);
|
||||||
|
file << modUsage.dump();
|
||||||
|
file.close();
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
error("Failed to update mods.json: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
|
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
|
||||||
if (ModInfos.empty()) {
|
if (ModInfos.empty()) {
|
||||||
CoreSend("L");
|
CoreSend("L");
|
||||||
@@ -451,6 +500,42 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
|||||||
|
|
||||||
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||||
fs::rename(tmp_name, name);
|
fs::rename(tmp_name, name);
|
||||||
|
UpdateModUsage(FileName);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||||
|
Terminate = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
WaitForConfirm();
|
||||||
|
continue;
|
||||||
|
} else if (auto OldCachedPath = fs::path(CachingDirectory) / std::filesystem::path(ModInfoIter->FileName).filename();
|
||||||
|
fs::exists(OldCachedPath) && GetSha256HashReallyFast(OldCachedPath.string()) == ModInfoIter->Hash) {
|
||||||
|
debug("Mod '" + FileName + "' found in old cache, copying it to the new cache");
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
try {
|
||||||
|
fs::copy_file(OldCachedPath, PathToSaveTo, fs::copy_options::overwrite_existing);
|
||||||
|
|
||||||
|
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||||
|
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto modname = ModInfoIter->FileName;
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
// Linux version of the game doesnt support uppercase letters in mod names
|
||||||
|
for (char& c : modname) {
|
||||||
|
c = ::tolower(c);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debug("Mod name: " + modname);
|
||||||
|
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / modname;
|
||||||
|
std::string tmp_name = name.string();
|
||||||
|
tmp_name += ".tmp";
|
||||||
|
|
||||||
|
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||||
|
fs::rename(tmp_name, name);
|
||||||
|
UpdateModUsage(FileName);
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
@@ -505,6 +590,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
|
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
|
||||||
|
UpdateModUsage(FName);
|
||||||
}
|
}
|
||||||
WaitForConfirm();
|
WaitForConfirm();
|
||||||
++ModNo;
|
++ModNo;
|
||||||
@@ -606,6 +692,7 @@ void SyncResources(SOCKET Sock) {
|
|||||||
auto tmp_name = name + ".tmp";
|
auto tmp_name = name + ".tmp";
|
||||||
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||||
fs::rename(tmp_name, name);
|
fs::rename(tmp_name, name);
|
||||||
|
UpdateModUsage(modname);
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
@@ -661,6 +748,7 @@ void SyncResources(SOCKET Sock) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
|
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
|
||||||
|
UpdateModUsage(FN->substr(pos));
|
||||||
}
|
}
|
||||||
WaitForConfirm();
|
WaitForConfirm();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ std::string TCPRcv(SOCKET Sock) {
|
|||||||
UUl("Invalid Socket");
|
UUl("Invalid Socket");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
int32_t Header, Temp;
|
int32_t Header;
|
||||||
|
int Temp;
|
||||||
std::vector<char> Data(sizeof(Header));
|
std::vector<char> Data(sizeof(Header));
|
||||||
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
|
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
|
||||||
if (!CheckBytes(Temp)) {
|
if (!CheckBytes(Temp)) {
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ std::string GetEN() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string GetVer() {
|
std::string GetVer() {
|
||||||
return "2.3";
|
return "2.4";
|
||||||
}
|
}
|
||||||
std::string GetPatch() {
|
std::string GetPatch() {
|
||||||
return ".2";
|
return ".1";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetEP(const char* P) {
|
std::string GetEP(const char* P) {
|
||||||
@@ -164,9 +164,9 @@ void CheckName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CheckForUpdates(const std::string& CV) {
|
void CheckForUpdates(const std::string& CV) {
|
||||||
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
|
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey);
|
||||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher", requestBody);
|
std::string LatestVersion = HTTP::Get(
|
||||||
std::string LatestVersion = HTTP::Get("https://backend.beammp.com/version/launcher", requestBody);
|
"https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey);
|
||||||
|
|
||||||
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
||||||
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
|
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
|
||||||
@@ -175,21 +175,25 @@ void CheckForUpdates(const std::string& CV) {
|
|||||||
|
|
||||||
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
|
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
|
||||||
if (!options.no_update) {
|
if (!options.no_update) {
|
||||||
info("Launcher update found!");
|
info("Launcher update " + LatestVersion + " found!");
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
|
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
|
||||||
#else
|
#else
|
||||||
fs::remove(Back);
|
fs::remove(Back);
|
||||||
fs::rename(EP, Back);
|
fs::rename(EP, Back);
|
||||||
info("Downloading Launcher update " + LatestHash);
|
info("Downloading Launcher update " + LatestHash);
|
||||||
HTTP::Download("https://backend.beammp.com/builds/launcher?download=true", requestBody, EP);
|
HTTP::Download(
|
||||||
|
"https://backend.beammp.com/builds/launcher?download=true"
|
||||||
|
"&pk="
|
||||||
|
+ PublicKey + "&branch=" + Branch,
|
||||||
|
EP);
|
||||||
URelaunch();
|
URelaunch();
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
|
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
info("Launcher version is up to date");
|
info("Launcher version is up to date. Latest version: " + LatestVersion);
|
||||||
TraceBack++;
|
TraceBack++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +231,7 @@ void LinuxPatch() {
|
|||||||
|
|
||||||
void InitLauncher() {
|
void InitLauncher() {
|
||||||
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
|
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
|
||||||
|
debug("Launcher Version : " + GetVer() + GetPatch());
|
||||||
CheckName();
|
CheckName();
|
||||||
LinuxPatch();
|
LinuxPatch();
|
||||||
CheckLocalKey();
|
CheckLocalKey();
|
||||||
@@ -303,8 +308,7 @@ void PreGame(const std::string& GamePath) {
|
|||||||
info("Game user path: " + GetGamePath());
|
info("Game user path: " + GetGamePath());
|
||||||
|
|
||||||
if (!options.no_download) {
|
if (!options.no_download) {
|
||||||
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
|
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
|
||||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod", requestBody);
|
|
||||||
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
||||||
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
|
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
|
||||||
[](auto const& c) -> bool { return !std::isalnum(c); }),
|
[](auto const& c) -> bool { return !std::isalnum(c); }),
|
||||||
@@ -329,7 +333,10 @@ void PreGame(const std::string& GamePath) {
|
|||||||
|
|
||||||
if (FileHash != LatestHash) {
|
if (FileHash != LatestHash) {
|
||||||
info("Downloading BeamMP Update " + LatestHash);
|
info("Downloading BeamMP Update " + LatestHash);
|
||||||
HTTP::Download("https://backend.beammp.com/builds/client?download=true", requestBody, ZipPath);
|
HTTP::Download("https://backend.beammp.com/builds/client?download=true"
|
||||||
|
"&pk="
|
||||||
|
+ PublicKey + "&branch=" + Branch,
|
||||||
|
ZipPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Target(GetGamePath() + "mods/unpacked/beammp");
|
std::string Target(GetGamePath() + "mods/unpacked/beammp");
|
||||||
|
|||||||
Reference in New Issue
Block a user