10 Commits

Author SHA1 Message Date
Lion Kortlepel
5e5fe80202 set terminate = false 2024-06-04 20:03:53 +02:00
Lion Kortlepel
7ff4681263 Revert "move NetReset"
This reverts commit 3be1262b07.
2024-06-04 19:32:58 +02:00
Lion Kortlepel
3be1262b07 move NetReset 2024-06-04 19:12:37 +02:00
Lion Kortlepel
cfcabf31a4 fix stupid socket 2024-06-04 18:42:38 +02:00
Lion Kortlepel
5714c3de76 move socket decls around 2024-06-04 18:08:38 +02:00
Lion Kortlepel
12711d77c7 add missing include 2024-06-04 17:42:44 +02:00
Lion Kortlepel
e08a4de6db fix for CSocket not existing 2024-06-04 17:38:54 +02:00
Lion Kortlepel
acd5f4ed09 prepend missing N 2024-06-04 17:34:14 +02:00
Lion Kortlepel
c4e7b9a919 send on correct socket 2024-06-04 17:33:12 +02:00
Lion Kortlepel
3db1f6773e add mod download warning 2024-06-04 16:45:02 +02:00
74 changed files with 1862 additions and 3103 deletions

View File

@@ -1,5 +0,0 @@
---
BasedOnStyle: WebKit
BreakBeforeBraces: Attach
SpaceAfterTemplateKeyword: false
...

View File

@@ -1,42 +0,0 @@
name: CMake Linux Build
on: [push, pull_request, workflow_dispatch]
env:
BUILD_TYPE: Release
jobs:
linux-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@v7
id: runvcpkg
with:
vcpkgArguments: 'zlib nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
- 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 -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake'
- name: Build
working-directory: ${{github.workspace}}/build-linux
shell: bash
run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: BeamMP-Launcher
path: ${{github.workspace}}/build-linux/BeamMP-Launcher

View File

@@ -1,6 +1,6 @@
name: CMake Windows Build name: CMake Windows Build
on: [push, pull_request, workflow_dispatch] on: [push, pull_request]
env: env:
BUILD_TYPE: Release BUILD_TYPE: Release
@@ -20,7 +20,7 @@ jobs:
with: with:
vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]' vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6' vcpkgGitCommitId: '16ee2ecb31788c336ace8bb14c21801efb6836e4'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment - name: Create Build Environment
@@ -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

72
.github/workflows/release-build.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
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
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

7
.gitignore vendored
View File

@@ -5,10 +5,3 @@ cmake-build-release
/*.sh /*.sh
/*.obj /*.obj
/*.exe /*.exe
.cache/
.https_debug/
Launcher.cfg
Resources/
bin/
compile_commands.json
key

View File

@@ -1,5 +1,4 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(Launcher) project(Launcher)
if (WIN32) if (WIN32)
@@ -12,29 +11,32 @@ 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")
file(GLOB source_files "src/*.cpp" "src/*/*.cpp" "src/*/*.hpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "include/*/*/*.hpp")
find_package(httplib CONFIG REQUIRED) find_package(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED)
find_package(CURL REQUIRED)
add_executable(${PROJECT_NAME} ${source_files}) add_executable(${PROJECT_NAME} ${source_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher") set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
if (WIN32) if (WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json CURL::libcurl) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json)
elseif (LINUX)
find_package(ZLIB REQUIRED) if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
find_package(OpenSSL REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/lib/discord-rpc.lib)
target_link_libraries(${PROJECT_NAME} PRIVATE else ()
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl) target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/debug/lib/discord-rpc.lib)
endif()
else(WIN32) #MINGW else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600") add_definitions("-D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z CURL::libcurl) target_link_libraries(${PROJECT_NAME} discord-rpc ssl crypto ws2_32 ssp crypt32 z)
endif(WIN32) endif(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE "include") target_include_directories(${PROJECT_NAME} PRIVATE "include")

View File

@@ -2,23 +2,6 @@
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 - Release
In the root directory of the project,
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel --config Release`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
## How to build - Debug
In the root directory of the project,
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
Copyright (c) 2019-present Anonymous275. 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.

15
include/Discord/discord_info.h Executable file
View File

@@ -0,0 +1,15 @@
// Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
///
/// Created by Anonymous275 on 7/16/2020
///
#pragma once
#include <string>
void Discord_Main();
std::string GetDName();
std::string GetDTag();
std::string GetDID();
void DAboard();

View File

@@ -0,0 +1,26 @@
#pragma once
#if defined(DISCORD_DYNAMIC_LIB)
#if defined(_WIN32)
#if defined(DISCORD_BUILDING_SDK)
#define DISCORD_EXPORT __declspec(dllexport)
#else
#define DISCORD_EXPORT __declspec(dllimport)
#endif
#else
#define DISCORD_EXPORT __attribute__((visibility("default")))
#endif
#else
#define DISCORD_EXPORT
#endif
#ifdef __cplusplus
extern "C" {
#endif
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,87 @@
#pragma once
#include <cstdint>
// clang-format off
#if defined(DISCORD_DYNAMIC_LIB)
# if defined(_WIN32)
# if defined(DISCORD_BUILDING_SDK)
# define DISCORD_EXPORT __declspec(dllexport)
# else
# define DISCORD_EXPORT __declspec(dllimport)
# endif
# else
# define DISCORD_EXPORT __attribute__((visibility("default")))
# endif
#else
# define DISCORD_EXPORT
#endif
// clang-format on
#ifdef __cplusplus
extern "C" {
#endif
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef struct DiscordEventHandlers {
void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */
DISCORD_EXPORT void Discord_RunCallbacks(void);
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
#ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
DISCORD_EXPORT void Discord_ClearPresence(void);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus
} /* extern "C" */
#endif

4
include/Http.h Normal file → Executable file
View File

@@ -6,16 +6,14 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include "Logger.h"
#include <string> #include <string>
#include "Logger.h"
class HTTP { class HTTP {
public: public:
static bool Download(const std::string &IP, const std::string &Path); static bool Download(const std::string &IP, const std::string &Path);
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;
}; };

3
include/Logger.h Normal file → Executable file
View File

@@ -6,8 +6,8 @@
/// Created by Anonymous275 on 4/2/2020. /// Created by Anonymous275 on 4/2/2020.
/// ///
#pragma once #pragma once
#include <iostream>
#include <string> #include <string>
#include <iostream>
void InitLog(); void InitLog();
void except(const std::string& toPrint); void except(const std::string& toPrint);
void fatal(const std::string& toPrint); void fatal(const std::string& toPrint);
@@ -15,4 +15,3 @@ void debug(const std::string& toPrint);
void error(const std::string& toPrint); void error(const std::string& toPrint);
void info(const std::string& toPrint); void info(const std::string& toPrint);
void warn(const std::string& toPrint); void warn(const std::string& toPrint);
std::string getDate();

View File

@@ -6,20 +6,15 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include <string> #include <string>
#ifdef __linux__
#include "linuxfixes.h"
#include <bits/types/siginfo_t.h>
#include <cstdint>
#include <sys/ucontext.h>
#endif
void NetReset(); void NetReset();
extern bool Dev; extern bool Dev;
extern int ping; extern int ping;
extern bool ModWarningConfirmed;
[[noreturn]] void CoreNetwork(); [[noreturn]] void CoreNetwork();
extern int ProxyPort; extern int ProxyPort;
extern int ClientID; extern int ClientID;
@@ -30,27 +25,27 @@ extern int DEFAULT_PORT;
extern uint64_t UDPSock; extern uint64_t UDPSock;
extern uint64_t TCPSock; extern uint64_t TCPSock;
extern std::string Branch; extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate; extern bool TCPTerminate;
extern std::string LastIP; extern std::string LastIP;
extern std::string MStatus; extern std::string MStatus;
extern std::string UlStatus; extern std::string UlStatus;
extern std::string PublicKey; extern std::string PublicKey;
extern std::string PrivateKey; extern std::string PrivateKey;
extern std::string ListOfMods;
int KillSocket(uint64_t Dead); int KillSocket(uint64_t Dead);
void UUl(const std::string& R); void UUl(const std::string& R);
void UDPSend(std::string Data); void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes); bool CheckBytes(int32_t Bytes);
void GameSend(std::string_view Data); void GameSend(std::string Data);
void SendLarge(std::string Data); void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock); std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock); void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string&IP); std::string GetAddr(const std::string&IP);
void ServerParser(std::string_view Data); void ServerParser(const std::string& 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::string&Data,uint64_t Sock);
void TCPClientMain(const std::string& IP,int Port); void TCPClientMain(const std::string& IP,int Port);
void UDPClientMain(const std::string& IP,int Port); void UDPClientMain(const std::string& IP,int Port);
void TCPGameServer(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);

0
include/Security/Game.h Normal file → Executable file
View File

0
include/Security/Init.h Normal file → Executable file
View File

11
include/Startup.h Normal file → Executable file
View File

@@ -6,16 +6,23 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include <compare>
#include <string> #include <string>
#include <compare>
#include <vector> #include <vector>
void InitLauncher(int argc, char* argv[]); 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;
struct VersionParser {
explicit VersionParser(const std::string& from_string);
std::strong_ordering operator<=>(VersionParser const& rhs) const noexcept;
bool operator==(VersionParser const& rhs) const noexcept;
std::vector<std::string> split;
std::vector<size_t> data;
};

View File

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

8
include/Zlib/Compressor.h Normal file → Executable file
View File

@@ -6,8 +6,6 @@
/// Created by Anonymous275 on 7/24/2020 /// Created by Anonymous275 on 7/24/2020
/// ///
#pragma once #pragma once
#include <span> #include <string>
#include <vector> std::string Comp(std::string Data);
std::string DeComp(std::string Compressed);
std::vector<char> Comp(std::span<const char> input);
std::vector<char> DeComp(std::span<const char> input);

View File

@@ -1,19 +0,0 @@
#ifndef _LINUXFIXES_H
#define _LINUXFIXES_H
#include <stdint.h>
// Translate windows sockets stuff to linux sockets
#define SOCKET uint64_t
#define SOCKADDR sockaddr
#define SOCKADDR_IN sockaddr_in
#define WSAGetLastError() errno
#define closesocket close
#define SD_BOTH SHUT_RDWR
// We dont need wsacleanup
#define WSACleanup()
#define SOCKET_ERROR -1
#define ZeroMemory(mem, len) memset(mem, 0, len)
#endif

0
include/rapidjson/allocators.h Normal file → Executable file
View File

0
include/rapidjson/cursorstreamwrapper.h Normal file → Executable file
View File

0
include/rapidjson/document.h Normal file → Executable file
View File

0
include/rapidjson/encodedstream.h Normal file → Executable file
View File

0
include/rapidjson/encodings.h Normal file → Executable file
View File

0
include/rapidjson/error/en.h Normal file → Executable file
View File

0
include/rapidjson/error/error.h Normal file → Executable file
View File

0
include/rapidjson/filereadstream.h Normal file → Executable file
View File

0
include/rapidjson/filewritestream.h Normal file → Executable file
View File

0
include/rapidjson/fwd.h Normal file → Executable file
View File

0
include/rapidjson/internal/biginteger.h Normal file → Executable file
View File

0
include/rapidjson/internal/clzll.h Normal file → Executable file
View File

0
include/rapidjson/internal/diyfp.h Normal file → Executable file
View File

0
include/rapidjson/internal/dtoa.h Normal file → Executable file
View File

0
include/rapidjson/internal/ieee754.h Normal file → Executable file
View File

0
include/rapidjson/internal/itoa.h Normal file → Executable file
View File

0
include/rapidjson/internal/meta.h Normal file → Executable file
View File

0
include/rapidjson/internal/pow10.h Normal file → Executable file
View File

0
include/rapidjson/internal/regex.h Normal file → Executable file
View File

0
include/rapidjson/internal/stack.h Normal file → Executable file
View File

0
include/rapidjson/internal/strfunc.h Normal file → Executable file
View File

0
include/rapidjson/internal/strtod.h Normal file → Executable file
View File

0
include/rapidjson/internal/swap.h Normal file → Executable file
View File

0
include/rapidjson/istreamwrapper.h Normal file → Executable file
View File

0
include/rapidjson/memorybuffer.h Normal file → Executable file
View File

0
include/rapidjson/memorystream.h Normal file → Executable file
View File

0
include/rapidjson/msinttypes/inttypes.h Normal file → Executable file
View File

0
include/rapidjson/msinttypes/stdint.h Normal file → Executable file
View File

0
include/rapidjson/ostreamwrapper.h Normal file → Executable file
View File

0
include/rapidjson/pointer.h Normal file → Executable file
View File

0
include/rapidjson/prettywriter.h Normal file → Executable file
View File

0
include/rapidjson/rapidjson.h Normal file → Executable file
View File

0
include/rapidjson/reader.h Normal file → Executable file
View File

0
include/rapidjson/schema.h Normal file → Executable file
View File

0
include/rapidjson/stream.h Normal file → Executable file
View File

0
include/rapidjson/stringbuffer.h Normal file → Executable file
View File

0
include/rapidjson/writer.h Normal file → Executable file
View File

View File

@@ -1,730 +0,0 @@
//MIT License
//
//Copyright(c) 2016 Matthias Moeller
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files(the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions :
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef __TYTI_STEAM_VDF_PARSER_H__
#define __TYTI_STEAM_VDF_PARSER_H__
#include <map>
#include <vector>
#include <unordered_map>
#include <utility>
#include <fstream>
#include <memory>
#include <unordered_set>
#include <algorithm>
#include <iterator>
#include <functional>
#include <system_error>
#include <exception>
//for wstring support
#include <locale>
#include <string>
// internal
#include <stack>
//VS < 2015 has only partial C++11 support
#if defined(_MSC_VER) && _MSC_VER < 1900
#ifndef CONSTEXPR
#define CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT
#endif
#else
#ifndef CONSTEXPR
#define CONSTEXPR constexpr
#define TYTI_UNDEF_CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT noexcept
#define TYTI_UNDEF_NOEXCEPT
#endif
#endif
namespace tyti
{
namespace vdf
{
namespace detail
{
///////////////////////////////////////////////////////////////////////////
// Helper functions selecting the right encoding (char/wchar_T)
///////////////////////////////////////////////////////////////////////////
template <typename T>
struct literal_macro_help
{
static CONSTEXPR const char* result(const char* c, const wchar_t*) NOEXCEPT
{
return c;
}
static CONSTEXPR const char result(const char c, const wchar_t) NOEXCEPT
{
return c;
}
};
template <>
struct literal_macro_help<wchar_t>
{
static CONSTEXPR const wchar_t* result(const char*, const wchar_t* wc) NOEXCEPT
{
return wc;
}
static CONSTEXPR const wchar_t result(const char, const wchar_t wc) NOEXCEPT
{
return wc;
}
};
#define TYTI_L(type, text) vdf::detail::literal_macro_help<type>::result(text, L##text)
inline std::string string_converter(const std::string& w) NOEXCEPT
{
return w;
}
// utility wrapper to adapt locale-bound facets for wstring/wbuffer convert
// from cppreference
template <class Facet>
struct deletable_facet : Facet
{
template <class... Args>
deletable_facet(Args &&... args) : Facet(std::forward<Args>(args)...) {}
~deletable_facet() {}
};
inline std::string string_converter(const std::wstring& w) //todo: use us-locale
{
std::wstring_convert<deletable_facet<std::codecvt<wchar_t, char, std::mbstate_t>>> conv1;
return conv1.to_bytes(w);
}
///////////////////////////////////////////////////////////////////////////
// Writer helper functions
///////////////////////////////////////////////////////////////////////////
template <typename charT>
class tabs
{
const size_t t;
public:
explicit CONSTEXPR tabs(size_t i) NOEXCEPT : t(i) {}
std::basic_string<charT> print() const { return std::basic_string<charT>(t, TYTI_L(charT, '\t')); }
inline CONSTEXPR tabs operator+(size_t i) const NOEXCEPT
{
return tabs(t + i);
}
};
template <typename oStreamT>
oStreamT& operator<<(oStreamT& s, const tabs<typename oStreamT::char_type> t)
{
s << t.print();
return s;
}
} // end namespace detail
///////////////////////////////////////////////////////////////////////////
// Interface
///////////////////////////////////////////////////////////////////////////
/// custom objects and their corresponding write functions
/// basic object node. Every object has a name and can contains attributes saved as key_value pairs or childrens
template <typename CharT>
struct basic_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_map<std::basic_string<char_type>, std::basic_string<char_type>> attribs;
std::unordered_map<std::basic_string<char_type>, std::shared_ptr<basic_object<char_type>>> childs;
void add_attribute(std::basic_string<char_type> key, std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_object<char_type>> child)
{
std::shared_ptr<basic_object<char_type>> obj{ child.release() };
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n)
{
name = std::move(n);
}
};
template <typename CharT>
struct basic_multikey_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_multimap<std::basic_string<char_type>, std::basic_string<char_type>> attribs;
std::unordered_multimap<std::basic_string<char_type>, std::shared_ptr<basic_multikey_object<char_type>>> childs;
void add_attribute(std::basic_string<char_type> key, std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_multikey_object<char_type>> child)
{
std::shared_ptr<basic_multikey_object<char_type>> obj{ child.release() };
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n)
{
name = std::move(n);
}
};
typedef basic_object<char> object;
typedef basic_object<wchar_t> wobject;
typedef basic_multikey_object<char> multikey_object;
typedef basic_multikey_object<wchar_t> wmultikey_object;
struct Options
{
bool strip_escape_symbols;
bool ignore_all_platform_conditionals;
bool ignore_includes;
Options() : strip_escape_symbols(true), ignore_all_platform_conditionals(false), ignore_includes(false) {}
};
//forward decls
//forward decl
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, const Options& opt = Options{});
/** \brief writes given object tree in vdf format to given stream.
Output is prettyfied, using tabs
*/
template <typename oStreamT, typename T>
void write(oStreamT& s, const T& r,
const detail::tabs<typename oStreamT::char_type> tab = detail::tabs<typename oStreamT::char_type>(0))
{
typedef typename oStreamT::char_type charT;
using namespace detail;
s << tab << TYTI_L(charT, '"') << r.name << TYTI_L(charT, "\"\n") << tab << TYTI_L(charT, "{\n");
for (const auto& i : r.attribs)
s << tab + 1 << TYTI_L(charT, '"') << i.first << TYTI_L(charT, "\"\t\t\"") << i.second << TYTI_L(charT, "\"\n");
for (const auto& i : r.childs)
if (i.second)
write(s, *i.second, tab + 1);
s << tab << TYTI_L(charT, "}\n");
}
namespace detail
{
template <typename iStreamT>
std::basic_string<typename iStreamT::char_type> read_file(iStreamT& inStream)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str;
inStream.seekg(0, std::ios::end);
str.resize(static_cast<size_t>(inStream.tellg()));
if (str.empty())
return str;
inStream.seekg(0, std::ios::beg);
inStream.read(&str[0], str.size());
return str;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param exclude_files list of files which cant be included anymore.
prevents circular includes
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
std::vector<std::unique_ptr<OutputT>> read_internal(IterT first, const IterT last,
std::unordered_set<std::basic_string<typename std::iterator_traits<IterT>::value_type>>& exclude_files,
const Options& opt)
{
static_assert(std::is_default_constructible<OutputT>::value,
"Output Type must be default constructible (provide constructor without arguments)");
static_assert(std::is_move_constructible<OutputT>::value,
"Output Type must be move constructible");
typedef typename std::iterator_traits<IterT>::value_type charT;
const std::basic_string<charT> comment_end_str = TYTI_L(charT, "*/");
const std::basic_string<charT> whitespaces = TYTI_L(charT, " \n\v\f\r\t");
#ifdef WIN32
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$WINDOWS");
};
#elif __APPLE__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || in == TYTI_L(charT, "$OSX");
};
#elif __linux__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || in == TYTI_L(charT, "$LINUX");
};
#else
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return false;
};
#endif
if (opt.ignore_all_platform_conditionals)
is_platform_str = [](const std::basic_string<charT>&) {
return false;
};
// function for skipping a comment block
// iter: iterator poition to the position after a '/'
auto skip_comments = [&comment_end_str](IterT iter, const IterT& last) -> IterT {
++iter;
if (iter != last)
{
if (*iter == TYTI_L(charT, '/'))
{
// line comment, skip whole line
iter = std::find(iter + 1, last, TYTI_L(charT, '\n'));
}
if (*iter == '*')
{
// block comment, skip until next occurance of "*\"
iter = std::search(iter + 1, last, std::begin(comment_end_str), std::end(comment_end_str));
iter += 2;
}
}
return iter;
};
auto end_quote = [](IterT iter, const IterT& last) -> IterT {
const auto begin = iter;
auto last_esc = iter;
do
{
++iter;
iter = std::find(iter, last, TYTI_L(charT, '\"'));
if (iter == last)
break;
last_esc = std::prev(iter);
while (last_esc != begin && *last_esc == '\\')
--last_esc;
} while (!(std::distance(last_esc, iter) % 2));
if (iter == last)
throw std::runtime_error{ "quote was opened but not closed." };
return iter;
};
auto end_word = [&whitespaces](IterT iter, const IterT& last) -> IterT {
const auto begin = iter;
auto last_esc = iter;
do
{
++iter;
iter = std::find_first_of(iter, last, std::begin(whitespaces), std::end(whitespaces));
if (iter == last)
break;
last_esc = std::prev(iter);
while (last_esc != begin && *last_esc == '\\')
--last_esc;
} while (!(std::distance(last_esc, iter) % 2));
//if (iter == last)
// throw std::runtime_error{ "word wasnt properly ended" };
return iter;
};
auto skip_whitespaces = [&whitespaces](IterT iter, const IterT& last) -> IterT {
iter = std::find_if_not(iter, last, [&whitespaces](charT c) {
// return true if whitespace
return std::any_of(std::begin(whitespaces), std::end(whitespaces), [c](charT pc) { return pc == c; });
});
return iter;
};
std::function<void(std::basic_string<charT>&)> strip_escape_symbols = [](std::basic_string<charT>& s) {
auto quote_searcher = [&s](size_t pos) { return s.find(TYTI_L(charT, "\\\""), pos); };
auto p = quote_searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\""));
p = quote_searcher(p);
}
auto searcher = [&s](size_t pos) { return s.find(TYTI_L(charT, "\\\\"), pos); };
p = searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\\"));
p = searcher(p);
}
};
if (!opt.strip_escape_symbols)
strip_escape_symbols = [](std::basic_string<charT>&) {};
auto conditional_fullfilled = [&skip_whitespaces, &is_platform_str](IterT& iter, const IterT& last) {
iter = skip_whitespaces(iter, last);
if (*iter == '[')
{
++iter;
const auto end = std::find(iter, last, ']');
const bool negate = *iter == '!';
if (negate)
++iter;
auto conditional = std::basic_string<charT>(iter, end);
const bool is_platform = is_platform_str(conditional);
iter = end + 1;
return static_cast<bool>(is_platform ^ negate);
}
return true;
};
//read header
// first, quoted name
std::unique_ptr<OutputT> curObj = nullptr;
std::vector<std::unique_ptr<OutputT>> roots;
std::stack<std::unique_ptr<OutputT>> lvls;
auto curIter = first;
while (curIter != last && *curIter != '\0')
{
//find first starting attrib/child, or ending
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '\0')
break;
if (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
}
else if (*curIter != TYTI_L(charT, '}'))
{
// get key
const auto keyEnd = (*curIter == TYTI_L(charT, '\"')) ? end_quote(curIter, last) : end_word(curIter, last);
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
std::basic_string<charT> key(curIter, keyEnd);
strip_escape_symbols(key);
curIter = keyEnd + ((*keyEnd == TYTI_L(charT, '\"')) ? 1 : 0);
curIter = skip_whitespaces(curIter, last);
auto conditional = conditional_fullfilled(curIter, last);
if (!conditional)
continue;
while (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{ "key declared, but no value" };
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{ "key declared, but no value" };
}
// get value
if (*curIter != '{')
{
const auto valueEnd = (*curIter == TYTI_L(charT, '\"')) ? end_quote(curIter, last) : end_word(curIter, last);
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
auto value = std::basic_string<charT>(curIter, valueEnd);
strip_escape_symbols(value);
curIter = valueEnd + ((*valueEnd == TYTI_L(charT, '\"')) ? 1 : 0);
auto conditional = conditional_fullfilled(curIter, last);
if (!conditional)
continue;
// process value
if (key != TYTI_L(charT, "#include") && key != TYTI_L(charT, "#base"))
{
if (curObj)
{
curObj->add_attribute(std::move(key), std::move(value));
}
else
{
throw std::runtime_error{ "unexpected key without object" };
}
}
else
{
if (!opt.ignore_includes && exclude_files.find(value) == exclude_files.end())
{
exclude_files.insert(value);
std::basic_ifstream<charT> i(detail::string_converter(value));
auto str = read_file(i);
auto file_objs = read_internal<OutputT>(str.begin(), str.end(), exclude_files, opt);
for (auto& n : file_objs)
{
if (curObj)
curObj->add_child(std::move(n));
else
roots.push_back(std::move(n));
}
exclude_files.erase(value);
}
}
}
else if (*curIter == '{')
{
if (curObj)
lvls.push(std::move(curObj));
curObj = std::make_unique<OutputT>();
curObj->set_name(std::move(key));
++curIter;
}
}
//end of new object
else if (curObj && *curIter == TYTI_L(charT, '}'))
{
if (!lvls.empty())
{
//get object before
std::unique_ptr<OutputT> prev{ std::move(lvls.top()) };
lvls.pop();
// add finished obj to obj before and release it from processing
prev->add_child(std::move(curObj));
curObj = std::move(prev);
}
else
{
roots.push_back(std::move(curObj));
curObj.reset();
}
++curIter;
}
else
{
throw std::runtime_error{ "unexpected '}'" };
}
}
if (curObj != nullptr || !lvls.empty())
{
throw std::runtime_error{ "object is not closed with '}'" };
}
return roots;
}
} // namespace detail
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, const Options& opt = Options{})
{
auto exclude_files = std::unordered_set<std::basic_string<typename std::iterator_traits<IterT>::value_type>>{};
auto roots = detail::read_internal<OutputT>(first, last, exclude_files, opt);
OutputT result;
if (roots.size() > 1)
{
for (auto& i : roots)
result.add_child(std::move(i));
}
else if (roots.size() == 1)
result = std::move(*roots[0]);
return result;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ec output bool. 0 if ok, otherwise, holds an system error code
Possible error codes:
std::errc::protocol_error: file is mailformatted
std::errc::not_enough_memory: not enough space
std::errc::invalid_argument: iterators throws e.g. out of range
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, IterT last, std::error_code& ec, const Options& opt = Options{}) NOEXCEPT
{
ec.clear();
OutputT r{};
try
{
r = read<OutputT>(first, last, opt);
}
catch (std::runtime_error&)
{
ec = std::make_error_code(std::errc::protocol_error);
}
catch (std::bad_alloc&)
{
ec = std::make_error_code(std::errc::not_enough_memory);
}
catch (...)
{
ec = std::make_error_code(std::errc::invalid_argument);
}
return r;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ok output bool. true, if parser successed, false, if parser failed
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, bool* ok, const Options& opt = Options{}) NOEXCEPT
{
std::error_code ec;
auto r = read<OutputT>(first, last, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename IterT>
inline auto read(IterT first, const IterT last, bool* ok, const Options& opt = Options{}) NOEXCEPT -> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, ok, opt);
}
template <typename IterT>
inline auto read(IterT first, IterT last, std::error_code& ec, const Options& opt = Options{}) NOEXCEPT
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, ec, opt);
}
template <typename IterT>
inline auto read(IterT first, const IterT last, const Options& opt = Options{})
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{})
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), ec, opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ec, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
ok == false, if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, bool* ok, const Options& opt = Options{})
{
std::error_code ec;
const auto r = read<OutputT>(inStream, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, bool* ok, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ok, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
throws "std::runtime_error" if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, const Options& opt)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, opt);
}
} // namespace vdf
} // namespace tyti
#ifndef TYTI_NO_L_UNDEF
#undef TYTI_L
#endif
#ifdef TYTI_UNDEF_CONSTEXPR
#undef CONSTEXPR
#undef TYTI_NO_L_UNDEF
#endif
#ifdef TYTI_UNDEF_NOTHROW
#undef NOTHROW
#undef TYTI_UNDEF_NOTHROW
#endif
#endif //__TYTI_STEAM_VDF_PARSER_H__

92
src/Compressor.cpp Normal file → Executable file
View File

@@ -6,57 +6,49 @@
/// Created by Anonymous275 on 7/15/2020 /// Created by Anonymous275 on 7/15/2020
/// ///
#include "Logger.h" #include <iostream>
#include <span>
#include <vector>
#include <zconf.h>
#include <zlib.h> #include <zlib.h>
#ifdef __linux__
#include <cstring>
#endif
std::vector<char> Comp(std::span<const char> input) { #define Biggest 30000
auto max_size = compressBound(input.size()); std::string Comp(std::string Data){
std::vector<char> output(max_size); char*C = new char[Biggest];
uLongf output_size = output.size(); memset(C, 0, Biggest);
int res = compress( z_stream defstream;
reinterpret_cast<Bytef*>(output.data()), defstream.zalloc = Z_NULL;
&output_size, defstream.zfree = Z_NULL;
reinterpret_cast<const Bytef*>(input.data()), defstream.opaque = Z_NULL;
static_cast<uLongf>(input.size())); defstream.avail_in = (uInt)Data.length();
if (res != Z_OK) { defstream.next_in = (Bytef *)&Data[0];
error("zlib compress() failed: " + std::to_string(res)); defstream.avail_out = Biggest;
throw std::runtime_error("zlib compress() failed"); defstream.next_out = reinterpret_cast<Bytef *>(C);
deflateInit(&defstream, Z_BEST_COMPRESSION);
deflate(&defstream, Z_SYNC_FLUSH);
deflate(&defstream, Z_FINISH);
deflateEnd(&defstream);
int TO = defstream.total_out;
std::string Ret(TO,0);
memcpy_s(&Ret[0],TO,C,TO);
delete [] C;
return Ret;
} }
debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B"); std::string DeComp(std::string Compressed){
output.resize(output_size); char*C = new char[Biggest];
return output; memset(C, 0, Biggest);
} z_stream infstream;
infstream.zalloc = Z_NULL;
std::vector<char> DeComp(std::span<const char> input) { infstream.zfree = Z_NULL;
std::vector<char> output_buffer(std::min<size_t>(input.size() * 5, 15 * 1024 * 1024)); infstream.opaque = Z_NULL;
infstream.avail_in = Biggest;
uLongf output_size = output_buffer.size(); infstream.next_in = (Bytef *)(&Compressed[0]);
infstream.avail_out = Biggest;
while (true) { infstream.next_out = (Bytef *)(C);
int res = uncompress( inflateInit(&infstream);
reinterpret_cast<Bytef*>(output_buffer.data()), inflate(&infstream, Z_SYNC_FLUSH);
&output_size, inflate(&infstream, Z_FINISH);
reinterpret_cast<const Bytef*>(input.data()), inflateEnd(&infstream);
static_cast<uLongf>(input.size())); int TO = infstream.total_out;
if (res == Z_BUF_ERROR) { std::string Ret(TO,0);
if (output_buffer.size() > 30 * 1024 * 1024) { memcpy_s(&Ret[0],TO,C,TO);
throw std::runtime_error("decompressed packet size of 30 MB exceeded"); delete [] C;
} return Ret;
debug("zlib uncompress() failed, trying with 2x buffer size of " + std::to_string(output_buffer.size() * 2));
output_buffer.resize(output_buffer.size() * 2);
output_size = output_buffer.size();
} else if (res != Z_OK) {
error("zlib uncompress() failed: " + std::to_string(res));
throw std::runtime_error("zlib uncompress() failed");
} else if (res == Z_OK) {
break;
}
} output_buffer.resize(output_size);
return output_buffer;
} }

27
src/Config.cpp Normal file → Executable file
View File

@@ -2,17 +2,15 @@
/// Created by Anonymous275 on 2/23/2021 /// Created by Anonymous275 on 2/23/2021
/// ///
#include "Logger.h"
#include "Network/network.hpp"
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "Network/network.h"
#include <filesystem>
#include "Logger.h"
#include <fstream>
#include <cstdint>
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,14 +20,10 @@ 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);
} }
} }
@@ -46,16 +40,14 @@ void ConfigInit() {
fatal("Config failed to parse make sure it's valid JSON!"); fatal("Config failed to parse make sure it's valid JSON!");
} }
ParseConfig(d); ParseConfig(d);
} else }else fatal("Failed to open Launcher.cfg!");
fatal("Failed to open Launcher.cfg!");
}else{ }else{
std::ofstream cfg("Launcher.cfg"); std::ofstream cfg("Launcher.cfg");
if(cfg.is_open()){ if(cfg.is_open()){
cfg << cfg <<
R"({ R"({
"Port": 4444, "Port": 4444,
"Build": "Default", "Build": "Default"
"CachingDirectory": "./Resources"
})"; })";
cfg.close(); cfg.close();
}else{ }else{
@@ -63,3 +55,4 @@ void ConfigInit() {
} }
} }
} }

111
src/Discord.cpp Executable file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
///
/// Created by Anonymous275 on 7/16/2020
///
#include "Discord/discord_rpc.h"
#include "Logger.h"
#include <cstring>
#include <thread>
#include <ctime>
struct DInfo{
std::string Name;
std::string Tag;
std::string DID;
};
DInfo* DiscordInfo = nullptr;
int64_t StartTime;
void updateDiscordPresence(){
//if (SendPresence) {
//char buffer[256];
DiscordRichPresence discordPresence;
memset(&discordPresence, 0, sizeof(discordPresence));
std::string P = "Playing with friends!"; ///to be revisited
discordPresence.state = P.c_str();
//sprintf(buffer, "Frustration level: %d", FrustrationLevel);
//discordPresence.details = buffer;
discordPresence.startTimestamp = StartTime;
//discordPresence.endTimestamp = time(0) + 5 * 60;
discordPresence.largeImageKey = "mainlogo";
//discordPresence.smallImageKey = "logo";
//discordPresence.partyId = "party1234";
//discordPresence.partySize = 1;
//discordPresence.partyMax = 6;
//discordPresence.matchSecret = "xyzzy";
//discordPresence.joinSecret = "join";
//discordPresence.spectateSecret = "look";
//discordPresence.instance = 0;
Discord_UpdatePresence(&discordPresence);
//}
//else {
// Discord_ClearPresence();
//}
}
void handleDiscordReady(const DiscordUser* User){
DiscordInfo = new DInfo{
User->username,
User->discriminator,
User->userId
};
}
void discordInit(){
DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers));
handlers.ready = handleDiscordReady;
/*handlers.disconnected = handleDiscordDisconnected;
handlers.errored = handleDiscordError;
handlers.joinGame = handleDiscordJoin;
handlers.spectateGame = handleDiscordSpectate;
handlers.joinRequest = handleDiscordJoinRequest;*/
Discord_Initialize("629743237988352010", &handlers, 1,nullptr);
}
[[noreturn]] void Loop(){
StartTime = time(nullptr);
while (true) {
updateDiscordPresence();
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
Discord_RunCallbacks();
if(DiscordInfo == nullptr){
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}else std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
void DMain(){
discordInit();
Loop();
}
std::string GetDName(){
return DiscordInfo->Name;
}
std::string GetDTag(){
return DiscordInfo->Tag;
}
std::string GetDID(){
return DiscordInfo->DID;
}
void DAboard(){
DiscordInfo = nullptr;
}
void ErrorAboard(){
error("Discord timeout! please start the discord app and try again after 30 secs");
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(6);
}
void Discord_Main(){
/*std::thread t1(DMain);
t1.detach();*/
/*info("Connecting to discord client...");
int C = 0;
while(DiscordInfo == nullptr && C < 80){
std::this_thread::sleep_for(std::chrono::milliseconds(300));
C++;
}
if(DiscordInfo == nullptr)ErrorAboard();*/
}

76
src/GameStart.cpp Normal file → Executable file
View File

@@ -6,31 +6,17 @@
/// Created by Anonymous275 on 7/19/2020 /// Created by Anonymous275 on 7/19/2020
/// ///
#if defined(_WIN32)
#include <windows.h>
#include <shlobj.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <pwd.h>
#include <spawn.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include "Logger.h"
#include "Startup.h"
#include <Security/Init.h> #include <Security/Init.h>
#include <filesystem> #include <windows.h>
#include "Startup.h"
#include "Logger.h"
#include <thread> #include <thread>
unsigned long GamePID = 0; unsigned long GamePID = 0;
#if defined(_WIN32)
std::string QueryKey(HKEY hKey,int ID); std::string QueryKey(HKEY hKey,int ID);
std::string GetGamePath(){ std::string GetGamePath(){
static std::string Path; static std::string Path;
if (!Path.empty()) if(!Path.empty())return Path;
return Path;
HKEY hKey; HKEY hKey;
LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive"; LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive";
@@ -41,40 +27,20 @@ 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 + "\\";
return Path; return Path;
} }
#elif defined(__linux__)
std::string GetGamePath() {
// Right now only steam is supported
struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir;
std::string Path = homeDir + "/.local/share/BeamNG.drive/";
std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "/";
return Path;
}
#endif
#if defined(_WIN32)
void StartGame(std::string Dir){ void StartGame(std::string Dir){
BOOL bSuccess = FALSE; BOOL bSuccess = FALSE;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
@@ -95,30 +61,6 @@ void StartGame(std::string Dir) {
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2); exit(2);
} }
#elif defined(__linux__)
void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
char* argv[] = { filename.data(), NULL };
pid_t pid;
posix_spawn_file_actions_t spawn_actions;
posix_spawn_file_actions_init(&spawn_actions);
posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO);
posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO);
int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, argv, environ);
if (result != 0) {
error("Failed to Launch the game! launcher closing soon");
return;
} else {
waitpid(pid, &status, 0);
error("Game Closed! launcher closing soon");
}
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2);
}
#endif
void InitGame(const std::string& Dir){ void InitGame(const std::string& Dir){
if(!Dev){ if(!Dev){

12
src/Logger.cpp Normal file → Executable file
View File

@@ -6,11 +6,11 @@
/// Created by Anonymous275 on 7/17/2020 /// Created by Anonymous275 on 7/17/2020
/// ///
#include "Logger.h"
#include "Startup.h" #include "Startup.h"
#include <chrono> #include "Logger.h"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <chrono>
#include <thread> #include <thread>
std::string getDate() { std::string getDate() {
@@ -39,8 +39,7 @@ void InitLog() {
LFS.open(GetEP() + "Launcher.log"); LFS.open(GetEP() + "Launcher.log");
if(!LFS.is_open()){ if(!LFS.is_open()){
error("logger file init failed!"); error("logger file init failed!");
} else }else LFS.close();
LFS.close();
} }
void addToLog(const std::string& Line){ void addToLog(const std::string& Line){
std::ofstream LFS; std::ofstream LFS;
@@ -54,10 +53,9 @@ void info(const std::string& toPrint) {
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::cout << Print;
}
addToLog(Print); addToLog(Print);
} }
void warn(const std::string& toPrint){ void warn(const std::string& toPrint){
@@ -75,7 +73,7 @@ void fatal(const std::string& toPrint) {
std::cout << Print; std::cout << Print;
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";

186
src/Network/Core.cpp Normal file → Executable file
View File

@@ -5,31 +5,19 @@
/// ///
/// Created by Anonymous275 on 7/20/2020 /// Created by Anonymous275 on 7/20/2020
/// ///
#include "Http.h"
#include "Network/network.hpp" #include "Network/network.h"
#include "Security/Init.h" #include "Security/Init.h"
#include <cstdlib>
#include <regex> #include <regex>
#if defined(_WIN32) #include "Http.h"
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include <cstring>
#include <errno.h>
#include <netdb.h>
#include <spawn.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include "Logger.h"
#include "Startup.h" #include "Startup.h"
#include <charconv> #include "Logger.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <set> #include <charconv>
#include <thread> #include <thread>
#include <set>
extern int TraceBack; extern int TraceBack;
std::set<std::string>* ConfList = nullptr; std::set<std::string>* ConfList = nullptr;
@@ -39,41 +27,18 @@ bool Terminate = false;
bool LoginAuth = false; bool LoginAuth = false;
std::string Username = ""; std::string Username = "";
std::string UserRole = ""; std::string UserRole = "";
int UserID = -1;
std::string UlStatus; std::string UlStatus;
std::string MStatus; std::string MStatus;
bool ModLoaded; bool ModLoaded;
int ping = -1; int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
bool SecurityWarning() {
confirmed = -1;
CoreSend("WMODS_FOUND");
while (confirmed == -1)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (confirmed == 1)
return true;
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
return false;
}
void StartSync(const std::string &Data){ void StartSync(const std::string &Data){
std::string IP = GetAddr(Data.substr(1,Data.find(':')-1)); std::string IP = GetAddr(Data.substr(1,Data.find(':')-1));
if(IP.find('.') == -1){ if(IP.find('.') == -1){
if (IP == "DNS") if(IP == "DNS")UlStatus ="UlConnection Failed! (DNS Lookup Failed)";
UlStatus = "UlConnection Failed! (DNS Lookup Failed)"; else UlStatus = "UlConnection Failed! (WSA failed to start)";
else ListOfMods = "-";
UlStatus = "UlConnection Failed! (WSA failed to start)";
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
CheckLocalKey(); CheckLocalKey();
@@ -87,25 +52,17 @@ void StartSync(const std::string& Data) {
info("Connecting to server"); info("Connecting to server");
} }
void CoreSend(std::string data) {
if (CoreSocket != -1) {
int res = send(CoreSocket, (data + "\n").c_str(), int(data.size()) + 1, 0);
if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
}
}
}
bool IsAllowedLink(const std::string& Link) { bool IsAllowedLink(const std::string& Link) {
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg|patreon\.com\/BeamMP))"); std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg))");
std::smatch link_match; std::smatch link_match;
return std::regex_search(Link,link_match, link_pattern) && link_match.position() == 0; return std::regex_search(Link,link_match, link_pattern) && link_match.position() == 0;
} }
bool ModWarningConfirmed = false;
void Parse(std::string Data,SOCKET CSocket){ void Parse(std::string Data,SOCKET CSocket){
char Code = Data.at(0), SubCode = 0; char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1) if(Data.length() > 1)SubCode = Data.at(1);
SubCode = Data.at(1);
switch (Code){ switch (Code){
case 'A': case 'A':
Data = Data.substr(0,1); Data = Data.substr(0,1);
@@ -117,54 +74,46 @@ void Parse(std::string Data, SOCKET CSocket) {
Data = Code + HTTP::Get("https://backend.beammp.com/servers-info"); Data = Code + HTTP::Get("https://backend.beammp.com/servers-info");
break; break;
case 'C': case 'C':
ListOfMods.clear();
StartSync(Data); StartSync(Data);
Data.clear(); while(ListOfMods.empty() && !Terminate){
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if(ListOfMods == "-")Data = "L";
else Data = "L"+ListOfMods;
break; break;
case 'O': //open default browser with URL case 'O': //open default browser with URL
if(IsAllowedLink(Data.substr(1))) { if(IsAllowedLink(Data.substr(1))) {
#if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid;
auto arg = Data.substr(1);
char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) {
debug("Browser PID: " + std::to_string(pid));
// we don't wait for it to exit, because we just don't care.
// typically, you'd waitpid() here.
} else {
error("Failed to open the following link in the browser (error follows below): " + arg);
error(std::string("posix_spawn: ") + strerror(status));
}
} else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1));
}
#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", Data.substr(1).c_str(), nullptr, nullptr,SW_SHOW); ///TODO: Look at when working on linux port
#endif
info("Opening Link \"" + Data.substr(1) + "\""); info("Opening Link \"" + Data.substr(1) + "\"");
} }
Data.clear(); Data.clear();
break; break;
// response to "WMODS_FOUND" message, either Y (yes ok) or N (no)
case 'W': {
if (SubCode == 'Y') {
ModWarningConfirmed = true;
} else if (SubCode == 'N') {
ModWarningConfirmed = false;
NetReset();
Terminate = true;
TCPTerminate = true;
}
}
case 'P': case 'P':
Data = Code + std::to_string(ProxyPort); Data = Code + std::to_string(ProxyPort);
break; break;
case 'U': case 'U':
if (SubCode == 'l') if(SubCode == 'l')Data = UlStatus;
Data = UlStatus;
if(SubCode == 'p'){ if(SubCode == 'p'){
if(ping > 800){ if(ping > 800){
Data = "Up-2"; Data = "Up-2";
} else }else Data = "Up" + std::to_string(ping);
Data = "Up" + std::to_string(ping);
} }
if(!SubCode){ if(!SubCode){
std::string Ping; std::string Ping;
if (ping > 800) if(ping > 800)Ping = "-2";
Ping = "-2"; else Ping = std::to_string(ping);
else
Ping = std::to_string(ping);
Data = std::string(UlStatus) + "\n" + "Up" + Ping; Data = std::string(UlStatus) + "\n" + "Up" + Ping;
} }
break; break;
@@ -178,10 +127,7 @@ void Parse(std::string Data, SOCKET CSocket) {
TCPTerminate = true; TCPTerminate = true;
ping = -1; ping = -1;
} }
if (SubCode == 'G') { if(SubCode == 'G')exit(2);
debug("Closing via 'G' packet");
exit(2);
}
Data.clear(); Data.clear();
break; break;
case 'R': //will send mod name case 'R': //will send mod name
@@ -205,23 +151,11 @@ void Parse(std::string Data, SOCKET CSocket) {
if (!UserRole.empty()) { if (!UserRole.empty()) {
Auth["role"] = UserRole; Auth["role"] = UserRole;
} }
if (UserID != -1) {
Auth["id"] = UserID;
}
Data = "N" + Auth.dump(); Data = "N" + Auth.dump();
}else{ }else{
Data = "N" + Login(Data.substr(Data.find(':') + 1)); Data = "N" + Login(Data.substr(Data.find(':') + 1));
} }
break; break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
default: default:
Data.clear(); Data.clear();
break; break;
@@ -234,23 +168,21 @@ void Parse(std::string Data, SOCKET CSocket) {
} }
} }
void GameHandler(SOCKET Client){ void GameHandler(SOCKET Client){
CoreSocket = Client;
int32_t Size,Temp,Rcv; int32_t Size,Temp,Rcv;
char Header[10] = {0}; char Header[10] = {0};
do{ do{
Rcv = 0; Rcv = 0;
do{ do{
Temp = recv(Client,&Header[Rcv],1,0); Temp = recv(Client,&Header[Rcv],1,0);
if (Temp < 1) if(Temp < 1)break;
break;
if(!isdigit(Header[Rcv]) && Header[Rcv] != '>') { if(!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
error("(Core) Invalid lua communication"); error("(Core) Invalid lua communication");
KillSocket(Client); KillSocket(Client);
return; return;
} }
}while(Header[Rcv++] != '>'); }while(Header[Rcv++] != '>');
if (Temp < 1) if(Temp < 1)break;
break;
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){ if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){
debug("(Core) Invalid lua Header -> " + std::string(Header,Rcv)); debug("(Core) Invalid lua Header -> " + std::string(Header,Rcv));
break; break;
@@ -260,14 +192,13 @@ void GameHandler(SOCKET Client) {
do{ do{
Temp = recv(Client,&Ret[Rcv],Size-Rcv,0); Temp = recv(Client,&Ret[Rcv],Size-Rcv,0);
if (Temp < 1) if(Temp < 1)break;
break;
Rcv += Temp; Rcv += Temp;
}while(Rcv < Size); }while(Rcv < Size);
if (Temp < 1) if(Temp < 1)break;
break;
Parse(Ret, Client); std::thread Respond(Parse, Ret, Client);
Respond.detach();
}while(Temp > 0); }while(Temp > 0);
if (Temp == 0) { if (Temp == 0) {
debug("(Core) Connection closing"); debug("(Core) Connection closing");
@@ -287,21 +218,19 @@ void localRes() {
} }
ConfList = new std::set<std::string>; ConfList = new std::set<std::string>;
} }
uint64_t TheClientSocket;
void CoreMain() { void CoreMain() {
debug("Core Network on start!"); debug("Core Network on start!");
WSADATA wsaData;
SOCKET LSocket,CSocket; SOCKET LSocket,CSocket;
struct addrinfo *res = nullptr; struct addrinfo *res = nullptr;
struct addrinfo hints{}; struct addrinfo hints{};
int iRes; int iRes = WSAStartup(514, &wsaData); //2.2
#ifdef _WIN32 if (iRes)debug("WSAStartup failed with error: " + std::to_string(iRes));
WSADATA wsaData;
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes)
debug("WSAStartup failed with error: " + std::to_string(iRes));
#endif
ZeroMemory(&hints, sizeof(hints)); ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP; hints.ai_protocol = IPPROTO_TCP;
@@ -341,6 +270,7 @@ void CoreMain() {
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError())); error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
continue; continue;
} }
TheClientSocket = CSocket;
localRes(); localRes();
info("Game Connected!"); info("Game Connected!");
GameHandler(CSocket); GameHandler(CSocket);
@@ -349,8 +279,6 @@ void CoreMain() {
KillSocket(LSocket); KillSocket(LSocket);
WSACleanup(); WSACleanup();
} }
#if defined(_WIN32)
int Handle(EXCEPTION_POINTERS *ep){ int Handle(EXCEPTION_POINTERS *ep){
char* hex = new char[100]; char* hex = new char[100];
sprintf_s(hex,100, "%lX", ep->ExceptionRecord->ExceptionCode); sprintf_s(hex,100, "%lX", ep->ExceptionRecord->ExceptionCode);
@@ -358,25 +286,17 @@ int Handle(EXCEPTION_POINTERS* ep) {
delete [] hex; delete [] hex;
return 1; return 1;
} }
#endif
[[noreturn]] void CoreNetwork(){ [[noreturn]] void CoreNetwork(){
while(true) { while(true) {
#if not defined(__MINGW32__) #ifndef __MINGW32__
__try{ __try{
#endif #endif
CoreMain(); CoreMain();
#ifndef __MINGW32__
#if not defined(__MINGW32__) and not defined(__linux__)
}__except(Handle(GetExceptionInformation())){} }__except(Handle(GetExceptionInformation())){}
#elif not defined(__MINGW32__) and defined(__linux__)
}
catch (...) {
except("(Core) Code : " + std::string(strerror(errno)));
}
#endif #endif
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
} }

16
src/Network/DNS.cpp Normal file → Executable file
View File

@@ -7,30 +7,18 @@
/// ///
#include <string> #include <string>
#if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include "Logger.h" #include "Logger.h"
std::string GetAddr(const std::string&IP){ std::string GetAddr(const std::string&IP){
if (IP.find_first_not_of("0123456789.") == -1) if(IP.find_first_not_of("0123456789.") == -1)return IP;
return IP;
hostent* host;
#ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
hostent *host;
if(WSAStartup(514, &wsaData) != 0){ if(WSAStartup(514, &wsaData) != 0){
error("WSA Startup Failed!"); error("WSA Startup Failed!");
WSACleanup(); WSACleanup();
return ""; return "";
} }
#endif
host = gethostbyname(IP.c_str()); host = gethostbyname(IP.c_str());
if(!host){ if(!host){
error("DNS lookup failed! on " + IP); error("DNS lookup failed! on " + IP);

118
src/Network/GlobalHandler.cpp Normal file → Executable file
View File

@@ -5,27 +5,14 @@
/// ///
/// Created by Anonymous275 on 7/25/2020 /// Created by Anonymous275 on 7/25/2020
/// ///
#include "Network/network.hpp" #include "Network/network.h"
#include <memory>
#include <zlib.h>
#if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include "Logger.h" #include "Logger.h"
#include <charconv> #include <charconv>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include <mutex>
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;
@@ -57,12 +44,12 @@ bool CheckBytes(uint32_t Bytes) {
return true; return true;
} }
void GameSend(std::string_view Data) { void GameSend(std::string Data){
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
if (TCPTerminate || !GConnected || CSocket == -1) if(TCPTerminate || !GConnected || CSocket == -1)return;
return;
int32_t Size,Temp,Sent; int32_t Size,Temp,Sent;
Data += '\n';
Size = int32_t(Data.size()); Size = int32_t(Data.size());
Sent = 0; Sent = 0;
#ifdef DEBUG #ifdef DEBUG
@@ -74,40 +61,25 @@ void GameSend(std::string_view Data) {
if(Sent > -1){ if(Sent > -1){
Temp = send(CSocket, &Data[Sent], Size - Sent, 0); Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
} }
if (!CheckBytes(Temp)) if(!CheckBytes(Temp))return;
return;
Sent += Temp; Sent += Temp;
}while(Sent < Size); }while(Sent < Size);
// send separately to avoid an allocation for += "\n"
Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) {
return;
}
} }
void ServerSend(std::string Data, bool Rel){ void ServerSend(std::string Data, bool Rel){
if (Terminate || Data.empty()) if(Terminate || Data.empty())return;
return;
if(Data.find("Zp") != std::string::npos && Data.size() > 500){ if(Data.find("Zp") != std::string::npos && Data.size() > 500){
abort(); abort();
} }
char C = 0; char C = 0;
bool Ack = false; bool Ack = false;
int DLen = int(Data.length()); int DLen = int(Data.length());
if (DLen > 3) if(DLen > 3)C = Data.at(0);
C = Data.at(0); if (C == 'O' || C == 'T')Ack = true;
if (C == 'O' || C == 'T') if(C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')Rel = true;
Ack = true;
if (C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')
Rel = true;
if (compressBound(Data.size()) > 1024)
Rel = true;
if(Ack || Rel){ if(Ack || Rel){
if (Ack || DLen > 1000) if(Ack || DLen > 1000)SendLarge(Data);
SendLarge(Data); else TCPSend(Data,TCPSock);
else }else UDPSend(Data);
TCPSend(Data, TCPSock);
} else
UDPSend(Data);
if (DLen > 1000) { if (DLen > 1000) {
debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : " debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : "
@@ -142,20 +114,15 @@ void NetReset() {
} }
SOCKET SetupListener(){ SOCKET SetupListener(){
if (GSocket != -1) if(GSocket != -1)return GSocket;
return GSocket;
struct addrinfo *result = nullptr; struct addrinfo *result = nullptr;
struct addrinfo hints{}; struct addrinfo hints{};
int iRes;
#ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
iRes = WSAStartup(514, &wsaData); // 2.2 int iRes = WSAStartup(514, &wsaData); //2.2
if (iRes != 0) { if (iRes != 0) {
error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes)); error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes));
return -1; return -1;
} }
#endif
ZeroMemory(&hints, sizeof(hints)); ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
@@ -199,19 +166,15 @@ void AutoPing() {
} }
} }
int ClientID = -1; int ClientID = -1;
void ParserAsync(std::string_view Data) { void ParserAsync(const std::string& Data){
if (Data.empty()) if(Data.empty())return;
return;
char Code = Data.at(0),SubCode = 0; char Code = Data.at(0),SubCode = 0;
if (Data.length() > 1) if(Data.length() > 1)SubCode = Data.at(1);
SubCode = Data.at(1);
switch (Code) { switch (Code) {
case 'p': case 'p':
PingEnd = std::chrono::high_resolution_clock::now(); PingEnd = std::chrono::high_resolution_clock::now();
if (PingStart > PingEnd) if(PingStart > PingEnd)ping = 0;
ping = 0; else ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd-PingStart).count());
else
ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd - PingStart).count());
return; return;
case 'M': case 'M':
MStatus = Data; MStatus = Data;
@@ -222,7 +185,7 @@ void ParserAsync(std::string_view Data) {
} }
GameSend(Data); GameSend(Data);
} }
void ServerParser(std::string_view Data) { void ServerParser(const std::string& Data){
ParserAsync(Data); ParserAsync(Data);
} }
void NetMain(const std::string& IP, int Port){ void NetMain(const std::string& IP, int Port){
@@ -235,8 +198,6 @@ void NetMain(const std::string& IP, int Port) {
} }
void TCPGameServer(const std::string& IP, int Port){ void TCPGameServer(const std::string& IP, int Port){
GSocket = SetupListener(); GSocket = SetupListener();
std::unique_ptr<std::thread> ClientThread {};
std::unique_ptr<std::thread> NetMainThread {};
while (!TCPTerminate && GSocket != -1){ while (!TCPTerminate && GSocket != -1){
debug("MAIN LOOP OF GAME SERVER"); debug("MAIN LOOP OF GAME SERVER");
GConnected = false; GConnected = false;
@@ -248,7 +209,8 @@ void TCPGameServer(const std::string& IP, int Port) {
break; break;
} }
if(CServer) { if(CServer) {
ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port); std::thread Client(TCPClientMain, IP, Port);
Client.detach();
} }
CSocket = accept(GSocket, nullptr, nullptr); CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) { if (CSocket == -1) {
@@ -258,7 +220,8 @@ void TCPGameServer(const std::string& IP, int Port) {
debug("(Proxy) Game Connected!"); debug("(Proxy) Game Connected!");
GConnected = true; GConnected = true;
if(CServer){ if(CServer){
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port); std::thread t1(NetMain, IP, Port);
t1.detach();
CServer = false; CServer = false;
} }
int32_t Size,Temp,Rcv; int32_t Size,Temp,Rcv;
@@ -270,11 +233,9 @@ void TCPGameServer(const std::string& IP, int Port) {
do{ do{
Temp = recv(CSocket,&Header[Rcv],1,0); Temp = recv(CSocket,&Header[Rcv],1,0);
if (Temp < 1 || TCPTerminate) if(Temp < 1 || TCPTerminate)break;
break;
}while(Header[Rcv++] != '>'); }while(Header[Rcv++] != '>');
if (Temp < 1 || TCPTerminate) if(Temp < 1 || TCPTerminate)break;
break;
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){ if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){
debug("(Game) Invalid lua Header -> " + std::string(Header,Rcv)); debug("(Game) Invalid lua Header -> " + std::string(Header,Rcv));
break; break;
@@ -283,35 +244,20 @@ void TCPGameServer(const std::string& IP, int Port) {
Rcv = 0; Rcv = 0;
do{ do{
Temp = recv(CSocket,&Ret[Rcv],Size-Rcv,0); Temp = recv(CSocket,&Ret[Rcv],Size-Rcv,0);
if (Temp < 1) if(Temp < 1)break;
break;
Rcv += Temp; Rcv += Temp;
}while(Rcv < Size && !TCPTerminate); }while(Rcv < Size && !TCPTerminate);
if (Temp < 1 || TCPTerminate) if(Temp < 1 || TCPTerminate)break;
break;
ServerSend(Ret,false); ServerSend(Ret,false);
}while(Temp > 0 && !TCPTerminate); }while(Temp > 0 && !TCPTerminate);
if (Temp == 0) if(Temp == 0)debug("(Proxy) Connection closing");
debug("(Proxy) Connection closing"); else debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
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 != SOCKET_ERROR)KillSocket(CSocket);
debug("Waiting for client thread");
ClientThread->join();
debug("Client thread done");
}
if (NetMainThread) {
debug("Waiting for net main thread");
NetMainThread->join();
debug("Net main thread done");
}
if (CSocket != SOCKET_ERROR)
KillSocket(CSocket);
debug("END OF GAME SERVER"); debug("END OF GAME SERVER");
} }

298
src/Network/Http.cpp Normal file → Executable file
View File

@@ -5,130 +5,107 @@
/// ///
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "Http.h"
#include <Logger.h>
#include <Network/network.hpp>
#include <Startup.h>
#include <Utils.h>
#include <cmath>
#include <curl/curl.h>
#include <curl/easy.h>
#include <filesystem>
#include <fstream>
#include <httplib.h>
#include <iostream> #include <iostream>
#include <Logger.h>
#include <fstream>
#include "Http.h"
#include <mutex> #include <mutex>
#include <nlohmann/json.hpp> #include <cmath>
#include <httplib.h>
void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try {
const std::filesystem::path folder = ".https_debug";
std::filesystem::create_directories(folder);
if (!std::filesystem::exists(folder / "WHAT IS THIS FOLDER.txt")) {
std::ofstream ignore { folder / "WHAT IS THIS FOLDER.txt" };
ignore << "This folder exists to help debug current issues with the backend. Do not share this folder with anyone but BeamMP staff. It contains detailed logs of any failed http requests." << std::endl;
}
const auto file = folder / (method + ".json");
// 1 MB limit
if (std::filesystem::exists(file) && std::filesystem::file_size(file) > 1'000'000) {
std::filesystem::rename(file, file.generic_string() + ".bak");
}
std::ofstream of { file, std::ios::app };
nlohmann::json js {
{ "utc", std::chrono::system_clock::now().time_since_epoch().count() },
{ "target", target },
{ "client_info", {
{ "openssl_verify_result", client.get_openssl_verify_result() },
{ "host", client.host() },
{ "port", client.port() },
{ "socket_open", client.is_socket_open() },
{ "valid", client.is_valid() },
} },
};
if (result) {
auto value = result.value();
js["result"] = {};
js["result"]["body"] = value.body;
js["result"]["status"] = value.status;
js["result"]["headers"] = value.headers;
js["result"]["version"] = value.version;
js["result"]["location"] = value.location;
js["result"]["reason"] = value.reason;
}
of << js.dump();
} catch (const std::exception& e) {
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 error(res->reason);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); // seconds
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";
} }
error("HTTP Get failed on " + to_string(res.error()));
}
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"); error("HTTP Post failed on " + to_string(res.error()));
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
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);
} }
return Ret; Ret = res->body;
}else{
error("HTTP Post failed on " + to_string(res.error()));
}
}
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())return false;
error("Download failed");
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 +120,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();
}

525
src/Network/Resources.cpp Normal file → Executable file
View File

@@ -6,51 +6,41 @@
/// Created by Anonymous275 on 4/11/2020 /// Created by Anonymous275 on 4/11/2020
/// ///
#include "Network/network.hpp" #include "Network/network.h"
#include <chrono>
#include <iomanip>
#include <ios>
#include <mutex>
#include <nlohmann/json.hpp>
#include <openssl/err.h>
#include <openssl/evp.h>
#if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include <arpa/inet.h>
#include <cstring>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Logger.h"
#include "Startup.h"
#include <Utils.h>
#include <atomic>
#include <cmath>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <fstream> #include "Startup.h"
#include <future> #include "Logger.h"
#include <iostream> #include <iostream>
#include <cstring>
#include <fstream>
#include <string>
#include <thread> #include <thread>
#include <atomic>
#include <vector>
#include <future>
#include <cmath>
#include "hashpp.h" extern SOCKET TheClientSocket;
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 { _wmkdir(L"Resources");
fs::create_directories(CachingDirectory);
} catch (const std::exception& e) {
error(std::string("Failed to create caching directory: ") + e.what() + ". This is a fatal error. Please make sure to configure a directory which you have permission to create, read and write from/to.");
std::this_thread::sleep_for(std::chrono::seconds(3));
std::exit(1);
}
} }
} }
void WaitForConfirm(){ void WaitForConfirm(){
@@ -60,6 +50,7 @@ void WaitForConfirm() {
ModLoaded = false; ModLoaded = false;
} }
void Abord(){ void Abord(){
Terminate = true; Terminate = true;
TCPTerminate = true; TCPTerminate = true;
@@ -73,20 +64,15 @@ std::string Auth(SOCKET 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(PublicKey,Sock);
if (Terminate) { if(Terminate)return "";
CoreSend("L");
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,27 +81,22 @@ 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("SR",Sock);
if (Terminate) { if(Terminate)return "";
CoreSend("L");
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("Done",Sock);
info("Done!"); info("Done!");
return ""; return "";
@@ -124,68 +105,41 @@ std::string Auth(SOCKET Sock) {
} }
void UpdateUl(bool D,const std::string& msg){ void UpdateUl(bool D,const std::string& msg){
if (D) if(D)UlStatus = "UlDownloading Resource " + msg;
UlStatus = "UlDownloading Resource " + msg; else UlStatus = "UlLoading Resource " + msg;
else
UlStatus = "UlLoading Resource " + msg;
} }
float DownloadSpeed = 0;
void AsyncUpdate(uint64_t& Rcv,uint64_t Size,const std::string& Name){ void AsyncUpdate(uint64_t& Rcv,uint64_t Size,const std::string& Name){
do { do {
double pr = double(Rcv) / double(Size) * 100; double pr = double(Rcv) / double(Size) * 100;
std::string Per = std::to_string(trunc(pr * 10) / 10); std::string Per = std::to_string(trunc(pr * 10) / 10);
std::string SpeedString = ""; UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)");
if (DownloadSpeed > 0.01) {
std::stringstream ss;
ss << " at " << std::setprecision(1) << std::fixed << DownloadSpeed << " Mbit/s";
SpeedString = ss.str();
}
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)" + SpeedString);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
}while(!Terminate && Rcv < Size); }while(!Terminate && Rcv < Size);
} }
// MICROSOFT, I DONT CARE, WRITE BETTER CODE char* TCPRcvRaw(SOCKET Sock,uint64_t& GRcv, uint64_t Size){
#undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if(Sock == -1){ if(Sock == -1){
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return {}; return nullptr;
} }
std::vector<char> File(Size); char* File = new char[Size];
uint64_t Rcv = 0; uint64_t Rcv = 0;
auto start = std::chrono::high_resolution_clock::now();
int i = 0;
do{ do{
// receive at most some MB at a time int Len = int(Size-Rcv);
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024); if(Len > 1000000)Len = 1000000;
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL); int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if(Temp < 1){ if(Temp < 1){
info(std::to_string(Temp)); info(std::to_string(Temp));
UUl("Socket Closed Code 1"); UUl("Socket Closed Code 1");
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
return {}; delete[] File;
return nullptr;
} }
Rcv += Temp; Rcv += Temp;
GRcv += Temp; GRcv += Temp;
auto end = std::chrono::high_resolution_clock::now();
auto difference = end - start;
float bits_per_s = float(Rcv * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
float megabits_per_s = bits_per_s / 1000;
DownloadSpeed = megabits_per_s;
// every 8th iteration print the speed
if (i % 8 == 0) {
debug("Download speed: " + std::to_string(uint32_t(megabits_per_s)) + "Mbit/s");
}
++i;
}while(Rcv < Size && !Terminate); }while(Rcv < Size && !Terminate);
return File; return File;
} }
@@ -219,78 +173,44 @@ SOCKET InitDSock() {
return DSock; return DSock;
} }
std::vector<char> SingleNormalDownload(SOCKET MSock, uint64_t Size, const std::string& Name) { std::string MultiDownload(SOCKET MSock,SOCKET DSock, uint64_t Size, const std::string& Name){
DownloadSpeed = 0;
uint64_t GRcv = 0; uint64_t GRcv = 0, MSize = Size/2, DSize = Size - MSize;
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); }); std::thread Au(AsyncUpdate,std::ref(GRcv), Size, Name);
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, Size); std::packaged_task<char*()> task([&] { return TCPRcvRaw(MSock,GRcv,MSize); });
std::future<char*> f1 = task.get_future();
std::thread Dt(std::move(task));
Dt.detach();
if (MData.empty()) { char* DData = TCPRcvRaw(DSock,GRcv,DSize);
KillSocket(MSock);
Terminate = true;
Au.join();
return {};
}
// ensure that GRcv is good before joining the async update thread if(!DData){
GRcv = MData.size();
if (GRcv != Size) {
error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
Terminate = true;
Au.join();
return {};
}
Au.join();
return MData;
}
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
uint64_t GRcv = 0;
uint64_t MSize = Size / 2;
uint64_t DSize = Size - MSize;
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, MSize);
if (MData.empty()) {
MultiKill(MSock,DSock); MultiKill(MSock,DSock);
Terminate = true; return "";
Au.join();
return {};
} }
const std::vector<char> DData = TCPRcvRaw(DSock, GRcv, DSize); f1.wait();
char* MData = f1.get();
if (DData.empty()) { if(!MData){
MultiKill(MSock,DSock); MultiKill(MSock,DSock);
Terminate = true; return "";
Au.join();
return {};
} }
// ensure that GRcv is good before joining the async update thread if(Au.joinable())Au.join();
GRcv = MData.size() + DData.size();
if (GRcv != Size) {
error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
Terminate = true;
Au.join();
return {};
}
Au.join();
std::vector<char> Result {}; ///omg yes very ugly my god but i was in a rush will revisit
Result.insert(Result.begin(), MData.begin(), MData.end()); std::string Ret(Size,0);
Result.insert(Result.end(), DData.begin(), DData.end()); memcpy_s(&Ret[0],MSize,MData,MSize);
return Result; delete[]MData;
memcpy_s(&Ret[MSize],DSize,DData,DSize);
delete[]DData;
return Ret;
} }
void InvalidResource(const std::string& File){ void InvalidResource(const std::string& File){
@@ -299,255 +219,45 @@ void InvalidResource(const std::string& File) {
Terminate = true; Terminate = true;
} }
std::string GetSha256HashReallyFast(const std::string& filename) {
try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
uint8_t sha256_value[EVP_MAX_MD_SIZE];
md = EVP_sha256();
if (md == nullptr) {
throw std::runtime_error("EVP_sha256() failed");
}
mdctx = EVP_MD_CTX_new();
if (mdctx == nullptr) {
throw std::runtime_error("EVP_MD_CTX_new() failed");
}
if (!EVP_DigestInit_ex2(mdctx, md, NULL)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestInit_ex2() failed");
}
std::ifstream stream(filename, std::ios::binary);
const size_t FileSize = std::filesystem::file_size(filename);
size_t Read = 0;
std::vector<char> Data;
while (Read < FileSize) {
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
size_t RealDataSize = Data.size();
stream.read(Data.data(), std::streamsize(Data.size()));
if (stream.eof() || stream.fail()) {
RealDataSize = size_t(stream.gcount());
}
Data.resize(RealDataSize);
if (RealDataSize == 0) {
break;
}
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestUpdate() failed");
}
Read += RealDataSize;
}
unsigned int sha256_len = 0;
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestFinal_ex() failed");
}
EVP_MD_CTX_free(mdctx);
std::string result;
for (size_t i = 0; i < sha256_len; i++) {
char buf[3];
sprintf(buf, "%02x", sha256_value[i]);
buf[2] = 0;
result += buf;
}
return result;
} catch (const std::exception& e) {
error("Sha256 hashing of '" + filename + "' failed: " + e.what());
return "";
}
}
struct ModInfo {
static std::vector<ModInfo> ParseModInfosFromPacket(const std::string& packet) {
std::vector<ModInfo> modInfos;
try {
auto json = nlohmann::json::parse(packet);
for (const auto& entry : json) {
ModInfo modInfo {
.FileName = entry["file_name"],
.FileSize = entry["file_size"],
.Hash = entry["hash"],
.HashAlgorithm = entry["hash_algorithm"],
};
modInfos.push_back(modInfo);
}
} catch (const std::exception& e) {
debug(std::string("Failed to receive mod list: ") + e.what());
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
return {};
}
return modInfos;
}
std::string FileName;
size_t FileSize;
std::string Hash;
std::string HashAlgorithm;
};
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
if (!SecurityWarning())
return;
info("Checking Resources...");
CheckForDir();
std::string t;
for (const auto& mod : ModInfos) {
t += mod.FileName + ";";
}
if (t.empty())
CoreSend("L");
else
CoreSend("L" + t);
t.clear();
info("Syncing...");
int ModNo = 1;
int TotalMods = ModInfos.size();
for (auto ModInfoIter = ModInfos.begin(), AlsoModInfoIter = ModInfos.begin(); ModInfoIter != ModInfos.end() && !Terminate; ++ModInfoIter, ++AlsoModInfoIter) {
if (ModInfoIter->Hash.length() < 8 || ModInfoIter->HashAlgorithm != "sha256") {
error("Unsupported hash algorithm or invalid hash for '" + ModInfoIter->FileName + "'");
Terminate = true;
return;
}
auto FileName = std::filesystem::path(ModInfoIter->FileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string();
auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string();
if (fs::exists(PathToSaveTo) && GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) {
debug("Mod '" + FileName + "' found in cache");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
auto modname = ModInfoIter->FileName;
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
debug("Mod name: " + modname);
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / modname;
std::string tmp_name = name.string();
tmp_name += ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
continue;
}
WaitForConfirm();
continue;
}
CheckForDir();
std::string FName = ModInfoIter->FileName;
do {
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + ModInfoIter->FileName, Sock);
std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) {
Terminate = true;
UUl("Server cannot find " + FName);
break;
}
std::string Name = std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName;
std::vector<char> DownloadedFile = SingleNormalDownload(Sock, ModInfoIter->FileSize, Name);
if (Terminate)
break;
UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName);
// 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
OutFile.flush();
}
// 2. verify size
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
Terminate = true;
}
} while (fs::file_size(PathToSaveTo) != ModInfoIter->FileSize && !Terminate);
if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
// Linux version of the game doesnt support uppercase letters in mod names
#if defined(__linux__)
for (char& c : FName) {
c = ::tolower(c);
}
#endif
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
}
WaitForConfirm();
++ModNo;
}
if (!Terminate) {
TCPSend("Done", Sock);
info("Done!");
} else {
UlStatus = "Ulstart";
info("Connection Terminated!");
}
}
void SyncResources(SOCKET Sock){ void SyncResources(SOCKET Sock){
std::string Ret = Auth(Sock); std::string Ret = Auth(Sock);
if(Ret.empty())return;
if (Ret.starts_with("R")) { ModWarningConfirmed = false;
debug("This server is likely outdated, not trying to parse new mod info format"); Terminate = false;
} else {
auto ModInfos = ModInfo::ParseModInfosFromPacket(Ret);
if (!ModInfos.empty()) { std::string Data = "WMODS_FOUND";
NewSyncResources(Sock, Ret, ModInfos); send(TheClientSocket, (Data + "\n").c_str(), int(Data.size())+1, 0);
return;
} while (!Terminate && !ModWarningConfirmed) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
if (Ret.empty()) if (!ModWarningConfirmed) {
return; UlStatus = "UlMods rejected!";
info("Mods rejected by user!");
if (!SecurityWarning()) // game has already cancelled by now
return; 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())ListOfMods = "-";
CoreSend("L"); else ListOfMods = t;
else
CoreSend("L" + 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('/');
@@ -556,42 +266,29 @@ void SyncResources(SOCKET Sock) {
InvalidResource(*FN); InvalidResource(*FN);
return; return;
} }
if (pos == std::string::npos) if (pos == std::string::npos)continue;
continue;
Amount++; Amount++;
} }
if (!FNames.empty()) if(!FNames.empty())info("Syncing...");
info("Syncing...");
SOCKET DSock = InitDSock(); SOCKET DSock = InitDSock();
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)continue;
if (FS->find_first_not_of("0123456789") != std::string::npos) if (fs::file_size(a) == std::stoull(*FS)){
continue; UpdateUl(false,std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/')));
if (fs::file_size(PathToSaveTo) == FileSize) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
std::this_thread::sleep_for(std::chrono::milliseconds(50)); 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 name = GetGamePath() + "mods/multiplayer" + a.substr(a.find_last_of('/'));
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
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()));
@@ -600,13 +297,11 @@ void SyncResources(SOCKET Sock) {
} }
WaitForConfirm(); WaitForConfirm();
continue; continue;
} else }else remove(a.c_str());
remove(PathToSaveTo.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("f" + *FN,Sock); TCPSend("f" + *FN,Sock);
std::string Data = TCPRcv(Sock); std::string Data = TCPRcv(Sock);
@@ -618,40 +313,26 @@ 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");
} }
fs::copy_file(a,GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
// 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, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
} }
WaitForConfirm(); WaitForConfirm();
} }
KillSocket(DSock); KillSocket(DSock);
if(!Terminate){ if(!Terminate){
TCPSend("Done",Sock); TCPSend("Done",Sock);

68
src/Network/VehicleData.cpp Normal file → Executable file
View File

@@ -5,89 +5,59 @@
/// ///
/// Created by Anonymous275 on 5/8/2020 /// Created by Anonymous275 on 5/8/2020
/// ///
#include "Network/network.hpp"
#include "Zlib/Compressor.h" #include "Zlib/Compressor.h"
#include <stdexcept> #include "Network/network.h"
#if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Logger.h" #include "Logger.h"
#include <array>
#include <string> #include <string>
#include <set>
SOCKET UDPSock = -1; SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr; sockaddr_in* ToServer = nullptr;
void UDPSend(std::string Data){ void UDPSend(std::string Data){
if (ClientID == -1 || UDPSock == -1) if(ClientID == -1 || UDPSock == -1)return;
return;
if(Data.length() > 400){ if(Data.length() > 400){
auto res = Comp(std::span<char>(Data.data(), Data.size())); std::string CMP(Comp(Data));
Data = "ABG:" + std::string(res.data(), res.size()); Data = "ABG:" + CMP;
} }
std::string Packet = char(ClientID+1) + std::string(":") + Data; std::string Packet = char(ClientID+1) + std::string(":") + Data;
int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer)); int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer));
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(std::string Data){
if(Data.length() > 400){ if(Data.length() > 400){
auto res = Comp(std::span<char>(Data.data(), Data.size())); std::string CMP(Comp(Data));
Data = "ABG:" + std::string(res.data(), res.size()); Data = "ABG:" + CMP;
} }
TCPSend(Data,TCPSock); TCPSend(Data,TCPSock);
} }
void UDPParser(std::string_view Packet) { void UDPParser(std::string Packet){
if(Packet.substr(0,4) == "ABG:"){ if(Packet.substr(0,4) == "ABG:"){
auto substr = Packet.substr(4); Packet = DeComp(Packet.substr(4));
try {
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket);
} catch (const std::runtime_error& err) {
error("Error in decompression of UDP, ignoring");
} }
} else {
ServerParser(Packet); ServerParser(Packet);
} }
}
void UDPRcv(){ void UDPRcv(){
sockaddr_in FromServer{}; sockaddr_in FromServer{};
#if defined(_WIN32)
int clientLength = sizeof(FromServer); int clientLength = sizeof(FromServer);
#elif defined(__linux__)
socklen_t clientLength = sizeof(FromServer);
#endif
ZeroMemory(&FromServer, clientLength); ZeroMemory(&FromServer, clientLength);
static thread_local std::array<char, 10240> Ret {}; std::string Ret(10240,0);
if (UDPSock == -1) if(UDPSock == -1)return;
return; int32_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&FromServer, &clientLength);
int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength); if (Rcv == SOCKET_ERROR)return;
if (Rcv == SOCKET_ERROR) UDPParser(Ret.substr(0,Rcv));
return;
Ret[Rcv] = 0;
UDPParser(std::string_view(Ret.data(), Rcv));
} }
void UDPClientMain(const std::string& IP,int Port){ void UDPClientMain(const std::string& IP,int Port){
#ifdef _WIN32
WSADATA data; WSADATA data;
if (WSAStartup(514, &data)){ if (WSAStartup(514, &data)){
error("Can't start Winsock!"); error("Can't start Winsock!");
return; return;
} }
#endif
delete ToServer; delete ToServer;
ToServer = new sockaddr_in; ToServer = new sockaddr_in;
ToServer->sin_family = AF_INET; ToServer->sin_family = AF_INET;
@@ -97,11 +67,7 @@ void UDPClientMain(const std::string& IP, int Port) {
GameSend("P"+std::to_string(ClientID)); GameSend("P"+std::to_string(ClientID));
TCPSend("H",TCPSock); TCPSend("H",TCPSock);
UDPSend("p"); UDPSend("p");
debug("Starting UDP receive loop"); while(!Terminate)UDPRcv();
while (!Terminate) {
UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock); KillSocket(UDPSock);
WSACleanup(); WSACleanup();
} }

60
src/Network/VehicleEvent.cpp Normal file → Executable file
View File

@@ -6,24 +6,15 @@
/// Created by Anonymous275 on 5/8/2020 /// Created by Anonymous275 on 5/8/2020
/// ///
#include "Logger.h"
#include <Zlib/Compressor.h>
#include <chrono> #include <chrono>
#include <iostream>
#include <vector> #include <vector>
#include "Logger.h"
#if defined(_WIN32) #include <iostream>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__) #include <Zlib/Compressor.h>
#include <arpa/inet.h>
#include <cstring> #include "Network/network.h"
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Network/network.hpp"
int LastPort; int LastPort;
std::string LastIP; std::string LastIP;
@@ -82,58 +73,53 @@ std::string TCPRcv(SOCKET Sock) {
UUl("Invalid Socket"); UUl("Invalid Socket");
return ""; return "";
} }
int32_t Header, Temp; int32_t Header,BytesRcv = 0,Temp;
std::vector<char> Data(sizeof(Header)); std::vector<char> Data(sizeof(Header));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL); do{
Temp = recv(Sock,&Data[BytesRcv],4-BytesRcv,0);
if(!CheckBytes(Temp)){ if(!CheckBytes(Temp)){
UUl("Socket Closed Code 3"); UUl("Socket Closed Code 3");
return ""; return "";
} }
memcpy(&Header, Data.data(), sizeof(Header)); BytesRcv += Temp;
}while(BytesRcv < 4);
memcpy(&Header,&Data[0],sizeof(Header));
if (!CheckBytes(Temp)) { if(!CheckBytes(BytesRcv)){
UUl("Socket Closed Code 4"); UUl("Socket Closed Code 4");
return ""; return "";
} }
Data.resize(Header);
Data.resize(Header, 0); BytesRcv = 0;
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL); do{
Temp = recv(Sock,&Data[BytesRcv],Header-BytesRcv,0);
if(!CheckBytes(Temp)){ if(!CheckBytes(Temp)){
UUl("Socket Closed Code 5"); UUl("Socket Closed Code 5");
return ""; return "";
} }
BytesRcv += Temp;
}while(BytesRcv < Header);
std::string Ret(Data.data(),Header); std::string Ret(Data.data(),Header);
if (Ret.substr(0, 4) == "ABG:") { if (Ret.substr(0, 4) == "ABG:") {
auto substr = Ret.substr(4); Ret = DeComp(Ret.substr(4));
try {
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
Ret = std::string(res.data(), res.size());
} catch (const std::runtime_error& err) {
// this happens e.g. when we're out of memory, or when we get incomplete data
error("Decompression failed");
return "";
}
} }
#ifdef DEBUG #ifdef DEBUG
//debug("Parsing from server -> " + std::to_string(Ret.size())); //debug("Parsing from server -> " + std::to_string(Ret.size()));
#endif #endif
if (Ret[0] == 'E' || Ret[0] == 'K') if(Ret[0] == 'E' || Ret[0] == 'K')UUl(Ret.substr(1));
UUl(Ret.substr(1));
return Ret; return Ret;
} }
void TCPClientMain(const std::string& IP,int Port){ void TCPClientMain(const std::string& IP,int Port){
LastIP = IP; LastIP = IP;
LastPort = Port; LastPort = Port;
WSADATA wsaData;
SOCKADDR_IN ServerAddr; SOCKADDR_IN ServerAddr;
int RetCode; int RetCode;
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(514, &wsaData); //2.2 WSAStartup(514, &wsaData); //2.2
#endif
TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(TCPSock == -1){ if(TCPSock == -1){
@@ -141,7 +127,6 @@ void TCPClientMain(const std::string& IP, int Port) {
WSACleanup(); WSACleanup();
return; return;
} }
ServerAddr.sin_family = AF_INET; ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port); ServerAddr.sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr); inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
@@ -152,7 +137,6 @@ void TCPClientMain(const std::string& IP, int Port) {
KillSocket(TCPSock); KillSocket(TCPSock);
WSACleanup(); WSACleanup();
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
info("Connected!"); info("Connected!");
@@ -168,8 +152,6 @@ void TCPClientMain(const std::string& IP, int Port) {
if(KillSocket(TCPSock) != 0) if(KillSocket(TCPSock) != 0)
debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError())); debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError()));
#ifdef _WIN32
if(WSACleanup() != 0) if(WSACleanup() != 0)
debug("(TCP) Client: WSACleanup() failed!..."); debug("(TCP) Client: WSACleanup() failed!...");
#endif
} }

234
src/Security/BeamNG.cpp Normal file → Executable file
View File

@@ -7,16 +7,10 @@
/// ///
#include <filesystem> #include <filesystem>
#if defined(_WIN32)
#include <windows.h> #include <windows.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <pwd.h>
#include <unistd.h>
#include <vector>
#endif
#include "Logger.h" #include "Logger.h"
#include <fstream> #include <fstream>
#include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -28,20 +22,32 @@ std::string GameDir;
void lowExit(int code){ void lowExit(int code){
TraceBack = 0; TraceBack = 0;
std::string msg = "Failed to find the game please launch it. Report this if the issue persists code "; std::string msg =
"Failed to find the game please launch it. Report this if the issue persists code ";
error(msg+std::to_string(code)); error(msg+std::to_string(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){
std::string GetGameDir() { TraceBack = 0;
#if defined(_WIN32) std::string msg =
return GameDir.substr(0, GameDir.find_last_of('\\')); "Sorry. We do not support cracked copies report this if you believe this is a mistake code ";
#elif defined(__linux__) error(msg+std::to_string(code));
return GameDir.substr(0, GameDir.find_last_of('/')); std::this_thread::sleep_for(std::chrono::seconds(10));
#endif exit(3);
}
void SteamExit(int code){
TraceBack = 0;
std::string msg =
"Illegal steam modifications detected report this if you believe this is a mistake code ";
error(msg+std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(4);
}*/
std::string GetGameDir(){
//if(TraceBack != 4)Exit(0);
return GameDir.substr(0,GameDir.find_last_of('\\'));
} }
#ifdef _WIN32
LONG OpenKey(HKEY root,const char* path,PHKEY hKey){ LONG OpenKey(HKEY root,const char* path,PHKEY hKey){
return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey); return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey);
} }
@@ -104,30 +110,18 @@ std::string QueryKey(HKEY hKey, int ID) {
std::string key = achValue; std::string key = achValue;
switch (ID){ switch (ID){
case 1: case 1: if(key == "SteamExe"){
if (key == "SteamExe") {
auto p = data.find_last_of("/\\"); auto p = data.find_last_of("/\\");
if(p != std::string::npos){ if(p != std::string::npos){
return data.substr(0,p); return data.substr(0,p);
} }
} }
break; break;
case 2: case 2: if(key == "Name" && data == "BeamNG.drive")return data;break;
if (key == "Name" && data == "BeamNG.drive") case 3: if(key == "rootpath")return data;break;
return data; case 4: if(key == "userpath_override")return data;
break; case 5: if(key == "Local AppData")return data;
case 3: default: break;
if (key == "rootpath")
return data;
break;
case 4:
if (key == "userpath_override")
return data;
case 5:
if (key == "Local AppData")
return data;
default:
break;
} }
} }
} }
@@ -135,8 +129,6 @@ std::string QueryKey(HKEY hKey, int ID) {
delete [] buffer; delete [] buffer;
return ""; return "";
} }
#endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
bool NameValid(const std::string& N){ bool NameValid(const std::string& N){
@@ -158,47 +150,160 @@ 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(){
#if defined(_WIN32)
//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);*/
std::string Result; std::string Result;
std::string K3 = R"(Software\BeamNG\BeamNG.drive)"; std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
HKEY hKey; HKEY hKey;
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())lowExit(3);
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive"); //if(IDCheck(Result,T))lowExit(5);
lowExit(3);
}
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);
#elif defined(__linux__) //if(TraceBack < 3)exit(-1);
struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir;
// Right now only steam is supported
std::ifstream libraryFolders(homeDir + "/.steam/root/steamapps/libraryfolders.vdf");
auto root = tyti::vdf::read(libraryFolders);
for (auto folderInfo : root.childs) {
if (std::filesystem::exists(folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/")) {
GameDir = folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/";
break;
}
}
#endif
} }
std::string CheckVer(const std::string &dir){ std::string CheckVer(const std::string &dir){
#if defined(_WIN32)
std::string temp,Path = dir + "\\integrity.json"; std::string temp,Path = dir + "\\integrity.json";
#elif defined(__linux__)
std::string temp, Path = dir + "/integrity.json";
#endif
std::ifstream f(Path.c_str(), std::ios::binary); std::ifstream f(Path.c_str(), std::ios::binary);
int Size = int(std::filesystem::file_size(Path)); int Size = int(std::filesystem::file_size(Path));
std::string vec(Size,0); std::string vec(Size,0);
@@ -207,8 +312,7 @@ std::string CheckVer(const std::string& dir) {
vec = vec.substr(vec.find_last_of("version"),vec.find_last_of('"')); vec = vec.substr(vec.find_last_of("version"),vec.find_last_of('"'));
for(const char &a : vec){ for(const char &a : vec){
if (isdigit(a) || a == '.') if(isdigit(a) || a == '.')temp+=a;
temp += a;
} }
return temp; return temp;
} }

31
src/Security/Login.cpp Normal file → Executable file
View File

@@ -6,11 +6,12 @@
/// Created by Anonymous275 on 11/26/2020 /// Created by Anonymous275 on 11/26/2020
/// ///
#include "Http.h"
#include "Logger.h"
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "Http.h"
#include <filesystem>
#include "Logger.h"
#include <fstream>
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string PublicKey; std::string PublicKey;
@@ -18,7 +19,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])){
@@ -27,8 +27,7 @@ void UpdateKey(const char* newKey) {
if(Key.is_open()){ if(Key.is_open()){
Key << newKey; Key << newKey;
Key.close(); Key.close();
} else }else fatal("Cannot write to disk!");
fatal("Cannot write to disk!");
}else if(fs::exists("key")){ }else if(fs::exists("key")){
remove("key"); remove("key");
} }
@@ -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());
} }
@@ -86,8 +81,7 @@ std::string Login(const std::string& fields) {
PublicKey = d["public_key"].get<std::string>(); PublicKey = d["public_key"].get<std::string>();
} }
info("Authentication successful!"); info("Authentication successful!");
} else }else info("Authentication failed!");
info("Authentication failed!");
if(d.contains("message")){ if(d.contains("message")){
d.erase("private_key"); d.erase("private_key");
d.erase("public_key"); d.erase("public_key");
@@ -119,7 +113,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 +128,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);
@@ -145,6 +137,5 @@ void CheckLocalKey() {
warn("Could not open saved key!"); warn("Could not open saved key!");
UpdateKey(nullptr); UpdateKey(nullptr);
} }
} else }else UpdateKey(nullptr);
UpdateKey(nullptr);
} }

243
src/Startup.cpp Normal file → Executable file
View File

@@ -6,25 +6,21 @@
/// Created by Anonymous275 on 7/16/2020 /// Created by Anonymous275 on 7/16/2020
/// ///
#include "zip_file.h"
#include <charconv>
#include <httplib.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string> #include <httplib.h>
#if defined(_WIN32) #include "zip_file.h"
#include <windows.h> #include <windows.h>
#elif defined(__linux__) #include "Discord/discord_info.h"
#include <unistd.h> #include "Network/network.h"
#endif
#include "Http.h"
#include "Logger.h"
#include "Network/network.hpp"
#include "Security/Init.h" #include "Security/Init.h"
#include <filesystem>
#include "Startup.h" #include "Startup.h"
#include "hashpp.h" #include "hashpp.h"
#include <filesystem> #include "Logger.h"
#include <fstream> #include <fstream>
#include <thread> #include <thread>
#include "Http.h"
extern int TraceBack; extern int TraceBack;
bool Dev = false; bool Dev = false;
@@ -32,59 +28,41 @@ int ProxyPort = 0;
namespace fs = std::filesystem; namespace fs = std::filesystem;
struct Version { VersionParser::VersionParser(const std::string& from_string) {
uint8_t major; std::string token;
uint8_t minor; std::istringstream tokenStream(from_string);
uint8_t patch; while (std::getline(tokenStream, token, '.')) {
Version(uint8_t major, uint8_t minor, uint8_t patch); data.emplace_back(std::stol(token));
Version(const std::array<uint8_t, 3>& v); split.emplace_back(token);
};
std::array<uint8_t, 3> VersionStrToInts(const std::string& str) {
std::array<uint8_t, 3> Version;
std::stringstream ss(str);
for (uint8_t& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
bool IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
} else if (Newest.major == Current.major && Newest.minor > Current.minor) {
return true;
} else if (Newest.major == Current.major && Newest.minor == Current.minor && Newest.patch > Current.patch) {
return true;
} else {
return false;
} }
} }
Version::Version(uint8_t major, uint8_t minor, uint8_t patch) std::strong_ordering VersionParser::operator<=>(
: major(major) const VersionParser& rhs) const noexcept {
, minor(minor) size_t const fields = std::min(data.size(), rhs.data.size());
, patch(patch) { } for (size_t i = 0; i != fields; ++i) {
if (data[i] == rhs.data[i]) continue;
Version::Version(const std::array<uint8_t, 3>& v) else if (data[i] < rhs.data[i]) return std::strong_ordering::less;
: Version(v[0], v[1], v[2]) { else return std::strong_ordering::greater;
} }
if (data.size() == rhs.data.size()) return std::strong_ordering::equal;
else if (data.size() > rhs.data.size()) return std::strong_ordering::greater;
else return std::strong_ordering::less;
}
bool VersionParser::operator==(const VersionParser& rhs) const noexcept {
return std::is_eq(*this <=> rhs);
}
std::string GetEN(){ std::string GetEN(){
#if defined(_WIN32)
return "BeamMP-Launcher.exe"; return "BeamMP-Launcher.exe";
#elif defined(__linux__)
return "BeamMP-Launcher";
#endif
} }
std::string GetVer(){ std::string GetVer(){
return "2.2"; return "2.0";
} }
std::string GetPatch(){ std::string GetPatch(){
return ".0"; return ".85";
} }
std::string GetEP(char*P){ std::string GetEP(char*P){
@@ -94,14 +72,12 @@ std::string GetEP(char* P) {
} (); } ();
return Ret; return Ret;
} }
#if defined(_WIN32)
void ReLaunch(int argc,char*args[]){ void ReLaunch(int argc,char*args[]){
std::string Arg; std::string Arg;
for(int c = 2; c <= argc; c++){ for(int c = 2; c <= argc; c++){
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);
@@ -119,42 +95,11 @@ void URelaunch(int argc, char* args[]) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
#elif defined(__linux__)
void ReLaunch(int argc, char* args[]) {
std::string Arg;
for (int c = 2; c <= argc; c++) {
Arg += " ";
Arg += args[c - 1];
}
info("Relaunch!");
system("clear");
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
void URelaunch(int argc, char* args[]) {
std::string Arg;
for (int c = 2; c <= argc; c++) {
Arg += " ";
Arg += args[c - 1];
}
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
#endif
void CheckName(int argc,char* args[]){ void CheckName(int argc,char* args[]){
#if defined(_WIN32)
std::string DN = GetEN(),CDir = args[0],FN = CDir.substr(CDir.find_last_of('\\')+1); std::string DN = GetEN(),CDir = args[0],FN = CDir.substr(CDir.find_last_of('\\')+1);
#elif defined(__linux__)
std::string DN = GetEN(), CDir = args[0], FN = CDir.substr(CDir.find_last_of('/') + 1);
#endif
if(FN != DN){ if(FN != DN){
if (fs::exists(DN)) if(fs::exists(DN))remove(DN.c_str());
remove(DN.c_str()); if(fs::exists(DN))ReLaunch(argc,args);
if (fs::exists(DN))
ReLaunch(argc, args);
std::rename(FN.c_str(), DN.c_str()); std::rename(FN.c_str(), DN.c_str());
URelaunch(argc,args); URelaunch(argc,args);
} }
@@ -170,23 +115,18 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP); std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion))) && !Dev) { if (FileHash != LatestHash && VersionParser(LatestVersion) > VersionParser(GetVer()+GetPatch())) {
info("Launcher update found!"); info("Launcher update found!");
#if defined(__linux__)
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
#else
fs::remove(Back); fs::remove(Back);
fs::rename(EP, Back); fs::rename(EP, Back);
info("Downloading Launcher update " + LatestHash); info("Downloading Launcher update " + LatestHash);
HTTP::Download( HTTP::Download(
"https://backend.beammp.com/builds/launcher?download=true" "https://backend.beammp.com/builds/launcher?download=true"
"&pk=" "&pk=" +
+ PublicKey + "&branch=" + Branch, PublicKey + "&branch=" + Branch,
EP); EP);
URelaunch(argc, args); URelaunch(argc, args);
#endif } else info("Launcher version is up to date");
} else
info("Launcher version is up to date");
TraceBack++; TraceBack++;
} }
@@ -199,17 +139,14 @@ void CustomPort(int argc, char* argv[]) {
warn("Running on custom port : " + std::to_string(DEFAULT_PORT)); warn("Running on custom port : " + std::to_string(DEFAULT_PORT));
} }
} }
if (argc > 2) if(argc > 2)Dev = true;
Dev = true;
} }
} }
#ifdef _WIN32
void LinuxPatch(){ void LinuxPatch(){
HKEY hKey = nullptr; HKEY hKey = nullptr;
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey); LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS || getenv("USER") == nullptr) if (result != ERROR_SUCCESS || getenv("USER") == nullptr)return;
return;
RegCloseKey(hKey); RegCloseKey(hKey);
info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit"); info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit");
info("Applying patches..."); info("Applying patches...");
@@ -231,10 +168,9 @@ void LinuxPatch() {
info("Patched!"); info("Patched!");
} }
#endif
#if defined(_WIN32)
void InitLauncher(int argc, char* argv[]) { void InitLauncher(int argc, char* argv[]) {
system("cls");
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str()); SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog(); InitLog();
CheckName(argc, argv); CheckName(argc, argv);
@@ -242,50 +178,36 @@ void InitLauncher(int argc, char* argv[]) {
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
Discord_Main();
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch()); CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
} }
#elif defined(__linux__)
void InitLauncher(int argc, char* argv[]) {
InitLog();
info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName(argc, argv);
CheckLocalKey();
ConfigInit();
CustomPort(argc, argv);
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
#endif
size_t DirCount(const std::filesystem::path& path){ size_t DirCount(const std::filesystem::path& path){
return (size_t)std::distance(std::filesystem::directory_iterator{path}, std::filesystem::directory_iterator{}); return (size_t)std::distance(std::filesystem::directory_iterator{path}, std::filesystem::directory_iterator{});
} }
void CheckMP(const std::string& Path) { void CheckMP(const std::string& Path) {
if (!fs::exists(Path)) if (!fs::exists(Path))return;
return;
size_t c = DirCount(fs::path(Path)); size_t c = DirCount(fs::path(Path));
try { try {
for (auto& p : fs::directory_iterator(Path)){ for (auto& p : fs::directory_iterator(Path)){
if(p.exists() && !p.is_directory()){ if(p.exists() && !p.is_directory()){
std::string Name = p.path().filename().string(); std::string Name = p.path().filename().string();
for (char& Ch : Name) for(char&Ch : Name)Ch = char(tolower(Ch));
Ch = char(tolower(Ch)); if(Name != "beammp.zip")fs::remove(p.path());
if (Name != "beammp.zip")
fs::remove(p.path());
} }
} }
} catch (...) { } catch (...) {
fatal("We were unable to clean the multiplayer mods folder! Is the game still running or do you have something open in that folder?"); fatal("We were unable to clean the multiplayer mods folder! Is the game still running or do you have something open in that folder?");
} }
} }
void EnableMP(){ void EnableMP(){
std::string File(GetGamePath() + "mods/db.json"); std::string File(GetGamePath() + "mods/db.json");
if (!fs::exists(File)) if(!fs::exists(File))return;
return;
auto Size = fs::file_size(File); auto Size = fs::file_size(File);
if (Size < 2) if(Size < 2)return;
return;
std::ifstream db(File); std::ifstream db(File);
if(db.is_open()) { if(db.is_open()) {
std::string Data(Size, 0); std::string Data(Size, 0);
@@ -314,14 +236,12 @@ 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());
if(!Dev) { if(!Dev) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey); std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower); transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(), LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
[](auto const& c) -> bool { return !std::isalnum(c); }), [](auto const& c ) -> bool { return !std::isalnum(c); } ), LatestHash.end());
LatestHash.end());
try { try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
@@ -331,21 +251,15 @@ void PreGame(const std::string& GamePath) {
}catch(std::exception&e){ }catch(std::exception&e){
fatal(e.what()); fatal(e.what());
} }
#if defined(_WIN32)
std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)"); std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)");
#elif defined(__linux__)
// Linux version of the game cant handle mods with uppercase names
std::string ZipPath(GetGamePath() + R"(mods/multiplayer/beammp.zip)");
#endif
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath); std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath);
if (FileHash != LatestHash) { if (FileHash != LatestHash) {
info("Downloading BeamMP Update " + LatestHash); info("Downloading BeamMP Update " + LatestHash);
HTTP::Download("https://backend.beammp.com/builds/client?download=true" HTTP::Download("https://backend.beammp.com/builds/client?download=true"
"&pk=" "&pk=" + PublicKey + "&branch=" + Branch, ZipPath);
+ PublicKey + "&branch=" + Branch,
ZipPath);
} }
std::string Target(GetGamePath() + "mods/unpacked/beammp"); std::string Target(GetGamePath() + "mods/unpacked/beammp");
@@ -355,3 +269,58 @@ 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();
}

44
src/main.cpp Normal file → Executable file
View File

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

View File

@@ -1,9 +0,0 @@
{
"dependencies": [
"cpp-httplib",
"nlohmann-json",
"zlib",
"openssl",
"curl"
]
}