Compare commits

..

50 Commits

Author SHA1 Message Date
Tixx 89392a67c2 Bump version to 3.9.1 2026-03-08 18:16:10 +01:00
Tixx 86a1c50c8b Add dynamic export (#470)
Testing builds with -Wl,--export-dynamic

Closes #464 

---

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.
2026-03-08 18:09:42 +01:00
Tixx d97010c8ca Harden malformed vehicle packet parsing (#475)
## Summary
Reject malformed vehicle reset and paint packets before slicing
structured payloads, and catch parser exceptions in the TCP client loop.

## Problem
Or:<pid>-<vid>:null and similarly malformed Op packets could reach
substr(npos) in ParseVehicle, throwing std::out_of_range. On TCP, that
exception could escape the client loop and kill the thread.

## Changes
- Guard Or reset packets when no { payload exists
- Guard Op paint packets when no [ payload exists
- Add a small helper and unit test for structured payload extraction
- Catch std::exception around GlobalParser in the TCP client loop and
disconnect the offending client

## Verification
- git diff --check
- Full build/tests not run in this shell because cmake was not installed
2026-03-08 17:29:55 +01:00
Tixx 8a613e0b16 Prevent log spam and simplify code 2026-03-08 17:24:30 +01:00
alex 48d721f602 Harden malformed vehicle packet parsing 2026-03-07 21:23:00 -07:00
0R3Z da0089fb2e add export-dynamic flag 2026-02-19 21:15:14 +01:00
Tixx 3befc84f00 Add build deb13 (#469)
add/test Debian 13 build to github actions

Closes #464 

---

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.
2026-02-19 20:07:28 +01:00
0R3Z 48a77e9ea6 add debian 13 to release workflow 2026-02-02 12:52:42 +01:00
0R3Z f9c85a1f35 fix release workflow formatting 2026-02-02 12:51:01 +01:00
0R3Z 168a410129 add build Debian 13 for ARM64 2026-01-25 18:11:33 +01:00
0R3Z 4926e1aebd add support for Debian 13 on github workflows 2026-01-25 17:59:02 +01:00
0R3Z 1e2d8688a8 add installation scripts for Debian 13 2026-01-25 17:58:26 +01:00
Tixx 6f196aca64 Decrease likelyhood of lua stack corruption (#462)
Decreases the likelyhood of lua stack corruption.

---

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.
2026-01-18 21:56:31 +01:00
Tixx 99476a2c77 Revert "Add stack trace to server lua engine (#350)" 2026-01-15 14:44:13 +01:00
Tixx 9fa9974159 Lua version change to 5.3.5 (#458)
Update Lua version to make plugin development easier across platforms
and simplify Windows builds by using vcpkg.

---

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.
2026-01-10 21:39:52 +01:00
boubouleuh 3ef816845d Lua version change to 5.3.5 2026-01-10 21:09:05 +01:00
Tixx 6ec4106ec7 refactor: optimize string operations and improve code clarity (#451)
- Avoid redundant substr() calls in packet parsing hot-path
(TServer.cpp)
  The previous code called substr(3) twice per packet, creating
  unnecessary temporary strings. Now stores the result once.

- Replace .size() == 0 with .empty() for idiomatic C++
  (TConsole.cpp, TLuaEngine.cpp)
2026-01-02 00:42:03 +01:00
Tixx b094e35f8c Skip invalid socket when accept() fails (#457)
When Acceptor.accept() returns an error (e.g., "Too many open files"),
the server was continuing to process an invalid socket, causing resource
leaks and potential infinite error loops.
Add continue statement to skip processing when accept() fails, allowing
the server to retry on the next iteration instead of crashing.
Fixes resource exhaustion DoS vulnerability where server would enter
error loop instead of handling gracefully.
<img width="1233" height="199" alt="image"
src="https://github.com/user-attachments/assets/bad8f559-6ef2-47ee-b1c1-3e6020cdfb77"
/>

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-29 17:38:53 +01:00
wadyankaw 83afafc0c3 Skip invalid socket when accept() fails
When Acceptor.accept() returns an error (e.g., "Too many open files"),
the server was continuing to process an invalid socket, causing resource
leaks and potential infinite error loops.

Add continue statement to skip processing when accept() fails, allowing
the server to retry on the next iteration instead of crashing.

Fixes resource exhaustion DoS vulnerability where server would enter
error loop instead of handling gracefully.
2025-12-28 03:54:55 +03:00
Kipstz 31122abe10 Merge branch 'BeamMP:minor' into refactor-string-optimizations 2025-12-28 00:56:24 +01:00
Tixx 0615b57a37 Add message length validation for chat messages (#456)
Right now server only checks if chat message is empty but doesnt check
the max length. Client limits to 500 chars but if someone modifies the
client they can send huge messages. I added a check on server side to
reject messages longer than 500 characters, same as client limit. I used
translator because I don't know English well

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-28 00:35:43 +01:00
wadyankaw 4a8378427a Add message length validation for chat messages
Right now server only checks if chat message is empty but doesnt check the max length. Client limits to 500 chars but if someone modifies the client they can send huge messages.
I added a check on server side to reject messages longer than 500 characters, same as client limit.
I used translator because I don't know English well
2025-12-28 02:24:08 +03:00
Kipstz b74f0c7ca8 refactor: optimize string operations and improve code clarity
- Avoid redundant substr() calls in packet parsing hot-path (TServer.cpp)
  The previous code called substr(3) twice per packet, creating
  unnecessary temporary strings. Now stores the result once.

- Replace .size() == 0 with .empty() for idiomatic C++
  (TConsole.cpp, TLuaEngine.cpp)
2025-12-27 23:46:45 +01:00
Tixx 420c64f6cf Fix build (#444)
By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-12-20 20:13:15 +01:00
Tixx add45c085b Remove debian 11 2025-12-13 00:10:30 +01:00
Tixx 372076a4ef Set fetch-depth to 0 2025-11-30 00:12:55 +01:00
Tixx eb2deb73c1 Update vcpkg 2025-11-29 23:23:36 +01:00
Tixx 21874afb87 Downgrade sol2 and force windows ver to 10 2025-11-29 23:20:27 +01:00
Tixx 184d50bf8c Update vcpkg submodule 2025-10-21 22:11:46 +02:00
Tixx c4c894c1f7 Bump version v3.9.0 2025-10-20 22:41:29 +02:00
Tixx 039a44bba5 Bump version to v3.8.5 2025-07-31 17:26:14 +02:00
Tixx add0b86b37 Implement Dialog packet and add MP.ConfirmationDialog (#427)
This PR implements a new lua function and packet used for sends dialogs
to the client.

## Example:


https://github.com/user-attachments/assets/97bb5813-ea12-4b1d-a049-2f7ebf6b6da3

Example serverside code:
```lua
--MP.ConfirmationDialog(player_id: number, title: string, body: string, buttons: object, interaction_id: string, warning: boolean = false, reportToServer: boolean = true, reportToExtensions: boolean = true)

function onChatMessage(player_id, player_name, message)
    MP.ConfirmationDialog(player_id, "Warning", "Watch your tone buddy!!", 
        {
            {
                label = "OK",
                key = "dialogOK",
                isCancel = true
            }
        }, "interactionID", true)
end

MP.RegisterEvent("onChatMessage", "onChatMessage")


function dialogOK(player_id, interaction_id)
    MP.SendChatMessage(-1, MP.GetPlayerName(player_id) .. " clicked OK")
end

MP.RegisterEvent("dialogOK", "dialogOK")
```

### Details:
Each dialog can have multiple buttons, each button having it's own
callback event (`key`).
Each dialog can also have one button with `isCancel` being true,
settings this property to true causes the button's event to be called
when the users pressed `esc` to exit out of the dialog. If a dialog is
created without any button being the cancel button then the user will
only be able to exit the dialog by restarting the session or pressing
one of the buttons.

`interaction_id` will be sent as the event data with a button press
event, to track from which dialog the button press came. As when
multiple dialogs are opened they will stack and it will become difficult
to track what button on which dialog was pressed without having multiple
event handlers.


Waiting on https://github.com/BeamMP/BeamMP/pull/715 to be merged.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-06-26 06:52:18 +02:00
Tixx 403c1d5f78 Add support for reporting to options in ConfirmationDialog 2025-06-25 13:51:20 +02:00
Tixx 6318ca79e7 Implement Dialog packet and add MP.ConfirmationDialog 2025-06-25 13:22:05 +02:00
Tixx 2bd4ee9321 Self check functionality (#426)
This PR adds a new console command (`nettest`) that sends a request to
the server check api in order to test connectivity via the server's
public ip (serverlist entry).

- [x] https://github.com/BeamMP/ServerCheck/pull/2

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-06-24 22:21:25 +02:00
Tixx 22c0a966bb Add nettest command 2025-06-21 20:32:25 +02:00
Tixx 731599f16e Json vehicle state and apply paint packet (#416)
Converts the vehicle stored client side from a raw string to parsed json
data. This allows us to more easily edit the vehicle state serverside,
which I've started using in this PR for updating the state after a paint
packet.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-06-19 17:46:49 +02:00
Tixx 38c6766b2b Bump version to v3.8.4 2025-06-14 20:14:42 +02:00
Tixx bcb035bafc Provider env ip (#432)
Adds `BEAMMP_PROVIDER_IP_ENV` for hosting panels, which allows the
server owner to configure which env var is read to get the ip interface
to bind to.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-05-17 20:41:38 +02:00
Tixx 068f553fa9 Add BEAMMP_PROVIDER_IP_ENV 2025-05-17 01:04:55 +02:00
Tixx ca11f353b0 Log IP setting in debug mode 2025-05-17 01:04:05 +02:00
Tixx b7cf304d49 Client resource hash database and client resource protection (#430)
# Mod database
This PR adds a local database of mods, which is used to cache mod hashes
and protection status.

## Mod hash caching
Mod hashes will now be cached based on last write date. This will speed
up server startup because only the mods with changes will have to be
hashed.

## Mod protection
You can now protect mods! This will allow you to host a server with
copyrighted content without actually hosting the copyrighted content.
Just run `protectmod <filename with .zip> <true/false>` in the console
to protect a mod. Users that join a server with protected mods will have
to obtain the file themselves and put it in their launcher's resources
folder. The launcher will inform the user about this if the file is
missing.

## Mod reloading
You can now reload client mods while the server is running by using
`reloadmods` in the console. Keep in mind that this is mainly intended
for development, therefore it will **not** force client to rejoin and
neither will is hot-reload mods on the client.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2025-05-11 01:32:19 +02:00
Tixx 03d3b873c4 Update protectmod help message
Co-authored-by: SaltySnail <51403141+SaltySnail@users.noreply.github.com>
2025-05-10 21:16:35 +02:00
Tixx 40c8c0c5c2 Add protectmod and reloadmods console commands 2025-04-26 21:15:16 +02:00
Tixx 7db40e068e Replace obsolete function 2025-04-01 09:43:49 +02:00
Tixx 6053aa6192 Fix protected mod kick 2025-04-01 09:11:49 +02:00
Tixx 0bb18de9f6 Check for and remove cached hashes not in folder 2025-03-31 23:55:10 +02:00
Tixx 7a439bb5b9 Add mod hash caching and mod protection 2025-03-31 08:04:15 +02:00
Tixx 52a1d9a99e Update vehicle state after paint packet 2025-01-25 22:16:17 +01:00
Tixx 2f577a2358 Store vehicles in parsed json 2025-01-25 22:16:06 +01:00
36 changed files with 520 additions and 153 deletions
+13 -12
View File
@@ -3,11 +3,11 @@ name: Linux
on:
push:
branches:
- 'develop'
- 'minor'
- "develop"
- "minor"
pull_request:
env:
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
VCPKG_FORCE_SYSTEM_BINARIES: 1
CMAKE_BUILD_TYPE: "Release"
@@ -19,10 +19,10 @@ jobs:
strategy:
matrix:
include:
- distro: debian
version: 11
- distro: debian
version: 12
- distro: debian
version: 13
- distro: ubuntu
version: 22.04
- distro: ubuntu
@@ -32,7 +32,7 @@ jobs:
steps:
- name: get-cmake
uses: lukka/get-cmake@v3.28.1
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
with:
@@ -47,7 +47,8 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Git config safe directory
shell: bash
@@ -90,10 +91,10 @@ jobs:
strategy:
matrix:
include:
- distro: debian
version: 11
- distro: debian
version: 12
- distro: debian
version: 13
- distro: ubuntu
version: 22.04
- distro: ubuntu
@@ -103,7 +104,7 @@ jobs:
steps:
- name: get-cmake
uses: lukka/get-cmake@v3.28.1
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
with:
@@ -118,7 +119,8 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Git config safe directory
shell: bash
@@ -153,4 +155,3 @@ jobs:
- name: Test
run: ./bin/BeamMP-Server-tests
+18 -15
View File
@@ -3,7 +3,7 @@ on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
@@ -14,8 +14,8 @@ jobs:
create-release:
runs-on: ubuntu-latest
name: Create Release
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Create Release
id: create_release
@@ -38,10 +38,10 @@ jobs:
strategy:
matrix:
include:
- distro: debian
version: 11
- distro: debian
version: 12
- distro: debian
version: 13
- distro: ubuntu
version: 22.04
- distro: ubuntu
@@ -51,7 +51,7 @@ jobs:
steps:
- name: get-cmake
uses: lukka/get-cmake@v3.28.1
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
with:
@@ -66,7 +66,8 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Git config safe directory
shell: bash
@@ -82,7 +83,7 @@ jobs:
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
- name: Upload Release Asset
id: upload-release-asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -109,10 +110,10 @@ jobs:
strategy:
matrix:
include:
- distro: debian
version: 11
- distro: debian
version: 12
- distro: debian
version: 13
- distro: ubuntu
version: 22.04
- distro: ubuntu
@@ -125,7 +126,7 @@ jobs:
steps:
- name: get-cmake
uses: lukka/get-cmake@v3.28.1
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
with:
@@ -140,7 +141,8 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Git config safe directory
shell: bash
@@ -156,7 +158,7 @@ jobs:
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
- name: Upload Release Asset
id: upload-release-asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -193,7 +195,8 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Create Build Environment
shell: bash
@@ -204,7 +207,7 @@ jobs:
run: bash ./scripts/windows/2-build.sh
- name: Upload Release Asset
id: upload-release-asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1
View File
@@ -26,6 +26,7 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
fetch-depth: 0
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
+2 -1
View File
@@ -148,7 +148,8 @@ if(UNIX)
endif(UNIX)
if (WIN32)
add_compile_options("-D_WIN32_WINNT=0x0601")
add_compile_definitions(_WIN32_WINNT=0x0A00)
add_compile_options("/bigobj")
endif(WIN32)
+7 -3
View File
@@ -24,6 +24,7 @@
#include <queue>
#include <string>
#include <unordered_set>
#include <utility>
#include "BoostAliases.h"
#include "Common.h"
@@ -56,14 +57,14 @@ 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 SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
@@ -101,6 +102,8 @@ public:
[[nodiscard]] TServer& Server() const;
void UpdatePingTime();
int SecondsSinceLastPing();
void SetMagic(std::vector<uint8_t> magic) { mMagic = std::move(magic); }
[[nodiscard]] const std::vector<uint8_t>& GetMagic() const { return mMagic; }
private:
void InsertVehicle(int ID, const std::string& Data);
@@ -125,6 +128,7 @@ private:
std::string mDID;
int mID = -1;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now();
std::vector<uint8_t> mMagic;
};
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
+4 -2
View File
@@ -74,7 +74,7 @@ public:
static TConsole& Console() { return mConsole; }
static std::string ServerVersionString();
static const Version& ServerVersion() { return mVersion; }
static Version ClientMinimumVersion() { return Version { 2, 2, 0 }; }
static Version ClientMinimumVersion() { return Version { 2, 7, 0 }; }
static std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
@@ -86,6 +86,8 @@ public:
};
}
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();
@@ -127,7 +129,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 8, 3 };
static inline Version mVersion { 3, 9, 1 };
};
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
+1
View File
@@ -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);
+1
View File
@@ -36,6 +36,7 @@ namespace MP {
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);
+6
View File
@@ -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 };
+1 -3
View File
@@ -192,9 +192,7 @@ public:
for (const auto& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) {
auto Result = EnqueueFunctionCall(Event.first, Function, Arguments, EventName);
Results.push_back(Result);
AddResultToCheck(Result);
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments, EventName));
}
}
}
+2
View File
@@ -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();
+2 -2
View File
@@ -30,10 +30,10 @@ public:
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] std::string NewFileList() const;
[[nodiscard]] nlohmann::json GetMods() const { return mMods; }
void RefreshFiles();
void SetProtected(const std::string& ModName, bool Protected);
private:
size_t mMaxModSize = 0;
+1 -1
View File
@@ -44,7 +44,7 @@ public:
void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
size_t ClientCount() const;
void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network);
void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network, bool udp);
static void HandleEvent(TClient& c, const std::string& Data);
RWMutex& GetClientMutex() const { return mClientsMutex; }
+7 -4
View File
@@ -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?
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 liblua5.3-dev curl zip unzip tar cmake make git g++ ninja-build
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
git config --global --add safe.directory $(pwd)
git config --global --add safe.directory $(pwd)/vcpkg
git config --global --add safe.directory $(pwd)/deps/commandline
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server-tests
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -ex
cmake --build bin --parallel -t BeamMP-Server
objcopy --only-keep-debug bin/BeamMP-Server bin/BeamMP-Server.debug
objcopy --add-gnu-debuglink bin/BeamMP-Server bin/BeamMP-Server.debug
strip -s bin/BeamMP-Server
@@ -0,0 +1,8 @@
#!/bin/bash
set -ex
apt-get update -y
apt-get install -y liblua5.3-0 curl
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+1 -1
View File
@@ -4,4 +4,4 @@ set -ex
./vcpkg/bootstrap-vcpkg.sh
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections" -DBeamMP-Server_ENABLE_LTO=ON
cmake . -B bin $1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3 -g -Wl,-z,norelro -Wl,--hash-style=gnu -Wl,-z,noseparate-code -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--export-dynamic" -DBeamMP-Server_ENABLE_LTO=ON
+4 -4
View File
@@ -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);
}
@@ -98,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) {
@@ -108,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) {
+3
View File
@@ -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 "";
}
+43 -1
View File
@@ -241,6 +241,48 @@ std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::str
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);
@@ -251,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);
+6 -1
View File
@@ -258,7 +258,11 @@ void TConfig::ParseFromFile(std::string_view name) {
} else {
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
}
TryReadValue(data, "General", StrIP, EnvStrIP, Settings::Key::General_IP);
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);
@@ -309,6 +313,7 @@ void TConfig::PrintDebug() {
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) + "\"");
+67 -14
View File
@@ -24,6 +24,7 @@
#include "CustomAssert.h"
#include "LuaAPI.h"
#include "TLuaEngine.h"
#include "Http.h"
#include <ctime>
#include <lua.hpp>
@@ -159,7 +160,7 @@ void TConsole::ChangeToRegularConsole() {
}
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (n == 0 && args.size() != 0) {
if (n == 0 && !args.empty()) {
Application::Console().WriteRaw("This command expects no arguments.");
return false;
} else if (args.size() != n) {
@@ -197,7 +198,7 @@ void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& a
} else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
}
} else if (args.size() == 0) {
} else if (args.empty()) {
ChangeToLuaConsole(mDefaultStateId);
}
}
@@ -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))) {
@@ -370,7 +423,7 @@ void TConsole::Command_Settings(const std::string&, const std::vector<std::strin
settings set <category> <setting> <value> sets specified setting to value
)";
if (args.size() == 0) {
if (args.empty()) {
beammp_errorf("No arguments specified for command 'settings'!");
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
return;
@@ -652,7 +705,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
}
}
}
if (NonNilFutures.size() == 0) {
if (NonNilFutures.empty()) {
if (!IgnoreNotACommand) {
Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands.");
}
+11 -28
View File
@@ -30,16 +30,12 @@
#include <condition_variable>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <optional>
#include <random>
#include <sol/stack_core.hpp>
#include <thread>
#include <tuple>
TLuaEngine* LuaAPI::MP::Engine;
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn);
TLuaEngine::TLuaEngine()
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
@@ -131,7 +127,7 @@ void TLuaEngine::operator()() {
}
}
}
if (mLuaStates.size() == 0) {
if (mLuaStates.empty()) {
beammp_trace("No Lua states, event loop running extremely sparsely");
Application::SleepSafeSeconds(10);
} else {
@@ -496,7 +492,6 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::variadic_results LocalArgs = JsonStringToArray(Str);
for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler];
Fn = AddTraceback(mStateView, Fn);
if (Fn.valid()) {
auto LuaResult = Fn(LocalArgs);
auto Result = std::make_shared<TLuaResult>();
@@ -505,9 +500,7 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
Result->Result = LuaResult;
} else {
Result->Error = true;
sol::error Err = LuaResult;
Result->ErrorMessage = Err.what();
beammp_errorf("An error occured while executing local event handler \"{}\" for event \"{}\": {}", Handler, EventName, Result->ErrorMessage);
Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
}
Result->MarkAsReady();
Return.push_back(Result);
@@ -668,7 +661,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
@@ -876,6 +869,12 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
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();
});
@@ -1083,21 +1082,6 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
mEngine->RegisterEvent(EventName, mStateId, FunctionName);
}
static sol::protected_function AddTraceback(sol::state_view StateView, sol::protected_function RawFn) {
StateView["INTERNAL_ERROR_HANDLER"] = [](lua_State *L) {
auto Error = sol::stack::get<std::optional<std::string>>(L);
std::string ErrorString = "<Unknown error>";
if (Error.has_value()) {
ErrorString = Error.value();
}
auto DebugTracebackFn = sol::state_view(L).globals().get<sol::table>("debug").get<sol::protected_function>("traceback");
// 2 = start collecting the trace one above the current function (1=current function)
std::string Traceback = DebugTracebackFn(ErrorString, 2);
return sol::stack::push(L, Traceback);
};
return sol::protected_function(RawFn, StateView["INTERNAL_ERROR_HANDLER"]);
}
void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId);
while (!Application::IsShuttingDown()) {
@@ -1162,8 +1146,8 @@ void TLuaEngine::StateThreadData::operator()() {
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
Result->StateId = mStateId;
sol::state_view StateView(mState);
auto RawFn = StateView[FnName];
if (RawFn.valid() && RawFn.get_type() == sol::type::function) {
auto Fn = StateView[FnName];
if (Fn.valid() && Fn.get_type() == sol::type::function) {
std::vector<sol::object> LuaArgs;
for (const auto& Arg : Args) {
if (Arg.valueless_by_exception()) {
@@ -1198,7 +1182,6 @@ void TLuaEngine::StateThreadData::operator()() {
break;
}
}
auto Fn = AddTraceback(StateView, RawFn);
auto Res = Fn(sol::as_args(LuaArgs));
if (Res.valid()) {
Result->Error = false;
+52 -15
View File
@@ -32,6 +32,8 @@
#include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/v6_only.hpp>
#include <cstring>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <zlib.h>
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
@@ -146,23 +148,30 @@ void TNetwork::UDPServerMain() {
}
if (Client->GetID() == ID) {
// not initialized yet
if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) {
// same IP (just a sanity check)
if (remote_client_ep.address() == Client->GetTCPSock().remote_endpoint().address()) {
Client->SetUDPAddr(remote_client_ep);
Client->SetIsUDPConnected(true);
beammp_debugf("UDP connected for client {}", ID);
} else {
beammp_debugf("Denied initial UDP packet due to IP mismatch");
if (Client->GetUDPAddr() == ip::udp::endpoint {} && !Client->IsUDPConnected() && !Client->GetMagic().empty()) {
if (Data.size() != 66) {
beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
return false;
}
const std::vector Magic(Data.begin() + 2, Data.end());
if (Magic != Client->GetMagic()) {
beammp_debugf("Invalid value for UDP IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
return false;
}
Client->SetMagic({});
Client->SetUDPAddr(remote_client_ep);
Client->SetIsUDPConnected(true);
return false;
}
if (Client->GetUDPAddr() == remote_client_ep) {
Data.erase(Data.begin(), Data.begin() + 2);
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this, true);
} else {
beammp_debugf("Ignored UDP packet due to remote address mismatch");
beammp_debugf("Ignored UDP packet for Client {} due to remote address mismatch. Source: {}, Client: {}", ID, remote_client_ep.address().to_string(), Client->GetUDPAddr().address().to_string());
return false;
}
}
@@ -234,10 +243,11 @@ void TNetwork::TCPServerMain() {
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) {
beammp_errorf("Failed to accept() new client: {}", ec.message());
continue;
}
TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn));
ID.detach(); // TODO: Add to a queue and attempt to join periodically
ID.detach();
} catch (const std::exception& e) {
beammp_errorf("Exception in accept routine: {}", e.what());
}
@@ -660,7 +670,13 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
Client->Disconnect("TCPRcv failed");
break;
}
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this);
try {
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} catch (const std::exception& e) {
beammp_warnf("Failed to receive/parse packet via TCP from client {}: {}", Client->GetID(), e.what());
Client->Disconnect("Failed to parse packet");
break;
}
}
if (QueueSync.joinable())
@@ -751,6 +767,18 @@ void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
SyncResources(*LockedClient);
if (LockedClient->IsDisconnected())
return;
std::vector<unsigned char> buf(64);
int ret = RAND_bytes(buf.data(), buf.size());
if (ret != 1) {
unsigned long error = ERR_get_error();
beammp_errorf("RAND_bytes failed with error code {}", error);
beammp_assert(ret != 1);
return;
}
LockedClient->SetMagic(buf);
buf.insert(buf.begin(), 'U');
(void)Respond(*LockedClient, buf, true);
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect
beammp_info(LockedClient->GetName() + " : Connected");
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
@@ -786,7 +814,7 @@ 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.NewFileList();
std::string ToSend = mResourceManager.GetMods().dump();
beammp_debugf("Mod Info: {}", ToSend);
if (!TCPSend(c, StringToVector(ToSend))) {
ClientKick(c, "TCP Send 'SY' failed");
@@ -808,6 +836,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)) {
@@ -965,7 +1002,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);
}
}
+104 -4
View File
@@ -58,22 +58,58 @@ TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
}
std::string TResourceManager::NewFileList() const {
return mMods.dump();
}
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;
@@ -133,9 +169,73 @@ void TResourceManager::RefreshFiles() {
{ "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());
}
}
}
+96 -35
View File
@@ -28,6 +28,7 @@
#include <any>
#include <optional>
#include <sstream>
#include <utility>
#include <nlohmann/json.hpp>
@@ -53,7 +54,6 @@ static std::optional<std::pair<int, int>> GetPidVid(const std::string& str) {
}
return std::nullopt;
}
TEST_CASE("GetPidVid") {
SUBCASE("Valid singledigit") {
const auto MaybePidVid = GetPidVid("0-1");
@@ -120,7 +120,6 @@ TEST_CASE("GetPidVid") {
CHECK(!MaybePidVid);
}
}
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -161,7 +160,7 @@ size_t TServer::ClientCount() const {
return mClients.size();
}
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network, bool udp) {
constexpr std::string_view ABG = "ABG:";
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
@@ -195,12 +194,29 @@ 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 pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
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;
}
switch (Code) {
case 'H': // initial connection
if (udp) {
beammp_debugf("Received 'H' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (!Network.SyncClient(Client)) {
// TODO handle
}
@@ -214,12 +230,20 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
}
return;
case 'O':
if (udp) {
beammp_debugf("Received 'O' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Packet.size() > 1000) {
beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.size()));
}
ParseVehicle(*LockedClient, StringPacket, Network);
return;
case 'C': {
if (udp) {
beammp_debugf("Received 'C' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Packet.size() < 4 || std::find(Packet.begin() + 3, Packet.end(), ':') == Packet.end())
break;
const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size());
@@ -232,6 +256,10 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Message.size() > 500) {
beammp_debugf("Chat message too long from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
@@ -250,16 +278,35 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
return;
}
case 'E':
if (udp) {
beammp_debugf("Received 'E' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
HandleEvent(*LockedClient, StringPacket);
return;
case 'N':
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 pidVidPart = StringPacket.substr(3);
auto MaybePidVid = GetPidVid(pidVidPart.substr(0, pidVidPart.find(':')));
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;
}
@@ -278,6 +325,15 @@ void TServer::HandleEvent(TClient& c, const std::string& RawData) {
}
std::string Name = RawData.substr(2, NameDataSep - 2);
std::string Data = RawData.substr(NameDataSep + 1);
std::vector<std::string> exclude = {"onInit", "onFileChanged","onVehicleDeleted","onConsoleInput","onPlayerAuth","postPlayerAuth", "onPlayerDisconnect",
"onPlayerConnecting","onPlayerJoining","onPlayerJoin","onChatMessage","postChatMessage","onVehicleSpawn","postVehicleSpawn","onVehicleEdited", "postVehicleEdited",
"onVehicleReset","onVehiclePaintChanged","onShutdown"};
if (std::ranges::find(exclude, Name) != exclude.end()) {
beammp_debugf("Excluded event triggered by client '{}' ({}): '{}', ignoring.", c.GetName(), c.GetID(), Name);
return;
}
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data));
}
@@ -328,8 +384,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 {
@@ -414,7 +471,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('{'));
auto BracketPos = Data.find('{');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Or' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true);
}
@@ -443,9 +506,27 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('['));
auto BracketPos = Data.find('[');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Op' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
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;
}
@@ -461,42 +542,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) {
+5 -1
View File
@@ -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();
}
+1 -1
Submodule vcpkg updated: 6978381401...5bf0c55239
+14 -2
View File
@@ -14,6 +14,18 @@
"openssl",
"rapidjson",
"sol2",
"curl"
]
"curl",
"lua"
],
"overrides": [
{
"name": "sol2",
"version": "3.3.1"
},
{
"name": "lua",
"version": "5.3.5#6"
}
],
"builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966"
}