From 5e448dc34fef1c1e77668e91eb7e6fffee3fd614 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Wed, 13 Nov 2024 23:39:20 +0100 Subject: [PATCH 01/14] Switch to wstring for paths on windows --- include/Http.h | 2 +- include/Logger.h | 7 ++ include/Security/Init.h | 8 +-- include/Startup.h | 6 +- include/Utils.h | 133 ++++++++++++++++++++++++++++++++++++++ src/GameStart.cpp | 67 +++++++++---------- src/Logger.cpp | 47 +++++++++++++- src/Network/Http.cpp | 4 +- src/Network/Resources.cpp | 86 ++++-------------------- src/Security/BeamNG.cpp | 47 +++++++------- src/Startup.cpp | 69 ++++++++++---------- src/main.cpp | 3 +- 12 files changed, 303 insertions(+), 176 deletions(-) diff --git a/include/Http.h b/include/Http.h index c3bbb0e..cd9bd83 100644 --- a/include/Http.h +++ b/include/Http.h @@ -9,7 +9,7 @@ #include class HTTP { public: - static bool Download(const std::string& IP, const std::string& Path); + static bool Download(const std::string& IP, const std::wstring& Path); static std::string Post(const std::string& IP, const std::string& Fields); static std::string Get(const std::string& IP); static bool ProgressBar(size_t c, size_t t); diff --git a/include/Logger.h b/include/Logger.h index 3a93a7c..ddea34b 100644 --- a/include/Logger.h +++ b/include/Logger.h @@ -14,4 +14,11 @@ 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(); diff --git a/include/Security/Init.h b/include/Security/Init.h index d34d3b4..4667bad 100644 --- a/include/Security/Init.h +++ b/include/Security/Init.h @@ -6,9 +6,9 @@ #pragma once #include -void PreGame(const std::string& GamePath); -std::string CheckVer(const std::string& path); -void InitGame(const std::string& Dir); -std::string GetGameDir(); +void PreGame(const std::wstring& GamePath); +std::string CheckVer(const std::wstring& path); +void InitGame(const std::wstring& Dir); +std::wstring GetGameDir(); void LegitimacyCheck(); void CheckLocalKey(); \ No newline at end of file diff --git a/include/Startup.h b/include/Startup.h index 036a4ae..daebb13 100644 --- a/include/Startup.h +++ b/include/Startup.h @@ -10,9 +10,9 @@ #include void InitLauncher(); -std::string GetEP(const char* P = nullptr); -std::string GetGamePath(); +std::wstring GetEP(const wchar_t* P = nullptr); +std::wstring GetGamePath(); std::string GetVer(); std::string GetPatch(); -std::string GetEN(); +std::wstring GetEN(); void ConfigInit(); diff --git a/include/Utils.h b/include/Utils.h index 33c01d6..c4ff384 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -7,6 +7,11 @@ #pragma once #include #include +#include +#include +#include +#include +#include #include #include @@ -112,4 +117,132 @@ namespace Utils { return ini; } + inline std::string ToString(const std::wstring& w) { + return std::wstring_convert>().to_bytes(w); + } + inline std::wstring ToWString(const std::string& s) { + return std::wstring_convert>().from_bytes(s); + } + inline 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 Data; + while (Read < FileSize) { + Data.resize(size_t(std::min(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 ""; + } + } + inline std::string GetSha256HashReallyFast(const std::wstring& 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::wifstream stream(filename, std::ios::binary); + + const size_t FileSize = std::filesystem::file_size(filename); + size_t Read = 0; + std::vector Data; + while (Read < FileSize) { + Data.resize(size_t(std::min(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(L"Sha256 hashing of '" + filename + L"' failed: " + ToWString(e.what())); + return ""; + } + } }; \ No newline at end of file diff --git a/src/GameStart.cpp b/src/GameStart.cpp index 7446ae9..d75fac0 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -4,10 +4,9 @@ SPDX-License-Identifier: AGPL-3.0-or-later */ - #if defined(_WIN32) -#include #include +#include #elif defined(__linux__) #include "vdf_parser.hpp" #include @@ -29,8 +28,8 @@ unsigned long GamePID = 0; #if defined(_WIN32) -std::string QueryKey(HKEY hKey, int ID); -std::string GetGamePath() { +std::wstring QueryKey(HKEY hKey, int ID); +std::wstring GetGamePath() { static std::filesystem::path Path; if (!Path.empty()) return Path.string(); @@ -54,22 +53,21 @@ std::string GetGamePath() { } auto ini = Utils::ParseINI(contents); - if (ini.empty()) { - warn("Failed to parse startup.ini"); + if (ini.empty()) { + warn("Failed to parse startup.ini"); + } else + debug("Successfully parsed startup.ini"); + + std::string userPath; + if (ini.contains("filesystem") && ini["filesystem"].contains("UserPath")) + userPath = ini["filesystem"]["UserPath"]; + + if (!userPath.empty()) + if (userPath = Utils::ExpandEnvVars(userPath); std::filesystem::exists(userPath)) { + Path = userPath; + debug("Using custom user folder path from startup.ini: " + Path.string()); } else - debug("Successfully parsed startup.ini"); - - - std::string userPath; - if (ini.contains("filesystem") && ini["filesystem"].contains("UserPath")) - userPath = ini["filesystem"]["UserPath"]; - - if (!userPath.empty()) - if (userPath = Utils::ExpandEnvVars(userPath); std::filesystem::exists(userPath)) { - Path = userPath; - debug("Using custom user folder path from startup.ini: " + Path.string()); - } else - warn("Found custom user folder path ("+ userPath + ") in startup.ini but it doesn't exist, skipping"); + warn("Found custom user folder path (" + userPath + ") in startup.ini but it doesn't exist, skipping"); } if (Path.empty()) { @@ -82,22 +80,24 @@ std::string GetGamePath() { Path = QueryKey(hKey, 4); if (Path.empty()) { - char appDataPath[MAX_PATH]; - HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath); + 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"); } Path = std::filesystem::path(appDataPath) / "BeamNG.drive"; + + delete[] appDataPath; } } } std::string Ver = CheckVer(GetGameDir()); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); - Path = Path / (Ver + "\\"); - return Path.string(); + Path += Utils::ToWString(Ver) + L"\\"; + return Path; } #elif defined(__linux__) std::string GetGamePath() { @@ -114,24 +114,24 @@ std::string GetGamePath() { #endif #if defined(_WIN32) -void StartGame(std::string Dir) { +void StartGame(std::wstring Dir) { BOOL bSuccess = FALSE; PROCESS_INFORMATION pi; - STARTUPINFO si = { 0 }; + STARTUPINFOW si = { 0 }; si.cb = sizeof(si); - std::string BaseDir = Dir; //+"\\Bin64"; + std::wstring BaseDir = Dir; //+"\\Bin64"; // Dir += R"(\Bin64\BeamNG.drive.x64.exe)"; - Dir += "\\BeamNG.drive.exe"; - std::string gameArgs = ""; + Dir += L"\\BeamNG.drive.exe"; + std::wstring gameArgs = L""; for (int i = 0; i < options.game_arguments_length; i++) { - gameArgs += " "; - gameArgs += options.game_arguments[i]; + gameArgs += L" "; + gameArgs += Utils::ToWString(options.game_arguments[i]); } debug("BeamNG executable path: " + Dir); - bSuccess = CreateProcessA(nullptr, (LPSTR)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi); + bSuccess = CreateProcessW(nullptr, (wchar_t*)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi); if (bSuccess) { info("Game Launched!"); GamePID = pi.dwProcessId; @@ -144,7 +144,8 @@ void StartGame(std::string Dir) { 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) { + 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; @@ -186,7 +187,7 @@ void StartGame(std::string Dir) { } #endif -void InitGame(const std::string& Dir) { +void InitGame(const std::wstring& Dir) { if (!options.no_launch) { std::thread Game(StartGame, Dir); Game.detach(); diff --git a/src/Logger.cpp b/src/Logger.cpp index b6fa327..f13056e 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -7,6 +7,7 @@ #include "Logger.h" #include "Startup.h" +#include "Utils.h" #include #include #include @@ -36,7 +37,7 @@ std::string getDate() { } void InitLog() { std::ofstream LFS; - LFS.open(GetEP() + "Launcher.log"); + LFS.open(GetEP() + L"Launcher.log"); if (!LFS.is_open()) { error("logger file init failed!"); } else @@ -44,7 +45,13 @@ void InitLog() { } void addToLog(const std::string& Line) { std::ofstream LFS; - LFS.open(GetEP() + "Launcher.log", std::ios_base::app); + LFS.open(GetEP() + L"Launcher.log", std::ios_base::app); + LFS << Line.c_str(); + LFS.close(); +} +void addToLog(const std::wstring& Line) { + std::wofstream LFS; + LFS.open(GetEP() + L"Launcher.log", std::ios_base::app); LFS << Line.c_str(); LFS.close(); } @@ -82,3 +89,39 @@ void except(const std::string& toPrint) { std::cout << Print; addToLog(Print); } + + +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); +} \ No newline at end of file diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index d9a5819..649c6a9 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -127,7 +127,7 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) { return Ret; } -bool HTTP::Download(const std::string& IP, const std::string& Path) { +bool HTTP::Download(const std::string& IP, const std::wstring& Path) { static std::mutex Lock; std::scoped_lock Guard(Lock); @@ -145,7 +145,7 @@ bool HTTP::Download(const std::string& IP, const std::string& Path) { File.close(); info("Download Complete!"); } else { - error("Failed to open file directory: " + Path); + error(L"Failed to open file directory: " + Path); return false; } diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index 1cfa90e..22da317 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -301,68 +301,6 @@ 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 Data; - while (Read < FileSize) { - Data.resize(size_t(std::min(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> ParseModInfosFromPacket(const std::string& packet) { bool success = false; @@ -507,13 +445,13 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string(); auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string(); - if (fs::exists(PathToSaveTo) && GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) { + if (fs::exists(PathToSaveTo) && Utils::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() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { + fs::create_directories(GetGamePath() + L"mods/multiplayer"); } auto modname = ModInfoIter->FileName; #if defined(__linux__) @@ -630,8 +568,8 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileSize && !Terminate); if (!Terminate) { - if (!fs::exists(GetGamePath() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { + fs::create_directories(GetGamePath() + L"mods/multiplayer"); } // Linux version of the game doesnt support uppercase letters in mod names @@ -729,8 +667,8 @@ void SyncResources(SOCKET Sock) { UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/'))); std::this_thread::sleep_for(std::chrono::milliseconds(50)); try { - if (!fs::exists(GetGamePath() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { + fs::create_directories(GetGamePath() + L"mods/multiplayer"); } auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); #if defined(__linux__) @@ -739,8 +677,8 @@ void SyncResources(SOCKET Sock) { c = ::tolower(c); } #endif - auto name = GetGamePath() + "mods/multiplayer" + modname; - auto tmp_name = name + ".tmp"; + auto name = GetGamePath() + L"mods/multiplayer" + Utils::ToWString(modname); + auto tmp_name = name + L".tmp"; fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing); fs::rename(tmp_name, name); UpdateModUsage(modname); @@ -787,8 +725,8 @@ void SyncResources(SOCKET Sock) { } } while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate); if (!Terminate) { - if (!fs::exists(GetGamePath() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { + fs::create_directories(GetGamePath() + L"mods/multiplayer"); } // Linux version of the game doesnt support uppercase letters in mod names @@ -798,7 +736,7 @@ void SyncResources(SOCKET Sock) { } #endif - fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing); + fs::copy_file(PathToSaveTo, GetGamePath() + L"mods/multiplayer" + Utils::ToWString(FName), fs::copy_options::overwrite_existing); UpdateModUsage(FN->substr(pos)); } WaitForConfirm(); diff --git a/src/Security/BeamNG.cpp b/src/Security/BeamNG.cpp index 88b4542..2704d56 100644 --- a/src/Security/BeamNG.cpp +++ b/src/Security/BeamNG.cpp @@ -23,7 +23,7 @@ #define MAX_VALUE_NAME 16383 int TraceBack = 0; -std::string GameDir; +std::wstring GameDir; void lowExit(int code) { TraceBack = 0; @@ -33,7 +33,7 @@ void lowExit(int code) { exit(2); } -std::string GetGameDir() { +std::wstring GetGameDir() { #if defined(_WIN32) return GameDir.substr(0, GameDir.find_last_of('\\')); #elif defined(__linux__) @@ -44,8 +44,8 @@ std::string GetGameDir() { LONG OpenKey(HKEY root, const char* path, PHKEY hKey) { return RegOpenKeyEx(root, reinterpret_cast(path), 0, KEY_READ, hKey); } -std::string QueryKey(HKEY hKey, int ID) { - TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name +std::wstring QueryKey(HKEY hKey, int ID) { + wchar_t* achKey; // 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 @@ -60,7 +60,7 @@ std::string QueryKey(HKEY hKey, int ID) { DWORD i, retCode; - TCHAR achValue[MAX_VALUE_NAME]; + wchar_t* achValue = new wchar_t[MAX_VALUE_NAME]; DWORD cchValue = MAX_VALUE_NAME; retCode = RegQueryInfoKey( @@ -82,9 +82,9 @@ std::string QueryKey(HKEY hKey, int ID) { if (cSubKeys) { for (i = 0; i < cSubKeys; i++) { cbName = MAX_KEY_LENGTH; - retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime); + retCode = RegEnumKeyExW(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime); if (retCode == ERROR_SUCCESS) { - if (strcmp(achKey, "Steam App 284160") == 0) { + if (wcscmp(achKey, L"Steam App 284160") == 0) { return achKey; } } @@ -94,36 +94,37 @@ std::string QueryKey(HKEY hKey, int ID) { for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++) { cchValue = MAX_VALUE_NAME; achValue[0] = '\0'; - retCode = RegEnumValue(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr); + retCode = RegEnumValueW(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr); if (retCode == ERROR_SUCCESS) { DWORD lpData = cbMaxValueData; buffer[0] = '\0'; - LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData); - std::string data = (char*)(buffer); - std::string key = achValue; + LONG dwRes = RegQueryValueExW(hKey, achValue, nullptr, nullptr, buffer, &lpData); + std::wstring data = (wchar_t*)(buffer); + std::wstring key = achValue; + switch (ID) { case 1: - if (key == "SteamExe") { - auto p = data.find_last_of("/\\"); + if (key == L"SteamExe") { + auto p = data.find_last_of(L"/\\"); if (p != std::string::npos) { return data.substr(0, p); } } break; case 2: - if (key == "Name" && data == "BeamNG.drive") + if (key == L"Name" && data == L"BeamNG.drive") return data; break; case 3: - if (key == "rootpath") + if (key == L"rootpath") return data; break; case 4: - if (key == "userpath_override") + if (key == L"userpath_override") return data; case 5: - if (key == "Local AppData") + if (key == L"Local AppData") return data; default: break; @@ -131,8 +132,9 @@ std::string QueryKey(HKEY hKey, int ID) { } } } + delete[] achValue; delete[] buffer; - return ""; + return L""; } #endif @@ -159,7 +161,7 @@ void FileList(std::vector& a, const std::string& Path) { } void LegitimacyCheck() { #if defined(_WIN32) - std::string Result; + std::wstring Result; std::string K3 = R"(Software\BeamNG\BeamNG.drive)"; HKEY hKey; LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey); @@ -229,11 +231,12 @@ void LegitimacyCheck() { } #endif } -std::string CheckVer(const std::string& dir) { +std::string CheckVer(const std::wstring& dir) { + std::string temp; #if defined(_WIN32) - std::string temp, Path = dir + "\\integrity.json"; + std::wstring Path = dir + L"\\integrity.json"; #elif defined(__linux__) - std::string temp, Path = dir + "/integrity.json"; + std::wstring Path = dir + L"/integrity.json"; #endif std::ifstream f(Path.c_str(), std::ios::binary); int Size = int(std::filesystem::file_size(Path)); diff --git a/src/Startup.cpp b/src/Startup.cpp index bb6536a..7ed8377 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -19,13 +19,14 @@ #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 #include #include -#include "Options.h" extern int TraceBack; int ProxyPort = 0; @@ -72,11 +73,11 @@ Version::Version(const std::array& v) : Version(v[0], v[1], v[2]) { } -std::string GetEN() { +std::wstring GetEN() { #if defined(_WIN32) - return "BeamMP-Launcher.exe"; + return L"BeamMP-Launcher.exe"; #elif defined(__linux__) - return "BeamMP-Launcher"; + return L"BeamMP-Launcher"; #endif } @@ -87,34 +88,34 @@ std::string GetPatch() { return ".1"; } -std::string GetEP(const char* P) { - static std::string Ret = [&]() { - std::string path(P); - return path.substr(0, path.find_last_of("\\/") + 1); +std::wstring GetEP(const wchar_t* P) { + static std::wstring Ret = [&]() { + std::wstring path(P); + return path.substr(0, path.find_last_of(L"\\/") + 1); }(); return Ret; } #if defined(_WIN32) void ReLaunch() { - std::string Arg; + std::wstring Arg; for (int c = 2; c <= options.argc; c++) { - Arg += options.argv[c - 1]; - Arg += " "; + Arg += Utils::ToWString(options.argv[c - 1]); + Arg += L" "; } info("Relaunch!"); system("cls"); - ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL); + ShellExecuteW(nullptr, L"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::string Arg; + std::wstring Arg; for (int c = 2; c <= options.argc; c++) { - Arg += options.argv[c - 1]; - Arg += " "; + Arg += Utils::ToWString(options.argv[c - 1]); + Arg += L" "; } - ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL); + ShellExecuteW(nullptr, L"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); @@ -149,16 +150,16 @@ void URelaunch() { void CheckName() { #if defined(_WIN32) - std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('\\') + 1); + std::wstring DN = GetEN(), CDir = Utils::ToWString(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); + std::wstring DN = GetEN(), CDir = Utils::ToWString(options.executable_name), FN = CDir.substr(CDir.find_last_of('/') + 1); #endif if (FN != DN) { if (fs::exists(DN)) - remove(DN.c_str()); + _wremove(DN.c_str()); if (fs::exists(DN)) ReLaunch(); - std::rename(FN.c_str(), DN.c_str()); + _wrename(FN.c_str(), DN.c_str()); URelaunch(); } } @@ -169,9 +170,9 @@ void CheckForUpdates(const std::string& CV) { "https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey); transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower); - std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back"); + std::wstring EP(GetEP() + GetEN()), Back(GetEP() + L"BeamMP-Launcher.back"); - std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP); + std::string FileHash = Utils::GetSha256HashReallyFast(EP); if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) { if (!options.no_update) { @@ -251,7 +252,7 @@ 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 std::string& Path) { +void CheckMP(const std::wstring& Path) { if (!fs::exists(Path)) return; size_t c = DirCount(fs::path(Path)); @@ -271,7 +272,7 @@ void CheckMP(const std::string& Path) { } void EnableMP() { - std::string File(GetGamePath() + "mods/db.json"); + std::wstring File(GetGamePath() + L"mods/db.json"); if (!fs::exists(File)) return; auto Size = fs::file_size(File); @@ -294,18 +295,18 @@ void EnableMP() { ofs << d.dump(); ofs.close(); } else { - error("Failed to write " + File); + error(L"Failed to write " + File); } } } } -void PreGame(const std::string& GamePath) { +void PreGame(const std::wstring& GamePath) { std::string GameVer = CheckVer(GamePath); info("Game Version : " + GameVer); - CheckMP(GetGamePath() + "mods/multiplayer"); - info("Game user path: " + GetGamePath()); + CheckMP(GetGamePath() + L"mods/multiplayer"); + info(L"Game user path: " + GetGamePath()); if (!options.no_download) { std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey); @@ -315,21 +316,21 @@ void PreGame(const std::string& GamePath) { LatestHash.end()); try { - if (!fs::exists(GetGamePath() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { + fs::create_directories(GetGamePath() + L"mods/multiplayer"); } EnableMP(); } catch (std::exception& e) { fatal(e.what()); } #if defined(_WIN32) - std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)"); + std::wstring ZipPath(GetGamePath() + LR"(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::wstring ZipPath(GetGamePath() + LR"(mods/multiplayer/beammp.zip)"); #endif - std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath); + std::string FileHash = Utils::GetSha256HashReallyFast(ZipPath); if (FileHash != LatestHash) { info("Downloading BeamMP Update " + LatestHash); @@ -339,7 +340,7 @@ void PreGame(const std::string& GamePath) { ZipPath); } - std::string Target(GetGamePath() + "mods/unpacked/beammp"); + std::wstring Target(GetGamePath() + L"mods/unpacked/beammp"); if (fs::is_directory(Target) && !fs::is_directory(Target + "/.git")) { fs::remove_all(Target); diff --git a/src/main.cpp b/src/main.cpp index ddfc4a5..b2f9099 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "Network/network.hpp" #include "Security/Init.h" #include "Startup.h" +#include "Utils.h" #include #include #include @@ -37,7 +38,7 @@ int main(int argc, const char** argv) try { curl_global_init(CURL_GLOBAL_ALL); - GetEP(argv[0]); + GetEP(Utils::ToWString(std::string(argv[0])).c_str()); InitLog(); ConfigInit(); From 06c741edc57936ff3d6955006f739116d744d8c8 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Sun, 26 Jan 2025 23:08:31 +0100 Subject: [PATCH 02/14] Fix wstring for windows and add crossplatform code for fs strings --- include/Http.h | 4 +- include/Security/Init.h | 8 ++-- include/Startup.h | 8 ++-- include/Utils.h | 79 +++++++++------------------------------ src/GameStart.cpp | 2 +- src/Logger.cpp | 10 +++-- src/Network/Http.cpp | 4 +- src/Network/Resources.cpp | 34 ++++++++--------- src/Security/BeamNG.cpp | 14 +++---- src/Startup.cpp | 44 ++++++++++------------ 10 files changed, 81 insertions(+), 126 deletions(-) diff --git a/include/Http.h b/include/Http.h index cd9bd83..f491dc4 100644 --- a/include/Http.h +++ b/include/Http.h @@ -6,10 +6,12 @@ #pragma once #include "Logger.h" +#include "Utils.h" + #include class HTTP { public: - static bool Download(const std::string& IP, const std::wstring& Path); + static bool Download(const std::string& IP, const beammp_fs_string& Path); static std::string Post(const std::string& IP, const std::string& Fields); static std::string Get(const std::string& IP); static bool ProgressBar(size_t c, size_t t); diff --git a/include/Security/Init.h b/include/Security/Init.h index 4667bad..242c929 100644 --- a/include/Security/Init.h +++ b/include/Security/Init.h @@ -6,9 +6,9 @@ #pragma once #include -void PreGame(const std::wstring& GamePath); -std::string CheckVer(const std::wstring& path); -void InitGame(const std::wstring& Dir); -std::wstring GetGameDir(); +void PreGame(const beammp_fs_string& GamePath); +std::string CheckVer(const beammp_fs_string& path); +void InitGame(const beammp_fs_string& Dir); +beammp_fs_string GetGameDir(); void LegitimacyCheck(); void CheckLocalKey(); \ No newline at end of file diff --git a/include/Startup.h b/include/Startup.h index daebb13..65084d0 100644 --- a/include/Startup.h +++ b/include/Startup.h @@ -5,14 +5,16 @@ */ #pragma once +#include "Utils.h" + #include #include #include void InitLauncher(); -std::wstring GetEP(const wchar_t* P = nullptr); -std::wstring GetGamePath(); +beammp_fs_string GetEP(const beammp_fs_char* P = nullptr); +beammp_fs_string GetGamePath(); std::string GetVer(); std::string GetPatch(); -std::wstring GetEN(); +beammp_fs_string GetEN(); void ConfigInit(); diff --git a/include/Utils.h b/include/Utils.h index c4ff384..28c601b 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -15,6 +15,16 @@ #include #include +#ifdef _WIN32 +#define beammp_fs_string std::wstring +#define beammp_fs_char wchar_t +#define beammp_wide(str) L##str +#else +#define beammp_fs_string std::string +#define beammp_fs_char char +#define beammp_wide(str) str +#endif + namespace Utils { inline std::vector Split(const std::string& String, const std::string& delimiter) { @@ -120,71 +130,16 @@ namespace Utils { inline std::string ToString(const std::wstring& w) { return std::wstring_convert>().to_bytes(w); } +#ifdef _WIN32 inline std::wstring ToWString(const std::string& s) { return std::wstring_convert>().from_bytes(s); } - inline 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 Data; - while (Read < FileSize) { - Data.resize(size_t(std::min(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 ""; - } +#else + inline std::string ToWString(const std::string& s) { + return s; } - inline std::string GetSha256HashReallyFast(const std::wstring& filename) { +#endif + inline std::string GetSha256HashReallyFast(const beammp_fs_string& filename) { try { EVP_MD_CTX* mdctx; const EVP_MD* md; @@ -241,7 +196,7 @@ namespace Utils { } return result; } catch (const std::exception& e) { - error(L"Sha256 hashing of '" + filename + L"' failed: " + ToWString(e.what())); + error(beammp_wide("Sha256 hashing of '") + filename + beammp_wide("' failed: ") + ToWString(e.what())); return ""; } } diff --git a/src/GameStart.cpp b/src/GameStart.cpp index d75fac0..70ed9a2 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -187,7 +187,7 @@ void StartGame(std::string Dir) { } #endif -void InitGame(const std::wstring& Dir) { +void InitGame(const beammp_fs_string& Dir) { if (!options.no_launch) { std::thread Game(StartGame, Dir); Game.detach(); diff --git a/src/Logger.cpp b/src/Logger.cpp index f13056e..33215fb 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -37,7 +37,7 @@ std::string getDate() { } void InitLog() { std::ofstream LFS; - LFS.open(GetEP() + L"Launcher.log"); + LFS.open(GetEP() + beammp_wide("Launcher.log")); if (!LFS.is_open()) { error("logger file init failed!"); } else @@ -45,13 +45,13 @@ void InitLog() { } void addToLog(const std::string& Line) { std::ofstream LFS; - LFS.open(GetEP() + L"Launcher.log", std::ios_base::app); + 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() + L"Launcher.log", std::ios_base::app); + LFS.open(GetEP() + beammp_wide("Launcher.log"), std::ios_base::app); LFS << Line.c_str(); LFS.close(); } @@ -91,6 +91,7 @@ void except(const std::string& toPrint) { } +#ifdef _WIN32 void info(const std::wstring& toPrint) { std::wstring Print = Utils::ToWString(getDate()) + L"[INFO] " + toPrint + L"\n"; std::wcout << Print; @@ -124,4 +125,5 @@ void except(const std::wstring& toPrint) { std::wstring Print = Utils::ToWString(getDate()) + L"[EXCEP] " + toPrint + L"\n"; std::wcout << Print; addToLog(Print); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/Network/Http.cpp b/src/Network/Http.cpp index 649c6a9..92d8500 100644 --- a/src/Network/Http.cpp +++ b/src/Network/Http.cpp @@ -127,7 +127,7 @@ std::string HTTP::Post(const std::string& IP, const std::string& Fields) { return Ret; } -bool HTTP::Download(const std::string& IP, const std::wstring& Path) { +bool HTTP::Download(const std::string& IP, const beammp_fs_string& Path) { static std::mutex Lock; std::scoped_lock Guard(Lock); @@ -145,7 +145,7 @@ bool HTTP::Download(const std::string& IP, const std::wstring& Path) { File.close(); info("Download Complete!"); } else { - error(L"Failed to open file directory: " + Path); + error(beammp_wide("Failed to open file directory: ") + Path); return false; } diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index 22da317..68e917d 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -444,14 +444,14 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string(); - auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string(); + auto PathToSaveTo = (fs::path(CachingDirectory) / FileName); if (fs::exists(PathToSaveTo) && Utils::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() + L"mods/multiplayer")) { - fs::create_directories(GetGamePath() + L"mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } auto modname = ModInfoIter->FileName; #if defined(__linux__) @@ -476,15 +476,15 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName).filename(); - fs::exists(OldCachedPath) && GetSha256HashReallyFast(OldCachedPath.string()) == ModInfoIter->Hash) { + fs::exists(OldCachedPath) && Utils::GetSha256HashReallyFast(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() + "mods/multiplayer")) { - fs::create_directories(GetGamePath() + "mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } auto modname = ModInfoIter->FileName; @@ -525,7 +525,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName; do { - debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'"); + debug("Loading file '" + FName + "' to '" + PathToSaveTo.string() + "'"); TCPSend("f" + ModInfoIter->FileName, Sock); std::string Data = TCPRcv(Sock); @@ -558,7 +558,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileSize && !Terminate); if (!Terminate) { - if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { - fs::create_directories(GetGamePath() + L"mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } // Linux version of the game doesnt support uppercase letters in mod names @@ -667,8 +667,8 @@ void SyncResources(SOCKET Sock) { 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() + L"mods/multiplayer")) { - fs::create_directories(GetGamePath() + L"mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); #if defined(__linux__) @@ -677,8 +677,8 @@ void SyncResources(SOCKET Sock) { c = ::tolower(c); } #endif - auto name = GetGamePath() + L"mods/multiplayer" + Utils::ToWString(modname); - auto tmp_name = name + L".tmp"; + auto name = GetGamePath() + beammp_wide("mods/multiplayer") + Utils::ToWString(modname); + auto tmp_name = name + beammp_wide(".tmp"); fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing); fs::rename(tmp_name, name); UpdateModUsage(modname); @@ -725,8 +725,8 @@ void SyncResources(SOCKET Sock) { } } while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate); if (!Terminate) { - if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { - fs::create_directories(GetGamePath() + L"mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } // Linux version of the game doesnt support uppercase letters in mod names @@ -736,7 +736,7 @@ void SyncResources(SOCKET Sock) { } #endif - fs::copy_file(PathToSaveTo, GetGamePath() + L"mods/multiplayer" + Utils::ToWString(FName), fs::copy_options::overwrite_existing); + fs::copy_file(PathToSaveTo, GetGamePath() + beammp_wide("mods/multiplayer") + Utils::ToWString(FName), fs::copy_options::overwrite_existing); UpdateModUsage(FN->substr(pos)); } WaitForConfirm(); diff --git a/src/Security/BeamNG.cpp b/src/Security/BeamNG.cpp index 2704d56..36ff5d6 100644 --- a/src/Security/BeamNG.cpp +++ b/src/Security/BeamNG.cpp @@ -15,6 +15,8 @@ #include #endif #include "Logger.h" +#include "Utils.h" + #include #include #include @@ -23,7 +25,7 @@ #define MAX_VALUE_NAME 16383 int TraceBack = 0; -std::wstring GameDir; +beammp_fs_string GameDir; void lowExit(int code) { TraceBack = 0; @@ -33,7 +35,7 @@ void lowExit(int code) { exit(2); } -std::wstring GetGameDir() { +beammp_fs_string GetGameDir() { #if defined(_WIN32) return GameDir.substr(0, GameDir.find_last_of('\\')); #elif defined(__linux__) @@ -231,13 +233,9 @@ void LegitimacyCheck() { } #endif } -std::string CheckVer(const std::wstring& dir) { +std::string CheckVer(const beammp_fs_string& dir) { std::string temp; -#if defined(_WIN32) - std::wstring Path = dir + L"\\integrity.json"; -#elif defined(__linux__) - std::wstring Path = dir + L"/integrity.json"; -#endif + beammp_fs_string Path = dir + beammp_wide("\\integrity.json"); std::ifstream f(Path.c_str(), std::ios::binary); int Size = int(std::filesystem::file_size(Path)); std::string vec(Size, 0); diff --git a/src/Startup.cpp b/src/Startup.cpp index 7ed8377..cd87590 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -73,12 +73,8 @@ Version::Version(const std::array& v) : Version(v[0], v[1], v[2]) { } -std::wstring GetEN() { -#if defined(_WIN32) - return L"BeamMP-Launcher.exe"; -#elif defined(__linux__) - return L"BeamMP-Launcher"; -#endif +beammp_fs_string GetEN() { + return beammp_wide("BeamMP-Launcher.exe"); } std::string GetVer() { @@ -88,10 +84,10 @@ std::string GetPatch() { return ".1"; } -std::wstring GetEP(const wchar_t* P) { - static std::wstring Ret = [&]() { - std::wstring path(P); - return path.substr(0, path.find_last_of(L"\\/") + 1); +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); }(); return Ret; } @@ -152,14 +148,14 @@ void CheckName() { #if defined(_WIN32) std::wstring DN = GetEN(), CDir = Utils::ToWString(options.executable_name), FN = CDir.substr(CDir.find_last_of('\\') + 1); #elif defined(__linux__) - 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); #endif if (FN != DN) { if (fs::exists(DN)) - _wremove(DN.c_str()); + fs::remove(DN.c_str()); if (fs::exists(DN)) ReLaunch(); - _wrename(FN.c_str(), DN.c_str()); + fs::rename(FN.c_str(), DN.c_str()); URelaunch(); } } @@ -170,7 +166,7 @@ void CheckForUpdates(const std::string& CV) { "https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey); transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower); - std::wstring EP(GetEP() + GetEN()), Back(GetEP() + L"BeamMP-Launcher.back"); + beammp_fs_string EP(GetEP() + GetEN()), Back(GetEP() + beammp_wide("BeamMP-Launcher.back")); std::string FileHash = Utils::GetSha256HashReallyFast(EP); @@ -252,7 +248,7 @@ 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 std::wstring& Path) { +void CheckMP(const beammp_fs_string& Path) { if (!fs::exists(Path)) return; size_t c = DirCount(fs::path(Path)); @@ -272,7 +268,7 @@ void CheckMP(const std::wstring& Path) { } void EnableMP() { - std::wstring File(GetGamePath() + L"mods/db.json"); + beammp_fs_string File(GetGamePath() + beammp_wide("mods/db.json")); if (!fs::exists(File)) return; auto Size = fs::file_size(File); @@ -295,18 +291,18 @@ void EnableMP() { ofs << d.dump(); ofs.close(); } else { - error(L"Failed to write " + File); + error(beammp_wide("Failed to write ") + File); } } } } -void PreGame(const std::wstring& GamePath) { +void PreGame(const beammp_fs_string& GamePath) { std::string GameVer = CheckVer(GamePath); info("Game Version : " + GameVer); - CheckMP(GetGamePath() + L"mods/multiplayer"); - info(L"Game user path: " + GetGamePath()); + CheckMP(GetGamePath() + beammp_wide("mods/multiplayer")); + info(beammp_wide("Game user path: ") + GetGamePath()); if (!options.no_download) { std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey); @@ -316,8 +312,8 @@ void PreGame(const std::wstring& GamePath) { LatestHash.end()); try { - if (!fs::exists(GetGamePath() + L"mods/multiplayer")) { - fs::create_directories(GetGamePath() + L"mods/multiplayer"); + if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); } EnableMP(); } catch (std::exception& e) { @@ -327,7 +323,7 @@ void PreGame(const std::wstring& GamePath) { std::wstring ZipPath(GetGamePath() + LR"(mods\multiplayer\BeamMP.zip)"); #elif defined(__linux__) // Linux version of the game cant handle mods with uppercase names - std::wstring ZipPath(GetGamePath() + LR"(mods/multiplayer/beammp.zip)"); + std::string ZipPath(GetGamePath() + R"(mods/multiplayer/beammp.zip)"); #endif std::string FileHash = Utils::GetSha256HashReallyFast(ZipPath); @@ -340,7 +336,7 @@ void PreGame(const std::wstring& GamePath) { ZipPath); } - std::wstring Target(GetGamePath() + L"mods/unpacked/beammp"); + beammp_fs_string Target(GetGamePath() + beammp_wide("mods/unpacked/beammp")); if (fs::is_directory(Target) && !fs::is_directory(Target + "/.git")) { fs::remove_all(Target); From 6c740e2562f3ad7b9ae93c0851f51f33f3753717 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:33:32 +0200 Subject: [PATCH 03/14] Fix merge --- include/Utils.h | 52 +++++++++++++++++++++++++++++++++++---- src/GameStart.cpp | 17 ++++++------- src/Network/Core.cpp | 1 + src/Network/Resources.cpp | 6 ++--- src/Security/BeamNG.cpp | 1 - src/Startup.cpp | 1 - 6 files changed, 59 insertions(+), 19 deletions(-) diff --git a/include/Utils.h b/include/Utils.h index 28c601b..f8d7d8c 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -19,6 +19,8 @@ #define beammp_fs_string std::wstring #define beammp_fs_char wchar_t #define beammp_wide(str) L##str +#define WIN32_LEAN_AND_MEAN +#include #else #define beammp_fs_string std::string #define beammp_fs_char char @@ -71,6 +73,38 @@ namespace Utils { 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; + 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())) { + result.append(envValue); + } + + lastPos = match.position() + match.length(); + } + + result.append(input, lastPos, input.length() - lastPos); + + return result; + } +#endif inline std::map> ParseINI(const std::string& contents) { std::map> ini; @@ -127,12 +161,20 @@ namespace Utils { return ini; } - inline std::string ToString(const std::wstring& w) { - return std::wstring_convert>().to_bytes(w); - } #ifdef _WIN32 - inline std::wstring ToWString(const std::string& s) { - return std::wstring_convert>().from_bytes(s); +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) { diff --git a/src/GameStart.cpp b/src/GameStart.cpp index 70ed9a2..1f39548 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -6,7 +6,6 @@ #if defined(_WIN32) #include -#include #elif defined(__linux__) #include "vdf_parser.hpp" #include @@ -32,14 +31,14 @@ std::wstring QueryKey(HKEY hKey, int ID); std::wstring GetGamePath() { static std::filesystem::path Path; if (!Path.empty()) - return Path.string(); + return Path.wstring(); if (options.user_path) { if (std::filesystem::exists(options.user_path)) { Path = options.user_path; - debug("Using custom user folder path: " + Path.string()); + debug(L"Using custom user folder path: " + Path.wstring()); } else - warn("Invalid or non-existent path (" + std::string(options.user_path) + ") specified using --user-path, skipping"); + warn(L"Invalid or non-existent path (" + Utils::ToWString(options.user_path) + L") specified using --user-path, skipping"); } if (const auto startupIniPath = std::filesystem::path(GetGameDir()) / "startup.ini"; exists(startupIniPath)) { @@ -58,16 +57,16 @@ std::wstring GetGamePath() { } else debug("Successfully parsed startup.ini"); - std::string userPath; + std::wstring userPath; if (ini.contains("filesystem") && ini["filesystem"].contains("UserPath")) - userPath = ini["filesystem"]["UserPath"]; + userPath = Utils::ToWString(ini["filesystem"]["UserPath"]); if (!userPath.empty()) if (userPath = Utils::ExpandEnvVars(userPath); std::filesystem::exists(userPath)) { Path = userPath; - debug("Using custom user folder path from startup.ini: " + Path.string()); + debug(L"Using custom user folder path from startup.ini: " + Path.wstring()); } else - warn("Found custom user folder path (" + userPath + ") in startup.ini but it doesn't exist, skipping"); + warn(L"Found custom user folder path (" + userPath + L") in startup.ini but it doesn't exist, skipping"); } if (Path.empty()) { @@ -129,7 +128,7 @@ void StartGame(std::wstring Dir) { gameArgs += Utils::ToWString(options.game_arguments[i]); } - debug("BeamNG executable path: " + Dir); + 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); if (bSuccess) { diff --git a/src/Network/Core.cpp b/src/Network/Core.cpp index 9121f2a..902e1c7 100644 --- a/src/Network/Core.cpp +++ b/src/Network/Core.cpp @@ -12,6 +12,7 @@ #if defined(_WIN32) #include #include +#include #elif defined(__linux__) #include #include diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index 68e917d..8f580a8 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -525,7 +525,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName; do { - debug("Loading file '" + FName + "' to '" + PathToSaveTo.string() + "'"); + debug(L"Loading file '" + Utils::ToWString(FName) + L"' to '" + PathToSaveTo.wstring() + L"'"); TCPSend("f" + ModInfoIter->FileName, Sock); std::string Data = TCPRcv(Sock); @@ -562,8 +562,8 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorHash) { - error("Failed to write or download the entire file '" + PathToSaveTo + "' correctly (hash mismatch)"); + if (Utils::GetSha256HashReallyFast(PathToSaveTo) != ModInfoIter->Hash) { + error(L"Failed to write or download the entire file '" + PathToSaveTo.wstring() + L"' correctly (hash mismatch)"); Terminate = true; } } while (fs::file_size(PathToSaveTo) != ModInfoIter->FileSize && !Terminate); diff --git a/src/Security/BeamNG.cpp b/src/Security/BeamNG.cpp index 36ff5d6..9b6cbe3 100644 --- a/src/Security/BeamNG.cpp +++ b/src/Security/BeamNG.cpp @@ -7,7 +7,6 @@ #include #if defined(_WIN32) -#include #elif defined(__linux__) #include "vdf_parser.hpp" #include diff --git a/src/Startup.cpp b/src/Startup.cpp index cd87590..a6b0590 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -12,7 +12,6 @@ #include #include #if defined(_WIN32) -#include #elif defined(__linux__) #include #endif From 8b96ffb0982eb7ea9afd4f3be2ec19898c5b9869 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:45:09 +0200 Subject: [PATCH 04/14] Fix .git folder check and GetGamePath() --- src/GameStart.cpp | 2 +- src/Startup.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GameStart.cpp b/src/GameStart.cpp index 1f39548..ddcc796 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -95,7 +95,7 @@ std::wstring GetGamePath() { std::string Ver = CheckVer(GetGameDir()); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); - Path += Utils::ToWString(Ver) + L"\\"; + (Path /= Utils::ToWString(Ver)) /= L"\\"; return Path; } #elif defined(__linux__) diff --git a/src/Startup.cpp b/src/Startup.cpp index a6b0590..86f59a6 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -337,7 +337,7 @@ void PreGame(const beammp_fs_string& GamePath) { beammp_fs_string Target(GetGamePath() + beammp_wide("mods/unpacked/beammp")); - if (fs::is_directory(Target) && !fs::is_directory(Target + "/.git")) { + if (fs::is_directory(Target) && !fs::is_directory(Target + beammp_wide("/.git"))) { fs::remove_all(Target); } } From e0257e95262d8a3f7d96d643fbae045f586285cc Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:51:16 +0200 Subject: [PATCH 05/14] Update NewSyncResources logs to use wstring --- src/Network/Resources.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index 8f580a8..bee2da6 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -525,7 +525,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName; do { - debug(L"Loading file '" + Utils::ToWString(FName) + L"' to '" + PathToSaveTo.wstring() + L"'"); + debug(beammp_wide("Loading file '") + Utils::ToWString(FName) + beammp_wide("' to '") + beammp_fs_string(PathToSaveTo) + beammp_wide("'")); TCPSend("f" + ModInfoIter->FileName, Sock); std::string Data = TCPRcv(Sock); @@ -558,12 +558,12 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorHash) { - error(L"Failed to write or download the entire file '" + PathToSaveTo.wstring() + L"' correctly (hash mismatch)"); + error(beammp_wide("Failed to write or download the entire file '") + beammp_fs_string(PathToSaveTo) + beammp_wide("' correctly (hash mismatch)")); Terminate = true; } } while (fs::file_size(PathToSaveTo) != ModInfoIter->FileSize && !Terminate); From 9c53f8659385f068170b9e36dcc52c1b4371aa3a Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:01:23 +0200 Subject: [PATCH 06/14] Convert GetGamePath() to fs::path --- include/Startup.h | 2 +- src/GameStart.cpp | 6 +++--- src/Network/Resources.cpp | 26 +++++++++++++------------- src/Startup.cpp | 16 ++++++++-------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/include/Startup.h b/include/Startup.h index 65084d0..55809b8 100644 --- a/include/Startup.h +++ b/include/Startup.h @@ -13,7 +13,7 @@ void InitLauncher(); beammp_fs_string GetEP(const beammp_fs_char* P = nullptr); -beammp_fs_string GetGamePath(); +std::filesystem::path GetGamePath(); std::string GetVer(); std::string GetPatch(); beammp_fs_string GetEN(); diff --git a/src/GameStart.cpp b/src/GameStart.cpp index ddcc796..2230443 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -28,7 +28,7 @@ unsigned long GamePID = 0; #if defined(_WIN32) std::wstring QueryKey(HKEY hKey, int ID); -std::wstring GetGamePath() { +std::filesystem::path GetGamePath() { static std::filesystem::path Path; if (!Path.empty()) return Path.wstring(); @@ -95,11 +95,11 @@ std::wstring GetGamePath() { std::string Ver = CheckVer(GetGameDir()); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); - (Path /= Utils::ToWString(Ver)) /= L"\\"; + Path /= Utils::ToWString(Ver); return Path; } #elif defined(__linux__) -std::string GetGamePath() { +std::filesystem::path GetGamePath() { // Right now only steam is supported struct passwd* pw = getpwuid(getuid()); std::string homeDir = pw->pw_dir; diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index bee2da6..a8e1fb5 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -450,8 +450,8 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName); 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() / beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer")); } auto modname = ModInfoIter->FileName; #if defined(__linux__) @@ -483,8 +483,8 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName; @@ -568,8 +568,8 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileSize && !Terminate); if (!Terminate) { - if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { - fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); + if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer")); } // Linux version of the game doesnt support uppercase letters in mod names @@ -667,8 +667,8 @@ void SyncResources(SOCKET Sock) { 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() / beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer")); } auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); #if defined(__linux__) @@ -677,8 +677,8 @@ void SyncResources(SOCKET Sock) { c = ::tolower(c); } #endif - auto name = GetGamePath() + beammp_wide("mods/multiplayer") + Utils::ToWString(modname); - auto tmp_name = name + beammp_wide(".tmp"); + auto name = GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(modname); + auto tmp_name = name / beammp_wide(".tmp"); fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing); fs::rename(tmp_name, name); UpdateModUsage(modname); @@ -725,8 +725,8 @@ void SyncResources(SOCKET Sock) { } } 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() / beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer")); } // Linux version of the game doesnt support uppercase letters in mod names @@ -736,7 +736,7 @@ void SyncResources(SOCKET Sock) { } #endif - fs::copy_file(PathToSaveTo, GetGamePath() + beammp_wide("mods/multiplayer") + Utils::ToWString(FName), fs::copy_options::overwrite_existing); + fs::copy_file(PathToSaveTo, GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(FName), fs::copy_options::overwrite_existing); UpdateModUsage(FN->substr(pos)); } WaitForConfirm(); diff --git a/src/Startup.cpp b/src/Startup.cpp index 86f59a6..f6f95c5 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -267,7 +267,7 @@ void CheckMP(const beammp_fs_string& Path) { } void EnableMP() { - beammp_fs_string File(GetGamePath() + beammp_wide("mods/db.json")); + beammp_fs_string File(GetGamePath() / beammp_wide("mods/db.json")); if (!fs::exists(File)) return; auto Size = fs::file_size(File); @@ -300,8 +300,8 @@ void PreGame(const beammp_fs_string& GamePath) { std::string GameVer = CheckVer(GamePath); info("Game Version : " + GameVer); - CheckMP(GetGamePath() + beammp_wide("mods/multiplayer")); - info(beammp_wide("Game user path: ") + GetGamePath()); + CheckMP(GetGamePath() / beammp_wide("mods/multiplayer")); + info(beammp_wide("Game user path: ") + beammp_fs_string(GetGamePath())); if (!options.no_download) { std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey); @@ -311,18 +311,18 @@ void PreGame(const beammp_fs_string& GamePath) { LatestHash.end()); try { - if (!fs::exists(GetGamePath() + beammp_wide("mods/multiplayer"))) { - fs::create_directories(GetGamePath() + beammp_wide("mods/multiplayer")); + if (!fs::exists(GetGamePath() / beammp_wide("mods/multiplayer"))) { + fs::create_directories(GetGamePath() / beammp_wide("mods/multiplayer")); } EnableMP(); } catch (std::exception& e) { fatal(e.what()); } #if defined(_WIN32) - std::wstring ZipPath(GetGamePath() + LR"(mods\multiplayer\BeamMP.zip)"); + std::wstring ZipPath(GetGamePath() / LR"(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 = Utils::GetSha256HashReallyFast(ZipPath); @@ -335,7 +335,7 @@ void PreGame(const beammp_fs_string& GamePath) { ZipPath); } - beammp_fs_string Target(GetGamePath() + beammp_wide("mods/unpacked/beammp")); + beammp_fs_string Target(GetGamePath() / beammp_wide("mods/unpacked/beammp")); if (fs::is_directory(Target) && !fs::is_directory(Target + beammp_wide("/.git"))) { fs::remove_all(Target); From 51d096deacc2e59a9cb0152dadbd4d00ba101f99 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:01:48 +0200 Subject: [PATCH 07/14] Check if BeamMP.zip exists before hashing --- src/Startup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Startup.cpp b/src/Startup.cpp index f6f95c5..d74763d 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -325,7 +325,7 @@ void PreGame(const beammp_fs_string& GamePath) { std::string ZipPath(GetGamePath() / R"(mods/multiplayer/beammp.zip)"); #endif - std::string FileHash = Utils::GetSha256HashReallyFast(ZipPath); + std::string FileHash = fs::exists(ZipPath) ? Utils::GetSha256HashReallyFast(ZipPath) : ""; if (FileHash != LatestHash) { info("Downloading BeamMP Update " + LatestHash); From 8025c0884fab28b9a61ffe46957bdee5bc76fedd Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:56:59 +0200 Subject: [PATCH 08/14] Fix download path generation --- include/Network/network.hpp | 3 ++- include/Utils.h | 4 ++-- src/Config.cpp | 6 +++--- src/GameStart.cpp | 4 ---- src/Network/Resources.cpp | 30 ++++++++++++++++-------------- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/include/Network/network.hpp b/include/Network/network.hpp index f0d3d4a..ca91711 100644 --- a/include/Network/network.hpp +++ b/include/Network/network.hpp @@ -6,6 +6,7 @@ #pragma once +#include #include #ifdef __linux__ @@ -29,7 +30,7 @@ extern bool Terminate; extern uint64_t UDPSock; extern uint64_t TCPSock; extern std::string Branch; -extern std::string CachingDirectory; +extern std::filesystem::path CachingDirectory; extern bool deleteDuplicateMods; extern bool TCPTerminate; extern std::string LastIP; diff --git a/include/Utils.h b/include/Utils.h index f8d7d8c..50cac74 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -200,11 +200,11 @@ inline std::wstring ToWString(const std::string& s) { throw std::runtime_error("EVP_DigestInit_ex2() failed"); } - std::wifstream stream(filename, std::ios::binary); + std::ifstream stream(filename, std::ios::binary); const size_t FileSize = std::filesystem::file_size(filename); size_t Read = 0; - std::vector Data; + std::vector Data; while (Read < FileSize) { Data.resize(size_t(std::min(FileSize - Read, 4096))); size_t RealDataSize = Data.size(); diff --git a/src/Config.cpp b/src/Config.cpp index 9e9d3d9..2798049 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -14,7 +14,7 @@ namespace fs = std::filesystem; std::string Branch; -std::string CachingDirectory = "./Resources"; +std::filesystem::path CachingDirectory = std::filesystem::path("./Resources"); bool deleteDuplicateMods = false; void ParseConfig(const nlohmann::json& d) { @@ -32,8 +32,8 @@ void ParseConfig(const nlohmann::json& d) { c = char(tolower(c)); } if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) { - CachingDirectory = d["CachingDirectory"].get(); - info("Mod caching directory: " + CachingDirectory); + CachingDirectory = std::filesystem::path(d["CachingDirectory"].get()); + info(L"Mod caching directory: " + CachingDirectory.relative_path().wstring()); } if (d.contains("Dev") && d["Dev"].is_boolean()) { diff --git a/src/GameStart.cpp b/src/GameStart.cpp index 2230443..ea70d47 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -47,10 +47,6 @@ std::filesystem::path GetGamePath() { std::string contents((std::istreambuf_iterator(startupIni)), std::istreambuf_iterator()); startupIni.close(); - if (contents.size() > 3) { - contents.erase(0, 3); - } - auto ini = Utils::ParseINI(contents); if (ini.empty()) { warn("Failed to parse startup.ini"); diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index a8e1fb5..ea99d98 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -343,7 +343,7 @@ nlohmann::json modUsage = {}; void UpdateModUsage(const std::string& fileName) { try { - fs::path usageFile = fs::path(CachingDirectory) / "mods.json"; + fs::path usageFile = CachingDirectory / "mods.json"; if (!fs::exists(usageFile)) { if (std::ofstream file(usageFile); !file.is_open()) { @@ -361,7 +361,7 @@ void UpdateModUsage(const std::string& fileName) { } if (modUsage.empty()) { - auto Size = fs::file_size(fs::path(CachingDirectory) / "mods.json"); + auto Size = fs::file_size(CachingDirectory / "mods.json"); std::string modsJson(Size, 0); file.read(&modsJson[0], Size); @@ -444,7 +444,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string(); - auto PathToSaveTo = (fs::path(CachingDirectory) / FileName); + auto PathToSaveTo = (CachingDirectory / FileName); if (fs::exists(PathToSaveTo) && Utils::GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) { debug("Mod '" + FileName + "' found in cache"); UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + ModInfoIter->FileName); @@ -475,7 +475,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorFileName).filename(); + } else if (auto OldCachedPath = CachingDirectory / std::filesystem::path(ModInfoIter->FileName).filename(); fs::exists(OldCachedPath) && Utils::GetSha256HashReallyFast(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); @@ -514,7 +514,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vectorProtected) { - std::string message = "Mod '" + ModInfoIter->FileName + "' is protected and therefore must be placed in the Resources/Caching folder manually here: " + fs::absolute(CachingDirectory).string(); + 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); @@ -626,7 +626,8 @@ void SyncResources(SOCKET Sock) { Ret.clear(); int Amount = 0, Pos = 0; - std::string PathToSaveTo, t; + std::filesystem::path PathToSaveTo; + std::string t; for (const std::string& name : FNames) { if (!name.empty()) { t += name.substr(name.find_last_of('/') + 1) + ";"; @@ -654,7 +655,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 + FN->substr(pos); + PathToSaveTo = CachingDirectory / std::filesystem::path(*FN).filename(); } else { continue; } @@ -664,13 +665,13 @@ 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.substr(PathToSaveTo.find_last_of('/'))); + UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.filename().string()); 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")); } - auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); + auto modname = PathToSaveTo.filename().string(); #if defined(__linux__) // Linux version of the game doesnt support uppercase letters in mod names for (char& c : modname) { @@ -678,7 +679,8 @@ void SyncResources(SOCKET Sock) { } #endif auto name = GetGamePath() / beammp_wide("mods/multiplayer") / Utils::ToWString(modname); - auto tmp_name = name / beammp_wide(".tmp"); + auto tmp_name = name; + tmp_name += L".tmp"; fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing); fs::rename(tmp_name, name); UpdateModUsage(modname); @@ -690,12 +692,12 @@ void SyncResources(SOCKET Sock) { WaitForConfirm(); continue; } else - remove(PathToSaveTo.c_str()); + fs::remove(PathToSaveTo.wstring()); } CheckForDir(); - std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/')); + std::string FName = PathToSaveTo.filename().string(); do { - debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'"); + debug("Loading file '" + FName + "' to '" + PathToSaveTo.string() + "'"); TCPSend("f" + *FN, Sock); std::string Data = TCPRcv(Sock); @@ -720,7 +722,7 @@ void SyncResources(SOCKET Sock) { } // 2. verify size if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) { - error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)"); + error(beammp_wide("Failed to write the entire file '") + beammp_fs_string(PathToSaveTo) + beammp_wide("' correctly (file size mismatch)")); Terminate = true; } } while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate); From 5f1e7c64093a0c110b087f8cfc443b5088a4c994 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:07:59 +0200 Subject: [PATCH 09/14] Fix resources dir log message for linux --- src/Config.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Config.cpp b/src/Config.cpp index 2798049..282b50d 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -6,11 +6,12 @@ #include "Logger.h" #include "Network/network.hpp" +#include "Options.h" +#include "Utils.h" #include #include #include #include -#include "Options.h" namespace fs = std::filesystem; std::string Branch; @@ -33,7 +34,7 @@ void ParseConfig(const nlohmann::json& d) { } if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) { CachingDirectory = std::filesystem::path(d["CachingDirectory"].get()); - info(L"Mod caching directory: " + CachingDirectory.relative_path().wstring()); + info(beammp_wide("Mod caching directory: ") + beammp_fs_string(CachingDirectory.relative_path()); } if (d.contains("Dev") && d["Dev"].is_boolean()) { From 303fc55d94cc98cb4c1dc4cd3872b35f7c3b93dc Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:13:46 +0200 Subject: [PATCH 10/14] Fix syntax error --- src/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.cpp b/src/Config.cpp index 282b50d..d7e41fa 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -34,7 +34,7 @@ void ParseConfig(const nlohmann::json& d) { } if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) { CachingDirectory = std::filesystem::path(d["CachingDirectory"].get()); - info(beammp_wide("Mod caching directory: ") + beammp_fs_string(CachingDirectory.relative_path()); + info(beammp_wide("Mod caching directory: ") + beammp_fs_string(CachingDirectory.relative_path())); } if (d.contains("Dev") && d["Dev"].is_boolean()) { From a5c02217fa9e9dc16ea7aee9fc4f0403d2d68f6f Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Fri, 20 Jun 2025 23:04:07 +0200 Subject: [PATCH 11/14] Update ini parse check formatting Co-authored-by: SaltySnail <51403141+SaltySnail@users.noreply.github.com> --- src/GameStart.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GameStart.cpp b/src/GameStart.cpp index ea70d47..ea33395 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -48,9 +48,9 @@ std::filesystem::path GetGamePath() { startupIni.close(); auto ini = Utils::ParseINI(contents); - if (ini.empty()) { + if (ini.empty()) warn("Failed to parse startup.ini"); - } else + else debug("Successfully parsed startup.ini"); std::wstring userPath; From 9d441462240b764a358c102ab017036d87a9192f Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:16:17 +0200 Subject: [PATCH 12/14] Regex assert Co-authored-by: SaltySnail <51403141+SaltySnail@users.noreply.github.com> --- include/Utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Utils.h b/include/Utils.h index 50cac74..a61c1be 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -89,6 +89,7 @@ namespace Utils { 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} From c89afdf47747d6364479a58b35f09a5869389bf5 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:17:14 +0200 Subject: [PATCH 13/14] Check Co-authored-by: SaltySnail <51403141+SaltySnail@users.noreply.github.com> --- include/Utils.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/Utils.h b/include/Utils.h index a61c1be..8f66350 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -95,7 +95,9 @@ namespace Utils { else if (match[3].matched) varName = match[3].str(); // ${VAR} if (const wchar_t* envValue = _wgetenv(varName.c_str())) { - result.append(envValue); + if (envValue != nullptr) { + result.append(envValue); + } } lastPos = match.position() + match.length(); From 1780133569985e0579dac3de45ebfe62e890deda Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:29:29 +0200 Subject: [PATCH 14/14] Include assert --- include/Utils.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Utils.h b/include/Utils.h index 8f66350..1606523 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -5,13 +5,14 @@ */ #pragma once -#include -#include +#include #include #include #include +#include #include #include +#include #include #include