13 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
28 changed files with 921 additions and 1027 deletions

View File

@@ -36,7 +36,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: BeamMP-Launcher name: BeamMP-Launcher
path: ${{github.workspace}}/build-linux/BeamMP-Launcher path: ${{github.workspace}}/build-linux/BeamMP-Launcher

View File

@@ -37,7 +37,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: BeamMP-Launcher.exe name: BeamMP-Launcher.exe
path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe

104
.github/workflows/release-build.yml vendored Normal file
View 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

View File

@@ -12,12 +12,9 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
file(GLOB source_files "src/*.cpp" "src/*/*.cpp" "src/*/*.hpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "include/*/*/*.hpp") file(GLOB source_files "src/*.cpp" "src/*/*.cpp" "src/*/*.hpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "include/*/*/*.hpp")
find_package(httplib CONFIG REQUIRED) find_package(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED)
find_package(CURL REQUIRED)
add_executable(${PROJECT_NAME} ${source_files}) add_executable(${PROJECT_NAME} ${source_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher") set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
@@ -26,15 +23,15 @@ if (WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json CURL::libcurl) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json)
elseif (LINUX) elseif (LINUX)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto)
else(WIN32) #MINGW else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600") add_definitions("-D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z CURL::libcurl) target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z)
endif(WIN32) endif(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE "include") target_include_directories(${PROJECT_NAME} PRIVATE "include")

View File

@@ -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. 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` 1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel` 2. `cmake --build bin --parallel`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
Copyright (c) 2019-present Anonymous275. Copyright (c) 2019-present Anonymous275.
BeamMP Launcher code is not in the public domain and is not free software. 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, One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries,

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

@@ -14,8 +14,7 @@ public:
static std::string Post(const std::string& IP, const std::string& Fields); static std::string Post(const std::string& IP, const std::string& Fields);
static std::string Get(const std::string& IP); static std::string Get(const std::string& IP);
static bool ProgressBar(size_t c, size_t t); static bool ProgressBar(size_t c, size_t t);
static void StartProxy();
public: public:
static bool isDownload; static bool isDownload;
static inline bool SkipSslVerify = false;
}; };

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
@@ -30,27 +33,25 @@ extern int DEFAULT_PORT;
extern uint64_t UDPSock; extern uint64_t UDPSock;
extern uint64_t TCPSock; extern uint64_t TCPSock;
extern std::string Branch; extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate; extern bool TCPTerminate;
extern std::string LastIP; extern std::string LastIP;
extern std::string MStatus; extern std::string MStatus;
extern std::string UlStatus; extern std::string UlStatus;
extern std::string PublicKey; extern std::string PublicKey;
extern std::string PrivateKey; extern std::string PrivateKey;
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_view 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(std::string_view 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);
bool SecurityWarning();
void CoreSend(std::string data);

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

@@ -14,8 +14,8 @@ void InitLauncher(int argc, char* argv[]);
std::string GetEP(char* P = nullptr); std::string GetEP(char* P = nullptr);
std::string GetGamePath(); std::string GetGamePath();
std::string GetVer(); std::string GetVer();
std::string GetPatch();
std::string GetEN(); std::string GetEN();
void StartProxy();
void ConfigInit(); void ConfigInit();
extern bool Dev; extern bool Dev;

View File

@@ -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;
};
};

View File

@@ -19,11 +19,12 @@ 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<const 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");

View File

@@ -11,8 +11,6 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string Branch; std::string Branch;
std::string CachingDirectory = "./Resources";
void ParseConfig(const nlohmann::json& d) { void ParseConfig(const nlohmann::json& d) {
if (d["Port"].is_number()) { if (d["Port"].is_number()) {
DEFAULT_PORT = d["Port"].get<int>(); DEFAULT_PORT = d["Port"].get<int>();
@@ -22,15 +20,12 @@ void ParseConfig(const nlohmann::json& d) {
// EA 2 // EA 2
// Dev 3 // Dev 3
// Custom 3 // Custom 3
if (d["Build"].is_string()) { if (d["Build"].is_string()) {
Branch = d["Build"].get<std::string>(); Branch = d["Build"].get<std::string>();
for (char& c : Branch) for (char& c : Branch)
c = char(tolower(c)); 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() { void ConfigInit() {
@@ -54,8 +49,7 @@ void ConfigInit() {
cfg << cfg <<
R"({ R"({
"Port": 4444, "Port": 4444,
"Build": "Default", "Build": "Default"
"CachingDirectory": "./Resources"
})"; })";
cfg.close(); cfg.close();
} else { } else {

View File

@@ -8,9 +8,10 @@
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <windows.h>
#include <shlobj.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>
@@ -41,20 +42,14 @@ std::string GetGamePath() {
Path = QueryKey(hKey, 4); Path = QueryKey(hKey, 4);
if (Path.empty()) { if (Path.empty()) {
Path = ""; sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)";
char appDataPath[MAX_PATH]; openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath); if (openRes != ERROR_SUCCESS) {
if (SUCCEEDED(result)) { fatal("Cannot get Local Appdata directory!");
Path = appDataPath;
} }
Path = QueryKey(hKey, 5);
if (Path.empty()) {
fatal("Cannot get Local Appdata directory");
}
Path += "\\BeamNG.drive\\"; Path += "\\BeamNG.drive\\";
} }
std::string Ver = CheckVer(GetGameDir()); std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "\\"; Path += Ver + "\\";
@@ -97,15 +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;
posix_spawn_file_actions_t spawn_actions;
posix_spawn_file_actions_init(&spawn_actions); posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO); auto status = posix_spawn_file_actions_init(&file_actions);
posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO); // disable stdout
int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, argv, environ); 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");
@@ -115,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)
return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n"; std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
if (Dev) { std::cout << Print << std::flush;
std::cout << Print;
}
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));
std::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>
@@ -39,31 +40,10 @@ bool Terminate = false;
bool LoginAuth = false; bool LoginAuth = false;
std::string Username = ""; std::string Username = "";
std::string UserRole = ""; std::string UserRole = "";
int UserID = -1;
std::string UlStatus; std::string UlStatus;
std::string MStatus; std::string MStatus;
bool ModLoaded; bool ModLoaded;
int ping = -1; 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) { void StartSync(const std::string& Data) {
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1)); 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)"; UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
else else
UlStatus = "UlConnection Failed! (WSA failed to start)"; UlStatus = "UlConnection Failed! (WSA failed to start)";
ListOfMods = "-";
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
CheckLocalKey(); CheckLocalKey();
@@ -87,45 +67,63 @@ void StartSync(const std::string& Data) {
info("Connecting to server"); 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) { bool IsAllowedLink(const std::string& Link) {
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg|patreon\.com\/BeamMP))"); 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':
StartSync(Data); ListOfMods.clear();
Data.clear(); StartSync(std::string(InData.data(), InData.size()));
while (ListOfMods.empty() && !Terminate) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (ListOfMods == "-")
OutData = "L";
else
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) {
@@ -137,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;
@@ -165,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') {
@@ -178,21 +176,21 @@ void Parse(std::string Data, SOCKET CSocket) {
TCPTerminate = true; TCPTerminate = true;
ping = -1; ping = -1;
} }
if (SubCode == 'G') { if (SubCode == 'G')
debug("Closing via 'G' packet");
exit(2); exit(2);
} OutData.clear();
Data.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') {
@@ -205,75 +203,38 @@ void Parse(std::string Data, SOCKET CSocket) {
if (!UserRole.empty()) { if (!UserRole.empty()) {
Auth["role"] = UserRole; Auth["role"] = UserRole;
} }
if (UserID != -1) { OutData = "N" + Auth.dump();
Auth["id"] = UserID;
}
Data = "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;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
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) {
CoreSocket = 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;
Parse(Ret, Client);
} 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);
} }
@@ -319,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,8 +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 <memory> #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>
@@ -57,13 +61,17 @@ bool CheckBytes(uint32_t Bytes) {
return true; return true;
} }
void GameSend(std::string_view 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;
Size = int32_t(Data.size()); uint32_t DataSize = RawData.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) {
@@ -79,20 +87,18 @@ void GameSend(std::string_view Data) {
Sent += Temp; Sent += Temp;
} while (Sent < Size); } while (Sent < Size);
// send separately to avoid an allocation for += "\n" // send separately to avoid an allocation for += "\n"
Temp = send(CSocket, "\n", 1, 0); /*Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) { if (!CheckBytes(Temp)) {
return; 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')
@@ -108,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() {
@@ -173,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()));
@@ -193,7 +196,7 @@ 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));
} }
@@ -235,8 +238,6 @@ void NetMain(const std::string& IP, int Port) {
} }
void TCPGameServer(const std::string& IP, int Port) { void TCPGameServer(const std::string& IP, int Port) {
GSocket = SetupListener(); GSocket = SetupListener();
std::unique_ptr<std::thread> ClientThread {};
std::unique_ptr<std::thread> NetMainThread {};
while (!TCPTerminate && GSocket != -1) { while (!TCPTerminate && GSocket != -1) {
debug("MAIN LOOP OF GAME SERVER"); debug("MAIN LOOP OF GAME SERVER");
GConnected = false; GConnected = false;
@@ -248,7 +249,8 @@ void TCPGameServer(const std::string& IP, int Port) {
break; break;
} }
if (CServer) { if (CServer) {
ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port); std::thread Client(TCPClientMain, IP, Port);
Client.detach();
} }
CSocket = accept(GSocket, nullptr, nullptr); CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) { if (CSocket == -1) {
@@ -258,59 +260,26 @@ void TCPGameServer(const std::string& IP, int Port) {
debug("(Proxy) Game Connected!"); debug("(Proxy) Game Connected!");
GConnected = true; GConnected = true;
if (CServer) { if (CServer) {
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port); std::thread t1(NetMain, IP, Port);
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;
Terminate = true; 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) if (CSocket != SOCKET_ERROR)
KillSocket(CSocket); KillSocket(CSocket);
debug("END OF GAME SERVER"); debug("END OF GAME SERVER");

View File

@@ -5,15 +5,11 @@
/// ///
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "Http.h" #include "Http.h"
#include <Logger.h> #include <Logger.h>
#include <Network/network.hpp>
#include <Startup.h>
#include <Utils.h>
#include <cmath> #include <cmath>
#include <curl/curl.h>
#include <curl/easy.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <httplib.h> #include <httplib.h>
@@ -61,74 +57,107 @@ void WriteHttpDebug(const httplib::Client& client, const std::string& method, co
error(e.what()); error(e.what());
} }
static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
std::string* Result = reinterpret_cast<std::string*>(userp);
std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
*Result += NewContents;
return size * nmemb;
}
bool HTTP::isDownload = false; bool HTTP::isDownload = false;
std::string HTTP::Get(const std::string& IP) { std::string HTTP::Get(const std::string& IP) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
auto pos = IP.find('/', 10);
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
cli.set_follow_location(true);
auto res = cli.Get(IP.substr(pos).c_str(), ProgressBar);
std::string Ret; std::string Ret;
static thread_local CURL* curl = curl_easy_init();
if (curl) { if (res) {
CURLcode res; if (res->status == 200) {
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); Ret = res->body;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); } else {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret); WriteHttpDebug(cli, "GET", IP, res);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); // seconds error("Failed to GET '" + IP + "': " + res->reason + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
return "";
} }
} else { } else {
error("Curl easy init failed"); if (isDownload) {
return ""; std::cout << "\n";
} }
WriteHttpDebug(cli, "GET", IP, res);
error("HTTP Get failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
}
return Ret; return Ret;
} }
std::string HTTP::Post(const std::string& IP, const std::string& Fields) { std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
auto pos = IP.find('/', 10);
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
std::string Ret; std::string Ret;
static thread_local CURL* curl = curl_easy_init();
if (curl) { if (!Fields.empty()) {
CURLcode res; httplib::Result res = cli.Post(IP.substr(pos).c_str(), Fields, "application/json");
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); if (res) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret); if (res->status != 200) {
curl_easy_setopt(curl, CURLOPT_POST, 1); error(res->reason);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); }
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); Ret = res->body;
struct curl_slist* list = nullptr; } else {
list = curl_slist_append(list, "Content-Type: application/json"); WriteHttpDebug(cli, "POST", IP, res);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); // seconds
res = curl_easy_perform(curl);
curl_slist_free_all(list);
if (res != CURLE_OK) {
error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
return "";
} }
} else { } else {
error("Curl easy init failed"); httplib::Result res = cli.Post(IP.substr(pos).c_str());
return ""; if (res) {
if (res->status != 200) {
error(res->reason);
} }
Ret = res->body;
} else {
WriteHttpDebug(cli, "POST", IP, res);
error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
}
}
if (Ret.empty())
return "-1";
else
return Ret; return Ret;
} }
bool HTTP::ProgressBar(size_t c, size_t t) {
if (isDownload) {
static double last_progress, progress_bar_adv;
progress_bar_adv = round(c / double(t) * 25);
std::cout << "\r";
std::cout << "Progress : [ ";
std::cout << round(c / double(t) * 100);
std::cout << "% ] [";
int i;
for (i = 0; i <= progress_bar_adv; i++)
std::cout << "#";
for (i = 0; i < 25 - progress_bar_adv; i++)
std::cout << ".";
std::cout << "]";
last_progress = round(c / double(t) * 100);
}
return true;
}
bool HTTP::Download(const std::string& IP, const std::string& Path) { bool HTTP::Download(const std::string& IP, const std::string& Path) {
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
info("Downloading an update (this may take a while)"); isDownload = true;
std::string Ret = Get(IP); std::string Ret = Get(IP);
isDownload = false;
if (Ret.empty()) { if (Ret.empty())
error("Download failed");
return false; return false;
}
std::ofstream File(Path, std::ios::binary); std::ofstream File(Path, std::ios::binary);
if (File.is_open()) { if (File.is_open()) {
@@ -143,126 +172,3 @@ bool HTTP::Download(const std::string& IP, const std::string& Path) {
return true; 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();
}

View File

@@ -7,13 +7,6 @@
/// ///
#include "Network/network.hpp" #include "Network/network.hpp"
#include <chrono>
#include <iomanip>
#include <ios>
#include <mutex>
#include <nlohmann/json.hpp>
#include <openssl/err.h>
#include <openssl/evp.h>
#if defined(_WIN32) #if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -28,7 +21,6 @@
#include "Logger.h" #include "Logger.h"
#include "Startup.h" #include "Startup.h"
#include <Utils.h>
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
@@ -36,21 +28,35 @@
#include <fstream> #include <fstream>
#include <future> #include <future>
#include <iostream> #include <iostream>
#include <string>
#include <thread> #include <thread>
#include <vector>
#include "hashpp.h"
namespace fs = std::filesystem; 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() { void CheckForDir() {
if (!fs::exists(CachingDirectory)) { if (!fs::exists("Resources")) {
try { // Could we just use fs::create_directory instead?
fs::create_directories(CachingDirectory); #if defined(_WIN32)
} catch (const std::exception& e) { _wmkdir(L"Resources");
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."); #elif defined(__linux__)
std::this_thread::sleep_for(std::chrono::seconds(3)); fs::create_directory(L"Resources");
std::exit(1); #endif
}
} }
} }
void WaitForConfirm() { void WaitForConfirm() {
@@ -67,26 +73,22 @@ 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);
if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') { if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
TCPSend(PublicKey, Sock); TCPSend(strtovec(PublicKey), Sock);
if (Terminate) { if (Terminate)
CoreSend("L");
return ""; return "";
}
Res = TCPRcv(Sock); Res = TCPRcv(Sock);
if (Res.empty() || Res[0] != 'P') { if (Res.empty() || Res[0] != 'P') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
@@ -95,28 +97,24 @@ std::string Auth(SOCKET Sock) {
ClientID = std::stoi(Res); ClientID = std::stoi(Res);
} else { } else {
Abord(); Abord();
CoreSend("L");
UUl("Authentication failed!"); UUl("Authentication failed!");
return ""; return "";
} }
TCPSend("SR", Sock); TCPSend(strtovec("SR"), Sock);
if (Terminate) { if (Terminate)
CoreSend("L");
return ""; return "";
}
Res = TCPRcv(Sock); Res = TCPRcv(Sock);
if (Res[0] == 'E' || Res[0] == 'K') { if (Res[0] == 'E' || Res[0] == 'K') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
if (Res.empty() || Res == "-") { if (Res.empty() || Res == "-") {
info("Didn't Receive any mods..."); info("Didn't Receive any mods...");
CoreSend("L"); ListOfMods = "-";
TCPSend("Done", Sock); TCPSend(strtovec("Done"), Sock);
info("Done!"); info("Done!");
return ""; return "";
} }
@@ -130,62 +128,38 @@ void UpdateUl(bool D, const std::string& msg) {
UlStatus = "UlLoading Resource " + msg; UlStatus = "UlLoading Resource " + msg;
} }
float DownloadSpeed = 0;
void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) { void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) {
do { do {
double pr = double(Rcv) / double(Size) * 100; double pr = double(Rcv) / double(Size) * 100;
std::string Per = std::to_string(trunc(pr * 10) / 10); std::string Per = std::to_string(trunc(pr * 10) / 10);
std::string SpeedString = ""; UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)");
if (DownloadSpeed > 0.01) {
std::stringstream ss;
ss << " at " << std::setprecision(1) << std::fixed << DownloadSpeed << " Mbit/s";
SpeedString = ss.str();
}
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)" + SpeedString);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while (!Terminate && Rcv < Size); } while (!Terminate && Rcv < Size);
} }
// MICROSOFT, I DONT CARE, WRITE BETTER CODE char* TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
#undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if (Sock == -1) { if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return {}; return nullptr;
} }
std::vector<char> File(Size); char* File = new char[Size];
uint64_t Rcv = 0; uint64_t Rcv = 0;
auto start = std::chrono::high_resolution_clock::now();
int i = 0;
do { do {
// receive at most some MB at a time int Len = int(Size - Rcv);
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024); if (Len > 1000000)
Len = 1000000;
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL); int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if (Temp < 1) { if (Temp < 1) {
info(std::to_string(Temp)); info(std::to_string(Temp));
UUl("Socket Closed Code 1"); UUl("Socket Closed Code 1");
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
return {}; delete[] File;
return nullptr;
} }
Rcv += Temp; Rcv += Temp;
GRcv += Temp; GRcv += Temp;
auto end = std::chrono::high_resolution_clock::now();
auto difference = end - start;
float bits_per_s = float(Rcv * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
float megabits_per_s = bits_per_s / 1000;
DownloadSpeed = megabits_per_s;
// every 8th iteration print the speed
if (i % 8 == 0) {
debug("Download speed: " + std::to_string(uint32_t(megabits_per_s)) + "Mbit/s");
}
++i;
} while (Rcv < Size && !Terminate); } while (Rcv < Size && !Terminate);
return File; return File;
} }
@@ -219,78 +193,44 @@ SOCKET InitDSock() {
return DSock; return DSock;
} }
std::vector<char> SingleNormalDownload(SOCKET MSock, uint64_t Size, const std::string& Name) { std::string MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
uint64_t GRcv = 0; 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, Size); 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);
KillSocket(MSock);
Terminate = true;
Au.join();
return {};
}
// ensure that GRcv is good before joining the async update thread if (!DData) {
GRcv = MData.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;
Au.join();
return {};
}
Au.join();
return MData;
}
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
uint64_t GRcv = 0;
uint64_t MSize = Size / 2;
uint64_t DSize = Size - MSize;
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, MSize);
if (MData.empty()) {
MultiKill(MSock, DSock); MultiKill(MSock, DSock);
Terminate = true; return "";
Au.join();
return {};
} }
const std::vector<char> DData = TCPRcvRaw(DSock, GRcv, DSize); f1.wait();
char* MData = f1.get();
if (DData.empty()) { if (!MData) {
MultiKill(MSock, DSock); MultiKill(MSock, DSock);
Terminate = true; return "";
Au.join();
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;
Au.join();
return {};
} }
if (Au.joinable())
Au.join(); Au.join();
std::vector<char> Result {}; /// omg yes very ugly my god but i was in a rush will revisit
Result.insert(Result.begin(), MData.begin(), MData.end()); std::string Ret(Size, 0);
Result.insert(Result.end(), DData.begin(), DData.end()); memcpy(&Ret[0], MData, MSize);
return Result; delete[] MData;
memcpy(&Ret[MSize], DData, DSize);
delete[] DData;
return Ret;
} }
void InvalidResource(const std::string& File) { void InvalidResource(const std::string& File) {
@@ -299,255 +239,31 @@ void InvalidResource(const std::string& File) {
Terminate = true; Terminate = true;
} }
std::string GetSha256HashReallyFast(const std::string& filename) {
try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
uint8_t sha256_value[EVP_MAX_MD_SIZE];
md = EVP_sha256();
if (md == nullptr) {
throw std::runtime_error("EVP_sha256() failed");
}
mdctx = EVP_MD_CTX_new();
if (mdctx == nullptr) {
throw std::runtime_error("EVP_MD_CTX_new() failed");
}
if (!EVP_DigestInit_ex2(mdctx, md, NULL)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestInit_ex2() failed");
}
std::ifstream stream(filename, std::ios::binary);
const size_t FileSize = std::filesystem::file_size(filename);
size_t Read = 0;
std::vector<char> Data;
while (Read < FileSize) {
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
size_t RealDataSize = Data.size();
stream.read(Data.data(), std::streamsize(Data.size()));
if (stream.eof() || stream.fail()) {
RealDataSize = size_t(stream.gcount());
}
Data.resize(RealDataSize);
if (RealDataSize == 0) {
break;
}
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestUpdate() failed");
}
Read += RealDataSize;
}
unsigned int sha256_len = 0;
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestFinal_ex() failed");
}
EVP_MD_CTX_free(mdctx);
std::string result;
for (size_t i = 0; i < sha256_len; i++) {
char buf[3];
sprintf(buf, "%02x", sha256_value[i]);
buf[2] = 0;
result += buf;
}
return result;
} catch (const std::exception& e) {
error("Sha256 hashing of '" + filename + "' failed: " + e.what());
return "";
}
}
struct ModInfo {
static std::vector<ModInfo> ParseModInfosFromPacket(const std::string& packet) {
std::vector<ModInfo> modInfos;
try {
auto json = nlohmann::json::parse(packet);
for (const auto& entry : json) {
ModInfo modInfo {
.FileName = entry["file_name"],
.FileSize = entry["file_size"],
.Hash = entry["hash"],
.HashAlgorithm = entry["hash_algorithm"],
};
modInfos.push_back(modInfo);
}
} catch (const std::exception& e) {
debug(std::string("Failed to receive mod list: ") + e.what());
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
return {};
}
return modInfos;
}
std::string FileName;
size_t FileSize;
std::string Hash;
std::string HashAlgorithm;
};
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
if (!SecurityWarning())
return;
info("Checking Resources...");
CheckForDir();
std::string t;
for (const auto& mod : ModInfos) {
t += mod.FileName + ";";
}
if (t.empty())
CoreSend("L");
else
CoreSend("L" + t);
t.clear();
info("Syncing...");
int ModNo = 1;
int TotalMods = ModInfos.size();
for (auto ModInfoIter = ModInfos.begin(), AlsoModInfoIter = ModInfos.begin(); ModInfoIter != ModInfos.end() && !Terminate; ++ModInfoIter, ++AlsoModInfoIter) {
if (ModInfoIter->Hash.length() < 8 || ModInfoIter->HashAlgorithm != "sha256") {
error("Unsupported hash algorithm or invalid hash for '" + ModInfoIter->FileName + "'");
Terminate = true;
return;
}
auto FileName = std::filesystem::path(ModInfoIter->FileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string();
auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string();
if (fs::exists(PathToSaveTo) && GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) {
debug("Mod '" + FileName + "' found in cache");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
auto modname = ModInfoIter->FileName;
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
debug("Mod name: " + modname);
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / modname;
std::string tmp_name = name.string();
tmp_name += ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
continue;
}
WaitForConfirm();
continue;
}
CheckForDir();
std::string FName = ModInfoIter->FileName;
do {
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + ModInfoIter->FileName, Sock);
std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) {
Terminate = true;
UUl("Server cannot find " + FName);
break;
}
std::string Name = std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName;
std::vector<char> DownloadedFile = SingleNormalDownload(Sock, ModInfoIter->FileSize, Name);
if (Terminate)
break;
UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName);
// 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
OutFile.flush();
}
// 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) != ModInfoIter->FileSize && !Terminate);
if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
// Linux version of the game doesnt support uppercase letters in mod names
#if defined(__linux__)
for (char& c : FName) {
c = ::tolower(c);
}
#endif
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
}
WaitForConfirm();
++ModNo;
}
if (!Terminate) {
TCPSend("Done", Sock);
info("Done!");
} else {
UlStatus = "Ulstart";
info("Connection Terminated!");
}
}
void SyncResources(SOCKET Sock) { void SyncResources(SOCKET Sock) {
std::string Ret = Auth(Sock); std::string Ret = Auth(Sock);
if (Ret.starts_with("R")) {
debug("This server is likely outdated, not trying to parse new mod info format");
} else {
auto ModInfos = ModInfo::ParseModInfosFromPacket(Ret);
if (!ModInfos.empty()) {
NewSyncResources(Sock, Ret, ModInfos);
return;
}
}
if (Ret.empty()) if (Ret.empty())
return; return;
if (!SecurityWarning())
return;
info("Checking Resources..."); info("Checking Resources...");
CheckForDir(); 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> FNames(list.begin(), list.begin() + (list.size() / 2));
std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end()); std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end());
list.clear(); list.clear();
Ret.clear(); Ret.clear();
int Amount = 0, Pos = 0; int Amount = 0, Pos = 0;
std::string PathToSaveTo, t; std::string a, t;
for (const std::string& name : FNames) { for (const std::string& name : FNames) {
if (!name.empty()) { if (!name.empty()) {
t += name.substr(name.find_last_of('/') + 1) + ";"; t += name.substr(name.find_last_of('/') + 1) + ";";
} }
} }
if (t.empty()) if (t.empty())
CoreSend("L"); ListOfMods = "-";
else else
CoreSend("L" + t); ListOfMods = t;
t.clear(); t.clear();
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
@@ -566,23 +282,21 @@ void SyncResources(SOCKET Sock) {
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
if (pos != std::string::npos) { if (pos != std::string::npos) {
PathToSaveTo = CachingDirectory + FN->substr(pos); a = "Resources" + FN->substr(pos);
} else { } else
continue; continue;
}
Pos++; Pos++;
auto FileSize = std::stoull(*FS); if (fs::exists(a)) {
if (fs::exists(PathToSaveTo)) {
if (FS->find_first_not_of("0123456789") != std::string::npos) if (FS->find_first_not_of("0123456789") != std::string::npos)
continue; continue;
if (fs::file_size(PathToSaveTo) == FileSize) { if (fs::file_size(a) == std::stoull(*FS)) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/'))); 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)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
try { try {
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 modname = PathToSaveTo.substr(PathToSaveTo.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 : modname) { for (char& c : modname) {
@@ -591,7 +305,7 @@ void SyncResources(SOCKET Sock) {
#endif #endif
auto name = GetGamePath() + "mods/multiplayer" + modname; auto name = GetGamePath() + "mods/multiplayer" + modname;
auto tmp_name = name + ".tmp"; 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); fs::rename(tmp_name, name);
} catch (std::exception& e) { } catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what())); error("Failed copy to the mods folder! " + std::string(e.what()));
@@ -601,13 +315,12 @@ void SyncResources(SOCKET Sock) {
WaitForConfirm(); WaitForConfirm();
continue; continue;
} else } else
remove(PathToSaveTo.c_str()); remove(a.c_str());
} }
CheckForDir(); CheckForDir();
std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); std::string FName = a.substr(a.find_last_of('/'));
do { do {
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'"); TCPSend(strtovec("f" + *FN), Sock);
TCPSend("f" + *FN, Sock);
std::string Data = TCPRcv(Sock); std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) { if (Data == "CO" || Terminate) {
@@ -618,23 +331,19 @@ void SyncResources(SOCKET Sock) {
std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName; 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) if (Terminate)
break; break;
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName); 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 } while (fs::file_size(a) != std::stoull(*FS) && !Terminate);
{
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);
if (!Terminate) { if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
@@ -647,14 +356,13 @@ void SyncResources(SOCKET Sock) {
} }
#endif #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(); WaitForConfirm();
} }
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

@@ -7,7 +7,6 @@
/// ///
#include "Network/network.hpp" #include "Network/network.hpp"
#include "Zlib/Compressor.h" #include "Zlib/Compressor.h"
#include <stdexcept>
#if defined(_WIN32) #if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -27,12 +26,15 @@
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,24 +42,22 @@ 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_view 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);
try {
auto res = DeComp(std::span<const char>(substr.data(), substr.size())); auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
std::string DeCompPacket = std::string(res.data(), res.size()); std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket); ServerParser(DeCompPacket);
} catch (const std::runtime_error& err) {
error("Error in decompression of UDP, ignoring");
}
} else { } else {
ServerParser(Packet); ServerParser(Packet);
} }
@@ -95,13 +95,10 @@ 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"));
debug("Starting UDP receive loop"); while (!Terminate)
while (!Terminate) {
UDPRcv(); UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock); KillSocket(UDPSock);
WSACleanup(); WSACleanup();
} }

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;
@@ -82,39 +82,39 @@ std::string TCPRcv(SOCKET Sock) {
UUl("Invalid Socket"); UUl("Invalid Socket");
return ""; return "";
} }
int32_t Header, Temp; int32_t Header, BytesRcv = 0, Temp;
std::vector<char> Data(sizeof(Header)); std::vector<char> Data(sizeof(Header));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL); do {
Temp = recv(Sock, &Data[BytesRcv], 4 - BytesRcv, 0);
if (!CheckBytes(Temp)) { if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 3"); UUl("Socket Closed Code 3");
return ""; return "";
} }
memcpy(&Header, Data.data(), sizeof(Header)); BytesRcv += Temp;
} while (BytesRcv < 4);
memcpy(&Header, &Data[0], sizeof(Header));
if (!CheckBytes(Temp)) { if (!CheckBytes(BytesRcv)) {
UUl("Socket Closed Code 4"); UUl("Socket Closed Code 4");
return ""; return "";
} }
Data.resize(Header);
Data.resize(Header, 0); BytesRcv = 0;
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL); do {
Temp = recv(Sock, &Data[BytesRcv], Header - BytesRcv, 0);
if (!CheckBytes(Temp)) { if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 5"); UUl("Socket Closed Code 5");
return ""; return "";
} }
BytesRcv += Temp;
} while (BytesRcv < Header);
std::string Ret(Data.data(), Header); std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") { if (Ret.substr(0, 4) == "ABG:") {
auto substr = Ret.substr(4); auto substr = Ret.substr(4);
try { auto res = DeComp(strtovec(substr));
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
Ret = std::string(res.data(), res.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 "";
}
} }
#ifdef DEBUG #ifdef DEBUG
@@ -152,7 +152,6 @@ void TCPClientMain(const std::string& IP, int Port) {
KillSocket(TCPSock); KillSocket(TCPSock);
WSACleanup(); WSACleanup();
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
info("Connected!"); info("Connected!");

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

@@ -17,6 +17,7 @@
#endif #endif
#include "Logger.h" #include "Logger.h"
#include <fstream> #include <fstream>
#include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -33,8 +34,24 @@ void lowExit(int code) {
std::this_thread::sleep_for(std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::seconds(10));
exit(2); 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() { std::string GetGameDir() {
// if(TraceBack != 4)Exit(0);
#if defined(_WIN32) #if defined(_WIN32)
return GameDir.substr(0, GameDir.find_last_of('\\')); return GameDir.substr(0, GameDir.find_last_of('\\'));
#elif defined(__linux__) #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() { 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) #if defined(_WIN32)
std::string Result; std::string Result;
std::string K3 = R"(Software\BeamNG\BeamNG.drive)"; std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
@@ -166,18 +327,17 @@ void LegitimacyCheck() {
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey); LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
if (dwRegOPenKey == ERROR_SUCCESS) { if (dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 3); Result = QueryKey(hKey, 3);
if (Result.empty()) { if (Result.empty())
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(3); lowExit(3);
} // if(IDCheck(Result,T))lowExit(5);
GameDir = Result; GameDir = Result;
} else { // TraceBack++;
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive"); } else
lowExit(4); lowExit(4);
}
K3.clear(); K3.clear();
Result.clear(); Result.clear();
RegCloseKey(hKey); RegCloseKey(hKey);
// if(TraceBack < 3)exit(-1);
#elif defined(__linux__) #elif defined(__linux__)
struct passwd* pw = getpwuid(getuid()); struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir; std::string homeDir = pw->pw_dir;

View File

@@ -18,7 +18,6 @@ std::string PrivateKey;
extern bool LoginAuth; extern bool LoginAuth;
extern std::string Username; extern std::string Username;
extern std::string UserRole; extern std::string UserRole;
extern int UserID;
void UpdateKey(const char* newKey) { void UpdateKey(const char* newKey) {
if (newKey && std::isalnum(newKey[0])) { if (newKey && std::isalnum(newKey[0])) {
@@ -49,7 +48,6 @@ std::string Login(const std::string& fields) {
if (fields == "LO") { if (fields == "LO") {
Username = ""; Username = "";
UserRole = ""; UserRole = "";
UserID = -1;
LoginAuth = false; LoginAuth = false;
UpdateKey(nullptr); UpdateKey(nullptr);
return ""; return "";
@@ -58,7 +56,7 @@ std::string Login(const std::string& fields) {
try { try {
std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields); std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields);
if (Buffer.empty()) { if (Buffer == "-1") {
return GetFail("Failed to communicate with the auth system!"); return GetFail("Failed to communicate with the auth system!");
} }
@@ -76,9 +74,6 @@ std::string Login(const std::string& fields) {
if (d.contains("role")) { if (d.contains("role")) {
UserRole = d["role"].get<std::string>(); UserRole = d["role"].get<std::string>();
} }
if (d.contains("id")) {
UserID = d["id"].get<int>();
}
if (d.contains("private_key")) { if (d.contains("private_key")) {
UpdateKey(d["private_key"].get<std::string>().c_str()); UpdateKey(d["private_key"].get<std::string>().c_str());
} }
@@ -119,7 +114,7 @@ void CheckLocalKey() {
nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false); nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
if (Buffer.empty() || Buffer.at(0) != '{' || d.is_discarded()) { if (Buffer == "-1" || Buffer.at(0) != '{' || d.is_discarded()) {
error(Buffer); error(Buffer);
info("Invalid answer from authentication servers."); info("Invalid answer from authentication servers.");
UpdateKey(nullptr); UpdateKey(nullptr);
@@ -134,9 +129,7 @@ void CheckLocalKey() {
if (d.contains("role")) { if (d.contains("role")) {
UserRole = d["role"].get<std::string>(); UserRole = d["role"].get<std::string>();
} }
if (d.contains("id")) { // info(Role);
UserID = d["id"].get<int>();
}
} else { } else {
info("Auto-Authentication unsuccessful please re-login!"); info("Auto-Authentication unsuccessful please re-login!");
UpdateKey(nullptr); UpdateKey(nullptr);

View File

@@ -81,7 +81,7 @@ std::string GetEN() {
} }
std::string GetVer() { std::string GetVer() {
return "2.2"; return "2.1";
} }
std::string GetPatch() { std::string GetPatch() {
return ".0"; return ".0";
@@ -101,7 +101,6 @@ void ReLaunch(int argc, char* args[]) {
Arg += " "; Arg += " ";
Arg += args[c - 1]; Arg += args[c - 1];
} }
info("Relaunch!");
system("cls"); system("cls");
ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL); ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0); ShowWindow(GetConsoleWindow(), 0);
@@ -126,7 +125,6 @@ void ReLaunch(int argc, char* args[]) {
Arg += " "; Arg += " ";
Arg += args[c - 1]; Arg += args[c - 1];
} }
info("Relaunch!");
system("clear"); system("clear");
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL); execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -169,8 +167,12 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back"); std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP); 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) { 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.");
@@ -202,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
@@ -235,6 +244,7 @@ void LinuxPatch() {
#if defined(_WIN32) #if defined(_WIN32)
void InitLauncher(int argc, char* argv[]) { void InitLauncher(int argc, char* argv[]) {
system("cls");
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str()); SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog(); InitLog();
CheckName(argc, argv); CheckName(argc, argv);
@@ -242,18 +252,34 @@ void InitLauncher(int argc, char* argv[]) {
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
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()); 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[]) {
system("clear");
InitLog(); InitLog();
info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName(argc, argv); CheckName(argc, argv);
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
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()); CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
} }
}
#endif #endif
size_t DirCount(const std::filesystem::path& path) { size_t DirCount(const std::filesystem::path& path) {
@@ -314,7 +340,8 @@ void PreGame(const std::string& GamePath) {
info("Game Version : " + GameVer); info("Game Version : " + GameVer);
CheckMP(GetGamePath() + "mods/multiplayer"); CheckMP(GetGamePath() + "mods/multiplayer");
info("Game user path: " + GetGamePath());
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);
@@ -355,3 +382,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();
}

View File

@@ -10,7 +10,6 @@
#include "Network/network.hpp" #include "Network/network.hpp"
#include "Security/Init.h" #include "Security/Init.h"
#include "Startup.h" #include "Startup.h"
#include <curl/curl.h>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
@@ -21,52 +20,26 @@
} }
} }
int main(int argc, char** argv) try { int main(int argc, char* argv[]) {
#ifdef DEBUG #ifdef DEBUG
std::thread th(flush); std::thread th(flush);
th.detach(); th.detach();
#endif #endif
curl_global_init(CURL_GLOBAL_ALL);
#if defined(_WIN32)
system("cls");
#elif defined(__linux__)
system("clear");
#endif
GetEP(argv[0]); 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); InitLauncher(argc, argv);
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
try { try {
LegitimacyCheck(); LegitimacyCheck();
} catch (std::exception& e) { } catch (std::exception& e) {
error("Failure in LegitimacyCheck: " + std::string(e.what())); fatal("Main 1 : " + std::string(e.what()));
throw;
} }
try { StartProxy();
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());
}
PreGame(GetGameDir()); PreGame(GetGameDir());
InitGame(GetGameDir()); InitGame(GetGameDir());
CoreNetwork(); CoreNetwork();
} catch (const std::exception& e) {
error(std::string("Exception in main(): ") + e.what()); /// TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...)
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));
} }

View File

@@ -3,7 +3,6 @@
"cpp-httplib", "cpp-httplib",
"nlohmann-json", "nlohmann-json",
"zlib", "zlib",
"openssl", "openssl"
"curl"
] ]
} }