mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2025-07-01 15:26:59 +00:00
Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
add0b86b37 | ||
|
403c1d5f78 | ||
|
6318ca79e7 | ||
|
2bd4ee9321 | ||
|
22c0a966bb | ||
|
731599f16e | ||
|
38c6766b2b | ||
|
bcb035bafc | ||
|
068f553fa9 | ||
|
ca11f353b0 | ||
|
b7cf304d49 | ||
|
03d3b873c4 | ||
|
ea9c808233 | ||
|
40bd050ca6 | ||
|
a0d75c01f0 | ||
|
8098431fad | ||
|
40c8c0c5c2 | ||
|
cd39f387c2 | ||
|
a5ca50866f | ||
|
10ea0cf59e | ||
|
7db40e068e | ||
|
6053aa6192 | ||
|
0bb18de9f6 | ||
|
7a439bb5b9 | ||
|
6c3174ac08 | ||
|
73e9595d14 | ||
|
3f7cf7a258 | ||
|
093310c124 | ||
|
6286457fa4 | ||
|
71b8a61c97 | ||
|
f0141e4fd3 | ||
|
00560f7646 | ||
|
27d50fc2b5 | ||
|
52a1d9a99e | ||
|
2f577a2358 | ||
|
6014536f52 | ||
|
fbce8a946e | ||
|
bd9b6212e2 | ||
|
b112ee20d8 | ||
|
8593aeb21d | ||
|
840f9b9f9d | ||
|
ec21cbbe86 | ||
|
e90f1af109 | ||
|
c78775bfd8 | ||
|
9c3042280d | ||
|
4f2ef3c3a7 | ||
|
6a2ee052ba | ||
|
2658d0f785 | ||
|
a7eeda0569 | ||
|
cd29f25435 | ||
|
7c864d94b3 | ||
|
26f1be0a51 | ||
|
d7e75ae0c7 | ||
|
1d90f53527 | ||
|
7dd6b41642 | ||
|
d7f3bc8b9f | ||
|
687a988701 | ||
|
9046b5a4d3 | ||
|
b4d4967529 | ||
|
51c24b82fe | ||
|
5e13f9dd2d | ||
|
f8d66c4336 | ||
|
9875defe86 | ||
|
fc208770dd | ||
|
99a51808a0 | ||
|
1c07cf83b2 | ||
|
99136f133a | ||
|
6c9d58582b | ||
|
4edae00998 | ||
|
17e9c05f46 | ||
|
71e3cb83ae | ||
|
fb2e26bd28 | ||
|
976ab68ca3 | ||
|
58a76e1df2 | ||
|
c696276fc3 | ||
|
6ce0608bb3 | ||
|
52ad237419 | ||
|
71038dc617 | ||
|
dc3bb517a3 | ||
|
7b2c48c8d4 | ||
|
ef557ebfc4 | ||
|
b2e953b92a | ||
|
576d765557 | ||
|
e3416804e4 | ||
|
9ad4f61209 | ||
|
aed6311146 | ||
|
5e41cefa87 | ||
|
b1710ee826 | ||
|
03b0bd4d2c | ||
|
5179ac1fdc | ||
|
54e31ce2ec | ||
|
4abe9b8636 | ||
|
956d6f50e1 | ||
|
6aeb2eb736 | ||
|
f40d4c1ddd | ||
|
4f052a3e0a | ||
|
2bf9e7d916 | ||
|
89c906232d | ||
|
c39beb5b72 | ||
|
7dd2d89ad9 | ||
|
3403c8acba | ||
|
0a6eecee69 | ||
|
cf3985ce00 | ||
|
b04c5068ea | ||
|
077bb6b1cd | ||
|
0850cde1fb | ||
|
611e53b484 | ||
|
f039f57f11 | ||
|
5d34090952 | ||
|
88ca17236a | ||
|
a4b62d013c | ||
|
9a0270cb09 | ||
|
55f1a3c734 | ||
|
bb3c762d68 | ||
|
3ade7f5743 | ||
|
9d44c2063c | ||
|
17185da53b | ||
|
623dfa17d5 | ||
|
caafb216c9 |
6
.github/pull_request_template.md
vendored
Normal file
6
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
Please replace this text <-> with your PR description and leave the below declarations intact.
|
||||
|
||||
---
|
||||
|
||||
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.
|
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -84,7 +84,7 @@ jobs:
|
||||
run: ./bin/BeamMP-Server-tests
|
||||
|
||||
arm64-matrix:
|
||||
runs-on: [Linux, ARM64]
|
||||
runs-on: ubuntu-22.04-arm
|
||||
env:
|
||||
VCPKG_DEFAULT_TRIPLET: "arm64-linux"
|
||||
strategy:
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -104,7 +104,7 @@ jobs:
|
||||
asset_content_type: application/x-elf
|
||||
|
||||
arm64-matrix:
|
||||
runs-on: [Linux, ARM64]
|
||||
runs-on: ubuntu-22.04-arm
|
||||
needs: create-release
|
||||
strategy:
|
||||
matrix:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
.idea/
|
||||
.sentry-native/
|
||||
*.orig
|
||||
*.toml
|
||||
boost_*
|
||||
|
@ -104,6 +104,7 @@ set(PRJ_LIBRARIES
|
||||
httplib::httplib
|
||||
libzip::zip
|
||||
OpenSSL::SSL OpenSSL::Crypto
|
||||
CURL::libcurl
|
||||
${LUA_LIBRARIES}
|
||||
)
|
||||
|
||||
@ -116,6 +117,7 @@ find_package(httplib CONFIG REQUIRED)
|
||||
find_package(libzip CONFIG REQUIRED)
|
||||
find_package(RapidJSON CONFIG REQUIRED)
|
||||
find_package(sol2 CONFIG REQUIRED)
|
||||
find_package(CURL CONFIG REQUIRED)
|
||||
add_subdirectory("deps/toml11")
|
||||
|
||||
include_directories(include)
|
||||
|
@ -108,8 +108,8 @@ A BeamMP-Developer must review your code in detail, and leave a review. If this
|
||||
5. Run `git submodule update --init --recursive`.
|
||||
6. Make a new branch for your feature or fix from the branch you are on. You can do this via `git checkout -b <branch name>`. See [this section on branches](#branches) for more info on branch naming.
|
||||
7. Install all dependencies. Those are usually listed in the `README.md` in the branch you're in, or, more reliably, in one of the files in `.github/workflows` (if you can read `yaml`).
|
||||
8. Run CMake to configure the project. You can find tutorials on this online. You will want to tell CMake to build with `CMAKE_BUILD_TYPE=Debug`, for example by passing it to CMake via the commandline switch `-DCMAKE_BUILD_TYPE=Debug`. You may also want to turn off sentry by setting `SENTRY_BACKEND=none` (for example via commandline switch `-DSENTRY_BACKEND=none`). An example invocation on Linux with GNU make would be
|
||||
`cmake . -DCMAKE_BUILD_TYPE=Debug -DSENTRY_BACKEND=none` .
|
||||
8. Run CMake to configure the project. You can find tutorials on this online. You will want to tell CMake to build with `CMAKE_BUILD_TYPE=Debug`, for example by passing it to CMake via the commandline switch `-DCMAKE_BUILD_TYPE=Debug`. An example invocation on Linux with GNU make would be
|
||||
`cmake . -DCMAKE_BUILD_TYPE=Debug` .
|
||||
9. Build the `BeamMP-Server` target to build the BeamMP-Server, or the `BeamMP-Server-tests` target to build the unit-tests (does not include a working server). In the example from 8. (on Linux), you could build with `make BeamMP-Server`, `make -j BeamMP-Server` or `cmake --build . --parallel --target BeamMP-Server` . Or, on Windows, (in Visual Studio), you would just press some big green "run" or "debug" button.
|
||||
10. When making changes, refer to [this section on how to commit properly](#how-to-commit). Not following those guidelines will result in your changes being rejected, so please take a look.
|
||||
11. Make sure to add Unit-tests with `doctest` if you build new stuff. You can find examples all over the latest version of the codebase (search for `TEST_CASE`).
|
||||
|
@ -38,7 +38,7 @@ If you need support with understanding the codebase, please write us in the Disc
|
||||
|
||||
## About Building from Source
|
||||
|
||||
We only allow building unmodified (original) source code for public use. `master` is considered **unstable** and we will not provide technical support if such a build doesn't work, so always build from a tag. You can checkout a tag with `git checkout tags/TAGNAME`, where `TAGNAME` is the tag, for example `v3.1.0`.
|
||||
We only allow building unmodified (original) source code for public use. `master` is considered **unstable** and we will not provide technical support if such a build doesn't work, so always build from a tag. You can checkout a tag with `git checkout tags/TAGNAME`, where `TAGNAME` is the tag, for example `v3.4.1`. See [the tags](https://github.com/BeamMP/BeamMP-Server/tags) for possible versions/tags, as well as [the releases](https://github.com/BeamMP/BeamMP-Server/releases) to check which version is marked as a release/prerelease. We recommend using the [latest release](https://github.com/BeamMP/BeamMP-Server/releases/latest).
|
||||
|
||||
## Supported Operating Systems
|
||||
|
||||
@ -66,9 +66,10 @@ You can build on **Windows, Linux** or other platforms by following these steps:
|
||||
|
||||
1. Check out the repository with git: `git clone --recursive https://github.com/BeamMP/BeamMP-Server`.
|
||||
2. Go into the directory `cd BeamMP-Server`.
|
||||
3. Run CMake `cmake -S . -B bin -DCMAKE_BUILD_TYPE=Release` - this can take a few minutes and may take a lot of disk space and bandwidth.
|
||||
4. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`.
|
||||
5. Your executable can be found in `bin/`.
|
||||
3. Specify the server version to build by checking out a tag: `git checkout tags/v3.4.1` - [Possible versions/tags](https://github.com/BeamMP/BeamMP-Server/tags)
|
||||
4. Run CMake `cmake -S . -B bin -DCMAKE_BUILD_TYPE=Release` - this can take a few minutes and may take a lot of disk space and bandwidth.
|
||||
5. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`.
|
||||
6. Your executable can be found in `bin/`.
|
||||
|
||||
When you make changes to the code, you only have to run step 4 again.
|
||||
### Building for FreeBSD
|
||||
|
@ -56,17 +56,16 @@ public:
|
||||
~TClient();
|
||||
TClient& operator=(const TClient&) = delete;
|
||||
|
||||
void AddNewCar(int Ident, const std::string& Data);
|
||||
void SetCarData(int Ident, const std::string& Data);
|
||||
void AddNewCar(int Ident, const nlohmann::json& Data);
|
||||
void SetCarData(int Ident, const nlohmann::json& Data);
|
||||
void SetCarPosition(int Ident, const std::string& Data);
|
||||
TVehicleDataLockPair GetAllCars();
|
||||
void SetName(const std::string& Name) { mName = Name; }
|
||||
void SetRoles(const std::string& Role) { mRole = Role; }
|
||||
void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; }
|
||||
std::string GetCarData(int Ident);
|
||||
nlohmann::json GetCarData(int Ident);
|
||||
std::string GetCarPositionRaw(int Ident);
|
||||
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; }
|
||||
void SetDownSock(ip::tcp::socket&& CSock) { mDownSocket = std::move(CSock); }
|
||||
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
|
||||
void Disconnect(std::string_view Reason);
|
||||
bool IsDisconnected() const { return !mSocket.is_open(); }
|
||||
@ -75,8 +74,6 @@ public:
|
||||
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
|
||||
[[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; }
|
||||
[[nodiscard]] ip::udp::endpoint& GetUDPAddr() { return mUDPAddress; }
|
||||
[[nodiscard]] ip::tcp::socket& GetDownSock() { return mDownSocket; }
|
||||
[[nodiscard]] const ip::tcp::socket& GetDownSock() const { return mDownSocket; }
|
||||
[[nodiscard]] ip::tcp::socket& GetTCPSock() { return mSocket; }
|
||||
[[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; }
|
||||
[[nodiscard]] std::string GetRoles() const { return mRole; }
|
||||
@ -122,7 +119,6 @@ private:
|
||||
SparseArray<std::string> mVehiclePosition;
|
||||
std::string mName = "Unknown Client";
|
||||
ip::tcp::socket mSocket;
|
||||
ip::tcp::socket mDownSocket;
|
||||
ip::udp::endpoint mUDPAddress {};
|
||||
int mUnicycleID = -1;
|
||||
std::string mRole;
|
||||
|
@ -74,7 +74,7 @@ public:
|
||||
static TConsole& Console() { return mConsole; }
|
||||
static std::string ServerVersionString();
|
||||
static const Version& ServerVersion() { return mVersion; }
|
||||
static uint8_t ClientMajorVersion() { return 2; }
|
||||
static Version ClientMinimumVersion() { return Version { 2, 2, 0 }; }
|
||||
static std::string PPS() { return mPPS; }
|
||||
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
|
||||
|
||||
@ -82,11 +82,13 @@ public:
|
||||
|
||||
static std::vector<std::string> GetBackendUrlsInOrder() {
|
||||
return {
|
||||
"backend.beammp.com",
|
||||
"https://backend.beammp.com",
|
||||
};
|
||||
}
|
||||
|
||||
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
|
||||
static std::string GetServerCheckUrl() { return "https://check.beammp.com"; }
|
||||
|
||||
static std::string GetBackendUrlForAuth() { return "https://auth.beammp.com"; }
|
||||
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
|
||||
static void CheckForUpdates();
|
||||
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
|
||||
@ -127,10 +129,11 @@ private:
|
||||
static inline std::mutex mShutdownHandlersMutex {};
|
||||
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
|
||||
|
||||
static inline Version mVersion { 3, 5, 1 };
|
||||
static inline Version mVersion { 3, 8, 4 };
|
||||
};
|
||||
|
||||
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
|
||||
std::string LowerString(std::string str);
|
||||
|
||||
std::string ThreadName(bool DebugModeOverride = false);
|
||||
void RegisterThread(const std::string& str);
|
||||
@ -139,7 +142,6 @@ void RegisterThread(const std::string& str);
|
||||
#define KB 1024llu
|
||||
#define MB (KB * 1024llu)
|
||||
#define GB (MB * 1024llu)
|
||||
#define SSU_UNRAW SECRET_SENTRY_URL
|
||||
|
||||
#define _file_basename std::filesystem::path(__FILE__).filename().string()
|
||||
#define _line std::to_string(__LINE__)
|
||||
@ -162,13 +164,13 @@ void RegisterThread(const std::string& str);
|
||||
#else
|
||||
#define _function_name std::string(__func__)
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define DEBUG
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
|
||||
// if this is defined, we will show the full function signature infront of
|
||||
// each info/debug/warn... call instead of the 'filename:line' format.
|
||||
#if defined(BMP_FULL_FUNCTION_NAMES)
|
||||
@ -178,7 +180,7 @@ void RegisterThread(const std::string& str);
|
||||
#endif
|
||||
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
|
||||
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
|
||||
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
|
||||
#define beammp_error(x) \
|
||||
@ -221,7 +223,7 @@ void RegisterThread(const std::string& str);
|
||||
#else
|
||||
#define beammp_trace(x)
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
|
||||
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
|
||||
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
|
||||
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
|
||||
|
@ -27,6 +27,7 @@ enum class Key {
|
||||
PROVIDER_UPDATE_MESSAGE,
|
||||
PROVIDER_DISABLE_CONFIG,
|
||||
PROVIDER_PORT_ENV,
|
||||
PROVIDER_IP_ENV
|
||||
};
|
||||
|
||||
std::optional<std::string> Get(Key key);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#if defined(BEAMMP_LINUX)
|
||||
#pragma GCC diagnostic push
|
||||
@ -38,8 +39,8 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Http {
|
||||
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
|
||||
std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {});
|
||||
std::string GET(const std::string& url, unsigned int* status = nullptr);
|
||||
std::string POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const std::map<std::string, std::string>& headers = {});
|
||||
namespace Status {
|
||||
std::string ToString(int code);
|
||||
}
|
||||
|
@ -35,8 +35,11 @@ namespace MP {
|
||||
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
|
||||
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
|
||||
std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message);
|
||||
std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category);
|
||||
std::pair<bool, std::string> ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning = false, const bool& reportToServer = true, const bool& reportToExtensions = true);
|
||||
std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
|
||||
void Set(int ConfigID, sol::object NewValue);
|
||||
TLuaValue Get(int ConfigID);
|
||||
bool IsPlayerGuest(int ID);
|
||||
bool IsPlayerConnected(int ID);
|
||||
void Sleep(size_t Ms);
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
namespace prof {
|
||||
|
||||
using Duration = std::chrono::duration<double, std::milli>;
|
||||
|
@ -68,8 +68,6 @@ struct Settings {
|
||||
// Keys have their TOML section name as prefix
|
||||
|
||||
// [Misc]
|
||||
Misc_SendErrorsShowMessage,
|
||||
Misc_SendErrors,
|
||||
Misc_ImScaredOfUpdates,
|
||||
Misc_UpdateReminderTime,
|
||||
|
||||
@ -81,12 +79,14 @@ struct Settings {
|
||||
General_Map,
|
||||
General_AuthKey,
|
||||
General_Private,
|
||||
General_IP,
|
||||
General_Port,
|
||||
General_MaxCars,
|
||||
General_LogChat,
|
||||
General_ResourceFolder,
|
||||
General_Debug,
|
||||
General_AllowGuests
|
||||
General_AllowGuests,
|
||||
General_InformationPacket,
|
||||
};
|
||||
|
||||
Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
|
||||
|
@ -59,6 +59,9 @@ private:
|
||||
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_Clear(const std::string&, const std::vector<std::string>& args);
|
||||
void Command_Version(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args);
|
||||
void Command_NetTest(const std::string& cmd, const std::vector<std::string>& args);
|
||||
|
||||
void Command_Say(const std::string& FullCommand);
|
||||
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
|
||||
@ -77,6 +80,9 @@ private:
|
||||
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
|
||||
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
|
||||
{ "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
|
||||
{ "protectmod", [this](const auto& a, const auto& b) { Command_ProtectMod(a, b); } },
|
||||
{ "reloadmods", [this](const auto& a, const auto& b) { Command_ReloadMods(a, b); } },
|
||||
{ "nettest", [this](const auto& a, const auto& b) { Command_NetTest(a, b); } },
|
||||
};
|
||||
|
||||
std::unique_ptr<Commandline> mCommandline { nullptr };
|
||||
|
@ -29,6 +29,7 @@ public:
|
||||
//~THeartbeatThread();
|
||||
void operator()() override;
|
||||
|
||||
static inline std::string lastCall = "";
|
||||
private:
|
||||
std::string GenerateCall();
|
||||
std::string GetPlayers();
|
||||
|
@ -263,6 +263,7 @@ private:
|
||||
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
||||
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
||||
sol::table Lua_GetPlayerIdentifiers(int ID);
|
||||
std::variant<std::string, sol::nil_t> Lua_GetPlayerRole(int ID);
|
||||
sol::table Lua_GetPlayers();
|
||||
std::string Lua_GetPlayerName(int ID);
|
||||
sol::table Lua_GetPlayerVehicles(int ID);
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel);
|
||||
void UpdatePlayer(TClient& Client);
|
||||
|
||||
TResourceManager& ResourceManager() const { return mResourceManager; }
|
||||
|
||||
private:
|
||||
void UDPServerMain();
|
||||
void TCPServerMain();
|
||||
@ -58,7 +60,6 @@ private:
|
||||
std::mutex mOpenIDMutex;
|
||||
|
||||
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
||||
void HandleDownload(TConnection&& TCPSock);
|
||||
void OnConnect(const std::weak_ptr<TClient>& c);
|
||||
void TCPClient(const std::weak_ptr<TClient>& c);
|
||||
void Looper(const std::weak_ptr<TClient>& c);
|
||||
@ -67,7 +68,7 @@ private:
|
||||
void Parse(TClient& c, const std::vector<uint8_t>& Packet);
|
||||
void SendFile(TClient& c, const std::string& Name);
|
||||
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
|
||||
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
|
||||
static void SendFileToClient(TClient& c, size_t Size, const std::string& Name);
|
||||
static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
class TResourceManager {
|
||||
public:
|
||||
@ -29,6 +30,10 @@ public:
|
||||
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
|
||||
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
|
||||
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
|
||||
[[nodiscard]] nlohmann::json GetMods() const { return mMods; }
|
||||
|
||||
void RefreshFiles();
|
||||
void SetProtected(const std::string& ModName, bool Protected);
|
||||
|
||||
private:
|
||||
size_t mMaxModSize = 0;
|
||||
@ -36,4 +41,7 @@ private:
|
||||
std::string mFileList;
|
||||
std::string mTrimmedList;
|
||||
int mModsLoaded = 0;
|
||||
|
||||
std::mutex mModsMutex;
|
||||
nlohmann::json mMods = nlohmann::json::array();
|
||||
};
|
||||
|
@ -18,11 +18,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
class TVehicleData final {
|
||||
public:
|
||||
TVehicleData(int ID, std::string Data);
|
||||
TVehicleData(int ID, nlohmann::json Data);
|
||||
~TVehicleData();
|
||||
// We cannot delete this, since vector needs to be able to copy when it resizes.
|
||||
// Deleting this causes some wacky template errors which are hard to decipher,
|
||||
@ -32,14 +33,16 @@ public:
|
||||
[[nodiscard]] bool IsInvalid() const { return mID == -1; }
|
||||
[[nodiscard]] int ID() const { return mID; }
|
||||
|
||||
[[nodiscard]] std::string Data() const { return mData; }
|
||||
void SetData(const std::string& Data) { mData = Data; }
|
||||
[[nodiscard]] nlohmann::json Data() const { return mData; }
|
||||
[[nodiscard]] std::string DataAsPacket(const std::string& Role, const std::string& Name, int ID) const;
|
||||
|
||||
void SetData(const nlohmann::json& Data) { mData = Data; }
|
||||
|
||||
bool operator==(const TVehicleData& v) const { return mID == v.mID; }
|
||||
|
||||
private:
|
||||
int mID { -1 };
|
||||
std::string mData;
|
||||
nlohmann::json mData;
|
||||
};
|
||||
|
||||
// TODO: unused now, remove?
|
||||
|
@ -57,7 +57,7 @@ int TClient::GetOpenCarID() const {
|
||||
return OpenID;
|
||||
}
|
||||
|
||||
void TClient::AddNewCar(int Ident, const std::string& Data) {
|
||||
void TClient::AddNewCar(int Ident, const nlohmann::json& Data) {
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
mVehicleData.emplace_back(Ident, Data);
|
||||
}
|
||||
@ -79,13 +79,17 @@ std::string TClient::GetCarPositionRaw(int Ident) {
|
||||
void TClient::Disconnect(std::string_view Reason) {
|
||||
beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason);
|
||||
boost::system::error_code ec;
|
||||
mSocket.shutdown(socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
beammp_debugf("Failed to shutdown client socket: {}", ec.message());
|
||||
}
|
||||
mSocket.close(ec);
|
||||
if (ec) {
|
||||
beammp_debugf("Failed to close client socket: {}", ec.message());
|
||||
if (mSocket.is_open()) {
|
||||
mSocket.shutdown(socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
beammp_debugf("Failed to shutdown client socket: {}", ec.message());
|
||||
}
|
||||
mSocket.close(ec);
|
||||
if (ec) {
|
||||
beammp_debugf("Failed to close client socket: {}", ec.message());
|
||||
}
|
||||
} else {
|
||||
beammp_debug("Socket is already closed.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +98,7 @@ void TClient::SetCarPosition(int Ident, const std::string& Data) {
|
||||
mVehiclePosition[size_t(Ident)] = Data;
|
||||
}
|
||||
|
||||
std::string TClient::GetCarData(int Ident) {
|
||||
nlohmann::json TClient::GetCarData(int Ident) {
|
||||
{ // lock
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
for (auto& v : mVehicleData) {
|
||||
@ -104,10 +108,10 @@ std::string TClient::GetCarData(int Ident) {
|
||||
}
|
||||
} // unlock
|
||||
DeleteCar(Ident);
|
||||
return "";
|
||||
return nlohmann::detail::value_t::null;
|
||||
}
|
||||
|
||||
void TClient::SetCarData(int Ident, const std::string& Data) {
|
||||
void TClient::SetCarData(int Ident, const nlohmann::json& Data) {
|
||||
{ // lock
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
for (auto& v : mVehicleData) {
|
||||
@ -144,7 +148,6 @@ void TClient::EnqueuePacket(const std::vector<uint8_t>& Packet) {
|
||||
TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
|
||||
: mServer(Server)
|
||||
, mSocket(std::move(Socket))
|
||||
, mDownSocket(ip::tcp::socket(Server.IoCtx()))
|
||||
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ void Application::CheckForUpdates() {
|
||||
// checks current version against latest version
|
||||
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
|
||||
for (const auto& url : GetBackendUrlsInOrder()) {
|
||||
auto Response = Http::GET(url, 443, "/v/s");
|
||||
auto Response = Http::GET(url + "/v/s");
|
||||
bool Matches = std::regex_match(Response, VersionRegex);
|
||||
if (Matches) {
|
||||
auto MyVersion = ServerVersion();
|
||||
@ -384,6 +384,13 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
|
||||
out.push_back(str.substr(start, end - start));
|
||||
}
|
||||
}
|
||||
|
||||
std::string LowerString(std::string str) {
|
||||
std::ranges::transform(str, str.begin(), ::tolower);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
static constexpr size_t STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024;
|
||||
static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024;
|
||||
|
||||
|
@ -39,6 +39,9 @@ std::string_view Env::ToString(Env::Key key) {
|
||||
case Key::PROVIDER_PORT_ENV:
|
||||
return "BEAMMP_PROVIDER_PORT_ENV";
|
||||
break;
|
||||
case Key::PROVIDER_IP_ENV:
|
||||
return "BEAMMP_PROVIDER_IP_ENV";
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
117
src/Http.cpp
117
src/Http.cpp
@ -29,67 +29,88 @@
|
||||
#include <stdexcept>
|
||||
|
||||
using json = nlohmann::json;
|
||||
struct Connection {
|
||||
std::string host {};
|
||||
int port {};
|
||||
Connection() = default;
|
||||
Connection(std::string host, int port)
|
||||
: host(host)
|
||||
, port(port) {};
|
||||
};
|
||||
constexpr uint8_t CONNECTION_AMOUNT = 10;
|
||||
static thread_local uint8_t write_index = 0;
|
||||
static thread_local std::array<Connection, CONNECTION_AMOUNT> connections;
|
||||
static thread_local std::array<std::shared_ptr<httplib::SSLClient>, CONNECTION_AMOUNT> clients;
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<httplib::SSLClient> getClient(Connection connectionInfo) {
|
||||
for (uint8_t i = 0; i < CONNECTION_AMOUNT; i++) {
|
||||
if (connectionInfo.host == connections[i].host
|
||||
&& connectionInfo.port == connections[i].port) {
|
||||
beammp_tracef("Old client reconnected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||
return clients[i];
|
||||
}
|
||||
}
|
||||
uint8_t i = write_index;
|
||||
write_index++;
|
||||
write_index %= CONNECTION_AMOUNT;
|
||||
clients[i] = std::make_shared<httplib::SSLClient>(connectionInfo.host, connectionInfo.port);
|
||||
connections[i] = { connectionInfo.host, connectionInfo.port };
|
||||
beammp_tracef("New client connected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||
return clients[i];
|
||||
static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
std::string* Result = reinterpret_cast<std::string*>(userp);
|
||||
std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
|
||||
*Result += NewContents;
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
|
||||
std::shared_ptr<httplib::SSLClient> client = getClient({ host, port });
|
||||
client->enable_server_certificate_verification(false);
|
||||
client->set_address_family(AF_INET);
|
||||
auto res = client->Get(target.c_str());
|
||||
if (res) {
|
||||
if (status) {
|
||||
*status = res->status;
|
||||
std::string Http::GET(const std::string& url, unsigned int* status) {
|
||||
std::string Ret;
|
||||
static thread_local CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||
errbuf[0] = 0;
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("GET to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
return Http::ErrorString;
|
||||
}
|
||||
return res->body;
|
||||
|
||||
if (status) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
|
||||
}
|
||||
|
||||
} else {
|
||||
beammp_error("Curl easy init failed");
|
||||
return Http::ErrorString;
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
|
||||
std::shared_ptr<httplib::SSLClient> client = getClient({ host, port });
|
||||
client->set_read_timeout(std::chrono::seconds(10));
|
||||
beammp_assert(client->is_valid());
|
||||
client->enable_server_certificate_verification(false);
|
||||
client->set_address_family(AF_INET);
|
||||
auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
|
||||
if (res) {
|
||||
if (status) {
|
||||
*status = res->status;
|
||||
std::string Http::POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers) {
|
||||
std::string Ret;
|
||||
static thread_local CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
CURLcode res;
|
||||
char errbuf[CURL_ERROR_SIZE];
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
|
||||
struct curl_slist* list = nullptr;
|
||||
list = curl_slist_append(list, ("Content-Type: " + ContentType).c_str());
|
||||
|
||||
for (auto [header, value] : headers) {
|
||||
list = curl_slist_append(list, (header + ": " + value).c_str());
|
||||
}
|
||||
return res->body;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||
errbuf[0] = 0;
|
||||
res = curl_easy_perform(curl);
|
||||
curl_slist_free_all(list);
|
||||
if (res != CURLE_OK) {
|
||||
beammp_error("POST to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
||||
beammp_error("Curl error: " + std::string(errbuf));
|
||||
return Http::ErrorString;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
|
||||
}
|
||||
|
||||
} else {
|
||||
beammp_debug("POST failed: " + httplib::to_string(res.error()));
|
||||
beammp_error("Curl easy init failed");
|
||||
return Http::ErrorString;
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
// RFC 2616, RFC 7231
|
||||
|
112
src/LuaAPI.cpp
112
src/LuaAPI.cpp
@ -148,6 +148,11 @@ static inline std::pair<bool, std::string> InternalTriggerClientEvent(int Player
|
||||
return { false, "Invalid Player ID" };
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
|
||||
if (!c->IsSyncing() && !c->IsSynced()) {
|
||||
return { false, "Player hasn't joined yet" };
|
||||
}
|
||||
|
||||
if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
||||
beammp_lua_errorf("Respond failed, dropping client {}", PlayerID);
|
||||
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
|
||||
@ -205,6 +210,79 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category) {
|
||||
std::pair<bool, std::string> Result;
|
||||
std::string Packet = "n" + Category + ":" + Icon + ":" + Message;
|
||||
if (ID == -1) {
|
||||
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||
Result.first = true;
|
||||
} else {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (MaybeClient) {
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->IsSynced()) {
|
||||
Result.first = false;
|
||||
Result.second = "Player is not synced yet";
|
||||
return Result;
|
||||
}
|
||||
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
||||
beammp_errorf("Failed to send notification to player (id {}) - did the player disconnect?", ID);
|
||||
Result.first = false;
|
||||
Result.second = "Failed to send packet";
|
||||
}
|
||||
Result.first = true;
|
||||
} else {
|
||||
beammp_lua_error("SendNotification invalid argument [1] invalid ID");
|
||||
Result.first = false;
|
||||
Result.second = "Invalid Player ID";
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> LuaAPI::MP::ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning, const bool& reportToServer, const bool& reportToExtensions) {
|
||||
std::pair<bool, std::string> Result;
|
||||
|
||||
const nlohmann::json PacketBody = {
|
||||
{ "title", Title },
|
||||
{ "body", Body },
|
||||
{ "buttons", nlohmann::json::parse(JsonEncode(buttons), nullptr, false) },
|
||||
{ "interactionID", InteractionID },
|
||||
{ "class", warning ? "experimental" : "" },
|
||||
{ "reportToServer", reportToServer },
|
||||
{ "reportToExtensions", reportToExtensions }
|
||||
};
|
||||
|
||||
std::string Packet = "D" + PacketBody.dump();
|
||||
if (ID == -1) {
|
||||
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||
Result.first = true;
|
||||
} else {
|
||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||
if (MaybeClient) {
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->IsSynced()) {
|
||||
Result.first = false;
|
||||
Result.second = "Player is not synced yet";
|
||||
return Result;
|
||||
}
|
||||
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
||||
beammp_errorf("Failed to send confirmation dialog to player (id {}) - did the player disconnect?", ID);
|
||||
Result.first = false;
|
||||
Result.second = "Failed to send packet";
|
||||
}
|
||||
Result.first = true;
|
||||
} else {
|
||||
beammp_lua_error("ConfirmationDialog invalid argument [1] invalid ID");
|
||||
Result.first = false;
|
||||
Result.second = "Invalid Player ID";
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
|
||||
std::pair<bool, std::string> Result;
|
||||
auto MaybeClient = GetClient(Engine->Server(), PID);
|
||||
@ -215,7 +293,7 @@ std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
|
||||
return Result;
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->GetCarData(VID).empty()) {
|
||||
if (c->GetCarData(VID) != nlohmann::detail::value_t::null) {
|
||||
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID));
|
||||
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||
@ -286,12 +364,44 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
|
||||
beammp_lua_error("set invalid argument [2] expected string");
|
||||
}
|
||||
break;
|
||||
case 7: // Information packet
|
||||
if (NewValue.is<bool>()) {
|
||||
Application::Settings.set(Settings::Key::General_InformationPacket, NewValue.as<bool>());
|
||||
beammp_info(std::string("Set `InformationPacket` to ") + (Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
|
||||
} else {
|
||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TLuaValue LuaAPI::MP::Get(int ConfigID) {
|
||||
switch (ConfigID) {
|
||||
case 0: // debug
|
||||
return Application::Settings.getAsBool(Settings::Key::General_Debug);
|
||||
case 1: // private
|
||||
return Application::Settings.getAsBool(Settings::Key::General_Private);
|
||||
case 2: // max cars
|
||||
return Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
||||
case 3: // max players
|
||||
return Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
|
||||
case 4: // Map
|
||||
return Application::Settings.getAsString(Settings::Key::General_Map);
|
||||
case 5: // Name
|
||||
return Application::Settings.getAsString(Settings::Key::General_Name);
|
||||
case 6: // Desc
|
||||
return Application::Settings.getAsString(Settings::Key::General_Description);
|
||||
case 7: // Information packet
|
||||
return Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
|
||||
default:
|
||||
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void LuaAPI::MP::Sleep(size_t Ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
|
||||
}
|
||||
|
@ -28,14 +28,14 @@ Settings::Settings() {
|
||||
{ General_Map, std::string("/levels/gridmap_v2/info.json") },
|
||||
{ General_AuthKey, std::string("") },
|
||||
{ General_Private, true },
|
||||
{ General_IP, "::"},
|
||||
{ General_Port, 30814 },
|
||||
{ General_MaxCars, 1 },
|
||||
{ General_LogChat, true },
|
||||
{ General_ResourceFolder, std::string("Resources") },
|
||||
{ General_Debug, false },
|
||||
{ General_AllowGuests, true },
|
||||
{ Misc_SendErrorsShowMessage, true },
|
||||
{ Misc_SendErrors, true },
|
||||
{ General_InformationPacket, true },
|
||||
{ Misc_ImScaredOfUpdates, true },
|
||||
{ Misc_UpdateReminderTime, "30s" }
|
||||
};
|
||||
@ -48,14 +48,14 @@ Settings::Settings() {
|
||||
{ { "General", "Map" }, { General_Map, READ_WRITE } },
|
||||
{ { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } },
|
||||
{ { "General", "Private" }, { General_Private, READ_ONLY } },
|
||||
{ { "General", "IP" }, { General_IP, READ_ONLY } },
|
||||
{ { "General", "Port" }, { General_Port, READ_ONLY } },
|
||||
{ { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } },
|
||||
{ { "General", "LogChat" }, { General_LogChat, READ_ONLY } },
|
||||
{ { "General", "ResourceFolder" }, { General_ResourceFolder, READ_ONLY } },
|
||||
{ { "General", "Debug" }, { General_Debug, READ_WRITE } },
|
||||
{ { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
|
||||
{ { "Misc", "SendErrorsShowMessage" }, { Misc_SendErrorsShowMessage, READ_WRITE } },
|
||||
{ { "Misc", "SendErrors" }, { Misc_SendErrors, READ_WRITE } },
|
||||
{ { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } },
|
||||
{ { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
|
||||
{ { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } }
|
||||
};
|
||||
|
@ -34,6 +34,8 @@ static constexpr std::string_view StrDebug = "Debug";
|
||||
static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG";
|
||||
static constexpr std::string_view StrPrivate = "Private";
|
||||
static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE";
|
||||
static constexpr std::string_view StrIP = "IP";
|
||||
static constexpr std::string_view EnvStrIP = "BEAMMP_IP";
|
||||
static constexpr std::string_view StrPort = "Port";
|
||||
static constexpr std::string_view EnvStrPort = "BEAMMP_PORT";
|
||||
static constexpr std::string_view StrMaxCars = "MaxCars";
|
||||
@ -56,13 +58,15 @@ static constexpr std::string_view StrLogChat = "LogChat";
|
||||
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
|
||||
static constexpr std::string_view StrAllowGuests = "AllowGuests";
|
||||
static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
|
||||
static constexpr std::string_view StrInformationPacket = "InformationPacket";
|
||||
static constexpr std::string_view EnvStrInformationPacket = "BEAMMP_INFORMATION_PACKET";
|
||||
static constexpr std::string_view StrPassword = "Password";
|
||||
|
||||
// Misc
|
||||
static constexpr std::string_view StrSendErrors = "SendErrors";
|
||||
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
|
||||
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
|
||||
static constexpr std::string_view EnvStrHideUpdateMessages = "BEAMMP_IM_SCARED_OF_UPDATES";
|
||||
static constexpr std::string_view StrUpdateReminderTime = "UpdateReminderTime";
|
||||
static constexpr std::string_view EnvStrUpdateReminderTime = "BEAMMP_UPDATE_REMINDER_TIME";
|
||||
|
||||
TEST_CASE("TConfig::TConfig") {
|
||||
const std::string CfgFile = "beammp_server_testconfig.toml";
|
||||
@ -132,8 +136,12 @@ void TConfig::FlushToFile() {
|
||||
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
|
||||
data["General"][StrDebug.data()] = Application::Settings.getAsBool(Settings::Key::General_Debug);
|
||||
data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private);
|
||||
SetComment(data["General"][StrInformationPacket.data()].comments(), " Whether to allow unconnected clients to get the public server information without joining");
|
||||
data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
|
||||
data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
|
||||
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
|
||||
data["General"][StrIP.data()] = Application::Settings.getAsString(Settings::Key::General_IP);
|
||||
SetComment(data["General"][StrIP.data()].comments(), " The IP address to bind the server to, this is NOT related to your public IP. Can be used if your machine has multiple network interfaces");
|
||||
data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
|
||||
data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name);
|
||||
SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation.");
|
||||
@ -148,12 +156,8 @@ void TConfig::FlushToFile() {
|
||||
// Misc
|
||||
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates);
|
||||
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.getAsBool(Settings::Key::Misc_SendErrors);
|
||||
data["Misc"][StrUpdateReminderTime.data()] = Application::Settings.getAsString(Settings::Key::Misc_UpdateReminderTime);
|
||||
SetComment(data["Misc"][StrUpdateReminderTime.data()].comments(), " Specifies the time between update reminders. You can use any of \"s, min, h, d\" at the end to specify the units seconds, minutes, hours or days. So 30d or 0.5min will print the update message every 30 days or half a minute.");
|
||||
SetComment(data["Misc"][StrSendErrors.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`");
|
||||
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.getAsBool(Settings::Key::Misc_SendErrorsShowMessage);
|
||||
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
|
||||
std::stringstream Ss;
|
||||
Ss << "# This is the BeamMP-Server config file.\n"
|
||||
"# Help & Documentation: `https://docs.beammp.com/server/server-maintenance/`\n"
|
||||
@ -248,7 +252,17 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
// Read into new Settings Singleton
|
||||
TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug);
|
||||
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private);
|
||||
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
|
||||
TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket);
|
||||
if (Env::Get(Env::Key::PROVIDER_PORT_ENV).has_value()) {
|
||||
TryReadValue(data, "General", StrPort, Env::Get(Env::Key::PROVIDER_PORT_ENV).value(), Settings::Key::General_Port);
|
||||
} else {
|
||||
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
|
||||
}
|
||||
if (Env::Get(Env::Key::PROVIDER_IP_ENV).has_value()) {
|
||||
TryReadValue(data, "General", StrIP, Env::Get(Env::Key::PROVIDER_IP_ENV).value(), Settings::Key::General_IP);
|
||||
} else {
|
||||
TryReadValue(data, "General", StrIP, EnvStrIP, Settings::Key::General_IP);
|
||||
}
|
||||
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
|
||||
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
|
||||
TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map);
|
||||
@ -260,10 +274,8 @@ void TConfig::ParseFromFile(std::string_view name) {
|
||||
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat);
|
||||
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests);
|
||||
// Misc
|
||||
TryReadValue(data, "Misc", StrSendErrors, "", Settings::Key::Misc_SendErrors);
|
||||
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Settings::Key::Misc_ImScaredOfUpdates);
|
||||
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Settings::Key::Misc_SendErrorsShowMessage);
|
||||
TryReadValue(data, "Misc", StrUpdateReminderTime, "", Settings::Key::Misc_UpdateReminderTime);
|
||||
TryReadValue(data, "Misc", StrHideUpdateMessages, EnvStrHideUpdateMessages, Settings::Key::Misc_ImScaredOfUpdates);
|
||||
TryReadValue(data, "Misc", StrUpdateReminderTime, EnvStrUpdateReminderTime, Settings::Key::Misc_UpdateReminderTime);
|
||||
|
||||
} catch (const std::exception& err) {
|
||||
beammp_error("Error parsing config file value: " + std::string(err.what()));
|
||||
@ -299,7 +311,9 @@ void TConfig::PrintDebug() {
|
||||
}
|
||||
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
|
||||
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
|
||||
beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
|
||||
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)));
|
||||
beammp_debug(std::string(StrIP) + ": \"" + Application::Settings.getAsString(Settings::Key::General_IP) + "\"");
|
||||
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
|
||||
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
|
||||
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\"");
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "CustomAssert.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "Http.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <lua.hpp>
|
||||
@ -208,16 +209,18 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
|
||||
}
|
||||
static constexpr const char* sHelpString = R"(
|
||||
Commands:
|
||||
help displays this help
|
||||
exit shuts down the server
|
||||
kick <name> [reason] kicks specified player with an optional reason
|
||||
list lists all players and info about them
|
||||
say <message> sends the message to all players in chat
|
||||
lua [state id] switches to lua, optionally into a specific state id's lua
|
||||
settings [command] sets or gets settings for the server, run `settings help` for more info
|
||||
status how the server is doing and what it's up to
|
||||
clear clears the console window
|
||||
version displays the server version)";
|
||||
help displays this help
|
||||
exit shuts down the server
|
||||
kick <name> [reason] kicks specified player with an optional reason
|
||||
list lists all players and info about them
|
||||
say <message> sends the message to all players in chat
|
||||
lua [state id] switches to lua, optionally into a specific state id's lua
|
||||
settings [command] sets or gets settings for the server, run `settings help` for more info
|
||||
status how the server is doing and what it's up to
|
||||
clear clears the console window
|
||||
version displays the server version
|
||||
protectmod <name> <value> sets whether a mod is protected, value can be true or false
|
||||
reloadmods reloads all mods from the Resources Client folder)";
|
||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
||||
}
|
||||
|
||||
@ -262,6 +265,56 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
|
||||
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
|
||||
Application::Console().WriteRaw(openssl_version);
|
||||
}
|
||||
void TConsole::Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& ModName = args.at(0);
|
||||
const auto& Protect = args.at(1);
|
||||
|
||||
for (auto mod : mLuaEngine->Network().ResourceManager().GetMods()) {
|
||||
if (mod["file_name"].get<std::string>() == ModName) {
|
||||
mLuaEngine->Network().ResourceManager().SetProtected(ModName, Protect == "true");
|
||||
Application::Console().WriteRaw("Mod " + ModName + " is now " + (Protect == "true" ? "protected" : "unprotected"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Application::Console().WriteRaw("Mod " + ModName + " not found.");
|
||||
}
|
||||
void TConsole::Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLuaEngine->Network().ResourceManager().RefreshFiles();
|
||||
Application::Console().WriteRaw("Mods reloaded.");
|
||||
}
|
||||
|
||||
void TConsole::Command_NetTest(const std::string& cmd, const std::vector<std::string>& args) {
|
||||
unsigned int status = 0;
|
||||
|
||||
std::string T = Http::GET(
|
||||
Application::GetServerCheckUrl() + "/api/v2/beammp/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)), &status
|
||||
);
|
||||
|
||||
beammp_debugf("Status and response from Server Check API: {0}, {1}", status, T);
|
||||
|
||||
auto Doc = nlohmann::json::parse(T, nullptr, false);
|
||||
|
||||
if (Doc.is_discarded() || !Doc.is_object()) {
|
||||
beammp_warn("Failed to parse Server Check API response, however the server will most likely still work correctly.");
|
||||
} else {
|
||||
std::string status = Doc["status"];
|
||||
std::string details = "Response from Server Check API: " + std::string(Doc["details"]);
|
||||
if (status == "ok") {
|
||||
beammp_info(details);
|
||||
} else {
|
||||
beammp_warn(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
|
||||
if (!EnsureArgsCount(args, 1, size_t(-1))) {
|
||||
|
@ -20,14 +20,12 @@
|
||||
|
||||
#include "ChronoWrapper.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "Http.h"
|
||||
// #include "SocketIO.h"
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/rapidjson.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace json = rapidjson;
|
||||
|
||||
void THeartbeatThread::operator()() {
|
||||
RegisterThread("Heartbeat");
|
||||
std::string Body;
|
||||
@ -61,15 +59,19 @@ void THeartbeatThread::operator()() {
|
||||
auto Target = "/heartbeat";
|
||||
unsigned int ResponseCode = 0;
|
||||
|
||||
json::Document Doc;
|
||||
nlohmann::json Doc;
|
||||
bool Ok = false;
|
||||
for (const auto& Url : Application::GetBackendUrlsInOrder()) {
|
||||
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
|
||||
Doc.Parse(T.data(), T.size());
|
||||
if (Doc.HasParseError() || !Doc.IsObject()) {
|
||||
T = Http::POST(Url + Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } });
|
||||
|
||||
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||
beammp_debug("Backend response was: `" + T + "`");
|
||||
}
|
||||
|
||||
Doc = nlohmann::json::parse(T, nullptr, false);
|
||||
if (Doc.is_discarded() || !Doc.is_object()) {
|
||||
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
||||
beammp_trace("Backend response failed to parse as valid json");
|
||||
beammp_trace("Response was: `" + T + "`");
|
||||
}
|
||||
} else if (ResponseCode != 200) {
|
||||
beammp_errorf("Response code from the heartbeat: {}", ResponseCode);
|
||||
@ -88,18 +90,18 @@ void THeartbeatThread::operator()() {
|
||||
const auto MessageKey = "msg";
|
||||
|
||||
if (Ok) {
|
||||
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) {
|
||||
Status = Doc[StatusKey].GetString();
|
||||
if (Doc.contains(StatusKey) && Doc[StatusKey].is_string()) {
|
||||
Status = Doc[StatusKey];
|
||||
} else {
|
||||
Ok = false;
|
||||
}
|
||||
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) {
|
||||
Code = Doc[CodeKey].GetString();
|
||||
if (Doc.contains(CodeKey) && Doc[CodeKey].is_string()) {
|
||||
Code = Doc[CodeKey];
|
||||
} else {
|
||||
Ok = false;
|
||||
}
|
||||
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) {
|
||||
Message = Doc[MessageKey].GetString();
|
||||
if (Doc.contains(MessageKey) && Doc[MessageKey].is_string()) {
|
||||
Message = Doc[MessageKey];
|
||||
} else {
|
||||
Ok = false;
|
||||
}
|
||||
@ -137,25 +139,30 @@ void THeartbeatThread::operator()() {
|
||||
}
|
||||
|
||||
std::string THeartbeatThread::GenerateCall() {
|
||||
std::stringstream Ret;
|
||||
nlohmann::json Ret = {
|
||||
{ "players", std::to_string(mServer.ClientCount()) },
|
||||
{ "maxplayers", std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) },
|
||||
{ "port", std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) },
|
||||
{ "map", Application::Settings.getAsString(Settings::Key::General_Map) },
|
||||
{ "private", Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false" },
|
||||
{ "version", Application::ServerVersionString() },
|
||||
{ "clientversion", Application::ClientMinimumVersion().AsString() },
|
||||
{ "name", Application::Settings.getAsString(Settings::Key::General_Name) },
|
||||
{ "tags", Application::Settings.getAsString(Settings::Key::General_Tags) },
|
||||
{ "guests", Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false" },
|
||||
{ "modlist", mResourceManager.TrimmedList() },
|
||||
{ "modstotalsize", std::to_string(mResourceManager.MaxModSize()) },
|
||||
{ "modstotal", std::to_string(mResourceManager.ModsLoaded()) },
|
||||
{ "playerslist", GetPlayers() },
|
||||
{ "desc", Application::Settings.getAsString(Settings::Key::General_Description) }
|
||||
};
|
||||
|
||||
Ret << "uuid=" << Application::Settings.getAsString(Settings::Key::General_AuthKey)
|
||||
<< "&players=" << mServer.ClientCount()
|
||||
<< "&maxplayers=" << Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)
|
||||
<< "&port=" << Application::Settings.getAsInt(Settings::Key::General_Port)
|
||||
<< "&map=" << Application::Settings.getAsString(Settings::Key::General_Map)
|
||||
<< "&private=" << (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")
|
||||
<< "&version=" << Application::ServerVersionString()
|
||||
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
||||
<< "&name=" << Application::Settings.getAsString(Settings::Key::General_Name)
|
||||
<< "&tags=" << Application::Settings.getAsString(Settings::Key::General_Tags)
|
||||
<< "&guests=" << (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false")
|
||||
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||
<< "&playerslist=" << GetPlayers()
|
||||
<< "&desc=" << Application::Settings.getAsString(Settings::Key::General_Description);
|
||||
return Ret.str();
|
||||
lastCall = Ret.dump();
|
||||
|
||||
// Add sensitive information here because value of lastCall is used for the information packet.
|
||||
Ret["uuid"] = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
||||
|
||||
return Ret.dump();
|
||||
}
|
||||
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
||||
: mResourceManager(ResourceManager)
|
||||
|
@ -40,18 +40,6 @@ TLuaEngine* LuaAPI::MP::Engine;
|
||||
|
||||
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn);
|
||||
|
||||
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName);
|
||||
|
||||
static std::optional<sol::function> GetLuaHandler(sol::state_view StateView, const std::string Handler, const std::string EventName) {
|
||||
auto Res = StateView.safe_script("return " + Handler, sol::script_pass_on_error);
|
||||
if (!Res.valid()) {
|
||||
beammp_errorf("invalid handler for event \"{}\". handler: \"{}\"", EventName, Handler);
|
||||
} else if (Res.get_type() == sol::type::function) {
|
||||
return Res.get<sol::function>();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TLuaEngine::TLuaEngine()
|
||||
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
|
||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
|
||||
@ -80,10 +68,11 @@ TEST_CASE("TLuaEngine ctor & dtor") {
|
||||
|
||||
void TLuaEngine::operator()() {
|
||||
RegisterThread("LuaEngine");
|
||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
||||
// lua engine main thread
|
||||
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
|
||||
CollectAndInitPlugins();
|
||||
|
||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
||||
// now call all onInit's
|
||||
auto Futures = TriggerEvent("onInit", "");
|
||||
WaitForAll(Futures, std::chrono::seconds(5));
|
||||
@ -506,11 +495,9 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
|
||||
|
||||
sol::variadic_results LocalArgs = JsonStringToArray(Str);
|
||||
for (const auto& Handler : MyHandlers) {
|
||||
auto Res = GetLuaHandler(mStateView, Handler, EventName);
|
||||
if (Res.has_value()) {
|
||||
sol::function Fn = Res.value();
|
||||
Fn = AddTraceback(mStateView, Fn);
|
||||
|
||||
auto Fn = mStateView[Handler];
|
||||
Fn = AddTraceback(mStateView, Fn);
|
||||
if (Fn.valid()) {
|
||||
auto LuaResult = Fn(LocalArgs);
|
||||
auto Result = std::make_shared<TLuaResult>();
|
||||
if (LuaResult.valid()) {
|
||||
@ -562,9 +549,8 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
|
||||
sol::table Result = mStateView.create_table();
|
||||
int i = 1;
|
||||
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
|
||||
auto Res = GetLuaHandler(mStateView, Handler, EventName);
|
||||
if (Res.has_value()) {
|
||||
sol::function Fn = Res.value();
|
||||
auto Fn = mStateView[Handler];
|
||||
if (Fn.valid() && Fn.get_type() == sol::type::function) {
|
||||
auto FnRet = Fn(EventArgs);
|
||||
if (FnRet.valid()) {
|
||||
Result.set(i, FnRet);
|
||||
@ -595,6 +581,16 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayerRole(int ID) {
|
||||
auto MaybeClient = GetClient(mEngine->Server(), ID);
|
||||
if (MaybeClient) {
|
||||
return MaybeClient.value().lock()->GetRoles();
|
||||
} else {
|
||||
return sol::nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
|
||||
sol::table Result = mStateView.create_table();
|
||||
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||
@ -672,7 +668,7 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
|
||||
sol::state_view StateView(mState);
|
||||
sol::table Result = StateView.create_table();
|
||||
for (const auto& v : VehicleData) {
|
||||
Result[v.ID()] = v.Data().substr(3);
|
||||
Result[v.ID()] = v.DataAsPacket(Client->GetRoles(), Client->GetName(), Client->GetID()).substr(3);
|
||||
}
|
||||
return Result;
|
||||
} else
|
||||
@ -869,6 +865,23 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
return Lua_GetPositionRaw(PID, VID);
|
||||
});
|
||||
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
|
||||
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
|
||||
if (Args.size() == 2) {
|
||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1));
|
||||
} else if (Args.size() == 3) {
|
||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(1));
|
||||
} else if (Args.size() == 4) {
|
||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(3));
|
||||
} else {
|
||||
beammp_lua_error("SendNotification expects 2, 3 or 4 arguments.");
|
||||
}
|
||||
});
|
||||
MPTable.set_function("ConfirmationDialog", sol::overload(
|
||||
&LuaAPI::MP::ConfirmationDialog,
|
||||
[&](const int& ID, const std::string& Title, const std::string& Body, const sol::table& Buttons, const std::string& InteractionID) {
|
||||
LuaAPI::MP::ConfirmationDialog(ID, Title, Body, Buttons, InteractionID);
|
||||
}
|
||||
));
|
||||
MPTable.set_function("GetPlayers", [&]() -> sol::table {
|
||||
return Lua_GetPlayers();
|
||||
});
|
||||
@ -883,6 +896,9 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
|
||||
return Lua_GetPlayerIdentifiers(ID);
|
||||
});
|
||||
MPTable.set_function("GetPlayerRole", [&](int ID) -> std::variant<std::string, sol::nil_t> {
|
||||
return Lua_GetPlayerRole(ID);
|
||||
});
|
||||
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
|
||||
// const std::string& EventName, size_t IntervalMS, int strategy
|
||||
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
|
||||
@ -910,6 +926,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
mEngine->CancelEventTimers(EventName, mStateId);
|
||||
});
|
||||
MPTable.set_function("Set", &LuaAPI::MP::Set);
|
||||
MPTable.set_function("Get", &LuaAPI::MP::Get);
|
||||
|
||||
auto UtilTable = StateView.create_named_table("Util");
|
||||
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
|
||||
@ -1001,7 +1018,8 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
||||
"MaxPlayers", 3,
|
||||
"Map", 4,
|
||||
"Name", 5,
|
||||
"Description", 6);
|
||||
"Description", 6,
|
||||
"InformationPacket", 7);
|
||||
|
||||
MPTable.create_named("CallStrategy",
|
||||
"BestEffort", CallStrategy::BestEffort,
|
||||
@ -1150,10 +1168,8 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
|
||||
Result->StateId = mStateId;
|
||||
sol::state_view StateView(mState);
|
||||
|
||||
auto Res = GetLuaHandler(StateView, FnName, TheQueuedFunction.EventName);
|
||||
if (Res.has_value()) {
|
||||
sol::function Fn = Res.value();
|
||||
auto RawFn = StateView[FnName];
|
||||
if (RawFn.valid() && RawFn.get_type() == sol::type::function) {
|
||||
std::vector<sol::object> LuaArgs;
|
||||
for (const auto& Arg : Args) {
|
||||
if (Arg.valueless_by_exception()) {
|
||||
@ -1188,7 +1204,7 @@ void TLuaEngine::StateThreadData::operator()() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Fn = AddTraceback(StateView, Fn);
|
||||
auto Fn = AddTraceback(StateView, RawFn);
|
||||
auto Res = Fn(sol::as_args(LuaArgs));
|
||||
if (Res.valid()) {
|
||||
Result->Error = false;
|
||||
|
282
src/TNetwork.cpp
282
src/TNetwork.cpp
@ -20,6 +20,7 @@
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "LuaAPI.h"
|
||||
#include "THeartbeatThread.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "TScopedTimer.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
@ -84,9 +85,17 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
|
||||
|
||||
void TNetwork::UDPServerMain() {
|
||||
RegisterThread("UDPServer");
|
||||
// listen on all ipv6 addresses
|
||||
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("::"), Application::Settings.getAsInt(Settings::Key::General_Port));
|
||||
|
||||
boost::system::error_code ec;
|
||||
auto address = ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
|
||||
|
||||
if (ec) {
|
||||
beammp_errorf("Failed to parse IP: {}", ec.message());
|
||||
Application::GracefullyShutdown();
|
||||
}
|
||||
|
||||
ip::udp::endpoint UdpListenEndpoint(address, Application::Settings.getAsInt(Settings::Key::General_Port));
|
||||
|
||||
mUDPSock.open(UdpListenEndpoint.protocol(), ec);
|
||||
if (ec) {
|
||||
beammp_error("open() failed: " + ec.message());
|
||||
@ -106,15 +115,25 @@ void TNetwork::UDPServerMain() {
|
||||
Application::GracefullyShutdown();
|
||||
}
|
||||
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
|
||||
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) + (" with a Max of ")
|
||||
beammp_info(("Vehicle data network online on port ") + std::to_string(UdpListenEndpoint.port()) + (" with a Max of ")
|
||||
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
|
||||
while (!Application::IsShuttingDown()) {
|
||||
try {
|
||||
ip::udp::endpoint remote_client_ep {};
|
||||
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
|
||||
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
||||
if (Data.empty() || Pos > Data.begin() + 2)
|
||||
if (Data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (Data.size() == 1 && Data.at(0) == 'P') {
|
||||
mUDPSock.send_to(const_buffer("P", 1), remote_client_ep, {}, ec);
|
||||
// ignore errors
|
||||
(void)ec;
|
||||
continue;
|
||||
}
|
||||
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
||||
if (Pos > Data.begin() + 2) {
|
||||
continue;
|
||||
}
|
||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
@ -159,12 +178,17 @@ void TNetwork::UDPServerMain() {
|
||||
void TNetwork::TCPServerMain() {
|
||||
RegisterThread("TCPServer");
|
||||
|
||||
// listen on all ipv6 addresses
|
||||
auto port = uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port));
|
||||
ip::tcp::endpoint ListenEp(ip::address::from_string("::"), port);
|
||||
beammp_infof("Listening on 0.0.0.0:{0} and [::]:{0}", port);
|
||||
ip::tcp::socket Listener(mServer.IoCtx());
|
||||
boost::system::error_code ec;
|
||||
auto address = ip::make_address(Application::Settings.getAsString(Settings::Key::General_IP), ec);
|
||||
if (ec) {
|
||||
beammp_errorf("Failed to parse IP: {}", ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
ip::tcp::endpoint ListenEp(address,
|
||||
uint16_t(Application::Settings.getAsInt(Settings::Key::General_Port)));
|
||||
|
||||
ip::tcp::socket Listener(mServer.IoCtx());
|
||||
Listener.open(ListenEp.protocol(), ec);
|
||||
if (ec) {
|
||||
beammp_errorf("Failed to open socket: {}", ec.message());
|
||||
@ -198,6 +222,7 @@ void TNetwork::TCPServerMain() {
|
||||
Application::GracefullyShutdown();
|
||||
}
|
||||
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
|
||||
beammp_infof("Listening on {0} port {1}", ListenEp.address().to_string(), static_cast<uint16_t>(ListenEp.port()));
|
||||
beammp_info("Vehicle event network online");
|
||||
do {
|
||||
try {
|
||||
@ -240,16 +265,28 @@ void TNetwork::Identify(TConnection&& RawConnection) {
|
||||
if (Code == 'C') {
|
||||
Client = Authentication(std::move(RawConnection));
|
||||
} else if (Code == 'D') {
|
||||
HandleDownload(std::move(RawConnection));
|
||||
beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored");
|
||||
return;
|
||||
} else if (Code == 'P') {
|
||||
boost::system::error_code ec;
|
||||
write(RawConnection.Socket, buffer("P"), ec);
|
||||
return;
|
||||
} else if (Code == 'I') {
|
||||
const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : "";
|
||||
|
||||
const auto Size = static_cast<int32_t>(Data.size());
|
||||
std::vector<uint8_t> ToSend;
|
||||
ToSend.resize(Data.size() + sizeof(Size));
|
||||
std::memcpy(ToSend.data(), &Size, sizeof(Size));
|
||||
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
|
||||
|
||||
boost::system::error_code ec;
|
||||
write(RawConnection.Socket, buffer(ToSend), ec);
|
||||
} else {
|
||||
beammp_errorf("Invalid code got in Identify: '{}'", Code);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
|
||||
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what());
|
||||
boost::system::error_code ec;
|
||||
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||
if (ec) {
|
||||
@ -262,27 +299,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::HandleDownload(TConnection&& Conn) {
|
||||
char D;
|
||||
boost::system::error_code ec;
|
||||
read(Conn.Socket, buffer(&D, 1), ec);
|
||||
if (ec) {
|
||||
Conn.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||
// ignore ec
|
||||
return;
|
||||
}
|
||||
auto ID = uint8_t(D);
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
if (c->GetID() == ID) {
|
||||
c->SetDownSock(std::move(Conn.Socket));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
std::string HashPassword(const std::string& str) {
|
||||
std::stringstream ret;
|
||||
@ -304,6 +321,11 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
Client->SetIdentifier("ip", ip);
|
||||
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6");
|
||||
|
||||
if (Application::GetSubsystemStatuses().at("Main") == Application::Status::Starting) {
|
||||
ClientKick(*Client, "The server is still starting, please try joining again later.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
beammp_info("Identifying new ClientConnection...");
|
||||
|
||||
auto Data = TCPRcv(*Client);
|
||||
@ -312,10 +334,11 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
if (Data.size() > 3 && std::equal(Data.begin(), Data.begin() + VC.size(), VC.begin(), VC.end())) {
|
||||
std::string ClientVersionStr(reinterpret_cast<const char*>(Data.data() + 2), Data.size() - 2);
|
||||
Version ClientVersion = Application::VersionStrToInts(ClientVersionStr + ".0");
|
||||
if (ClientVersion.major != Application::ClientMajorVersion()) {
|
||||
beammp_errorf("Client tried to connect with version '{}', but only versions '{}.x.x' is allowed",
|
||||
ClientVersion.AsString(), Application::ClientMajorVersion());
|
||||
ClientKick(*Client, "Outdated Version!");
|
||||
Version MinClientVersion = Application::ClientMinimumVersion();
|
||||
if (Application::IsOutdated(ClientVersion, MinClientVersion)) {
|
||||
beammp_errorf("Client tried to connect with version '{}', but only versions >= {} are allowed",
|
||||
ClientVersion.AsString(), MinClientVersion.AsString());
|
||||
ClientKick(*Client, fmt::format("Outdated version, launcher version >={} required to join!", MinClientVersion.AsString()));
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
@ -350,7 +373,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
auto Target = "/pkToUser";
|
||||
|
||||
unsigned int ResponseCode = 0;
|
||||
AuthResStr = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, AuthReq.dump(), "application/json", &ResponseCode);
|
||||
AuthResStr = Http::POST(Application::GetBackendUrlForAuth() + Target, AuthReq.dump(), "application/json", &ResponseCode);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
beammp_debugf("Invalid json sent by client, kicking: {}", e.what());
|
||||
@ -358,6 +381,8 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
beammp_debug("Response from authentication backend: " + AuthResStr);
|
||||
|
||||
try {
|
||||
nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr);
|
||||
|
||||
@ -404,10 +429,21 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
|
||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||
TLuaEngine::WaitForAll(Futures);
|
||||
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
|
||||
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
|
||||
});
|
||||
bool NotAllowed = false;
|
||||
bool BypassLimit = false;
|
||||
|
||||
for (const auto& Result : Futures) {
|
||||
if (!Result->Error && Result->Result.is<int>()) {
|
||||
auto Res = Result->Result.as<int>();
|
||||
|
||||
if (Res == 1) {
|
||||
NotAllowed = true;
|
||||
break;
|
||||
} else if (Res == 2) {
|
||||
BypassLimit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string Reason;
|
||||
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
|
||||
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
|
||||
@ -423,35 +459,27 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||
Reason = "No guests are allowed on this server! To join, sign up at: forum.beammp.com.";
|
||||
}
|
||||
|
||||
bool Allowed = true;
|
||||
if (NotAllowed) {
|
||||
Allowed = false;
|
||||
if (!NotAllowed && !NotAllowedWithReason && mServer.ClientCount() >= size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) && !BypassLimit) {
|
||||
NotAllowedWithReason = true;
|
||||
Reason = "Server full!";
|
||||
}
|
||||
|
||||
if (NotAllowedWithReason) {
|
||||
Allowed = false;
|
||||
}
|
||||
|
||||
if (NotAllowed) {
|
||||
ClientKick(*Client, "you are not allowed on the server!");
|
||||
} else if (NotAllowedWithReason) {
|
||||
ClientKick(*Client, Reason);
|
||||
} else if (NotAllowed) {
|
||||
ClientKick(*Client, "you are not allowed on the server!");
|
||||
}
|
||||
|
||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postPlayerAuth", "", NotAllowed || NotAllowedWithReason, Reason, Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||
// the post event is not cancellable so we dont wait for it
|
||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||
|
||||
if (!Allowed) {
|
||||
return {};
|
||||
} else if (mServer.ClientCount() < size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers))) {
|
||||
if (!NotAllowed && !NotAllowedWithReason) {
|
||||
beammp_info("Identification success");
|
||||
mServer.InsertClient(Client);
|
||||
TCPClient(Client);
|
||||
} else {
|
||||
ClientKick(*Client, "Server full!");
|
||||
}
|
||||
|
||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postPlayerAuth", "", Allowed, Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||
// the post event is not cancellable so we dont wait for it
|
||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
||||
|
||||
return Client;
|
||||
}
|
||||
|
||||
@ -758,11 +786,11 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
case 'S':
|
||||
if (SubCode == 'R') {
|
||||
beammp_debug("Sending Mod Info");
|
||||
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||
if (ToSend.empty())
|
||||
ToSend = "-";
|
||||
std::string ToSend = mResourceManager.GetMods().dump();
|
||||
beammp_debugf("Mod Info: {}", ToSend);
|
||||
if (!TCPSend(c, StringToVector(ToSend))) {
|
||||
// TODO: error
|
||||
ClientKick(c, "TCP Send 'SY' failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -772,8 +800,6 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
||||
}
|
||||
|
||||
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
|
||||
|
||||
if (!fs::path(UnsafeName).has_filename()) {
|
||||
if (!TCPSend(c, StringToVector("CO"))) {
|
||||
// TODO: handle
|
||||
@ -782,6 +808,15 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||
return;
|
||||
}
|
||||
auto FileName = fs::path(UnsafeName).filename().string();
|
||||
|
||||
for (auto mod : mResourceManager.GetMods()) {
|
||||
if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) {
|
||||
beammp_warn("Client tried to access protected file " + UnsafeName);
|
||||
c.Disconnect("Mod is protected thus cannot be downloaded");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;
|
||||
|
||||
if (!std::filesystem::exists(FileName)) {
|
||||
@ -796,87 +831,9 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||
// TODO: handle
|
||||
}
|
||||
|
||||
/// Wait for connections
|
||||
int T = 0;
|
||||
while (!c.GetDownSock().is_open() && T < 50) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
T++;
|
||||
}
|
||||
|
||||
if (!c.GetDownSock().is_open()) {
|
||||
beammp_error("Client doesn't have a download socket!");
|
||||
if (!c.IsDisconnected())
|
||||
c.Disconnect("Missing download socket");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t Size = size_t(std::filesystem::file_size(FileName));
|
||||
size_t MSize = Size / 2;
|
||||
|
||||
std::thread SplitThreads[2] {
|
||||
std::thread([&] {
|
||||
RegisterThread("SplitLoad_0");
|
||||
SplitLoad(c, 0, MSize, false, FileName);
|
||||
}),
|
||||
std::thread([&] {
|
||||
RegisterThread("SplitLoad_1");
|
||||
SplitLoad(c, MSize, Size, true, FileName);
|
||||
})
|
||||
};
|
||||
|
||||
for (auto& SplitThread : SplitThreads) {
|
||||
if (SplitThread.joinable()) {
|
||||
SplitThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<size_t /* count */, size_t /* last chunk */> SplitIntoChunks(size_t FullSize, size_t ChunkSize) {
|
||||
if (FullSize < ChunkSize) {
|
||||
return { 0, FullSize };
|
||||
}
|
||||
size_t Count = FullSize / (FullSize / ChunkSize);
|
||||
size_t LastChunkSize = FullSize - (Count * ChunkSize);
|
||||
return { Count, LastChunkSize };
|
||||
}
|
||||
|
||||
TEST_CASE("SplitIntoChunks") {
|
||||
size_t FullSize;
|
||||
size_t ChunkSize;
|
||||
SUBCASE("Normal case") {
|
||||
FullSize = 1234567;
|
||||
ChunkSize = 1234;
|
||||
}
|
||||
SUBCASE("Zero original size") {
|
||||
FullSize = 0;
|
||||
ChunkSize = 100;
|
||||
}
|
||||
SUBCASE("Equal full size and chunk size") {
|
||||
FullSize = 125;
|
||||
ChunkSize = 125;
|
||||
}
|
||||
SUBCASE("Even split") {
|
||||
FullSize = 10000;
|
||||
ChunkSize = 100;
|
||||
}
|
||||
SUBCASE("Odd split") {
|
||||
FullSize = 13;
|
||||
ChunkSize = 2;
|
||||
}
|
||||
SUBCASE("Large sizes") {
|
||||
FullSize = 10 * GB;
|
||||
ChunkSize = 125 * MB;
|
||||
}
|
||||
auto [Count, LastSize] = SplitIntoChunks(FullSize, ChunkSize);
|
||||
CHECK((Count * ChunkSize) + LastSize == FullSize);
|
||||
}
|
||||
|
||||
const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size) {
|
||||
if (TCPSendRaw(c, Socket, DataPtr, Size)) {
|
||||
return DataPtr + Size;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
SendFileToClient(c, Size, FileName);
|
||||
}
|
||||
|
||||
#if defined(BEAMMP_LINUX)
|
||||
@ -886,8 +843,8 @@ const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& So
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const std::string& Name) {
|
||||
TScopedTimer timer(fmt::format("Download of {}-{} for '{}'", Offset, End, Name));
|
||||
void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) {
|
||||
TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID()));
|
||||
#if defined(BEAMMP_LINUX)
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
// on linux, we can use sendfile(2)!
|
||||
@ -897,11 +854,11 @@ void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const st
|
||||
return;
|
||||
}
|
||||
// native handle, needed in order to make native syscalls with it
|
||||
int socket = D ? c.GetDownSock().native_handle() : c.GetTCPSock().native_handle();
|
||||
int socket = c.GetTCPSock().native_handle();
|
||||
|
||||
ssize_t ret = 0;
|
||||
auto ToSendTotal = End - Offset;
|
||||
auto Start = Offset;
|
||||
auto ToSendTotal = Size;
|
||||
auto Start = 0;
|
||||
while (ret < ssize_t(ToSendTotal)) {
|
||||
auto SysOffset = off_t(Start + size_t(ret));
|
||||
ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
|
||||
@ -915,35 +872,32 @@ void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const st
|
||||
std::ifstream f(Name.c_str(), std::ios::binary);
|
||||
uint32_t Split = 125 * MB;
|
||||
std::vector<uint8_t> Data;
|
||||
if (End > Split)
|
||||
if (Size > Split)
|
||||
Data.resize(Split);
|
||||
else
|
||||
Data.resize(End);
|
||||
ip::tcp::socket* TCPSock { nullptr };
|
||||
if (D)
|
||||
TCPSock = &c.GetDownSock();
|
||||
else
|
||||
TCPSock = &c.GetTCPSock();
|
||||
while (!c.IsDisconnected() && Offset < End) {
|
||||
size_t Diff = End - Offset;
|
||||
Data.resize(Size);
|
||||
ip::tcp::socket* TCPSock = &c.GetTCPSock();
|
||||
std::streamsize Sent = 0;
|
||||
while (!c.IsDisconnected() && Sent < Size) {
|
||||
size_t Diff = Size - Sent;
|
||||
if (Diff > Split) {
|
||||
f.seekg(Offset, std::ios_base::beg);
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(reinterpret_cast<char*>(Data.data()), Split);
|
||||
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
|
||||
if (!c.IsDisconnected())
|
||||
c.Disconnect("TCPSendRaw failed in mod download (1)");
|
||||
break;
|
||||
}
|
||||
Offset += Split;
|
||||
Sent += Split;
|
||||
} else {
|
||||
f.seekg(Offset, std::ios_base::beg);
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(reinterpret_cast<char*>(Data.data()), Diff);
|
||||
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
|
||||
if (!c.IsDisconnected())
|
||||
c.Disconnect("TCPSendRaw failed in mod download (2)");
|
||||
break;
|
||||
}
|
||||
Offset += Diff;
|
||||
Sent += Diff;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1020,7 +974,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
||||
res = false;
|
||||
return false;
|
||||
}
|
||||
res = Respond(*LockedClient, StringToVector(v.Data()), true, true);
|
||||
res = Respond(*LockedClient, StringToVector(v.DataAsPacket(client->GetRoles(), client->GetName(), client->GetID())), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,21 +57,26 @@ void TPluginMonitor::operator()() {
|
||||
mFileTimes[Pair.first] = CurrentTime;
|
||||
// grandparent of the path should be Resources/Server
|
||||
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
|
||||
beammp_infof("File \"{}\" changed, reloading", Pair.first);
|
||||
// is in root folder, so reload
|
||||
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
|
||||
auto Size = std::filesystem::file_size(Pair.first);
|
||||
auto Contents = std::make_shared<std::string>();
|
||||
Contents->resize(Size);
|
||||
FileStream.read(Contents->data(), Contents->size());
|
||||
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
|
||||
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
|
||||
auto Res = mEngine->EnqueueScript(StateID, Chunk);
|
||||
Res->WaitUntilReady();
|
||||
if (Res->Error) {
|
||||
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
|
||||
if (LowerString(fs::path(Pair.first).extension().string()) == ".lua") {
|
||||
beammp_infof("File \"{}\" changed, reloading", Pair.first);
|
||||
// is in root folder, so reload
|
||||
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
|
||||
auto Size = std::filesystem::file_size(Pair.first);
|
||||
auto Contents = std::make_shared<std::string>();
|
||||
Contents->resize(Size);
|
||||
FileStream.read(Contents->data(), Contents->size());
|
||||
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
|
||||
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
|
||||
auto Res = mEngine->EnqueueScript(StateID, Chunk);
|
||||
Res->WaitUntilReady();
|
||||
if (Res->Error) {
|
||||
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
|
||||
} else {
|
||||
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
|
||||
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
|
||||
}
|
||||
} else {
|
||||
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
|
||||
beammp_debugf("File \"{}\" changed, not reloading because it's not a lua file. Triggering 'onFileChanged' event instead", Pair.first);
|
||||
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
|
||||
}
|
||||
} else {
|
||||
|
@ -17,9 +17,14 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "TResourceManager.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fmt/core.h>
|
||||
#include <ios>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@ -52,3 +57,185 @@ TResourceManager::TResourceManager() {
|
||||
|
||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
|
||||
}
|
||||
|
||||
void TResourceManager::RefreshFiles() {
|
||||
mMods.clear();
|
||||
std::unique_lock Lock(mModsMutex);
|
||||
|
||||
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
|
||||
|
||||
nlohmann::json modsDB;
|
||||
|
||||
if (std::filesystem::exists(Path + "/mods.json")) {
|
||||
try {
|
||||
std::ifstream stream(Path + "/mods.json");
|
||||
|
||||
stream >> modsDB;
|
||||
|
||||
stream.close();
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to load mods.json: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
std::string File(entry.path().string());
|
||||
|
||||
if (entry.path().filename().string() == "mods.json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
|
||||
beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modsDB.contains(entry.path().filename().string())) {
|
||||
auto& dbEntry = modsDB[entry.path().filename().string()];
|
||||
if (entry.last_write_time().time_since_epoch().count() > dbEntry["lastwrite"] || std::filesystem::file_size(File) != dbEntry["filesize"].get<size_t>()) {
|
||||
beammp_infof("File '{}' has been modified, rehashing", File);
|
||||
} else {
|
||||
dbEntry["exists"] = true;
|
||||
|
||||
mMods.push_back(nlohmann::json {
|
||||
{ "file_name", std::filesystem::path(File).filename() },
|
||||
{ "file_size", std::filesystem::file_size(File) },
|
||||
{ "hash_algorithm", "sha256" },
|
||||
{ "hash", dbEntry["hash"] },
|
||||
{ "protected", dbEntry["protected"] } });
|
||||
|
||||
beammp_debugf("Mod '{}' loaded from cache", File);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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(File, std::ios::binary);
|
||||
|
||||
const size_t FileSize = std::filesystem::file_size(File);
|
||||
size_t Read = 0;
|
||||
std::vector<char> Data;
|
||||
while (Read < FileSize) {
|
||||
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
|
||||
size_t RealDataSize = Data.size();
|
||||
stream.read(Data.data(), std::streamsize(Data.size()));
|
||||
if (stream.eof() || stream.fail()) {
|
||||
RealDataSize = size_t(stream.gcount());
|
||||
}
|
||||
Data.resize(RealDataSize);
|
||||
if (RealDataSize == 0) {
|
||||
break;
|
||||
}
|
||||
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
throw std::runtime_error("EVP_DigestUpdate() failed");
|
||||
}
|
||||
Read += RealDataSize;
|
||||
}
|
||||
unsigned int sha256_len = 0;
|
||||
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
throw std::runtime_error("EVP_DigestFinal_ex() failed");
|
||||
}
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
|
||||
stream.close();
|
||||
|
||||
std::string result;
|
||||
for (size_t i = 0; i < sha256_len; i++) {
|
||||
result += fmt::format("{:02x}", sha256_value[i]);
|
||||
}
|
||||
beammp_debugf("sha256('{}'): {}", File, result);
|
||||
mMods.push_back(nlohmann::json {
|
||||
{ "file_name", std::filesystem::path(File).filename() },
|
||||
{ "file_size", std::filesystem::file_size(File) },
|
||||
{ "hash_algorithm", "sha256" },
|
||||
{ "hash", result },
|
||||
{ "protected", false } });
|
||||
|
||||
modsDB[std::filesystem::path(File).filename().string()] = {
|
||||
{ "lastwrite", entry.last_write_time().time_since_epoch().count() },
|
||||
{ "hash", result },
|
||||
{ "filesize", std::filesystem::file_size(File) },
|
||||
{ "protected", false },
|
||||
{ "exists", true }
|
||||
};
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = modsDB.begin(); it != modsDB.end();) {
|
||||
if (!it.value().contains("exists")) {
|
||||
it = modsDB.erase(it);
|
||||
} else {
|
||||
it.value().erase("exists");
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
std::ofstream stream(Path + "/mods.json");
|
||||
|
||||
stream << modsDB.dump(4);
|
||||
|
||||
stream.close();
|
||||
} catch (std::exception& e) {
|
||||
beammp_error("Failed to update mod DB: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void TResourceManager::SetProtected(const std::string& ModName, bool Protected) {
|
||||
std::unique_lock Lock(mModsMutex);
|
||||
|
||||
for (auto& mod : mMods) {
|
||||
if (mod["file_name"].get<std::string>() == ModName) {
|
||||
mod["protected"] = Protected;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto modsDBPath = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/mods.json";
|
||||
|
||||
if (std::filesystem::exists(modsDBPath)) {
|
||||
try {
|
||||
nlohmann::json modsDB;
|
||||
|
||||
std::fstream stream(modsDBPath);
|
||||
|
||||
stream >> modsDB;
|
||||
|
||||
if (modsDB.contains(ModName)) {
|
||||
modsDB[ModName]["protected"] = Protected;
|
||||
}
|
||||
|
||||
stream.clear();
|
||||
stream.seekp(0, std::ios::beg);
|
||||
|
||||
stream << modsDB.dump(4);
|
||||
|
||||
stream.close();
|
||||
} catch (const std::exception& e) {
|
||||
beammp_errorf("Failed to update mods.json: {}", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +195,18 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
|
||||
// V to Y
|
||||
if (Code <= 89 && Code >= 86) {
|
||||
int PID = -1;
|
||||
int VID = -1;
|
||||
|
||||
auto MaybePidVid = GetPidVid(StringPacket.substr(3).substr(0, StringPacket.substr(3).find(':', 1)));
|
||||
if (MaybePidVid) {
|
||||
std::tie(PID, VID) = MaybePidVid.value();
|
||||
}
|
||||
|
||||
if (PID == -1 || VID == -1 || PID != LockedClient->GetID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PPSMonitor.IncrementInternalPPS();
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
return;
|
||||
@ -253,14 +265,27 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
|
||||
HandleEvent(*LockedClient, StringPacket);
|
||||
return;
|
||||
case 'N':
|
||||
beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||
return;
|
||||
case 'Z': // position packet
|
||||
case 'Z': { // position packet
|
||||
PPSMonitor.IncrementInternalPPS();
|
||||
|
||||
int PID = -1;
|
||||
int VID = -1;
|
||||
|
||||
auto MaybePidVid = GetPidVid(StringPacket.substr(3).substr(0, StringPacket.substr(3).find(':', 1)));
|
||||
if (MaybePidVid) {
|
||||
std::tie(PID, VID) = MaybePidVid.value();
|
||||
}
|
||||
|
||||
if (PID == -1 || VID == -1 || PID != LockedClient->GetID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
HandlePosition(*LockedClient, StringPacket);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -329,8 +354,9 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
||||
});
|
||||
|
||||
bool SpawnConfirmed = false;
|
||||
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
|
||||
c.AddNewCar(CarID, Packet);
|
||||
auto CarJsonDoc = nlohmann::json::parse(CarJson, nullptr, false);
|
||||
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn && !CarJsonDoc.is_discarded()) {
|
||||
c.AddNewCar(CarID, CarJsonDoc);
|
||||
Network.SendToAll(nullptr, StringToVector(Packet), true, true);
|
||||
SpawnConfirmed = true;
|
||||
} else {
|
||||
@ -436,6 +462,31 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||
return;
|
||||
}
|
||||
case 'p': {
|
||||
beammp_trace(std::string(("got 'Op' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1)));
|
||||
if (MaybePidVid) {
|
||||
std::tie(PID, VID) = MaybePidVid.value();
|
||||
}
|
||||
|
||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||
Data = Data.substr(Data.find('['));
|
||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
|
||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||
|
||||
auto CarData = c.GetCarData(VID);
|
||||
if (CarData == nlohmann::detail::value_t::null)
|
||||
return;
|
||||
|
||||
if (CarData.contains("vcf") && CarData.at("vcf").is_object())
|
||||
if (CarData.at("vcf").contains("paints") && CarData.at("vcf").at("paints").is_array()) {
|
||||
CarData.at("vcf")["paints"] = nlohmann::json::parse(Data);
|
||||
c.SetCarData(VID, CarData);
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
||||
return;
|
||||
@ -448,42 +499,22 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
|
||||
beammp_error("Malformed packet received, no '{' found");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string Packet = pckt.substr(FoundPos);
|
||||
std::string VD = c.GetCarData(VID);
|
||||
if (VD.empty()) {
|
||||
nlohmann::json VD = c.GetCarData(VID);
|
||||
if (VD == nlohmann::detail::value_t::null) {
|
||||
beammp_error("Tried to apply change to vehicle that does not exist");
|
||||
return;
|
||||
}
|
||||
std::string Header = VD.substr(0, VD.find('{'));
|
||||
|
||||
FoundPos = VD.find('{');
|
||||
if (FoundPos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
VD = VD.substr(FoundPos);
|
||||
rapidjson::Document Veh, Pack;
|
||||
Veh.Parse(VD.c_str());
|
||||
if (Veh.HasParseError()) {
|
||||
beammp_error("Could not get vehicle config!");
|
||||
return;
|
||||
}
|
||||
Pack.Parse(Packet.c_str());
|
||||
if (Pack.HasParseError() || Pack.IsNull()) {
|
||||
nlohmann::json Pack = nlohmann::json::parse(Packet, nullptr, false);
|
||||
|
||||
if (Pack.is_discarded()) {
|
||||
beammp_error("Could not get active vehicle config!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& M : Pack.GetObject()) {
|
||||
if (Veh[M.name].IsNull()) {
|
||||
Veh.AddMember(M.name, M.value, Veh.GetAllocator());
|
||||
} else {
|
||||
Veh[M.name] = Pack[M.name];
|
||||
}
|
||||
}
|
||||
rapidjson::StringBuffer Buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(Buffer);
|
||||
Veh.Accept(writer);
|
||||
c.SetCarData(VID, Header + Buffer.GetString());
|
||||
c.SetCarData(VID, Pack);
|
||||
}
|
||||
|
||||
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "Common.h"
|
||||
#include <utility>
|
||||
|
||||
TVehicleData::TVehicleData(int ID, std::string Data)
|
||||
TVehicleData::TVehicleData(int ID, nlohmann::json Data)
|
||||
: mID(ID)
|
||||
, mData(std::move(Data)) {
|
||||
beammp_trace("vehicle " + std::to_string(mID) + " constructed");
|
||||
@ -30,3 +30,7 @@ TVehicleData::TVehicleData(int ID, std::string Data)
|
||||
TVehicleData::~TVehicleData() {
|
||||
beammp_trace("vehicle " + std::to_string(mID) + " destroyed");
|
||||
}
|
||||
|
||||
std::string TVehicleData::DataAsPacket(const std::string& Role, const std::string& Name, const int ID) const {
|
||||
return "Os:" + Role + ":" + Name + ":" + std::to_string(ID) + "-" + std::to_string(this->mID) + ":" + this->mData.dump();
|
||||
}
|
28
src/main.cpp
28
src/main.cpp
@ -36,19 +36,19 @@
|
||||
#include <thread>
|
||||
|
||||
static const std::string sCommandlineArguments = R"(
|
||||
USAGE:
|
||||
USAGE:
|
||||
BeamMP-Server [arguments]
|
||||
|
||||
|
||||
ARGUMENTS:
|
||||
--help
|
||||
--help
|
||||
Displays this help and exits.
|
||||
--port=1234
|
||||
Sets the server's listening TCP and
|
||||
UDP port. Overrides ENV and ServerConfig.
|
||||
--config=/path/to/ServerConfig.toml
|
||||
Absolute or relative path to the
|
||||
Absolute or relative path to the
|
||||
Server Config file, including the
|
||||
filename. For paths and filenames with
|
||||
filename. For paths and filenames with
|
||||
spaces, put quotes around the path.
|
||||
--working-directory=/path/to/folder
|
||||
Sets the working directory of the Server.
|
||||
@ -59,7 +59,7 @@ ARGUMENTS:
|
||||
|
||||
EXAMPLES:
|
||||
BeamMP-Server --config=../MyWestCoastServerConfig.toml
|
||||
Runs the BeamMP-Server and uses the server config file
|
||||
Runs the BeamMP-Server and uses the server config file
|
||||
which is one directory above it and is named
|
||||
'MyWestCoastServerConfig.toml'.
|
||||
)";
|
||||
@ -182,24 +182,24 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
|
||||
TServer Server(Arguments.List);
|
||||
|
||||
auto LuaEngine = std::make_shared<TLuaEngine>();
|
||||
LuaEngine->SetServer(&Server);
|
||||
Application::Console().InitializeLuaConsole(*LuaEngine);
|
||||
|
||||
RegisterThread("Main");
|
||||
|
||||
beammp_trace("Running in debug mode on a debug build");
|
||||
TResourceManager ResourceManager;
|
||||
ResourceManager.RefreshFiles();
|
||||
TPPSMonitor PPSMonitor(Server);
|
||||
THeartbeatThread Heartbeat(ResourceManager, Server);
|
||||
TNetwork Network(Server, PPSMonitor, ResourceManager);
|
||||
|
||||
auto LuaEngine = std::make_shared<TLuaEngine>();
|
||||
LuaEngine->SetServer(&Server);
|
||||
Application::Console().InitializeLuaConsole(*LuaEngine);
|
||||
LuaEngine->SetNetwork(&Network);
|
||||
PPSMonitor.SetNetwork(Network);
|
||||
Application::CheckForUpdates();
|
||||
|
||||
TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine);
|
||||
|
||||
Application::SetSubsystemStatus("Main", Application::Status::Good);
|
||||
RegisterThread("Main(Waiting)");
|
||||
|
||||
std::set<std::string> IgnoreSubsystems {
|
||||
@ -214,6 +214,10 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
std::string SystemsBadList {};
|
||||
auto Statuses = Application::GetSubsystemStatuses();
|
||||
for (const auto& NameStatusPair : Statuses) {
|
||||
if (NameStatusPair.first == "Main") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
|
||||
continue; // ignore
|
||||
}
|
||||
@ -227,6 +231,8 @@ int BeamMPServerMain(MainArguments Arguments) {
|
||||
// remove ", "
|
||||
SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2);
|
||||
if (FullyStarted) {
|
||||
Application::SetSubsystemStatus("Main", Application::Status::Good);
|
||||
|
||||
if (!WithErrors) {
|
||||
beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY");
|
||||
} else {
|
||||
|
@ -13,6 +13,7 @@
|
||||
"nlohmann-json",
|
||||
"openssl",
|
||||
"rapidjson",
|
||||
"sol2"
|
||||
"sol2",
|
||||
"curl"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user