63 Commits

Author SHA1 Message Date
Lion
7600372ca1 Fix linux executable name after BNG0.33.2 (#126) 2024-09-28 16:51:02 +02:00
Lion
54cd5b5e0e Add additional SSL Verify logging (#127) 2024-09-28 16:50:30 +02:00
Mackenzie
ede6fcd7dd log SSL errors 2024-09-27 20:33:14 +01:00
Mackenzie
eaeacbd8de log non-200 status codes 2024-09-27 20:23:28 +01:00
O1LER
0ffed00bcb rename linux executable for bng0.33.2 2024-09-27 17:48:46 +02:00
Lion
c0c3d6b30e Add download speed to UI (#125) 2024-09-24 21:59:01 +02:00
Lion
9c59a83f04 turn off stdout, stderr of the game on linux (#124) 2024-09-24 21:58:37 +02:00
Lion Kortlepel
95436cb073 turn off stdout, stderr of the game on linux 2024-09-24 21:56:55 +02:00
Lion Kortlepel
cbb5502a40 send download speed to game UI, bump version to 2.1.4 2024-09-24 21:50:09 +02:00
Lion Kortlepel
d6dfe85f69 add download speed to ingame ui 2024-09-24 21:10:10 +02:00
Tixx
ae9af1470c Removal invalid comma causing the default config to be broken (#123) 2024-09-24 12:47:20 +02:00
Tixx
9255c70b0b Removal invalid comma 2024-09-24 12:38:24 +02:00
Lion Kortlepel
53c514ecc6 bump to 2.1.3 2024-09-23 23:13:51 +02:00
Lion Kortlepel
e348d59a7e fix linux executable name 2024-09-23 23:13:34 +02:00
Lion
244d27341f Fix release actions (#122) 2024-09-23 22:49:33 +02:00
Lion Kortlepel
3a55b62907 remove release action 2024-09-23 22:49:03 +02:00
Lion
0c3ae43910 Add CachingDirectory config setting to cache mods elsewhere (#121)
also moved cls/clear to the beginning, idk wtf it was doing in there.
2024-09-23 22:45:21 +02:00
Lion Kortlepel
8436586566 print version on startup
🚀
2024-09-23 22:43:32 +02:00
Lion Kortlepel
19d1245379 catch errors when the custom caching directory is not accessible
🧯
2024-09-23 22:39:44 +02:00
Lion
470eeac821 Add better error handling (#119) 2024-09-23 22:34:19 +02:00
Lion
9c6aa86e68 Add print to inform the user that they must keep the window open (#120) 2024-09-23 22:33:54 +02:00
Lion Kortlepel
1362471657 add CachingDirectory config setting to cache mods elsewhere
also moved cls/clear to the beginning, idk wtf it was doing in there.
2024-09-23 22:31:58 +02:00
Lion Kortlepel
aa46b454e2 add print to inform the user that they must keep the window open 2024-09-23 22:12:00 +02:00
Lion Kortlepel
02465c529d add more logging to exit 2024-09-23 22:08:45 +02:00
Lion Kortlepel
c68cbf8946 remove unused """security""" code 2024-09-23 22:04:34 +02:00
Lion Kortlepel
46542c1dce always log debug to Launcher.log 2024-09-23 22:00:41 +02:00
Lion Kortlepel
97f58dd413 add better error handling to main() 2024-09-23 21:58:27 +02:00
Lion
4bedfc8e96 Little Itsy Bitsy TCP fixes (#118) 2024-09-23 21:46:11 +02:00
Lion Kortlepel
cd17df5cc2 add more debug statements, wait for threads before shutting down 2024-09-22 21:37:52 +02:00
Lion Kortlepel
0b589a74c9 refactor tcp receive to be less weird 2024-09-22 20:31:25 +02:00
Lion Kortlepel
1260515a40 fix crash when cancelling download 2024-09-22 20:20:31 +02:00
Lion
007cd6573e Refactor downloading (#116)
The way it was done was so horrid, it was not only impossible to debug,
with TODO comments saying it sucks, and other shit like that, but it was
also just full of data races. You can rest easy however - I left most of
the data races in there <3 For nostalgia (totally not because it's a
massive pain to fix that).

We now do single-threaded download, which can not only saturate my 100
Mbit/s line without any hickups, it can also go up to ~600000 Mbit/s for
localhost transfers :) So I think it's fine.
2024-09-22 20:04:45 +02:00
Lion
7b022f9907 Add --skip-ssl-verify cli option (#117)
This is a temporary fix for if anyone has issues with SSL certificate
validation. The use of this must come with the disclaimer that,
obviously, this bypasses the security that SSL gives entirely. Anyone
could MITM you at that point. Don't use, basically.
2024-09-22 19:56:43 +02:00
Lion Kortlepel
96c9c89238 add extra layer of checks for data races in download
yeah
2024-09-22 19:52:52 +02:00
Lion
b4949af1d7 Check 'User Shell Folders' (#111)
this PR is a continuation of #69
2024-09-22 19:47:50 +02:00
Lion
85086909a6 Merge pull request #108 from WiserTixx/implement-mods-warning
Implement mods warning
2024-09-22 19:46:34 +02:00
Lion Kortlepel
79209219dd remove extraneous game user path print 2024-09-22 19:42:55 +02:00
Lion Kortlepel
18e1b7a2bb add --skip-ssl-verify cli option 2024-09-22 19:42:00 +02:00
Lion Kortlepel
a5766639d6 add back user path print
Thanks @WiserTixx for finding a good place for it
2024-09-22 19:29:39 +02:00
Lion Kortlepel
191fbf083d fix stupid microsoft macro <3 2024-09-22 19:06:46 +02:00
Lion Kortlepel
8c4342853a refactor downloading
The way it was done was so horrid, it was not only impossible to debug,
with TODO comments saying it sucks, and other shit like that, but it was
also just full of data races. You can rest easy however - I left most of
the data races in there <3 For nostalgia (totally not because it's a
massive pain to fix that).

We now do single-threaded download, which can not only saturate my 100
Mbit/s line without any hickups, it can also go up to ~600000 Mbit/s for
localhost transfers :) So I think it's fine.
2024-09-22 18:52:50 +02:00
Tixx
3937ac1ae7 Fix joining 2024-09-14 22:17:21 +02:00
Tixx
a128099619 Patch up removal of while loop in Core 2024-09-14 22:17:21 +02:00
Tixx
deed24f6e8 Fix client lua error 2024-09-14 22:17:21 +02:00
Tixx
ac2db7c73f Remove now unused variable 2024-09-14 22:17:21 +02:00
Tixx
06db6d0341 Implement mod warning 2024-09-14 22:17:21 +02:00
Deer McDurr
2d43e11e96 Merge pull request #114 from WiserTixx/action-fix
Fix github actions
2024-09-14 22:14:53 +02:00
Tixx
8911158f81 Fix actions 2024-09-14 22:03:52 +02:00
20dka
a714dc3188 fix windows build and implement suggestion from lionkor 2024-09-08 16:42:03 +02:00
yeranya
29445f65ce check 'User Shell Folders' key in addition to 'Shell Folders' 2024-09-08 16:24:58 +02:00
Deer McDurr
48be292850 Merge pull request #103 from purifiedfr/readme-build-guide
Update the Build guide in README
2024-09-08 16:00:06 +02:00
purified
2397f45d3f Add the guide on how to clone the repository with the evpp submodule 2024-09-08 15:57:15 +02:00
purified
d1fb67f1f0 Update the Build guide in README
Add instructions for building in Release mode
Add the reminder to change the vcpkg location
Add the reminder to run the commands in the root of the project
2024-09-08 15:57:15 +02:00
Deer McDurr
eae6d11476 Merge pull request #110 from WiserTixx/improve-http-proxy
HTTP proxy improvements, avatar endpoint
2024-09-08 14:46:49 +02:00
Tixx
452fc1e484 Move HTTP Proxy and remove and relocate duplicate code 2024-09-07 22:35:27 +02:00
Tixx
de3888618a Safety improvements 2024-09-07 22:00:51 +02:00
Tixx
4678701f42 HTTP proxy improvements
Adds the avatar endpoint and adds the possibility to easily add others
2024-09-07 21:19:42 +02:00
Deer McDurr
7481ba4539 Merge pull request #109 from WiserTixx/allow-patreon-link
Add BeamMP patreon to the allowed links
2024-09-07 20:53:40 +02:00
Tixx
d791e2ac92 Add BeamMP patreon to the allowed links 2024-08-22 22:09:02 +02:00
Lion
a60ff48c08 Merge pull request #105 from WiserTixx/id-from-auth
Send id from auth to game
2024-08-17 20:34:19 +02:00
Lion
da3b49aa12 Merge pull request #106 from WiserTixx/fix-http-proxy-ub
Fix UB which was causing the http proxy to crash
2024-08-17 20:32:59 +02:00
Tixx
e505874af9 Send id from auth to game 2024-08-11 11:39:14 +02:00
Tixx
2f0a9fba99 move macro definition to cmakelist 2024-08-10 23:22:17 +02:00
28 changed files with 931 additions and 967 deletions

View File

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

View File

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

View File

@@ -1,104 +0,0 @@
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,11 +12,11 @@ set(CMAKE_CXX_STANDARD 20)
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")
find_package(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(asio CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
add_executable(${PROJECT_NAME} ${source_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
@@ -25,15 +25,15 @@ if (WIN32)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json asio::asio fmt::fmt)
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json)
elseif (LINUX)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto asio::asio fmt::fmt)
elseif (WIN32) #MINGW
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto)
else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z asio::asio fmt::fmt)
target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z)
endif(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE "include")

View File

@@ -2,11 +2,24 @@
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.
## How to build
**To clone this repository**: `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Launcher.git`
## How to build - Release
In the root directory of the project,
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel --config Release`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
## How to build - Debug
In the root directory of the project,
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
Copyright (c) 2019-present Anonymous275.
BeamMP Launcher code is not in the public domain and is not free software.
One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries,

View File

@@ -1,11 +0,0 @@
#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,7 +14,8 @@ public:
static std::string Post(const std::string& IP, const std::string& Fields);
static std::string Get(const std::string& IP);
static bool ProgressBar(size_t c, size_t t);
static void StartProxy();
public:
static bool isDownload;
};
static inline bool SkipSslVerify = false;
};

View File

@@ -7,10 +7,6 @@
///
#pragma once
#include "Helpers.h"
#include "asio/io_context.hpp"
#include "asio/ip/address.hpp"
#include <span>
#include <string>
#ifdef __linux__
@@ -18,13 +14,8 @@
#include <bits/types/siginfo_t.h>
#include <cstdint>
#include <sys/ucontext.h>
#include <vector>
#endif
#include <asio.hpp>
extern asio::io_context io;
void NetReset();
extern bool Dev;
extern int ping;
@@ -36,31 +27,30 @@ extern int LastPort;
extern bool ModLoaded;
extern bool Terminate;
extern int DEFAULT_PORT;
extern std::shared_ptr<asio::ip::udp::socket> UDPSock;
extern std::shared_ptr<asio::ip::tcp::socket> TCPSock;
extern uint64_t UDPSock;
extern uint64_t TCPSock;
extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate;
extern std::string LastIP;
extern std::string MStatus;
extern std::string UlStatus;
extern std::string PublicKey;
extern std::string PrivateKey;
extern std::string ListOfMods;
void KillSocket(std::shared_ptr<asio::ip::tcp::socket>& Dead);
void KillSocket(std::shared_ptr<asio::ip::udp::socket>& Dead);
void KillSocket(asio::ip::tcp::socket& Dead);
void KillSocket(asio::ip::udp::socket& Dead);
int KillSocket(uint64_t Dead);
void UUl(const std::string& R);
void UDPSend(const std::vector<char>& Data);
void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes);
void GameSend(std::string_view Data);
void SendLarge(const std::vector<char>& Data);
std::string TCPRcv(asio::ip::tcp::socket& Sock);
void SyncResources(asio::ip::tcp::socket& TCPSock);
void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string& IP);
void ServerParser(std::string_view Data);
std::string Login(const std::string& fields);
void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock);
void TCPClientMain(asio::ip::tcp::socket&& Socket);
void UDPClientMain(asio::ip::address addr, uint16_t port);
void TCPGameServer(asio::ip::tcp::socket&& Socket);
void TCPSend(const std::string& Data, uint64_t Sock);
void TCPClientMain(const std::string& IP, int Port);
void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);

View File

@@ -1,6 +0,0 @@
#pragma once
#include <asio.hpp>
#include <vector>
void ReceiveFromGame(asio::ip::tcp::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 GetGamePath();
std::string GetVer();
std::string GetPatch();
std::string GetEN();
void StartProxy();
void ConfigInit();
extern bool Dev;

20
include/Utils.h Normal file
View File

@@ -0,0 +1,20 @@
#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,12 +19,11 @@ std::vector<char> Comp(std::span<const char> input) {
auto max_size = compressBound(input.size());
std::vector<char> output(max_size);
uLongf output_size = output.size();
int res = compress2(
int res = compress(
reinterpret_cast<Bytef*>(output.data()),
&output_size,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size()),
3);
static_cast<uLongf>(input.size()));
if (res != Z_OK) {
error("zlib compress() failed: " + std::to_string(res));
throw std::runtime_error("zlib compress() failed");

View File

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

View File

@@ -10,8 +10,6 @@
#include <windows.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <cerrno>
#include <cstring>
#include <pwd.h>
#include <spawn.h>
#include <sys/types.h>
@@ -45,11 +43,17 @@ std::string GetGamePath() {
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)";
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
if (openRes != ERROR_SUCCESS) {
fatal("Cannot get Local Appdata directory!");
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders)";
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
}
if (openRes != ERROR_SUCCESS) {
fatal("Cannot get Local Appdata directory");
}
Path = QueryKey(hKey, 5);
Path += "\\BeamNG.drive\\";
}
std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "\\";
@@ -92,27 +96,15 @@ void StartGame(std::string Dir) {
}
#elif defined(__linux__)
void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
char* argv[] = { filename.data(), NULL };
pid_t pid;
posix_spawn_file_actions_t file_actions;
auto status = posix_spawn_file_actions_init(&file_actions);
// disable stdout
if (status != 0) {
error(std::string("posix_spawn_file_actions_init failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDOUT failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDERR failed: ") + std::strerror(errno));
}
// launch the game
int result = posix_spawn(&pid, filename.c_str(), &file_actions, NULL, argv, environ);
posix_spawn_file_actions_t spawn_actions;
posix_spawn_file_actions_init(&spawn_actions);
posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO);
posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO);
int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, argv, environ);
if (result != 0) {
error("Failed to Launch the game! launcher closing soon");
@@ -122,11 +114,6 @@ void StartGame(std::string Dir) {
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));
exit(2);
}

View File

@@ -1,9 +0,0 @@
#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) {
std::string Print = getDate() + "[INFO] " + toPrint + "\n";
std::cout << Print << std::flush;
std::cout << Print;
addToLog(Print);
}
void debug(const std::string& toPrint) {
if (!Dev)
return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
std::cout << Print << std::flush;
if (Dev) {
std::cout << Print;
}
addToLog(Print);
}
void warn(const std::string& toPrint) {
std::string Print = getDate() + "[WARN] " + toPrint + "\n";
std::cout << Print << std::flush;
std::cout << Print;
addToLog(Print);
}
void error(const std::string& toPrint) {
std::string Print = getDate() + "[ERROR] " + toPrint + "\n";
std::cout << Print << std::flush;
std::cout << Print;
addToLog(Print);
}
void fatal(const std::string& toPrint) {
std::string Print = getDate() + "[FATAL] " + toPrint + "\n";
std::cout << Print << std::flush;
std::cout << Print;
addToLog(Print);
std::this_thread::sleep_for(std::chrono::seconds(5));
_Exit(-1);
std::exit(1);
}
void except(const std::string& toPrint) {
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";
std::cout << Print << std::flush;
std::cout << Print;
addToLog(Print);
}

View File

@@ -7,17 +7,18 @@
///
#include "Http.h"
#include "Network/network.hpp"
#include "NetworkHelpers.h"
#include "Security/Init.h"
#include <asio/io_context.hpp>
#include <cstdlib>
#include <optional>
#include <regex>
#if defined(__linux__)
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#elif defined(__linux__)
#include <cstring>
#include <errno.h>
#include <netdb.h>
#include <spawn.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -26,7 +27,6 @@
#include "Logger.h"
#include "Startup.h"
#include <charconv>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <set>
#include <thread>
@@ -39,122 +39,93 @@ bool Terminate = false;
bool LoginAuth = false;
std::string Username = "";
std::string UserRole = "";
int UserID = -1;
std::string UlStatus;
std::string MStatus;
bool ModLoaded;
int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
asio::io_context io {};
bool SecurityWarning() {
confirmed = -1;
CoreSend("WMODS_FOUND");
static asio::ip::tcp::socket ResolveAndConnect(const std::string& host_port_string) {
while (confirmed == -1)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
using namespace asio;
ip::tcp::resolver resolver(io);
asio::error_code ec;
auto port = host_port_string.substr(host_port_string.find_last_of(':') + 1);
auto host = host_port_string.substr(0, host_port_string.find_last_of(':'));
auto resolved = resolver.resolve(host, port, ec);
if (ec) {
::error(fmt::format("Failed to resolve '[{}]:{}': {}", host, port, ec.message()));
throw std::runtime_error(fmt::format("Failed to resolve '{}': {}", host_port_string, ec.message()));
}
bool connected = false;
if (confirmed == 1)
return true;
UlStatus = "UlLoading...";
for (const auto& addr : resolved) {
try {
info(fmt::format("Resolved and connected to '[{}]:{}'",
addr.endpoint().address().to_string(),
addr.endpoint().port()));
ip::tcp::socket socket(io);
socket.connect(addr);
// done, connected fine
return socket;
} catch (...) {
// ignore
}
}
throw std::runtime_error(fmt::format("Failed to connect to '{}'; connection refused", host_port_string));
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
return false;
}
void StartSync(const std::string& Data) {
try {
auto Socket = ResolveAndConnect(Data.substr(1));
ListOfMods = "-";
CheckLocalKey();
TCPTerminate = false;
Terminate = false;
ConfList->clear();
ping = -1;
std::thread GS(TCPGameServer, std::move(Socket));
GS.detach();
info("Connecting to server");
} catch (const std::exception& e) {
UlStatus = "UlConnection Failed!";
error(fmt::format("Client: connect failed! Error: {}", e.what()));
WSACleanup();
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
if (IP.find('.') == -1) {
if (IP == "DNS")
UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
else
UlStatus = "UlConnection Failed! (WSA failed to start)";
Terminate = true;
CoreSend("L");
return;
}
CheckLocalKey();
UlStatus = "UlLoading...";
TCPTerminate = false;
Terminate = false;
ConfList->clear();
ping = -1;
std::thread GS(TCPGameServer, IP, std::stoi(Data.substr(Data.find(':') + 1)));
GS.detach();
info("Connecting to server");
}
void CoreSend(std::string data) {
if (CoreSocket != -1) {
int res = send(CoreSocket, (data + "\n").c_str(), int(data.size()) + 1, 0);
if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
}
}
}
bool IsAllowedLink(const std::string& Link) {
std::vector<std::string> allowed_links = {
R"(patreon\.com\/beammp$)",
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;
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg|patreon\.com\/BeamMP))");
std::smatch link_match;
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
}
void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
std::string OutData;
char Code = InData[0], SubCode = 0;
if (InData.size() > 1)
SubCode = InData[1];
void Parse(std::string Data, SOCKET CSocket) {
char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
switch (Code) {
case 'A':
OutData = "A";
Data = Data.substr(0, 1);
break;
case 'B':
NetReset();
Terminate = true;
TCPTerminate = true;
OutData = Code + HTTP::Get("https://backend.beammp.com/servers-info");
Data = Code + HTTP::Get("https://backend.beammp.com/servers-info");
break;
case 'C':
ListOfMods.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;
StartSync(Data);
Data.clear();
break;
case 'O': // open default browser with URL
if (IsAllowedLink(bytespan_to_string(InData.subspan(1)))) {
if (IsAllowedLink(Data.substr(1))) {
#if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid;
auto arg = bytespan_to_string(InData.subspan(1));
auto arg = Data.substr(1);
char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) {
@@ -166,27 +137,27 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
error(std::string("posix_spawn: ") + strerror(status));
}
} else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + bytespan_to_string(InData.subspan(1)));
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1));
}
#elif defined(WIN32)
ShellExecuteA(nullptr, "open", InData.subspan(1).data(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
ShellExecuteA(nullptr, "open", Data.substr(1).c_str(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
#endif
info("Opening Link \"" + bytespan_to_string(InData.subspan(1)) + "\"");
info("Opening Link \"" + Data.substr(1) + "\"");
}
OutData.clear();
Data.clear();
break;
case 'P':
OutData = Code + std::to_string(ProxyPort);
Data = Code + std::to_string(ProxyPort);
break;
case 'U':
if (SubCode == 'l')
OutData = UlStatus;
Data = UlStatus;
if (SubCode == 'p') {
if (ping > 800) {
OutData = "Up-2";
Data = "Up-2";
} else
OutData = "Up" + std::to_string(ping);
Data = "Up" + std::to_string(ping);
}
if (!SubCode) {
std::string Ping;
@@ -194,11 +165,11 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
Ping = "-2";
else
Ping = std::to_string(ping);
OutData = std::string(UlStatus) + "\n" + "Up" + Ping;
Data = std::string(UlStatus) + "\n" + "Up" + Ping;
}
break;
case 'M':
OutData = MStatus;
Data = MStatus;
break;
case 'Q':
if (SubCode == 'S') {
@@ -207,21 +178,21 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
TCPTerminate = true;
ping = -1;
}
if (SubCode == 'G')
if (SubCode == 'G') {
debug("Closing via 'G' packet");
exit(2);
OutData.clear();
}
Data.clear();
break;
case 'R': // will send mod name
{
auto str = bytespan_to_string(InData);
if (ConfList->find(str) == ConfList->end()) {
ConfList->insert(str);
if (ConfList->find(Data) == ConfList->end()) {
ConfList->insert(Data);
ModLoaded = true;
}
OutData.clear();
} break;
Data.clear();
break;
case 'Z':
OutData = "Z" + GetVer();
Data = "Z" + GetVer();
break;
case 'N':
if (SubCode == 'c') {
@@ -234,39 +205,75 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
if (!UserRole.empty()) {
Auth["role"] = UserRole;
}
OutData = "N" + Auth.dump();
if (UserID != -1) {
Auth["id"] = UserID;
}
Data = "N" + Auth.dump();
} else {
auto indata_str = bytespan_to_string(InData);
OutData = "N" + Login(indata_str.substr(indata_str.find(':') + 1));
Data = "N" + Login(Data.substr(Data.find(':') + 1));
}
break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
default:
OutData.clear();
Data.clear();
break;
}
if (!OutData.empty() && CSocket.is_open()) {
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));
asio::error_code ec;
asio::write(CSocket, asio::buffer(ToSend), ec);
if (ec) {
debug(fmt::format("(Core) send failed with error: {}", ec.message()));
if (!Data.empty() && CSocket != -1) {
int res = send(CSocket, (Data + "\n").c_str(), int(Data.size()) + 1, 0);
if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
}
}
}
void GameHandler(asio::ip::tcp::socket& Client) {
std::vector<char> data {};
void GameHandler(SOCKET Client) {
CoreSocket = Client;
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
do {
try {
ReceiveFromGame(Client, data);
Parse(data, Client);
} catch (const std::exception& e) {
error(std::string("Error while receiving from game: ") + e.what());
Rcv = 0;
do {
Temp = recv(Client, &Header[Rcv], 1, 0);
if (Temp < 1)
break;
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;
}
} while (true);
std::string Ret(Size, 0);
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();
KillSocket(Client);
}
@@ -282,52 +289,63 @@ void localRes() {
}
void CoreMain() {
debug("Core Network on start!");
SOCKET LSocket, CSocket;
struct addrinfo* res = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes)
debug("WSAStartup failed with error: " + std::to_string(iRes));
#endif
asio::ip::tcp::endpoint listen_ep(asio::ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(DEFAULT_PORT));
asio::ip::tcp::socket LSocket(io);
asio::error_code ec;
LSocket.open(listen_ep.protocol(), ec);
if (ec) {
::error(fmt::format("Failed to open core socket: {}", ec.message()));
return;
}
asio::ip::tcp::socket::linger linger_opt {};
linger_opt.enabled(false);
LSocket.set_option(linger_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
asio::ip::tcp::socket::reuse_address reuse_opt { true };
LSocket.set_option(reuse_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
ZeroMemory(&hints, sizeof(hints));
auto acceptor = asio::ip::tcp::acceptor(io, listen_ep);
acceptor.listen(asio::ip::tcp::socket::max_listen_connections, ec);
if (ec) {
::error(fmt::format("listen() failed, which is needed for the launcher to operate. Error: {}",
ec.message()));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res);
if (iRes) {
debug("(Core) addr info failed with error: " + std::to_string(iRes));
WSACleanup();
return;
}
LSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (LSocket == -1) {
debug("(Core) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
WSACleanup();
return;
}
iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen));
if (iRes == SOCKET_ERROR) {
error("(Core) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return;
}
iRes = listen(LSocket, SOMAXCONN);
if (iRes == SOCKET_ERROR) {
debug("(Core) listen failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return;
}
do {
auto CSocket = acceptor.accept(ec);
if (ec) {
error(fmt::format("(Core) accept failed with error: {}", ec.message()));
CSocket = accept(LSocket, nullptr, nullptr);
if (CSocket == -1) {
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
continue;
}
localRes();
info("Game Connected!");
GameHandler(CSocket);
warn("Game Reconnecting...");
} while (LSocket.is_open());
} while (CSocket);
KillSocket(LSocket);
WSACleanup();
}

View File

@@ -5,13 +5,8 @@
///
/// Created by Anonymous275 on 7/25/2020
///
#include "Helpers.h"
#include "Network/network.hpp"
#include "NetworkHelpers.h"
#include "asio/socket_base.hpp"
#include <algorithm>
#include <span>
#include <vector>
#include <memory>
#include <zlib.h>
#if defined(_WIN32)
#include <winsock2.h>
@@ -28,7 +23,6 @@
#include "Logger.h"
#include <charconv>
#include <fmt/format.h>
#include <mutex>
#include <string>
#include <thread>
@@ -36,31 +30,20 @@
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
bool GConnected = false;
bool CServer = true;
std::shared_ptr<asio::ip::tcp::socket> CSocket = nullptr;
std::shared_ptr<asio::ip::tcp::socket> GSocket = nullptr;
SOCKET CSocket = -1;
SOCKET GSocket = -1;
void KillSocket(std::shared_ptr<asio::ip::tcp::socket>& Dead) {
if (!Dead)
return;
asio::error_code ec;
Dead->shutdown(asio::socket_base::shutdown_both, ec);
}
void KillSocket(std::shared_ptr<asio::ip::udp::socket>& Dead) {
if (!Dead)
return;
asio::error_code ec;
Dead->shutdown(asio::socket_base::shutdown_both, ec);
}
void KillSocket(asio::ip::tcp::socket& Dead) {
asio::error_code ec;
Dead.shutdown(asio::socket_base::shutdown_both, ec);
}
void KillSocket(asio::ip::udp::socket& Dead) {
asio::error_code ec;
Dead.shutdown(asio::socket_base::shutdown_both, ec);
int KillSocket(uint64_t Dead) {
if (Dead == (SOCKET)-1) {
debug("Kill socket got -1 returning...");
return 0;
}
shutdown(Dead, SD_BOTH);
int a = closesocket(Dead);
if (a != 0) {
warn("Failed to close socket!");
}
return a;
}
bool CheckBytes(uint32_t Bytes) {
@@ -74,34 +57,42 @@ bool CheckBytes(uint32_t Bytes) {
return true;
}
void GameSend(std::string_view RawData) {
void GameSend(std::string_view Data) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
if (TCPTerminate || !GConnected || CSocket == nullptr)
if (TCPTerminate || !GConnected || CSocket == -1)
return;
int32_t Size, Temp, Sent;
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();
Size = int32_t(Data.size());
Sent = 0;
asio::error_code ec;
asio::write(*CSocket, asio::buffer(Data), ec);
if (ec) {
debug(fmt::format("(TCP CB) recv failed with error: {}", ec.message()));
KillSocket(TCPSock);
Terminate = true;
#ifdef DEBUG
if (Size > 1000) {
debug("Launcher -> game (" + std::to_string(Size) + ")");
}
#endif
do {
if (Sent > -1) {
Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
}
if (!CheckBytes(Temp))
return;
Sent += Temp;
} while (Sent < Size);
// send separately to avoid an allocation for += "\n"
Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) {
return;
}
}
void ServerSend(const std::vector<char>& Data, bool Rel) {
void ServerSend(std::string Data, bool Rel) {
if (Terminate || Data.empty())
return;
if (Data.find("Zp") != std::string::npos && Data.size() > 500) {
abort();
}
char C = 0;
bool Ack = false;
int DLen = int(Data.size());
int DLen = int(Data.length());
if (DLen > 3)
C = Data.at(0);
if (C == 'O' || C == 'T')
@@ -113,10 +104,18 @@ void ServerSend(const std::vector<char>& Data, bool Rel) {
if (Ack || Rel) {
if (Ack || DLen > 1000)
SendLarge(Data);
else if (TCPSock)
TCPSend(Data, *TCPSock);
else
TCPSend(Data, TCPSock);
} else
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() {
@@ -125,23 +124,76 @@ void NetReset() {
Terminate = false;
UlStatus = "Ulstart";
MStatus = " ";
if (UDPSock != nullptr) {
KillSocket(*UDPSock);
if (UDPSock != (SOCKET)(-1)) {
debug("Terminating UDP Socket: " + std::to_string(TCPSock));
KillSocket(UDPSock);
}
UDPSock = nullptr;
if (TCPSock != nullptr) {
KillSocket(*TCPSock);
UDPSock = -1;
if (TCPSock != (SOCKET)(-1)) {
debug("Terminating TCP Socket: " + std::to_string(TCPSock));
KillSocket(TCPSock);
}
TCPSock = nullptr;
if (GSocket != nullptr) {
KillSocket(*GSocket);
TCPSock = -1;
if (GSocket != (SOCKET)(-1)) {
debug("Terminating GTCP Socket: " + std::to_string(GSocket));
KillSocket(GSocket);
}
GSocket = nullptr;
GSocket = -1;
}
SOCKET SetupListener() {
if (GSocket != -1)
return GSocket;
struct addrinfo* result = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes != 0) {
error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes));
return -1;
}
#endif
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT + 1).c_str(), &hints, &result);
if (iRes != 0) {
error("(Proxy) info failed with error: " + std::to_string(iRes));
WSACleanup();
}
GSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (GSocket == -1) {
error("(Proxy) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(result);
WSACleanup();
return -1;
}
iRes = bind(GSocket, result->ai_addr, (int)result->ai_addrlen);
if (iRes == SOCKET_ERROR) {
error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(result);
KillSocket(GSocket);
WSACleanup();
return -1;
}
freeaddrinfo(result);
iRes = listen(GSocket, SOMAXCONN);
if (iRes == SOCKET_ERROR) {
error("(Proxy) listen failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(GSocket);
WSACleanup();
return -1;
}
return GSocket;
}
void AutoPing() {
while (!Terminate) {
ServerSend(strtovec("p"), false);
ServerSend("p", false);
PingStart = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
@@ -173,47 +225,19 @@ void ParserAsync(std::string_view Data) {
void ServerParser(std::string_view Data) {
ParserAsync(Data);
}
void NetMain(asio::ip::address addr, uint16_t port) {
void NetMain(const std::string& IP, int Port) {
std::thread Ping(AutoPing);
Ping.detach();
UDPClientMain(addr, port);
UDPClientMain(IP, Port);
CServer = true;
Terminate = true;
info("Connection Terminated!");
}
void TCPGameServer(asio::ip::tcp::socket&& Socket) {
asio::ip::tcp::endpoint listen_ep(asio::ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(DEFAULT_PORT + 1));
asio::ip::tcp::socket listener(io);
asio::error_code ec;
listener.open(listen_ep.protocol(), ec);
if (ec) {
::error(fmt::format("Failed to open game socket: {}", ec.message()));
return;
}
asio::ip::tcp::socket::linger linger_opt {};
linger_opt.enabled(false);
listener.set_option(linger_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening game socket to not linger / reuse address. "
"This may cause the game socket to refuse to bind(). Error: {}",
ec.message()));
}
asio::ip::tcp::socket::reuse_address reuse_opt { true };
listener.set_option(reuse_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
auto acceptor = asio::ip::tcp::acceptor(io, listen_ep);
acceptor.listen(asio::ip::tcp::socket::max_listen_connections, ec);
if (ec) {
debug(fmt::format("Proxy accept failed: {}", ec.message()));
TCPTerminate = true; // skip the loop
}
debug(fmt::format("Game server listening on {}:{}", acceptor.local_endpoint().address().to_string(), acceptor.local_endpoint().port()));
while (!TCPTerminate && acceptor.is_open()) {
void TCPGameServer(const std::string& IP, int Port) {
GSocket = SetupListener();
std::unique_ptr<std::thread> ClientThread {};
std::unique_ptr<std::thread> NetMainThread {};
while (!TCPTerminate && GSocket != -1) {
debug("MAIN LOOP OF GAME SERVER");
GConnected = false;
if (!CServer) {
@@ -224,35 +248,70 @@ void TCPGameServer(asio::ip::tcp::socket&& Socket) {
break;
}
if (CServer) {
std::thread Client(TCPClientMain, std::move(Socket));
Client.detach();
ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port);
}
CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) {
debug("(Proxy) accept failed with error: " + std::to_string(WSAGetLastError()));
break;
}
CSocket = std::make_shared<asio::ip::tcp::socket>(acceptor.accept());
debug("(Proxy) Game Connected!");
GConnected = true;
if (CServer) {
std::thread t1(NetMain, CSocket->remote_endpoint().address(), CSocket->remote_endpoint().port());
t1.detach();
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
CServer = false;
}
std::vector<char> data {};
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
// Read byte by byte until '>' is rcved then get the size and read based on it
while (!TCPTerminate && !CSocket) {
try {
ReceiveFromGame(*CSocket, data);
ServerSend(data, false);
} catch (const std::exception& e) {
error(std::string("Error while receiving from game: ") + e.what());
do {
Rcv = 0;
do {
Temp = recv(CSocket, &Header[Rcv], 1, 0);
if (Temp < 1 || TCPTerminate)
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;
}
}
std::string Ret(Size, 0);
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;
GConnected = false;
Terminate = true;
if (CSocket != nullptr)
if (ClientThread) {
debug("Waiting for client thread");
ClientThread->join();
debug("Client thread done");
}
if (NetMainThread) {
debug("Waiting for net main thread");
NetMainThread->join();
debug("Net main thread done");
}
if (CSocket != SOCKET_ERROR)
KillSocket(CSocket);
debug("END OF GAME SERVER");
}

View File

@@ -5,7 +5,6 @@
///
/// Created by Anonymous275 on 7/18/2020
///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "Http.h"
#include <Logger.h>
@@ -16,6 +15,9 @@
#include <iostream>
#include <mutex>
#include <nlohmann/json.hpp>
#include <Startup.h>
#include <Network/network.hpp>
#include <Utils.h>
void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try {
const std::filesystem::path folder = ".https_debug";
@@ -67,6 +69,10 @@ std::string HTTP::Get(const std::string& IP) {
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
cli.set_follow_location(true);
if (SkipSslVerify) {
debug("Skipping SSL server validation via --skip-ssl-verify");
cli.enable_server_certificate_verification(false);
}
auto res = cli.Get(IP.substr(pos).c_str(), ProgressBar);
std::string Ret;
@@ -75,14 +81,20 @@ std::string HTTP::Get(const std::string& IP) {
Ret = res->body;
} else {
WriteHttpDebug(cli, "GET", IP, res);
error("Failed to GET '" + IP + "': " + res->reason + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
error("Failed to GET (status " + std::to_string(res->status) + ") '" + IP + "': " + res->reason + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
}
} else {
if (isDownload) {
std::cout << "\n";
}
auto result = cli.get_openssl_verify_result();
std::string verify_error;
if (result) {
verify_error = X509_verify_cert_error_string(result);
}
WriteHttpDebug(cli, "GET", IP, res);
error("HTTP Get failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
error("HTTP Get failed on " + to_string(res.error()) + ", ssl verify = " + verify_error);
}
return Ret;
@@ -96,6 +108,10 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
if (SkipSslVerify) {
debug("Skipping SSL server validation via --skip-ssl-verify");
cli.enable_server_certificate_verification(false);
}
std::string Ret;
if (!Fields.empty()) {
@@ -118,8 +134,13 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
}
Ret = res->body;
} else {
auto result = cli.get_openssl_verify_result();
std::string verify_error;
if (result) {
verify_error = X509_verify_cert_error_string(result);
}
WriteHttpDebug(cli, "POST", IP, res);
error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + verify_error);
}
}
@@ -172,3 +193,127 @@ bool HTTP::Download(const std::string& IP, const std::string& Path) {
return true;
}
void set_headers(httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "*");
res.set_header("Access-Control-Request-Method", "POST, OPTIONS, GET");
res.set_header("Access-Control-Request-Headers", "X-API-Version");
}
void HTTP::StartProxy() {
std::thread proxy([&]() {
httplib::Server HTTPProxy;
httplib::Headers headers = {
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
{ "Accept", "*/*" }
};
httplib::Client backend("https://backend.beammp.com");
httplib::Client forum("https://forum.beammp.com");
const std::string pattern = ".*";
auto handle_request = [&](const httplib::Request& req, httplib::Response& res) {
set_headers(res);
if (req.has_header("X-BMP-Authentication")) {
headers.emplace("X-BMP-Authentication", PrivateKey);
}
if (req.has_header("X-API-Version")) {
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
}
const std::vector<std::string> path = Utils::Split(req.path, "/");
httplib::Result cli_res;
const std::string method = req.method;
std::string host = "";
if (!path.empty())
host = path[0];
if (host == "backend") {
std::string remaining_path = req.path.substr(std::strlen("/backend"));
if (method == "GET")
cli_res = backend.Get(remaining_path, headers);
else if (method == "POST")
cli_res = backend.Post(remaining_path, headers);
} else if (host == "avatar") {
bool error = false;
std::string username;
std::string avatar_size = "100";
if (path.size() > 1) {
username = path[1];
} else {
error = true;
}
if (path.size() > 2) {
try {
if (std::stoi(path[2]) > 0)
avatar_size = path[2];
} catch (std::exception&) {}
}
httplib::Result summary_res;
if (!error) {
summary_res = forum.Get("/u/" + username + ".json", headers);
if (!summary_res || summary_res->status != 200) {
error = true;
}
}
if (!error) {
try {
nlohmann::json d = nlohmann::json::parse(summary_res->body, nullptr, false); // can fail with parse_error
auto user = d.at("user"); // can fail with out_of_range
auto avatar_link_json = user.at("avatar_template"); // can fail with out_of_range
auto avatar_link = avatar_link_json.get<std::string>();
size_t start_pos = avatar_link.find("{size}");
if (start_pos != std::string::npos)
avatar_link.replace(start_pos, std::strlen("{size}"), avatar_size);
cli_res = forum.Get(avatar_link, headers);
} catch (std::exception&) {
error = true;
}
}
if (error) {
cli_res = forum.Get("/user_avatar/forum.beammp.com/user/0/0.png", headers);
}
} else {
res.set_content("Host not found", "text/plain");
return;
}
if (cli_res) {
res.set_content(cli_res->body, cli_res->get_header_value("Content-Type"));
} else {
res.set_content(to_string(cli_res.error()), "text/plain");
}
};
HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) {
handle_request(req, res);
});
HTTPProxy.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) {
handle_request(req, res);
});
ProxyPort = HTTPProxy.bind_to_any_port("0.0.0.0");
debug("HTTP Proxy listening on port " + std::to_string(ProxyPort));
HTTPProxy.listen_after_bind();
});
proxy.detach();
}

View File

@@ -7,7 +7,10 @@
///
#include "Network/network.hpp"
#include "fmt/core.h"
#include <chrono>
#include <iomanip>
#include <ios>
#include <mutex>
#if defined(_WIN32)
#include <ws2tcpip.h>
@@ -28,37 +31,21 @@
#include <filesystem>
#include <fstream>
#include <future>
#include <asio.hpp>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <Utils.h>
namespace fs = std::filesystem;
std::string ListOfMods;
std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
std::vector<std::string> Val;
size_t pos;
std::string token, s = String;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
if (!token.empty())
Val.push_back(token);
s.erase(0, pos + delimiter.length());
}
if (!s.empty())
Val.push_back(s);
return Val;
}
void CheckForDir() {
if (!fs::exists("Resources")) {
// Could we just use fs::create_directory instead?
#if defined(_WIN32)
_wmkdir(L"Resources");
#elif defined(__linux__)
fs::create_directory(L"Resources");
#endif
if (!fs::exists(CachingDirectory)) {
try {
fs::create_directories(CachingDirectory);
} catch (const std::exception& e) {
error(std::string("Failed to create caching directory: ") + e.what() + ". This is a fatal error. Please make sure to configure a directory which you have permission to create, read and write from/to.");
std::this_thread::sleep_for(std::chrono::seconds(3));
std::exit(1);
}
}
}
void WaitForConfirm() {
@@ -74,23 +61,27 @@ void Abord() {
info("Terminated!");
}
std::string Auth(asio::ip::tcp::socket& Sock) {
TCPSend(strtovec("VC" + GetVer()), Sock);
std::string Auth(SOCKET Sock) {
TCPSend("VC" + GetVer(), Sock);
auto Res = TCPRcv(Sock);
if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
Abord();
CoreSend("L");
return "";
}
TCPSend(strtovec(PublicKey), Sock);
if (Terminate)
TCPSend(PublicKey, Sock);
if (Terminate) {
CoreSend("L");
return "";
}
Res = TCPRcv(Sock);
if (Res.empty() || Res[0] != 'P') {
Abord();
CoreSend("L");
return "";
}
@@ -99,24 +90,28 @@ std::string Auth(asio::ip::tcp::socket& Sock) {
ClientID = std::stoi(Res);
} else {
Abord();
CoreSend("L");
UUl("Authentication failed!");
return "";
}
TCPSend(strtovec("SR"), Sock);
if (Terminate)
TCPSend("SR", Sock);
if (Terminate) {
CoreSend("L");
return "";
}
Res = TCPRcv(Sock);
if (Res[0] == 'E' || Res[0] == 'K') {
Abord();
CoreSend("L");
return "";
}
if (Res.empty() || Res == "-") {
info("Didn't Receive any mods...");
ListOfMods = "-";
TCPSend(strtovec("Done"), Sock);
CoreSend("L");
TCPSend("Done", Sock);
info("Done!");
return "";
}
@@ -130,54 +125,88 @@ void UpdateUl(bool D, const std::string& msg) {
UlStatus = "UlLoading Resource " + msg;
}
float DownloadSpeed = 0;
void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) {
do {
double pr = double(Rcv) / double(Size) * 100;
std::string Per = std::to_string(trunc(pr * 10) / 10);
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)");
std::string SpeedString = "";
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));
} while (!Terminate && Rcv < Size);
}
char* TCPRcvRaw(asio::ip::tcp::socket& Sock, uint64_t& GRcv, uint64_t Size) {
char* File = new char[Size];
// MICROSOFT, I DONT CARE, WRITE BETTER CODE
#undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return {};
}
std::vector<char> File(Size);
uint64_t Rcv = 0;
asio::error_code ec;
auto start = std::chrono::high_resolution_clock::now();
int i = 0;
do {
int Len = int(Size - Rcv);
if (Len > 1000000)
Len = 1000000;
int32_t Temp = asio::read(Sock, asio::buffer(&File[Rcv], Len), ec);
if (ec) {
::error(fmt::format("Failed to receive data from server: {}", ec.message()));
UUl("Failed to receive data from server, connection closed (Code 1)");
// receive at most some MB at a time
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if (Temp < 1) {
info(std::to_string(Temp));
UUl("Socket Closed Code 1");
KillSocket(Sock);
Terminate = true;
delete[] File;
return nullptr;
return {};
}
Rcv += 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);
return File;
}
void MultiKill(asio::ip::tcp::socket& Sock, asio::ip::tcp::socket& Sock1) {
void MultiKill(SOCKET Sock, SOCKET Sock1) {
KillSocket(Sock1);
KillSocket(Sock);
Terminate = true;
}
std::shared_ptr<asio::ip::tcp::socket> InitDSock(asio::ip::tcp::endpoint ep) {
auto DSock = std::make_shared<asio::ip::tcp::socket>(io);
asio::error_code ec;
DSock->connect(ep, ec);
if (ec) {
SOCKET InitDSock() {
SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
if (DSock < 1) {
KillSocket(DSock);
Terminate = true;
return nullptr;
return 0;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(LastPort);
inet_pton(AF_INET, LastIP.c_str(), &ServerAddr.sin_addr);
if (connect(DSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
KillSocket(DSock);
Terminate = true;
return 0;
}
char Code[2] = { 'D', char(ClientID) };
asio::write(*DSock, asio::buffer(Code, 2), ec);
if (ec) {
if (send(DSock, Code, 2, 0) != 2) {
KillSocket(DSock);
Terminate = true;
return 0;
@@ -185,44 +214,49 @@ std::shared_ptr<asio::ip::tcp::socket> InitDSock(asio::ip::tcp::endpoint ep) {
return DSock;
}
std::string MultiDownload(asio::ip::tcp::socket& MSock, asio::ip::tcp::socket& DSock, uint64_t Size, const std::string& Name) {
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
uint64_t GRcv = 0, MSize = Size / 2, DSize = Size - MSize;
uint64_t GRcv = 0;
std::thread Au(AsyncUpdate, std::ref(GRcv), Size, Name);
uint64_t MSize = Size / 2;
uint64_t DSize = Size - MSize;
std::packaged_task<char*()> task([&] { return TCPRcvRaw(MSock, GRcv, MSize); });
std::future<char*> f1 = task.get_future();
std::thread Dt(std::move(task));
Dt.detach();
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
char* DData = TCPRcvRaw(DSock, GRcv, DSize);
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, MSize);
if (!DData) {
if (MData.empty()) {
MultiKill(MSock, DSock);
return "";
}
f1.wait();
char* MData = f1.get();
if (!MData) {
MultiKill(MSock, DSock);
return "";
}
if (Au.joinable())
Terminate = true;
Au.join();
return {};
}
/// omg yes very ugly my god but i was in a rush will revisit
std::string Ret(Size, 0);
memcpy(&Ret[0], MData, MSize);
delete[] MData;
const std::vector<char> DData = TCPRcvRaw(DSock, GRcv, DSize);
memcpy(&Ret[MSize], DData, DSize);
delete[] DData;
if (DData.empty()) {
MultiKill(MSock, DSock);
Terminate = true;
Au.join();
return {};
}
return Ret;
// 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 {};
}
Au.join();
std::vector<char> Result{};
Result.insert(Result.begin(), MData.begin(), MData.end());
Result.insert(Result.end(), DData.begin(), DData.end());
return Result;
}
void InvalidResource(const std::string& File) {
@@ -231,31 +265,34 @@ void InvalidResource(const std::string& File) {
Terminate = true;
}
void SyncResources(asio::ip::tcp::socket& Sock) {
void SyncResources(SOCKET Sock) {
std::string Ret = Auth(Sock);
if (Ret.empty())
return;
if (!SecurityWarning())
return;
info("Checking Resources...");
CheckForDir();
std::vector<std::string> list = Split(Ret, ";");
std::vector<std::string> list = Utils::Split(Ret, ";");
std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2));
std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end());
list.clear();
Ret.clear();
int Amount = 0, Pos = 0;
std::string a, t;
std::string PathToSaveTo, t;
for (const std::string& name : FNames) {
if (!name.empty()) {
t += name.substr(name.find_last_of('/') + 1) + ";";
}
}
if (t.empty())
ListOfMods = "-";
CoreSend("L");
else
ListOfMods = t;
CoreSend("L" + t);
t.clear();
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/');
@@ -270,25 +307,27 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
}
if (!FNames.empty())
info("Syncing...");
auto DSock = InitDSock(Sock.remote_endpoint());
SOCKET DSock = InitDSock();
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/');
if (pos != std::string::npos) {
a = "Resources" + FN->substr(pos);
} else
PathToSaveTo = CachingDirectory + FN->substr(pos);
} else {
continue;
}
Pos++;
if (fs::exists(a)) {
auto FileSize = std::stoull(*FS);
if (fs::exists(PathToSaveTo)) {
if (FS->find_first_not_of("0123456789") != std::string::npos)
continue;
if (fs::file_size(a) == std::stoull(*FS)) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/')));
if (fs::file_size(PathToSaveTo) == FileSize) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
auto modname = a.substr(a.find_last_of('/'));
auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
@@ -297,7 +336,7 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
#endif
auto name = GetGamePath() + "mods/multiplayer" + modname;
auto tmp_name = name + ".tmp";
fs::copy_file(a, tmp_name, fs::copy_options::overwrite_existing);
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()));
@@ -307,12 +346,13 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
WaitForConfirm();
continue;
} else
remove(a.c_str());
remove(PathToSaveTo.c_str());
}
CheckForDir();
std::string FName = a.substr(a.find_last_of('/'));
std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
do {
TCPSend(strtovec("f" + *FN), Sock);
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + *FN, Sock);
std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) {
@@ -323,19 +363,23 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName;
Data = MultiDownload(Sock, *DSock, std::stoull(*FS), Name);
std::vector<char> DownloadedFile = MultiDownload(Sock, DSock, FileSize, Name);
if (Terminate)
break;
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName);
std::ofstream LFS;
LFS.open(a.c_str(), std::ios_base::app | std::ios::binary);
if (LFS.is_open()) {
LFS.write(&Data[0], Data.size());
LFS.close();
}
} while (fs::file_size(a) != std::stoull(*FS) && !Terminate);
// 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
}
// 2. verify size
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
Terminate = true;
}
} while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate);
if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
@@ -348,13 +392,13 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
}
#endif
fs::copy_file(a, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
}
WaitForConfirm();
}
KillSocket(DSock);
if (!Terminate) {
TCPSend(strtovec("Done"), Sock);
TCPSend("Done", Sock);
info("Done!");
} else {
UlStatus = "Ulstart";

View File

@@ -7,8 +7,7 @@
///
#include "Network/network.hpp"
#include "Zlib/Compressor.h"
#include "asio/ip/address.hpp"
#include "fmt/format.h"
#include <stdexcept>
#if defined(_WIN32)
#include <ws2tcpip.h>
@@ -25,78 +24,84 @@
#include <array>
#include <string>
std::shared_ptr<asio::ip::udp::socket> UDPSock = nullptr;
SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr;
void UDPSend(const std::vector<char>& RawData) {
if (ClientID == -1 || UDPSock == nullptr)
void UDPSend(std::string Data) {
if (ClientID == -1 || UDPSock == -1)
return;
std::string Data;
if (Data.size() > 400) {
auto res = Comp(RawData);
if (Data.length() > 400) {
auto res = Comp(std::span<char>(Data.data(), Data.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;
int sendOk = UDPSock->send(asio::buffer(Packet));
int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer));
if (sendOk == SOCKET_ERROR)
error("Error Code : " + std::to_string(WSAGetLastError()));
}
void SendLarge(const std::vector<char>& Data) {
if (Data.size() > 400) {
auto res = Comp(Data);
res.insert(res.begin(), { 'A', 'B', 'G', ':' });
if (!TCPSock) {
::debug("TCPSock is null");
return;
}
TCPSend(res, *TCPSock);
} else {
if (!TCPSock) {
::debug("TCPSock is null");
return;
}
TCPSend(Data, *TCPSock);
void SendLarge(std::string Data) {
if (Data.length() > 400) {
auto res = Comp(std::span<char>(Data.data(), Data.size()));
Data = "ABG:" + std::string(res.data(), res.size());
}
TCPSend(Data, TCPSock);
}
void UDPParser(std::string_view Packet) {
if (Packet.substr(0, 4) == "ABG:") {
auto substr = Packet.substr(4);
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket);
try {
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket);
} catch (const std::runtime_error& err) {
error("Error in decompression of UDP, ignoring");
}
} else {
ServerParser(Packet);
}
}
void UDPRcv() {
sockaddr_in FromServer {};
#if defined(_WIN32)
int clientLength = sizeof(FromServer);
#elif defined(__linux__)
socklen_t clientLength = sizeof(FromServer);
#endif
ZeroMemory(&FromServer, clientLength);
static thread_local std::array<char, 10240> Ret {};
if (UDPSock == nullptr) {
::debug("UDPSock is null");
if (UDPSock == -1)
return;
}
asio::error_code ec;
int32_t Rcv = UDPSock->receive(asio::buffer(Ret.data(), Ret.size() - 1), 0, ec);
if (ec)
int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
if (Rcv == SOCKET_ERROR)
return;
Ret[Rcv] = 0;
UDPParser(std::string_view(Ret.data(), Rcv));
}
void UDPClientMain(asio::ip::address addr, uint16_t port) {
UDPSock = std::make_shared<asio::ip::udp::socket>(io);
asio::error_code ec;
UDPSock->connect(asio::ip::udp::endpoint(addr, port), ec);
if (ec) {
::error(fmt::format("Failed to connect UDP to server: {}", ec.message()));
Terminate = true;
void UDPClientMain(const std::string& IP, int Port) {
#ifdef _WIN32
WSADATA data;
if (WSAStartup(514, &data)) {
error("Can't start Winsock!");
return;
}
#endif
delete ToServer;
ToServer = new sockaddr_in;
ToServer->sin_family = AF_INET;
ToServer->sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
GameSend("P" + std::to_string(ClientID));
TCPSend(strtovec("H"), *TCPSock);
UDPSend(strtovec("p"));
while (!Terminate)
TCPSend("H", TCPSock);
UDPSend("p");
debug("Starting UDP receive loop");
while (!Terminate) {
UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock);
WSACleanup();
}

View File

@@ -7,7 +7,6 @@
///
#include "Logger.h"
#include "fmt/format.h"
#include <Zlib/Compressor.h>
#include <chrono>
#include <iostream>
@@ -28,7 +27,7 @@
int LastPort;
std::string LastIP;
std::shared_ptr<asio::ip::tcp::socket> TCPSock = nullptr;
SOCKET TCPSock = -1;
bool CheckBytes(int32_t Bytes) {
if (Bytes == 0) {
@@ -47,8 +46,8 @@ void UUl(const std::string& R) {
UlStatus = "UlDisconnected: " + R;
}
void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock) {
if (!Sock.is_open()) {
void TCPSend(const std::string& Data, uint64_t Sock) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return;
@@ -58,44 +57,64 @@ void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock) {
std::string Send(4, 0);
Size = int32_t(Data.size());
memcpy(&Send[0], &Size, sizeof(Size));
Send += std::string(Data.data(), Data.size());
Send += Data;
// Do not use Size before this point for anything but the header
Sent = 0;
Size += 4;
asio::error_code ec;
asio::write(Sock, asio::buffer(Send), ec);
if (ec) {
UUl(fmt::format("Failed to send data: {}", ec.message()));
}
do {
if (size_t(Sent) >= Send.size()) {
error("string OOB in " + std::string(__func__));
UUl("TCP Send OOB");
return;
}
Temp = send(Sock, &Send[Sent], Size - Sent, 0);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 2");
return;
}
Sent += Temp;
} while (Sent < Size);
}
std::string TCPRcv(asio::ip::tcp::socket& Sock) {
if (!Sock.is_open()) {
std::string TCPRcv(SOCKET Sock) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return "";
}
int32_t Header, BytesRcv = 0, Temp;
int32_t Header, Temp;
std::vector<char> Data(sizeof(Header));
asio::error_code ec;
asio::read(Sock, asio::buffer(Data), ec);
if (ec) {
UUl(fmt::format("Failed to receive header: {}", ec.message()));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 3");
return "";
}
memcpy(&Header, &Data[0], sizeof(Header));
memcpy(&Header, Data.data(), sizeof(Header));
Data.resize(Header);
asio::read(Sock, asio::buffer(Data), ec);
if (ec) {
UUl(fmt::format("Failed to receive data: {}", ec.message()));
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 4");
return "";
}
Data.resize(Header, 0);
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 5");
return "";
}
std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") {
auto substr = Ret.substr(4);
auto res = DeComp(strtovec(substr));
Ret = std::string(res.data(), res.size());
try {
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
Ret = std::string(res.data(), res.size());
} catch (const std::runtime_error& err) {
// this happens e.g. when we're out of memory, or when we get incomplete data
error("Decompression failed");
return "";
}
}
#ifdef DEBUG
@@ -106,24 +125,51 @@ std::string TCPRcv(asio::ip::tcp::socket& Sock) {
return Ret;
}
void TCPClientMain(asio::ip::tcp::socket&& socket) {
if (!TCPSock) {
return;
}
LastIP = socket.remote_endpoint().address().to_string();
LastPort = socket.remote_endpoint().port();
void TCPClientMain(const std::string& IP, int Port) {
LastIP = IP;
LastPort = Port;
SOCKADDR_IN ServerAddr;
int RetCode;
TCPSock = std::make_shared<asio::ip::tcp::socket>(std::move(socket));
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(514, &wsaData); // 2.2
#endif
TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (TCPSock == -1) {
printf("Client: socket failed! Error code: %d\n", WSAGetLastError());
WSACleanup();
return;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
RetCode = connect(TCPSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
if (RetCode != 0) {
UlStatus = "UlConnection Failed!";
error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock);
WSACleanup();
Terminate = true;
CoreSend("L");
return;
}
info("Connected!");
char Code = 'C';
asio::write(*TCPSock, asio::buffer(&Code, 1));
SyncResources(*TCPSock);
while (!Terminate && TCPSock) {
ServerParser(TCPRcv(*TCPSock));
send(TCPSock, &Code, 1, 0);
SyncResources(TCPSock);
while (!Terminate) {
ServerParser(TCPRcv(TCPSock));
}
GameSend("T");
KillSocket(TCPSock);
////Game Send Terminate
if (KillSocket(TCPSock) != 0)
debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError()));
#ifdef _WIN32
if (WSACleanup() != 0)
debug("(TCP) Client: WSACleanup() failed!...");
#endif
}

View File

@@ -1,35 +0,0 @@
#include "NetworkHelpers.h"
#include <array>
#include <cerrno>
#include <cstring>
#include <stdexcept>
using asio::ip::tcp;
static uint32_t RecvHeader(tcp::socket& socket) {
std::array<uint8_t, sizeof(uint32_t)> header_buffer {};
asio::error_code ec;
auto n = asio::read(socket, asio::buffer(header_buffer), ec);
if (ec) {
throw std::runtime_error(std::string("recv() of header failed: ") + ec.message());
}
if (n == 0) {
throw std::runtime_error("Game disconnected");
}
return *reinterpret_cast<uint32_t*>(header_buffer.data());
}
/// Throws!!!
void ReceiveFromGame(tcp::socket& socket, std::vector<char>& out_data) {
auto header = RecvHeader(socket);
out_data.resize(header);
asio::error_code ec;
auto n = asio::read(socket, asio::buffer(out_data), ec);
if (ec) {
throw std::runtime_error(std::string("recv() of data failed: ") + ec.message());
}
if (n == 0) {
throw std::runtime_error("Game disconnected");
}
}

View File

@@ -17,7 +17,6 @@
#endif
#include "Logger.h"
#include <fstream>
#include <sstream>
#include <string>
#include <thread>
@@ -34,24 +33,8 @@ void lowExit(int code) {
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(2);
}
/*void Exit(int code){
TraceBack = 0;
std::string msg =
"Sorry. We do not support cracked copies report this if you believe this is a mistake code ";
error(msg+std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(3);
}
void SteamExit(int code){
TraceBack = 0;
std::string msg =
"Illegal steam modifications detected report this if you believe this is a mistake code ";
error(msg+std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(4);
}*/
std::string GetGameDir() {
// if(TraceBack != 4)Exit(0);
#if defined(_WIN32)
return GameDir.substr(0, GameDir.find_last_of('\\'));
#elif defined(__linux__)
@@ -175,151 +158,7 @@ void FileList(std::vector<std::string>& a, const std::string& Path) {
}
}
}
bool Find(const std::string& FName, const std::string& Path) {
std::vector<std::string> FS;
FileList(FS, Path + "\\userdata");
for (std::string& a : FS) {
if (a.find(FName) != std::string::npos) {
FS.clear();
return true;
}
}
FS.clear();
return false;
}
bool FindHack(const std::string& Path) {
bool s = true;
for (const auto& entry : fs::directory_iterator(Path)) {
std::string Name = entry.path().filename().string();
for (char& c : Name)
c = char(tolower(c));
if (Name == "steam.exe")
s = false;
if (Name.find("greenluma") != -1) {
error("Found malicious file/folder \"" + Name + "\"");
return true;
}
Name.clear();
}
return s;
}
std::vector<std::string> GetID(const std::string& log) {
std::string vec, t, r;
std::vector<std::string> Ret;
std::ifstream f(log.c_str(), std::ios::binary);
f.seekg(0, std::ios_base::end);
std::streampos fileSize = f.tellg();
vec.resize(size_t(fileSize) + 1);
f.seekg(0, std::ios_base::beg);
f.read(&vec[0], fileSize);
f.close();
std::stringstream ss(vec);
bool S = false;
while (std::getline(ss, t, '{')) {
if (!S)
S = true;
else {
for (char& c : t) {
if (isdigit(c))
r += c;
}
break;
}
}
Ret.emplace_back(r);
r.clear();
S = false;
bool L = true;
while (std::getline(ss, t, '}')) {
if (L) {
L = false;
continue;
}
for (char& c : t) {
if (c == '"') {
if (!S)
S = true;
else {
if (r.length() > 10) {
Ret.emplace_back(r);
}
r.clear();
S = false;
continue;
}
}
if (isdigit(c))
r += c;
}
}
vec.clear();
return Ret;
}
std::string GetManifest(const std::string& Man) {
std::string vec;
std::ifstream f(Man.c_str(), std::ios::binary);
f.seekg(0, std::ios_base::end);
std::streampos fileSize = f.tellg();
vec.resize(size_t(fileSize) + 1);
f.seekg(0, std::ios_base::beg);
f.read(&vec[0], fileSize);
f.close();
std::string ToFind = "\"LastOwner\"\t\t\"";
int pos = int(vec.find(ToFind));
if (pos != -1) {
pos += int(ToFind.length());
vec = vec.substr(pos);
return vec.substr(0, vec.find('\"'));
} else
return "";
}
bool IDCheck(std::string Man, std::string steam) {
bool a = false, b = true;
int pos = int(Man.rfind("steamapps"));
// if(pos == -1)Exit(5);
Man = Man.substr(0, pos + 9) + "\\appmanifest_284160.acf";
steam += "\\config\\loginusers.vdf";
if (fs::exists(Man) && fs::exists(steam)) {
for (const std::string& ID : GetID(steam)) {
if (ID == GetManifest(Man))
b = false;
}
// if(b)Exit(6);
} else
a = true;
return a;
}
void LegitimacyCheck() {
// std::string K1 = R"(Software\Valve\Steam)";
// std::string K2 = R"(Software\Valve\Steam\Apps\284160)";
/*LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K1.c_str(), &hKey);
if(dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 1);
if(Result.empty())Exit(1);
if(fs::exists(Result)){
if(!Find("284160.json",Result))Exit(2);
if(FindHack(Result))SteamExit(1);
}else Exit(3);
T = Result;
Result.clear();
TraceBack++;
}else Exit(4);
K1.clear();
RegCloseKey(hKey);
dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K2.c_str(), &hKey);
if(dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 2);
if(Result.empty())lowExit(1);
TraceBack++;
}else lowExit(2);
K2.clear();
RegCloseKey(hKey);*/
#if defined(_WIN32)
std::string Result;
std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
@@ -327,17 +166,18 @@ RegCloseKey(hKey);*/
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
if (dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 3);
if (Result.empty())
if (Result.empty()) {
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(3);
// if(IDCheck(Result,T))lowExit(5);
}
GameDir = Result;
// TraceBack++;
} else
} else {
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(4);
}
K3.clear();
Result.clear();
RegCloseKey(hKey);
// if(TraceBack < 3)exit(-1);
#elif defined(__linux__)
struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir;

View File

@@ -18,6 +18,7 @@ std::string PrivateKey;
extern bool LoginAuth;
extern std::string Username;
extern std::string UserRole;
extern int UserID;
void UpdateKey(const char* newKey) {
if (newKey && std::isalnum(newKey[0])) {
@@ -48,6 +49,7 @@ std::string Login(const std::string& fields) {
if (fields == "LO") {
Username = "";
UserRole = "";
UserID = -1;
LoginAuth = false;
UpdateKey(nullptr);
return "";
@@ -74,6 +76,9 @@ std::string Login(const std::string& fields) {
if (d.contains("role")) {
UserRole = d["role"].get<std::string>();
}
if (d.contains("id")) {
UserID = d["id"].get<int>();
}
if (d.contains("private_key")) {
UpdateKey(d["private_key"].get<std::string>().c_str());
}
@@ -129,6 +134,9 @@ void CheckLocalKey() {
if (d.contains("role")) {
UserRole = d["role"].get<std::string>();
}
if (d.contains("id")) {
UserID = d["id"].get<int>();
}
// info(Role);
} else {
info("Auto-Authentication unsuccessful please re-login!");

View File

@@ -84,7 +84,7 @@ std::string GetVer() {
return "2.1";
}
std::string GetPatch() {
return ".0";
return ".4";
}
std::string GetEP(char* P) {
@@ -101,6 +101,7 @@ void ReLaunch(int argc, char* args[]) {
Arg += " ";
Arg += args[c - 1];
}
info("Relaunch!");
system("cls");
ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0);
@@ -125,6 +126,7 @@ void ReLaunch(int argc, char* args[]) {
Arg += " ";
Arg += args[c - 1];
}
info("Relaunch!");
system("clear");
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -167,12 +169,8 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
#if defined(_WIN32)
#elif defined(__linux__)
system("clear");
#endif
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion))) && !Dev) {
info("Launcher update found!");
#if defined(__linux__)
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
@@ -204,13 +202,6 @@ void CustomPort(int argc, char* argv[]) {
if (argc > 2)
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
@@ -244,7 +235,6 @@ void LinuxPatch() {
#if defined(_WIN32)
void InitLauncher(int argc, char* argv[]) {
system("cls");
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog();
CheckName(argc, argv);
@@ -256,21 +246,13 @@ void InitLauncher(int argc, char* argv[]) {
}
#elif defined(__linux__)
void InitLauncher(int argc, char* argv[]) {
system("clear");
InitLog();
info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName(argc, argv);
CheckLocalKey();
ConfigInit();
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
@@ -332,8 +314,7 @@ void PreGame(const std::string& GamePath) {
info("Game Version : " + GameVer);
CheckMP(GetGamePath() + "mods/multiplayer");
info("Game user path: '" + GetGamePath() + "'");
info("Game user path: " + GetGamePath());
if (!Dev) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
@@ -374,59 +355,3 @@ 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

@@ -20,26 +20,51 @@
}
}
int main(int argc, char* argv[]) {
int main(int argc, char** argv) try {
#ifdef DEBUG
std::thread th(flush);
th.detach();
#endif
#if defined(_WIN32)
system("cls");
#elif defined(__linux__)
system("clear");
#endif
GetEP(argv[0]);
for (int i = 0; i < argc; ++i) {
if (std::string_view(argv[i]) == "--skip-ssl-verify") {
info("SSL verification skip enabled");
HTTP::SkipSslVerify = true;
}
}
InitLauncher(argc, argv);
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
try {
LegitimacyCheck();
} catch (std::exception& e) {
fatal("Main 1 : " + std::string(e.what()));
error("Failure in LegitimacyCheck: " + std::string(e.what()));
throw;
}
StartProxy();
try {
HTTP::StartProxy();
} catch (const std::exception& e) {
error(std::string("Failed to start HTTP proxy: Some in-game functions may not work. Error: ") + e.what());
}
PreGame(GetGameDir());
InitGame(GetGameDir());
CoreNetwork();
/// TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...)
} catch (const std::exception& e) {
error(std::string("Exception in main(): ") + e.what());
info("Closing in 5 seconds");
info("If this keeps happening, contact us on either: Forum: https://forum.beammp.com, Discord: https://discord.gg/beammp");
std::this_thread::sleep_for(std::chrono::seconds(5));
}

View File

@@ -3,8 +3,6 @@
"cpp-httplib",
"nlohmann-json",
"zlib",
"openssl",
"asio",
"fmt"
"openssl"
]
}