mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2026-04-04 06:46:06 +00:00
Compare commits
13 Commits
fix-resour
...
ipv6-suppo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f65fbf60fe | ||
|
|
188a31c69e | ||
|
|
caab92375d | ||
|
|
b76930b0bd | ||
|
|
96d579f64b | ||
|
|
ba35d039ae | ||
|
|
3535923f40 | ||
|
|
3ca6e7fd3d | ||
|
|
7c24020124 | ||
|
|
88a9d4a1b1 | ||
|
|
4a5728d421 | ||
|
|
137d9dd1e2 | ||
|
|
7b733bf8eb |
2
.github/workflows/cmake-linux.yml
vendored
2
.github/workflows/cmake-linux.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: cmake --build . --config $BUILD_TYPE
|
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
|
||||||
|
|||||||
2
.github/workflows/cmake-windows.yml
vendored
2
.github/workflows/cmake-windows.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
run: cmake --build . --config $BUILD_TYPE
|
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
104
.github/workflows/release-build.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
name: Release Create & Build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Sequence of patterns matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Create Release
|
||||||
|
outputs:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
steps:
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: Release ${{ github.ref }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
body: |
|
||||||
|
Files included in this release:
|
||||||
|
- `BeamMP-Launcher.exe` windows build
|
||||||
|
- `BeamMP-Launcher` linux build
|
||||||
|
|
||||||
|
upload-release-files-windows:
|
||||||
|
name: Upload Windows Release Files
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: create-release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Restore artifacts, or run vcpkg, build and cache artifacts
|
||||||
|
uses: lukka/run-vcpkg@main
|
||||||
|
id: runvcpkg
|
||||||
|
with:
|
||||||
|
vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
|
||||||
|
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||||
|
vcpkgGitCommitId: '16ee2ecb31788c336ace8bb14c21801efb6836e4'
|
||||||
|
vcpkgTriplet: 'x64-windows-static'
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{github.workspace}}/build-windows
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{github.workspace}}/build-windows
|
||||||
|
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{github.workspace}}/build-windows
|
||||||
|
shell: bash
|
||||||
|
run: cmake --build . --config $BUILD_TYPE
|
||||||
|
|
||||||
|
- name: Upload Release Asset
|
||||||
|
id: upload-release-asset
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
asset_path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe
|
||||||
|
asset_name: BeamMP-Launcher.exe
|
||||||
|
asset_content_type: application/vnd.microsoft.portable-executable
|
||||||
|
|
||||||
|
upload-release-files-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{github.workspace}}/build-linux
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{github.workspace}}/build-linux
|
||||||
|
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{github.workspace}}/build-linux
|
||||||
|
shell: bash
|
||||||
|
run: cmake --build . --config $BUILD_TYPE
|
||||||
|
|
||||||
|
- name: Upload Release Asset
|
||||||
|
id: upload-release-asset
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||||
|
asset_path: ${{github.workspace}}/build-linux/BeamMP-Launcher
|
||||||
|
asset_name: BeamMP-Launcher
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
@@ -12,12 +12,11 @@ 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)
|
find_package(asio CONFIG REQUIRED)
|
||||||
|
find_package(fmt CONFIG 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 +25,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 asio::asio fmt::fmt)
|
||||||
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 asio::asio fmt::fmt)
|
||||||
else(WIN32) #MINGW
|
elseif (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 asio::asio fmt::fmt)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE "include")
|
target_include_directories(${PROJECT_NAME} PRIVATE "include")
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -2,24 +2,11 @@
|
|||||||
|
|
||||||
The launcher is the way we communitcate to outside the game, it does a few automated actions such as but not limited to: downloading the mod, launching the game, and create a connection to a server.
|
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
11
include/Helpers.h
Normal 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);
|
||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,10 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include "asio/io_context.hpp"
|
||||||
|
#include "asio/ip/address.hpp"
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@@ -14,8 +18,13 @@
|
|||||||
#include <bits/types/siginfo_t.h>
|
#include <bits/types/siginfo_t.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <sys/ucontext.h>
|
#include <sys/ucontext.h>
|
||||||
|
#include <vector>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <asio.hpp>
|
||||||
|
|
||||||
|
extern asio::io_context io;
|
||||||
|
|
||||||
void NetReset();
|
void NetReset();
|
||||||
extern bool Dev;
|
extern bool Dev;
|
||||||
extern int ping;
|
extern int ping;
|
||||||
@@ -27,30 +36,31 @@ extern int LastPort;
|
|||||||
extern bool ModLoaded;
|
extern bool ModLoaded;
|
||||||
extern bool Terminate;
|
extern bool Terminate;
|
||||||
extern int DEFAULT_PORT;
|
extern int DEFAULT_PORT;
|
||||||
extern uint64_t UDPSock;
|
extern std::shared_ptr<asio::ip::udp::socket> UDPSock;
|
||||||
extern uint64_t TCPSock;
|
extern std::shared_ptr<asio::ip::tcp::socket> 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;
|
||||||
int KillSocket(uint64_t Dead);
|
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);
|
||||||
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(asio::ip::tcp::socket& Sock);
|
||||||
void SyncResources(uint64_t TCPSock);
|
void SyncResources(asio::ip::tcp::socket& 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, asio::ip::tcp::socket& Sock);
|
||||||
void TCPClientMain(const std::string& IP, int Port);
|
void TCPClientMain(asio::ip::tcp::socket&& Socket);
|
||||||
void UDPClientMain(const std::string& IP, int Port);
|
void UDPClientMain(asio::ip::address addr, uint16_t port);
|
||||||
void TCPGameServer(const std::string& IP, int Port);
|
void TCPGameServer(asio::ip::tcp::socket&& Socket);
|
||||||
bool SecurityWarning();
|
|
||||||
void CoreSend(std::string data);
|
|
||||||
|
|||||||
6
include/NetworkHelpers.h
Normal file
6
include/NetworkHelpers.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void ReceiveFromGame(asio::ip::tcp::socket& socket, std::vector<char>& out_data);
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
9
src/Helpers.cpp
Normal 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());
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,17 @@
|
|||||||
///
|
///
|
||||||
#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 <asio/io_context.hpp>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <optional>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#if defined(_WIN32)
|
#if defined(__linux__)
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#elif defined(__linux__)
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <spawn.h>
|
#include <spawn.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -27,6 +26,7 @@
|
|||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "Startup.h"
|
#include "Startup.h"
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <fmt/format.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -39,93 +39,122 @@ 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() {
|
asio::io_context io {};
|
||||||
confirmed = -1;
|
|
||||||
CoreSend("WMODS_FOUND");
|
|
||||||
|
|
||||||
while (confirmed == -1)
|
static asio::ip::tcp::socket ResolveAndConnect(const std::string& host_port_string) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
|
|
||||||
if (confirmed == 1)
|
using namespace asio;
|
||||||
return true;
|
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;
|
||||||
|
|
||||||
NetReset();
|
UlStatus = "UlLoading...";
|
||||||
Terminate = true;
|
|
||||||
TCPTerminate = true;
|
|
||||||
ping = -1;
|
|
||||||
|
|
||||||
return false;
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartSync(const std::string& Data) {
|
void StartSync(const std::string& Data) {
|
||||||
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
|
try {
|
||||||
if (IP.find('.') == -1) {
|
auto Socket = ResolveAndConnect(Data.substr(1));
|
||||||
if (IP == "DNS")
|
ListOfMods = "-";
|
||||||
UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
|
CheckLocalKey();
|
||||||
else
|
TCPTerminate = false;
|
||||||
UlStatus = "UlConnection Failed! (WSA failed to start)";
|
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();
|
||||||
Terminate = true;
|
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) {
|
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, asio::ip::tcp::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 +166,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 +194,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 +207,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 +234,39 @@ 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.is_open()) {
|
||||||
int res = send(CSocket, (Data + "\n").c_str(), int(Data.size()) + 1, 0);
|
uint32_t DataSize = OutData.size();
|
||||||
if (res < 0) {
|
std::vector<char> ToSend(sizeof(DataSize) + OutData.size());
|
||||||
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
|
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void GameHandler(SOCKET Client) {
|
void GameHandler(asio::ip::tcp::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);
|
||||||
}
|
}
|
||||||
@@ -289,63 +282,52 @@ void localRes() {
|
|||||||
}
|
}
|
||||||
void CoreMain() {
|
void CoreMain() {
|
||||||
debug("Core Network on start!");
|
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
|
|
||||||
|
|
||||||
ZeroMemory(&hints, sizeof(hints));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
hints.ai_family = AF_INET;
|
auto acceptor = asio::ip::tcp::acceptor(io, listen_ep);
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
acceptor.listen(asio::ip::tcp::socket::max_listen_connections, ec);
|
||||||
hints.ai_protocol = IPPROTO_TCP;
|
if (ec) {
|
||||||
hints.ai_flags = AI_PASSIVE;
|
::error(fmt::format("listen() failed, which is needed for the launcher to operate. Error: {}",
|
||||||
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res);
|
ec.message()));
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
CSocket = accept(LSocket, nullptr, nullptr);
|
auto CSocket = acceptor.accept(ec);
|
||||||
if (CSocket == -1) {
|
if (ec) {
|
||||||
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
|
error(fmt::format("(Core) accept failed with error: {}", ec.message()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
localRes();
|
localRes();
|
||||||
info("Game Connected!");
|
info("Game Connected!");
|
||||||
GameHandler(CSocket);
|
GameHandler(CSocket);
|
||||||
warn("Game Reconnecting...");
|
warn("Game Reconnecting...");
|
||||||
} while (CSocket);
|
} while (LSocket.is_open());
|
||||||
KillSocket(LSocket);
|
KillSocket(LSocket);
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,13 @@
|
|||||||
///
|
///
|
||||||
/// 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 "asio/socket_base.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -23,6 +28,7 @@
|
|||||||
|
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <fmt/format.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -30,20 +36,31 @@
|
|||||||
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
|
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
|
||||||
bool GConnected = false;
|
bool GConnected = false;
|
||||||
bool CServer = true;
|
bool CServer = true;
|
||||||
SOCKET CSocket = -1;
|
std::shared_ptr<asio::ip::tcp::socket> CSocket = nullptr;
|
||||||
SOCKET GSocket = -1;
|
std::shared_ptr<asio::ip::tcp::socket> GSocket = nullptr;
|
||||||
|
|
||||||
int KillSocket(uint64_t Dead) {
|
void KillSocket(std::shared_ptr<asio::ip::tcp::socket>& Dead) {
|
||||||
if (Dead == (SOCKET)-1) {
|
if (!Dead)
|
||||||
debug("Kill socket got -1 returning...");
|
return;
|
||||||
return 0;
|
asio::error_code ec;
|
||||||
}
|
Dead->shutdown(asio::socket_base::shutdown_both, ec);
|
||||||
shutdown(Dead, SD_BOTH);
|
}
|
||||||
int a = closesocket(Dead);
|
|
||||||
if (a != 0) {
|
void KillSocket(std::shared_ptr<asio::ip::udp::socket>& Dead) {
|
||||||
warn("Failed to close socket!");
|
if (!Dead)
|
||||||
}
|
return;
|
||||||
return a;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckBytes(uint32_t Bytes) {
|
bool CheckBytes(uint32_t Bytes) {
|
||||||
@@ -57,42 +74,34 @@ 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 == nullptr)
|
||||||
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
|
|
||||||
if (Size > 1000) {
|
asio::error_code ec;
|
||||||
debug("Launcher -> game (" + std::to_string(Size) + ")");
|
asio::write(*CSocket, asio::buffer(Data), ec);
|
||||||
}
|
if (ec) {
|
||||||
#endif
|
debug(fmt::format("(TCP CB) recv failed with error: {}", ec.message()));
|
||||||
do {
|
KillSocket(TCPSock);
|
||||||
if (Sent > -1) {
|
Terminate = true;
|
||||||
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(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')
|
||||||
@@ -104,18 +113,10 @@ void ServerSend(std::string Data, bool Rel) {
|
|||||||
if (Ack || Rel) {
|
if (Ack || Rel) {
|
||||||
if (Ack || DLen > 1000)
|
if (Ack || DLen > 1000)
|
||||||
SendLarge(Data);
|
SendLarge(Data);
|
||||||
else
|
else if (TCPSock)
|
||||||
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() {
|
||||||
@@ -124,76 +125,23 @@ void NetReset() {
|
|||||||
Terminate = false;
|
Terminate = false;
|
||||||
UlStatus = "Ulstart";
|
UlStatus = "Ulstart";
|
||||||
MStatus = " ";
|
MStatus = " ";
|
||||||
if (UDPSock != (SOCKET)(-1)) {
|
if (UDPSock != nullptr) {
|
||||||
debug("Terminating UDP Socket: " + std::to_string(TCPSock));
|
KillSocket(*UDPSock);
|
||||||
KillSocket(UDPSock);
|
|
||||||
}
|
}
|
||||||
UDPSock = -1;
|
UDPSock = nullptr;
|
||||||
if (TCPSock != (SOCKET)(-1)) {
|
if (TCPSock != nullptr) {
|
||||||
debug("Terminating TCP Socket: " + std::to_string(TCPSock));
|
KillSocket(*TCPSock);
|
||||||
KillSocket(TCPSock);
|
|
||||||
}
|
}
|
||||||
TCPSock = -1;
|
TCPSock = nullptr;
|
||||||
if (GSocket != (SOCKET)(-1)) {
|
if (GSocket != nullptr) {
|
||||||
debug("Terminating GTCP Socket: " + std::to_string(GSocket));
|
KillSocket(*GSocket);
|
||||||
KillSocket(GSocket);
|
|
||||||
}
|
}
|
||||||
GSocket = -1;
|
GSocket = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
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));
|
||||||
}
|
}
|
||||||
@@ -225,19 +173,47 @@ void ParserAsync(std::string_view Data) {
|
|||||||
void ServerParser(std::string_view Data) {
|
void ServerParser(std::string_view Data) {
|
||||||
ParserAsync(Data);
|
ParserAsync(Data);
|
||||||
}
|
}
|
||||||
void NetMain(const std::string& IP, int Port) {
|
void NetMain(asio::ip::address addr, uint16_t port) {
|
||||||
std::thread Ping(AutoPing);
|
std::thread Ping(AutoPing);
|
||||||
Ping.detach();
|
Ping.detach();
|
||||||
UDPClientMain(IP, Port);
|
UDPClientMain(addr, port);
|
||||||
CServer = true;
|
CServer = true;
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
info("Connection Terminated!");
|
info("Connection Terminated!");
|
||||||
}
|
}
|
||||||
void TCPGameServer(const std::string& IP, int Port) {
|
void TCPGameServer(asio::ip::tcp::socket&& Socket) {
|
||||||
GSocket = SetupListener();
|
asio::ip::tcp::endpoint listen_ep(asio::ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(DEFAULT_PORT + 1));
|
||||||
std::unique_ptr<std::thread> ClientThread {};
|
asio::ip::tcp::socket listener(io);
|
||||||
std::unique_ptr<std::thread> NetMainThread {};
|
asio::error_code ec;
|
||||||
while (!TCPTerminate && GSocket != -1) {
|
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()) {
|
||||||
debug("MAIN LOOP OF GAME SERVER");
|
debug("MAIN LOOP OF GAME SERVER");
|
||||||
GConnected = false;
|
GConnected = false;
|
||||||
if (!CServer) {
|
if (!CServer) {
|
||||||
@@ -248,70 +224,35 @@ 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, std::move(Socket));
|
||||||
}
|
Client.detach();
|
||||||
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!");
|
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, CSocket->remote_endpoint().address(), CSocket->remote_endpoint().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 {
|
while (!TCPTerminate && !CSocket) {
|
||||||
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);
|
}
|
||||||
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) {
|
if (CSocket != nullptr)
|
||||||
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);
|
KillSocket(CSocket);
|
||||||
debug("END OF GAME SERVER");
|
debug("END OF GAME SERVER");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Ret;
|
|
||||||
|
if (Ret.empty())
|
||||||
|
return "-1";
|
||||||
|
else
|
||||||
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,13 +7,7 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
#include "Network/network.hpp"
|
#include "Network/network.hpp"
|
||||||
#include <chrono>
|
#include "fmt/core.h"
|
||||||
#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,29 +22,43 @@
|
|||||||
|
|
||||||
#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>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <future>
|
#include <future>
|
||||||
|
#include <asio.hpp>
|
||||||
#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() {
|
||||||
@@ -66,27 +74,23 @@ void Abord() {
|
|||||||
info("Terminated!");
|
info("Terminated!");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Auth(SOCKET Sock) {
|
std::string Auth(asio::ip::tcp::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 +99,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,88 +130,54 @@ 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(asio::ip::tcp::socket& Sock, uint64_t& GRcv, uint64_t Size) {
|
||||||
#undef min
|
char* File = new char[Size];
|
||||||
|
|
||||||
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;
|
uint64_t Rcv = 0;
|
||||||
|
asio::error_code ec;
|
||||||
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)
|
||||||
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
Len = 1000000;
|
||||||
if (Temp < 1) {
|
int32_t Temp = asio::read(Sock, asio::buffer(&File[Rcv], Len), ec);
|
||||||
info(std::to_string(Temp));
|
if (ec) {
|
||||||
UUl("Socket Closed Code 1");
|
::error(fmt::format("Failed to receive data from server: {}", ec.message()));
|
||||||
|
UUl("Failed to receive data from server, connection 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;
|
||||||
}
|
}
|
||||||
void MultiKill(SOCKET Sock, SOCKET Sock1) {
|
void MultiKill(asio::ip::tcp::socket& Sock, asio::ip::tcp::socket& Sock1) {
|
||||||
KillSocket(Sock1);
|
KillSocket(Sock1);
|
||||||
KillSocket(Sock);
|
KillSocket(Sock);
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
}
|
}
|
||||||
SOCKET InitDSock() {
|
std::shared_ptr<asio::ip::tcp::socket> InitDSock(asio::ip::tcp::endpoint ep) {
|
||||||
SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
auto DSock = std::make_shared<asio::ip::tcp::socket>(io);
|
||||||
SOCKADDR_IN ServerAddr;
|
asio::error_code ec;
|
||||||
if (DSock < 1) {
|
DSock->connect(ep, ec);
|
||||||
|
if (ec) {
|
||||||
KillSocket(DSock);
|
KillSocket(DSock);
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
|
||||||
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) };
|
char Code[2] = { 'D', char(ClientID) };
|
||||||
if (send(DSock, Code, 2, 0) != 2) {
|
asio::write(*DSock, asio::buffer(Code, 2), ec);
|
||||||
|
if (ec) {
|
||||||
KillSocket(DSock);
|
KillSocket(DSock);
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -219,78 +185,44 @@ SOCKET InitDSock() {
|
|||||||
return DSock;
|
return DSock;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<char> SingleNormalDownload(SOCKET MSock, uint64_t Size, const std::string& Name) {
|
std::string MultiDownload(asio::ip::tcp::socket& MSock, asio::ip::tcp::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
|
if (Au.joinable())
|
||||||
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();
|
Au.join();
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Au.join();
|
/// omg yes very ugly my god but i was in a rush will revisit
|
||||||
|
std::string Ret(Size, 0);
|
||||||
|
memcpy(&Ret[0], MData, MSize);
|
||||||
|
delete[] MData;
|
||||||
|
|
||||||
std::vector<char> Result {};
|
memcpy(&Ret[MSize], DData, DSize);
|
||||||
Result.insert(Result.begin(), MData.begin(), MData.end());
|
delete[] DData;
|
||||||
Result.insert(Result.end(), DData.begin(), DData.end());
|
|
||||||
return Result;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InvalidResource(const std::string& File) {
|
void InvalidResource(const std::string& File) {
|
||||||
@@ -299,255 +231,31 @@ void InvalidResource(const std::string& File) {
|
|||||||
Terminate = true;
|
Terminate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetSha256HashReallyFast(const std::string& filename) {
|
void SyncResources(asio::ip::tcp::socket& Sock) {
|
||||||
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) {
|
|
||||||
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('/');
|
||||||
@@ -562,27 +270,25 @@ void SyncResources(SOCKET Sock) {
|
|||||||
}
|
}
|
||||||
if (!FNames.empty())
|
if (!FNames.empty())
|
||||||
info("Syncing...");
|
info("Syncing...");
|
||||||
SOCKET DSock = InitDSock();
|
auto DSock = InitDSock(Sock.remote_endpoint());
|
||||||
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 +297,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 +307,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 +323,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 +348,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";
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
///
|
///
|
||||||
#include "Network/network.hpp"
|
#include "Network/network.hpp"
|
||||||
#include "Zlib/Compressor.h"
|
#include "Zlib/Compressor.h"
|
||||||
#include <stdexcept>
|
#include "asio/ip/address.hpp"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
@@ -24,84 +25,78 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
SOCKET UDPSock = -1;
|
std::shared_ptr<asio::ip::udp::socket> UDPSock = 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 == nullptr)
|
||||||
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 = UDPSock->send(asio::buffer(Packet));
|
||||||
if (sendOk == SOCKET_ERROR)
|
if (sendOk == SOCKET_ERROR)
|
||||||
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', ':' });
|
||||||
|
if (!TCPSock) {
|
||||||
|
::debug("TCPSock is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TCPSend(res, *TCPSock);
|
||||||
|
} else {
|
||||||
|
if (!TCPSock) {
|
||||||
|
::debug("TCPSock is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void UDPRcv() {
|
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 {};
|
static thread_local std::array<char, 10240> Ret {};
|
||||||
if (UDPSock == -1)
|
if (UDPSock == nullptr) {
|
||||||
|
::debug("UDPSock is null");
|
||||||
return;
|
return;
|
||||||
int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
|
}
|
||||||
if (Rcv == SOCKET_ERROR)
|
asio::error_code ec;
|
||||||
|
int32_t Rcv = UDPSock->receive(asio::buffer(Ret.data(), Ret.size() - 1), 0, ec);
|
||||||
|
if (ec)
|
||||||
return;
|
return;
|
||||||
Ret[Rcv] = 0;
|
Ret[Rcv] = 0;
|
||||||
UDPParser(std::string_view(Ret.data(), Rcv));
|
UDPParser(std::string_view(Ret.data(), Rcv));
|
||||||
}
|
}
|
||||||
void UDPClientMain(const std::string& IP, int Port) {
|
void UDPClientMain(asio::ip::address addr, uint16_t port) {
|
||||||
#ifdef _WIN32
|
UDPSock = std::make_shared<asio::ip::udp::socket>(io);
|
||||||
WSADATA data;
|
asio::error_code ec;
|
||||||
if (WSAStartup(514, &data)) {
|
UDPSock->connect(asio::ip::udp::endpoint(addr, port), ec);
|
||||||
error("Can't start Winsock!");
|
if (ec) {
|
||||||
return;
|
::error(fmt::format("Failed to connect UDP to server: {}", ec.message()));
|
||||||
|
Terminate = true;
|
||||||
}
|
}
|
||||||
#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));
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
#include <Zlib/Compressor.h>
|
#include <Zlib/Compressor.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
int LastPort;
|
int LastPort;
|
||||||
std::string LastIP;
|
std::string LastIP;
|
||||||
SOCKET TCPSock = -1;
|
std::shared_ptr<asio::ip::tcp::socket> TCPSock = nullptr;
|
||||||
|
|
||||||
bool CheckBytes(int32_t Bytes) {
|
bool CheckBytes(int32_t Bytes) {
|
||||||
if (Bytes == 0) {
|
if (Bytes == 0) {
|
||||||
@@ -46,8 +47,8 @@ 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, asio::ip::tcp::socket& Sock) {
|
||||||
if (Sock == -1) {
|
if (!Sock.is_open()) {
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
UUl("Invalid Socket");
|
UUl("Invalid Socket");
|
||||||
return;
|
return;
|
||||||
@@ -57,64 +58,44 @@ 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;
|
||||||
do {
|
asio::error_code ec;
|
||||||
if (size_t(Sent) >= Send.size()) {
|
asio::write(Sock, asio::buffer(Send), ec);
|
||||||
error("string OOB in " + std::string(__func__));
|
if (ec) {
|
||||||
UUl("TCP Send OOB");
|
UUl(fmt::format("Failed to send data: {}", ec.message()));
|
||||||
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(SOCKET Sock) {
|
std::string TCPRcv(asio::ip::tcp::socket& Sock) {
|
||||||
if (Sock == -1) {
|
if (!Sock.is_open()) {
|
||||||
Terminate = true;
|
Terminate = true;
|
||||||
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);
|
asio::error_code ec;
|
||||||
if (!CheckBytes(Temp)) {
|
asio::read(Sock, asio::buffer(Data), ec);
|
||||||
UUl("Socket Closed Code 3");
|
if (ec) {
|
||||||
return "";
|
UUl(fmt::format("Failed to receive header: {}", ec.message()));
|
||||||
}
|
}
|
||||||
memcpy(&Header, Data.data(), sizeof(Header));
|
memcpy(&Header, &Data[0], sizeof(Header));
|
||||||
|
|
||||||
if (!CheckBytes(Temp)) {
|
Data.resize(Header);
|
||||||
UUl("Socket Closed Code 4");
|
asio::read(Sock, asio::buffer(Data), ec);
|
||||||
return "";
|
if (ec) {
|
||||||
}
|
UUl(fmt::format("Failed to receive data: {}", ec.message()));
|
||||||
|
|
||||||
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);
|
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
|
||||||
@@ -125,51 +106,24 @@ std::string TCPRcv(SOCKET Sock) {
|
|||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TCPClientMain(const std::string& IP, int Port) {
|
void TCPClientMain(asio::ip::tcp::socket&& socket) {
|
||||||
LastIP = IP;
|
if (!TCPSock) {
|
||||||
LastPort = Port;
|
return;
|
||||||
|
}
|
||||||
|
LastIP = socket.remote_endpoint().address().to_string();
|
||||||
|
LastPort = socket.remote_endpoint().port();
|
||||||
SOCKADDR_IN ServerAddr;
|
SOCKADDR_IN ServerAddr;
|
||||||
int RetCode;
|
int RetCode;
|
||||||
#ifdef _WIN32
|
TCPSock = std::make_shared<asio::ip::tcp::socket>(std::move(socket));
|
||||||
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!");
|
info("Connected!");
|
||||||
|
|
||||||
char Code = 'C';
|
char Code = 'C';
|
||||||
send(TCPSock, &Code, 1, 0);
|
asio::write(*TCPSock, asio::buffer(&Code, 1));
|
||||||
SyncResources(TCPSock);
|
SyncResources(*TCPSock);
|
||||||
while (!Terminate) {
|
while (!Terminate && TCPSock) {
|
||||||
ServerParser(TCPRcv(TCPSock));
|
ServerParser(TCPRcv(*TCPSock));
|
||||||
}
|
}
|
||||||
GameSend("T");
|
GameSend("T");
|
||||||
////Game Send Terminate
|
KillSocket(TCPSock);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/NetworkHelpers.cpp
Normal file
35
src/NetworkHelpers.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -246,13 +256,21 @@ void InitLauncher(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
#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);
|
||||||
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
|
bool update = true;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (std::string_view(argv[i]) == "--no-update") {
|
||||||
|
update = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -314,7 +332,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 +374,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();
|
||||||
|
}
|
||||||
|
|||||||
37
src/main.cpp
37
src/main.cpp
@@ -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));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"nlohmann-json",
|
"nlohmann-json",
|
||||||
"zlib",
|
"zlib",
|
||||||
"openssl",
|
"openssl",
|
||||||
"curl"
|
"asio",
|
||||||
|
"fmt"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user