mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2026-04-03 14:26:15 +00:00
Compare commits
1 Commits
v2.1.2
...
95-ipv6-su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169b14490c |
2
.github/workflows/cmake-linux.yml
vendored
2
.github/workflows/cmake-linux.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: BeamMP-Launcher
|
||||
path: ${{github.workspace}}/build-linux/BeamMP-Launcher
|
||||
|
||||
2
.github/workflows/cmake-windows.yml
vendored
2
.github/workflows/cmake-windows.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: BeamMP-Launcher.exe
|
||||
path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe
|
||||
|
||||
104
.github/workflows/release-build.yml
vendored
Normal file
104
.github/workflows/release-build.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
name: Release Create & Build
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
name: Create Release
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: |
|
||||
Files included in this release:
|
||||
- `BeamMP-Launcher.exe` windows build
|
||||
- `BeamMP-Launcher` linux build
|
||||
|
||||
upload-release-files-windows:
|
||||
name: Upload Windows Release Files
|
||||
runs-on: windows-latest
|
||||
needs: create-release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Restore artifacts, or run vcpkg, build and cache artifacts
|
||||
uses: lukka/run-vcpkg@main
|
||||
id: runvcpkg
|
||||
with:
|
||||
vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: '16ee2ecb31788c336ace8bb14c21801efb6836e4'
|
||||
vcpkgTriplet: 'x64-windows-static'
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-windows
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe
|
||||
asset_name: BeamMP-Launcher.exe
|
||||
asset_content_type: application/vnd.microsoft.portable-executable
|
||||
|
||||
upload-release-files-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-linux
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ${{github.workspace}}/build-linux/BeamMP-Launcher
|
||||
asset_name: BeamMP-Launcher
|
||||
asset_content_type: application/octet-stream
|
||||
15
README.md
15
README.md
@@ -2,24 +2,11 @@
|
||||
|
||||
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
|
||||
|
||||
## How to build - 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.
|
||||
|
||||
## How to build - 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.
|
||||
|
||||
Copyright (c) 2019-present Anonymous275.
|
||||
BeamMP Launcher code is not in the public domain and is not free software.
|
||||
One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries,
|
||||
|
||||
@@ -14,8 +14,7 @@ public:
|
||||
static std::string Post(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 void StartProxy();
|
||||
|
||||
public:
|
||||
static bool isDownload;
|
||||
static inline bool SkipSslVerify = false;
|
||||
};
|
||||
};
|
||||
@@ -30,13 +30,13 @@ extern int DEFAULT_PORT;
|
||||
extern uint64_t UDPSock;
|
||||
extern uint64_t TCPSock;
|
||||
extern std::string Branch;
|
||||
extern std::string CachingDirectory;
|
||||
extern bool TCPTerminate;
|
||||
extern std::string LastIP;
|
||||
extern std::string MStatus;
|
||||
extern std::string UlStatus;
|
||||
extern std::string PublicKey;
|
||||
extern std::string PrivateKey;
|
||||
extern std::string ListOfMods;
|
||||
int KillSocket(uint64_t Dead);
|
||||
void UUl(const std::string& R);
|
||||
void UDPSend(std::string Data);
|
||||
@@ -52,5 +52,3 @@ void TCPSend(const std::string& Data, uint64_t Sock);
|
||||
void TCPClientMain(const std::string& IP, int Port);
|
||||
void UDPClientMain(const std::string& IP, int Port);
|
||||
void TCPGameServer(const std::string& IP, int Port);
|
||||
bool SecurityWarning();
|
||||
void CoreSend(std::string data);
|
||||
|
||||
@@ -14,8 +14,8 @@ void InitLauncher(int argc, char* argv[]);
|
||||
std::string GetEP(char* P = nullptr);
|
||||
std::string GetGamePath();
|
||||
std::string GetVer();
|
||||
std::string GetPatch();
|
||||
std::string GetEN();
|
||||
void StartProxy();
|
||||
void ConfigInit();
|
||||
extern bool Dev;
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Utils {
|
||||
inline std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
|
||||
std::vector<std::string> Val;
|
||||
size_t pos;
|
||||
std::string token, s = String;
|
||||
while ((pos = s.find(delimiter)) != std::string::npos) {
|
||||
token = s.substr(0, pos);
|
||||
if (!token.empty())
|
||||
Val.push_back(token);
|
||||
s.erase(0, pos + delimiter.length());
|
||||
}
|
||||
if (!s.empty())
|
||||
Val.push_back(s);
|
||||
return Val;
|
||||
};
|
||||
};
|
||||
@@ -11,8 +11,6 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string Branch;
|
||||
std::string CachingDirectory = "./Resources";
|
||||
|
||||
void ParseConfig(const nlohmann::json& d) {
|
||||
if (d["Port"].is_number()) {
|
||||
DEFAULT_PORT = d["Port"].get<int>();
|
||||
@@ -22,15 +20,12 @@ void ParseConfig(const nlohmann::json& d) {
|
||||
// EA 2
|
||||
// Dev 3
|
||||
// Custom 3
|
||||
|
||||
if (d["Build"].is_string()) {
|
||||
Branch = d["Build"].get<std::string>();
|
||||
for (char& c : Branch)
|
||||
c = char(tolower(c));
|
||||
}
|
||||
if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) {
|
||||
CachingDirectory = d["CachingDirectory"].get<std::string>();
|
||||
info("Mod caching directory: " + CachingDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigInit() {
|
||||
@@ -54,8 +49,7 @@ void ConfigInit() {
|
||||
cfg <<
|
||||
R"({
|
||||
"Port": 4444,
|
||||
"Build": "Default",
|
||||
"CachingDirectory": "./Resources",
|
||||
"Build": "Default"
|
||||
})";
|
||||
cfg.close();
|
||||
} else {
|
||||
|
||||
@@ -43,20 +43,15 @@ std::string GetGamePath() {
|
||||
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)";
|
||||
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
|
||||
if (openRes != ERROR_SUCCESS) {
|
||||
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders)";
|
||||
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
|
||||
fatal("Cannot get Local Appdata directory!");
|
||||
}
|
||||
if (openRes != ERROR_SUCCESS) {
|
||||
fatal("Cannot get Local Appdata directory");
|
||||
}
|
||||
|
||||
Path = QueryKey(hKey, 5);
|
||||
Path += "\\BeamNG.drive\\";
|
||||
}
|
||||
|
||||
std::string Ver = CheckVer(GetGameDir());
|
||||
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
|
||||
Path += Ver + "\\";
|
||||
info("Game user path: '" + Path + "'");
|
||||
return Path;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
@@ -69,6 +64,7 @@ std::string GetGamePath() {
|
||||
std::string Ver = CheckVer(GetGameDir());
|
||||
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
|
||||
Path += Ver + "/";
|
||||
info("Game user path: '" + Path + "'");
|
||||
return Path;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -54,10 +54,10 @@ void info(const std::string& toPrint) {
|
||||
addToLog(Print);
|
||||
}
|
||||
void debug(const std::string& toPrint) {
|
||||
if (!Dev)
|
||||
return;
|
||||
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
|
||||
if (Dev) {
|
||||
std::cout << Print;
|
||||
}
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void warn(const std::string& toPrint) {
|
||||
@@ -75,7 +75,7 @@ void fatal(const std::string& toPrint) {
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
std::exit(1);
|
||||
_Exit(-1);
|
||||
}
|
||||
void except(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";
|
||||
|
||||
@@ -44,26 +44,6 @@ std::string UlStatus;
|
||||
std::string MStatus;
|
||||
bool ModLoaded;
|
||||
int ping = -1;
|
||||
SOCKET CoreSocket = -1;
|
||||
signed char confirmed = -1;
|
||||
|
||||
bool SecurityWarning() {
|
||||
confirmed = -1;
|
||||
CoreSend("WMODS_FOUND");
|
||||
|
||||
while (confirmed == -1)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
if (confirmed == 1)
|
||||
return true;
|
||||
|
||||
NetReset();
|
||||
Terminate = true;
|
||||
TCPTerminate = true;
|
||||
ping = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void StartSync(const std::string& Data) {
|
||||
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
|
||||
@@ -72,8 +52,8 @@ void StartSync(const std::string& Data) {
|
||||
UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
|
||||
else
|
||||
UlStatus = "UlConnection Failed! (WSA failed to start)";
|
||||
ListOfMods = "-";
|
||||
Terminate = true;
|
||||
CoreSend("L");
|
||||
return;
|
||||
}
|
||||
CheckLocalKey();
|
||||
@@ -87,17 +67,8 @@ void StartSync(const std::string& Data) {
|
||||
info("Connecting to server");
|
||||
}
|
||||
|
||||
void CoreSend(std::string data) {
|
||||
if (CoreSocket != -1) {
|
||||
int res = send(CoreSocket, (data + "\n").c_str(), int(data.size()) + 1, 0);
|
||||
if (res < 0) {
|
||||
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsAllowedLink(const std::string& Link) {
|
||||
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg|patreon\.com\/BeamMP))");
|
||||
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg))");
|
||||
std::smatch link_match;
|
||||
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
|
||||
}
|
||||
@@ -117,8 +88,15 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
Data = Code + HTTP::Get("https://backend.beammp.com/servers-info");
|
||||
break;
|
||||
case 'C':
|
||||
ListOfMods.clear();
|
||||
StartSync(Data);
|
||||
Data.clear();
|
||||
while (ListOfMods.empty() && !Terminate) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
if (ListOfMods == "-")
|
||||
Data = "L";
|
||||
else
|
||||
Data = "L" + ListOfMods;
|
||||
break;
|
||||
case 'O': // open default browser with URL
|
||||
if (IsAllowedLink(Data.substr(1))) {
|
||||
@@ -178,10 +156,8 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
TCPTerminate = true;
|
||||
ping = -1;
|
||||
}
|
||||
if (SubCode == 'G') {
|
||||
debug("Closing via 'G' packet");
|
||||
if (SubCode == 'G')
|
||||
exit(2);
|
||||
}
|
||||
Data.clear();
|
||||
break;
|
||||
case 'R': // will send mod name
|
||||
@@ -213,15 +189,6 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
Data = "N" + Login(Data.substr(Data.find(':') + 1));
|
||||
}
|
||||
break;
|
||||
case 'W':
|
||||
if (SubCode == 'Y') {
|
||||
confirmed = 1;
|
||||
} else if (SubCode == 'N') {
|
||||
confirmed = 0;
|
||||
}
|
||||
|
||||
Data.clear();
|
||||
break;
|
||||
default:
|
||||
Data.clear();
|
||||
break;
|
||||
@@ -234,7 +201,7 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
}
|
||||
}
|
||||
void GameHandler(SOCKET Client) {
|
||||
CoreSocket = Client;
|
||||
|
||||
int32_t Size, Temp, Rcv;
|
||||
char Header[10] = { 0 };
|
||||
do {
|
||||
@@ -302,7 +269,7 @@ void CoreMain() {
|
||||
|
||||
ZeroMemory(&hints, sizeof(hints));
|
||||
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
///
|
||||
|
||||
#include <string>
|
||||
#include "IPRegex.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <winsock2.h>
|
||||
@@ -19,8 +20,9 @@
|
||||
#include "Logger.h"
|
||||
|
||||
std::string GetAddr(const std::string& IP) {
|
||||
if (IP.find_first_not_of("0123456789.") == -1)
|
||||
if (!std::regex_match(IP, IP_REGEX)) {
|
||||
return IP;
|
||||
}
|
||||
hostent* host;
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
@@ -40,4 +42,4 @@ std::string GetAddr(const std::string& IP) {
|
||||
std::string Ret = inet_ntoa(*((struct in_addr*)host->h_addr));
|
||||
WSACleanup();
|
||||
return Ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
/// Created by Anonymous275 on 7/25/2020
|
||||
///
|
||||
#include "Network/network.hpp"
|
||||
#include <memory>
|
||||
#include <zlib.h>
|
||||
#if defined(_WIN32)
|
||||
#include <winsock2.h>
|
||||
@@ -125,17 +124,17 @@ void NetReset() {
|
||||
UlStatus = "Ulstart";
|
||||
MStatus = " ";
|
||||
if (UDPSock != (SOCKET)(-1)) {
|
||||
debug("Terminating UDP Socket: " + std::to_string(TCPSock));
|
||||
debug("Terminating UDP Socket : " + std::to_string(TCPSock));
|
||||
KillSocket(UDPSock);
|
||||
}
|
||||
UDPSock = -1;
|
||||
if (TCPSock != (SOCKET)(-1)) {
|
||||
debug("Terminating TCP Socket: " + std::to_string(TCPSock));
|
||||
debug("Terminating TCP Socket : " + std::to_string(TCPSock));
|
||||
KillSocket(TCPSock);
|
||||
}
|
||||
TCPSock = -1;
|
||||
if (GSocket != (SOCKET)(-1)) {
|
||||
debug("Terminating GTCP Socket: " + std::to_string(GSocket));
|
||||
debug("Terminating GTCP Socket : " + std::to_string(GSocket));
|
||||
KillSocket(GSocket);
|
||||
}
|
||||
GSocket = -1;
|
||||
@@ -157,7 +156,7 @@ SOCKET SetupListener() {
|
||||
#endif
|
||||
|
||||
ZeroMemory(&hints, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
@@ -235,8 +234,6 @@ void NetMain(const std::string& IP, int Port) {
|
||||
}
|
||||
void TCPGameServer(const std::string& IP, int Port) {
|
||||
GSocket = SetupListener();
|
||||
std::unique_ptr<std::thread> ClientThread {};
|
||||
std::unique_ptr<std::thread> NetMainThread {};
|
||||
while (!TCPTerminate && GSocket != -1) {
|
||||
debug("MAIN LOOP OF GAME SERVER");
|
||||
GConnected = false;
|
||||
@@ -248,7 +245,8 @@ void TCPGameServer(const std::string& IP, int Port) {
|
||||
break;
|
||||
}
|
||||
if (CServer) {
|
||||
ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port);
|
||||
std::thread Client(TCPClientMain, IP, Port);
|
||||
Client.detach();
|
||||
}
|
||||
CSocket = accept(GSocket, nullptr, nullptr);
|
||||
if (CSocket == -1) {
|
||||
@@ -258,7 +256,8 @@ void TCPGameServer(const std::string& IP, int Port) {
|
||||
debug("(Proxy) Game Connected!");
|
||||
GConnected = true;
|
||||
if (CServer) {
|
||||
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
|
||||
std::thread t1(NetMain, IP, Port);
|
||||
t1.detach();
|
||||
CServer = false;
|
||||
}
|
||||
int32_t Size, Temp, Rcv;
|
||||
@@ -301,16 +300,6 @@ void TCPGameServer(const std::string& IP, int Port) {
|
||||
TCPTerminate = true;
|
||||
GConnected = false;
|
||||
Terminate = true;
|
||||
if (ClientThread) {
|
||||
debug("Waiting for client thread");
|
||||
ClientThread->join();
|
||||
debug("Client thread done");
|
||||
}
|
||||
if (NetMainThread) {
|
||||
debug("Waiting for net main thread");
|
||||
NetMainThread->join();
|
||||
debug("Net main thread done");
|
||||
}
|
||||
if (CSocket != SOCKET_ERROR)
|
||||
KillSocket(CSocket);
|
||||
debug("END OF GAME SERVER");
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <Startup.h>
|
||||
#include <Network/network.hpp>
|
||||
#include <Utils.h>
|
||||
|
||||
void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try {
|
||||
const std::filesystem::path folder = ".https_debug";
|
||||
@@ -69,10 +66,6 @@ std::string HTTP::Get(const std::string& IP) {
|
||||
httplib::Client cli(IP.substr(0, pos).c_str());
|
||||
cli.set_connection_timeout(std::chrono::seconds(10));
|
||||
cli.set_follow_location(true);
|
||||
if (SkipSslVerify) {
|
||||
debug("Skipping SSL server validation via --skip-ssl-verify");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
}
|
||||
auto res = cli.Get(IP.substr(pos).c_str(), ProgressBar);
|
||||
std::string Ret;
|
||||
|
||||
@@ -102,10 +95,6 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
||||
|
||||
httplib::Client cli(IP.substr(0, pos).c_str());
|
||||
cli.set_connection_timeout(std::chrono::seconds(10));
|
||||
if (SkipSslVerify) {
|
||||
debug("Skipping SSL server validation via --skip-ssl-verify");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
}
|
||||
std::string Ret;
|
||||
|
||||
if (!Fields.empty()) {
|
||||
@@ -182,127 +171,3 @@ bool HTTP::Download(const std::string& IP, const std::string& Path) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_headers(httplib::Response& res) {
|
||||
res.set_header("Access-Control-Allow-Origin", "*");
|
||||
res.set_header("Access-Control-Request-Method", "POST, OPTIONS, GET");
|
||||
res.set_header("Access-Control-Request-Headers", "X-API-Version");
|
||||
}
|
||||
|
||||
|
||||
void HTTP::StartProxy() {
|
||||
std::thread proxy([&]() {
|
||||
httplib::Server HTTPProxy;
|
||||
httplib::Headers headers = {
|
||||
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
|
||||
{ "Accept", "*/*" }
|
||||
};
|
||||
httplib::Client backend("https://backend.beammp.com");
|
||||
httplib::Client forum("https://forum.beammp.com");
|
||||
|
||||
const std::string pattern = ".*";
|
||||
|
||||
auto handle_request = [&](const httplib::Request& req, httplib::Response& res) {
|
||||
set_headers(res);
|
||||
if (req.has_header("X-BMP-Authentication")) {
|
||||
headers.emplace("X-BMP-Authentication", PrivateKey);
|
||||
}
|
||||
if (req.has_header("X-API-Version")) {
|
||||
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
|
||||
}
|
||||
|
||||
const std::vector<std::string> path = Utils::Split(req.path, "/");
|
||||
|
||||
httplib::Result cli_res;
|
||||
const std::string method = req.method;
|
||||
std::string host = "";
|
||||
|
||||
if (!path.empty())
|
||||
host = path[0];
|
||||
|
||||
if (host == "backend") {
|
||||
std::string remaining_path = req.path.substr(std::strlen("/backend"));
|
||||
|
||||
if (method == "GET")
|
||||
cli_res = backend.Get(remaining_path, headers);
|
||||
else if (method == "POST")
|
||||
cli_res = backend.Post(remaining_path, headers);
|
||||
|
||||
} else if (host == "avatar") {
|
||||
bool error = false;
|
||||
std::string username;
|
||||
std::string avatar_size = "100";
|
||||
|
||||
if (path.size() > 1) {
|
||||
username = path[1];
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (path.size() > 2) {
|
||||
try {
|
||||
if (std::stoi(path[2]) > 0)
|
||||
avatar_size = path[2];
|
||||
|
||||
} catch (std::exception&) {}
|
||||
}
|
||||
|
||||
httplib::Result summary_res;
|
||||
|
||||
if (!error) {
|
||||
summary_res = forum.Get("/u/" + username + ".json", headers);
|
||||
|
||||
if (!summary_res || summary_res->status != 200) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
try {
|
||||
nlohmann::json d = nlohmann::json::parse(summary_res->body, nullptr, false); // can fail with parse_error
|
||||
|
||||
auto user = d.at("user"); // can fail with out_of_range
|
||||
auto avatar_link_json = user.at("avatar_template"); // can fail with out_of_range
|
||||
|
||||
auto avatar_link = avatar_link_json.get<std::string>();
|
||||
size_t start_pos = avatar_link.find("{size}");
|
||||
if (start_pos != std::string::npos)
|
||||
avatar_link.replace(start_pos, std::strlen("{size}"), avatar_size);
|
||||
|
||||
cli_res = forum.Get(avatar_link, headers);
|
||||
|
||||
} catch (std::exception&) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
cli_res = forum.Get("/user_avatar/forum.beammp.com/user/0/0.png", headers);
|
||||
}
|
||||
|
||||
} else {
|
||||
res.set_content("Host not found", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cli_res) {
|
||||
res.set_content(cli_res->body, cli_res->get_header_value("Content-Type"));
|
||||
} else {
|
||||
res.set_content(to_string(cli_res.error()), "text/plain");
|
||||
}
|
||||
};
|
||||
|
||||
HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) {
|
||||
handle_request(req, res);
|
||||
});
|
||||
|
||||
HTTPProxy.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) {
|
||||
handle_request(req, res);
|
||||
});
|
||||
|
||||
ProxyPort = HTTPProxy.bind_to_any_port("0.0.0.0");
|
||||
debug("HTTP Proxy listening on port " + std::to_string(ProxyPort));
|
||||
HTTPProxy.listen_after_bind();
|
||||
});
|
||||
proxy.detach();
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
///
|
||||
|
||||
#include "Network/network.hpp"
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <ws2tcpip.h>
|
||||
@@ -30,20 +28,35 @@
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <Utils.h>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
std::string ListOfMods;
|
||||
std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
|
||||
std::vector<std::string> Val;
|
||||
size_t pos;
|
||||
std::string token, s = String;
|
||||
while ((pos = s.find(delimiter)) != std::string::npos) {
|
||||
token = s.substr(0, pos);
|
||||
if (!token.empty())
|
||||
Val.push_back(token);
|
||||
s.erase(0, pos + delimiter.length());
|
||||
}
|
||||
if (!s.empty())
|
||||
Val.push_back(s);
|
||||
return Val;
|
||||
}
|
||||
|
||||
void CheckForDir() {
|
||||
if (!fs::exists(CachingDirectory)) {
|
||||
try {
|
||||
fs::create_directories(CachingDirectory);
|
||||
} catch (const std::exception& e) {
|
||||
error(std::string("Failed to create caching directory: ") + e.what() + ". This is a fatal error. Please make sure to configure a directory which you have permission to create, read and write from/to.");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
std::exit(1);
|
||||
}
|
||||
if (!fs::exists("Resources")) {
|
||||
// Could we just use fs::create_directory instead?
|
||||
#if defined(_WIN32)
|
||||
_wmkdir(L"Resources");
|
||||
#elif defined(__linux__)
|
||||
fs::create_directory(L"Resources");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void WaitForConfirm() {
|
||||
@@ -66,20 +79,16 @@ std::string Auth(SOCKET Sock) {
|
||||
|
||||
if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
|
||||
Abord();
|
||||
CoreSend("L");
|
||||
return "";
|
||||
}
|
||||
|
||||
TCPSend(PublicKey, Sock);
|
||||
if (Terminate) {
|
||||
CoreSend("L");
|
||||
if (Terminate)
|
||||
return "";
|
||||
}
|
||||
|
||||
Res = TCPRcv(Sock);
|
||||
if (Res.empty() || Res[0] != 'P') {
|
||||
Abord();
|
||||
CoreSend("L");
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -88,27 +97,23 @@ std::string Auth(SOCKET Sock) {
|
||||
ClientID = std::stoi(Res);
|
||||
} else {
|
||||
Abord();
|
||||
CoreSend("L");
|
||||
UUl("Authentication failed!");
|
||||
return "";
|
||||
}
|
||||
TCPSend("SR", Sock);
|
||||
if (Terminate) {
|
||||
CoreSend("L");
|
||||
if (Terminate)
|
||||
return "";
|
||||
}
|
||||
|
||||
Res = TCPRcv(Sock);
|
||||
|
||||
if (Res[0] == 'E' || Res[0] == 'K') {
|
||||
Abord();
|
||||
CoreSend("L");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Res.empty() || Res == "-") {
|
||||
info("Didn't Receive any mods...");
|
||||
CoreSend("L");
|
||||
ListOfMods = "-";
|
||||
TCPSend("Done", Sock);
|
||||
info("Done!");
|
||||
return "";
|
||||
@@ -132,44 +137,29 @@ void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) {
|
||||
} while (!Terminate && Rcv < Size);
|
||||
}
|
||||
|
||||
// MICROSOFT, I DONT CARE, WRITE BETTER CODE
|
||||
#undef min
|
||||
|
||||
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
||||
char* TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
||||
if (Sock == -1) {
|
||||
Terminate = true;
|
||||
UUl("Invalid Socket");
|
||||
return {};
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<char> File(Size);
|
||||
char* File = new char[Size];
|
||||
uint64_t Rcv = 0;
|
||||
|
||||
int i = 0;
|
||||
do {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// receive at most some MB at a time
|
||||
int Len = std::min(int(Size - Rcv), 2 * 1024 * 1024);
|
||||
int Len = int(Size - Rcv);
|
||||
if (Len > 1000000)
|
||||
Len = 1000000;
|
||||
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
||||
if (Temp < 1) {
|
||||
info(std::to_string(Temp));
|
||||
UUl("Socket Closed Code 1");
|
||||
KillSocket(Sock);
|
||||
Terminate = true;
|
||||
return {};
|
||||
delete[] File;
|
||||
return nullptr;
|
||||
}
|
||||
Rcv += Temp;
|
||||
GRcv += Temp;
|
||||
|
||||
// every 8th iteration calculate download speed for that iteration
|
||||
if (i % 8 == 0) {
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto difference = end - start;
|
||||
float bits_per_s = float(Temp * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
|
||||
float megabits_per_s = bits_per_s / 1000;
|
||||
debug("Download speed: " + std::to_string(uint32_t(megabits_per_s)) + "Mbit/s");
|
||||
}
|
||||
++i;
|
||||
} while (Rcv < Size && !Terminate);
|
||||
return File;
|
||||
}
|
||||
@@ -179,16 +169,16 @@ void MultiKill(SOCKET Sock, SOCKET Sock1) {
|
||||
Terminate = true;
|
||||
}
|
||||
SOCKET InitDSock() {
|
||||
SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
SOCKET DSock = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
|
||||
SOCKADDR_IN ServerAddr;
|
||||
if (DSock < 1) {
|
||||
KillSocket(DSock);
|
||||
Terminate = true;
|
||||
return 0;
|
||||
}
|
||||
ServerAddr.sin_family = AF_INET;
|
||||
ServerAddr.sin_family = AF_UNSPEC;
|
||||
ServerAddr.sin_port = htons(LastPort);
|
||||
inet_pton(AF_INET, LastIP.c_str(), &ServerAddr.sin_addr);
|
||||
inet_pton(AF_UNSPEC, LastIP.c_str(), &ServerAddr.sin_addr);
|
||||
if (connect(DSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
|
||||
KillSocket(DSock);
|
||||
Terminate = true;
|
||||
@@ -203,47 +193,44 @@ SOCKET InitDSock() {
|
||||
return DSock;
|
||||
}
|
||||
|
||||
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
|
||||
uint64_t GRcv = 0;
|
||||
std::string MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
|
||||
|
||||
uint64_t MSize = Size / 2;
|
||||
uint64_t DSize = Size - MSize;
|
||||
uint64_t GRcv = 0, MSize = Size / 2, DSize = Size - MSize;
|
||||
|
||||
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
|
||||
std::thread Au(AsyncUpdate, std::ref(GRcv), Size, Name);
|
||||
|
||||
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, MSize);
|
||||
std::packaged_task<char*()> task([&] { return TCPRcvRaw(MSock, GRcv, MSize); });
|
||||
std::future<char*> f1 = task.get_future();
|
||||
std::thread Dt(std::move(task));
|
||||
Dt.detach();
|
||||
|
||||
if (MData.empty()) {
|
||||
char* DData = TCPRcvRaw(DSock, GRcv, DSize);
|
||||
|
||||
if (!DData) {
|
||||
MultiKill(MSock, DSock);
|
||||
Terminate = true;
|
||||
Au.join();
|
||||
return {};
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::vector<char> DData = TCPRcvRaw(DSock, GRcv, DSize);
|
||||
f1.wait();
|
||||
char* MData = f1.get();
|
||||
|
||||
if (DData.empty()) {
|
||||
if (!MData) {
|
||||
MultiKill(MSock, DSock);
|
||||
Terminate = true;
|
||||
Au.join();
|
||||
return {};
|
||||
return "";
|
||||
}
|
||||
|
||||
// ensure that GRcv is good before joining the async update thread
|
||||
GRcv = MData.size() + DData.size();
|
||||
if (GRcv != Size) {
|
||||
error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
|
||||
Terminate = true;
|
||||
if (Au.joinable())
|
||||
Au.join();
|
||||
return {};
|
||||
}
|
||||
|
||||
Au.join();
|
||||
/// omg yes very ugly my god but i was in a rush will revisit
|
||||
std::string Ret(Size, 0);
|
||||
memcpy(&Ret[0], MData, MSize);
|
||||
delete[] MData;
|
||||
|
||||
std::vector<char> Result{};
|
||||
Result.insert(Result.begin(), MData.begin(), MData.end());
|
||||
Result.insert(Result.end(), DData.begin(), DData.end());
|
||||
return Result;
|
||||
memcpy(&Ret[MSize], DData, DSize);
|
||||
delete[] DData;
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void InvalidResource(const std::string& File) {
|
||||
@@ -257,29 +244,26 @@ void SyncResources(SOCKET Sock) {
|
||||
if (Ret.empty())
|
||||
return;
|
||||
|
||||
if (!SecurityWarning())
|
||||
return;
|
||||
|
||||
info("Checking Resources...");
|
||||
CheckForDir();
|
||||
|
||||
std::vector<std::string> list = Utils::Split(Ret, ";");
|
||||
std::vector<std::string> list = Split(Ret, ";");
|
||||
std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2));
|
||||
std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end());
|
||||
list.clear();
|
||||
Ret.clear();
|
||||
|
||||
int Amount = 0, Pos = 0;
|
||||
std::string PathToSaveTo, t;
|
||||
std::string a, t;
|
||||
for (const std::string& name : FNames) {
|
||||
if (!name.empty()) {
|
||||
t += name.substr(name.find_last_of('/') + 1) + ";";
|
||||
}
|
||||
}
|
||||
if (t.empty())
|
||||
CoreSend("L");
|
||||
ListOfMods = "-";
|
||||
else
|
||||
CoreSend("L" + t);
|
||||
ListOfMods = t;
|
||||
t.clear();
|
||||
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
|
||||
auto pos = FN->find_last_of('/');
|
||||
@@ -298,23 +282,21 @@ void SyncResources(SOCKET Sock) {
|
||||
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
|
||||
auto pos = FN->find_last_of('/');
|
||||
if (pos != std::string::npos) {
|
||||
PathToSaveTo = CachingDirectory + FN->substr(pos);
|
||||
} else {
|
||||
a = "Resources" + FN->substr(pos);
|
||||
} else
|
||||
continue;
|
||||
}
|
||||
Pos++;
|
||||
auto FileSize = std::stoull(*FS);
|
||||
if (fs::exists(PathToSaveTo)) {
|
||||
if (fs::exists(a)) {
|
||||
if (FS->find_first_not_of("0123456789") != std::string::npos)
|
||||
continue;
|
||||
if (fs::file_size(PathToSaveTo) == FileSize) {
|
||||
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
|
||||
if (fs::file_size(a) == std::stoull(*FS)) {
|
||||
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/')));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
try {
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
|
||||
auto modname = a.substr(a.find_last_of('/'));
|
||||
#if defined(__linux__)
|
||||
// Linux version of the game doesnt support uppercase letters in mod names
|
||||
for (char& c : modname) {
|
||||
@@ -323,7 +305,7 @@ void SyncResources(SOCKET Sock) {
|
||||
#endif
|
||||
auto name = GetGamePath() + "mods/multiplayer" + modname;
|
||||
auto tmp_name = name + ".tmp";
|
||||
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(a, tmp_name, fs::copy_options::overwrite_existing);
|
||||
fs::rename(tmp_name, name);
|
||||
} catch (std::exception& e) {
|
||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||
@@ -333,12 +315,11 @@ void SyncResources(SOCKET Sock) {
|
||||
WaitForConfirm();
|
||||
continue;
|
||||
} else
|
||||
remove(PathToSaveTo.c_str());
|
||||
remove(a.c_str());
|
||||
}
|
||||
CheckForDir();
|
||||
std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
|
||||
std::string FName = a.substr(a.find_last_of('/'));
|
||||
do {
|
||||
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
|
||||
TCPSend("f" + *FN, Sock);
|
||||
|
||||
std::string Data = TCPRcv(Sock);
|
||||
@@ -350,23 +331,19 @@ void SyncResources(SOCKET Sock) {
|
||||
|
||||
std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName;
|
||||
|
||||
std::vector<char> DownloadedFile = MultiDownload(Sock, DSock, FileSize, Name);
|
||||
Data = MultiDownload(Sock, DSock, std::stoull(*FS), Name);
|
||||
|
||||
if (Terminate)
|
||||
break;
|
||||
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName);
|
||||
std::ofstream LFS;
|
||||
LFS.open(a.c_str(), std::ios_base::app | std::ios::binary);
|
||||
if (LFS.is_open()) {
|
||||
LFS.write(&Data[0], Data.size());
|
||||
LFS.close();
|
||||
}
|
||||
|
||||
// 1. write downloaded file to disk
|
||||
{
|
||||
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
|
||||
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
|
||||
}
|
||||
// 2. verify size
|
||||
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
|
||||
error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
|
||||
Terminate = true;
|
||||
}
|
||||
} while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate);
|
||||
} while (fs::file_size(a) != std::stoull(*FS) && !Terminate);
|
||||
if (!Terminate) {
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
@@ -379,7 +356,7 @@ void SyncResources(SOCKET Sock) {
|
||||
}
|
||||
#endif
|
||||
|
||||
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(a, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
WaitForConfirm();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
///
|
||||
#include "Network/network.hpp"
|
||||
#include "Zlib/Compressor.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <ws2tcpip.h>
|
||||
@@ -51,13 +50,9 @@ void SendLarge(std::string Data) {
|
||||
void UDPParser(std::string_view Packet) {
|
||||
if (Packet.substr(0, 4) == "ABG:") {
|
||||
auto substr = Packet.substr(4);
|
||||
try {
|
||||
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
|
||||
std::string DeCompPacket = std::string(res.data(), res.size());
|
||||
ServerParser(DeCompPacket);
|
||||
} catch (const std::runtime_error& err) {
|
||||
error("Error in decompression of UDP, ignoring");
|
||||
}
|
||||
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
|
||||
std::string DeCompPacket = std::string(res.data(), res.size());
|
||||
ServerParser(DeCompPacket);
|
||||
} else {
|
||||
ServerParser(Packet);
|
||||
}
|
||||
@@ -90,18 +85,15 @@ void UDPClientMain(const std::string& IP, int Port) {
|
||||
|
||||
delete ToServer;
|
||||
ToServer = new sockaddr_in;
|
||||
ToServer->sin_family = AF_INET;
|
||||
ToServer->sin_family = AF_UNSPEC;
|
||||
ToServer->sin_port = htons(Port);
|
||||
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
|
||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
inet_pton(AF_UNSPEC, IP.c_str(), &ToServer->sin_addr);
|
||||
UDPSock = socket(AF_UNSPEC, SOCK_DGRAM, 0);
|
||||
GameSend("P" + std::to_string(ClientID));
|
||||
TCPSend("H", TCPSock);
|
||||
UDPSend("p");
|
||||
debug("Starting UDP receive loop");
|
||||
while (!Terminate) {
|
||||
while (!Terminate)
|
||||
UDPRcv();
|
||||
}
|
||||
debug("UDP receive loop done");
|
||||
KillSocket(UDPSock);
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
@@ -82,39 +82,39 @@ std::string TCPRcv(SOCKET Sock) {
|
||||
UUl("Invalid Socket");
|
||||
return "";
|
||||
}
|
||||
int32_t Header, Temp;
|
||||
int32_t Header, BytesRcv = 0, Temp;
|
||||
std::vector<char> Data(sizeof(Header));
|
||||
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
|
||||
if (!CheckBytes(Temp)) {
|
||||
UUl("Socket Closed Code 3");
|
||||
return "";
|
||||
}
|
||||
memcpy(&Header, Data.data(), sizeof(Header));
|
||||
do {
|
||||
Temp = recv(Sock, &Data[BytesRcv], 4 - BytesRcv, 0);
|
||||
if (!CheckBytes(Temp)) {
|
||||
UUl("Socket Closed Code 3");
|
||||
return "";
|
||||
}
|
||||
BytesRcv += Temp;
|
||||
} while (BytesRcv < 4);
|
||||
memcpy(&Header, &Data[0], sizeof(Header));
|
||||
|
||||
if (!CheckBytes(Temp)) {
|
||||
if (!CheckBytes(BytesRcv)) {
|
||||
UUl("Socket Closed Code 4");
|
||||
return "";
|
||||
}
|
||||
|
||||
Data.resize(Header, 0);
|
||||
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL);
|
||||
if (!CheckBytes(Temp)) {
|
||||
UUl("Socket Closed Code 5");
|
||||
return "";
|
||||
}
|
||||
Data.resize(Header);
|
||||
BytesRcv = 0;
|
||||
do {
|
||||
Temp = recv(Sock, &Data[BytesRcv], Header - BytesRcv, 0);
|
||||
if (!CheckBytes(Temp)) {
|
||||
UUl("Socket Closed Code 5");
|
||||
return "";
|
||||
}
|
||||
BytesRcv += Temp;
|
||||
} while (BytesRcv < Header);
|
||||
|
||||
std::string Ret(Data.data(), Header);
|
||||
|
||||
if (Ret.substr(0, 4) == "ABG:") {
|
||||
auto substr = Ret.substr(4);
|
||||
try {
|
||||
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
|
||||
Ret = std::string(res.data(), res.size());
|
||||
} catch (const std::runtime_error& err) {
|
||||
// this happens e.g. when we're out of memory, or when we get incomplete data
|
||||
error("Decompression failed");
|
||||
return "";
|
||||
}
|
||||
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
|
||||
Ret = std::string(res.data(), res.size());
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
@@ -134,7 +134,7 @@ void TCPClientMain(const std::string& IP, int Port) {
|
||||
WSADATA wsaData;
|
||||
WSAStartup(514, &wsaData); // 2.2
|
||||
#endif
|
||||
TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
TCPSock = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (TCPSock == -1) {
|
||||
printf("Client: socket failed! Error code: %d\n", WSAGetLastError());
|
||||
@@ -142,9 +142,9 @@ void TCPClientMain(const std::string& IP, int Port) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerAddr.sin_family = AF_INET;
|
||||
ServerAddr.sin_family = AF_UNSPEC;
|
||||
ServerAddr.sin_port = htons(Port);
|
||||
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
|
||||
inet_pton(AF_UNSPEC, IP.c_str(), &ServerAddr.sin_addr);
|
||||
RetCode = connect(TCPSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
|
||||
if (RetCode != 0) {
|
||||
UlStatus = "UlConnection Failed!";
|
||||
@@ -152,7 +152,6 @@ void TCPClientMain(const std::string& IP, int Port) {
|
||||
KillSocket(TCPSock);
|
||||
WSACleanup();
|
||||
Terminate = true;
|
||||
CoreSend("L");
|
||||
return;
|
||||
}
|
||||
info("Connected!");
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#endif
|
||||
#include "Logger.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
@@ -33,8 +34,24 @@ void lowExit(int code) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
exit(2);
|
||||
}
|
||||
|
||||
/*void Exit(int code){
|
||||
TraceBack = 0;
|
||||
std::string msg =
|
||||
"Sorry. We do not support cracked copies report this if you believe this is a mistake code ";
|
||||
error(msg+std::to_string(code));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
exit(3);
|
||||
}
|
||||
void SteamExit(int code){
|
||||
TraceBack = 0;
|
||||
std::string msg =
|
||||
"Illegal steam modifications detected report this if you believe this is a mistake code ";
|
||||
error(msg+std::to_string(code));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
exit(4);
|
||||
}*/
|
||||
std::string GetGameDir() {
|
||||
// if(TraceBack != 4)Exit(0);
|
||||
#if defined(_WIN32)
|
||||
return GameDir.substr(0, GameDir.find_last_of('\\'));
|
||||
#elif defined(__linux__)
|
||||
@@ -158,7 +175,151 @@ void FileList(std::vector<std::string>& a, const std::string& Path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Find(const std::string& FName, const std::string& Path) {
|
||||
std::vector<std::string> FS;
|
||||
FileList(FS, Path + "\\userdata");
|
||||
for (std::string& a : FS) {
|
||||
if (a.find(FName) != std::string::npos) {
|
||||
FS.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
FS.clear();
|
||||
return false;
|
||||
}
|
||||
bool FindHack(const std::string& Path) {
|
||||
bool s = true;
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
std::string Name = entry.path().filename().string();
|
||||
for (char& c : Name)
|
||||
c = char(tolower(c));
|
||||
if (Name == "steam.exe")
|
||||
s = false;
|
||||
if (Name.find("greenluma") != -1) {
|
||||
error("Found malicious file/folder \"" + Name + "\"");
|
||||
return true;
|
||||
}
|
||||
Name.clear();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
std::vector<std::string> GetID(const std::string& log) {
|
||||
std::string vec, t, r;
|
||||
std::vector<std::string> Ret;
|
||||
std::ifstream f(log.c_str(), std::ios::binary);
|
||||
f.seekg(0, std::ios_base::end);
|
||||
std::streampos fileSize = f.tellg();
|
||||
vec.resize(size_t(fileSize) + 1);
|
||||
f.seekg(0, std::ios_base::beg);
|
||||
f.read(&vec[0], fileSize);
|
||||
f.close();
|
||||
std::stringstream ss(vec);
|
||||
bool S = false;
|
||||
while (std::getline(ss, t, '{')) {
|
||||
if (!S)
|
||||
S = true;
|
||||
else {
|
||||
for (char& c : t) {
|
||||
if (isdigit(c))
|
||||
r += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ret.emplace_back(r);
|
||||
r.clear();
|
||||
S = false;
|
||||
bool L = true;
|
||||
while (std::getline(ss, t, '}')) {
|
||||
if (L) {
|
||||
L = false;
|
||||
continue;
|
||||
}
|
||||
for (char& c : t) {
|
||||
if (c == '"') {
|
||||
if (!S)
|
||||
S = true;
|
||||
else {
|
||||
if (r.length() > 10) {
|
||||
Ret.emplace_back(r);
|
||||
}
|
||||
r.clear();
|
||||
S = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (isdigit(c))
|
||||
r += c;
|
||||
}
|
||||
}
|
||||
vec.clear();
|
||||
return Ret;
|
||||
}
|
||||
std::string GetManifest(const std::string& Man) {
|
||||
std::string vec;
|
||||
std::ifstream f(Man.c_str(), std::ios::binary);
|
||||
f.seekg(0, std::ios_base::end);
|
||||
std::streampos fileSize = f.tellg();
|
||||
vec.resize(size_t(fileSize) + 1);
|
||||
f.seekg(0, std::ios_base::beg);
|
||||
f.read(&vec[0], fileSize);
|
||||
f.close();
|
||||
std::string ToFind = "\"LastOwner\"\t\t\"";
|
||||
int pos = int(vec.find(ToFind));
|
||||
if (pos != -1) {
|
||||
pos += int(ToFind.length());
|
||||
vec = vec.substr(pos);
|
||||
return vec.substr(0, vec.find('\"'));
|
||||
} else
|
||||
return "";
|
||||
}
|
||||
bool IDCheck(std::string Man, std::string steam) {
|
||||
bool a = false, b = true;
|
||||
int pos = int(Man.rfind("steamapps"));
|
||||
// if(pos == -1)Exit(5);
|
||||
Man = Man.substr(0, pos + 9) + "\\appmanifest_284160.acf";
|
||||
steam += "\\config\\loginusers.vdf";
|
||||
if (fs::exists(Man) && fs::exists(steam)) {
|
||||
for (const std::string& ID : GetID(steam)) {
|
||||
if (ID == GetManifest(Man))
|
||||
b = false;
|
||||
}
|
||||
// if(b)Exit(6);
|
||||
} else
|
||||
a = true;
|
||||
return a;
|
||||
}
|
||||
void LegitimacyCheck() {
|
||||
|
||||
// std::string K1 = R"(Software\Valve\Steam)";
|
||||
// std::string K2 = R"(Software\Valve\Steam\Apps\284160)";
|
||||
|
||||
/*LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K1.c_str(), &hKey);
|
||||
|
||||
if(dwRegOPenKey == ERROR_SUCCESS) {
|
||||
Result = QueryKey(hKey, 1);
|
||||
if(Result.empty())Exit(1);
|
||||
|
||||
if(fs::exists(Result)){
|
||||
if(!Find("284160.json",Result))Exit(2);
|
||||
if(FindHack(Result))SteamExit(1);
|
||||
}else Exit(3);
|
||||
|
||||
T = Result;
|
||||
Result.clear();
|
||||
TraceBack++;
|
||||
}else Exit(4);
|
||||
|
||||
K1.clear();
|
||||
RegCloseKey(hKey);
|
||||
dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K2.c_str(), &hKey);
|
||||
if(dwRegOPenKey == ERROR_SUCCESS) {
|
||||
Result = QueryKey(hKey, 2);
|
||||
if(Result.empty())lowExit(1);
|
||||
TraceBack++;
|
||||
}else lowExit(2);
|
||||
K2.clear();
|
||||
RegCloseKey(hKey);*/
|
||||
#if defined(_WIN32)
|
||||
std::string Result;
|
||||
std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
|
||||
@@ -166,18 +327,17 @@ void LegitimacyCheck() {
|
||||
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
|
||||
if (dwRegOPenKey == ERROR_SUCCESS) {
|
||||
Result = QueryKey(hKey, 3);
|
||||
if (Result.empty()) {
|
||||
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
if (Result.empty())
|
||||
lowExit(3);
|
||||
}
|
||||
// if(IDCheck(Result,T))lowExit(5);
|
||||
GameDir = Result;
|
||||
} else {
|
||||
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
// TraceBack++;
|
||||
} else
|
||||
lowExit(4);
|
||||
}
|
||||
K3.clear();
|
||||
Result.clear();
|
||||
RegCloseKey(hKey);
|
||||
// if(TraceBack < 3)exit(-1);
|
||||
#elif defined(__linux__)
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
std::string homeDir = pw->pw_dir;
|
||||
|
||||
@@ -81,10 +81,10 @@ std::string GetEN() {
|
||||
}
|
||||
|
||||
std::string GetVer() {
|
||||
return "2.1";
|
||||
return "2.0";
|
||||
}
|
||||
std::string GetPatch() {
|
||||
return ".2";
|
||||
return ".99";
|
||||
}
|
||||
|
||||
std::string GetEP(char* P) {
|
||||
@@ -101,7 +101,6 @@ void ReLaunch(int argc, char* args[]) {
|
||||
Arg += " ";
|
||||
Arg += args[c - 1];
|
||||
}
|
||||
info("Relaunch!");
|
||||
system("cls");
|
||||
ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
|
||||
ShowWindow(GetConsoleWindow(), 0);
|
||||
@@ -126,7 +125,6 @@ void ReLaunch(int argc, char* args[]) {
|
||||
Arg += " ";
|
||||
Arg += args[c - 1];
|
||||
}
|
||||
info("Relaunch!");
|
||||
system("clear");
|
||||
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
@@ -169,6 +167,10 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
|
||||
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
|
||||
|
||||
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
|
||||
#if defined(_WIN32)
|
||||
#elif defined(__linux__)
|
||||
system("clear");
|
||||
#endif
|
||||
|
||||
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion))) && !Dev) {
|
||||
info("Launcher update found!");
|
||||
@@ -235,6 +237,7 @@ void LinuxPatch() {
|
||||
|
||||
#if defined(_WIN32)
|
||||
void InitLauncher(int argc, char* argv[]) {
|
||||
system("cls");
|
||||
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
|
||||
InitLog();
|
||||
CheckName(argc, argv);
|
||||
@@ -246,8 +249,8 @@ void InitLauncher(int argc, char* argv[]) {
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
void InitLauncher(int argc, char* argv[]) {
|
||||
system("clear");
|
||||
InitLog();
|
||||
info("BeamMP Launcher v" + GetVer() + GetPatch());
|
||||
CheckName(argc, argv);
|
||||
CheckLocalKey();
|
||||
ConfigInit();
|
||||
@@ -314,7 +317,6 @@ void PreGame(const std::string& GamePath) {
|
||||
info("Game Version : " + GameVer);
|
||||
|
||||
CheckMP(GetGamePath() + "mods/multiplayer");
|
||||
info("Game user path: " + GetGamePath());
|
||||
|
||||
if (!Dev) {
|
||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
|
||||
@@ -355,3 +357,59 @@ void PreGame(const std::string& GamePath) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_headers(httplib::Response& res) {
|
||||
res.set_header("Access-Control-Allow-Origin", "*");
|
||||
res.set_header("Access-Control-Request-Method", "POST, OPTIONS, GET");
|
||||
res.set_header("Access-Control-Request-Headers", "X-API-Version");
|
||||
}
|
||||
|
||||
void StartProxy() {
|
||||
std::thread proxy([&]() {
|
||||
httplib::Server HTTPProxy;
|
||||
httplib::Headers headers = {
|
||||
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
|
||||
{ "Accept", "*/*" }
|
||||
};
|
||||
std::string pattern = "/:any1";
|
||||
for (int i = 2; i <= 4; i++) {
|
||||
HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) {
|
||||
httplib::Client cli("https://backend.beammp.com");
|
||||
set_headers(res);
|
||||
if (req.has_header("X-BMP-Authentication")) {
|
||||
headers.emplace("X-BMP-Authentication", PrivateKey);
|
||||
}
|
||||
if (req.has_header("X-API-Version")) {
|
||||
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
|
||||
}
|
||||
if (auto cli_res = cli.Get(req.path, headers); cli_res) {
|
||||
res.set_content(cli_res->body, cli_res->get_header_value("Content-Type"));
|
||||
} else {
|
||||
res.set_content(to_string(cli_res.error()), "text/plain");
|
||||
}
|
||||
});
|
||||
|
||||
HTTPProxy.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) {
|
||||
httplib::Client cli("https://backend.beammp.com");
|
||||
set_headers(res);
|
||||
if (req.has_header("X-BMP-Authentication")) {
|
||||
headers.emplace("X-BMP-Authentication", PrivateKey);
|
||||
}
|
||||
if (req.has_header("X-API-Version")) {
|
||||
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
|
||||
}
|
||||
if (auto cli_res = cli.Post(req.path, headers, req.body,
|
||||
req.get_header_value("Content-Type"));
|
||||
cli_res) {
|
||||
res.set_content(cli_res->body, cli_res->get_header_value("Content-Type"));
|
||||
} else {
|
||||
res.set_content(to_string(cli_res.error()), "text/plain");
|
||||
}
|
||||
});
|
||||
pattern += "/:any" + std::to_string(i);
|
||||
}
|
||||
ProxyPort = HTTPProxy.bind_to_any_port("0.0.0.0");
|
||||
HTTPProxy.listen_after_bind();
|
||||
});
|
||||
proxy.detach();
|
||||
}
|
||||
|
||||
35
src/main.cpp
35
src/main.cpp
@@ -20,51 +20,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) try {
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef DEBUG
|
||||
std::thread th(flush);
|
||||
th.detach();
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
system("cls");
|
||||
#elif defined(__linux__)
|
||||
system("clear");
|
||||
#endif
|
||||
|
||||
|
||||
GetEP(argv[0]);
|
||||
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if (std::string_view(argv[i]) == "--skip-ssl-verify") {
|
||||
info("SSL verification skip enabled");
|
||||
HTTP::SkipSslVerify = true;
|
||||
}
|
||||
}
|
||||
|
||||
InitLauncher(argc, argv);
|
||||
|
||||
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
|
||||
|
||||
try {
|
||||
LegitimacyCheck();
|
||||
} catch (std::exception& e) {
|
||||
error("Failure in LegitimacyCheck: " + std::string(e.what()));
|
||||
throw;
|
||||
fatal("Main 1 : " + std::string(e.what()));
|
||||
}
|
||||
|
||||
try {
|
||||
HTTP::StartProxy();
|
||||
} catch (const std::exception& e) {
|
||||
error(std::string("Failed to start HTTP proxy: Some in-game functions may not work. Error: ") + e.what());
|
||||
}
|
||||
StartProxy();
|
||||
PreGame(GetGameDir());
|
||||
InitGame(GetGameDir());
|
||||
CoreNetwork();
|
||||
} catch (const std::exception& e) {
|
||||
error(std::string("Exception in main(): ") + e.what());
|
||||
info("Closing in 5 seconds");
|
||||
info("If this keeps happening, contact us on either: Forum: https://forum.beammp.com, Discord: https://discord.gg/beammp");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
|
||||
/// TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user