Compare commits

..

104 Commits

Author SHA1 Message Date
Tixx 43e86a1e22 Change to luajit 2026-02-19 22:08:06 +01:00
Tixx 70fc8436ed Remove lua version lock 2026-01-18 23:32:54 +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 ea9c808233 Bump version 2025-05-05 23:53:17 +02:00
Tixx 40bd050ca6 Prevent lua sending client events during downloading (#431)
This PR fixes an issue where players would get personal events during
downloads, which would corrupt the download and block the user from
being able to properly join the server.

---

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-05 23:46:20 +02:00
Tixx a0d75c01f0 Revert "support for nested lua handlers" (#428)
Reverts a PR that has been causing sol to crash.

---

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-05 23:46:11 +02:00
Tixx 8098431fad Prevent lua sending client events during downloading 2025-04-30 15:30:15 +02:00
Tixx 40c8c0c5c2 Add protectmod and reloadmods console commands 2025-04-26 21:15:16 +02:00
Tixx cd39f387c2 Make PluginMonitor not try to run non-lua files (#429)
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-04-19 19:21:08 -02:00
Tixx a5ca50866f Lowercase lua extension check 2025-04-19 21:59:50 +02:00
Tixx 10ea0cf59e Make PluginMonitor not try to run non-lua files 2025-04-13 13:14:25 +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 6c3174ac08 Add custom IP bind option (#425)
This PR adds an option in the server config to bind to a custom IP.

- [x] Review config comment and make sure that it is not confused with
port forwarding
- [x] Validate IP addresses

---

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-03-29 20:39:02 +01:00
Tixx 73e9595d14 Switch C-Style int cast to c++ style uint16 cast 2025-03-29 20:17:25 +01:00
Tixx 3f7cf7a258 Bump version 2025-03-16 08:08:40 +01:00
Tixx 093310c124 Update IP config comment 2025-03-15 22:36:13 +01:00
Tixx 6286457fa4 Revert "support for nested lua handlers" 2025-03-15 22:29:37 +01:00
Tixx 71b8a61c97 Add custom IP bind option 2025-03-15 20:45:48 +01:00
Tixx f0141e4fd3 Wait for lua and other systems (#421)
This PR makes it so that connections are denied if lua hasn't loaded
yet, and makes it so that lua waits for the server to load before
accessing then uninitialized memory.
---

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-02-20 17:07:54 +01:00
Tixx 00560f7646 Wait for the server to start before loading plugins or allowing connections 2025-02-15 21:47:17 +01:00
Tixx 27d50fc2b5 Switch to github arm runners (#418)
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-02-08 21:28:09 +01: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
Tixx 6014536f52 Switch to github arm runners 2025-01-25 21:28:15 +01:00
Tixx fbce8a946e Add ENV for missing settings (#415)
Add ENV for missing settings.

Issue: https://github.com/BeamMP/BeamMP-Server/issues/414

Please let me know if there are any issues.

---

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-01-19 20:40:18 +01:00
Tixx bd9b6212e2 Bump version 2025-01-19 13:41:14 +01:00
Tixx b112ee20d8 Force IPv4 for backend requests (#417)
Force the use of ipv4 for backend requests because the launcher doesn't
support ipv6 yet

---

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-01-19 13:38:36 +01:00
Tixx 8593aeb21d Force IPv4 2025-01-19 11:53:42 +01:00
pedrotski 840f9b9f9d Update src/TConfig.cpp
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2025-01-19 06:59:35 +08:00
Tixx ec21cbbe86 Bump version 2025-01-18 21:40:07 +01:00
Tixx e90f1af109 Fix crash (#413)
This PR fixes the segfault caused by rapidjson on the first heartbeat,
and the memory leak that happens during mod hashing.

---

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-01-18 21:36:54 +01:00
Tixx c78775bfd8 Add size header to information packet (#409)
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-01-18 20:52:20 +01:00
pedrotski 9c3042280d Update TConfig.cpp
Add ENV for missing settings.

Issue: https://github.com/BeamMP/BeamMP-Server/issues/414

Please let me know if there are any issues.
2025-01-19 00:32:20 +08:00
Tixx 4f2ef3c3a7 Fix hashing memory leak 2025-01-17 14:40:27 +01:00
Tixx 6a2ee052ba Fix curl post headers 2025-01-16 14:22:47 +01:00
Tixx 2658d0f785 Fix segfault by switching from rapidjson to nlohmann::json 2025-01-16 14:21:43 +01:00
Tixx a7eeda0569 Add size header to information packet 2025-01-12 16:55:38 +01:00
Tixx cd29f25435 Switch to curl (#405)
This PR converts all the http client code to use curl instead of
httplib. This will hopefully do something about the connectivity issues
with the backend and auth.

---

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-01-12 13:00:44 +01:00
Tixx 7c864d94b3 Fix heartbeat request 2025-01-11 22:27:42 +01:00
Tixx 26f1be0a51 Switch to curl 2025-01-11 22:18:50 +01:00
Tixx d7e75ae0c7 Fix windows build 2025-01-11 22:18:19 +01:00
Tixx 1d90f53527 Add curl 2025-01-11 22:18:19 +01:00
Tixx 7dd6b41642 Debug log responses from auth and backend (#403)
Logs the reponses from authentication and backend.

---

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-01-11 22:14:42 +01:00
Tixx d7f3bc8b9f Debug log responses from auth and backend 2024-12-23 18:18:42 +01:00
Tixx 687a988701 Fix build failure with Boost >= v1.87.0 (#402)
Closes #401

Replace deprecated `ip::address::from_string` with `ip::make_address` to
avoid build errors with Boost versions >= 1.87.0

---

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.
2024-12-21 20:30:02 +01:00
Mathijs 9046b5a4d3 Fix build failure with Boost >= v1.87.0
Closes #401
2024-12-18 14:12:38 +01:00
Tixx b4d4967529 Bump version 2024-12-09 09:52:58 +01:00
Tixx 51c24b82fe remove sentry leftovers (#392)
Just cleaning up some sentry related code, mentions, etc.

---

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.
2024-11-30 21:19:48 +01:00
Tixx 5e13f9dd2d fix crash when double closing (#383) 2024-11-30 21:14:09 +01:00
Tixx f8d66c4336 Re-Add BEAMMP_PROVIDER_PORT_ENV (#399)
#398

---

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.
2024-11-30 20:23:31 +01:00
Tixx 9875defe86 Re-Add BEAMMP_PROVIDER_PORT_ENV 2024-11-30 18:04:23 +01:00
Lion fc208770dd Fix postPlayerAuth and add reason value (#395)
Closes #393 and #394
This PR cleans up and fixes the code in TNetwork::Authentication and
sends the kick reason to lua.
The event is now `function postPlayerAuth(Kicked: bool, Reason: string,
Name: string, Role: string, Guest: bool, Identifiers: table)`

---

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.
2024-11-25 10:15:20 +01:00
Tixx 99a51808a0 Fix postPlayerAuth not running until after leaving 2024-11-25 00:33:10 +01:00
Tixx 1c07cf83b2 Fix postPlayerAuth and add reason value 2024-11-24 22:28:32 +01:00
0R3Z 99136f133a remove sentry leftovers 2024-11-16 19:02:50 +01:00
Tixx 6c9d58582b Swap build instruction sequence (#391)
Swap the sequence of the build instruction steps 2 and 3 so it actually
works

No AI involved, i used my own brain to mess it up and to fix it 😉

---

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.
2024-11-14 22:40:26 +01:00
O1LER 4edae00998 Swap build instruction sequence 2024-11-14 18:29:30 +01:00
Lion 17e9c05f46 add PR template 2024-11-13 16:21:49 +01:00
Lion 71e3cb83ae Allow for empty icon param in MP.SendNotification (#389)
This PR allows you to call MP.SendNotification with only a player ID and
a message, removing the requirement for an icon.
2024-11-12 13:32:58 +01:00
Tixx fb2e26bd28 Allow for empty icon param in MP.SendNotification 2024-11-12 12:35:12 +01:00
Tixx 976ab68ca3 Bump version 2024-11-05 23:13:46 +01:00
Tixx 58a76e1df2 Add some information about tags in the build instructions (#386) 2024-11-05 10:30:05 +01:00
O1LER c696276fc3 Update README.md
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-11-02 23:30:22 +01:00
O1LER 6ce0608bb3 Third times the charm 2024-11-02 14:35:17 +01:00
O1LER 52ad237419 Fix bracket skill issue 2024-11-02 14:33:52 +01:00
O1LER 71038dc617 Hide links 2024-11-02 14:32:47 +01:00
O1LER dc3bb517a3 Update README.md
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-11-02 14:29:48 +01:00
O1LER 7b2c48c8d4 Add some information about tags in the build instructions 2024-11-02 14:04:09 +01:00
Lion ef557ebfc4 Fix backend heartbeat (#385) 2024-11-01 17:40:33 +01:00
Tixx b2e953b92a Fix JSON heartbeat request 2024-11-01 17:30:17 +01:00
Lion Kortlepel 576d765557 fix crash when double closing 2024-11-01 15:07:54 +01:00
39 changed files with 677 additions and 313 deletions
+6
View 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.
+3 -5
View File
@@ -19,8 +19,6 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian
version: 11
- distro: debian - distro: debian
version: 12 version: 12
- distro: ubuntu - distro: ubuntu
@@ -48,6 +46,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -84,14 +83,12 @@ jobs:
run: ./bin/BeamMP-Server-tests run: ./bin/BeamMP-Server-tests
arm64-matrix: arm64-matrix:
runs-on: [Linux, ARM64] runs-on: ubuntu-22.04-arm
env: env:
VCPKG_DEFAULT_TRIPLET: "arm64-linux" VCPKG_DEFAULT_TRIPLET: "arm64-linux"
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian
version: 11
- distro: debian - distro: debian
version: 12 version: 12
- distro: ubuntu - distro: ubuntu
@@ -119,6 +116,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
+4 -5
View File
@@ -38,8 +38,6 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian
version: 11
- distro: debian - distro: debian
version: 12 version: 12
- distro: ubuntu - distro: ubuntu
@@ -67,6 +65,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -104,13 +103,11 @@ jobs:
asset_content_type: application/x-elf asset_content_type: application/x-elf
arm64-matrix: arm64-matrix:
runs-on: [Linux, ARM64] runs-on: ubuntu-22.04-arm
needs: create-release needs: create-release
strategy: strategy:
matrix: matrix:
include: include:
- distro: debian
version: 11
- distro: debian - distro: debian
version: 12 version: 12
- distro: ubuntu - distro: ubuntu
@@ -141,6 +138,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Git config safe directory - name: Git config safe directory
shell: bash shell: bash
@@ -194,6 +192,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Create Build Environment - name: Create Build Environment
shell: bash shell: bash
+1
View File
@@ -26,6 +26,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0
- name: Setup vcpkg - name: Setup vcpkg
uses: lukka/run-vcpkg@v11 uses: lukka/run-vcpkg@v11
-1
View File
@@ -1,5 +1,4 @@
.idea/ .idea/
.sentry-native/
*.orig *.orig
*.toml *.toml
boost_* boost_*
+10 -4
View File
@@ -80,14 +80,15 @@ set(PRJ_SOURCES
src/ChronoWrapper.cpp src/ChronoWrapper.cpp
) )
find_package(Lua REQUIRED) # fix for sol2
find_path(LUAJIT_INCLUDE_DIR lua.hpp PATH_SUFFIXES luajit)
# set the source file containing main() # set the source file containing main()
set(PRJ_MAIN src/main.cpp) set(PRJ_MAIN src/main.cpp)
# set the source file containing the test's main # set the source file containing the test's main
set(PRJ_TEST_MAIN test/test_main.cpp) set(PRJ_TEST_MAIN test/test_main.cpp)
# set include paths not part of libraries # set include paths not part of libraries
set(PRJ_INCLUDE_DIRS ${LUA_INCLUDE_DIR}) set(PRJ_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIR})
# set compile features (e.g. standard version) # set compile features (e.g. standard version)
set(PRJ_COMPILE_FEATURES cxx_std_20) set(PRJ_COMPILE_FEATURES cxx_std_20)
# set #defines (test enable/disable not included here) # set #defines (test enable/disable not included here)
@@ -104,9 +105,12 @@ set(PRJ_LIBRARIES
httplib::httplib httplib::httplib
libzip::zip libzip::zip
OpenSSL::SSL OpenSSL::Crypto OpenSSL::SSL OpenSSL::Crypto
${LUA_LIBRARIES} CURL::libcurl
lua51
) )
link_directories(${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib)
# add dependency find_package calls and similar here # add dependency find_package calls and similar here
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
@@ -116,6 +120,7 @@ find_package(httplib CONFIG REQUIRED)
find_package(libzip CONFIG REQUIRED) find_package(libzip CONFIG REQUIRED)
find_package(RapidJSON CONFIG REQUIRED) find_package(RapidJSON CONFIG REQUIRED)
find_package(sol2 CONFIG REQUIRED) find_package(sol2 CONFIG REQUIRED)
find_package(CURL CONFIG REQUIRED)
add_subdirectory("deps/toml11") add_subdirectory("deps/toml11")
include_directories(include) include_directories(include)
@@ -146,7 +151,8 @@ if(UNIX)
endif(UNIX) endif(UNIX)
if (WIN32) if (WIN32)
add_compile_options("-D_WIN32_WINNT=0x0601") add_compile_definitions(_WIN32_WINNT=0x0A00)
add_compile_options("/bigobj") add_compile_options("/bigobj")
endif(WIN32) endif(WIN32)
+2 -2
View File
@@ -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`. 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. 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`). 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 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 -DSENTRY_BACKEND=none` . `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. 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. 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`). 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`).
+5 -4
View File
@@ -38,7 +38,7 @@ If you need support with understanding the codebase, please write us in the Disc
## About Building from Source ## 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 ## 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`. 1. Check out the repository with git: `git clone --recursive https://github.com/BeamMP/BeamMP-Server`.
2. Go into the directory `cd 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. 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. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`. 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. Your executable can be found in `bin/`. 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. When you make changes to the code, you only have to run step 4 again.
### Building for FreeBSD ### Building for FreeBSD
+7 -3
View File
@@ -24,6 +24,7 @@
#include <queue> #include <queue>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include "BoostAliases.h" #include "BoostAliases.h"
#include "Common.h" #include "Common.h"
@@ -56,14 +57,14 @@ public:
~TClient(); ~TClient();
TClient& operator=(const TClient&) = delete; TClient& operator=(const TClient&) = delete;
void AddNewCar(int Ident, const std::string& Data); void AddNewCar(int Ident, const nlohmann::json& Data);
void SetCarData(int Ident, const std::string& Data); void SetCarData(int Ident, const nlohmann::json& Data);
void SetCarPosition(int Ident, const std::string& Data); void SetCarPosition(int Ident, const std::string& Data);
TVehicleDataLockPair GetAllCars(); TVehicleDataLockPair GetAllCars();
void SetName(const std::string& Name) { mName = Name; } void SetName(const std::string& Name) { mName = Name; }
void SetRoles(const std::string& Role) { mRole = Role; } void SetRoles(const std::string& Role) { mRole = Role; }
void SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; } 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); std::string GetCarPositionRaw(int Ident);
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; } void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; }
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); } void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
@@ -101,6 +102,8 @@ public:
[[nodiscard]] TServer& Server() const; [[nodiscard]] TServer& Server() const;
void UpdatePingTime(); void UpdatePingTime();
int SecondsSinceLastPing(); int SecondsSinceLastPing();
void SetMagic(std::vector<uint8_t> magic) { mMagic = std::move(magic); }
[[nodiscard]] const std::vector<uint8_t>& GetMagic() const { return mMagic; }
private: private:
void InsertVehicle(int ID, const std::string& Data); void InsertVehicle(int ID, const std::string& Data);
@@ -125,6 +128,7 @@ private:
std::string mDID; std::string mDID;
int mID = -1; int mID = -1;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime = std::chrono::high_resolution_clock::now(); 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); std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
+7 -5
View File
@@ -74,7 +74,7 @@ public:
static TConsole& Console() { return mConsole; } static TConsole& Console() { return mConsole; }
static std::string ServerVersionString(); static std::string ServerVersionString();
static const Version& ServerVersion() { return mVersion; } 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 std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; } static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
@@ -82,11 +82,13 @@ public:
static std::vector<std::string> GetBackendUrlsInOrder() { static std::vector<std::string> GetBackendUrlsInOrder() {
return { 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 std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates(); static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str); static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
@@ -127,10 +129,11 @@ private:
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 7, 0 }; static inline Version mVersion { 3, 9, 0 };
}; };
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out); 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); std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str); void RegisterThread(const std::string& str);
@@ -139,7 +142,6 @@ void RegisterThread(const std::string& str);
#define KB 1024llu #define KB 1024llu
#define MB (KB * 1024llu) #define MB (KB * 1024llu)
#define GB (MB * 1024llu) #define GB (MB * 1024llu)
#define SSU_UNRAW SECRET_SENTRY_URL
#define _file_basename std::filesystem::path(__FILE__).filename().string() #define _file_basename std::filesystem::path(__FILE__).filename().string()
#define _line std::to_string(__LINE__) #define _line std::to_string(__LINE__)
+1
View File
@@ -27,6 +27,7 @@ enum class Key {
PROVIDER_UPDATE_MESSAGE, PROVIDER_UPDATE_MESSAGE,
PROVIDER_DISABLE_CONFIG, PROVIDER_DISABLE_CONFIG,
PROVIDER_PORT_ENV, PROVIDER_PORT_ENV,
PROVIDER_IP_ENV
}; };
std::optional<std::string> Get(Key key); std::optional<std::string> Get(Key key);
+3 -2
View File
@@ -23,6 +23,7 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <curl/curl.h>
#if defined(BEAMMP_LINUX) #if defined(BEAMMP_LINUX)
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -38,8 +39,8 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace Http { namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); std::string GET(const std::string& url, 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 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 { namespace Status {
std::string ToString(int code); std::string ToString(int code);
} }
+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> 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> 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> 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); std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue); void Set(int ConfigID, sol::object NewValue);
TLuaValue Get(int ConfigID); TLuaValue Get(int ConfigID);
+3
View File
@@ -6,6 +6,9 @@
#include <limits> #include <limits>
#include <unordered_map> #include <unordered_map>
#undef max
#undef min
namespace prof { namespace prof {
using Duration = std::chrono::duration<double, std::milli>; using Duration = std::chrono::duration<double, std::milli>;
+1 -2
View File
@@ -68,8 +68,6 @@ struct Settings {
// Keys have their TOML section name as prefix // Keys have their TOML section name as prefix
// [Misc] // [Misc]
Misc_SendErrorsShowMessage,
Misc_SendErrors,
Misc_ImScaredOfUpdates, Misc_ImScaredOfUpdates,
Misc_UpdateReminderTime, Misc_UpdateReminderTime,
@@ -81,6 +79,7 @@ struct Settings {
General_Map, General_Map,
General_AuthKey, General_AuthKey,
General_Private, General_Private,
General_IP,
General_Port, General_Port,
General_MaxCars, General_MaxCars,
General_LogChat, General_LogChat,
+6
View File
@@ -59,6 +59,9 @@ private:
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args); 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_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_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); void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n); 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); } }, { "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 { "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); } }, { "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 }; 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& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) { for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) { if (Event.first != IgnoreId) {
auto Result = EnqueueFunctionCall(Event.first, Function, Arguments, EventName); Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments, EventName));
Results.push_back(Result);
AddResultToCheck(Result);
} }
} }
} }
+2
View File
@@ -45,6 +45,8 @@ public:
void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel); void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel);
void UpdatePlayer(TClient& Client); void UpdatePlayer(TClient& Client);
TResourceManager& ResourceManager() const { return mResourceManager; }
private: private:
void UDPServerMain(); void UDPServerMain();
void TCPServerMain(); void TCPServerMain();
+2 -2
View File
@@ -30,10 +30,10 @@ public:
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; } [[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
[[nodiscard]] std::string FileSizes() const { return mFileSizes; } [[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; } [[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] nlohmann::json GetMods() const { return mMods; }
[[nodiscard]] std::string NewFileList() const;
void RefreshFiles(); void RefreshFiles();
void SetProtected(const std::string& ModName, bool Protected);
private: private:
size_t mMaxModSize = 0; size_t mMaxModSize = 0;
+1 -1
View File
@@ -44,7 +44,7 @@ public:
void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn); void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
size_t ClientCount() const; 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); static void HandleEvent(TClient& c, const std::string& Data);
RWMutex& GetClientMutex() const { return mClientsMutex; } RWMutex& GetClientMutex() const { return mClientsMutex; }
+7 -4
View File
@@ -18,11 +18,12 @@
#pragma once #pragma once
#include <nlohmann/json.hpp>
#include <string> #include <string>
class TVehicleData final { class TVehicleData final {
public: public:
TVehicleData(int ID, std::string Data); TVehicleData(int ID, nlohmann::json Data);
~TVehicleData(); ~TVehicleData();
// We cannot delete this, since vector needs to be able to copy when it resizes. // 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, // 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]] bool IsInvalid() const { return mID == -1; }
[[nodiscard]] int ID() const { return mID; } [[nodiscard]] int ID() const { return mID; }
[[nodiscard]] std::string Data() const { return mData; } [[nodiscard]] nlohmann::json Data() const { return mData; }
void SetData(const std::string& Data) { mData = Data; } [[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; } bool operator==(const TVehicleData& v) const { return mID == v.mID; }
private: private:
int mID { -1 }; int mID { -1 };
std::string mData; nlohmann::json mData;
}; };
// TODO: unused now, remove? // TODO: unused now, remove?
+15 -11
View File
@@ -57,7 +57,7 @@ int TClient::GetOpenCarID() const {
return OpenID; 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); std::unique_lock lock(mVehicleDataMutex);
mVehicleData.emplace_back(Ident, Data); mVehicleData.emplace_back(Ident, Data);
} }
@@ -79,13 +79,17 @@ std::string TClient::GetCarPositionRaw(int Ident) {
void TClient::Disconnect(std::string_view Reason) { void TClient::Disconnect(std::string_view Reason) {
beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason); beammp_debugf("Disconnecting client {} for reason: {}", GetID(), Reason);
boost::system::error_code ec; boost::system::error_code ec;
mSocket.shutdown(socket_base::shutdown_both, ec); if (mSocket.is_open()) {
if (ec) { mSocket.shutdown(socket_base::shutdown_both, ec);
beammp_debugf("Failed to shutdown client socket: {}", ec.message()); if (ec) {
} beammp_debugf("Failed to shutdown client socket: {}", ec.message());
mSocket.close(ec); }
if (ec) { mSocket.close(ec);
beammp_debugf("Failed to close client socket: {}", ec.message()); 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; mVehiclePosition[size_t(Ident)] = Data;
} }
std::string TClient::GetCarData(int Ident) { nlohmann::json TClient::GetCarData(int Ident) {
{ // lock { // lock
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
for (auto& v : mVehicleData) { for (auto& v : mVehicleData) {
@@ -104,10 +108,10 @@ std::string TClient::GetCarData(int Ident) {
} }
} // unlock } // unlock
DeleteCar(Ident); 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 { // lock
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
for (auto& v : mVehicleData) { for (auto& v : mVehicleData) {
+8 -1
View File
@@ -215,7 +215,7 @@ void Application::CheckForUpdates() {
// checks current version against latest version // checks current version against latest version
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
for (const auto& url : GetBackendUrlsInOrder()) { 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); bool Matches = std::regex_match(Response, VersionRegex);
if (Matches) { if (Matches) {
auto MyVersion = ServerVersion(); 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)); 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 STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024;
static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024; static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024;
+3
View File
@@ -39,6 +39,9 @@ std::string_view Env::ToString(Env::Key key) {
case Key::PROVIDER_PORT_ENV: case Key::PROVIDER_PORT_ENV:
return "BEAMMP_PROVIDER_PORT_ENV"; return "BEAMMP_PROVIDER_PORT_ENV";
break; break;
case Key::PROVIDER_IP_ENV:
return "BEAMMP_PROVIDER_IP_ENV";
break;
} }
return ""; return "";
} }
+69 -48
View File
@@ -29,67 +29,88 @@
#include <stdexcept> #include <stdexcept>
using json = nlohmann::json; 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) { static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
for (uint8_t i = 0; i < CONNECTION_AMOUNT; i++) { std::string* Result = reinterpret_cast<std::string*>(userp);
if (connectionInfo.host == connections[i].host std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
&& connectionInfo.port == connections[i].port) { *Result += NewContents;
beammp_tracef("Old client reconnected, with ip {} and port {}", connectionInfo.host, connectionInfo.port); return size * nmemb;
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];
} }
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { std::string Http::GET(const std::string& url, unsigned int* status) {
std::shared_ptr<httplib::SSLClient> client = getClient({ host, port }); std::string Ret;
client->enable_server_certificate_verification(false); static thread_local CURL* curl = curl_easy_init();
client->set_address_family(AF_INET); if (curl) {
auto res = client->Get(target.c_str()); CURLcode res;
if (res) { char errbuf[CURL_ERROR_SIZE];
if (status) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
*status = res->status; 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 { } else {
beammp_error("Curl easy init failed");
return Http::ErrorString; 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::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::shared_ptr<httplib::SSLClient> client = getClient({ host, port }); std::string Ret;
client->set_read_timeout(std::chrono::seconds(10)); static thread_local CURL* curl = curl_easy_init();
beammp_assert(client->is_valid()); if (curl) {
client->enable_server_certificate_verification(false); CURLcode res;
client->set_address_family(AF_INET); char errbuf[CURL_ERROR_SIZE];
auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
if (res) { curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if (status) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
*status = res->status; 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 { } else {
beammp_debug("POST failed: " + httplib::to_string(res.error())); beammp_error("Curl easy init failed");
return Http::ErrorString; return Http::ErrorString;
} }
return Ret;
} }
// RFC 2616, RFC 7231 // RFC 2616, RFC 7231
+49 -2
View File
@@ -148,6 +148,11 @@ static inline std::pair<bool, std::string> InternalTriggerClientEvent(int Player
return { false, "Invalid Player ID" }; return { false, "Invalid Player ID" };
} }
auto c = MaybeClient.value().lock(); 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)) { if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_lua_errorf("Respond failed, dropping client {}", PlayerID); beammp_lua_errorf("Respond failed, dropping client {}", PlayerID);
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets"); LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
@@ -207,7 +212,7 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
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> LuaAPI::MP::SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
std::string Packet = "N" + Category + ":" + Icon + ":" + Message; std::string Packet = "n" + Category + ":" + Icon + ":" + Message;
if (ID == -1) { if (ID == -1) {
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true); Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
Result.first = true; Result.first = true;
@@ -236,6 +241,48 @@ std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::str
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> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
auto MaybeClient = GetClient(Engine->Server(), PID); auto MaybeClient = GetClient(Engine->Server(), PID);
@@ -246,7 +293,7 @@ std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
return Result; return Result;
} }
auto c = MaybeClient.value().lock(); 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); std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", PID, VID));
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true); Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
+2 -4
View File
@@ -28,6 +28,7 @@ Settings::Settings() {
{ General_Map, std::string("/levels/gridmap_v2/info.json") }, { General_Map, std::string("/levels/gridmap_v2/info.json") },
{ General_AuthKey, std::string("") }, { General_AuthKey, std::string("") },
{ General_Private, true }, { General_Private, true },
{ General_IP, "::"},
{ General_Port, 30814 }, { General_Port, 30814 },
{ General_MaxCars, 1 }, { General_MaxCars, 1 },
{ General_LogChat, true }, { General_LogChat, true },
@@ -35,8 +36,6 @@ Settings::Settings() {
{ General_Debug, false }, { General_Debug, false },
{ General_AllowGuests, true }, { General_AllowGuests, true },
{ General_InformationPacket, true }, { General_InformationPacket, true },
{ Misc_SendErrorsShowMessage, true },
{ Misc_SendErrors, true },
{ Misc_ImScaredOfUpdates, true }, { Misc_ImScaredOfUpdates, true },
{ Misc_UpdateReminderTime, "30s" } { Misc_UpdateReminderTime, "30s" }
}; };
@@ -49,6 +48,7 @@ Settings::Settings() {
{ { "General", "Map" }, { General_Map, READ_WRITE } }, { { "General", "Map" }, { General_Map, READ_WRITE } },
{ { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } }, { { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } },
{ { "General", "Private" }, { General_Private, READ_ONLY } }, { { "General", "Private" }, { General_Private, READ_ONLY } },
{ { "General", "IP" }, { General_IP, READ_ONLY } },
{ { "General", "Port" }, { General_Port, READ_ONLY } }, { { "General", "Port" }, { General_Port, READ_ONLY } },
{ { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } }, { { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } },
{ { "General", "LogChat" }, { General_LogChat, READ_ONLY } }, { { "General", "LogChat" }, { General_LogChat, READ_ONLY } },
@@ -56,8 +56,6 @@ Settings::Settings() {
{ { "General", "Debug" }, { General_Debug, READ_WRITE } }, { { "General", "Debug" }, { General_Debug, READ_WRITE } },
{ { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } }, { { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
{ { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } }, { { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } },
{ { "Misc", "SendErrorsShowMessage" }, { Misc_SendErrorsShowMessage, READ_WRITE } },
{ { "Misc", "SendErrors" }, { Misc_SendErrors, READ_WRITE } },
{ { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } }, { { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
{ { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } } { { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } }
}; };
+19 -11
View File
@@ -34,6 +34,8 @@ static constexpr std::string_view StrDebug = "Debug";
static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG"; static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG";
static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view StrPrivate = "Private";
static constexpr std::string_view EnvStrPrivate = "BEAMMP_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 StrPort = "Port";
static constexpr std::string_view EnvStrPort = "BEAMMP_PORT"; static constexpr std::string_view EnvStrPort = "BEAMMP_PORT";
static constexpr std::string_view StrMaxCars = "MaxCars"; static constexpr std::string_view StrMaxCars = "MaxCars";
@@ -61,10 +63,10 @@ static constexpr std::string_view EnvStrInformationPacket = "BEAMMP_INFORMATION_
static constexpr std::string_view StrPassword = "Password"; static constexpr std::string_view StrPassword = "Password";
// Misc // 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 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 StrUpdateReminderTime = "UpdateReminderTime";
static constexpr std::string_view EnvStrUpdateReminderTime = "BEAMMP_UPDATE_REMINDER_TIME";
TEST_CASE("TConfig::TConfig") { TEST_CASE("TConfig::TConfig") {
const std::string CfgFile = "beammp_server_testconfig.toml"; const std::string CfgFile = "beammp_server_testconfig.toml";
@@ -138,6 +140,8 @@ void TConfig::FlushToFile() {
data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket); data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests); data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests"); 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"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name); 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."); 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.");
@@ -152,12 +156,8 @@ void TConfig::FlushToFile() {
// Misc // Misc
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates); 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."); 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); 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"][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; std::stringstream Ss;
Ss << "# This is the BeamMP-Server config file.\n" Ss << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://docs.beammp.com/server/server-maintenance/`\n" "# Help & Documentation: `https://docs.beammp.com/server/server-maintenance/`\n"
@@ -253,7 +253,16 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug); TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug);
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private); TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private);
TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket); TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket);
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port); 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", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers); TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map); TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map);
@@ -265,10 +274,8 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat); TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat);
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests); TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests);
// Misc // Misc
TryReadValue(data, "Misc", StrSendErrors, "", Settings::Key::Misc_SendErrors); TryReadValue(data, "Misc", StrHideUpdateMessages, EnvStrHideUpdateMessages, Settings::Key::Misc_ImScaredOfUpdates);
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Settings::Key::Misc_ImScaredOfUpdates); TryReadValue(data, "Misc", StrUpdateReminderTime, EnvStrUpdateReminderTime, Settings::Key::Misc_UpdateReminderTime);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Settings::Key::Misc_SendErrorsShowMessage);
TryReadValue(data, "Misc", StrUpdateReminderTime, "", Settings::Key::Misc_UpdateReminderTime);
} catch (const std::exception& err) { } catch (const std::exception& err) {
beammp_error("Error parsing config file value: " + std::string(err.what())); beammp_error("Error parsing config file value: " + std::string(err.what()));
@@ -306,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(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(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(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(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(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\""); beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\"");
+68 -16
View File
@@ -24,6 +24,7 @@
#include "CustomAssert.h" #include "CustomAssert.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include "Http.h"
#include <ctime> #include <ctime>
#include <lua.hpp> #include <lua.hpp>
@@ -159,7 +160,7 @@ void TConsole::ChangeToRegularConsole() {
} }
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) { 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."); Application::Console().WriteRaw("This command expects no arguments.");
return false; return false;
} else if (args.size() != n) { } else if (args.size() != n) {
@@ -197,7 +198,7 @@ void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& a
} else { } else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua."); 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); ChangeToLuaConsole(mDefaultStateId);
} }
} }
@@ -208,16 +209,18 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
} }
static constexpr const char* sHelpString = R"( static constexpr const char* sHelpString = R"(
Commands: Commands:
help displays this help help displays this help
exit shuts down the server exit shuts down the server
kick <name> [reason] kicks specified player with an optional reason kick <name> [reason] kicks specified player with an optional reason
list lists all players and info about them list lists all players and info about them
say <message> sends the message to all players in chat say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua 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 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 status how the server is doing and what it's up to
clear clears the console window clear clears the console window
version displays the server version)"; 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)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
} }
@@ -257,11 +260,60 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
Application::Console().WriteRaw("Platform: " + platform); Application::Console().WriteRaw("Platform: " + platform);
Application::Console().WriteRaw("Server: v" + Application::ServerVersionString()); Application::Console().WriteRaw("Server: v" + Application::ServerVersionString());
std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE); Application::Console().WriteRaw(LUA_RELEASE);
Application::Console().WriteRaw(lua_version);
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH); std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version); 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) { void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 1, size_t(-1))) { if (!EnsureArgsCount(args, 1, size_t(-1))) {
@@ -370,7 +422,7 @@ void TConsole::Command_Settings(const std::string&, const std::vector<std::strin
settings set <category> <setting> <value> sets specified setting to value 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'!"); beammp_errorf("No arguments specified for command 'settings'!");
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
return; return;
@@ -652,7 +704,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
} }
} }
} }
if (NonNilFutures.size() == 0) { if (NonNilFutures.empty()) {
if (!IgnoreNotACommand) { if (!IgnoreNotACommand) {
Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands."); Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands.");
} }
+22 -22
View File
@@ -24,12 +24,8 @@
#include "Http.h" #include "Http.h"
// #include "SocketIO.h" // #include "SocketIO.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
#include <sstream> #include <sstream>
namespace json = rapidjson;
void THeartbeatThread::operator()() { void THeartbeatThread::operator()() {
RegisterThread("Heartbeat"); RegisterThread("Heartbeat");
std::string Body; std::string Body;
@@ -63,15 +59,19 @@ void THeartbeatThread::operator()() {
auto Target = "/heartbeat"; auto Target = "/heartbeat";
unsigned int ResponseCode = 0; unsigned int ResponseCode = 0;
json::Document Doc; nlohmann::json Doc;
bool Ok = false; bool Ok = false;
for (const auto& Url : Application::GetBackendUrlsInOrder()) { for (const auto& Url : Application::GetBackendUrlsInOrder()) {
T = Http::POST(Url, 443, Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } }); T = Http::POST(Url + Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } });
Doc.Parse(T.data(), T.size());
if (Doc.HasParseError() || !Doc.IsObject()) { 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)) { if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
beammp_trace("Backend response failed to parse as valid json"); beammp_trace("Backend response failed to parse as valid json");
beammp_trace("Response was: `" + T + "`");
} }
} else if (ResponseCode != 200) { } else if (ResponseCode != 200) {
beammp_errorf("Response code from the heartbeat: {}", ResponseCode); beammp_errorf("Response code from the heartbeat: {}", ResponseCode);
@@ -90,18 +90,18 @@ void THeartbeatThread::operator()() {
const auto MessageKey = "msg"; const auto MessageKey = "msg";
if (Ok) { if (Ok) {
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) { if (Doc.contains(StatusKey) && Doc[StatusKey].is_string()) {
Status = Doc[StatusKey].GetString(); Status = Doc[StatusKey];
} else { } else {
Ok = false; Ok = false;
} }
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) { if (Doc.contains(CodeKey) && Doc[CodeKey].is_string()) {
Code = Doc[CodeKey].GetString(); Code = Doc[CodeKey];
} else { } else {
Ok = false; Ok = false;
} }
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) { if (Doc.contains(MessageKey) && Doc[MessageKey].is_string()) {
Message = Doc[MessageKey].GetString(); Message = Doc[MessageKey];
} else { } else {
Ok = false; Ok = false;
} }
@@ -140,19 +140,19 @@ void THeartbeatThread::operator()() {
std::string THeartbeatThread::GenerateCall() { std::string THeartbeatThread::GenerateCall() {
nlohmann::json Ret = { nlohmann::json Ret = {
{ "players", mServer.ClientCount() }, { "players", std::to_string(mServer.ClientCount()) },
{ "maxplayers", Application::Settings.getAsInt(Settings::Key::General_MaxPlayers) }, { "maxplayers", std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) },
{ "port", Application::Settings.getAsInt(Settings::Key::General_Port) }, { "port", std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) },
{ "map", Application::Settings.getAsString(Settings::Key::General_Map) }, { "map", Application::Settings.getAsString(Settings::Key::General_Map) },
{ "private", Application::Settings.getAsBool(Settings::Key::General_Private) }, { "private", Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false" },
{ "version", Application::ServerVersionString() }, { "version", Application::ServerVersionString() },
{ "clientversion", Application::ClientMinimumVersion().AsString() }, { "clientversion", Application::ClientMinimumVersion().AsString() },
{ "name", Application::Settings.getAsString(Settings::Key::General_Name) }, { "name", Application::Settings.getAsString(Settings::Key::General_Name) },
{ "tags", Application::Settings.getAsString(Settings::Key::General_Tags) }, { "tags", Application::Settings.getAsString(Settings::Key::General_Tags) },
{ "guests", Application::Settings.getAsBool(Settings::Key::General_AllowGuests) }, { "guests", Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false" },
{ "modlist", mResourceManager.TrimmedList() }, { "modlist", mResourceManager.TrimmedList() },
{ "modstotalsize", mResourceManager.MaxModSize() }, { "modstotalsize", std::to_string(mResourceManager.MaxModSize()) },
{ "modstotal", mResourceManager.ModsLoaded() }, { "modstotal", std::to_string(mResourceManager.ModsLoaded()) },
{ "playerslist", GetPlayers() }, { "playerslist", GetPlayers() },
{ "desc", Application::Settings.getAsString(Settings::Key::General_Description) } { "desc", Application::Settings.getAsString(Settings::Key::General_Description) }
}; };
+22 -53
View File
@@ -30,28 +30,12 @@
#include <condition_variable> #include <condition_variable>
#include <fmt/core.h> #include <fmt/core.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <optional>
#include <random> #include <random>
#include <sol/stack_core.hpp>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
TLuaEngine* LuaAPI::MP::Engine; 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() TLuaEngine::TLuaEngine()
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") { : mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting); Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
@@ -80,10 +64,11 @@ TEST_CASE("TLuaEngine ctor & dtor") {
void TLuaEngine::operator()() { void TLuaEngine::operator()() {
RegisterThread("LuaEngine"); RegisterThread("LuaEngine");
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
// lua engine main thread // lua engine main thread
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE); beammp_infof(LUA_RELEASE);
CollectAndInitPlugins(); CollectAndInitPlugins();
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
// now call all onInit's // now call all onInit's
auto Futures = TriggerEvent("onInit", ""); auto Futures = TriggerEvent("onInit", "");
WaitForAll(Futures, std::chrono::seconds(5)); WaitForAll(Futures, std::chrono::seconds(5));
@@ -142,7 +127,7 @@ void TLuaEngine::operator()() {
} }
} }
} }
if (mLuaStates.size() == 0) { if (mLuaStates.empty()) {
beammp_trace("No Lua states, event loop running extremely sparsely"); beammp_trace("No Lua states, event loop running extremely sparsely");
Application::SleepSafeSeconds(10); Application::SleepSafeSeconds(10);
} else { } else {
@@ -506,11 +491,8 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
sol::variadic_results LocalArgs = JsonStringToArray(Str); sol::variadic_results LocalArgs = JsonStringToArray(Str);
for (const auto& Handler : MyHandlers) { for (const auto& Handler : MyHandlers) {
auto Res = GetLuaHandler(mStateView, Handler, EventName); auto Fn = mStateView[Handler];
if (Res.has_value()) { if (Fn.valid()) {
sol::function Fn = Res.value();
Fn = AddTraceback(mStateView, Fn);
auto LuaResult = Fn(LocalArgs); auto LuaResult = Fn(LocalArgs);
auto Result = std::make_shared<TLuaResult>(); auto Result = std::make_shared<TLuaResult>();
if (LuaResult.valid()) { if (LuaResult.valid()) {
@@ -518,9 +500,7 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string
Result->Result = LuaResult; Result->Result = LuaResult;
} else { } else {
Result->Error = true; Result->Error = true;
sol::error Err = LuaResult; Result->ErrorMessage = "Function result in TriggerGlobalEvent was invalid";
Result->ErrorMessage = Err.what();
beammp_errorf("An error occured while executing local event handler \"{}\" for event \"{}\": {}", Handler, EventName, Result->ErrorMessage);
} }
Result->MarkAsReady(); Result->MarkAsReady();
Return.push_back(Result); Return.push_back(Result);
@@ -562,9 +542,8 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
sol::table Result = mStateView.create_table(); sol::table Result = mStateView.create_table();
int i = 1; int i = 1;
for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) { for (const auto& Handler : mEngine->GetEventHandlersForState(EventName, mStateId)) {
auto Res = GetLuaHandler(mStateView, Handler, EventName); auto Fn = mStateView[Handler];
if (Res.has_value()) { if (Fn.valid() && Fn.get_type() == sol::type::function) {
sol::function Fn = Res.value();
auto FnRet = Fn(EventArgs); auto FnRet = Fn(EventArgs);
if (FnRet.valid()) { if (FnRet.valid()) {
Result.set(i, FnRet); Result.set(i, FnRet);
@@ -682,7 +661,7 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerVehicles(int ID) {
sol::state_view StateView(mState); sol::state_view StateView(mState);
sol::table Result = StateView.create_table(); sol::table Result = StateView.create_table();
for (const auto& v : VehicleData) { 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; return Result;
} else } else
@@ -880,14 +859,22 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
}); });
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage); MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) { MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
if (Args.size() == 3) { 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)); 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) { } 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)); LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(3));
} else { } else {
beammp_lua_error("SendNotification expects 2 or 3 arguments."); 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 { MPTable.set_function("GetPlayers", [&]() -> sol::table {
return Lua_GetPlayers(); return Lua_GetPlayers();
}); });
@@ -1095,21 +1082,6 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
mEngine->RegisterEvent(EventName, mStateId, FunctionName); 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()() { void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId); RegisterThread("Lua:" + mStateId);
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
@@ -1174,10 +1146,8 @@ void TLuaEngine::StateThreadData::operator()() {
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc // TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
Result->StateId = mStateId; Result->StateId = mStateId;
sol::state_view StateView(mState); sol::state_view StateView(mState);
auto Fn = StateView[FnName];
auto Res = GetLuaHandler(StateView, FnName, TheQueuedFunction.EventName); if (Fn.valid() && Fn.get_type() == sol::type::function) {
if (Res.has_value()) {
sol::function Fn = Res.value();
std::vector<sol::object> LuaArgs; std::vector<sol::object> LuaArgs;
for (const auto& Arg : Args) { for (const auto& Arg : Args) {
if (Arg.valueless_by_exception()) { if (Arg.valueless_by_exception()) {
@@ -1212,7 +1182,6 @@ void TLuaEngine::StateThreadData::operator()() {
break; break;
} }
} }
Fn = AddTraceback(StateView, Fn);
auto Res = Fn(sol::as_args(LuaArgs)); auto Res = Fn(sol::as_args(LuaArgs));
if (Res.valid()) { if (Res.valid()) {
Result->Error = false; Result->Error = false;
+95 -43
View File
@@ -32,6 +32,8 @@
#include <boost/asio/ip/address_v6.hpp> #include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/v6_only.hpp> #include <boost/asio/ip/v6_only.hpp>
#include <cstring> #include <cstring>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <zlib.h> #include <zlib.h>
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
@@ -85,9 +87,17 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
void TNetwork::UDPServerMain() { void TNetwork::UDPServerMain() {
RegisterThread("UDPServer"); 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; 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); mUDPSock.open(UdpListenEndpoint.protocol(), ec);
if (ec) { if (ec) {
beammp_error("open() failed: " + ec.message()); beammp_error("open() failed: " + ec.message());
@@ -107,7 +117,7 @@ void TNetwork::UDPServerMain() {
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); 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")); + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
while (!Application::IsShuttingDown()) { while (!Application::IsShuttingDown()) {
try { try {
@@ -138,23 +148,30 @@ void TNetwork::UDPServerMain() {
} }
if (Client->GetID() == ID) { if (Client->GetID() == ID) {
// not initialized yet if (Client->GetUDPAddr() == ip::udp::endpoint {} && !Client->IsUDPConnected() && !Client->GetMagic().empty()) {
if (Client->GetUDPAddr() == ip::udp::endpoint {} || !Client->IsUDPConnected()) { if (Data.size() != 66) {
// same IP (just a sanity check) beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
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");
return false; 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) { if (Client->GetUDPAddr() == remote_client_ep) {
Data.erase(Data.begin(), Data.begin() + 2); 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 { } 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; return false;
} }
} }
@@ -170,12 +187,17 @@ void TNetwork::UDPServerMain() {
void TNetwork::TCPServerMain() { void TNetwork::TCPServerMain() {
RegisterThread("TCPServer"); 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; 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); Listener.open(ListenEp.protocol(), ec);
if (ec) { if (ec) {
beammp_errorf("Failed to open socket: {}", ec.message()); beammp_errorf("Failed to open socket: {}", ec.message());
@@ -209,6 +231,7 @@ void TNetwork::TCPServerMain() {
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good); 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"); beammp_info("Vehicle event network online");
do { do {
try { try {
@@ -220,10 +243,11 @@ void TNetwork::TCPServerMain() {
ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec); ip::tcp::socket ClientSocket = Acceptor.accept(ClientEp, ec);
if (ec) { if (ec) {
beammp_errorf("Failed to accept() new client: {}", ec.message()); beammp_errorf("Failed to accept() new client: {}", ec.message());
continue;
} }
TConnection Conn { std::move(ClientSocket), ClientEp }; TConnection Conn { std::move(ClientSocket), ClientEp };
std::thread ID(&TNetwork::Identify, this, std::move(Conn)); 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) { } catch (const std::exception& e) {
beammp_errorf("Exception in accept routine: {}", e.what()); beammp_errorf("Exception in accept routine: {}", e.what());
} }
@@ -258,8 +282,16 @@ void TNetwork::Identify(TConnection&& RawConnection) {
write(RawConnection.Socket, buffer("P"), ec); write(RawConnection.Socket, buffer("P"), ec);
return; return;
} else if (Code == 'I') { } 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; boost::system::error_code ec;
write(RawConnection.Socket, buffer(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : ""), ec); write(RawConnection.Socket, buffer(ToSend), ec);
} else { } else {
beammp_errorf("Invalid code got in Identify: '{}'", Code); beammp_errorf("Invalid code got in Identify: '{}'", Code);
} }
@@ -299,6 +331,11 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
Client->SetIdentifier("ip", ip); Client->SetIdentifier("ip", ip);
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6"); 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..."); beammp_info("Identifying new ClientConnection...");
auto Data = TCPRcv(*Client); auto Data = TCPRcv(*Client);
@@ -346,7 +383,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
auto Target = "/pkToUser"; auto Target = "/pkToUser";
unsigned int ResponseCode = 0; 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) { } catch (const std::exception& e) {
beammp_debugf("Invalid json sent by client, kicking: {}", e.what()); beammp_debugf("Invalid json sent by client, kicking: {}", e.what());
@@ -354,6 +391,8 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
return nullptr; return nullptr;
} }
beammp_debug("Response from authentication backend: " + AuthResStr);
try { try {
nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr);
@@ -430,35 +469,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."; Reason = "No guests are allowed on this server! To join, sign up at: forum.beammp.com.";
} }
bool Allowed = true; if (!NotAllowed && !NotAllowedWithReason && mServer.ClientCount() >= size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) && !BypassLimit) {
if (NotAllowed) { NotAllowedWithReason = true;
Allowed = false; Reason = "Server full!";
} }
if (NotAllowedWithReason) { if (NotAllowedWithReason) {
Allowed = false;
}
if (NotAllowed) {
ClientKick(*Client, "you are not allowed on the server!");
} else if (NotAllowedWithReason) {
ClientKick(*Client, Reason); 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) { if (!NotAllowed && !NotAllowedWithReason) {
return {};
} else if (mServer.ClientCount() < size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) || BypassLimit) {
beammp_info("Identification success"); beammp_info("Identification success");
mServer.InsertClient(Client); mServer.InsertClient(Client);
TCPClient(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; return Client;
} }
@@ -639,7 +670,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
Client->Disconnect("TCPRcv failed"); Client->Disconnect("TCPRcv failed");
break; break;
} }
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this); mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} }
if (QueueSync.joinable()) if (QueueSync.joinable())
@@ -730,6 +761,18 @@ void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
SyncResources(*LockedClient); SyncResources(*LockedClient);
if (LockedClient->IsDisconnected()) if (LockedClient->IsDisconnected())
return; 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 (void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect
beammp_info(LockedClient->GetName() + " : Connected"); beammp_info(LockedClient->GetName() + " : Connected");
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID())); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
@@ -765,7 +808,7 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
case 'S': case 'S':
if (SubCode == 'R') { if (SubCode == 'R') {
beammp_debug("Sending Mod Info"); beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.NewFileList(); std::string ToSend = mResourceManager.GetMods().dump();
beammp_debugf("Mod Info: {}", ToSend); beammp_debugf("Mod Info: {}", ToSend);
if (!TCPSend(c, StringToVector(ToSend))) { if (!TCPSend(c, StringToVector(ToSend))) {
ClientKick(c, "TCP Send 'SY' failed"); ClientKick(c, "TCP Send 'SY' failed");
@@ -787,6 +830,15 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
return; return;
} }
auto FileName = fs::path(UnsafeName).filename().string(); 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; FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;
if (!std::filesystem::exists(FileName)) { if (!std::filesystem::exists(FileName)) {
@@ -944,7 +996,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
res = false; res = false;
return 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);
} }
} }
+19 -14
View File
@@ -57,21 +57,26 @@ void TPluginMonitor::operator()() {
mFileTimes[Pair.first] = CurrentTime; mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server // grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) { if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_infof("File \"{}\" changed, reloading", Pair.first); if (LowerString(fs::path(Pair.first).extension().string()) == ".lua") {
// is in root folder, so reload beammp_infof("File \"{}\" changed, reloading", Pair.first);
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary); // is in root folder, so reload
auto Size = std::filesystem::file_size(Pair.first); std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Contents = std::make_shared<std::string>(); auto Size = std::filesystem::file_size(Pair.first);
Contents->resize(Size); auto Contents = std::make_shared<std::string>();
FileStream.read(Contents->data(), Contents->size()); Contents->resize(Size);
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string()); FileStream.read(Contents->data(), Contents->size());
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path()); TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto Res = mEngine->EnqueueScript(StateID, Chunk); auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
Res->WaitUntilReady(); auto Res = mEngine->EnqueueScript(StateID, Chunk);
if (Res->Error) { Res->WaitUntilReady();
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage); 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 { } 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)); mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
} }
} else { } else {
+106 -4
View File
@@ -58,22 +58,58 @@ TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
} }
std::string TResourceManager::NewFileList() const {
return mMods.dump();
}
void TResourceManager::RefreshFiles() { void TResourceManager::RefreshFiles() {
mMods.clear(); mMods.clear();
std::unique_lock Lock(mModsMutex); std::unique_lock Lock(mModsMutex);
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client"; 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)) { for (const auto& entry : fs::directory_iterator(Path)) {
std::string File(entry.path().string()); 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())) { if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
beammp_warnf("'{}' is not a ZIP file and will be ignored", File); beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
continue; 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 { try {
EVP_MD_CTX* mdctx; EVP_MD_CTX* mdctx;
const EVP_MD* md; const EVP_MD* md;
@@ -121,6 +157,8 @@ void TResourceManager::RefreshFiles() {
} }
EVP_MD_CTX_free(mdctx); EVP_MD_CTX_free(mdctx);
stream.close();
std::string result; std::string result;
for (size_t i = 0; i < sha256_len; i++) { for (size_t i = 0; i < sha256_len; i++) {
result += fmt::format("{:02x}", sha256_value[i]); result += fmt::format("{:02x}", sha256_value[i]);
@@ -131,9 +169,73 @@ void TResourceManager::RefreshFiles() {
{ "file_size", std::filesystem::file_size(File) }, { "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" }, { "hash_algorithm", "sha256" },
{ "hash", result }, { "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) { } catch (const std::exception& e) {
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what()); 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());
}
}
} }
+80 -32
View File
@@ -161,7 +161,7 @@ size_t TServer::ClientCount() const {
return mClients.size(); 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:"; constexpr std::string_view ABG = "ABG:";
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) { 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()); Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
@@ -195,12 +195,29 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
// V to Y // V to Y
if (Code <= 89 && Code >= 86) { 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(); PPSMonitor.IncrementInternalPPS();
Network.SendToAll(LockedClient.get(), Packet, false, false); Network.SendToAll(LockedClient.get(), Packet, false, false);
return; return;
} }
switch (Code) { switch (Code) {
case 'H': // initial connection 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)) { if (!Network.SyncClient(Client)) {
// TODO handle // TODO handle
} }
@@ -214,12 +231,20 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
} }
return; return;
case 'O': case 'O':
if (udp) {
beammp_debugf("Received 'O' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
if (Packet.size() > 1000) { if (Packet.size() > 1000) {
beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.size())); beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.size()));
} }
ParseVehicle(*LockedClient, StringPacket, Network); ParseVehicle(*LockedClient, StringPacket, Network);
return; return;
case 'C': { 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()) if (Packet.size() < 4 || std::find(Packet.begin() + 3, Packet.end(), ':') == Packet.end())
break; break;
const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size()); const auto PacketAsString = std::string(reinterpret_cast<const char*>(Packet.data()), Packet.size());
@@ -232,6 +257,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()); beammp_debugf("Empty chat message received from '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return; 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); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Message);
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1)); LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), PacketAsString.substr(PacketAsString.find(':', 3) + 1));
@@ -250,17 +279,35 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
return; return;
} }
case 'E': case 'E':
if (udp) {
beammp_debugf("Received 'E' packet over UDP from client '{}' ({}), ignoring it", LockedClient->GetName(), LockedClient->GetID());
return;
}
HandleEvent(*LockedClient, StringPacket); HandleEvent(*LockedClient, StringPacket);
return; return;
case 'N': case 'N':
beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
Network.SendToAll(LockedClient.get(), Packet, false, true); Network.SendToAll(LockedClient.get(), Packet, false, true);
return; return;
case 'Z': // position packet case 'Z': { // position packet
PPSMonitor.IncrementInternalPPS(); 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); Network.SendToAll(LockedClient.get(), Packet, false, false);
HandlePosition(*LockedClient, StringPacket); HandlePosition(*LockedClient, StringPacket);
return; return;
}
default: default:
return; return;
} }
@@ -279,6 +326,15 @@ void TServer::HandleEvent(TClient& c, const std::string& RawData) {
} }
std::string Name = RawData.substr(2, NameDataSep - 2); std::string Name = RawData.substr(2, NameDataSep - 2);
std::string Data = RawData.substr(NameDataSep + 1); 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)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data));
} }
@@ -329,8 +385,9 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}); });
bool SpawnConfirmed = false; bool SpawnConfirmed = false;
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) { auto CarJsonDoc = nlohmann::json::parse(CarJson, nullptr, false);
c.AddNewCar(CarID, Packet); if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn && !CarJsonDoc.is_discarded()) {
c.AddNewCar(CarID, CarJsonDoc);
Network.SendToAll(nullptr, StringToVector(Packet), true, true); Network.SendToAll(nullptr, StringToVector(Packet), true, true);
SpawnConfirmed = true; SpawnConfirmed = true;
} else { } else {
@@ -447,6 +504,17 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
Data = Data.substr(Data.find('[')); Data = Data.substr(Data.find('['));
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data)); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true); 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; return;
} }
@@ -462,42 +530,22 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
beammp_error("Malformed packet received, no '{' found"); beammp_error("Malformed packet received, no '{' found");
return; return;
} }
std::string Packet = pckt.substr(FoundPos); std::string Packet = pckt.substr(FoundPos);
std::string VD = c.GetCarData(VID); nlohmann::json VD = c.GetCarData(VID);
if (VD.empty()) { if (VD == nlohmann::detail::value_t::null) {
beammp_error("Tried to apply change to vehicle that does not exist"); beammp_error("Tried to apply change to vehicle that does not exist");
return; return;
} }
std::string Header = VD.substr(0, VD.find('{'));
FoundPos = VD.find('{'); nlohmann::json Pack = nlohmann::json::parse(Packet, nullptr, false);
if (FoundPos == std::string::npos) {
return; if (Pack.is_discarded()) {
}
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()) {
beammp_error("Could not get active vehicle config!"); beammp_error("Could not get active vehicle config!");
return; return;
} }
for (auto& M : Pack.GetObject()) { c.SetCarData(VID, Pack);
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());
} }
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) { void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
+5 -1
View File
@@ -21,7 +21,7 @@
#include "Common.h" #include "Common.h"
#include <utility> #include <utility>
TVehicleData::TVehicleData(int ID, std::string Data) TVehicleData::TVehicleData(int ID, nlohmann::json Data)
: mID(ID) : mID(ID)
, mData(std::move(Data)) { , mData(std::move(Data)) {
beammp_trace("vehicle " + std::to_string(mID) + " constructed"); beammp_trace("vehicle " + std::to_string(mID) + " constructed");
@@ -30,3 +30,7 @@ TVehicleData::TVehicleData(int ID, std::string Data)
TVehicleData::~TVehicleData() { TVehicleData::~TVehicleData() {
beammp_trace("vehicle " + std::to_string(mID) + " destroyed"); 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();
}
+10 -5
View File
@@ -182,10 +182,6 @@ int BeamMPServerMain(MainArguments Arguments) {
TServer Server(Arguments.List); TServer Server(Arguments.List);
auto LuaEngine = std::make_shared<TLuaEngine>();
LuaEngine->SetServer(&Server);
Application::Console().InitializeLuaConsole(*LuaEngine);
RegisterThread("Main"); RegisterThread("Main");
beammp_trace("Running in debug mode on a debug build"); beammp_trace("Running in debug mode on a debug build");
@@ -194,13 +190,16 @@ int BeamMPServerMain(MainArguments Arguments) {
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);
auto LuaEngine = std::make_shared<TLuaEngine>();
LuaEngine->SetServer(&Server);
Application::Console().InitializeLuaConsole(*LuaEngine);
LuaEngine->SetNetwork(&Network); LuaEngine->SetNetwork(&Network);
PPSMonitor.SetNetwork(Network); PPSMonitor.SetNetwork(Network);
Application::CheckForUpdates(); Application::CheckForUpdates();
TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine); TPluginMonitor PluginMonitor(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server", LuaEngine);
Application::SetSubsystemStatus("Main", Application::Status::Good);
RegisterThread("Main(Waiting)"); RegisterThread("Main(Waiting)");
std::set<std::string> IgnoreSubsystems { std::set<std::string> IgnoreSubsystems {
@@ -215,6 +214,10 @@ int BeamMPServerMain(MainArguments Arguments) {
std::string SystemsBadList {}; std::string SystemsBadList {};
auto Statuses = Application::GetSubsystemStatuses(); auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) { for (const auto& NameStatusPair : Statuses) {
if (NameStatusPair.first == "Main") {
continue;
}
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) { if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
continue; // ignore continue; // ignore
} }
@@ -228,6 +231,8 @@ int BeamMPServerMain(MainArguments Arguments) {
// remove ", " // remove ", "
SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2); SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2);
if (FullyStarted) { if (FullyStarted) {
Application::SetSubsystemStatus("Main", Application::Status::Good);
if (!WithErrors) { if (!WithErrors) {
beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY"); beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY");
} else { } else {
+1 -1
Submodule vcpkg updated: 6978381401...5bf0c55239
+11 -2
View File
@@ -13,6 +13,15 @@
"nlohmann-json", "nlohmann-json",
"openssl", "openssl",
"rapidjson", "rapidjson",
"sol2" "sol2",
] "curl",
"luajit"
],
"overrides": [
{
"name": "sol2",
"version": "3.3.1"
}
],
"builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966"
} }