Compare commits

..

10 Commits

Author SHA1 Message Date
Tixx
5b2eb0a1a0
Change to execution paths to binary paths (#200)
Previously, it used argv[0] for the execution path, which is fine in
most cases, but in my case, it is not.
The thing is, I use NixOS, and I have created a Nix module for BeamMP.
When running BeamMP-Launcher, it tries to check for updates, and fails
because of the SHA hash.
There are other ways around this, such as setting the execution
directory, but I think this is a far clearer approach

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-08-02 19:38:18 +02:00
Vali0004
f27d3c6120 Fix bug with BP and not having a trailing slash by default 2025-08-02 00:21:40 -04:00
Vali0004
7a4b24d616 Change to canonical paths for executable paths 2025-08-02 00:14:37 -04:00
Tixx
b6b0e4ba3e
Refine updating (#201)
This PR makes the launcher update more reliable by downloading the
update to a "new_BeamMP-Launcher.exe" file, instead of the launcher
renaming itself to a backup (effectively bricking itself and the
shortcut) and then hoping the download doesn't fail.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-07-27 01:58:06 +02:00
Tixx
a87aa7230a
Make null termination in hex encoding more clear 2025-07-27 01:07:23 +02:00
Tixx
b9cc025083
Check hashes after downloading 2025-07-25 23:50:16 +02:00
Tixx
c0ed056440
Fix for backup failing when it's in use
The backup file may still be in use by older launcher versions (v2.0.71) if they update
2025-07-17 14:33:25 +02:00
Tixx
e6e5bf8327
Fix launcher update to delete backup after download 2025-07-17 14:32:17 +02:00
Tixx
e7cfb6e406
Add Debian legacy Steam installation path (#198)
Current Steam installations seem to have a much cleaner filesystem
layout on Debian while older ones look quite different.

Older Debian installations ~/.steam/root points to ~/.steam while newer
ones point to ~/.steam/debian-installation and hence BeamMP cannot find
integrity.json. 'steamapps' is at ~/.steam/debian-installation in newer
installations.

On the older installations, 'steamapps' is at ~/.steam/steam/steamapps.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-07-09 11:12:11 +02:00
Markus Ingalsuo
185818d174 Add Debian legacy Steam installation path
Current Steam installations seem to have a much cleaner filesystem
layout on Debian while older ones look quite different.

Older Debian installations ~/.steam/root points to ~/.steam while newer
ones point to ~/.steam/debian-installation and hence BeamMP cannot
find integrity.json. 'steamapps' is at ~/.steam/debian-installation in
newer installations.

On the older installations, 'steamapps' is at ~/.steam/steam/steamapps.

Signed-off-by: Markus Ingalsuo <markus.ingalsuo@gmail.com>
2025-07-09 11:34:45 +03:00
7 changed files with 116 additions and 23 deletions

View File

@ -11,7 +11,7 @@
#include <string>
class HTTP {
public:
static bool Download(const std::string& IP, const beammp_fs_string& Path);
static bool Download(const std::string& IP, const beammp_fs_string& Path, const std::string& Hash);
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);

View File

@ -13,6 +13,7 @@
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 GetVer();
std::string GetPatch();

View File

@ -29,7 +29,6 @@
#endif
namespace Utils {
inline std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
std::vector<std::string> Val;
size_t pos;
@ -185,7 +184,7 @@ inline std::wstring ToWString(const std::string& s) {
return s;
}
#endif
inline std::string GetSha256HashReallyFast(const beammp_fs_string& filename) {
inline std::string GetSha256HashReallyFastFile(const beammp_fs_string& filename) {
try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
@ -237,7 +236,51 @@ inline std::wstring ToWString(const std::string& s) {
for (size_t i = 0; i < sha256_len; i++) {
char buf[3];
sprintf(buf, "%02x", sha256_value[i]);
buf[2] = 0;
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;

View File

@ -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 beammp_fs_string& Path) {
bool HTTP::Download(const std::string& IP, const beammp_fs_string& Path, const std::string& Hash) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
@ -139,6 +139,16 @@ bool HTTP::Download(const std::string& IP, const beammp_fs_string& Path) {
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;

View File

@ -445,7 +445,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
}
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::GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) {
if (fs::exists(PathToSaveTo) && Utils::GetSha256HashReallyFastFile(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));
@ -476,7 +476,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
WaitForConfirm();
continue;
} else if (auto OldCachedPath = CachingDirectory / std::filesystem::path(ModInfoIter->FileName).filename();
fs::exists(OldCachedPath) && Utils::GetSha256HashReallyFast(OldCachedPath) == ModInfoIter->Hash) {
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));
@ -562,7 +562,7 @@ void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<Mo
Terminate = true;
}
if (Utils::GetSha256HashReallyFast(PathToSaveTo) != ModInfoIter->Hash) {
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)"));
Terminate = true;
}

View File

@ -187,6 +187,7 @@ 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
};

View File

@ -14,7 +14,10 @@
#if defined(_WIN32)
#elif defined(__linux__)
#include <unistd.h>
#endif
#elif defined (__APPLE__)
#include <unistd.h>
#include <libproc.h>
#endif // __APPLE__
#include "Http.h"
#include "Logger.h"
#include "Network/network.hpp"
@ -94,6 +97,34 @@ beammp_fs_string GetEP(const beammp_fs_char* P) {
}();
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;
@ -103,7 +134,7 @@ void ReLaunch() {
}
info("Relaunch!");
system("cls");
ShellExecuteW(nullptr, L"runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShellExecuteW(nullptr, L"runas", (GetBP() / GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
@ -114,7 +145,7 @@ void URelaunch() {
Arg += Utils::ToWString(options.argv[c - 1]);
Arg += L" ";
}
ShellExecuteW(nullptr, L"open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShellExecuteW(nullptr, L"open", (GetBP() / GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
@ -128,7 +159,7 @@ void ReLaunch() {
}
info("Relaunch!");
system("clear");
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
int ret = execv((GetBP() / GetEN()).c_str(), const_cast<char**>(options.argv));
if (ret < 0) {
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
exit(1);
@ -137,7 +168,7 @@ void ReLaunch() {
exit(1);
}
void URelaunch() {
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
int ret = execv((GetBP() / GetEN()).c_str(), const_cast<char**>(options.argv));
if (ret < 0) {
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
exit(1);
@ -169,9 +200,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);
beammp_fs_string EP(GetEP() + GetEN()), Back(GetEP() + beammp_wide("BeamMP-Launcher.back"));
beammp_fs_string BP(GetBP() / GetEN()), Back(GetBP() / beammp_wide("BeamMP-Launcher.back"));
std::string FileHash = Utils::GetSha256HashReallyFast(EP);
std::string FileHash = Utils::GetSha256HashReallyFastFile(BP);
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
if (!options.no_update) {
@ -179,14 +210,21 @@ void CheckForUpdates(const std::string& CV) {
#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);
HTTP::Download(
"https://backend.beammp.com/builds/launcher?download=true"
"&pk="
+ PublicKey + "&branch=" + Branch,
EP);
beammp_wide("new_") + BP, LatestHash);
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(beammp_wide("new_") + BP, BP);
URelaunch();
#endif
} else {
@ -247,8 +285,8 @@ void InitLauncher() {
}
#endif
size_t DirCount(const std::filesystem::path& path) {
return (size_t)std::distance(std::filesystem::directory_iterator { path }, std::filesystem::directory_iterator {});
size_t DirCount(const fs::path& path) {
return (size_t)std::distance(fs::directory_iterator { path }, fs::directory_iterator {});
}
void CheckMP(const beammp_fs_string& Path) {
@ -329,14 +367,14 @@ void PreGame(const beammp_fs_string& GamePath) {
std::string ZipPath(GetGamePath() / R"(mods/multiplayer/beammp.zip)");
#endif
std::string FileHash = fs::exists(ZipPath) ? Utils::GetSha256HashReallyFast(ZipPath) : "";
std::string FileHash = fs::exists(ZipPath) ? Utils::GetSha256HashReallyFastFile(ZipPath) : "";
if (FileHash != LatestHash) {
info("Downloading BeamMP Update " + LatestHash);
HTTP::Download("https://backend.beammp.com/builds/client?download=true"
"&pk="
+ PublicKey + "&branch=" + Branch,
ZipPath);
ZipPath, LatestHash);
}
beammp_fs_string Target(GetGamePath() / beammp_wide("mods/unpacked/beammp"));