diff --git a/.github/workflows/cmake-windows.yml b/.github/workflows/cmake-windows.yml index aa9b43b..b882766 100644 --- a/.github/workflows/cmake-windows.yml +++ b/.github/workflows/cmake-windows.yml @@ -20,7 +20,7 @@ jobs: with: vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' - vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' + vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832' vcpkgTriplet: 'x64-windows-static' - name: Create Build Environment diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 8d2063a..2f8e3f3 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -85,7 +85,7 @@ jobs: with: vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' - vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' + vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832' vcpkgTriplet: 'x64-windows-static' - name: Create Build Environment diff --git a/.gitmodules b/.gitmodules index b54a97b..5cfa6fa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,27 +1,3 @@ [submodule "deps/commandline"] path = deps/commandline url = https://github.com/lionkor/commandline -[submodule "deps/asio"] - path = deps/asio - url = https://github.com/chriskohlhoff/asio -[submodule "deps/rapidjson"] - path = deps/rapidjson - url = https://github.com/Tencent/rapidjson -[submodule "deps/toml11"] - path = deps/toml11 - url = https://github.com/ToruNiina/toml11 -[submodule "deps/sentry-native"] - path = deps/sentry-native - url = https://github.com/getsentry/sentry-native -[submodule "deps/sol2"] - path = deps/sol2 - url = https://github.com/ThePhD/sol2 -[submodule "deps/libzip"] - path = deps/libzip - url = https://github.com/nih-at/libzip -[submodule "deps/cpp-httplib"] - path = deps/cpp-httplib - url = https://github.com/yhirose/cpp-httplib -[submodule "deps/json"] - path = deps/json - url = https://github.com/nlohmann/json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a36c83..680cd8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,17 @@ cmake_minimum_required(VERSION 3.0) +if (WIN32) + set(VCPKG_TARGET_TRIPLET "x64-windows-static") +elseif(UNIX) + set(VCPKG_TARGET_TRIPLET "x64-linux") +endif() + +option(USE_VCPKG "USE_VCPKG" ON) + +if(USE_VCPKG) + include(vcpkg.cmake) +endif() + message(STATUS "You can find build instructions and a list of dependencies in the README at \ https://github.com/BeamMP/BeamMP-Server") @@ -10,15 +22,6 @@ project(BeamMP-Server set(HTTPLIB_REQUIRE_OPENSSL ON) -include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp") -include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") -include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib") -include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include") -include_directories("${PROJECT_SOURCE_DIR}/deps") - add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT) if(APPLE) @@ -36,30 +39,14 @@ if (WIN32) STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) endif() -include_directories("include/sentry-native/include") -set(SENTRY_BUILD_SHARED_LIBS OFF) -if (MSVC) - set(SENTRY_BUILD_RUNTIMESTATIC ON) -endif() -message(STATUS "Checking for Sentry URL") -# this is set by the build system. -# IMPORTANT: if you're building from source, just leave this empty -if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL) - message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \ - This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.") - set(BEAMMP_SECRET_SENTRY_URL "") - set(SENTRY_BACKEND none) -else() - string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN) - message(STATUS "Sentry URL is length ${URL_LEN}") - set(SENTRY_BACKEND breakpad) -endif() -add_subdirectory("deps/sentry-native") - if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") endif () +message(STATUS "Adding local source dependencies") +# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG +add_subdirectory(deps) + message(STATUS "Setting compiler flags") if (WIN32) @@ -77,10 +64,19 @@ elseif (UNIX) endif (SANITIZE) endif () - -message(STATUS "Adding local source dependencies") -# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG -add_subdirectory(deps) +message(STATUS "Checking for Sentry URL") +# this is set by the build system. +# IMPORTANT: if you're building from source, just leave this empty +if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL) + message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \ + This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.") + set(BEAMMP_SECRET_SENTRY_URL "") + set(SENTRY_BACKEND none) +else() + string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN) + message(STATUS "Sentry URL is length ${URL_LEN}") + set(SENTRY_BACKEND breakpad) +endif() set(CMAKE_CXX_STANDARD 17) @@ -112,57 +108,34 @@ add_executable(BeamMP-Server target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}") include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_include_directories(BeamMP-Server PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/commandline") +include(FindThreads) -if (APPLE) - message(STATUS "NOT looking for Lua on APPLE") -else() - message(STATUS "Looking for Lua") - find_package(Lua REQUIRED VERSION 5.3) -endif() +find_package(OpenSSL REQUIRED) +find_package(ZLIB REQUIRED) +find_package(CURL CONFIG REQUIRED) +find_package(Lua REQUIRED 5.3) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(RapidJSON CONFIG REQUIRED) +find_package(sentry CONFIG REQUIRED) +find_package(sol2 CONFIG REQUIRED) +find_package(toml11 CONFIG REQUIRED) +find_path(CPP_HTTPLIB_INCLUDE_DIRS "httplib.h") -target_include_directories(BeamMP-Server PUBLIC - ${LUA_INCLUDE_DIR} - ${CURL_INCLUDE_DIRS} - "include/tomlplusplus" - "include/sentry-native/include" - "include/curl/include") +target_include_directories(BeamMP-Server PRIVATE + ${CPP_HTTPLIB_INCLUDE_DIRS} + ${LUA_INCLUDE_DIR} +) -message(STATUS "Looking for SSL") +target_link_libraries(BeamMP-Server PRIVATE OpenSSL::SSL OpenSSL::Crypto + ZLIB::ZLIB + CURL::libcurl + ${LUA_LIBRARIES} + Threads::Threads + commandline + nlohmann_json::nlohmann_json + rapidjson + sentry::sentry + sol2::sol2 + toml11::toml11 +) -if (APPLE) - set(OPENSSL_LIBRARIES ssl crypto) -else() - find_package(OpenSSL REQUIRED) -endif() - -target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES}) -message(STATUS "CURL IS ${CURL_LIBRARIES}") - -if (UNIX) - target_link_libraries(BeamMP-Server - z - pthread - ${LUA_LIBRARIES} - crypto - ${OPENSSL_LIBRARIES} - commandline - sentry - ssl) -elseif (WIN32) - include(FindLua) - message(STATUS "Looking for libz") - find_package(ZLIB REQUIRED) - message(STATUS "Looking for RapidJSON") - find_package(RapidJSON CONFIG REQUIRED) - target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) - target_link_libraries(BeamMP-Server - ws2_32 - ZLIB::ZLIB - ${LUA_LIBRARIES} - ${OPENSSL_LIBRARIES} - commandline - sentry) -endif () diff --git a/Changelog.md b/Changelog.md index 982c893..7ff6d04 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,13 @@ +# v3.0.2 + +- ADDED Periodic update message if a new server is released +- ADDED Config setting for the IP the http server listens on +- CHANGED Default MaxPlayers to 8 +- CHANGED Default http server listen IP to localhost +- FIXED `MP.CreateEventTimer` filling up the queue (see ) +- FIXED `MP.TriggerClientEvent` not kicking the client if it failed +- FIXED Lua result queue handling not checking all results + # v3.0.1 - ADDED Backup URLs to UpdateCheck (will fail less often now) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4e556b0..6f87000 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -1,9 +1,4 @@ -include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include") -include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp") -include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") -include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include") include_directories("${PROJECT_SOURCE_DIR}/deps") +include_directories("${PROJECT_SOURCE_DIR}/deps/commandline") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline") -add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2") diff --git a/deps/asio b/deps/asio deleted file mode 160000 index d038fb3..0000000 --- a/deps/asio +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d038fb3c2fb56fb91ff1d17b0715cff7887aa09e diff --git a/deps/cpp-httplib b/deps/cpp-httplib deleted file mode 160000 index b324921..0000000 --- a/deps/cpp-httplib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b324921c1aeff2976544128e4bb2a0979a4aa595 diff --git a/deps/json b/deps/json deleted file mode 160000 index eb21824..0000000 --- a/deps/json +++ /dev/null @@ -1 +0,0 @@ -Subproject commit eb2182414749825be086c825edb5229e5c28503d diff --git a/deps/libzip b/deps/libzip deleted file mode 160000 index 76df02f..0000000 --- a/deps/libzip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 76df02f86b9746e139fd9fc934a70e3a21bbc557 diff --git a/deps/rapidjson b/deps/rapidjson deleted file mode 160000 index 00dbcf2..0000000 --- a/deps/rapidjson +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 00dbcf2c6e03c47d6c399338b6de060c71356464 diff --git a/deps/sentry-native b/deps/sentry-native deleted file mode 160000 index 90966cc..0000000 --- a/deps/sentry-native +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 90966cc1022b8155681b6899539b35466baccf2c diff --git a/deps/sol2 b/deps/sol2 deleted file mode 160000 index c068aef..0000000 --- a/deps/sol2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c068aefbeddb3dd1f1fd38d42843ecb49a3b4cdb diff --git a/deps/toml11 b/deps/toml11 deleted file mode 160000 index fda0a2b..0000000 --- a/deps/toml11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fda0a2b9abd16e356f777c40a675131821c71b00 diff --git a/include/Client.h b/include/Client.h index 93882f2..d513659 100644 --- a/include/Client.h +++ b/include/Client.h @@ -92,7 +92,7 @@ private: std::queue mPacketsSync; std::unordered_map mIdentifiers; bool mIsGuest = false; - std::mutex mVehicleDataMutex; + mutable std::mutex mVehicleDataMutex; TSetOfVehicleData mVehicleData; std::string mName = "Unknown Client"; SOCKET mSocket[2] { SOCKET(0), SOCKET(0) }; diff --git a/include/Common.h b/include/Common.h index f448937..afbafbf 100644 --- a/include/Common.h +++ b/include/Common.h @@ -42,7 +42,7 @@ public: std::string MapName { "/levels/gridmap_v2/info.json" }; std::string Key {}; bool HTTPServerEnabled { false }; - int MaxPlayers { 10 }; + int MaxPlayers { 8 }; bool Private { true }; int MaxCars { 1 }; bool DebugModeEnabled { false }; @@ -51,7 +51,9 @@ public: bool SendErrors { true }; bool SendErrorsMessageEnabled { true }; int HTTPServerPort { 8080 }; - bool HTTPServerUseSSL { true }; + std::string HTTPServerIP { "127.0.0.1" }; + bool HTTPServerUseSSL { false }; + bool HideUpdateMessages { false }; [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } }; @@ -118,7 +120,7 @@ private: static inline std::mutex mShutdownHandlersMutex {}; static inline std::deque mShutdownHandlers {}; - static inline Version mVersion { 3, 0, 1 }; + static inline Version mVersion { 3, 0, 2 }; }; std::string ThreadName(bool DebugModeOverride = false); diff --git a/include/TConfig.h b/include/TConfig.h index 3fcc597..b05f422 100644 --- a/include/TConfig.h +++ b/include/TConfig.h @@ -5,7 +5,7 @@ #include #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT -#include // header-only version of TOML++ +#include // header-only version of TOML++ namespace fs = std::filesystem; diff --git a/include/TConsole.h b/include/TConsole.h index c393842..839f862 100644 --- a/include/TConsole.h +++ b/include/TConsole.h @@ -15,6 +15,7 @@ public: void WriteRaw(const std::string& str); void InitializeLuaConsole(TLuaEngine& Engine); void BackupOldLog(); + void StartLoggingToFile(); Commandline& Internal() { return mCommandline; } private: @@ -37,4 +38,6 @@ private: bool mFirstTime { true }; std::string mStateId; const std::string mDefaultStateId = "BEAMMP_SERVER_CONSOLE"; + std::ofstream mLogFileStream; + std::mutex mLogFileStreamMtx; }; diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 9b0a884..876d910 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -6,12 +6,13 @@ #include #include #include +#include #include #include #include #include #include -#include +#include #include #include @@ -72,6 +73,18 @@ private: class TLuaEngine : IThreaded { public: + enum CallStrategy : int { + BestEffort, + Precise, + }; + + struct QueuedFunction { + std::string FunctionName; + std::shared_ptr Result; + std::vector Args; + std::string EventName; // optional, may be empty + }; + TLuaEngine(); ~TLuaEngine() noexcept { beammp_debug("Lua Engine terminated"); @@ -145,7 +158,7 @@ public: return Results; // } std::set GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); - void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS); + void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy); void CancelEventTimers(const std::string& EventName, TLuaStateId StateId); sol::state_view GetStateForPlugin(const fs::path& PluginPath); TLuaStateId GetStateIDForPlugin(const fs::path& PluginPath); @@ -166,6 +179,7 @@ private: ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); } [[nodiscard]] std::shared_ptr EnqueueScript(const TLuaChunk& Script); [[nodiscard]] std::shared_ptr EnqueueFunctionCall(const std::string& FunctionName, const std::vector& Args); + [[nodiscard]] std::shared_ptr EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector& Args, const std::string& EventName, CallStrategy Strategy); void RegisterEvent(const std::string& EventName, const std::string& FunctionName); void AddPath(const fs::path& Path); // to be added to path and cpath void operator()() override; @@ -188,7 +202,7 @@ private: std::thread mThread; std::queue>> mStateExecuteQueue; std::recursive_mutex mStateExecuteQueueMutex; - std::queue, std::vector>> mStateFunctionQueue; + std::vector mStateFunctionQueue; std::mutex mStateFunctionQueueMutex; std::condition_variable mStateFunctionQueueCond; TLuaEngine* mEngine; @@ -202,6 +216,7 @@ private: std::chrono::high_resolution_clock::time_point LastCompletion {}; std::string EventName; TLuaStateId StateId; + CallStrategy Strategy; bool Expired(); void Reset(); }; @@ -218,8 +233,9 @@ private: std::recursive_mutex mLuaEventsMutex; std::vector mTimedEvents; std::recursive_mutex mTimedEventsMutex; - std::queue> mResultsToCheck; - std::recursive_mutex mResultsToCheckMutex; + std::list> mResultsToCheck; + std::mutex mResultsToCheckMutex; + std::condition_variable mResultsToCheckCond; }; // std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr arg, bool Wait); diff --git a/src/Client.cpp b/src/Client.cpp index d5736fe..4edbbc7 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -27,6 +27,7 @@ void TClient::ClearCars() { int TClient::GetOpenCarID() const { int OpenID = 0; bool found; + std::unique_lock lock(mVehicleDataMutex); do { found = true; for (auto& v : mVehicleData) { diff --git a/src/Common.cpp b/src/Common.cpp index a113f74..34b28ea 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -97,6 +97,7 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status void Application::CheckForUpdates() { Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); + static bool FirstTime = true; // checks current version against latest version std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; for (const auto& url : GetBackendUrlsInOrder()) { @@ -107,24 +108,31 @@ void Application::CheckForUpdates() { auto RemoteVersion = Version(VersionStrToInts(Response)); if (IsOutdated(MyVersion, RemoteVersion)) { std::string RealVersionString = RemoteVersion.AsString(); - beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET)); + beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION IS OUT! Please update to the new version (v" + RealVersionString + ") of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server" + std::string(ANSI_RESET)); } else { - beammp_info("Server up-to-date!"); + if (FirstTime) { + beammp_info("Server up-to-date!"); + } } Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good); break; } else { - beammp_debug("Failed to fetch version from: " + url); - beammp_trace("got " + Response); - auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetContext("get-response", { { "response", Response } }); - Sentry.LogError("failed to get server version", _file_basename, _line); - Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad); + if (FirstTime) { + beammp_debug("Failed to fetch version from: " + url); + beammp_trace("got " + Response); + auto Lock = Sentry.CreateExclusiveContext(); + Sentry.SetContext("get-response", { { "response", Response } }); + Sentry.LogError("failed to get server version", _file_basename, _line); + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad); + } } } if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) { - beammp_warn("Unable to fetch version info from backend."); + if (FirstTime) { + beammp_warn("Unable to fetch version info from backend."); + } } + FirstTime = false; } // thread name stuff diff --git a/src/Http.cpp b/src/Http.cpp index ad88fb0..1130341 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -148,7 +148,7 @@ Http::Server::THttpServerInstance::THttpServerInstance() { mThread.detach(); } -void Http::Server::THttpServerInstance::operator()() { +void Http::Server::THttpServerInstance::operator()() try { beammp_info("HTTP Server started on port " + std::to_string(Application::Settings.HTTPServerPort)); std::unique_ptr HttpLibServerInstance = std::make_unique(); // todo: make this IP agnostic so people can set their own IP @@ -184,6 +184,14 @@ void Http::Server::THttpServerInstance::operator()() { HttpLibServerInstance->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) { res.set_content(std::string(Magic), "text/plain"); }); + HttpLibServerInstance->set_logger([](const httplib::Request& Req, const httplib::Response& Res) { + beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status)); + }); Application::SetSubsystemStatus("HTTPServer", Application::Status::Good); - HttpLibServerInstance->listen("0.0.0.0", Application::Settings.HTTPServerPort); + auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort); + if (!ret) { + beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it."); + } +} catch (const std::exception& e) { + beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what())); } diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp index 14cd8f6..4a76cdc 100644 --- a/src/LuaAPI.cpp +++ b/src/LuaAPI.cpp @@ -112,7 +112,8 @@ bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, } auto c = MaybeClient.value().lock(); if (!Engine->Network().Respond(*c, Packet, true)) { - beammp_lua_error("Respond failed"); + beammp_lua_error("Respond failed, dropping client " + std::to_string(PlayerID)); + Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets"); return false; } } diff --git a/src/TConfig.cpp b/src/TConfig.cpp index ee1a936..da99cc1 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -17,12 +17,17 @@ static constexpr std::string_view StrName = "Name"; static constexpr std::string_view StrDescription = "Description"; static constexpr std::string_view StrResourceFolder = "ResourceFolder"; static constexpr std::string_view StrAuthKey = "AuthKey"; + +// Misc static constexpr std::string_view StrSendErrors = "SendErrors"; static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage"; -static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; +static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates"; // HTTP +static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; +static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL"; static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; +static constexpr std::string_view StrHTTPServerIP = "HTTPServerIP"; TConfig::TConfig(const std::string& ConfigFileName) : mConfigFileName(ConfigFileName) { @@ -56,7 +61,6 @@ void SetComment(CommentsT& Comments, const std::string& Comment) { */ void TConfig::FlushToFile() { auto data = toml::parse(mConfigFileName); - data["General"] = toml::table(); data["General"][StrAuthKey.data()] = Application::Settings.Key; SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server"); data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled; @@ -68,14 +72,20 @@ void TConfig::FlushToFile() { data["General"][StrMap.data()] = Application::Settings.MapName; data["General"][StrDescription.data()] = Application::Settings.ServerDesc; data["General"][StrResourceFolder.data()] = Application::Settings.Resource; - data["General"][StrSendErrors.data()] = Application::Settings.SendErrors; - SetComment(data["General"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here"); - data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; - SetComment(data["General"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`"); + // Misc + data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages; + SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless."); + data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors; + SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here"); + data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; + SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`"); + // HTTP data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; + SetComment(data["HTTP"][StrHTTPServerIP.data()].comments(), " Which IP to listen on. Pick 0.0.0.0 for a public-facing server with no specific IP, and 127.0.0.1 or 'localhost' for a local server."); + data["HTTP"][StrHTTPServerIP.data()] = Application::Settings.HTTPServerIP; data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled; SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server"); - std::ofstream Stream(mConfigFileName); + std::ofstream Stream(mConfigFileName, std::ios::trunc | std::ios::out); Stream << data << std::flush; } @@ -151,10 +161,13 @@ void TConfig::ParseFromFile(std::string_view name) { TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc); TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource); TryReadValue(data, "General", StrAuthKey, Application::Settings.Key); - TryReadValue(data, "General", StrSendErrors, Application::Settings.SendErrors); - TryReadValue(data, "General", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); + // Misc + TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors); + TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages); + TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); // HTTP TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort); + TryReadValue(data, "HTTP", StrHTTPServerIP, Application::Settings.HTTPServerIP); TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled); } catch (const std::exception& err) { beammp_error("Error parsing config file value: " + std::string(err.what())); @@ -190,6 +203,7 @@ void TConfig::PrintDebug() { beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\""); beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\""); beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\""); + beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\""); // special! beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + ""); } diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 79d30a4..f6eb1fa 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -97,6 +97,17 @@ void TConsole::BackupOldLog() { } } +void TConsole::StartLoggingToFile() { + mLogFileStream.open("Server.log"); + Application::Console().Internal().on_write = [this](const std::string& ToWrite) { + // TODO: Sanitize by removing all ansi escape codes (vt100) + std::unique_lock Lock(mLogFileStreamMtx); + mLogFileStream.write(ToWrite.c_str(), ToWrite.size()); + mLogFileStream.write("\n", 1); + mLogFileStream.flush(); + }; +} + void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) { if (!mIsLuaConsole) { if (!mLuaEngine) { diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 6efdd74..d65380e 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -19,7 +19,9 @@ void THeartbeatThread::operator()() { static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); bool isAuth = false; + size_t UpdateReminderCounter = 0; while (!mShutdown) { + ++UpdateReminderCounter; Body = GenerateCall(); // a hot-change occurs when a setting has changed, to update the backend of that change. auto Now = std::chrono::high_resolution_clock::now(); @@ -62,8 +64,8 @@ void THeartbeatThread::operator()() { beammp_trace(T); Doc.Parse(T.data(), T.size()); if (Doc.HasParseError() || !Doc.IsObject()) { - beammp_error("Backend response failed to parse as valid json"); - beammp_debug("Response was: `" + T + "`"); + beammp_debug("Failed to contact backend at " + Url + " (this is not an error)."); + beammp_trace("Response was: " + T); Sentry.SetContext("JSON Response", { { "reponse", T } }); SentryReportError(Url + Target, ResponseCode); } else if (ResponseCode != 200) { @@ -105,6 +107,10 @@ void THeartbeatThread::operator()() { beammp_error("Missing/invalid json members in backend response"); Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__)); } + } else { + if (!Application::Settings.Private) { + beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); + } } if (Ok && !isAuth) { @@ -124,6 +130,9 @@ void THeartbeatThread::operator()() { if (isAuth) { Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } + if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) { + Application::CheckForUpdates(); + } } } diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 41ffb96..1d684d8 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -44,7 +44,7 @@ void TLuaEngine::operator()() { CollectAndInitPlugins(); // now call all onInit's auto Futures = TriggerEvent("onInit", ""); - WaitForAll(Futures); + WaitForAll(Futures, std::chrono::seconds(5)); for (const auto& Future : Futures) { if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) { beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage); @@ -54,25 +54,21 @@ void TLuaEngine::operator()() { auto ResultCheckThread = std::thread([&] { RegisterThread("ResultCheckThread"); while (!mShutdown) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::unique_lock Lock(mResultsToCheckMutex); + mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20)); if (!mResultsToCheck.empty()) { - auto Res = mResultsToCheck.front(); - mResultsToCheck.pop(); - Lock.unlock(); - - if (!Res->Ready) { - Lock.lock(); - mResultsToCheck.push(Res); - Lock.unlock(); - } - if (Res->Error) { - if (Res->ErrorMessage != BeamMPFnNotFoundError) { - beammp_lua_error(Res->Function + ": " + Res->ErrorMessage); + mResultsToCheck.remove_if([](const std::shared_ptr& Ptr) -> bool { + if (Ptr->Ready) { + return true; + } else if (Ptr->Error) { + if (Ptr->ErrorMessage != BeamMPFnNotFoundError) { + beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage); + } + return true; } - } + return false; + }); } - std::this_thread::yield(); } }); // event loop @@ -85,21 +81,31 @@ void TLuaEngine::operator()() { std::unique_lock Lock(mTimedEventsMutex); for (auto& Timer : mTimedEvents) { if (Timer.Expired()) { + auto LastCompletionBeforeReset = Timer.LastCompletion; Timer.Reset(); auto Handlers = GetEventHandlersForState(Timer.EventName, Timer.StateId); std::unique_lock StateLock(mLuaStatesMutex); std::unique_lock Lock2(mResultsToCheckMutex); for (auto& Handler : Handlers) { - auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCall(Handler, {}); - mResultsToCheck.push(Res); + auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCallFromCustomEvent(Handler, {}, Timer.EventName, Timer.Strategy); + if (Res) { + mResultsToCheck.push_back(Res); + mResultsToCheckCond.notify_one(); + } else { + // "revert" reset + Timer.LastCompletion = LastCompletionBeforeReset; + // beammp_trace("Reverted reset of \"" + Timer.EventName + "\" timer"); + // no need to try to enqueue more handlers for this event (they will all fail) + break; + } } } } } - std::chrono::high_resolution_clock::duration Diff; - if ((Diff = std::chrono::high_resolution_clock::now() - Before) - < std::chrono::milliseconds(10)) { - std::this_thread::sleep_for(Diff); + const auto Expected = std::chrono::milliseconds(10); + if (auto Diff = std::chrono::high_resolution_clock::now() - Before; + Diff < Expected) { + std::this_thread::sleep_for(Expected - Diff); } else { beammp_trace("Event loop cannot keep up! Running " + std::to_string(Diff.count()) + "s behind"); } @@ -144,7 +150,8 @@ TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) { void TLuaEngine::AddResultToCheck(const std::shared_ptr& Result) { std::unique_lock Lock(mResultsToCheckMutex); - mResultsToCheck.push(Result); + mResultsToCheck.push_back(Result); + mResultsToCheckCond.notify_one(); } void TLuaEngine::WaitForAll(std::vector>& Results, const std::optional& Max) { @@ -155,8 +162,10 @@ void TLuaEngine::WaitForAll(std::vector>& Results, c std::this_thread::sleep_for(std::chrono::milliseconds(10)); ms += 10; if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) { - beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms)"); + beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms)."); Cancelled = true; + } else if (ms > 1000 * 60) { + beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state."); } } if (Cancelled) { @@ -174,7 +183,8 @@ void TLuaEngine::WaitForAll(std::vector>& Results, c void TLuaEngine::ReportErrors(const std::vector>& Results) { std::unique_lock Lock2(mResultsToCheckMutex); for (const auto& Result : Results) { - mResultsToCheck.push(Result); + mResultsToCheck.push_back(Result); + mResultsToCheckCond.notify_one(); } } @@ -502,11 +512,27 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi return Lua_GetPlayerIdentifiers(ID); }); MPTable.set_function("Sleep", &LuaAPI::MP::Sleep); - MPTable.set_function("CreateEventTimer", [&](const std::string& EventName, size_t IntervalMS) { + // const std::string& EventName, size_t IntervalMS, int strategy + MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) { + if (Args.size() < 2 || Args.size() > 3) { + beammp_lua_error("CreateEventTimer expects 2 or 3 arguments."); + } + if (Args.get_type(0) != sol::type::string) { + beammp_lua_error("CreateEventTimer expects 1st argument to be a string"); + } + if (Args.get_type(1) != sol::type::number) { + beammp_lua_error("CreateEventTimer expects 2nd argument to be a number"); + } + if (Args.size() == 3 && Args.get_type(2) != sol::type::number) { + beammp_lua_error("CreateEventTimer expects 3rd argument to be a number (MP.CallStrategy)"); + } + auto EventName = Args.get(0); + auto IntervalMS = Args.get(1); + CallStrategy Strategy = Args.size() > 2 ? Args.get(2) : CallStrategy::BestEffort; if (IntervalMS < 25) { beammp_warn("Timer for \"" + EventName + "\" on \"" + mStateId + "\" is set to trigger at <25ms, which is likely too fast and won't cancel properly."); } - mEngine->CreateEventTimer(EventName, mStateId, IntervalMS); + mEngine->CreateEventTimer(EventName, mStateId, IntervalMS, Strategy); }); MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) { mEngine->CancelEventTimers(EventName, mStateId); @@ -526,6 +552,10 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi "Name", 5, "Description", 6); + MPTable.create_named("CallStrategy", + "BestEffort", CallStrategy::BestEffort, + "Precise", CallStrategy::Precise); + auto FSTable = StateView.create_named_table("FS"); FSTable.set_function("CreateDirectory", &LuaAPI::FS::CreateDirectory); FSTable.set_function("Exists", &LuaAPI::FS::Exists); @@ -548,12 +578,34 @@ std::shared_ptr TLuaEngine::StateThreadData::EnqueueScript(const TLu return Result; } +std::shared_ptr TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector& Args, const std::string& EventName, CallStrategy Strategy) { + // TODO: Document all this + decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end(); + if (Strategy == CallStrategy::BestEffort) { + Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(), + [&EventName](const QueuedFunction& Element) { + return Element.EventName == EventName; + }); + } + if (Iter == mStateFunctionQueue.end()) { + auto Result = std::make_shared(); + Result->StateId = mStateId; + Result->Function = FunctionName; + std::unique_lock Lock(mStateFunctionQueueMutex); + mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName }); + mStateFunctionQueueCond.notify_all(); + return Result; + } else { + return nullptr; + } +} + std::shared_ptr TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector& Args) { auto Result = std::make_shared(); Result->StateId = mStateId; Result->Function = FunctionName; std::unique_lock Lock(mStateFunctionQueueMutex); - mStateFunctionQueue.push({ FunctionName, Result, Args }); + mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" }); mStateFunctionQueueCond.notify_all(); return Result; } @@ -616,12 +668,13 @@ void TLuaEngine::StateThreadData::operator()() { std::chrono::milliseconds(500), [&]() -> bool { return !mStateFunctionQueue.empty(); }); if (NotExpired) { - auto FnNameResultPair = std::move(mStateFunctionQueue.front()); - mStateFunctionQueue.pop(); + auto TheQueuedFunction = std::move(mStateFunctionQueue.front()); + mStateFunctionQueue.erase(mStateFunctionQueue.begin()); Lock.unlock(); - auto& FnName = std::get<0>(FnNameResultPair); - auto& Result = std::get<1>(FnNameResultPair); - auto Args = std::get<2>(FnNameResultPair); + auto& FnName = TheQueuedFunction.FunctionName; + auto& Result = TheQueuedFunction.Result; + auto Args = TheQueuedFunction.Args; + // TODO: Use TheQueuedFunction.EventName for errors, warnings, etc Result->StateId = mStateId; sol::state_view StateView(mState); auto Fn = StateView[FnName]; @@ -669,13 +722,14 @@ void TLuaEngine::StateThreadData::operator()() { } } -void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS) { +void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) { std::unique_lock Lock(mTimedEventsMutex); TimedEvent Event { std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) }, std::chrono::high_resolution_clock::now(), EventName, - StateId + StateId, + Strategy }; mTimedEvents.push_back(std::move(Event)); beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval"); diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 1e0e5be..03c90f1 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -154,6 +154,19 @@ void TNetwork::TCPServerMain() { beammp_warn(("Got an invalid client socket on connect! Skipping...")); continue; } + // set timeout (DWORD, aka uint32_t) + uint32_t SendTimeoutMS = 30 * 1000; +#if defined(BEAMMP_WINDOWS) + int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&SendTimeoutMS), sizeof(SendTimeoutMS)); +#else // POSIX + struct timeval optval; + optval.tv_sec = (int)(SendTimeoutMS / 1000); + optval.tv_usec = (SendTimeoutMS % 1000) * 1000; + int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&optval), sizeof(optval)); +#endif + if (ret < 0) { + throw std::runtime_error("setsockopt recv timeout: " + GetPlatformAgnosticErrorString()); + } std::thread ID(&TNetwork::Identify, this, client); ID.detach(); // TODO: Add to a queue and attempt to join periodically } catch (const std::exception& e) { @@ -514,6 +527,7 @@ void TNetwork::Looper(const std::weak_ptr& c) { } } } + void TNetwork::TCPClient(const std::weak_ptr& c) { // TODO: the c.expired() might cause issues here, remove if you end up here with your debugger if (c.expired() || c.lock()->GetTCPSock() == -1) { diff --git a/src/TSentry.cpp b/src/TSentry.cpp index da05c10..7f2b416 100644 --- a/src/TSentry.cpp +++ b/src/TSentry.cpp @@ -72,7 +72,7 @@ void TSentry::Log(SentryLevel level, const std::string& logger, const std::strin SetContext("threads", { { "thread-name", ThreadName(true) } }); auto Msg = sentry_value_new_message_event(sentry_level_t(level), logger.c_str(), text.c_str()); sentry_capture_event(Msg); - sentry_remove_transaction(); + sentry_set_transaction(nullptr); } void TSentry::LogError(const std::string& text, const std::string& file, const std::string& line) { diff --git a/src/main.cpp b/src/main.cpp index c43feda..f867ccd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -119,21 +119,20 @@ int BeamMPServerMain(MainArguments Arguments) { } Application::SetSubsystemStatus("Main", Application::Status::Starting); - bool Success = Application::Console().Internal().enable_write_to_file("Server.log"); - if (!Success) { - beammp_error("unable to open file for writing: \"Server.log\""); - } + + Application::Console().StartLoggingToFile(); SetupSignalHandlers(); bool Shutdown = false; Application::RegisterShutdownHandler([&Shutdown] { + beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown."); Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown); Shutdown = true; }); Application::RegisterShutdownHandler([] { auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", ""); - TLuaEngine::WaitForAll(Futures); + TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5)); }); TServer Server(Arguments.List); @@ -171,6 +170,10 @@ int BeamMPServerMain(MainArguments Arguments) { Application::SetSubsystemStatus("Main", Application::Status::Good); RegisterThread("Main(Waiting)"); + std::set IgnoreSubsystems { + "UpdateCheck" // Ignore as not to confuse users (non-vital system) + }; + bool FullyStarted = false; while (!Shutdown) { if (!FullyStarted) { @@ -179,6 +182,9 @@ int BeamMPServerMain(MainArguments Arguments) { std::string SystemsBadList {}; auto Statuses = Application::GetSubsystemStatuses(); for (const auto& NameStatusPair : Statuses) { + if (IgnoreSubsystems.count(NameStatusPair.first) > 0) { + continue; // ignore + } if (NameStatusPair.second == Application::Status::Starting) { FullyStarted = false; } else if (NameStatusPair.second == Application::Status::Bad) { diff --git a/vcpkg.cmake b/vcpkg.cmake new file mode 100644 index 0000000..56b51e5 --- /dev/null +++ b/vcpkg.cmake @@ -0,0 +1,18 @@ +include(FetchContent) + +message(STATUS "Getting, checking and running vcpkg, this may take a while") + +FetchContent_Declare( + vcpkg + GIT_REPOSITORY https://github.com/microsoft/vcpkg.git +) + +FetchContent_GetProperties(vcpkg) + +if(NOT vcpkg_POPULATED) + FetchContent_Populate(vcpkg) + execute_process(COMMAND ./${vcpkg_SOURCE_DIR}/bootstrap-vcpkg.sh) +endif() + +set(CMAKE_TOOLCHAIN_FILE ${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake +CACHE STRING "Vcpkg toolchain file") diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..f28fa5b --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "beammp-server", + "version-string": "3.0.2", + "homepage": "https://beammp.com", + "dependencies": [ + "sentry-native", + "lua", + "zlib", + "rapidjson", + "nlohmann-json", + "openssl", + "curl", + "sol2", + "cpp-httplib", + "toml11" + ] +}