diff --git a/.github/workflows/cmake-macos.yml b/.github/workflows/cmake-macos.yml new file mode 100644 index 00000000..6efce071 --- /dev/null +++ b/.github/workflows/cmake-macos.yml @@ -0,0 +1,46 @@ +name: CMake MacOS Build + +on: [push, pull_request, workflow_dispatch] + +env: + BUILD_TYPE: Release + +jobs: + macos-build: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + with: + submodules: 'true' + + - name: Restore artifacts, or run vcpkg, build and cache artifacts + uses: lukka/run-vcpkg@v7 + id: runvcpkg + with: + vcpkgArguments: 'zlib:arm64-osx nlohmann-json:arm64-osx openssl:arm64-osx cpp-httplib[openssl]:arm64-osx curl:arm64-osx' + vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' + vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6' + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build-macos + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build-macos + run: | + cmake $GITHUB_WORKSPACE \ + -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' \ + -DCMAKE_OSX_ARCHITECTURES=arm64 + + - name: Build + working-directory: ${{github.workspace}}/build-macos + shell: bash + run: cmake --build . --config $BUILD_TYPE + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + with: + name: BeamMP-Launcher + path: ${{github.workspace}}/build-macos/BeamMP-Launcher \ No newline at end of file diff --git a/.gitignore b/.gitignore index d1128670..2f495436 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,13 @@ bin/ compile_commands.json key out/ +build/ +.vscode/ +vcpkg/ +vcpkg_installed/ +CMakeFiles/ +cmake_install.cmake +CMakeCache.txt +Makefile +BeamMP-Launcher +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 67e666ef..acb53e28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ if (WIN32) message(STATUS "MSVC -> forcing use of statically-linked runtime.") STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) +elseif(APPLE) + set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Build architectures for MacOS" FORCE) endif(WIN32) set(CMAKE_CXX_STANDARD 20) @@ -32,6 +34,11 @@ elseif (UNIX) find_package(OpenSSL REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl) +elseif(APPLE) + find_package(ZLIB REQUIRED) + find_package(OpenSSL REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE + ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl) else(WIN32) #MINGW add_definitions("-D_WIN32_WINNT=0x0600") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static") diff --git a/include/Network/network.hpp b/include/Network/network.hpp index 5b1ec0f6..6b116e9d 100644 --- a/include/Network/network.hpp +++ b/include/Network/network.hpp @@ -16,6 +16,10 @@ #include #endif +#ifdef __APPLE__ +#include "linuxfixes.h" +#endif + void NetReset(); extern bool Dev; extern int ping; diff --git a/include/Options.h b/include/Options.h index 279a8d45..54fa21ee 100644 --- a/include/Options.h +++ b/include/Options.h @@ -11,7 +11,7 @@ struct Options { #if defined(_WIN32) std::string executable_name = "BeamMP-Launcher.exe"; -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) std::string executable_name = "BeamMP-Launcher"; #endif unsigned int port = 4444; @@ -21,6 +21,11 @@ struct Options { bool no_launch = false; const char **game_arguments = nullptr; int game_arguments_length = 0; +#if defined(__APPLE__) + std::string bottle; + std::string bottle_path; + std::string wine_executable; +#endif const char** argv = nullptr; int argc = 0; }; diff --git a/include/Security/Init.h b/include/Security/Init.h index d34d3b4f..9b8b1032 100644 --- a/include/Security/Init.h +++ b/include/Security/Init.h @@ -10,5 +10,9 @@ void PreGame(const std::string& GamePath); std::string CheckVer(const std::string& path); void InitGame(const std::string& Dir); std::string GetGameDir(); +#if defined(__APPLE__) +std::string GetBottlePath(); +std::string GetBottleName(); +#endif void LegitimacyCheck(); void CheckLocalKey(); \ No newline at end of file diff --git a/include/Utils.h b/include/Utils.h index 77638ec2..ca52a1ce 100644 --- a/include/Utils.h +++ b/include/Utils.h @@ -7,6 +7,7 @@ #pragma once #include #include +#include namespace Utils { inline std::vector Split(const std::string& String, const std::string& delimiter) { @@ -22,5 +23,33 @@ namespace Utils { if (!s.empty()) Val.push_back(s); return Val; - }; -}; \ No newline at end of file + } + + inline std::string ToLower(const std::string& str) { + std::string lowerStr = str; + std::transform(str.begin(), str.end(), lowerStr.begin(), ::tolower); + return lowerStr; + } + + inline std::string Trim(const std::string& s) { + auto start = s.find_first_not_of(" \t\n\r"); + auto end = s.find_last_not_of(" \t\n\r"); + return (start == std::string::npos) ? "" : s.substr(start, end - start + 1); + } + + #if defined(__APPLE__) + inline std::pair runCommand(const char* cmd) { + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + throw std::runtime_error("failed to run popen()"); + } + while (fgets(buffer.data(), static_cast(buffer.size()), pipe.get()) != nullptr) { + result += buffer.data(); + } + int returnCode = pclose(pipe.release()); + return { result, returnCode }; + } + #endif +} \ No newline at end of file diff --git a/src/GameStart.cpp b/src/GameStart.cpp index f94012bf..ff4c64d2 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -8,7 +8,7 @@ #if defined(_WIN32) #include #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include "vdf_parser.hpp" #include #include @@ -23,6 +23,7 @@ #include #include #include "Options.h" +#include "Utils.h" unsigned long GamePID = 0; #if defined(_WIN32) @@ -72,6 +73,15 @@ std::string GetGamePath() { Path += Ver + "/"; return Path; } +#elif defined(__APPLE__) +std::string GetGamePath() { + std::string BottlePath = GetBottlePath(); + std::string Path = BottlePath + "/drive_c/users/crossover/AppData/Local/BeamNG.drive/"; + std::string Ver = CheckVer(GetGameDir()); + Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); + Path += Ver + "/"; + return Path; +} #endif #if defined(_WIN32) @@ -131,6 +141,104 @@ void StartGame(std::string Dir) { std::this_thread::sleep_for(std::chrono::seconds(5)); exit(2); } + +#elif defined(__APPLE__) +void StartGame(std::string Dir) { + extern char **environ; + int status; + + std::filesystem::path executable = std::filesystem::path(Dir) / "Bin64" / "BeamNG.drive.x64.exe"; + if (!std::filesystem::exists(executable)) { + error("The executable BeamNG.drive.x64.exe does not exist in folder: " + executable.string()); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + + std::vector argv; + std::filesystem::path spawnWineExecutable; + std::filesystem::path bottlePath = std::filesystem::path(GetBottlePath()); + + if (options.wine_executable.empty()) { + auto mdfindCmd = Utils::runCommand("mdfind kMDItemCFBundleIdentifier = 'com.codeweavers.CrossOver'"); + std::string mdfindPaths = mdfindCmd.first; + debug("Shared path: " + mdfindPaths); + if (mdfindCmd.second != 0) { + error("Failed to detect CrossOver directory. Please ensure CrossOver is installed on your Mac."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + std::istringstream stream(mdfindPaths); + std::string line; + std::vector paths; + while (std::getline(stream, line)) { + debug("Line: " + line); + paths.push_back(line); + } + if (paths.empty()) { + error("No CrossOver.app found."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + if (paths.size() > 1) { + debug("Multiple paths found for CrossOver:"); + } + std::filesystem::path crossoverPath; + int crossOverCount = 0; + for (const auto& p : paths) { + debug("Found path for CrossOver: " + p); + if (p.find("CrossOver.app") != std::string::npos) { + crossoverPath = std::filesystem::path(p); + crossOverCount++; + if (crossOverCount > 1) { + error("Multiple CrossOver.app found. Please specify the path to the wine executable using the --wine option."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + } + } + if (crossoverPath.empty()) { + error("No valid CrossOver.app found."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + debug("Selected CrossOver path: " + crossoverPath.string()); + + spawnWineExecutable = crossoverPath / "Contents" / "SharedSupport" / "CrossOver" / "bin" / "wine"; + argv.push_back(spawnWineExecutable.c_str()); + argv.push_back("--bottle"); + argv.push_back(bottlePath.c_str()); + } else { + spawnWineExecutable = options.wine_executable; + std::string wineprefix = "WINEPREFIX=" + bottlePath.string(); + argv.push_back(wineprefix.c_str()); + argv.push_back(spawnWineExecutable.c_str()); + } + + argv.push_back(executable.c_str()); + for (int i = 0; i < options.game_arguments_length; i++) { + argv.push_back(options.game_arguments[i]); + } + argv.push_back(nullptr); + + // Lancement du processus + pid_t pid; + posix_spawn_file_actions_t spawn_actions; + posix_spawn_file_actions_init(&spawn_actions); + posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO); + posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO); + + int result = posix_spawn(&pid, spawnWineExecutable.c_str(), &spawn_actions, nullptr, + const_cast(argv.data()), environ); + if (result != 0) { + error("Failed to launch the game! Launcher will now exit."); + return; + } + waitpid(pid, &status, 0); + info("Game closed! Launcher will now exit."); + + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(2); +} #endif void InitGame(const std::string& Dir) { diff --git a/src/Network/Core.cpp b/src/Network/Core.cpp index 5cec91c3..3b1f7809 100644 --- a/src/Network/Core.cpp +++ b/src/Network/Core.cpp @@ -12,7 +12,7 @@ #if defined(_WIN32) #include #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include #include #include @@ -23,6 +23,10 @@ #include #endif +#if defined(__APPLE__) +#include +#endif + #include "Logger.h" #include "Startup.h" #include @@ -469,21 +473,19 @@ int Handle(EXCEPTION_POINTERS* ep) { [[noreturn]] void CoreNetwork() { while (true) { -#if not defined(__MINGW32__) +#if defined(_WIN32) __try { -#endif - CoreMain(); - -#if not defined(__MINGW32__) and not defined(__linux__) } __except (Handle(GetExceptionInformation())) { } -#elif not defined(__MINGW32__) and defined(__linux__) - } - catch (...) { - except("(Core) Code : " + std::string(strerror(errno))); - } +#else + try { + CoreMain(); + } catch (const std::exception& e) { + error("(Core) Exception: " + std::string(e.what())); + } catch (...) { + error("(Core) Unknown exception"); + } #endif - std::this_thread::sleep_for(std::chrono::seconds(1)); } -} +} \ No newline at end of file diff --git a/src/Network/DNS.cpp b/src/Network/DNS.cpp index f79b34b8..e2ea45f9 100644 --- a/src/Network/DNS.cpp +++ b/src/Network/DNS.cpp @@ -9,7 +9,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include "linuxfixes.h" #include #include diff --git a/src/Network/GlobalHandler.cpp b/src/Network/GlobalHandler.cpp index 50eb2c90..2bb3b79a 100644 --- a/src/Network/GlobalHandler.cpp +++ b/src/Network/GlobalHandler.cpp @@ -10,7 +10,7 @@ #if defined(_WIN32) #include #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include "linuxfixes.h" #include #include diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index 876c6f18..a8211e0e 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -16,7 +16,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include #include #include @@ -34,7 +34,6 @@ #include #include #include -#include #include #include "hashpp.h" diff --git a/src/Network/VehicleData.cpp b/src/Network/VehicleData.cpp index d928a964..08d1c42b 100644 --- a/src/Network/VehicleData.cpp +++ b/src/Network/VehicleData.cpp @@ -10,7 +10,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include "linuxfixes.h" #include #include @@ -65,7 +65,7 @@ void UDPRcv() { sockaddr_in FromServer {}; #if defined(_WIN32) int clientLength = sizeof(FromServer); -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) socklen_t clientLength = sizeof(FromServer); #endif ZeroMemory(&FromServer, clientLength); diff --git a/src/Network/VehicleEvent.cpp b/src/Network/VehicleEvent.cpp index 91a06c03..71f3d64c 100644 --- a/src/Network/VehicleEvent.cpp +++ b/src/Network/VehicleEvent.cpp @@ -13,7 +13,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include #include #include diff --git a/src/Options.cpp b/src/Options.cpp index 6d2f0c07..9c4332ed 100644 --- a/src/Options.cpp +++ b/src/Options.cpp @@ -86,6 +86,17 @@ void InitOptions(int argc, const char *argv[], Options &options) { options.no_download = true; options.no_launch = true; options.no_update = true; + #if defined(__APPLE__) + } else if ((argument == "--bottle" && !options.bottle_path.empty()) || (argument == "--bottle-path" && !options.bottle.empty())) { + error("--bottle and --bottle-path cannot be used at the same time."); + exit(1); + } else if (argument == "--bottle") { + options.bottle = argv[i + 1]; + } else if (argument == "--bottle-path") { + options.bottle_path = argv[i + 1]; + } else if (argument == "--wine") { + options.wine_executable = argv[i + 1]; + #endif } else if (argument == "--" || argument == "--game") { options.game_arguments = &argv[i + 1]; options.game_arguments_length = argc - i - 1; @@ -101,6 +112,12 @@ void InitOptions(int argc, const char *argv[], Options &options) { "\t--no-update Skip applying launcher updates (you must update manually)\n" "\t--no-launch Skip launching the game (you must launch the game manually)\n" "\t--dev Developer mode, same as --verbose --no-download --no-launch --no-update\n" + #if defined(__APPLE__) + "\t--bottle Name of the CrossOver Bottle where the game is located in. Only works for CrossOver Bottles.\n" + "\t--bottle-path Path to the Bottle where the game is located in.\n" + "\t--wine Path to the wine executable, by default the wine executable of CrossOver is used.\n" + "\tWARNING: The bottle-path and bottle options cannot be used at the same time.\n" + #endif "\t--game Passes ALL following arguments to the game, see also `--`\n" << std::flush; exit(0); diff --git a/src/Security/BeamNG.cpp b/src/Security/BeamNG.cpp index 88b4542c..2e8b07bb 100644 --- a/src/Security/BeamNG.cpp +++ b/src/Security/BeamNG.cpp @@ -8,12 +8,21 @@ #include #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include "vdf_parser.hpp" #include #include #include #endif +#include "Utils.h" +#include "Options.h" +#include +#include +#include +#include +#include +#include +#include #include "Logger.h" #include #include @@ -23,7 +32,13 @@ #define MAX_VALUE_NAME 16383 int TraceBack = 0; -std::string GameDir; +#if defined(__APPLE__) + std::filesystem::path GameDir; +#else + std::string GameDir; +#endif + +std::filesystem::path BottlePath; void lowExit(int code) { TraceBack = 0; @@ -38,6 +53,8 @@ std::string GetGameDir() { return GameDir.substr(0, GameDir.find_last_of('\\')); #elif defined(__linux__) return GameDir.substr(0, GameDir.find_last_of('/')); +#elif defined(__APPLE__) + return GameDir; #endif } #ifdef _WIN32 @@ -136,8 +153,6 @@ std::string QueryKey(HKEY hKey, int ID) { } #endif -namespace fs = std::filesystem; - bool NameValid(const std::string& N) { if (N == "config" || N == "librarycache") { return true; @@ -148,7 +163,7 @@ bool NameValid(const std::string& N) { return false; } void FileList(std::vector& a, const std::string& Path) { - for (const auto& entry : fs::directory_iterator(Path)) { + for (const auto& entry : std::filesystem::directory_iterator(Path)) { const auto& DPath = entry.path(); if (!entry.is_directory()) { a.emplace_back(DPath.string()); @@ -157,6 +172,112 @@ void FileList(std::vector& a, const std::string& Path) { } } } + +#if defined(__APPLE__) + +std::string GetBottlePath() { + return BottlePath; +} + +std::string GetBottleName() { + std::filesystem::path bottlePath(BottlePath); + return bottlePath.filename().string(); +} + +std::map GetDriveMappings(const std::filesystem::path& bottlePath) { + std::map driveMappings; + std::filesystem::path dosDevicesPath = bottlePath / "dosdevices/"; + + if (std::filesystem::exists(dosDevicesPath)) { + for (const auto& entry : std::filesystem::directory_iterator(dosDevicesPath)) { + if (entry.is_symlink()) { + char driveLetter = entry.path().filename().string()[0]; + driveLetter = std::tolower(driveLetter); + std::string macPath = std::filesystem::read_symlink(entry.path()).string(); + if (!std::filesystem::path(macPath).is_absolute()) { + macPath = dosDevicesPath / macPath; + } + driveMappings[driveLetter] = macPath; + + } + } + } else { + error("Failed to find dosdevices directory for bottle '" + bottlePath.string() + "'"); + } + return driveMappings; +} + +bool CheckForGame(const std::string& libraryPath, const std::map& driveMappings) { + char driveLetter = std::tolower(libraryPath[0]); + + if (!driveMappings.contains(driveLetter)) { + warn(std::string("Drive letter ") + driveLetter + " not found in mappings."); + return false; + } + + std::filesystem::path basePath = driveMappings.at(driveLetter); + debug("Base path: " + basePath.string()); + + std::string cleanPathStr = libraryPath.substr(2); + std::replace(cleanPathStr.begin(), cleanPathStr.end(), '\\', '/'); + if (!cleanPathStr.empty() ){ + if (cleanPathStr[0] == '/') { + cleanPathStr.erase(0, 1); + } + if (cleanPathStr.back() == '/') { + cleanPathStr.pop_back(); + } + } + std::filesystem::path cleanLibraryPath = std::filesystem::path(cleanPathStr); + + + debug("Cleaned library path: " + cleanLibraryPath.string()); + + std::filesystem::path beamngPath = basePath / cleanLibraryPath / "steamapps/common/BeamNG.drive"; + + beamngPath = beamngPath.lexically_normal(); + + debug("Checking for BeamNG.drive at: " + beamngPath.string()); + + if (std::filesystem::exists(beamngPath)) { + GameDir = beamngPath; + info("BeamNG.drive found at: " + GameDir.string()); + return true; + } + + + return false; +} + +void ProcessBottle(const std::filesystem::path& bottlePath) { + info("Checking bottle: " + bottlePath.filename().string()); + auto driveMappings = GetDriveMappings(bottlePath); + + const std::filesystem::path libraryFilePath = bottlePath / "drive_c/Program Files (x86)/Steam/config/libraryfolders.vdf"; + if (!std::filesystem::exists(libraryFilePath)) { + warn("Library file not found in bottle: " + bottlePath.filename().string()); + return; + } + + std::ifstream libraryFile(libraryFilePath); + if (!libraryFile.is_open()) { + error("Failed to open libraryfolders.vdf in bottle: " + bottlePath.filename().string()); + return; + } + + auto root = tyti::vdf::read(libraryFile); + libraryFile.close(); + + for (const auto& [key, folderInfo] : root.childs) { + if (folderInfo->attribs.contains("path") && + CheckForGame(folderInfo->attribs.at("path"), driveMappings)) { + BottlePath = bottlePath.string(); + return; + } + } +} +#endif + void LegitimacyCheck() { #if defined(_WIN32) std::string Result; @@ -223,16 +344,90 @@ void LegitimacyCheck() { break; } } - if (GameDir.empty()) { - error("The game directory was not found."); - return; + +#elif defined(__APPLE__) + if (options.bottle_path.empty()) { + const char* homeDir = getpwuid(getuid())->pw_dir; + std::filesystem::path crossoverBottlesPath; + + auto [output, status] = Utils::runCommand("defaults read com.codeweavers.CrossOver.plist BottleDir"); + + if (status != 0) { + std::filesystem::path defaultBottlesPath = std::filesystem::path(homeDir) / "Library/Application Support/CrossOver/Bottles"; + + defaultBottlesPath = std::filesystem::canonical(defaultBottlesPath); + + if (!std::filesystem::exists(defaultBottlesPath)) { + error("Failed to detect CrossOver installation, make sure you have installed it and have a bottle created."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + crossoverBottlesPath = defaultBottlesPath; + } else { + crossoverBottlesPath = std::filesystem::path(Utils::Trim(output)); + } + + if (options.bottle.empty()) { + debug("Checking all bottles in: " + crossoverBottlesPath.string()); + //vérifier que le répertoire existe avant de continuer + try { + if (!std::filesystem::exists(crossoverBottlesPath) || !std::filesystem::is_directory(crossoverBottlesPath)) { + error("Chemin des bouteilles invalide: " + crossoverBottlesPath.string() + + "\nExiste: " + std::to_string(std::filesystem::exists(crossoverBottlesPath)) + + "\nEst un dossier: " + std::to_string(std::filesystem::is_directory(crossoverBottlesPath))); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + + } catch (const std::filesystem::filesystem_error& e) { + error("Erreur d'accès: " + std::string(e.what())); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(1); + } + for (const auto& bottle : std::filesystem::directory_iterator(crossoverBottlesPath)) { + debug("Checking bottle: " + bottle.path().filename().string()); + if (bottle.is_directory()) { + + ProcessBottle(bottle); + if (!GameDir.empty()) { + return; + } + } + + } + } else { + std::filesystem::path bottlePath = crossoverBottlesPath / options.bottle; + debug("Checking bottle: " + bottlePath.string()); + if (!std::filesystem::exists(bottlePath)) { + error("Bottle does not exist: " + bottlePath.string()); + exit(1); + } + ProcessBottle(bottlePath); + if (!GameDir.empty()) { + return; + } + } + } else { + std::filesystem::path bottlePath = options.bottle_path; + if (!std::filesystem::exists(bottlePath)) { + error("Bottle path does not exist: " + bottlePath.string()); + exit(1); + } + ProcessBottle(bottlePath); + if (!GameDir.empty()) { + return; + } } + + error("Failed to find BeamNG.drive installation in any CrossOver bottle. Make sure BeamNG.drive is installed in a CrossOver bottle, or set it with the --bottle or --bottle-path argument."); + exit(1); + #endif } std::string CheckVer(const std::string& dir) { #if defined(_WIN32) std::string temp, Path = dir + "\\integrity.json"; -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) std::string temp, Path = dir + "/integrity.json"; #endif std::ifstream f(Path.c_str(), std::ios::binary); diff --git a/src/Startup.cpp b/src/Startup.cpp index 0df85a46..6fda7555 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -13,7 +13,7 @@ #include #if defined(_WIN32) #include -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) #include #endif #include "Http.h" @@ -75,7 +75,7 @@ Version::Version(const std::array& v) std::string GetEN() { #if defined(_WIN32) return "BeamMP-Launcher.exe"; -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) return "BeamMP-Launcher"; #endif } @@ -119,7 +119,7 @@ void URelaunch() { std::this_thread::sleep_for(std::chrono::seconds(1)); exit(1); } -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) void ReLaunch() { std::string Arg; for (int c = 2; c <= options.argc; c++) { @@ -150,7 +150,7 @@ void URelaunch() { void CheckName() { #if defined(_WIN32) std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('\\') + 1); -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('/') + 1); #endif if (FN != DN) { @@ -175,8 +175,8 @@ void CheckForUpdates(const std::string& CV) { if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) { if (!options.no_update) { - info("Launcher update " + LatestVersion + " found!"); -#if defined(__linux__) + info("Launcher update found!"); +#if defined(__linux__) || defined(__APPLE__) error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches."); #else fs::remove(Back); @@ -237,7 +237,7 @@ void InitLauncher() { CheckLocalKey(); CheckForUpdates(std::string(GetVer()) + GetPatch()); } -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) void InitLauncher() { info("BeamMP Launcher v" + GetVer() + GetPatch()); @@ -324,7 +324,7 @@ void PreGame(const std::string& GamePath) { } #if defined(_WIN32) std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)"); -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) // Linux version of the game cant handle mods with uppercase names std::string ZipPath(GetGamePath() + R"(mods/multiplayer/beammp.zip)"); #endif diff --git a/src/main.cpp b/src/main.cpp index ddfc4a52..69dca82c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,7 +26,7 @@ Options options; int main(int argc, const char** argv) try { #if defined(_WIN32) system("cls"); -#elif defined(__linux__) +#elif defined(__linux__) || defined(__APPLE__) system("clear"); #endif