36 Commits

Author SHA1 Message Date
Tixx
cc6167cd2e Bump version 2025-05-03 22:20:55 +02:00
SaltySnail
e216b6ec06 Get error code when the game fails to launch on windows (#179)
This PR logs the error `CreateProcessA` returns when failing on windows.
This can help with figuring out why the launcher `Failed to Launch the
game!`

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-04-19 21:43:41 +02:00
Tixx
6597fe5e26 Rename windows api variable 2025-04-19 21:23:51 +02:00
Tixx
2fa5d69369 Link to the docs (#181)
Link to the docs instead

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-04-05 12:45:22 +02:00
O1LER
fec80e2c67 Link to the docs 2025-04-05 12:04:57 +02:00
Tixx
fa8627a22b Fix recv return type and better download error handling (#178)
Fixes the recv return value type. This PR corrects the recv return value
type from `int32_t` to `int`. Casting the return value from `int` to
`int32_t` (Currently the case, changed by this pr) would in some cases,
if the transmitted packet was large enough, flip the value causing it to
be a high negative number, which recv will never return. This happens
frequently when downloading big mods over a fast connection.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-03-29 20:18:19 +01:00
Tixx
dd5256ae22 Increase download speed calculation precision 2025-03-29 00:19:25 +01:00
Tixx
e24cbf61bb Only fail on socket error or connection closed 2025-03-29 00:19:24 +01:00
Tixx
472e2d16b6 Fix recv return type and better download error handling 2025-03-29 00:19:24 +01:00
Tixx
ae650cc142 Get error code when the game fails to launch on windows 2025-03-28 23:16:37 +01:00
Tixx
ad7177bec8 Include chrono (#176)
By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-03-23 22:38:20 +01:00
Tixx
a4005c5876 Include chrono 2025-03-15 23:06:49 +01:00
Tixx
a3ad6f8700 Properly handle the futures (#172)
This PR makes it so that the std::async calls are actually asynchronous.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-03-08 22:29:08 +01:00
Tixx
d3bddb0203 Properly handle the future 2025-02-23 22:04:15 +01:00
Tixx
9e93fa35fa Bump version 2025-01-21 22:36:40 +01:00
Tixx
8373a70c4b Mod download improvements (#162)
This PR adds multiple features relating to mod downloads.
1. With this PR the launcher will convert old style mods (without hash)
to new style mods (with hash). This is so people don't have to
re-download all of their mods after joining a server which was
previously on a version below 3.6.0
2. This PR will save the last used date of mods in a JSON file in the
caching directory. This will allow for smart deletion of cached mods

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-18 22:56:33 +01:00
Tixx
d52a791dd9 Create mods.json if its missing 2025-01-18 22:29:49 +01:00
Tixx
08a6f9a093 Log current and backend version (#169)
By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-18 21:13:54 +01:00
Tixx
e5e40e186b Server info (#161)
Adds an `I` packet to the core handler, which allows the mod to use the
newly added [information packet
](https://github.com/BeamMP/BeamMP-Server/pull/382) in the server.
Usage:
Mod sends `I0.0.0.0:0000` and the launcher will send either
`I0.0.0.0:0000;` back if something went wrong, or `I0.0.0.0:0000;{Server
information JSON}` if it succeeded.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-18 20:52:44 +01:00
Tixx
bfbff52cb1 Log current and backend version 2025-01-12 23:17:23 +01:00
Tixx
8d4ba6f158 Implement size header for info packet 2025-01-12 16:57:33 +01:00
Tixx
a5d450b680 Raise buffer and remove timeout 2025-01-11 22:13:12 +01:00
Tixx
f4e985976f Strip packet letter in log 2025-01-11 21:55:08 +01:00
Tixx
db9ec53a6e Up curl connection timeout to 2 minutes (#160)
In some cases it can take longer than 10 seconds to connect to the
backend, so in this PR I've raised the limit to 2 minutes.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-11 21:54:35 +01:00
Tixx
f9b2edd410 Better curl debug (#165)
This PR makes it so the launcher logs curl's error description.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-11 21:51:13 +01:00
Tixx
333a95262b Check port and timeout recv 2025-01-11 21:50:39 +01:00
Tixx
e53885a8a8 Raise http get timeout to 2 minutes 2025-01-11 21:45:06 +01:00
Tixx
c22ea1e85d Fix typo in README.md (#166)
Fix non-critical typo in README:md

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-01-08 16:41:24 +01:00
O1LER
f8ea9bd8a3 Fix typo in README.md 2025-01-08 16:38:29 +01:00
Tixx
ad8eab3d66 Log error buffer 2024-12-29 16:39:56 +01:00
Tixx
e880da5cf9 Up curl connection timeout to 2 minutes 2024-12-25 00:54:19 +01:00
Tixx
d14b64c652 Fix linux build 2024-12-25 00:07:13 +01:00
Tixx
649514ca1a Save mod usage date 2024-12-24 13:53:50 +01:00
Tixx
7149075d53 Implement core server information packet 2024-12-22 23:56:29 +01:00
Tixx
c485fba26b Change an easily confusable warning to debug 2024-12-22 12:13:59 +01:00
Tixx
d35567dd47 Look for new mods in the old format 2024-12-22 12:07:25 +01:00
9 changed files with 245 additions and 71 deletions

View File

@@ -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.
**To clone this repository**: `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Launcher.git`
## 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
## [Getting started](https://docs.beammp.com/game/getting-started/)
## License

View File

@@ -13,6 +13,7 @@
#include <bits/types/siginfo_t.h>
#include <cstdint>
#include <sys/ucontext.h>
#include <arpa/inet.h>
#endif
void NetReset();

View File

@@ -90,6 +90,8 @@ void StartGame(std::string Dir) {
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);
if (bSuccess) {
info("Game Launched!");
@@ -97,7 +99,19 @@ void StartGame(std::string Dir) {
WaitForSingleObject(pi.hProcess, INFINITE);
error("Game Closed! launcher closing soon");
} 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));
exit(2);

View File

@@ -89,6 +89,98 @@ void StartSync(const std::string& Data) {
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;
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;
}
std::vector<std::future<void>> futures;
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;
if (Data.length() > 1)
SubCode = Data.at(1);
@@ -121,9 +223,9 @@ void Parse(std::string Data, SOCKET CSocket) {
Terminate = true;
TCPTerminate = true;
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"));
});
}));
}
break;
case 'C':
@@ -220,9 +322,9 @@ void Parse(std::string Data, SOCKET CSocket) {
}
Data = "N" + Auth.dump();
} 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)));
});
}));
Data.clear();
}
break;
@@ -235,6 +337,12 @@ void Parse(std::string Data, SOCKET CSocket) {
Data.clear();
break;
case 'I': {
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
GetServerInfo(data);
});
break;
}
default:
Data.clear();
break;
@@ -244,7 +352,8 @@ void Parse(std::string Data, SOCKET CSocket) {
}
void GameHandler(SOCKET Client) {
CoreSocket = Client;
int32_t Size, Temp, Rcv;
int32_t Size, Rcv;
int Temp;
char Header[10] = { 0 };
do {
Rcv = 0;

View File

@@ -26,6 +26,7 @@
#include <string>
#include <thread>
#include "Options.h"
#include <chrono>
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
bool GConnected = false;
@@ -261,7 +262,8 @@ void TCPGameServer(const std::string& IP, int Port) {
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
CServer = false;
}
int32_t Size, Temp, Rcv;
int32_t Size, Rcv;
int Temp;
char Header[10] = { 0 };
// Read byte by byte until '>' is rcved then get the size and read based on it

View File

@@ -73,14 +73,18 @@ std::string HTTP::Get(const std::string& IP) {
static thread_local CURL* curl = curl_easy_init();
if (curl) {
CURLcode res;
char errbuf[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
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_ERRORBUFFER, errbuf);
errbuf[0] = 0;
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
error("Curl error: " + std::string(errbuf));
return "";
}
} else {
@@ -95,6 +99,7 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
static thread_local CURL* curl = curl_easy_init();
if (curl) {
CURLcode res;
char errbuf[CURL_ERROR_SIZE];
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
@@ -104,12 +109,15 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
struct curl_slist* list = nullptr;
list = curl_slist_append(list, "Content-Type: application/json");
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_ERRORBUFFER, errbuf);
errbuf[0] = 0;
res = curl_easy_perform(curl);
curl_slist_free_all(list);
if (res != CURLE_OK) {
error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
error("Curl error: " + std::string(errbuf));
return "";
}
} else {

View File

@@ -164,9 +164,12 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
do {
// receive at most some MB at a time
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if (Temp < 1) {
info(std::to_string(Temp));
int Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if (Temp == -1 || Temp == 0) {
debug("Recv returned: " + std::to_string(Temp));
if (Temp == -1) {
error("Socket error during download: " + std::to_string(WSAGetLastError()));
}
UUl("Socket Closed Code 1");
KillSocket(Sock);
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 difference = end - start;
float bits_per_s = float(Rcv * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
float megabits_per_s = bits_per_s / 1000;
double bits_per_s = double(Rcv * 8) / double(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
double megabits_per_s = bits_per_s / 1000;
DownloadSpeed = megabits_per_s;
// every 8th iteration print the speed
if (i % 8 == 0) {
@@ -382,7 +385,7 @@ struct ModInfo {
}
} catch (const std::exception& e) {
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);
}
@@ -392,6 +395,52 @@ struct ModInfo {
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) {
if (ModInfos.empty()) {
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::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) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
@@ -505,6 +590,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
#endif
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
UpdateModUsage(FName);
}
WaitForConfirm();
++ModNo;
@@ -606,6 +692,7 @@ void SyncResources(SOCKET Sock) {
auto tmp_name = name + ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
UpdateModUsage(modname);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
@@ -661,6 +748,7 @@ void SyncResources(SOCKET Sock) {
#endif
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
UpdateModUsage(FN->substr(pos));
}
WaitForConfirm();
}

View File

@@ -81,7 +81,8 @@ std::string TCPRcv(SOCKET Sock) {
UUl("Invalid Socket");
return "";
}
int32_t Header, Temp;
int32_t Header;
int Temp;
std::vector<char> Data(sizeof(Header));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
if (!CheckBytes(Temp)) {

View File

@@ -81,10 +81,10 @@ std::string GetEN() {
}
std::string GetVer() {
return "2.3";
return "2.4";
}
std::string GetPatch() {
return ".2";
return ".1";
}
std::string GetEP(const char* P) {
@@ -175,7 +175,7 @@ void CheckForUpdates(const std::string& CV) {
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
if (!options.no_update) {
info("Launcher update found!");
info("Launcher update " + LatestVersion + " found!");
#if defined(__linux__)
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
#else
@@ -193,7 +193,7 @@ void CheckForUpdates(const std::string& CV) {
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
}
} else
info("Launcher version is up to date");
info("Launcher version is up to date. Latest version: " + LatestVersion);
TraceBack++;
}
@@ -231,6 +231,7 @@ void LinuxPatch() {
void InitLauncher() {
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
debug("Launcher Version : " + GetVer() + GetPatch());
CheckName();
LinuxPatch();
CheckLocalKey();