mirror of
https://github.com/BeamMP/BeamMP-Launcher.git
synced 2026-04-03 06:16:15 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3abf7e6e |
2
.github/workflows/cmake-linux.yml
vendored
2
.github/workflows/cmake-linux.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
vcpkgArguments: 'zlib nlohmann-json openssl cpp-httplib[openssl]'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: 'b5d1a94fb7f88fd835e360fd23a45a09ceedbf48'
|
||||
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-linux
|
||||
|
||||
2
.github/workflows/cmake-windows.yml
vendored
2
.github/workflows/cmake-windows.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
with:
|
||||
vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: 'b5d1a94fb7f88fd835e360fd23a45a09ceedbf48'
|
||||
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
|
||||
vcpkgTriplet: 'x64-windows-static'
|
||||
|
||||
- name: Create Build Environment
|
||||
|
||||
52
README.md
52
README.md
@@ -2,7 +2,57 @@
|
||||
|
||||
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.
|
||||
|
||||
## [Getting started](https://docs.beammp.com/game/getting-started/)
|
||||
**To clone this repository**: `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Launcher.git`
|
||||
|
||||
## How to build for Windows
|
||||
|
||||
Make sure you have the necessary development tools installed:
|
||||
|
||||
[vcpkg](https://vcpkg.io/en/)
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
## How to build for Linux
|
||||
|
||||
Make sure you have `vcpkg` installed, as well as basic development tools, often found in packages, for example:
|
||||
|
||||
- Debian: `sudo apt install build-essential`
|
||||
- Fedora: `sudo dnf groupinstall "Development Tools"`
|
||||
- Arch: `sudo pacman -S base-devel`
|
||||
- openSUSE: `zypper in -t pattern devel-basis`
|
||||
|
||||
### Release
|
||||
|
||||
In the root directory of the project,
|
||||
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
|
||||
2. `cmake --build bin --parallel --config Release`
|
||||
|
||||
### Debug
|
||||
|
||||
In the root directory of the project,
|
||||
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
|
||||
2. `cmake --build bin --parallel`
|
||||
|
||||
## Running out of RAM while building
|
||||
|
||||
Should you run out of RAM while building, you can ommit the `--parallel` intruction, it will then use less RAM due to building only on one CPU thread.
|
||||
|
||||
You can also specify a number of threads to use, for example `--parallel 4` will use four CPU threads, but due to the small project size, you may be faster just omitting `--parallel` instead of trying to find the highest possible multithread number
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -6,14 +6,12 @@
|
||||
|
||||
#pragma once
|
||||
#include "Logger.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include <string>
|
||||
class HTTP {
|
||||
public:
|
||||
static bool Download(const std::string& IP, const beammp_fs_string& Path, const std::string& Hash);
|
||||
static bool Download(const std::string& IP, const std::string& Fields, const std::string& Path);
|
||||
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, const std::string& Fields = "");
|
||||
static bool ProgressBar(size_t c, size_t t);
|
||||
static void StartProxy();
|
||||
public:
|
||||
|
||||
@@ -14,11 +14,4 @@ void debug(const std::string& toPrint);
|
||||
void error(const std::string& toPrint);
|
||||
void info(const std::string& toPrint);
|
||||
void warn(const std::string& toPrint);
|
||||
|
||||
void except(const std::wstring& toPrint);
|
||||
void fatal(const std::wstring& toPrint);
|
||||
void debug(const std::wstring& toPrint);
|
||||
void error(const std::wstring& toPrint);
|
||||
void info(const std::wstring& toPrint);
|
||||
void warn(const std::wstring& toPrint);
|
||||
std::string getDate();
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#ifdef __linux__
|
||||
@@ -14,7 +13,6 @@
|
||||
#include <bits/types/siginfo_t.h>
|
||||
#include <cstdint>
|
||||
#include <sys/ucontext.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
void NetReset();
|
||||
@@ -30,15 +28,13 @@ extern bool Terminate;
|
||||
extern uint64_t UDPSock;
|
||||
extern uint64_t TCPSock;
|
||||
extern std::string Branch;
|
||||
extern std::filesystem::path CachingDirectory;
|
||||
extern bool deleteDuplicateMods;
|
||||
extern std::string CachingDirectory;
|
||||
extern bool TCPTerminate;
|
||||
extern std::string LastIP;
|
||||
extern std::string MStatus;
|
||||
extern std::string UlStatus;
|
||||
extern std::string PublicKey;
|
||||
extern std::string PrivateKey;
|
||||
extern std::string magic;
|
||||
int KillSocket(uint64_t Dead);
|
||||
void UUl(const std::string& R);
|
||||
void UDPSend(std::string Data);
|
||||
|
||||
@@ -19,7 +19,6 @@ struct Options {
|
||||
bool no_download = false;
|
||||
bool no_update = false;
|
||||
bool no_launch = false;
|
||||
const char* user_path = nullptr;
|
||||
const char **game_arguments = nullptr;
|
||||
int game_arguments_length = 0;
|
||||
const char** argv = nullptr;
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
void PreGame(const beammp_fs_string& GamePath);
|
||||
std::string CheckVer(const std::filesystem::path& path);
|
||||
void InitGame(const beammp_fs_string& Dir);
|
||||
beammp_fs_string GetGameDir();
|
||||
void PreGame(const std::string& GamePath);
|
||||
std::string CheckVer(const std::string& path);
|
||||
void InitGame(const std::string& Dir);
|
||||
std::string GetGameDir();
|
||||
void LegitimacyCheck();
|
||||
void CheckLocalKey();
|
||||
@@ -5,17 +5,14 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Utils.h"
|
||||
|
||||
#include <compare>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void InitLauncher();
|
||||
beammp_fs_string GetEP(const beammp_fs_char* P = nullptr);
|
||||
std::filesystem::path GetBP(const beammp_fs_char* P = nullptr);
|
||||
std::filesystem::path GetGamePath();
|
||||
std::string GetEP(const char* P = nullptr);
|
||||
std::string GetGamePath();
|
||||
std::string GetVer();
|
||||
std::string GetPatch();
|
||||
beammp_fs_string GetEN();
|
||||
std::string GetEN();
|
||||
void ConfigInit();
|
||||
|
||||
317
include/Utils.h
317
include/Utils.h
@@ -5,46 +5,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <regex>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#if defined(__linux__)
|
||||
#include <sys/socket.h>
|
||||
#include "linuxfixes.h"
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define beammp_fs_string std::wstring
|
||||
#define beammp_fs_char wchar_t
|
||||
#define beammp_wide(str) L##str
|
||||
#define beammp_stdout std::wcout
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#define beammp_fs_string std::string
|
||||
#define beammp_fs_char char
|
||||
#define beammp_wide(str) str
|
||||
#define beammp_stdout std::cout
|
||||
#endif
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
namespace Utils {
|
||||
inline std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
|
||||
@@ -60,280 +22,5 @@ namespace Utils {
|
||||
if (!s.empty())
|
||||
Val.push_back(s);
|
||||
return Val;
|
||||
}
|
||||
inline std::string ExpandEnvVars(const std::string& input) {
|
||||
std::string result;
|
||||
std::regex envPattern(R"(%([^%]+)%|\$([A-Za-z_][A-Za-z0-9_]*)|\$\{([^}]+)\})");
|
||||
|
||||
std::sregex_iterator begin(input.begin(), input.end(), envPattern);
|
||||
std::sregex_iterator end;
|
||||
|
||||
size_t lastPos = 0;
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
const auto& match = *it;
|
||||
|
||||
result.append(input, lastPos, match.position() - lastPos);
|
||||
|
||||
std::string varName;
|
||||
if (match[1].matched) varName = match[1].str(); // %VAR%
|
||||
else if (match[2].matched) varName = match[2].str(); // $VAR
|
||||
else if (match[3].matched) varName = match[3].str(); // ${VAR}
|
||||
|
||||
if (const char* envValue = std::getenv(varName.c_str())) {
|
||||
result.append(envValue);
|
||||
}
|
||||
|
||||
lastPos = match.position() + match.length();
|
||||
}
|
||||
|
||||
result.append(input, lastPos, input.length() - lastPos);
|
||||
|
||||
return result;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
inline std::wstring ExpandEnvVars(const std::wstring& input) {
|
||||
std::wstring result;
|
||||
std::wregex envPattern(LR"(%([^%]+)%|\$([A-Za-z_][A-Za-z0-9_]*)|\$\{([^}]+)\})");
|
||||
|
||||
std::wsregex_iterator begin(input.begin(), input.end(), envPattern);
|
||||
std::wsregex_iterator end;
|
||||
|
||||
size_t lastPos = 0;
|
||||
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
const auto& match = *it;
|
||||
|
||||
result.append(input, lastPos, match.position() - lastPos);
|
||||
|
||||
std::wstring varName;
|
||||
assert(match.size() == 4 && "Input regex has incorrect amount of capturing groups");
|
||||
if (match[1].matched) varName = match[1].str(); // %VAR%
|
||||
else if (match[2].matched) varName = match[2].str(); // $VAR
|
||||
else if (match[3].matched) varName = match[3].str(); // ${VAR}
|
||||
|
||||
if (const wchar_t* envValue = _wgetenv(varName.c_str())) {
|
||||
if (envValue != nullptr) {
|
||||
result.append(envValue);
|
||||
}
|
||||
}
|
||||
|
||||
lastPos = match.position() + match.length();
|
||||
}
|
||||
|
||||
result.append(input, lastPos, input.length() - lastPos);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
inline std::map<std::string, std::variant<std::map<std::string, std::string>, std::string>> ParseINI(const std::string& contents) {
|
||||
std::map<std::string, std::variant<std::map<std::string, std::string>, std::string>> ini;
|
||||
|
||||
std::string currentSection;
|
||||
auto sections = Split(contents, "\n");
|
||||
|
||||
for (size_t i = 0; i < sections.size(); i++) {
|
||||
std::string line = sections[i];
|
||||
if (line.empty() || line[0] == ';' || line[0] == '#')
|
||||
continue;
|
||||
|
||||
auto invalidLineLog = [&]{
|
||||
debug("Invalid INI line: " + line);
|
||||
debug("Surrounding lines: \n" +
|
||||
(i > 0 ? sections[i - 1] : "") + "\n" +
|
||||
(i < sections.size() - 1 ? sections[i + 1] : ""));
|
||||
};
|
||||
|
||||
if (line[0] == '[') {
|
||||
currentSection = line.substr(1, line.find(']') - 1);
|
||||
} else {
|
||||
std::string key, value;
|
||||
size_t pos = line.find('=');
|
||||
if (pos != std::string::npos) {
|
||||
key = line.substr(0, pos);
|
||||
|
||||
key = key.substr(0, key.find_last_not_of(" \t") + 1);
|
||||
|
||||
value = line.substr(pos + 1);
|
||||
if (currentSection.empty()) {
|
||||
ini[key] = value;
|
||||
} else {
|
||||
std::get<std::map<std::string, std::string>>(ini[currentSection])[key] = value;
|
||||
}
|
||||
} else {
|
||||
invalidLineLog();
|
||||
continue;
|
||||
}
|
||||
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t") + 1);
|
||||
|
||||
std::get<std::map<std::string, std::string>>(ini[currentSection])[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return ini;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
inline std::wstring ToWString(const std::string& s) {
|
||||
if (s.empty()) return std::wstring();
|
||||
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
|
||||
if (size_needed <= 0) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring result(size_needed, 0);
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &result[0], size_needed);
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
inline std::string ToWString(const std::string& s) {
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
inline std::string GetSha256HashReallyFastFile(const beammp_fs_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(beammp_wide("Sha256 hashing of '") + filename + beammp_wide("' failed: ") + ToWString(e.what()));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
inline std::string GetSha256HashReallyFast(const std::string& text, const beammp_fs_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");
|
||||
}
|
||||
|
||||
if (!EVP_DigestUpdate(mdctx, text.data(), text.size())) {
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
throw std::runtime_error("EVP_DigestUpdate() failed");
|
||||
}
|
||||
|
||||
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(beammp_wide("Sha256 hashing of '") + filename + beammp_wide("' failed: ") + ToWString(e.what()));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::vector<char> PrependHeader(const T& data) {
|
||||
std::vector<char> size_buffer(4);
|
||||
uint32_t len = data.size();
|
||||
std::memcpy(size_buffer.data(), &len, 4);
|
||||
std::vector<char> buffer;
|
||||
buffer.reserve(size_buffer.size() + data.size());
|
||||
buffer.insert(buffer.begin(), size_buffer.begin(), size_buffer.end());
|
||||
buffer.insert(buffer.end(), data.begin(), data.end());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline uint32_t RecvHeader(SOCKET socket) {
|
||||
std::array<uint8_t, sizeof(uint32_t)> header_buffer {};
|
||||
auto n = recv(socket, reinterpret_cast<char*>(header_buffer.data()), header_buffer.size(), MSG_WAITALL);
|
||||
if (n < 0) {
|
||||
throw std::runtime_error(std::string("recv() of header failed: ") + std::strerror(errno));
|
||||
} else if (n == 0) {
|
||||
throw std::runtime_error("Game disconnected");
|
||||
}
|
||||
return *reinterpret_cast<uint32_t*>(header_buffer.data());
|
||||
}
|
||||
|
||||
/// Throws!!!
|
||||
inline void ReceiveFromGame(SOCKET socket, std::vector<char>& out_data) {
|
||||
auto header = RecvHeader(socket);
|
||||
out_data.resize(header);
|
||||
auto n = recv(socket, reinterpret_cast<char*>(out_data.data()), out_data.size(), MSG_WAITALL);
|
||||
if (n < 0) {
|
||||
throw std::runtime_error(std::string("recv() of data failed: ") + std::strerror(errno));
|
||||
} else if (n == 0) {
|
||||
throw std::runtime_error("Game disconnected");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -6,17 +6,15 @@
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Network/network.hpp"
|
||||
#include "Options.h"
|
||||
#include "Utils.h"
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "Options.h"
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string Branch;
|
||||
std::filesystem::path CachingDirectory = std::filesystem::path("./Resources");
|
||||
bool deleteDuplicateMods = false;
|
||||
std::string CachingDirectory = "./Resources";
|
||||
|
||||
void ParseConfig(const nlohmann::json& d) {
|
||||
if (d["Port"].is_number()) {
|
||||
@@ -33,8 +31,8 @@ void ParseConfig(const nlohmann::json& d) {
|
||||
c = char(tolower(c));
|
||||
}
|
||||
if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) {
|
||||
CachingDirectory = std::filesystem::path(d["CachingDirectory"].get<std::string>());
|
||||
info(beammp_wide("Mod caching directory: ") + beammp_fs_string(CachingDirectory.relative_path()));
|
||||
CachingDirectory = d["CachingDirectory"].get<std::string>();
|
||||
info("Mod caching directory: " + CachingDirectory);
|
||||
}
|
||||
|
||||
if (d.contains("Dev") && d["Dev"].is_boolean()) {
|
||||
@@ -44,11 +42,6 @@ void ParseConfig(const nlohmann::json& d) {
|
||||
options.no_launch = dev;
|
||||
options.no_update = dev;
|
||||
}
|
||||
|
||||
if (d.contains(("DeleteDuplicateMods")) && d["DeleteDuplicateMods"].is_boolean()) {
|
||||
deleteDuplicateMods = d["DeleteDuplicateMods"].get<bool>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ConfigInit() {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include "Utils.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#elif defined(__linux__)
|
||||
#include "vdf_parser.hpp"
|
||||
@@ -18,162 +18,93 @@
|
||||
#endif
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Options.h"
|
||||
#include "Startup.h"
|
||||
#include <Security/Init.h>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
#include <fstream>
|
||||
#include "Options.h"
|
||||
|
||||
unsigned long GamePID = 0;
|
||||
#if defined(_WIN32)
|
||||
std::wstring QueryKey(HKEY hKey, int ID);
|
||||
std::filesystem::path GetGamePath() {
|
||||
static std::filesystem::path Path;
|
||||
std::string QueryKey(HKEY hKey, int ID);
|
||||
std::string GetGamePath() {
|
||||
static std::string Path;
|
||||
if (!Path.empty())
|
||||
return Path.wstring();
|
||||
return Path;
|
||||
|
||||
if (options.user_path) {
|
||||
if (std::filesystem::exists(options.user_path)) {
|
||||
Path = options.user_path;
|
||||
debug(L"Using custom user folder path: " + Path.wstring());
|
||||
} else
|
||||
warn(L"Invalid or non-existent path (" + Utils::ToWString(options.user_path) + L") specified using --user-path, skipping");
|
||||
HKEY hKey;
|
||||
LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive";
|
||||
LONG openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
|
||||
if (openRes != ERROR_SUCCESS) {
|
||||
fatal("Please launch the game at least once!");
|
||||
}
|
||||
Path = QueryKey(hKey, 4);
|
||||
|
||||
if (const auto startupIniPath = std::filesystem::path(GetGameDir()) / "startup.ini"; exists(startupIniPath)) {
|
||||
|
||||
if (std::ifstream startupIni(startupIniPath); startupIni.is_open()) {
|
||||
std::string contents((std::istreambuf_iterator(startupIni)), std::istreambuf_iterator<char>());
|
||||
startupIni.close();
|
||||
|
||||
auto ini = Utils::ParseINI(contents);
|
||||
if (ini.empty())
|
||||
warn("Failed to parse startup.ini");
|
||||
else
|
||||
debug("Successfully parsed startup.ini");
|
||||
|
||||
std::wstring userPath;
|
||||
if (ini.contains("filesystem") && std::get<std::map<std::string, std::string>>(ini["filesystem"]).contains("UserPath"))
|
||||
userPath = Utils::ToWString(std::get<std::map<std::string, std::string>>(ini["filesystem"])["UserPath"]);
|
||||
|
||||
if (userPath = Utils::ExpandEnvVars(userPath); std::filesystem::exists(userPath)) {
|
||||
Path = userPath;
|
||||
debug(L"Using custom user folder path from startup.ini: " + Path.wstring());
|
||||
} else
|
||||
warn(L"Found custom user folder path (" + userPath + L") in startup.ini but it doesn't exist, skipping");
|
||||
if (Path.empty()) {
|
||||
Path = "";
|
||||
char appDataPath[MAX_PATH];
|
||||
HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
|
||||
if (SUCCEEDED(result)) {
|
||||
Path = appDataPath;
|
||||
}
|
||||
|
||||
if (Path.empty()) {
|
||||
wchar_t* appDataPath = new wchar_t[MAX_PATH];
|
||||
HRESULT result = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
fatal("Cannot get Local Appdata directory");
|
||||
}
|
||||
|
||||
auto BeamNGAppdataPath = std::filesystem::path(appDataPath) / "BeamNG";
|
||||
|
||||
if (const auto beamngIniPath = BeamNGAppdataPath / "BeamNG.Drive.ini"; exists(beamngIniPath)) {
|
||||
if (std::ifstream beamngIni(beamngIniPath); beamngIni.is_open()) {
|
||||
std::string contents((std::istreambuf_iterator(beamngIni)), std::istreambuf_iterator<char>());
|
||||
beamngIni.close();
|
||||
|
||||
auto ini = Utils::ParseINI(contents);
|
||||
if (ini.empty())
|
||||
warn("Failed to parse BeamNG.Drive.ini");
|
||||
else
|
||||
debug("Successfully parsed BeamNG.Drive.ini");
|
||||
|
||||
std::wstring userPath;
|
||||
|
||||
if (ini.contains("userFolder")) {
|
||||
userPath = Utils::ToWString(std::get<std::string>(ini["userFolder"]));
|
||||
userPath.erase(0, userPath.find_first_not_of(L" \t"));
|
||||
|
||||
}
|
||||
|
||||
if (userPath = std::filesystem::path(Utils::ExpandEnvVars(userPath)); std::filesystem::exists(userPath)) {
|
||||
Path = userPath;
|
||||
debug(L"Using custom user folder path from BeamNG.Drive.ini: " + Path.wstring());
|
||||
} else
|
||||
warn(L"Found custom user folder path (" + userPath + L") in BeamNG.Drive.ini but it doesn't exist, skipping");
|
||||
}
|
||||
}
|
||||
|
||||
if (Path.empty()) {
|
||||
Path = BeamNGAppdataPath / "BeamNG.drive";
|
||||
}
|
||||
|
||||
delete[] appDataPath;
|
||||
fatal("Cannot get Local Appdata directory");
|
||||
}
|
||||
|
||||
Path += "\\BeamNG.drive\\";
|
||||
}
|
||||
|
||||
std::string Ver = CheckVer(GetGameDir());
|
||||
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
|
||||
Path /= Utils::ToWString("current");
|
||||
Path += Ver + "\\";
|
||||
return Path;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
std::filesystem::path GetGamePath() {
|
||||
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/BeamNG.drive/";
|
||||
std::string Path = homeDir + "/.local/share/BeamNG.drive/";
|
||||
std::string Ver = CheckVer(GetGameDir());
|
||||
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
|
||||
Path += "current/";
|
||||
Path += Ver + "/";
|
||||
return Path;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
void StartGame(std::wstring Dir) {
|
||||
void StartGame(std::string Dir) {
|
||||
BOOL bSuccess = FALSE;
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFOW si = { 0 };
|
||||
STARTUPINFO si = { 0 };
|
||||
si.cb = sizeof(si);
|
||||
std::wstring BaseDir = Dir; //+"\\Bin64";
|
||||
std::string BaseDir = Dir; //+"\\Bin64";
|
||||
// Dir += R"(\Bin64\BeamNG.drive.x64.exe)";
|
||||
Dir += L"\\BeamNG.drive.exe";
|
||||
std::wstring gameArgs = L"";
|
||||
Dir += "\\BeamNG.drive.exe";
|
||||
std::string gameArgs = "";
|
||||
|
||||
for (int i = 0; i < options.game_arguments_length; i++) {
|
||||
gameArgs += L" ";
|
||||
gameArgs += Utils::ToWString(options.game_arguments[i]);
|
||||
gameArgs += " ";
|
||||
gameArgs += options.game_arguments[i];
|
||||
}
|
||||
|
||||
debug(L"BeamNG executable path: " + Dir);
|
||||
|
||||
bSuccess = CreateProcessW(nullptr, (wchar_t*)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
|
||||
bSuccess = CreateProcessA(nullptr, (LPSTR)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
|
||||
if (bSuccess) {
|
||||
info("Game Launched!");
|
||||
GamePID = pi.dwProcessId;
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
error("Game Closed! launcher closing soon");
|
||||
} else {
|
||||
std::string err = "";
|
||||
|
||||
DWORD dw = GetLastError();
|
||||
LPVOID lpErrorMsgBuffer;
|
||||
|
||||
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpErrorMsgBuffer, 0, nullptr)
|
||||
== 0) {
|
||||
err = "Unknown error code: " + std::to_string(dw);
|
||||
} else {
|
||||
err = "Error " + std::to_string(dw) + ": " + (char*)lpErrorMsgBuffer;
|
||||
}
|
||||
|
||||
error("Failed to Launch the game! launcher closing soon. " + err);
|
||||
error("Failed to Launch the game! launcher closing soon");
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
exit(2);
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
void StartGame(std::string Dir) {
|
||||
int status;
|
||||
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
|
||||
std::vector<const char*> argv;
|
||||
argv.push_back(filename.data());
|
||||
@@ -183,24 +114,11 @@ void StartGame(std::string Dir) {
|
||||
|
||||
argv.push_back(nullptr);
|
||||
pid_t pid;
|
||||
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
auto status = posix_spawn_file_actions_init(&file_actions);
|
||||
// disable stdout
|
||||
if (status != 0) {
|
||||
error(std::string("posix_spawn_file_actions_init failed: ") + std::strerror(errno));
|
||||
}
|
||||
status = posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO);
|
||||
if (status != 0) {
|
||||
error(std::string("posix_spawn_file_actions_addclose for STDOUT failed: ") + std::strerror(errno));
|
||||
}
|
||||
status = posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO);
|
||||
if (status != 0) {
|
||||
error(std::string("posix_spawn_file_actions_addclose for STDERR failed: ") + std::strerror(errno));
|
||||
}
|
||||
|
||||
// launch the game
|
||||
int result = posix_spawn(&pid, filename.c_str(), &file_actions, NULL, const_cast<char**>(argv.data()), environ);
|
||||
posix_spawn_file_actions_t spawn_actions;
|
||||
posix_spawn_file_actions_init(&spawn_actions);
|
||||
posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO);
|
||||
posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO);
|
||||
int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, const_cast<char**>(argv.data()), environ);
|
||||
|
||||
if (result != 0) {
|
||||
error("Failed to Launch the game! launcher closing soon");
|
||||
@@ -215,7 +133,7 @@ void StartGame(std::string Dir) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void InitGame(const beammp_fs_string& Dir) {
|
||||
void InitGame(const std::string& Dir) {
|
||||
if (!options.no_launch) {
|
||||
std::thread Game(StartGame, Dir);
|
||||
Game.detach();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Startup.h"
|
||||
#include "Utils.h"
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -37,7 +36,7 @@ std::string getDate() {
|
||||
}
|
||||
void InitLog() {
|
||||
std::ofstream LFS;
|
||||
LFS.open(GetEP() + beammp_wide("Launcher.log"));
|
||||
LFS.open(GetEP() + "Launcher.log");
|
||||
if (!LFS.is_open()) {
|
||||
error("logger file init failed!");
|
||||
} else
|
||||
@@ -45,85 +44,41 @@ void InitLog() {
|
||||
}
|
||||
void addToLog(const std::string& Line) {
|
||||
std::ofstream LFS;
|
||||
LFS.open(GetEP() + beammp_wide("Launcher.log"), std::ios_base::app);
|
||||
LFS << Line.c_str();
|
||||
LFS.close();
|
||||
}
|
||||
void addToLog(const std::wstring& Line) {
|
||||
std::wofstream LFS;
|
||||
LFS.open(GetEP() + beammp_wide("Launcher.log"), std::ios_base::app);
|
||||
LFS.open(GetEP() + "Launcher.log", std::ios_base::app);
|
||||
LFS << Line.c_str();
|
||||
LFS.close();
|
||||
}
|
||||
void info(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[INFO] " + toPrint + "\n";
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void debug(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
|
||||
if (options.verbose) {
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
}
|
||||
addToLog(Print);
|
||||
}
|
||||
void warn(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[WARN] " + toPrint + "\n";
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void error(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[ERROR] " + toPrint + "\n";
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void fatal(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[FATAL] " + toPrint + "\n";
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
std::exit(1);
|
||||
}
|
||||
void except(const std::string& toPrint) {
|
||||
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";
|
||||
beammp_stdout << Utils::ToWString(Print);
|
||||
std::cout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
void info(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[INFO] " + toPrint + L"\n";
|
||||
std::wcout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void debug(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[DEBUG] " + toPrint + L"\n";
|
||||
if (options.verbose) {
|
||||
std::wcout << Print;
|
||||
}
|
||||
addToLog(Print);
|
||||
}
|
||||
void warn(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[WARN] " + toPrint + L"\n";
|
||||
std::wcout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void error(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[ERROR] " + toPrint + L"\n";
|
||||
std::wcout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
void fatal(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[FATAL] " + toPrint + L"\n";
|
||||
std::wcout << Print;
|
||||
addToLog(Print);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
std::exit(1);
|
||||
}
|
||||
void except(const std::wstring& toPrint) {
|
||||
std::wstring Print = Utils::ToWString(getDate()) + L"[EXCEP] " + toPrint + L"\n";
|
||||
std::wcout << Print;
|
||||
addToLog(Print);
|
||||
}
|
||||
#endif
|
||||
@@ -7,13 +7,11 @@
|
||||
#include "Http.h"
|
||||
#include "Network/network.hpp"
|
||||
#include "Security/Init.h"
|
||||
#include "Utils.h"
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#if defined(_WIN32)
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <shellapi.h>
|
||||
#elif defined(__linux__)
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
@@ -91,106 +89,13 @@ void StartSync(const std::string& Data) {
|
||||
info("Connecting to server");
|
||||
}
|
||||
|
||||
void GetServerInfo(std::string Data) {
|
||||
debug("Fetching server info of " + Data.substr(1));
|
||||
|
||||
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
|
||||
if (IP.find('.') == -1) {
|
||||
if (IP == "DNS")
|
||||
warn("Connection Failed! (DNS Lookup Failed) for " + Data);
|
||||
else
|
||||
warn("Connection Failed! (WSA failed to start) for " + Data);
|
||||
CoreSend("I" + Data + ";");
|
||||
return;
|
||||
}
|
||||
|
||||
SOCKET ISock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
SOCKADDR_IN ServerAddr;
|
||||
if (ISock < 1) {
|
||||
debug("Socket creation failed with error: " + std::to_string(WSAGetLastError()));
|
||||
KillSocket(ISock);
|
||||
CoreSend("I" + Data + ";");
|
||||
return;
|
||||
}
|
||||
ServerAddr.sin_family = AF_INET;
|
||||
|
||||
int port = std::stoi(Data.substr(Data.find(':') + 1));
|
||||
|
||||
if (port < 1 || port > 65535) {
|
||||
debug("Invalid port number: " + std::to_string(port));
|
||||
KillSocket(ISock);
|
||||
CoreSend("I" + Data + ";");
|
||||
return;
|
||||
}
|
||||
|
||||
ServerAddr.sin_port = htons(port);
|
||||
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
|
||||
if (connect(ISock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
|
||||
debug("Connection to server failed with error: " + std::to_string(WSAGetLastError()));
|
||||
KillSocket(ISock);
|
||||
CoreSend("I" + Data + ";");
|
||||
return;
|
||||
}
|
||||
|
||||
char Code[1] = { 'I' };
|
||||
if (send(ISock, Code, 1, 0) != 1) {
|
||||
debug("Sending data to server failed with error: " + std::to_string(WSAGetLastError()));
|
||||
KillSocket(ISock);
|
||||
CoreSend("I" + Data + ";");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string buffer = ([&]() -> std::string {
|
||||
int32_t Header;
|
||||
std::vector<char> data(sizeof(Header));
|
||||
int Temp = recv(ISock, data.data(), sizeof(Header), MSG_WAITALL);
|
||||
|
||||
auto checkBytes = ([&](const int32_t bytes) -> bool {
|
||||
if (bytes == 0) {
|
||||
return false;
|
||||
} else if (bytes < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!checkBytes(Temp)) {
|
||||
return "";
|
||||
}
|
||||
memcpy(&Header, data.data(), sizeof(Header));
|
||||
|
||||
if (!checkBytes(Temp)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
data.resize(Header, 0);
|
||||
Temp = recv(ISock, data.data(), Header, MSG_WAITALL);
|
||||
if (!checkBytes(Temp)) {
|
||||
return "";
|
||||
}
|
||||
return std::string(data.data(), Header);
|
||||
})();
|
||||
|
||||
if (!buffer.empty()) {
|
||||
debug("Server Info: " + buffer);
|
||||
|
||||
CoreSend("I" + Data + ";" + buffer);
|
||||
} else {
|
||||
debug("Receiving data from server failed with error: " + std::to_string(WSAGetLastError()));
|
||||
debug("Failed to receive server info from " + Data);
|
||||
CoreSend("I" + Data + ";");
|
||||
}
|
||||
|
||||
KillSocket(ISock);
|
||||
}
|
||||
std::mutex sendMutex;
|
||||
|
||||
void CoreSend(std::string data) {
|
||||
std::lock_guard lock(sendMutex);
|
||||
|
||||
if (CoreSocket != -1) {
|
||||
auto ToSend = Utils::PrependHeader(data);
|
||||
int res = send(CoreSocket, ToSend.data(), ToSend.size(), 0);
|
||||
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()));
|
||||
}
|
||||
@@ -203,17 +108,7 @@ bool IsAllowedLink(const std::string& Link) {
|
||||
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
|
||||
}
|
||||
|
||||
std::vector<std::future<void>> futures;
|
||||
|
||||
void Parse(std::string Data, SOCKET CSocket) {
|
||||
std::erase_if(futures, [](const std::future<void>& f) {
|
||||
if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
char Code = Data.at(0), SubCode = 0;
|
||||
if (Data.length() > 1)
|
||||
SubCode = Data.at(1);
|
||||
@@ -226,9 +121,9 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
Terminate = true;
|
||||
TCPTerminate = true;
|
||||
Data.clear();
|
||||
futures.push_back(std::async(std::launch::async, []() {
|
||||
auto future = std::async(std::launch::async, []() {
|
||||
CoreSend("B" + HTTP::Get("https://backend.beammp.com/servers-info"));
|
||||
}));
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
@@ -280,7 +175,7 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
Ping = "-2";
|
||||
else
|
||||
Ping = std::to_string(ping);
|
||||
Data = "Up" + Ping;
|
||||
Data = std::string(UlStatus) + "\n" + "Up" + Ping;
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
@@ -325,9 +220,9 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
}
|
||||
Data = "N" + Auth.dump();
|
||||
} else {
|
||||
futures.push_back(std::async(std::launch::async, [data = std::move(Data)]() {
|
||||
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
|
||||
CoreSend("N" + Login(data.substr(data.find(':') + 1)));
|
||||
}));
|
||||
});
|
||||
Data.clear();
|
||||
}
|
||||
break;
|
||||
@@ -340,39 +235,54 @@ void Parse(std::string Data, SOCKET CSocket) {
|
||||
|
||||
Data.clear();
|
||||
break;
|
||||
case 'I': {
|
||||
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
|
||||
GetServerInfo(data);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Data.clear();
|
||||
break;
|
||||
}
|
||||
if (!Data.empty() && CSocket != -1) {
|
||||
auto ToSend = Utils::PrependHeader(Data);
|
||||
int res = send(CSocket, ToSend.data(), ToSend.size(), 0);
|
||||
if (res < 0) {
|
||||
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
|
||||
}
|
||||
}
|
||||
if (!Data.empty())
|
||||
CoreSend(Data);
|
||||
}
|
||||
void GameHandler(SOCKET Client) {
|
||||
CoreSocket = Client;
|
||||
std::vector<char> data{};
|
||||
int Temp;
|
||||
int32_t Size, Temp, Rcv;
|
||||
char Header[10] = { 0 };
|
||||
do {
|
||||
try {
|
||||
Utils::ReceiveFromGame(Client, data);
|
||||
Parse(std::string(data.data(), data.size()), Client);
|
||||
} catch (const std::exception& e) {
|
||||
error(std::string("Error while receiving from game on core: ") + e.what());
|
||||
Rcv = 0;
|
||||
do {
|
||||
Temp = recv(Client, &Header[Rcv], 1, 0);
|
||||
if (Temp < 1)
|
||||
break;
|
||||
if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
|
||||
error("(Core) Invalid lua communication");
|
||||
KillSocket(Client);
|
||||
return;
|
||||
}
|
||||
} while (Header[Rcv++] != '>');
|
||||
if (Temp < 1)
|
||||
break;
|
||||
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
|
||||
debug("(Core) Invalid lua Header -> " + std::string(Header, Rcv));
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
debug("(Core) Connection closing");
|
||||
std::string Ret(Size, 0);
|
||||
Rcv = 0;
|
||||
|
||||
do {
|
||||
Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
|
||||
if (Temp < 1)
|
||||
break;
|
||||
Rcv += Temp;
|
||||
} while (Rcv < Size);
|
||||
if (Temp < 1)
|
||||
break;
|
||||
|
||||
Parse(Ret, Client);
|
||||
} while (Temp > 0);
|
||||
if (Temp == 0) {
|
||||
debug("(Core) Connection closing");
|
||||
} else {
|
||||
debug("(Core) recv failed with error: " + std::to_string(WSAGetLastError()));
|
||||
}
|
||||
NetReset();
|
||||
KillSocket(Client);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
#include "Network/network.hpp"
|
||||
#include "Utils.h"
|
||||
#include <memory>
|
||||
#include <zlib.h>
|
||||
#if defined(_WIN32)
|
||||
@@ -22,20 +21,17 @@
|
||||
#endif
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Options.h"
|
||||
#include <charconv>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "Options.h"
|
||||
#include <chrono>
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
|
||||
bool GConnected = false;
|
||||
bool CServer = true;
|
||||
SOCKET CSocket = -1;
|
||||
SOCKET GSocket = -1;
|
||||
std::string magic;
|
||||
|
||||
int KillSocket(uint64_t Dead) {
|
||||
if (Dead == (SOCKET)-1) {
|
||||
@@ -64,13 +60,30 @@ bool CheckBytes(uint32_t Bytes) {
|
||||
void GameSend(std::string_view Data) {
|
||||
static std::mutex Lock;
|
||||
std::scoped_lock Guard(Lock);
|
||||
auto ToSend = Utils::PrependHeader<std::string_view>(Data);
|
||||
auto Result = send(CSocket, ToSend.data(), ToSend.size(), 0);
|
||||
if (Result < 0) {
|
||||
error("(Game) send failed with error: " + std::to_string(WSAGetLastError()));
|
||||
if (TCPTerminate || !GConnected || CSocket == -1)
|
||||
return;
|
||||
int32_t Size, Temp, Sent;
|
||||
Size = int32_t(Data.size());
|
||||
Sent = 0;
|
||||
#ifdef DEBUG
|
||||
if (Size > 1000) {
|
||||
debug("Launcher -> game (" + std::to_string(Size) + ")");
|
||||
}
|
||||
#endif
|
||||
do {
|
||||
if (Sent > -1) {
|
||||
Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
|
||||
}
|
||||
if (!CheckBytes(Temp))
|
||||
return;
|
||||
Sent += Temp;
|
||||
} while (Sent < Size);
|
||||
// send separately to avoid an allocation for += "\n"
|
||||
Temp = send(CSocket, "\n", 1, 0);
|
||||
if (!CheckBytes(Temp)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerSend(std::string Data, bool Rel) {
|
||||
if (Terminate || Data.empty())
|
||||
return;
|
||||
@@ -204,8 +217,6 @@ void ParserAsync(std::string_view Data) {
|
||||
MStatus = Data;
|
||||
UlStatus = "Uldone";
|
||||
return;
|
||||
case 'U':
|
||||
magic = Data.substr(1);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -250,22 +261,42 @@ void TCPGameServer(const std::string& IP, int Port) {
|
||||
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
|
||||
CServer = false;
|
||||
}
|
||||
int32_t Size, Rcv;
|
||||
int Temp;
|
||||
int32_t Size, Temp, Rcv;
|
||||
char Header[10] = { 0 };
|
||||
std::vector<char> data {};
|
||||
|
||||
// Read byte by byte until '>' is rcved then get the size and read based on it
|
||||
do {
|
||||
try {
|
||||
Utils::ReceiveFromGame(CSocket, data);
|
||||
ServerSend(std::string(data.data(), data.size()), false);
|
||||
} catch (const std::exception& e) {
|
||||
error(std::string("Error while receiving from game on proxy: ") + e.what());
|
||||
Rcv = 0;
|
||||
|
||||
do {
|
||||
Temp = recv(CSocket, &Header[Rcv], 1, 0);
|
||||
if (Temp < 1 || TCPTerminate)
|
||||
break;
|
||||
} while (Header[Rcv++] != '>');
|
||||
if (Temp < 1 || TCPTerminate)
|
||||
break;
|
||||
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
|
||||
debug("(Game) Invalid lua Header -> " + std::string(Header, Rcv));
|
||||
break;
|
||||
}
|
||||
} while (!TCPTerminate);
|
||||
debug("(Proxy) Connection closing");
|
||||
std::string Ret(Size, 0);
|
||||
Rcv = 0;
|
||||
do {
|
||||
Temp = recv(CSocket, &Ret[Rcv], Size - Rcv, 0);
|
||||
if (Temp < 1)
|
||||
break;
|
||||
Rcv += Temp;
|
||||
} while (Rcv < Size && !TCPTerminate);
|
||||
if (Temp < 1 || TCPTerminate)
|
||||
break;
|
||||
|
||||
ServerSend(Ret, false);
|
||||
|
||||
} while (Temp > 0 && !TCPTerminate);
|
||||
if (Temp == 0)
|
||||
debug("(Proxy) Connection closing");
|
||||
else
|
||||
debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
|
||||
}
|
||||
TCPTerminate = true;
|
||||
GConnected = false;
|
||||
|
||||
@@ -20,6 +20,45 @@
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
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);
|
||||
@@ -29,23 +68,24 @@ static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void*
|
||||
}
|
||||
|
||||
bool HTTP::isDownload = false;
|
||||
std::string HTTP::Get(const std::string& IP) {
|
||||
std::string HTTP::Get(const std::string& IP, const std::string& Fields) {
|
||||
std::string Ret;
|
||||
static thread_local CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||
errbuf[0] = 0;
|
||||
if (!Fields.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
|
||||
}
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
error("Curl error: " + std::string(errbuf));
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
@@ -60,7 +100,6 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
||||
static thread_local CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||
@@ -70,15 +109,12 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
||||
struct curl_slist* list = nullptr;
|
||||
list = curl_slist_append(list, "Content-Type: application/json");
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||
errbuf[0] = 0;
|
||||
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)));
|
||||
error("Curl error: " + std::string(errbuf));
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
@@ -88,35 +124,25 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
|
||||
return Ret;
|
||||
}
|
||||
|
||||
bool HTTP::Download(const std::string& IP, const beammp_fs_string& Path, const std::string& Hash) {
|
||||
bool HTTP::Download(const std::string& IP, const std::string& Fields, const std::string& Path) {
|
||||
static std::mutex Lock;
|
||||
std::scoped_lock Guard(Lock);
|
||||
|
||||
info("Downloading an update (this may take a while)");
|
||||
std::string Ret = Get(IP);
|
||||
std::string Ret = Get(IP, Fields);
|
||||
|
||||
if (Ret.empty()) {
|
||||
error("Download failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string RetHash = Utils::GetSha256HashReallyFast(Ret, Path);
|
||||
|
||||
debug("Return hash: " + RetHash);
|
||||
debug("Expected hash: " + Hash);
|
||||
|
||||
if (RetHash != Hash) {
|
||||
error("Downloaded file hash does not match expected hash");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream File(Path, std::ios::binary);
|
||||
if (File.is_open()) {
|
||||
File << Ret;
|
||||
File.close();
|
||||
info("Download Complete!");
|
||||
} else {
|
||||
error(beammp_wide("Failed to open file directory: ") + Path);
|
||||
error("Failed to open file directory: " + Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,12 +164,9 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
||||
do {
|
||||
// receive at most some MB at a time
|
||||
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
|
||||
int Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
||||
if (Temp == -1 || Temp == 0) {
|
||||
debug("Recv returned: " + std::to_string(Temp));
|
||||
if (Temp == -1) {
|
||||
error("Socket error during download: " + std::to_string(WSAGetLastError()));
|
||||
}
|
||||
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
|
||||
if (Temp < 1) {
|
||||
info(std::to_string(Temp));
|
||||
UUl("Socket Closed Code 1");
|
||||
KillSocket(Sock);
|
||||
Terminate = true;
|
||||
@@ -180,8 +177,8 @@ std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto difference = end - start;
|
||||
double bits_per_s = double(Rcv * 8) / double(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
|
||||
double megabits_per_s = bits_per_s / 1000;
|
||||
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) {
|
||||
@@ -301,6 +298,68 @@ void InvalidResource(const std::string& File) {
|
||||
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::pair<bool, std::vector<ModInfo>> ParseModInfosFromPacket(const std::string& packet) {
|
||||
bool success = false;
|
||||
@@ -318,17 +377,12 @@ struct ModInfo {
|
||||
.Hash = entry["hash"],
|
||||
.HashAlgorithm = entry["hash_algorithm"],
|
||||
};
|
||||
|
||||
if (entry.contains("protected")) {
|
||||
modInfo.Protected = entry["protected"];
|
||||
}
|
||||
|
||||
modInfos.push_back(modInfo);
|
||||
success = true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
debug(std::string("Failed to receive mod list: ") + e.what());
|
||||
debug("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
|
||||
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
|
||||
}
|
||||
return std::make_pair(success, modInfos);
|
||||
}
|
||||
@@ -336,55 +390,8 @@ struct ModInfo {
|
||||
size_t FileSize;
|
||||
std::string Hash;
|
||||
std::string HashAlgorithm;
|
||||
bool Protected = false;
|
||||
};
|
||||
|
||||
nlohmann::json modUsage = {};
|
||||
|
||||
void UpdateModUsage(const std::string& fileName) {
|
||||
try {
|
||||
fs::path usageFile = CachingDirectory / "mods.json";
|
||||
|
||||
if (!fs::exists(usageFile)) {
|
||||
if (std::ofstream file(usageFile); !file.is_open()) {
|
||||
error("Failed to create mods.json");
|
||||
return;
|
||||
} else {
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
std::fstream file(usageFile, std::ios::in | std::ios::out);
|
||||
if (!file.is_open()) {
|
||||
error("Failed to open or create mods.json");
|
||||
return;
|
||||
}
|
||||
|
||||
if (modUsage.empty()) {
|
||||
auto Size = fs::file_size(CachingDirectory / "mods.json");
|
||||
std::string modsJson(Size, 0);
|
||||
file.read(&modsJson[0], Size);
|
||||
|
||||
if (!modsJson.empty()) {
|
||||
auto parsedModJson = nlohmann::json::parse(modsJson, nullptr, false);
|
||||
|
||||
if (parsedModJson.is_object())
|
||||
modUsage = parsedModJson;
|
||||
}
|
||||
}
|
||||
|
||||
modUsage[fileName] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
file.clear();
|
||||
file.seekp(0, std::ios::beg);
|
||||
file << modUsage.dump();
|
||||
file.close();
|
||||
} catch (std::exception& e) {
|
||||
error("Failed to update mods.json: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
|
||||
if (ModInfos.empty()) {
|
||||
CoreSend("L");
|
||||
@@ -413,45 +420,22 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
|
||||
info("Syncing...");
|
||||
|
||||
std::vector<std::pair<std::string, std::filesystem::path>> CachedMods = {};
|
||||
if (deleteDuplicateMods) {
|
||||
for (const auto& entry : fs::directory_iterator(CachingDirectory)) {
|
||||
const std::string filename = entry.path().filename().string();
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".zip" && filename.length() > 10) {
|
||||
CachedMods.push_back(std::make_pair(filename.substr(0, filename.length() - 13) + ".zip", entry.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ModNo = 0;
|
||||
int ModNo = 1;
|
||||
int TotalMods = ModInfos.size();
|
||||
for (auto ModInfoIter = ModInfos.begin(), AlsoModInfoIter = ModInfos.begin(); ModInfoIter != ModInfos.end() && !Terminate; ++ModInfoIter, ++AlsoModInfoIter) {
|
||||
++ModNo;
|
||||
if (deleteDuplicateMods) {
|
||||
for (auto& CachedMod : CachedMods) {
|
||||
const bool cachedModExists = CachedMod.first == ModInfoIter->FileName;
|
||||
const bool cachedModIsNotNewestVersion = CachedMod.second.stem().string() + ".zip" != std::filesystem::path(ModInfoIter->FileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + ".zip";
|
||||
if (cachedModExists && cachedModIsNotNewestVersion) {
|
||||
debug("Found duplicate mod '" + CachedMod.second.stem().string() + ".zip" + "' in cache, removing it");
|
||||
std::filesystem::remove(CachedMod.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = (CachingDirectory / FileName);
|
||||
if (fs::exists(PathToSaveTo) && Utils::GetSha256HashReallyFastFile(PathToSaveTo) == ModInfoIter->Hash) {
|
||||
auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string();
|
||||
if (fs::exists(PathToSaveTo) && GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) {
|
||||
debug("Mod '" + FileName + "' found in cache");
|
||||
UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + ModInfoIter->FileName);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
try {
|
||||
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
auto modname = ModInfoIter->FileName;
|
||||
#if defined(__linux__)
|
||||
@@ -467,43 +451,6 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
|
||||
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||
fs::rename(tmp_name, name);
|
||||
UpdateModUsage(FileName);
|
||||
} catch (std::exception& e) {
|
||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||
Terminate = true;
|
||||
continue;
|
||||
}
|
||||
WaitForConfirm();
|
||||
continue;
|
||||
} else if (auto OldCachedPath = CachingDirectory / std::filesystem::path(ModInfoIter->FileName).filename();
|
||||
fs::exists(OldCachedPath) && Utils::GetSha256HashReallyFastFile(OldCachedPath) == ModInfoIter->Hash) {
|
||||
debug("Mod '" + FileName + "' found in old cache, copying it to the new cache");
|
||||
UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + ModInfoIter->FileName);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
try {
|
||||
fs::copy_file(OldCachedPath, PathToSaveTo, fs::copy_options::overwrite_existing);
|
||||
|
||||
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("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);
|
||||
UpdateModUsage(FileName);
|
||||
} catch (std::exception& e) {
|
||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||
Terminate = true;
|
||||
@@ -512,20 +459,10 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
WaitForConfirm();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ModInfoIter->Protected) {
|
||||
std::string message = "Mod '" + ModInfoIter->FileName + "' is protected and therefore must be placed in the Resources/Caching folder manually here: " + absolute(CachingDirectory).string();
|
||||
|
||||
error(message);
|
||||
UUl(message);
|
||||
Terminate = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CheckForDir();
|
||||
std::string FName = ModInfoIter->FileName;
|
||||
do {
|
||||
debug(beammp_wide("Loading file '") + Utils::ToWString(FName) + beammp_wide("' to '") + beammp_fs_string(PathToSaveTo) + beammp_wide("'"));
|
||||
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
|
||||
TCPSend("f" + ModInfoIter->FileName, Sock);
|
||||
|
||||
std::string Data = TCPRcv(Sock);
|
||||
@@ -535,13 +472,6 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
break;
|
||||
}
|
||||
|
||||
if (Data != "AG") {
|
||||
UUl("Received corrupted download confirmation, aborting download.");
|
||||
debug("Corrupted download confirmation: " + Data);
|
||||
Terminate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string Name = std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName;
|
||||
|
||||
std::vector<char> DownloadedFile = SingleNormalDownload(Sock, ModInfoIter->FileSize, Name);
|
||||
@@ -556,20 +486,15 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
|
||||
OutFile.flush();
|
||||
}
|
||||
// 2. verify size and hash
|
||||
// 2. verify size
|
||||
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
|
||||
error(beammp_wide("Failed to write the entire file '") + beammp_fs_string(PathToSaveTo) + beammp_wide("' correctly (file size mismatch)"));
|
||||
Terminate = true;
|
||||
}
|
||||
|
||||
if (Utils::GetSha256HashReallyFastFile(PathToSaveTo) != ModInfoIter->Hash) {
|
||||
error(beammp_wide("Failed to write or download the entire file '") + beammp_fs_string(PathToSaveTo) + beammp_wide("' correctly (hash mismatch)"));
|
||||
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() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
|
||||
// Linux version of the game doesnt support uppercase letters in mod names
|
||||
@@ -580,9 +505,9 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
|
||||
#endif
|
||||
|
||||
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
|
||||
UpdateModUsage(FName);
|
||||
}
|
||||
WaitForConfirm();
|
||||
++ModNo;
|
||||
}
|
||||
|
||||
if (!Terminate) {
|
||||
@@ -626,8 +551,7 @@ void SyncResources(SOCKET Sock) {
|
||||
Ret.clear();
|
||||
|
||||
int Amount = 0, Pos = 0;
|
||||
std::filesystem::path PathToSaveTo;
|
||||
std::string t;
|
||||
std::string PathToSaveTo, t;
|
||||
for (const std::string& name : FNames) {
|
||||
if (!name.empty()) {
|
||||
t += name.substr(name.find_last_of('/') + 1) + ";";
|
||||
@@ -655,7 +579,7 @@ void SyncResources(SOCKET Sock) {
|
||||
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
|
||||
auto pos = FN->find_last_of('/');
|
||||
if (pos != std::string::npos) {
|
||||
PathToSaveTo = CachingDirectory / std::filesystem::path(*FN).filename();
|
||||
PathToSaveTo = CachingDirectory + FN->substr(pos);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@@ -665,25 +589,23 @@ void SyncResources(SOCKET Sock) {
|
||||
if (FS->find_first_not_of("0123456789") != std::string::npos)
|
||||
continue;
|
||||
if (fs::file_size(PathToSaveTo) == FileSize) {
|
||||
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.filename().string());
|
||||
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
try {
|
||||
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
auto modname = PathToSaveTo.filename().string();
|
||||
auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
|
||||
#if defined(__linux__)
|
||||
// Linux version of the game doesnt support uppercase letters in mod names
|
||||
for (char& c : modname) {
|
||||
c = ::tolower(c);
|
||||
}
|
||||
#endif
|
||||
auto name = GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(modname);
|
||||
auto tmp_name = name;
|
||||
tmp_name += L".tmp";
|
||||
auto name = GetGamePath() + "mods/multiplayer" + modname;
|
||||
auto tmp_name = name + ".tmp";
|
||||
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
|
||||
fs::rename(tmp_name, name);
|
||||
UpdateModUsage(modname);
|
||||
} catch (std::exception& e) {
|
||||
error("Failed copy to the mods folder! " + std::string(e.what()));
|
||||
Terminate = true;
|
||||
@@ -692,12 +614,12 @@ void SyncResources(SOCKET Sock) {
|
||||
WaitForConfirm();
|
||||
continue;
|
||||
} else
|
||||
fs::remove(PathToSaveTo.wstring());
|
||||
remove(PathToSaveTo.c_str());
|
||||
}
|
||||
CheckForDir();
|
||||
std::string FName = PathToSaveTo.filename().string();
|
||||
std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
|
||||
do {
|
||||
debug("Loading file '" + FName + "' to '" + PathToSaveTo.string() + "'");
|
||||
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
|
||||
TCPSend("f" + *FN, Sock);
|
||||
|
||||
std::string Data = TCPRcv(Sock);
|
||||
@@ -722,13 +644,13 @@ void SyncResources(SOCKET Sock) {
|
||||
}
|
||||
// 2. verify size
|
||||
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
|
||||
error(beammp_wide("Failed to write the entire file '") + beammp_fs_string(PathToSaveTo) + beammp_wide("' correctly (file size mismatch)"));
|
||||
error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
|
||||
Terminate = true;
|
||||
}
|
||||
} while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate);
|
||||
if (!Terminate) {
|
||||
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
|
||||
// Linux version of the game doesnt support uppercase letters in mod names
|
||||
@@ -738,8 +660,7 @@ void SyncResources(SOCKET Sock) {
|
||||
}
|
||||
#endif
|
||||
|
||||
fs::copy_file(PathToSaveTo, GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(FName), fs::copy_options::overwrite_existing);
|
||||
UpdateModUsage(FN->substr(pos));
|
||||
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
|
||||
}
|
||||
WaitForConfirm();
|
||||
}
|
||||
|
||||
@@ -93,9 +93,6 @@ void UDPClientMain(const std::string& IP, int Port) {
|
||||
ToServer->sin_port = htons(Port);
|
||||
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
|
||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (!magic.empty())
|
||||
for (int i = 0; i < 10; i++)
|
||||
UDPSend(magic);
|
||||
GameSend("P" + std::to_string(ClientID));
|
||||
TCPSend("H", TCPSock);
|
||||
UDPSend("p");
|
||||
|
||||
@@ -81,8 +81,7 @@ std::string TCPRcv(SOCKET Sock) {
|
||||
UUl("Invalid Socket");
|
||||
return "";
|
||||
}
|
||||
int32_t Header;
|
||||
int Temp;
|
||||
int32_t Header, Temp;
|
||||
std::vector<char> Data(sizeof(Header));
|
||||
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
|
||||
if (!CheckBytes(Temp)) {
|
||||
|
||||
@@ -86,12 +86,6 @@ void InitOptions(int argc, const char *argv[], Options &options) {
|
||||
options.no_download = true;
|
||||
options.no_launch = true;
|
||||
options.no_update = true;
|
||||
} else if (argument == "--user-path") {
|
||||
if (i + 1 >= argc) {
|
||||
error("You must specify a path after the `--user-path` argument");
|
||||
}
|
||||
options.user_path = argv[i + 1];
|
||||
i++;
|
||||
} else if (argument == "--" || argument == "--game") {
|
||||
options.game_arguments = &argv[i + 1];
|
||||
options.game_arguments_length = argc - i - 1;
|
||||
@@ -107,7 +101,6 @@ void InitOptions(int argc, const char *argv[], Options &options) {
|
||||
"\t--no-update Skip applying launcher updates (you must update manually)\n"
|
||||
"\t--no-launch Skip launching the game (you must launch the game manually)\n"
|
||||
"\t--dev Developer mode, same as --verbose --no-download --no-launch --no-update\n"
|
||||
"\t--user-path <path> Path to BeamNG's User Path\n"
|
||||
"\t--game <args...> Passes ALL following arguments to the game, see also `--`\n"
|
||||
<< std::flush;
|
||||
exit(0);
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
|
||||
|
||||
#include <filesystem>
|
||||
#include "Utils.h"
|
||||
#if defined(_WIN32)
|
||||
#include <shlobj_core.h>
|
||||
#include <windows.h>
|
||||
#elif defined(__linux__)
|
||||
#include "vdf_parser.hpp"
|
||||
#include <pwd.h>
|
||||
@@ -24,7 +23,7 @@
|
||||
#define MAX_VALUE_NAME 16383
|
||||
|
||||
int TraceBack = 0;
|
||||
beammp_fs_string GameDir;
|
||||
std::string GameDir;
|
||||
|
||||
void lowExit(int code) {
|
||||
TraceBack = 0;
|
||||
@@ -34,7 +33,7 @@ void lowExit(int code) {
|
||||
exit(2);
|
||||
}
|
||||
|
||||
beammp_fs_string GetGameDir() {
|
||||
std::string GetGameDir() {
|
||||
#if defined(_WIN32)
|
||||
return GameDir.substr(0, GameDir.find_last_of('\\'));
|
||||
#elif defined(__linux__)
|
||||
@@ -45,8 +44,8 @@ beammp_fs_string GetGameDir() {
|
||||
LONG OpenKey(HKEY root, const char* path, PHKEY hKey) {
|
||||
return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey);
|
||||
}
|
||||
std::wstring QueryKey(HKEY hKey, int ID) {
|
||||
wchar_t* achKey; // buffer for subkey name
|
||||
std::string QueryKey(HKEY hKey, int ID) {
|
||||
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
|
||||
DWORD cbName; // size of name string
|
||||
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
|
||||
DWORD cchClassName = MAX_PATH; // size of class string
|
||||
@@ -61,7 +60,7 @@ std::wstring QueryKey(HKEY hKey, int ID) {
|
||||
|
||||
DWORD i, retCode;
|
||||
|
||||
wchar_t* achValue = new wchar_t[MAX_VALUE_NAME];
|
||||
TCHAR achValue[MAX_VALUE_NAME];
|
||||
DWORD cchValue = MAX_VALUE_NAME;
|
||||
|
||||
retCode = RegQueryInfoKey(
|
||||
@@ -83,9 +82,9 @@ std::wstring QueryKey(HKEY hKey, int ID) {
|
||||
if (cSubKeys) {
|
||||
for (i = 0; i < cSubKeys; i++) {
|
||||
cbName = MAX_KEY_LENGTH;
|
||||
retCode = RegEnumKeyExW(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime);
|
||||
retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
if (wcscmp(achKey, L"Steam App 284160") == 0) {
|
||||
if (strcmp(achKey, "Steam App 284160") == 0) {
|
||||
return achKey;
|
||||
}
|
||||
}
|
||||
@@ -95,37 +94,36 @@ std::wstring QueryKey(HKEY hKey, int ID) {
|
||||
for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++) {
|
||||
cchValue = MAX_VALUE_NAME;
|
||||
achValue[0] = '\0';
|
||||
retCode = RegEnumValueW(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr);
|
||||
retCode = RegEnumValue(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
DWORD lpData = cbMaxValueData;
|
||||
buffer[0] = '\0';
|
||||
LONG dwRes = RegQueryValueExW(hKey, achValue, nullptr, nullptr, buffer, &lpData);
|
||||
std::wstring data = (wchar_t*)(buffer);
|
||||
std::wstring key = achValue;
|
||||
|
||||
LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData);
|
||||
std::string data = (char*)(buffer);
|
||||
std::string key = achValue;
|
||||
|
||||
switch (ID) {
|
||||
case 1:
|
||||
if (key == L"SteamExe") {
|
||||
auto p = data.find_last_of(L"/\\");
|
||||
if (key == "SteamExe") {
|
||||
auto p = data.find_last_of("/\\");
|
||||
if (p != std::string::npos) {
|
||||
return data.substr(0, p);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (key == L"Name" && data == L"BeamNG.drive")
|
||||
if (key == "Name" && data == "BeamNG.drive")
|
||||
return data;
|
||||
break;
|
||||
case 3:
|
||||
if (key == L"rootpath")
|
||||
if (key == "rootpath")
|
||||
return data;
|
||||
break;
|
||||
case 4:
|
||||
if (key == L"userpath_override")
|
||||
if (key == "userpath_override")
|
||||
return data;
|
||||
case 5:
|
||||
if (key == L"Local AppData")
|
||||
if (key == "Local AppData")
|
||||
return data;
|
||||
default:
|
||||
break;
|
||||
@@ -133,9 +131,8 @@ std::wstring QueryKey(HKEY hKey, int ID) {
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] achValue;
|
||||
delete[] buffer;
|
||||
return L"";
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -162,70 +159,24 @@ void FileList(std::vector<std::string>& a, const std::string& Path) {
|
||||
}
|
||||
void LegitimacyCheck() {
|
||||
#if defined(_WIN32)
|
||||
wchar_t* appDataPath = new wchar_t[MAX_PATH];
|
||||
HRESULT result = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
|
||||
|
||||
if (!SUCCEEDED(result)) {
|
||||
fatal("Cannot get Local Appdata directory");
|
||||
}
|
||||
|
||||
auto BeamNGAppdataPath = std::filesystem::path(appDataPath) / "BeamNG";
|
||||
|
||||
if (const auto beamngIniPath = BeamNGAppdataPath / "BeamNG.Drive.ini"; exists(beamngIniPath)) {
|
||||
if (std::ifstream beamngIni(beamngIniPath); beamngIni.is_open()) {
|
||||
std::string contents((std::istreambuf_iterator(beamngIni)), std::istreambuf_iterator<char>());
|
||||
beamngIni.close();
|
||||
|
||||
if (contents.size() >= 3 && (unsigned char)contents[0] == 0xEF && (unsigned char)contents[1] == 0xBB && (unsigned char)contents[2] == 0xBF) {
|
||||
contents = contents.substr(3);
|
||||
}
|
||||
|
||||
auto ini = Utils::ParseINI(contents);
|
||||
if (ini.empty())
|
||||
lowExit(3);
|
||||
else
|
||||
debug("Successfully parsed BeamNG.Drive.ini");
|
||||
|
||||
if (ini.contains("installPath")) {
|
||||
std::wstring installPath = Utils::ToWString(std::get<std::string>(ini["installPath"]));
|
||||
installPath.erase(0, installPath.find_first_not_of(L" \t"));
|
||||
|
||||
if (installPath = std::filesystem::path(Utils::ExpandEnvVars(installPath)); std::filesystem::exists(installPath)) {
|
||||
GameDir = installPath;
|
||||
debug(L"GameDir from BeamNG.Drive.ini: " + installPath);
|
||||
} else {
|
||||
lowExit(4);
|
||||
}
|
||||
} else {
|
||||
lowExit(5);
|
||||
}
|
||||
std::string Result;
|
||||
std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
|
||||
HKEY hKey;
|
||||
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
|
||||
if (dwRegOPenKey == ERROR_SUCCESS) {
|
||||
Result = QueryKey(hKey, 3);
|
||||
if (Result.empty()) {
|
||||
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
lowExit(3);
|
||||
}
|
||||
GameDir = Result;
|
||||
} else {
|
||||
std::wstring Result;
|
||||
|
||||
std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
|
||||
|
||||
HKEY hKey;
|
||||
|
||||
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
|
||||
if (dwRegOPenKey == ERROR_SUCCESS) {
|
||||
Result = QueryKey(hKey, 3);
|
||||
if (Result.empty()) {
|
||||
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
lowExit(6);
|
||||
}
|
||||
GameDir = Result;
|
||||
debug(L"GameDir from registry: " + Result);
|
||||
} else {
|
||||
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
lowExit(7);
|
||||
}
|
||||
K3.clear();
|
||||
Result.clear();
|
||||
RegCloseKey(hKey);
|
||||
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
|
||||
lowExit(4);
|
||||
}
|
||||
|
||||
delete[] appDataPath;
|
||||
K3.clear();
|
||||
Result.clear();
|
||||
RegCloseKey(hKey);
|
||||
#elif defined(__linux__)
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
std::filesystem::path homeDir = pw->pw_dir;
|
||||
@@ -233,7 +184,6 @@ void LegitimacyCheck() {
|
||||
// Right now only steam is supported
|
||||
std::vector<std::filesystem::path> steamappsCommonPaths = {
|
||||
".steam/root/steamapps", // default
|
||||
".steam/steam/steamapps", // Legacy Steam installations
|
||||
".var/app/com.valvesoftware.Steam/.steam/root/steamapps", // flatpak
|
||||
"snap/steam/common/.local/share/Steam/steamapps" // snap
|
||||
};
|
||||
@@ -268,7 +218,7 @@ void LegitimacyCheck() {
|
||||
std::ifstream libraryFolders(libraryFoldersPath);
|
||||
auto root = tyti::vdf::read(libraryFolders);
|
||||
for (auto folderInfo : root.childs) {
|
||||
if ((folderInfo.second->childs["apps"]->attribs).contains("284160") && std::filesystem::exists(folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/integrity.json")){
|
||||
if (std::filesystem::exists(folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/integrity.json")){
|
||||
GameDir = folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/";
|
||||
break;
|
||||
}
|
||||
@@ -279,9 +229,12 @@ void LegitimacyCheck() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
std::string CheckVer(const std::filesystem::path& dir) {
|
||||
std::string temp;
|
||||
std::filesystem::path Path = dir / beammp_wide("integrity.json");
|
||||
std::string CheckVer(const std::string& dir) {
|
||||
#if defined(_WIN32)
|
||||
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);
|
||||
int Size = int(std::filesystem::file_size(Path));
|
||||
std::string vec(Size, 0);
|
||||
|
||||
332
src/Startup.cpp
332
src/Startup.cpp
@@ -4,6 +4,7 @@
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
||||
#include "zip_file.h"
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
@@ -11,23 +12,20 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#elif defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <libproc.h>
|
||||
#include <unistd.h>
|
||||
#endif // __APPLE__
|
||||
#endif
|
||||
#include "Http.h"
|
||||
#include "Logger.h"
|
||||
#include "Network/network.hpp"
|
||||
#include "Options.h"
|
||||
#include "Security/Init.h"
|
||||
#include "Startup.h"
|
||||
#include "Utils.h"
|
||||
#include "hashpp.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include "Options.h"
|
||||
|
||||
extern int TraceBack;
|
||||
int ProxyPort = 0;
|
||||
@@ -74,77 +72,49 @@ Version::Version(const std::array<uint8_t, 3>& v)
|
||||
: Version(v[0], v[1], v[2]) {
|
||||
}
|
||||
|
||||
beammp_fs_string GetEN() {
|
||||
std::string GetEN() {
|
||||
#if defined(_WIN32)
|
||||
return L"BeamMP-Launcher.exe";
|
||||
return "BeamMP-Launcher.exe";
|
||||
#elif defined(__linux__)
|
||||
return "BeamMP-Launcher";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetVer() {
|
||||
return "2.8";
|
||||
return "2.3";
|
||||
}
|
||||
std::string GetPatch() {
|
||||
return ".0";
|
||||
return ".2";
|
||||
}
|
||||
|
||||
beammp_fs_string GetEP(const beammp_fs_char* P) {
|
||||
static beammp_fs_string Ret = [&]() {
|
||||
beammp_fs_string path(P);
|
||||
return path.substr(0, path.find_last_of(beammp_wide("\\/")) + 1);
|
||||
std::string GetEP(const char* P) {
|
||||
static std::string Ret = [&]() {
|
||||
std::string path(P);
|
||||
return path.substr(0, path.find_last_of("\\/") + 1);
|
||||
}();
|
||||
return Ret;
|
||||
}
|
||||
|
||||
fs::path GetBP(const beammp_fs_char* P) {
|
||||
fs::path fspath = {};
|
||||
#if defined(_WIN32)
|
||||
beammp_fs_char path[256];
|
||||
GetModuleFileNameW(nullptr, path, sizeof(path));
|
||||
fspath = path;
|
||||
#elif defined(__linux__)
|
||||
fspath = fs::canonical("/proc/self/exe");
|
||||
#elif defined(__APPLE__)
|
||||
pid_t pid = getpid();
|
||||
char path[PROC_PIDPATHINFO_MAXSIZE];
|
||||
// While this is fine for a raw executable,
|
||||
// an application bundle is read-only and these files
|
||||
// should instead be placed in Application Support.
|
||||
proc_pidpath(pid, path, sizeof(path));
|
||||
fspath = std::string(path);
|
||||
#else
|
||||
fspath = beammp_fs_string(P);
|
||||
#endif
|
||||
fspath = fs::weakly_canonical(fspath.string() + "/..");
|
||||
#if defined(_WIN32)
|
||||
return fspath.wstring();
|
||||
#else
|
||||
return fspath.string();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
void ReLaunch() {
|
||||
std::wstring Arg;
|
||||
std::string Arg;
|
||||
for (int c = 2; c <= options.argc; c++) {
|
||||
Arg += Utils::ToWString(options.argv[c - 1]);
|
||||
Arg += L" ";
|
||||
Arg += options.argv[c - 1];
|
||||
Arg += " ";
|
||||
}
|
||||
info("Relaunch!");
|
||||
system("cls");
|
||||
ShellExecuteW(nullptr, L"runas", (GetBP() / 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);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
exit(1);
|
||||
}
|
||||
void URelaunch() {
|
||||
std::wstring Arg;
|
||||
std::string Arg;
|
||||
for (int c = 2; c <= options.argc; c++) {
|
||||
Arg += Utils::ToWString(options.argv[c - 1]);
|
||||
Arg += L" ";
|
||||
Arg += options.argv[c - 1];
|
||||
Arg += " ";
|
||||
}
|
||||
ShellExecuteW(nullptr, L"open", (GetBP() / GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
|
||||
ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
|
||||
ShowWindow(GetConsoleWindow(), 0);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
exit(1);
|
||||
@@ -158,7 +128,7 @@ void ReLaunch() {
|
||||
}
|
||||
info("Relaunch!");
|
||||
system("clear");
|
||||
int ret = execv((GetBP() / GetEN()).c_str(), const_cast<char**>(options.argv));
|
||||
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
|
||||
if (ret < 0) {
|
||||
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
|
||||
exit(1);
|
||||
@@ -167,7 +137,7 @@ void ReLaunch() {
|
||||
exit(1);
|
||||
}
|
||||
void URelaunch() {
|
||||
int ret = execv((GetBP() / GetEN()).c_str(), const_cast<char**>(options.argv));
|
||||
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
|
||||
if (ret < 0) {
|
||||
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
|
||||
exit(1);
|
||||
@@ -179,229 +149,51 @@ void URelaunch() {
|
||||
|
||||
void CheckName() {
|
||||
#if defined(_WIN32)
|
||||
std::wstring DN = GetEN(), CDir = Utils::ToWString(options.executable_name), FN = CDir.substr(CDir.find_last_of('\\') + 1);
|
||||
std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('\\') + 1);
|
||||
#elif defined(__linux__)
|
||||
std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('/') + 1);
|
||||
#endif
|
||||
if (FN != DN) {
|
||||
if (fs::exists(DN))
|
||||
fs::remove(DN.c_str());
|
||||
remove(DN.c_str());
|
||||
if (fs::exists(DN))
|
||||
ReLaunch();
|
||||
fs::rename(FN.c_str(), DN.c_str());
|
||||
std::rename(FN.c_str(), DN.c_str());
|
||||
URelaunch();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <wincrypt.h>
|
||||
#include <wintrust.h>
|
||||
#include <softpub.h>
|
||||
#include <iostream>
|
||||
|
||||
#pragma comment(lib, "wintrust.lib")
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
|
||||
bool CheckThumbprint(std::filesystem::path filepath)
|
||||
{
|
||||
HCERTSTORE hStore = NULL;
|
||||
HCRYPTMSG hMsg = NULL;
|
||||
|
||||
if (!CryptQueryObject(
|
||||
CERT_QUERY_OBJECT_FILE,
|
||||
filepath.wstring().c_str(),
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
|
||||
CERT_QUERY_FORMAT_FLAG_BINARY,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
&hStore,
|
||||
&hMsg,
|
||||
NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD dwSignerInfo = 0;
|
||||
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo) || dwSignerInfo == 0)
|
||||
return false;
|
||||
|
||||
PCMSG_SIGNER_INFO pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
|
||||
if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, pSignerInfo, &dwSignerInfo))
|
||||
{
|
||||
LocalFree(pSignerInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
CERT_INFO certInfo = {};
|
||||
certInfo.Issuer = pSignerInfo->Issuer;
|
||||
certInfo.SerialNumber = pSignerInfo->SerialNumber;
|
||||
|
||||
PCCERT_CONTEXT pCertContext = CertFindCertificateInStore(
|
||||
hStore,
|
||||
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
0,
|
||||
CERT_FIND_SUBJECT_CERT,
|
||||
&certInfo,
|
||||
NULL);
|
||||
|
||||
if (!pCertContext)
|
||||
{
|
||||
LocalFree(pSignerInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
BYTE hash[64];
|
||||
DWORD hashSize = sizeof(hash);
|
||||
|
||||
bool match = false;
|
||||
if (CertGetCertificateContextProperty(pCertContext, CERT_SHA256_HASH_PROP_ID, hash, &hashSize))
|
||||
{
|
||||
std::string pubKeyData(
|
||||
reinterpret_cast<const char*>(pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData),
|
||||
pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData
|
||||
);
|
||||
|
||||
std::string pubKeyHash = Utils::GetSha256HashReallyFast(pubKeyData, L"PubKey");
|
||||
debug("pub key hash: " + pubKeyHash);
|
||||
|
||||
std::string fileThumbprint;
|
||||
for (DWORD i = 0; i < hashSize; i++)
|
||||
{
|
||||
char buf[3];
|
||||
sprintf_s(buf, "%02x", hash[i]);
|
||||
fileThumbprint += buf;
|
||||
}
|
||||
|
||||
debug("File thumbprint: " +fileThumbprint);
|
||||
debug(filepath);
|
||||
|
||||
if (fileThumbprint == "937f055b713de69416926ed4651d65219a0a0e77d7a78c1932c007e14326da33" && pubKeyHash == "2afad4a5773b0ac449f48350ce0d09c372be0d5bcbaa6d01332ce000baffde99"){
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(pCertContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
LocalFree(pSignerInfo);
|
||||
|
||||
return match;
|
||||
}
|
||||
#include <windows.h>
|
||||
#include <wintrust.h>
|
||||
#include <Softpub.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#pragma comment(lib, "wintrust")
|
||||
|
||||
bool VerifySignature(const std::filesystem::path& filePath)
|
||||
{
|
||||
std::wstring path = filePath.wstring();
|
||||
|
||||
WINTRUST_FILE_INFO fileInfo = {};
|
||||
fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
|
||||
fileInfo.pcwszFilePath = path.c_str();
|
||||
fileInfo.hFile = NULL;
|
||||
fileInfo.pgKnownSubject = NULL;
|
||||
|
||||
WINTRUST_DATA winTrustData = {};
|
||||
winTrustData.cbStruct = sizeof(WINTRUST_DATA);
|
||||
winTrustData.dwUIChoice = WTD_UI_NONE;
|
||||
winTrustData.dwUnionChoice = WTD_CHOICE_FILE;
|
||||
winTrustData.pFile = &fileInfo;
|
||||
|
||||
winTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
|
||||
|
||||
GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
|
||||
LONG status = WinVerifyTrust(
|
||||
NULL,
|
||||
&policyGUID,
|
||||
&winTrustData
|
||||
);
|
||||
|
||||
debug(filePath);
|
||||
debug("Signature check code: " + std::to_string(status));
|
||||
|
||||
winTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
|
||||
WinVerifyTrust(NULL, &policyGUID, &winTrustData);
|
||||
|
||||
return (status == CERT_E_UNTRUSTEDROOT);
|
||||
}
|
||||
#endif
|
||||
|
||||
void CheckForUpdates(const std::string& CV) {
|
||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey);
|
||||
std::string LatestVersion = HTTP::Get(
|
||||
"https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey);
|
||||
|
||||
std::regex sha256_pattern(R"(^[a-fA-F0-9]{64}$)");
|
||||
std::smatch match;
|
||||
|
||||
if (LatestHash.length() != 64 || !std::regex_match(LatestHash, match, sha256_pattern)) {
|
||||
error("Invalid hash from backend, skipping update check.");
|
||||
debug("Launcher hash in question: " + LatestHash);
|
||||
return;
|
||||
}
|
||||
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
|
||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher", requestBody);
|
||||
std::string LatestVersion = HTTP::Get("https://backend.beammp.com/version/launcher", requestBody);
|
||||
|
||||
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
||||
beammp_fs_string BP(GetBP() / GetEN()), Back(GetBP() / beammp_wide("BeamMP-Launcher.back"));
|
||||
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
|
||||
|
||||
std::string FileHash = Utils::GetSha256HashReallyFastFile(BP);
|
||||
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
|
||||
|
||||
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
|
||||
if (!options.no_update) {
|
||||
info("Launcher update " + LatestVersion + " 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::rename(EP, Back);
|
||||
info("Downloading Launcher update " + LatestHash);
|
||||
std::wstring DownloadLocation = GetBP() / (beammp_wide("new_") + GetEN());
|
||||
if (HTTP::Download(
|
||||
"https://backend.beammp.com/builds/launcher?download=true"
|
||||
"&pk="
|
||||
+ PublicKey + "&branch=" + Branch,
|
||||
DownloadLocation, LatestHash)) {
|
||||
if (!VerifySignature(DownloadLocation) || !CheckThumbprint(DownloadLocation)) {
|
||||
std::error_code ec;
|
||||
fs::remove(DownloadLocation, ec);
|
||||
if (ec) {
|
||||
error("Failed to remove broken launcher update");
|
||||
}
|
||||
throw std::runtime_error("The authenticity of the updated launcher could not be verified, it was corrupted or tampered with.");
|
||||
}
|
||||
|
||||
info("Update signature is valid");
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove(Back, ec);
|
||||
if (ec == std::errc::permission_denied) {
|
||||
error("Failed to remove old backup file: " + ec.message() + ". Using alternative name.");
|
||||
fs::rename(BP, Back + beammp_wide(".") + Utils::ToWString(FileHash.substr(0, 8)));
|
||||
} else {
|
||||
fs::rename(BP, Back);
|
||||
}
|
||||
fs::rename(GetBP() / (beammp_wide("new_") + GetEN()), BP);
|
||||
URelaunch();
|
||||
} else {
|
||||
if (fs::exists(DownloadLocation)) {
|
||||
std::error_code error_code;
|
||||
fs::remove(DownloadLocation, error_code);
|
||||
if (error_code) {
|
||||
error("Failed to remove broken launcher update");
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Failed to download the launcher update! Please try manually updating it, https://docs.beammp.com/FAQ/Update-launcher/");
|
||||
}
|
||||
HTTP::Download("https://backend.beammp.com/builds/launcher?download=true", requestBody, EP);
|
||||
URelaunch();
|
||||
#endif
|
||||
} else {
|
||||
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
|
||||
}
|
||||
} else
|
||||
info("Launcher version is up to date. Latest version: " + LatestVersion);
|
||||
info("Launcher version is up to date");
|
||||
TraceBack++;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
void LinuxPatch() {
|
||||
HKEY hKey = nullptr;
|
||||
@@ -435,9 +227,6 @@ void LinuxPatch() {
|
||||
|
||||
void InitLauncher() {
|
||||
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
_setmode(_fileno(stdout), _O_U8TEXT);
|
||||
debug("Launcher Version : " + GetVer() + GetPatch());
|
||||
CheckName();
|
||||
LinuxPatch();
|
||||
CheckLocalKey();
|
||||
@@ -453,11 +242,11 @@ void InitLauncher() {
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t DirCount(const fs::path& path) {
|
||||
return (size_t)std::distance(fs::directory_iterator { path }, fs::directory_iterator {});
|
||||
size_t DirCount(const std::filesystem::path& path) {
|
||||
return (size_t)std::distance(std::filesystem::directory_iterator { path }, std::filesystem::directory_iterator {});
|
||||
}
|
||||
|
||||
void CheckMP(const beammp_fs_string& Path) {
|
||||
void CheckMP(const std::string& Path) {
|
||||
if (!fs::exists(Path))
|
||||
return;
|
||||
size_t c = DirCount(fs::path(Path));
|
||||
@@ -477,7 +266,7 @@ void CheckMP(const beammp_fs_string& Path) {
|
||||
}
|
||||
|
||||
void EnableMP() {
|
||||
beammp_fs_string File(GetGamePath() / beammp_wide("mods/db.json"));
|
||||
std::string File(GetGamePath() + "mods/db.json");
|
||||
if (!fs::exists(File))
|
||||
return;
|
||||
auto Size = fs::file_size(File);
|
||||
@@ -500,63 +289,52 @@ void EnableMP() {
|
||||
ofs << d.dump();
|
||||
ofs.close();
|
||||
} else {
|
||||
error(beammp_wide("Failed to write ") + File);
|
||||
error("Failed to write " + File);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreGame(const beammp_fs_string& GamePath) {
|
||||
void PreGame(const std::string& GamePath) {
|
||||
std::string GameVer = CheckVer(GamePath);
|
||||
info("Game Version : " + GameVer);
|
||||
|
||||
CheckMP(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
info(beammp_wide("Game user path: ") + beammp_fs_string(GetGamePath()));
|
||||
CheckMP(GetGamePath() + "mods/multiplayer");
|
||||
info("Game user path: " + GetGamePath());
|
||||
|
||||
if (!options.no_download) {
|
||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
|
||||
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
|
||||
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod", requestBody);
|
||||
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
|
||||
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
|
||||
[](auto const& c) -> bool { return !std::isalnum(c); }),
|
||||
LatestHash.end());
|
||||
|
||||
std::regex sha256_pattern(R"(^[a-fA-F0-9]{64}$)");
|
||||
std::smatch match;
|
||||
|
||||
if (LatestHash.length() != 64 || !std::regex_match(LatestHash, match, sha256_pattern)) {
|
||||
error("Invalid hash from backend, skipping mod update check.");
|
||||
debug("Mod hash in question: " + LatestHash);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) {
|
||||
fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer"));
|
||||
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
|
||||
fs::create_directories(GetGamePath() + "mods/multiplayer");
|
||||
}
|
||||
EnableMP();
|
||||
} catch (std::exception& e) {
|
||||
fatal(e.what());
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
std::wstring ZipPath(GetGamePath() / LR"(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)");
|
||||
std::string ZipPath(GetGamePath() + R"(mods/multiplayer/beammp.zip)");
|
||||
#endif
|
||||
|
||||
std::string FileHash = fs::exists(ZipPath) ? Utils::GetSha256HashReallyFastFile(ZipPath) : "";
|
||||
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath);
|
||||
|
||||
if (FileHash != LatestHash) {
|
||||
info("Downloading BeamMP Update " + LatestHash);
|
||||
HTTP::Download("https://backend.beammp.com/builds/client?download=true"
|
||||
"&pk="
|
||||
+ PublicKey + "&branch=" + Branch,
|
||||
ZipPath, LatestHash);
|
||||
HTTP::Download("https://backend.beammp.com/builds/client?download=true", requestBody, ZipPath);
|
||||
}
|
||||
|
||||
beammp_fs_string Target(GetGamePath() / beammp_wide("mods/unpacked/beammp"));
|
||||
std::string Target(GetGamePath() + "mods/unpacked/beammp");
|
||||
|
||||
if (fs::is_directory(Target) && !fs::is_directory(Target + beammp_wide("/.git"))) {
|
||||
if (fs::is_directory(Target)) {
|
||||
fs::remove_all(Target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "Network/network.hpp"
|
||||
#include "Security/Init.h"
|
||||
#include "Startup.h"
|
||||
#include "Utils.h"
|
||||
#include <curl/curl.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
@@ -38,7 +37,7 @@ int main(int argc, const char** argv) try {
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
|
||||
GetEP(Utils::ToWString(std::string(argv[0])).c_str());
|
||||
GetEP(argv[0]);
|
||||
|
||||
InitLog();
|
||||
ConfigInit();
|
||||
|
||||
Reference in New Issue
Block a user