22 Commits

Author SHA1 Message Date
snepsnepsnep
5368d16f27 fix --no-update flag on windows 2024-09-23 01:29:24 +02:00
Lion Kortlepel
188a31c69e fix browser open 2024-06-29 22:56:11 +02:00
Lion Kortlepel
caab92375d properly clean up posix_spawn_file_actions 2024-06-28 15:40:50 +02:00
Lion Kortlepel
b76930b0bd fix game's stdout/stderr printing to launcher console on linux 2024-06-28 15:37:46 +02:00
Lion Kortlepel
96d579f64b fix bug which caused user path to print multiple times 2024-06-28 15:37:27 +02:00
Lion Kortlepel
ba35d039ae add --dev, --no-dev, --no-update flags
these will be replaced by #90 eventually
2024-06-28 09:23:42 +02:00
Lion Kortlepel
3535923f40 set dev to false 2024-06-28 09:23:42 +02:00
Lion Kortlepel
3ca6e7fd3d reduce compression to 3 instead of 6 2024-06-28 09:23:42 +02:00
Lion Kortlepel
7c24020124 implement receiving new header format 2024-06-28 09:23:30 +02:00
snepsnepsnep
88a9d4a1b1 flush console prints 2024-06-28 09:13:26 +02:00
Lion Kortlepel
4a5728d421 implement binary header 2024-06-28 09:13:26 +02:00
Lion Kortlepel
137d9dd1e2 implement string int header 2024-06-28 09:13:26 +02:00
Lion Kortlepel
7b733bf8eb temporarily set dev to true always 2024-06-27 09:34:52 +02:00
Lion Kortlepel
b034072027 fix potential UB in decompression 2024-06-23 23:04:55 +02:00
Lion Kortlepel
f94b9adf7a print game's U S E R path 2024-06-22 23:26:10 +02:00
Lion Kortlepel
c95178ea59 dont auto-update in dev mode 2024-06-22 23:20:46 +02:00
Lion Kortlepel
1f7c498bd9 fix compiler error in decomp 2024-06-22 23:05:01 +02:00
Lion Kortlepel
e46d4b2f0e Merge branch 'performance-improvements' 2024-06-22 23:01:15 +02:00
Lion Kortlepel
d32da036bc fix mod name bug 2024-06-21 17:30:47 +02:00
Lion Kortlepel
8b0f4f99f6 use thread_local static buffer to receive into, null term manually 2024-06-19 16:53:17 +02:00
Lion Kortlepel
17e887442c avoid a substr() which costs us ~20% of runtime performance 2024-06-19 16:18:11 +02:00
Lion Kortlepel
fc454cd11e avoid creating a thread every packet 2024-06-19 15:53:49 +02:00
17 changed files with 328 additions and 214 deletions

11
include/Helpers.h Normal file
View File

@@ -0,0 +1,11 @@
#include <span>
#include <string>
#include <vector>
#pragma once
using ByteSpan = std::span<const char>;
std::string bytespan_to_string(ByteSpan span);
std::vector<char> strtovec(std::string_view str);

View File

@@ -7,12 +7,15 @@
/// ///
#pragma once #pragma once
#include "Helpers.h"
#include <span>
#include <string> #include <string>
#ifdef __linux__ #ifdef __linux__
#include "linuxfixes.h" #include "linuxfixes.h"
#include <bits/types/siginfo_t.h> #include <bits/types/siginfo_t.h>
#include <cstdint> #include <cstdint>
#include <vector>
#include <sys/ucontext.h> #include <sys/ucontext.h>
#endif #endif
@@ -39,16 +42,16 @@ extern std::string PrivateKey;
extern std::string ListOfMods; extern std::string ListOfMods;
int KillSocket(uint64_t Dead); int KillSocket(uint64_t Dead);
void UUl(const std::string& R); void UUl(const std::string& R);
void UDPSend(std::string Data); void UDPSend(const std::vector<char>& Data);
bool CheckBytes(int32_t Bytes); bool CheckBytes(int32_t Bytes);
void GameSend(std::string Data); void GameSend(std::string_view Data);
void SendLarge(std::string Data); void SendLarge(const std::vector<char>& Data);
std::string TCPRcv(uint64_t Sock); std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock); void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string& IP); std::string GetAddr(const std::string& IP);
void ServerParser(const std::string& Data); void ServerParser(std::string_view Data);
std::string Login(const std::string& fields); std::string Login(const std::string& fields);
void TCPSend(const std::string& Data, uint64_t Sock); void TCPSend(const std::vector<char>& Data, uint64_t Sock);
void TCPClientMain(const std::string& IP, int Port); void TCPClientMain(const std::string& IP, int Port);
void UDPClientMain(const std::string& IP, int Port); void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port);

11
include/NetworkHelpers.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#if defined(__linux__)
#include "linuxfixes.h"
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <vector>
void ReceiveFromGame(SOCKET socket, std::vector<char>& out_data);

View File

@@ -19,10 +19,3 @@ void StartProxy();
void ConfigInit(); void ConfigInit();
extern bool Dev; extern bool Dev;
struct VersionParser {
explicit VersionParser(const std::string& from_string);
std::strong_ordering operator<=>(VersionParser const& rhs) const noexcept;
bool operator==(VersionParser const& rhs) const noexcept;
std::vector<std::string> split;
std::vector<size_t> data;
};

View File

@@ -9,5 +9,5 @@
#include <span> #include <span>
#include <vector> #include <vector>
std::vector<char> Comp(std::span<char> input); std::vector<char> Comp(std::span<const char> input);
std::vector<char> DeComp(std::span<char> input); std::vector<char> DeComp(std::span<const char> input);

View File

@@ -15,15 +15,16 @@
#include <cstring> #include <cstring>
#endif #endif
std::vector<char> Comp(std::span<char> input) { std::vector<char> Comp(std::span<const char> input) {
auto max_size = compressBound(input.size()); auto max_size = compressBound(input.size());
std::vector<char> output(max_size); std::vector<char> output(max_size);
uLongf output_size = output.size(); uLongf output_size = output.size();
int res = compress( int res = compress2(
reinterpret_cast<Bytef*>(output.data()), reinterpret_cast<Bytef*>(output.data()),
&output_size, &output_size,
reinterpret_cast<Bytef*>(input.data()), reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size())); static_cast<uLongf>(input.size()),
3);
if (res != Z_OK) { if (res != Z_OK) {
error("zlib compress() failed: " + std::to_string(res)); error("zlib compress() failed: " + std::to_string(res));
throw std::runtime_error("zlib compress() failed"); throw std::runtime_error("zlib compress() failed");
@@ -33,7 +34,7 @@ std::vector<char> Comp(std::span<char> input) {
return output; return output;
} }
std::vector<char> DeComp(std::span<char> input) { std::vector<char> DeComp(std::span<const char> input) {
std::vector<char> output_buffer(std::min<size_t>(input.size() * 5, 15 * 1024 * 1024)); std::vector<char> output_buffer(std::min<size_t>(input.size() * 5, 15 * 1024 * 1024));
uLongf output_size = output_buffer.size(); uLongf output_size = output_buffer.size();

View File

@@ -48,8 +48,8 @@ void ConfigInit() {
if (cfg.is_open()) { if (cfg.is_open()) {
cfg << cfg <<
R"({ R"({
"Port": 4444, "Port": 4444,
"Build": "Default" "Build": "Default"
})"; })";
cfg.close(); cfg.close();
} else { } else {

View File

@@ -10,6 +10,8 @@
#include <windows.h> #include <windows.h>
#elif defined(__linux__) #elif defined(__linux__)
#include "vdf_parser.hpp" #include "vdf_parser.hpp"
#include <cerrno>
#include <cstring>
#include <pwd.h> #include <pwd.h>
#include <spawn.h> #include <spawn.h>
#include <sys/types.h> #include <sys/types.h>
@@ -90,11 +92,27 @@ void StartGame(std::string Dir) {
} }
#elif defined(__linux__) #elif defined(__linux__)
void StartGame(std::string Dir) { void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64"); std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
char* argv[] = { filename.data(), NULL }; char* argv[] = { filename.data(), NULL };
pid_t pid; pid_t pid;
int result = posix_spawn(&pid, filename.c_str(), NULL, NULL, argv, environ);
posix_spawn_file_actions_t file_actions;
auto status = posix_spawn_file_actions_init(&file_actions);
// disable stdout
if (status != 0) {
error(std::string("posix_spawn_file_actions_init failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDOUT failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDERR failed: ") + std::strerror(errno));
}
// launch the game
int result = posix_spawn(&pid, filename.c_str(), &file_actions, NULL, argv, environ);
if (result != 0) { if (result != 0) {
error("Failed to Launch the game! launcher closing soon"); error("Failed to Launch the game! launcher closing soon");
@@ -104,6 +122,11 @@ void StartGame(std::string Dir) {
error("Game Closed! launcher closing soon"); error("Game Closed! launcher closing soon");
} }
status = posix_spawn_file_actions_destroy(&file_actions);
if (status != 0) {
warn(std::string("posix_spawn_file_actions_destroy failed: ") + std::strerror(errno));
}
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2); exit(2);
} }

9
src/Helpers.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "Helpers.h"
std::string bytespan_to_string(ByteSpan span) {
return std::string(span.data(), span.size());
}
std::vector<char> strtovec(std::string_view str) {
return std::vector<char>(str.begin(), str.end());
}

View File

@@ -50,35 +50,35 @@ void addToLog(const std::string& Line) {
} }
void info(const std::string& toPrint) { void info(const std::string& toPrint) {
std::string Print = getDate() + "[INFO] " + toPrint + "\n"; std::string Print = getDate() + "[INFO] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
} }
void debug(const std::string& toPrint) { void debug(const std::string& toPrint) {
if (!Dev) if (!Dev)
return; return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n"; std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
} }
void warn(const std::string& toPrint) { void warn(const std::string& toPrint) {
std::string Print = getDate() + "[WARN] " + toPrint + "\n"; std::string Print = getDate() + "[WARN] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
} }
void error(const std::string& toPrint) { void error(const std::string& toPrint) {
std::string Print = getDate() + "[ERROR] " + toPrint + "\n"; std::string Print = getDate() + "[ERROR] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
} }
void fatal(const std::string& toPrint) { void fatal(const std::string& toPrint) {
std::string Print = getDate() + "[FATAL] " + toPrint + "\n"; std::string Print = getDate() + "[FATAL] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
_Exit(-1); _Exit(-1);
} }
void except(const std::string& toPrint) { void except(const std::string& toPrint) {
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n"; std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";
std::cout << Print; std::cout << Print << std::flush;
addToLog(Print); addToLog(Print);
} }

View File

@@ -7,6 +7,7 @@
/// ///
#include "Http.h" #include "Http.h"
#include "Network/network.hpp" #include "Network/network.hpp"
#include "NetworkHelpers.h"
#include "Security/Init.h" #include "Security/Init.h"
#include <cstdlib> #include <cstdlib>
#include <regex> #include <regex>
@@ -67,42 +68,62 @@ void StartSync(const std::string& Data) {
} }
bool IsAllowedLink(const std::string& Link) { bool IsAllowedLink(const std::string& Link) {
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg))"); std::vector<std::string> allowed_links = {
std::smatch link_match; R"(patreon\.com\/beammp$)",
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0; R"(discord\.gg\/beammp$)",
R"(forum\.beammp\.com$)",
R"(beammp\.com$)",
R"(patreon\.com\/beammp\/$)",
R"(discord\.gg\/beammp\/$)",
R"(forum\.beammp\.com\/$)",
R"(beammp\.com\/$)",
R"(docs\.beammp\.com$)",
R"(wiki\.beammp\.com$)",
R"(docs\.beammp\.com\/$)",
R"(wiki\.beammp\.com\/$)",
R"(docs\.beammp\.com\/.*$)",
R"(wiki\.beammp\.com\/.*$)",
};
for (const auto& allowed_link : allowed_links) {
if (std::regex_match(Link, std::regex(std::string(R"(^http(s)?:\/\/)") + allowed_link))) {
return true;
}
}
return false;
} }
void Parse(std::string Data, SOCKET CSocket) { void Parse(std::span<char> InData, SOCKET CSocket) {
char Code = Data.at(0), SubCode = 0; std::string OutData;
if (Data.length() > 1) char Code = InData[0], SubCode = 0;
SubCode = Data.at(1); if (InData.size() > 1)
SubCode = InData[1];
switch (Code) { switch (Code) {
case 'A': case 'A':
Data = Data.substr(0, 1); OutData = "A";
break; break;
case 'B': case 'B':
NetReset(); NetReset();
Terminate = true; Terminate = true;
TCPTerminate = true; TCPTerminate = true;
Data = Code + HTTP::Get("https://backend.beammp.com/servers-info"); OutData = Code + HTTP::Get("https://backend.beammp.com/servers-info");
break; break;
case 'C': case 'C':
ListOfMods.clear(); ListOfMods.clear();
StartSync(Data); StartSync(std::string(InData.data(), InData.size()));
while (ListOfMods.empty() && !Terminate) { while (ListOfMods.empty() && !Terminate) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
if (ListOfMods == "-") if (ListOfMods == "-")
Data = "L"; OutData = "L";
else else
Data = "L" + ListOfMods; OutData = "L" + ListOfMods;
break; break;
case 'O': // open default browser with URL case 'O': // open default browser with URL
if (IsAllowedLink(Data.substr(1))) { if (IsAllowedLink(bytespan_to_string(InData.subspan(1)))) {
#if defined(__linux) #if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) { if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid; pid_t pid;
auto arg = Data.substr(1); auto arg = bytespan_to_string(InData.subspan(1));
char* argv[] = { browser, arg.data() }; char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ); auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) { if (status == 0) {
@@ -114,27 +135,27 @@ void Parse(std::string Data, SOCKET CSocket) {
error(std::string("posix_spawn: ") + strerror(status)); error(std::string("posix_spawn: ") + strerror(status));
} }
} else { } else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1)); error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + bytespan_to_string(InData.subspan(1)));
} }
#elif defined(WIN32) #elif defined(WIN32)
ShellExecuteA(nullptr, "open", Data.substr(1).c_str(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port ShellExecuteA(nullptr, "open", InData.subspan(1).data(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
#endif #endif
info("Opening Link \"" + Data.substr(1) + "\""); info("Opening Link \"" + bytespan_to_string(InData.subspan(1)) + "\"");
} }
Data.clear(); OutData.clear();
break; break;
case 'P': case 'P':
Data = Code + std::to_string(ProxyPort); OutData = Code + std::to_string(ProxyPort);
break; break;
case 'U': case 'U':
if (SubCode == 'l') if (SubCode == 'l')
Data = UlStatus; OutData = UlStatus;
if (SubCode == 'p') { if (SubCode == 'p') {
if (ping > 800) { if (ping > 800) {
Data = "Up-2"; OutData = "Up-2";
} else } else
Data = "Up" + std::to_string(ping); OutData = "Up" + std::to_string(ping);
} }
if (!SubCode) { if (!SubCode) {
std::string Ping; std::string Ping;
@@ -142,11 +163,11 @@ void Parse(std::string Data, SOCKET CSocket) {
Ping = "-2"; Ping = "-2";
else else
Ping = std::to_string(ping); Ping = std::to_string(ping);
Data = std::string(UlStatus) + "\n" + "Up" + Ping; OutData = std::string(UlStatus) + "\n" + "Up" + Ping;
} }
break; break;
case 'M': case 'M':
Data = MStatus; OutData = MStatus;
break; break;
case 'Q': case 'Q':
if (SubCode == 'S') { if (SubCode == 'S') {
@@ -157,17 +178,19 @@ void Parse(std::string Data, SOCKET CSocket) {
} }
if (SubCode == 'G') if (SubCode == 'G')
exit(2); exit(2);
Data.clear(); OutData.clear();
break; break;
case 'R': // will send mod name case 'R': // will send mod name
if (ConfList->find(Data) == ConfList->end()) { {
ConfList->insert(Data); auto str = bytespan_to_string(InData);
if (ConfList->find(str) == ConfList->end()) {
ConfList->insert(str);
ModLoaded = true; ModLoaded = true;
} }
Data.clear(); OutData.clear();
break; } break;
case 'Z': case 'Z':
Data = "Z" + GetVer(); OutData = "Z" + GetVer();
break; break;
case 'N': case 'N':
if (SubCode == 'c') { if (SubCode == 'c') {
@@ -180,64 +203,38 @@ void Parse(std::string Data, SOCKET CSocket) {
if (!UserRole.empty()) { if (!UserRole.empty()) {
Auth["role"] = UserRole; Auth["role"] = UserRole;
} }
Data = "N" + Auth.dump(); OutData = "N" + Auth.dump();
} else { } else {
Data = "N" + Login(Data.substr(Data.find(':') + 1)); auto indata_str = bytespan_to_string(InData);
OutData = "N" + Login(indata_str.substr(indata_str.find(':') + 1));
} }
break; break;
default: default:
Data.clear(); OutData.clear();
break; break;
} }
if (!Data.empty() && CSocket != -1) { if (!OutData.empty() && CSocket != -1) {
int res = send(CSocket, (Data + "\n").c_str(), int(Data.size()) + 1, 0); uint32_t DataSize = OutData.size();
std::vector<char> ToSend(sizeof(DataSize) + OutData.size());
std::copy_n(reinterpret_cast<char*>(&DataSize), sizeof(DataSize), ToSend.begin());
std::copy_n(OutData.data(), OutData.size(), ToSend.begin() + sizeof(DataSize));
int res = send(CSocket, ToSend.data(), int(ToSend.size()), 0);
if (res < 0) { if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError())); debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
} }
} }
} }
void GameHandler(SOCKET Client) { void GameHandler(SOCKET Client) {
std::vector<char> data {};
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
do { do {
Rcv = 0; try {
do { ReceiveFromGame(Client, data);
Temp = recv(Client, &Header[Rcv], 1, 0); Parse(data, Client);
if (Temp < 1) } catch (const std::exception& e) {
break; error(std::string("Error while receiving from game: ") + e.what());
if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
error("(Core) Invalid lua communication");
KillSocket(Client);
return;
}
} while (Header[Rcv++] != '>');
if (Temp < 1)
break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Core) Invalid lua Header -> " + std::string(Header, Rcv));
break; break;
} }
std::string Ret(Size, 0); } while (true);
Rcv = 0;
do {
Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
} while (Rcv < Size);
if (Temp < 1)
break;
std::thread Respond(Parse, Ret, Client);
Respond.detach();
} while (Temp > 0);
if (Temp == 0) {
debug("(Core) Connection closing");
} else {
debug("(Core) recv failed with error: " + std::to_string(WSAGetLastError()));
}
NetReset(); NetReset();
KillSocket(Client); KillSocket(Client);
} }
@@ -283,6 +280,11 @@ void CoreMain() {
WSACleanup(); WSACleanup();
return; return;
} }
#if defined(__linux__)
int opt = 1;
if (setsockopt(LSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
error("setsockopt(SO_REUSEADDR) failed");
#endif
iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen)); iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen));
if (iRes == SOCKET_ERROR) { if (iRes == SOCKET_ERROR) {
error("(Core) bind failed with error: " + std::to_string(WSAGetLastError())); error("(Core) bind failed with error: " + std::to_string(WSAGetLastError()));

View File

@@ -5,7 +5,12 @@
/// ///
/// Created by Anonymous275 on 7/25/2020 /// Created by Anonymous275 on 7/25/2020
/// ///
#include "Helpers.h"
#include "Network/network.hpp" #include "Network/network.hpp"
#include "NetworkHelpers.h"
#include <algorithm>
#include <span>
#include <vector>
#include <zlib.h> #include <zlib.h>
#if defined(_WIN32) #if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
@@ -56,14 +61,17 @@ bool CheckBytes(uint32_t Bytes) {
return true; return true;
} }
void GameSend(std::string Data) { void GameSend(std::string_view RawData) {
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
if (TCPTerminate || !GConnected || CSocket == -1) if (TCPTerminate || !GConnected || CSocket == -1)
return; return;
int32_t Size, Temp, Sent; int32_t Size, Temp, Sent;
Data += '\n'; uint32_t DataSize = RawData.size();
Size = int32_t(Data.size()); std::vector<char> Data(sizeof(DataSize) + RawData.size());
std::copy_n(reinterpret_cast<char*>(&DataSize), sizeof(DataSize), Data.begin());
std::copy_n(RawData.data(), RawData.size(), Data.begin() + sizeof(DataSize));
Size = Data.size();
Sent = 0; Sent = 0;
#ifdef DEBUG #ifdef DEBUG
if (Size > 1000) { if (Size > 1000) {
@@ -78,16 +86,19 @@ void GameSend(std::string Data) {
return; return;
Sent += Temp; Sent += Temp;
} while (Sent < Size); } while (Sent < Size);
// send separately to avoid an allocation for += "\n"
/*Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) {
return;
}*/
} }
void ServerSend(std::string Data, bool Rel) {
void ServerSend(const std::vector<char>& Data, bool Rel) {
if (Terminate || Data.empty()) if (Terminate || Data.empty())
return; return;
if (Data.find("Zp") != std::string::npos && Data.size() > 500) {
abort();
}
char C = 0; char C = 0;
bool Ack = false; bool Ack = false;
int DLen = int(Data.length()); int DLen = int(Data.size());
if (DLen > 3) if (DLen > 3)
C = Data.at(0); C = Data.at(0);
if (C == 'O' || C == 'T') if (C == 'O' || C == 'T')
@@ -103,14 +114,6 @@ void ServerSend(std::string Data, bool Rel) {
TCPSend(Data, TCPSock); TCPSend(Data, TCPSock);
} else } else
UDPSend(Data); UDPSend(Data);
if (DLen > 1000) {
debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : "
+ Data.substr(0, 10)
+ Data.substr(Data.length() - 10));
} else if (C == 'Z') {
// debug("(Game->Launcher) : " + Data);
}
} }
void NetReset() { void NetReset() {
@@ -168,6 +171,11 @@ SOCKET SetupListener() {
WSACleanup(); WSACleanup();
return -1; return -1;
} }
#if defined (__linux__)
int opt = 1;
if (setsockopt(GSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
error("setsockopt(SO_REUSEADDR) failed");
#endif
iRes = bind(GSocket, result->ai_addr, (int)result->ai_addrlen); iRes = bind(GSocket, result->ai_addr, (int)result->ai_addrlen);
if (iRes == SOCKET_ERROR) { if (iRes == SOCKET_ERROR) {
error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError())); error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError()));
@@ -188,13 +196,13 @@ SOCKET SetupListener() {
} }
void AutoPing() { void AutoPing() {
while (!Terminate) { while (!Terminate) {
ServerSend("p", false); ServerSend(strtovec("p"), false);
PingStart = std::chrono::high_resolution_clock::now(); PingStart = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
} }
int ClientID = -1; int ClientID = -1;
void ParserAsync(const std::string& Data) { void ParserAsync(std::string_view Data) {
if (Data.empty()) if (Data.empty())
return; return;
char Code = Data.at(0), SubCode = 0; char Code = Data.at(0), SubCode = 0;
@@ -217,7 +225,7 @@ void ParserAsync(const std::string& Data) {
} }
GameSend(Data); GameSend(Data);
} }
void ServerParser(const std::string& Data) { void ServerParser(std::string_view Data) {
ParserAsync(Data); ParserAsync(Data);
} }
void NetMain(const std::string& IP, int Port) { void NetMain(const std::string& IP, int Port) {
@@ -256,42 +264,18 @@ void TCPGameServer(const std::string& IP, int Port) {
t1.detach(); t1.detach();
CServer = false; CServer = false;
} }
int32_t Size, Temp, Rcv; std::vector<char> data {};
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
do { do {
Rcv = 0; try {
ReceiveFromGame(CSocket, data);
do { ServerSend(data, false);
Temp = recv(CSocket, &Header[Rcv], 1, 0); } catch (const std::exception& e) {
if (Temp < 1 || TCPTerminate) error(std::string("Error while receiving from game: ") + e.what());
break;
} while (Header[Rcv++] != '>');
if (Temp < 1 || TCPTerminate)
break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Game) Invalid lua Header -> " + std::string(Header, Rcv));
break; break;
} }
std::string Ret(Size, 0); } while (!TCPTerminate);
Rcv = 0;
do {
Temp = recv(CSocket, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
} while (Rcv < Size && !TCPTerminate);
if (Temp < 1 || TCPTerminate)
break;
ServerSend(Ret, false);
} while (Temp > 0 && !TCPTerminate);
if (Temp == 0)
debug("(Proxy) Connection closing");
else
debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
} }
TCPTerminate = true; TCPTerminate = true;
GConnected = false; GConnected = false;

View File

@@ -73,7 +73,7 @@ void Abord() {
} }
std::string Auth(SOCKET Sock) { std::string Auth(SOCKET Sock) {
TCPSend("VC" + GetVer(), Sock); TCPSend(strtovec("VC" + GetVer()), Sock);
auto Res = TCPRcv(Sock); auto Res = TCPRcv(Sock);
@@ -82,7 +82,7 @@ std::string Auth(SOCKET Sock) {
return ""; return "";
} }
TCPSend(PublicKey, Sock); TCPSend(strtovec(PublicKey), Sock);
if (Terminate) if (Terminate)
return ""; return "";
@@ -100,7 +100,7 @@ std::string Auth(SOCKET Sock) {
UUl("Authentication failed!"); UUl("Authentication failed!");
return ""; return "";
} }
TCPSend("SR", Sock); TCPSend(strtovec("SR"), Sock);
if (Terminate) if (Terminate)
return ""; return "";
@@ -114,7 +114,7 @@ std::string Auth(SOCKET Sock) {
if (Res.empty() || Res == "-") { if (Res.empty() || Res == "-") {
info("Didn't Receive any mods..."); info("Didn't Receive any mods...");
ListOfMods = "-"; ListOfMods = "-";
TCPSend("Done", Sock); TCPSend(strtovec("Done"), Sock);
info("Done!"); info("Done!");
return ""; return "";
} }
@@ -296,13 +296,14 @@ void SyncResources(SOCKET Sock) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
} }
auto name = GetGamePath() + "mods/multiplayer" + a.substr(a.find_last_of('/')); auto modname = a.substr(a.find_last_of('/'));
#if defined(__linux__) #if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names // Linux version of the game doesnt support uppercase letters in mod names
for (char& c : name) { for (char& c : modname) {
c = ::tolower(c); c = ::tolower(c);
} }
#endif #endif
auto name = GetGamePath() + "mods/multiplayer" + modname;
auto tmp_name = name + ".tmp"; auto tmp_name = name + ".tmp";
fs::copy_file(a, tmp_name, fs::copy_options::overwrite_existing); fs::copy_file(a, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name); fs::rename(tmp_name, name);
@@ -319,7 +320,7 @@ void SyncResources(SOCKET Sock) {
CheckForDir(); CheckForDir();
std::string FName = a.substr(a.find_last_of('/')); std::string FName = a.substr(a.find_last_of('/'));
do { do {
TCPSend("f" + *FN, Sock); TCPSend(strtovec("f" + *FN), Sock);
std::string Data = TCPRcv(Sock); std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) { if (Data == "CO" || Terminate) {
@@ -361,7 +362,7 @@ void SyncResources(SOCKET Sock) {
} }
KillSocket(DSock); KillSocket(DSock);
if (!Terminate) { if (!Terminate) {
TCPSend("Done", Sock); TCPSend(strtovec("Done"), Sock);
info("Done!"); info("Done!");
} else { } else {
UlStatus = "Ulstart"; UlStatus = "Ulstart";

View File

@@ -14,25 +14,27 @@
#include "linuxfixes.h" #include "linuxfixes.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <cstring> #include <cstring>
#include <errno.h>
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#endif #endif
#include "Logger.h" #include "Logger.h"
#include <set> #include <array>
#include <string> #include <string>
SOCKET UDPSock = -1; SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr; sockaddr_in* ToServer = nullptr;
void UDPSend(std::string Data) { void UDPSend(const std::vector<char>& RawData) {
if (ClientID == -1 || UDPSock == -1) if (ClientID == -1 || UDPSock == -1)
return; return;
if (Data.length() > 400) { std::string Data;
auto res = Comp(std::span<char>(Data.data(), Data.size())); if (Data.size() > 400) {
auto res = Comp(RawData);
Data = "ABG:" + std::string(res.data(), res.size()); Data = "ABG:" + std::string(res.data(), res.size());
} else {
Data = std::string(RawData.data(), RawData.size());
} }
std::string Packet = char(ClientID + 1) + std::string(":") + Data; std::string Packet = char(ClientID + 1) + std::string(":") + Data;
int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer)); int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer));
@@ -40,21 +42,25 @@ void UDPSend(std::string Data) {
error("Error Code : " + std::to_string(WSAGetLastError())); error("Error Code : " + std::to_string(WSAGetLastError()));
} }
void SendLarge(std::string Data) { void SendLarge(const std::vector<char>& Data) {
if (Data.length() > 400) { if (Data.size() > 400) {
auto res = Comp(std::span<char>(Data.data(), Data.size())); auto res = Comp(Data);
Data = "ABG:" + std::string(res.data(), res.size()); res.insert(res.begin(), {'A', 'B', 'G', ':'});
TCPSend(res, TCPSock);
} else {
TCPSend(Data, TCPSock);
} }
TCPSend(Data, TCPSock);
} }
void UDPParser(std::string Packet) { void UDPParser(std::string_view Packet) {
if (Packet.substr(0, 4) == "ABG:") { if (Packet.substr(0, 4) == "ABG:") {
auto substr = Packet.substr(4); auto substr = Packet.substr(4);
auto res = DeComp(std::span<char>(substr.data(), substr.size())); auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
Packet = std::string(res.data(), res.size()); std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket);
} else {
ServerParser(Packet);
} }
ServerParser(Packet);
} }
void UDPRcv() { void UDPRcv() {
sockaddr_in FromServer {}; sockaddr_in FromServer {};
@@ -64,13 +70,14 @@ void UDPRcv() {
socklen_t clientLength = sizeof(FromServer); socklen_t clientLength = sizeof(FromServer);
#endif #endif
ZeroMemory(&FromServer, clientLength); ZeroMemory(&FromServer, clientLength);
std::string Ret(10240, 0); static thread_local std::array<char, 10240> Ret {};
if (UDPSock == -1) if (UDPSock == -1)
return; return;
int32_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&FromServer, &clientLength); int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
if (Rcv == SOCKET_ERROR) if (Rcv == SOCKET_ERROR)
return; return;
UDPParser(Ret.substr(0, Rcv)); Ret[Rcv] = 0;
UDPParser(std::string_view(Ret.data(), Rcv));
} }
void UDPClientMain(const std::string& IP, int Port) { void UDPClientMain(const std::string& IP, int Port) {
#ifdef _WIN32 #ifdef _WIN32
@@ -88,8 +95,8 @@ void UDPClientMain(const std::string& IP, int Port) {
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr); inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
UDPSock = socket(AF_INET, SOCK_DGRAM, 0); UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
GameSend("P" + std::to_string(ClientID)); GameSend("P" + std::to_string(ClientID));
TCPSend("H", TCPSock); TCPSend(strtovec("H"), TCPSock);
UDPSend("p"); UDPSend(strtovec("p"));
while (!Terminate) while (!Terminate)
UDPRcv(); UDPRcv();
KillSocket(UDPSock); KillSocket(UDPSock);

View File

@@ -46,7 +46,7 @@ void UUl(const std::string& R) {
UlStatus = "UlDisconnected: " + R; UlStatus = "UlDisconnected: " + R;
} }
void TCPSend(const std::string& Data, uint64_t Sock) { void TCPSend(const std::vector<char>& Data, uint64_t Sock) {
if (Sock == -1) { if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
@@ -57,7 +57,7 @@ void TCPSend(const std::string& Data, uint64_t Sock) {
std::string Send(4, 0); std::string Send(4, 0);
Size = int32_t(Data.size()); Size = int32_t(Data.size());
memcpy(&Send[0], &Size, sizeof(Size)); memcpy(&Send[0], &Size, sizeof(Size));
Send += Data; Send += std::string(Data.data(), Data.size());
// Do not use Size before this point for anything but the header // Do not use Size before this point for anything but the header
Sent = 0; Sent = 0;
Size += 4; Size += 4;
@@ -113,7 +113,7 @@ std::string TCPRcv(SOCKET Sock) {
if (Ret.substr(0, 4) == "ABG:") { if (Ret.substr(0, 4) == "ABG:") {
auto substr = Ret.substr(4); auto substr = Ret.substr(4);
auto res = DeComp(std::span<char>(substr.data(), substr.size())); auto res = DeComp(strtovec(substr));
Ret = std::string(res.data(), res.size()); Ret = std::string(res.data(), res.size());
} }

35
src/NetworkHelpers.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "NetworkHelpers.h"
#include <array>
#include <cerrno>
#include <cstring>
#include <stdexcept>
#if defined(__linux__)
#include <sys/socket.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
static uint32_t RecvHeader(SOCKET socket) {
std::array<uint8_t, sizeof(uint32_t)> header_buffer {};
auto n = recv(socket, reinterpret_cast<char*>(header_buffer.data()), header_buffer.size(), MSG_WAITALL);
if (n < 0) {
throw std::runtime_error(std::string("recv() of header failed: ") + std::strerror(errno));
} else if (n == 0) {
throw std::runtime_error("Game disconnected");
}
return *reinterpret_cast<uint32_t*>(header_buffer.data());
}
/// Throws!!!
void ReceiveFromGame(SOCKET socket, std::vector<char>& out_data) {
auto header = RecvHeader(socket);
out_data.resize(header);
auto n = recv(socket, reinterpret_cast<char*>(out_data.data()), out_data.size(), MSG_WAITALL);
if (n < 0) {
throw std::runtime_error(std::string("recv() of data failed: ") + std::strerror(errno));
} else if (n == 0) {
throw std::runtime_error("Game disconnected");
}
}

View File

@@ -7,6 +7,7 @@
/// ///
#include "zip_file.h" #include "zip_file.h"
#include <charconv>
#include <httplib.h> #include <httplib.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string> #include <string>
@@ -31,36 +32,44 @@ int ProxyPort = 0;
namespace fs = std::filesystem; namespace fs = std::filesystem;
VersionParser::VersionParser(const std::string& from_string) { struct Version {
std::string token; uint8_t major;
std::istringstream tokenStream(from_string); uint8_t minor;
while (std::getline(tokenStream, token, '.')) { uint8_t patch;
data.emplace_back(std::stol(token)); Version(uint8_t major, uint8_t minor, uint8_t patch);
split.emplace_back(token); Version(const std::array<uint8_t, 3>& v);
};
std::array<uint8_t, 3> VersionStrToInts(const std::string& str) {
std::array<uint8_t, 3> Version;
std::stringstream ss(str);
for (uint8_t& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
bool IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
} else if (Newest.major == Current.major && Newest.minor > Current.minor) {
return true;
} else if (Newest.major == Current.major && Newest.minor == Current.minor && Newest.patch > Current.patch) {
return true;
} else {
return false;
} }
} }
std::strong_ordering VersionParser::operator<=>( Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
const VersionParser& rhs) const noexcept { : major(major)
size_t const fields = std::min(data.size(), rhs.data.size()); , minor(minor)
for (size_t i = 0; i != fields; ++i) { , patch(patch) { }
if (data[i] == rhs.data[i])
continue;
else if (data[i] < rhs.data[i])
return std::strong_ordering::less;
else
return std::strong_ordering::greater;
}
if (data.size() == rhs.data.size())
return std::strong_ordering::equal;
else if (data.size() > rhs.data.size())
return std::strong_ordering::greater;
else
return std::strong_ordering::less;
}
bool VersionParser::operator==(const VersionParser& rhs) const noexcept { Version::Version(const std::array<uint8_t, 3>& v)
return std::is_eq(*this <=> rhs); : Version(v[0], v[1], v[2]) {
} }
std::string GetEN() { std::string GetEN() {
@@ -72,10 +81,10 @@ std::string GetEN() {
} }
std::string GetVer() { std::string GetVer() {
return "2.0"; return "2.1";
} }
std::string GetPatch() { std::string GetPatch() {
return ".85"; return ".0";
} }
std::string GetEP(char* P) { std::string GetEP(char* P) {
@@ -163,7 +172,7 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
system("clear"); system("clear");
#endif #endif
if (FileHash != LatestHash && VersionParser(LatestVersion) > VersionParser(GetVer() + GetPatch())) { if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
info("Launcher update found!"); info("Launcher update 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.");
@@ -177,7 +186,7 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
+ PublicKey + "&branch=" + Branch, + PublicKey + "&branch=" + Branch,
EP); EP);
URelaunch(argc, args); URelaunch(argc, args);
#endif #endif
} else } else
info("Launcher version is up to date"); info("Launcher version is up to date");
TraceBack++; TraceBack++;
@@ -195,6 +204,13 @@ void CustomPort(int argc, char* argv[]) {
if (argc > 2) if (argc > 2)
Dev = true; Dev = true;
} }
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "--dev") {
Dev = true;
} else if (std::string_view(argv[i]) == "--no-dev") {
Dev = false;
}
}
} }
#ifdef _WIN32 #ifdef _WIN32
@@ -236,7 +252,15 @@ void InitLauncher(int argc, char* argv[]) {
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch()); bool update = true;
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "--no-update") {
update = false;
}
}
if (update) {
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
} }
#elif defined(__linux__) #elif defined(__linux__)
void InitLauncher(int argc, char* argv[]) { void InitLauncher(int argc, char* argv[]) {
@@ -246,7 +270,15 @@ void InitLauncher(int argc, char* argv[]) {
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch()); bool update = true;
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "--no-update") {
update = false;
}
}
if (update) {
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
} }
#endif #endif
@@ -309,6 +341,8 @@ void PreGame(const std::string& GamePath) {
CheckMP(GetGamePath() + "mods/multiplayer"); CheckMP(GetGamePath() + "mods/multiplayer");
info("Game user path: '" + GetGamePath() + "'");
if (!Dev) { if (!Dev) {
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?branch=" + Branch + "&pk=" + PublicKey);
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower); transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);