mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-07-13 10:13:42 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70967a81a3 | |||
| 93a477e9c3 | |||
| 2c05a442ee |
@@ -1,6 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -19,12 +19,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- distro: debian
|
||||||
|
version: 11
|
||||||
- distro: debian
|
- distro: debian
|
||||||
version: 12
|
version: 12
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 24.04
|
version: 20.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -46,7 +48,6 @@ 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
|
||||||
@@ -62,13 +63,13 @@ jobs:
|
|||||||
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
||||||
path: ./bin/BeamMP-Server
|
path: ./bin/BeamMP-Server
|
||||||
|
|
||||||
- name: Archive server debug info artifact
|
- name: Archive server debug info artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.x86_64
|
||||||
path: ./bin/BeamMP-Server.debug
|
path: ./bin/BeamMP-Server.debug
|
||||||
@@ -83,18 +84,20 @@ jobs:
|
|||||||
run: ./bin/BeamMP-Server-tests
|
run: ./bin/BeamMP-Server-tests
|
||||||
|
|
||||||
arm64-matrix:
|
arm64-matrix:
|
||||||
runs-on: ubuntu-22.04-arm
|
runs-on: [Linux, ARM64]
|
||||||
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
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 24.04
|
version: 20.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -116,7 +119,6 @@ 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
|
||||||
@@ -132,13 +134,13 @@ jobs:
|
|||||||
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
run: bash ./scripts/${{ matrix.distro }}-${{ matrix.version }}/3-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
name: BeamMP-Server.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
||||||
path: ./bin/BeamMP-Server
|
path: ./bin/BeamMP-Server
|
||||||
|
|
||||||
- name: Archive server debug info artifact
|
- name: Archive server debug info artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
name: debuginfo.${{ matrix.distro }}.${{ matrix.version }}.arm64
|
||||||
path: ./bin/BeamMP-Server.debug
|
path: ./bin/BeamMP-Server.debug
|
||||||
|
|||||||
@@ -38,12 +38,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- distro: debian
|
||||||
|
version: 11
|
||||||
- distro: debian
|
- distro: debian
|
||||||
version: 12
|
version: 12
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 24.04
|
version: 20.04
|
||||||
container:
|
container:
|
||||||
image: ${{ matrix.distro }}:${{ matrix.version }}
|
image: ${{ matrix.distro }}:${{ matrix.version }}
|
||||||
steps:
|
steps:
|
||||||
@@ -65,7 +67,6 @@ 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
|
||||||
@@ -103,17 +104,19 @@ jobs:
|
|||||||
asset_content_type: application/x-elf
|
asset_content_type: application/x-elf
|
||||||
|
|
||||||
arm64-matrix:
|
arm64-matrix:
|
||||||
runs-on: ubuntu-22.04-arm
|
runs-on: [Linux, ARM64]
|
||||||
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
|
||||||
version: 22.04
|
version: 22.04
|
||||||
- distro: ubuntu
|
- distro: ubuntu
|
||||||
version: 24.04
|
version: 20.04
|
||||||
env:
|
env:
|
||||||
VCPKG_DEFAULT_TRIPLET: "arm64-linux"
|
VCPKG_DEFAULT_TRIPLET: "arm64-linux"
|
||||||
VCPKG_FORCE_SYSTEM_BINARIES: 1
|
VCPKG_FORCE_SYSTEM_BINARIES: 1
|
||||||
@@ -138,7 +141,6 @@ 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
|
||||||
@@ -192,7 +194,6 @@ 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
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ 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
|
||||||
@@ -42,7 +41,7 @@ jobs:
|
|||||||
run: bash ./scripts/windows/2-build.sh
|
run: bash ./scripts/windows/2-build.sh
|
||||||
|
|
||||||
- name: Archive server artifact
|
- name: Archive server artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: BeamMP-Server-windows
|
name: BeamMP-Server-windows
|
||||||
path: ./bin/Release/BeamMP-Server.exe
|
path: ./bin/Release/BeamMP-Server.exe
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.idea/
|
.idea/
|
||||||
|
.sentry-native/
|
||||||
*.orig
|
*.orig
|
||||||
*.toml
|
*.toml
|
||||||
boost_*
|
boost_*
|
||||||
|
|||||||
@@ -4,6 +4,3 @@
|
|||||||
[submodule "vcpkg"]
|
[submodule "vcpkg"]
|
||||||
path = vcpkg
|
path = vcpkg
|
||||||
url = https://github.com/Microsoft/vcpkg.git
|
url = https://github.com/Microsoft/vcpkg.git
|
||||||
[submodule "deps/toml11"]
|
|
||||||
path = deps/toml11
|
|
||||||
url = https://github.com/ToruNiina/toml11
|
|
||||||
|
|||||||
+11
-17
@@ -20,7 +20,7 @@ include(cmake/Git.cmake)
|
|||||||
### SETTINGS ###
|
### SETTINGS ###
|
||||||
|
|
||||||
# add all headers (.h, .hpp) to this
|
# add all headers (.h, .hpp) to this
|
||||||
set(PRJ_HEADERS
|
set(PRJ_HEADERS
|
||||||
include/ArgsParser.h
|
include/ArgsParser.h
|
||||||
include/BoostAliases.h
|
include/BoostAliases.h
|
||||||
include/Client.h
|
include/Client.h
|
||||||
@@ -35,6 +35,7 @@ set(PRJ_HEADERS
|
|||||||
include/Json.h
|
include/Json.h
|
||||||
include/LuaAPI.h
|
include/LuaAPI.h
|
||||||
include/RWMutex.h
|
include/RWMutex.h
|
||||||
|
include/RateLimiter.h
|
||||||
include/SignalHandling.h
|
include/SignalHandling.h
|
||||||
include/TConfig.h
|
include/TConfig.h
|
||||||
include/TConsole.h
|
include/TConsole.h
|
||||||
@@ -49,7 +50,6 @@ set(PRJ_HEADERS
|
|||||||
include/TServer.h
|
include/TServer.h
|
||||||
include/VehicleData.h
|
include/VehicleData.h
|
||||||
include/Env.h
|
include/Env.h
|
||||||
include/Settings.h
|
|
||||||
include/Profiling.h
|
include/Profiling.h
|
||||||
include/ChronoWrapper.h
|
include/ChronoWrapper.h
|
||||||
)
|
)
|
||||||
@@ -73,28 +73,27 @@ set(PRJ_SOURCES
|
|||||||
src/TResourceManager.cpp
|
src/TResourceManager.cpp
|
||||||
src/TScopedTimer.cpp
|
src/TScopedTimer.cpp
|
||||||
src/TServer.cpp
|
src/TServer.cpp
|
||||||
|
src/RateLimiter.cpp
|
||||||
src/VehicleData.cpp
|
src/VehicleData.cpp
|
||||||
src/Env.cpp
|
src/Env.cpp
|
||||||
src/Settings.cpp
|
|
||||||
src/Profiling.cpp
|
src/Profiling.cpp
|
||||||
src/ChronoWrapper.cpp
|
src/ChronoWrapper.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# fix for sol2
|
find_package(Lua REQUIRED)
|
||||||
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 ${LUAJIT_INCLUDE_DIR})
|
set(PRJ_INCLUDE_DIRS ${LUA_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)
|
||||||
set(PRJ_DEFINITIONS CPPHTTPLIB_OPENSSL_SUPPORT)
|
set(PRJ_DEFINITIONS CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
# add all libraries used by the project (WARNING: also set them in vcpkg.json!)
|
# add all libraries used by the project (WARNING: also set them in vcpkg.json!)
|
||||||
set(PRJ_LIBRARIES
|
set(PRJ_LIBRARIES
|
||||||
fmt::fmt
|
fmt::fmt
|
||||||
doctest::doctest
|
doctest::doctest
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
@@ -105,12 +104,9 @@ set(PRJ_LIBRARIES
|
|||||||
httplib::httplib
|
httplib::httplib
|
||||||
libzip::zip
|
libzip::zip
|
||||||
OpenSSL::SSL OpenSSL::Crypto
|
OpenSSL::SSL OpenSSL::Crypto
|
||||||
CURL::libcurl
|
${LUA_LIBRARIES}
|
||||||
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)
|
||||||
@@ -120,8 +116,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)
|
find_package(toml11 CONFIG REQUIRED)
|
||||||
add_subdirectory("deps/toml11")
|
|
||||||
|
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
|
|
||||||
@@ -130,7 +125,7 @@ include(FindThreads)
|
|||||||
|
|
||||||
### END SETTINGS ###
|
### END SETTINGS ###
|
||||||
|
|
||||||
# DONT change anything beyond this point unless you've read the cmake bible and
|
# DONT change anything beyond this point unless you've read the cmake bible and
|
||||||
# swore on it not to bonk up the ci/cd pipelines with your changes.
|
# swore on it not to bonk up the ci/cd pipelines with your changes.
|
||||||
|
|
||||||
####################
|
####################
|
||||||
@@ -151,8 +146,7 @@ if(UNIX)
|
|||||||
endif(UNIX)
|
endif(UNIX)
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_compile_definitions(_WIN32_WINNT=0x0A00)
|
add_compile_options("-D_WIN32_WINNT=0x0601")
|
||||||
|
|
||||||
add_compile_options("/bigobj")
|
add_compile_options("/bigobj")
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
@@ -170,7 +164,7 @@ set(PRJ_DEFINITIONS ${PRJ_DEFINITIONS}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# build commandline manually for funky windows flags to carry over without a custom toolchain file
|
# build commandline manually for funky windows flags to carry over without a custom toolchain file
|
||||||
add_library(commandline_static
|
add_library(commandline_static
|
||||||
deps/commandline/src/impls.h
|
deps/commandline/src/impls.h
|
||||||
deps/commandline/src/windows_impl.cpp
|
deps/commandline/src/windows_impl.cpp
|
||||||
deps/commandline/src/linux_impl.cpp
|
deps/commandline/src/linux_impl.cpp
|
||||||
|
|||||||
+2
-2
@@ -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`. 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`. You may also want to turn off sentry by setting `SENTRY_BACKEND=none` (for example via commandline switch `-DSENTRY_BACKEND=none`). An example invocation on Linux with GNU make would be
|
||||||
`cmake . -DCMAKE_BUILD_TYPE=Debug` .
|
`cmake . -DCMAKE_BUILD_TYPE=Debug -DSENTRY_BACKEND=none` .
|
||||||
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`).
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Feel free to ask any questions via the following channels:
|
|||||||
|
|
||||||
These values are guesstimated and are subject to change with each release.
|
These values are guesstimated and are subject to change with each release.
|
||||||
|
|
||||||
* RAM: 30-100 MiB usable (not counting OS overhead)
|
* RAM: 50+ MiB usable (not counting OS overhead)
|
||||||
* CPU: >1GHz, preferably multicore
|
* CPU: >1GHz, preferably multicore
|
||||||
* OS: Windows, Linux (theoretically any POSIX)
|
* OS: Windows, Linux (theoretically any POSIX)
|
||||||
* GPU: None
|
* GPU: None
|
||||||
@@ -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.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).
|
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`.
|
||||||
|
|
||||||
## Supported Operating Systems
|
## Supported Operating Systems
|
||||||
|
|
||||||
@@ -66,10 +66,9 @@ 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. 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)
|
3. Run CMake `cmake -S . -B bin -DCMAKE_BUILD_TYPE=Release` - this can take a few minutes and may take a lot of disk space and bandwidth.
|
||||||
4. Run CMake `cmake -S . -B bin -DCMAKE_BUILD_TYPE=Release` - this can take a few minutes and may take a lot of disk space and bandwidth.
|
4. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`.
|
||||||
5. Build via `cmake --build bin --parallel --config Release -t BeamMP-Server`.
|
5. Your executable can be found in `bin/`.
|
||||||
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
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
|
# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md
|
||||||
# Courtesy of Jason Turner
|
# Courtesy of Jason Turner
|
||||||
# License here: https://github.com/cpp-best-practices/cppbestpractices/blob/master/LICENSE
|
# License here: https://github.com/cpp-best-practices/cppbestpractices/blob/master/LICENSE
|
||||||
#
|
#
|
||||||
# This version has been modified by the owners of the current respository.
|
# This version has been modified by the owners of the current respository.
|
||||||
# Modifications have mostly been marked with "modified" or similar, though this is not
|
# Modifications have mostly been marked with "modified" or similar, though this is not
|
||||||
# strictly required.
|
# strictly required.
|
||||||
|
|
||||||
function(set_project_warnings project_name)
|
function(set_project_warnings project_name)
|
||||||
@@ -73,6 +73,7 @@ function(set_project_warnings project_name)
|
|||||||
-Werror=missing-declarations
|
-Werror=missing-declarations
|
||||||
-Werror=missing-field-initializers
|
-Werror=missing-field-initializers
|
||||||
-Werror=ctor-dtor-privacy
|
-Werror=ctor-dtor-privacy
|
||||||
|
-Werror=switch-enum
|
||||||
-Wswitch-default
|
-Wswitch-default
|
||||||
-Werror=unused-result
|
-Werror=unused-result
|
||||||
-Werror=implicit-fallthrough
|
-Werror=implicit-fallthrough
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ find_package(Git)
|
|||||||
if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES)
|
if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES)
|
||||||
if(Git_FOUND)
|
if(Git_FOUND)
|
||||||
message(STATUS "Git found, submodule update and init")
|
message(STATUS "Git found, submodule update and init")
|
||||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init
|
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
||||||
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
||||||
|
|||||||
Vendored
-1
Submodule deps/toml11 deleted from f33ca743fb
@@ -4,5 +4,5 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ChronoWrapper {
|
namespace ChronoWrapper {
|
||||||
std::chrono::high_resolution_clock::duration TimeFromStringWithLiteral(const std::string& time_str);
|
std::chrono::high_resolution_clock::duration TimeFromStringWithLiteral(const std::string& time_str);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -24,7 +24,6 @@
|
|||||||
#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"
|
||||||
@@ -57,16 +56,17 @@ public:
|
|||||||
~TClient();
|
~TClient();
|
||||||
TClient& operator=(const TClient&) = delete;
|
TClient& operator=(const TClient&) = delete;
|
||||||
|
|
||||||
void AddNewCar(int Ident, const nlohmann::json& Data);
|
void AddNewCar(int Ident, const std::string& Data);
|
||||||
void SetCarData(int Ident, const nlohmann::json& Data);
|
void SetCarData(int Ident, const std::string& 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; }
|
||||||
nlohmann::json GetCarData(int Ident);
|
std::string 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 SetDownSock(ip::tcp::socket&& CSock) { mDownSocket = std::move(CSock); }
|
||||||
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
|
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
|
||||||
void Disconnect(std::string_view Reason);
|
void Disconnect(std::string_view Reason);
|
||||||
bool IsDisconnected() const { return !mSocket.is_open(); }
|
bool IsDisconnected() const { return !mSocket.is_open(); }
|
||||||
@@ -75,6 +75,8 @@ public:
|
|||||||
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
|
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
|
||||||
[[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; }
|
[[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; }
|
||||||
[[nodiscard]] ip::udp::endpoint& GetUDPAddr() { return mUDPAddress; }
|
[[nodiscard]] ip::udp::endpoint& GetUDPAddr() { return mUDPAddress; }
|
||||||
|
[[nodiscard]] ip::tcp::socket& GetDownSock() { return mDownSocket; }
|
||||||
|
[[nodiscard]] const ip::tcp::socket& GetDownSock() const { return mDownSocket; }
|
||||||
[[nodiscard]] ip::tcp::socket& GetTCPSock() { return mSocket; }
|
[[nodiscard]] ip::tcp::socket& GetTCPSock() { return mSocket; }
|
||||||
[[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; }
|
[[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; }
|
||||||
[[nodiscard]] std::string GetRoles() const { return mRole; }
|
[[nodiscard]] std::string GetRoles() const { return mRole; }
|
||||||
@@ -86,7 +88,7 @@ public:
|
|||||||
void ClearCars();
|
void ClearCars();
|
||||||
[[nodiscard]] int GetID() const { return mID; }
|
[[nodiscard]] int GetID() const { return mID; }
|
||||||
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
||||||
[[nodiscard]] bool IsUDPConnected() const { return mIsUDPConnected; }
|
[[nodiscard]] bool IsConnected() const { return mIsConnected; }
|
||||||
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
||||||
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
||||||
[[nodiscard]] bool IsGuest() const { return mIsGuest; }
|
[[nodiscard]] bool IsGuest() const { return mIsGuest; }
|
||||||
@@ -98,18 +100,16 @@ public:
|
|||||||
[[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; }
|
[[nodiscard]] const std::queue<std::vector<uint8_t>>& MissedPacketQueue() const { return mPacketsSync; }
|
||||||
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
|
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
|
||||||
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
|
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
|
||||||
void SetIsUDPConnected(bool NewIsConnected) { mIsUDPConnected = NewIsConnected; }
|
void SetIsConnected(bool NewIsConnected) { mIsConnected = NewIsConnected; }
|
||||||
[[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);
|
||||||
|
|
||||||
TServer& mServer;
|
TServer& mServer;
|
||||||
bool mIsUDPConnected = false;
|
bool mIsConnected = false;
|
||||||
bool mIsSynced = false;
|
bool mIsSynced = false;
|
||||||
bool mIsSyncing = false;
|
bool mIsSyncing = false;
|
||||||
mutable std::mutex mMissedPacketsMutex;
|
mutable std::mutex mMissedPacketsMutex;
|
||||||
@@ -122,13 +122,13 @@ private:
|
|||||||
SparseArray<std::string> mVehiclePosition;
|
SparseArray<std::string> mVehiclePosition;
|
||||||
std::string mName = "Unknown Client";
|
std::string mName = "Unknown Client";
|
||||||
ip::tcp::socket mSocket;
|
ip::tcp::socket mSocket;
|
||||||
|
ip::tcp::socket mDownSocket;
|
||||||
ip::udp::endpoint mUDPAddress {};
|
ip::udp::endpoint mUDPAddress {};
|
||||||
int mUnicycleID = -1;
|
int mUnicycleID = -1;
|
||||||
std::string mRole;
|
std::string mRole;
|
||||||
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);
|
||||||
|
|||||||
+93
-26
@@ -29,7 +29,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <span>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
@@ -38,7 +37,6 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
#include "TConsole.h"
|
#include "TConsole.h"
|
||||||
|
|
||||||
struct Version {
|
struct Version {
|
||||||
@@ -61,6 +59,34 @@ using SparseArray = std::unordered_map<size_t, T>;
|
|||||||
class Application final {
|
class Application final {
|
||||||
public:
|
public:
|
||||||
// types
|
// types
|
||||||
|
struct TSettings {
|
||||||
|
std::string ServerName { "BeamMP Server" };
|
||||||
|
std::string ServerDesc { "BeamMP Default Description" };
|
||||||
|
std::string ServerTags { "Freeroam" };
|
||||||
|
std::string Resource { "Resources" };
|
||||||
|
std::string MapName { "/levels/gridmap_v2/info.json" };
|
||||||
|
std::string Key {};
|
||||||
|
std::string Password {};
|
||||||
|
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
|
||||||
|
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
|
||||||
|
bool HTTPServerEnabled { false };
|
||||||
|
int MaxPlayers { 8 };
|
||||||
|
bool Private { true };
|
||||||
|
int MaxCars { 1 };
|
||||||
|
bool DebugModeEnabled { false };
|
||||||
|
int Port { 30814 };
|
||||||
|
std::string CustomIP {};
|
||||||
|
bool LogChat { true };
|
||||||
|
bool AllowGuests { true };
|
||||||
|
bool SendErrors { true };
|
||||||
|
bool SendErrorsMessageEnabled { true };
|
||||||
|
int HTTPServerPort { 8080 };
|
||||||
|
std::string HTTPServerIP { "127.0.0.1" };
|
||||||
|
bool HTTPServerUseSSL { false };
|
||||||
|
bool HideUpdateMessages { false };
|
||||||
|
std::string UpdateReminderTime { "30s" };
|
||||||
|
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
|
||||||
|
};
|
||||||
|
|
||||||
using TShutdownHandler = std::function<void()>;
|
using TShutdownHandler = std::function<void()>;
|
||||||
|
|
||||||
@@ -74,21 +100,19 @@ 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, 7, 0 }; }
|
static uint8_t ClientMajorVersion() { return 2; }
|
||||||
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; }
|
||||||
|
|
||||||
static inline struct Settings Settings { };
|
static TSettings Settings;
|
||||||
|
|
||||||
static std::vector<std::string> GetBackendUrlsInOrder() {
|
static std::vector<std::string> GetBackendUrlsInOrder() {
|
||||||
return {
|
return {
|
||||||
"https://backend.beammp.com",
|
"backend.beammp.com",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string GetServerCheckUrl() { return "https://check.beammp.com"; }
|
static std::string GetBackendUrlForAuth() { return "auth.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);
|
||||||
@@ -129,11 +153,10 @@ 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, 9, 0 };
|
static inline Version mVersion { 3, 5, 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);
|
||||||
@@ -142,6 +165,7 @@ 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__)
|
||||||
@@ -164,13 +188,13 @@ void RegisterThread(const std::string& str);
|
|||||||
#else
|
#else
|
||||||
#define _function_name std::string(__func__)
|
#define _function_name std::string(__func__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define DEBUG
|
#define DEBUG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
|
|
||||||
// if this is defined, we will show the full function signature infront of
|
// if this is defined, we will show the full function signature infront of
|
||||||
// each info/debug/warn... call instead of the 'filename:line' format.
|
// each info/debug/warn... call instead of the 'filename:line' format.
|
||||||
#if defined(BMP_FULL_FUNCTION_NAMES)
|
#if defined(BMP_FULL_FUNCTION_NAMES)
|
||||||
@@ -180,7 +204,7 @@ void RegisterThread(const std::string& str);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // defined(DEBUG)
|
#endif // defined(DEBUG)
|
||||||
|
|
||||||
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
|
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
|
||||||
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
|
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
|
||||||
#define beammp_error(x) \
|
#define beammp_error(x) \
|
||||||
@@ -202,13 +226,13 @@ void RegisterThread(const std::string& str);
|
|||||||
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
||||||
#define beammp_debug(x) \
|
#define beammp_debug(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
if (Application::Settings.DebugModeEnabled) { \
|
||||||
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
#define beammp_event(x) \
|
#define beammp_event(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
if (Application::Settings.DebugModeEnabled) { \
|
||||||
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
@@ -216,14 +240,14 @@ void RegisterThread(const std::string& str);
|
|||||||
#if defined(DEBUG)
|
#if defined(DEBUG)
|
||||||
#define beammp_trace(x) \
|
#define beammp_trace(x) \
|
||||||
do { \
|
do { \
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) { \
|
if (Application::Settings.DebugModeEnabled) { \
|
||||||
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
#else
|
#else
|
||||||
#define beammp_trace(x)
|
#define beammp_trace(x)
|
||||||
#endif // defined(DEBUG)
|
#endif // defined(DEBUG)
|
||||||
|
|
||||||
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
|
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
|
||||||
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
|
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
|
||||||
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
|
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
|
||||||
@@ -265,14 +289,57 @@ void RegisterThread(const std::string& str);
|
|||||||
|
|
||||||
void LogChatMessage(const std::string& name, int id, const std::string& msg);
|
void LogChatMessage(const std::string& name, int id, const std::string& msg);
|
||||||
|
|
||||||
std::vector<uint8_t> Comp(std::span<const uint8_t> input);
|
#define Biggest 30000
|
||||||
std::vector<uint8_t> DeComp(std::span<const uint8_t> input);
|
|
||||||
|
template <typename T>
|
||||||
|
inline T Comp(const T& Data) {
|
||||||
|
std::array<char, Biggest> C {};
|
||||||
|
// obsolete
|
||||||
|
C.fill(0);
|
||||||
|
z_stream defstream;
|
||||||
|
defstream.zalloc = nullptr;
|
||||||
|
defstream.zfree = nullptr;
|
||||||
|
defstream.opaque = nullptr;
|
||||||
|
defstream.avail_in = uInt(Data.size());
|
||||||
|
defstream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(&Data[0]));
|
||||||
|
defstream.avail_out = Biggest;
|
||||||
|
defstream.next_out = reinterpret_cast<Bytef*>(C.data());
|
||||||
|
deflateInit(&defstream, Z_BEST_COMPRESSION);
|
||||||
|
deflate(&defstream, Z_SYNC_FLUSH);
|
||||||
|
deflate(&defstream, Z_FINISH);
|
||||||
|
deflateEnd(&defstream);
|
||||||
|
size_t TotalOut = defstream.total_out;
|
||||||
|
T Ret;
|
||||||
|
Ret.resize(TotalOut);
|
||||||
|
std::fill(Ret.begin(), Ret.end(), 0);
|
||||||
|
std::copy_n(C.begin(), TotalOut, Ret.begin());
|
||||||
|
return Ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T DeComp(const T& Compressed) {
|
||||||
|
std::array<char, Biggest> C {};
|
||||||
|
// not needed
|
||||||
|
C.fill(0);
|
||||||
|
z_stream infstream;
|
||||||
|
infstream.zalloc = nullptr;
|
||||||
|
infstream.zfree = nullptr;
|
||||||
|
infstream.opaque = nullptr;
|
||||||
|
infstream.avail_in = Biggest;
|
||||||
|
infstream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(&Compressed[0]));
|
||||||
|
infstream.avail_out = Biggest;
|
||||||
|
infstream.next_out = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(C.data()));
|
||||||
|
inflateInit(&infstream);
|
||||||
|
inflate(&infstream, Z_SYNC_FLUSH);
|
||||||
|
inflate(&infstream, Z_FINISH);
|
||||||
|
inflateEnd(&infstream);
|
||||||
|
size_t TotalOut = infstream.total_out;
|
||||||
|
T Ret;
|
||||||
|
Ret.resize(TotalOut);
|
||||||
|
std::fill(Ret.begin(), Ret.end(), 0);
|
||||||
|
std::copy_n(C.begin(), TotalOut, Ret.begin());
|
||||||
|
return Ret;
|
||||||
|
}
|
||||||
|
|
||||||
std::string GetPlatformAgnosticErrorString();
|
std::string GetPlatformAgnosticErrorString();
|
||||||
#define S_DSN SU_RAW
|
#define S_DSN SU_RAW
|
||||||
|
|
||||||
class InvalidDataError : std::runtime_error {
|
|
||||||
public:
|
|
||||||
InvalidDataError() : std::runtime_error("Invalid data") {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Compat.h"
|
|
||||||
|
|
||||||
static const char* const ANSI_RESET = "\u001b[0m";
|
static const char* const ANSI_RESET = "\u001b[0m";
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
|
|||||||
static const char* const ANSI_BOLD = "\u001b[1m";
|
static const char* const ANSI_BOLD = "\u001b[1m";
|
||||||
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
||||||
|
|
||||||
#if defined(DEBUG) && !defined(BEAMMP_FREEBSD)
|
#ifdef DEBUG
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
|
inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line,
|
||||||
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
|
[[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) {
|
||||||
@@ -82,8 +81,8 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
|
|||||||
beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \
|
beammp_errorf("Assertion failed in '{}:{}': {}.", __func__, _line, #cond); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
#define beammp_assert_not_reachable() \
|
#define beammp_assert_not_reachable() \
|
||||||
do { \
|
do { \
|
||||||
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
|
beammp_errorf("Assertion failed in '{}:{}': Unreachable code reached. This may result in a crash or undefined state of the program.", __func__, _line); \
|
||||||
} while (false)
|
} while (false)
|
||||||
#endif // DEBUG
|
#endif // DEBUG
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ 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);
|
||||||
|
|||||||
+2
-3
@@ -23,7 +23,6 @@
|
|||||||
#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
|
||||||
@@ -39,8 +38,8 @@
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace Http {
|
namespace Http {
|
||||||
std::string GET(const std::string& url, unsigned int* status = nullptr);
|
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
|
||||||
std::string POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const std::map<std::string, std::string>& headers = {});
|
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 = {});
|
||||||
namespace Status {
|
namespace Status {
|
||||||
std::string ToString(int code);
|
std::string ToString(int code);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rapidjson/document.h"
|
|
||||||
#include "rapidjson/prettywriter.h"
|
|
||||||
#include "rapidjson/stringbuffer.h"
|
#include "rapidjson/stringbuffer.h"
|
||||||
|
#include "rapidjson/prettywriter.h"
|
||||||
|
#include "rapidjson/document.h"
|
||||||
#include "rapidjson/writer.h"
|
#include "rapidjson/writer.h"
|
||||||
|
|||||||
@@ -35,11 +35,8 @@ namespace MP {
|
|||||||
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
|
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
|
||||||
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
|
std::pair<bool, std::string> 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> 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);
|
|
||||||
bool IsPlayerGuest(int ID);
|
bool IsPlayerGuest(int ID);
|
||||||
bool IsPlayerConnected(int ID);
|
bool IsPlayerConnected(int ID);
|
||||||
void Sleep(size_t Ms);
|
void Sleep(size_t Ms);
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
#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
-1
@@ -23,8 +23,8 @@
|
|||||||
* and write locks and read locks are mutually exclusive.
|
* and write locks and read locks are mutually exclusive.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
// Use ReadLock(m) and WriteLock(m) to lock it.
|
// Use ReadLock(m) and WriteLock(m) to lock it.
|
||||||
using RWMutex = std::shared_mutex;
|
using RWMutex = std::shared_mutex;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "Common.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
class RateLimiter {
|
||||||
|
public:
|
||||||
|
RateLimiter();
|
||||||
|
bool isConnectionAllowed(const std::string& client_address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>>> m_connection;
|
||||||
|
std::mutex m_connection_mutex;
|
||||||
|
|
||||||
|
void blockIP(const std::string& client_address);
|
||||||
|
bool isIPBlocked(const std::string& client_address);
|
||||||
|
};
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
// BeamMP, the BeamNG.drive multiplayer mod.
|
|
||||||
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
|
||||||
//
|
|
||||||
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "Sync.h"
|
|
||||||
#include <concepts>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <doctest/doctest.h>
|
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
struct ComposedKey {
|
|
||||||
std::string Category;
|
|
||||||
std::string Key;
|
|
||||||
|
|
||||||
bool operator==(const ComposedKey& rhs) const {
|
|
||||||
return (this->Category == rhs.Category && this->Key == rhs.Key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct fmt::formatter<ComposedKey> : formatter<std::string> {
|
|
||||||
auto format(ComposedKey key, format_context& ctx) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline auto fmt::formatter<ComposedKey>::format(ComposedKey key, fmt::format_context& ctx) const {
|
|
||||||
std::string key_metadata = fmt::format("{}::{}", key.Category, key.Key);
|
|
||||||
return formatter<std::string>::format(key_metadata, ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
template <>
|
|
||||||
class hash<ComposedKey> {
|
|
||||||
public:
|
|
||||||
std::uint64_t operator()(const ComposedKey& key) const {
|
|
||||||
std::hash<std::string> hash_fn;
|
|
||||||
return hash_fn(key.Category + key.Key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Settings {
|
|
||||||
using SettingsTypeVariant = std::variant<std::string, bool, int>;
|
|
||||||
|
|
||||||
Settings();
|
|
||||||
|
|
||||||
enum Key {
|
|
||||||
// Keys that correspond to the keys set in TOML
|
|
||||||
// Keys have their TOML section name as prefix
|
|
||||||
|
|
||||||
// [Misc]
|
|
||||||
Misc_ImScaredOfUpdates,
|
|
||||||
Misc_UpdateReminderTime,
|
|
||||||
|
|
||||||
// [General]
|
|
||||||
General_Description,
|
|
||||||
General_Tags,
|
|
||||||
General_MaxPlayers,
|
|
||||||
General_Name,
|
|
||||||
General_Map,
|
|
||||||
General_AuthKey,
|
|
||||||
General_Private,
|
|
||||||
General_IP,
|
|
||||||
General_Port,
|
|
||||||
General_MaxCars,
|
|
||||||
General_LogChat,
|
|
||||||
General_ResourceFolder,
|
|
||||||
General_Debug,
|
|
||||||
General_AllowGuests,
|
|
||||||
General_InformationPacket,
|
|
||||||
};
|
|
||||||
|
|
||||||
Sync<std::unordered_map<Key, SettingsTypeVariant>> SettingsMap;
|
|
||||||
enum SettingsAccessMask {
|
|
||||||
READ_ONLY, // Value can be read from console
|
|
||||||
READ_WRITE, // Value can be read and written to from console
|
|
||||||
NO_ACCESS // Value is inaccessible from console (no read OR write)
|
|
||||||
};
|
|
||||||
|
|
||||||
using SettingsAccessControl = std::pair<
|
|
||||||
Key, // The Key's corresponding enum encoding
|
|
||||||
SettingsAccessMask // Console read/write permissions
|
|
||||||
>;
|
|
||||||
|
|
||||||
Sync<std::unordered_map<ComposedKey, SettingsAccessControl>> InputAccessMapping;
|
|
||||||
std::string getAsString(Key key);
|
|
||||||
|
|
||||||
int getAsInt(Key key);
|
|
||||||
|
|
||||||
bool getAsBool(Key key);
|
|
||||||
|
|
||||||
SettingsTypeVariant get(Key key);
|
|
||||||
|
|
||||||
void set(Key key, const std::string& value);
|
|
||||||
|
|
||||||
template <typename Integer, std::enable_if_t<std::is_same_v<Integer, int>, bool> = true>
|
|
||||||
void set(Key key, Integer value) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined setting key accessed in Settings::set(int)" };
|
|
||||||
}
|
|
||||||
if (!std::holds_alternative<int>(map->at(key))) {
|
|
||||||
throw std::logic_error { fmt::format("Wrong value type in Settings::set(int): index {}", map->at(key).index()) };
|
|
||||||
}
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
template <typename Boolean, std::enable_if_t<std::is_same_v<bool, Boolean>, bool> = true>
|
|
||||||
void set(Key key, Boolean value) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined setting key accessed in Settings::set(bool)" };
|
|
||||||
}
|
|
||||||
if (!std::holds_alternative<bool>(map->at(key))) {
|
|
||||||
throw std::logic_error { fmt::format("Wrong value type in Settings::set(bool): index {}", map->at(key).index()) };
|
|
||||||
}
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<ComposedKey, SettingsAccessControl> getAccessControlMap() const;
|
|
||||||
SettingsAccessControl getConsoleInputAccessMapping(const ComposedKey& keyName);
|
|
||||||
|
|
||||||
void setConsoleInputAccessMapping(const ComposedKey& keyName, const std::string& value);
|
|
||||||
void setConsoleInputAccessMapping(const ComposedKey& keyName, int value);
|
|
||||||
void setConsoleInputAccessMapping(const ComposedKey& keyName, bool value);
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <boost/thread/synchronized_value.hpp>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
/// This header provides convenience aliases for synchronization primitives.
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using Sync = boost::synchronized_value<T, std::recursive_mutex>;
|
|
||||||
+3
-2
@@ -19,7 +19,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -41,7 +40,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
void CreateConfigFile();
|
void CreateConfigFile();
|
||||||
void ParseFromFile(std::string_view name);
|
void ParseFromFile(std::string_view name);
|
||||||
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, Settings::Key key);
|
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue);
|
||||||
|
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue);
|
||||||
|
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue);
|
||||||
|
|
||||||
void ParseOldFormat();
|
void ParseOldFormat();
|
||||||
std::string TagsAsPrettyArray() const;
|
std::string TagsAsPrettyArray() const;
|
||||||
|
|||||||
@@ -59,9 +59,6 @@ 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);
|
||||||
@@ -80,9 +77,6 @@ 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 };
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ public:
|
|||||||
//~THeartbeatThread();
|
//~THeartbeatThread();
|
||||||
void operator()() override;
|
void operator()() override;
|
||||||
|
|
||||||
static inline std::string lastCall = "";
|
|
||||||
private:
|
private:
|
||||||
std::string GenerateCall();
|
std::string GenerateCall();
|
||||||
std::string GetPlayers();
|
std::string GetPlayers();
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public:
|
|||||||
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
|
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
|
||||||
bool HasState(TLuaStateId StateId);
|
bool HasState(TLuaStateId StateId);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args);
|
||||||
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
|
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
|
||||||
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
|
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
|
||||||
/**
|
/**
|
||||||
@@ -192,7 +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) {
|
||||||
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments, EventName));
|
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ public:
|
|||||||
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
std::vector<TLuaValue> Arguments { TLuaValue { std::forward<ArgsT>(Args) }... };
|
||||||
const auto Handlers = GetEventHandlersForState(EventName, StateId);
|
const auto Handlers = GetEventHandlersForState(EventName, StateId);
|
||||||
for (const auto& Handler : Handlers) {
|
for (const auto& Handler : Handlers) {
|
||||||
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments, EventName));
|
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
|
||||||
}
|
}
|
||||||
return Results;
|
return Results;
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ private:
|
|||||||
StateThreadData(const StateThreadData&) = delete;
|
StateThreadData(const StateThreadData&) = delete;
|
||||||
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
virtual ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args);
|
||||||
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy);
|
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName, CallStrategy Strategy);
|
||||||
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
|
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
|
||||||
void AddPath(const fs::path& Path); // to be added to path and cpath
|
void AddPath(const fs::path& Path); // to be added to path and cpath
|
||||||
@@ -261,7 +261,6 @@ private:
|
|||||||
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
||||||
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
|
||||||
sol::table Lua_GetPlayerIdentifiers(int ID);
|
sol::table Lua_GetPlayerIdentifiers(int ID);
|
||||||
std::variant<std::string, sol::nil_t> Lua_GetPlayerRole(int ID);
|
|
||||||
sol::table Lua_GetPlayers();
|
sol::table Lua_GetPlayers();
|
||||||
std::string Lua_GetPlayerName(int ID);
|
std::string Lua_GetPlayerName(int ID);
|
||||||
sol::table Lua_GetPlayerVehicles(int ID);
|
sol::table Lua_GetPlayerVehicles(int ID);
|
||||||
|
|||||||
+2
-4
@@ -45,12 +45,9 @@ 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();
|
||||||
|
|
||||||
TServer& mServer;
|
TServer& mServer;
|
||||||
TPPSMonitor& mPPSMonitor;
|
TPPSMonitor& mPPSMonitor;
|
||||||
ip::udp::socket mUDPSock;
|
ip::udp::socket mUDPSock;
|
||||||
@@ -60,6 +57,7 @@ private:
|
|||||||
std::mutex mOpenIDMutex;
|
std::mutex mOpenIDMutex;
|
||||||
|
|
||||||
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
|
||||||
|
void HandleDownload(TConnection&& TCPSock);
|
||||||
void OnConnect(const std::weak_ptr<TClient>& c);
|
void OnConnect(const std::weak_ptr<TClient>& c);
|
||||||
void TCPClient(const std::weak_ptr<TClient>& c);
|
void TCPClient(const std::weak_ptr<TClient>& c);
|
||||||
void Looper(const std::weak_ptr<TClient>& c);
|
void Looper(const std::weak_ptr<TClient>& c);
|
||||||
@@ -68,7 +66,7 @@ private:
|
|||||||
void Parse(TClient& c, const std::vector<uint8_t>& Packet);
|
void Parse(TClient& c, const std::vector<uint8_t>& Packet);
|
||||||
void SendFile(TClient& c, const std::string& Name);
|
void SendFile(TClient& c, const std::string& Name);
|
||||||
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
|
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
|
||||||
static void SendFileToClient(TClient& c, size_t Size, const std::string& Name);
|
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
|
||||||
static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
|
static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TNetwork;
|
|||||||
class TPPSMonitor : public IThreaded {
|
class TPPSMonitor : public IThreaded {
|
||||||
public:
|
public:
|
||||||
explicit TPPSMonitor(TServer& Server);
|
explicit TPPSMonitor(TServer& Server);
|
||||||
virtual ~TPPSMonitor() { }
|
virtual ~TPPSMonitor() {}
|
||||||
|
|
||||||
void operator()() override;
|
void operator()() override;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
class TResourceManager {
|
class TResourceManager {
|
||||||
public:
|
public:
|
||||||
@@ -30,10 +29,6 @@ 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; }
|
|
||||||
|
|
||||||
void RefreshFiles();
|
|
||||||
void SetProtected(const std::string& ModName, bool Protected);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t mMaxModSize = 0;
|
size_t mMaxModSize = 0;
|
||||||
@@ -41,7 +36,4 @@ private:
|
|||||||
std::string mFileList;
|
std::string mFileList;
|
||||||
std::string mTrimmedList;
|
std::string mTrimmedList;
|
||||||
int mModsLoaded = 0;
|
int mModsLoaded = 0;
|
||||||
|
|
||||||
std::mutex mModsMutex;
|
|
||||||
nlohmann::json mMods = nlohmann::json::array();
|
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -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, bool udp);
|
void GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network);
|
||||||
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; }
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class TVehicleData final {
|
class TVehicleData final {
|
||||||
public:
|
public:
|
||||||
TVehicleData(int ID, nlohmann::json Data);
|
TVehicleData(int ID, std::string 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,
|
||||||
@@ -33,16 +32,14 @@ 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]] nlohmann::json Data() const { return mData; }
|
[[nodiscard]] std::string Data() const { return mData; }
|
||||||
[[nodiscard]] std::string DataAsPacket(const std::string& Role, const std::string& Name, int ID) const;
|
void SetData(const std::string& Data) { mData = Data; }
|
||||||
|
|
||||||
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 };
|
||||||
nlohmann::json mData;
|
std::string mData;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: unused now, remove?
|
// TODO: unused now, remove?
|
||||||
|
|||||||
+13
-9
@@ -2,21 +2,25 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
std::chrono::high_resolution_clock::duration ChronoWrapper::TimeFromStringWithLiteral(const std::string& time_str) {
|
std::chrono::high_resolution_clock::duration ChronoWrapper::TimeFromStringWithLiteral(const std::string& time_str)
|
||||||
|
{
|
||||||
// const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|ms|us|ns|[dhs]))"); //i.e one of: "25ns, 6us, 256ms, 2s, 13min, 69h, 356d" will get matched (only available in newer C++ versions)
|
// const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|ms|us|ns|[dhs]))"); //i.e one of: "25ns, 6us, 256ms, 2s, 13min, 69h, 356d" will get matched (only available in newer C++ versions)
|
||||||
const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|[dhs]))"); // i.e one of: "2.01s, 13min, 69h, 356.69d" will get matched
|
const std::regex time_regex(R"((\d+\.{0,1}\d*)(min|[dhs]))"); //i.e one of: "2.01s, 13min, 69h, 356.69d" will get matched
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
float time_value;
|
float time_value;
|
||||||
if (!std::regex_search(time_str, match, time_regex))
|
if (!std::regex_search(time_str, match, time_regex)) return std::chrono::nanoseconds(0);
|
||||||
return std::chrono::nanoseconds(0);
|
|
||||||
time_value = stof(match.str(1));
|
time_value = stof(match.str(1));
|
||||||
|
beammp_debugf("Parsed time was: {}{}", time_value, match.str(2));
|
||||||
if (match.str(2) == "d") {
|
if (match.str(2) == "d") {
|
||||||
return std::chrono::seconds((uint64_t)(time_value * 86400)); // 86400 seconds in a day
|
return std::chrono::seconds((uint64_t)(time_value * 86400)); //86400 seconds in a day
|
||||||
} else if (match.str(2) == "h") {
|
}
|
||||||
return std::chrono::seconds((uint64_t)(time_value * 3600)); // 3600 seconds in an hour
|
else if (match.str(2) == "h") {
|
||||||
} else if (match.str(2) == "min") {
|
return std::chrono::seconds((uint64_t)(time_value * 3600)); //3600 seconds in an hour
|
||||||
|
}
|
||||||
|
else if (match.str(2) == "min") {
|
||||||
return std::chrono::seconds((uint64_t)(time_value * 60));
|
return std::chrono::seconds((uint64_t)(time_value * 60));
|
||||||
} else if (match.str(2) == "s") {
|
}
|
||||||
|
else if (match.str(2) == "s") {
|
||||||
return std::chrono::seconds((uint64_t)time_value);
|
return std::chrono::seconds((uint64_t)time_value);
|
||||||
}
|
}
|
||||||
return std::chrono::nanoseconds(0);
|
return std::chrono::nanoseconds(0);
|
||||||
|
|||||||
+12
-15
@@ -57,7 +57,7 @@ int TClient::GetOpenCarID() const {
|
|||||||
return OpenID;
|
return OpenID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TClient::AddNewCar(int Ident, const nlohmann::json& Data) {
|
void TClient::AddNewCar(int Ident, const std::string& Data) {
|
||||||
std::unique_lock lock(mVehicleDataMutex);
|
std::unique_lock lock(mVehicleDataMutex);
|
||||||
mVehicleData.emplace_back(Ident, Data);
|
mVehicleData.emplace_back(Ident, Data);
|
||||||
}
|
}
|
||||||
@@ -79,17 +79,13 @@ 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;
|
||||||
if (mSocket.is_open()) {
|
mSocket.shutdown(socket_base::shutdown_both, ec);
|
||||||
mSocket.shutdown(socket_base::shutdown_both, ec);
|
if (ec) {
|
||||||
if (ec) {
|
beammp_debugf("Failed to shutdown client socket: {}", ec.message());
|
||||||
beammp_debugf("Failed to shutdown client socket: {}", ec.message());
|
}
|
||||||
}
|
mSocket.close(ec);
|
||||||
mSocket.close(ec);
|
if (ec) {
|
||||||
if (ec) {
|
beammp_debugf("Failed to close client socket: {}", ec.message());
|
||||||
beammp_debugf("Failed to close client socket: {}", ec.message());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beammp_debug("Socket is already closed.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +94,7 @@ void TClient::SetCarPosition(int Ident, const std::string& Data) {
|
|||||||
mVehiclePosition[size_t(Ident)] = Data;
|
mVehiclePosition[size_t(Ident)] = Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json TClient::GetCarData(int Ident) {
|
std::string TClient::GetCarData(int Ident) {
|
||||||
{ // lock
|
{ // lock
|
||||||
std::unique_lock lock(mVehicleDataMutex);
|
std::unique_lock lock(mVehicleDataMutex);
|
||||||
for (auto& v : mVehicleData) {
|
for (auto& v : mVehicleData) {
|
||||||
@@ -108,10 +104,10 @@ nlohmann::json TClient::GetCarData(int Ident) {
|
|||||||
}
|
}
|
||||||
} // unlock
|
} // unlock
|
||||||
DeleteCar(Ident);
|
DeleteCar(Ident);
|
||||||
return nlohmann::detail::value_t::null;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TClient::SetCarData(int Ident, const nlohmann::json& Data) {
|
void TClient::SetCarData(int Ident, const std::string& Data) {
|
||||||
{ // lock
|
{ // lock
|
||||||
std::unique_lock lock(mVehicleDataMutex);
|
std::unique_lock lock(mVehicleDataMutex);
|
||||||
for (auto& v : mVehicleData) {
|
for (auto& v : mVehicleData) {
|
||||||
@@ -148,6 +144,7 @@ void TClient::EnqueuePacket(const std::vector<uint8_t>& Packet) {
|
|||||||
TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
|
TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
|
||||||
: mServer(Server)
|
: mServer(Server)
|
||||||
, mSocket(std::move(Socket))
|
, mSocket(std::move(Socket))
|
||||||
|
, mDownSocket(ip::tcp::socket(Server.IoCtx()))
|
||||||
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
|
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-80
@@ -22,19 +22,20 @@
|
|||||||
#include "TConsole.h"
|
#include "TConsole.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <chrono>
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include "Compat.h"
|
#include "Compat.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
|
|
||||||
|
Application::TSettings Application::Settings = {};
|
||||||
|
|
||||||
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
||||||
std::unique_lock Lock(mShutdownHandlersMutex);
|
std::unique_lock Lock(mShutdownHandlersMutex);
|
||||||
if (Handler) {
|
if (Handler) {
|
||||||
@@ -215,7 +216,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 + "/v/s");
|
auto Response = Http::GET(url, 443, "/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();
|
||||||
@@ -256,7 +257,7 @@ static std::mutex ThreadNameMapMutex {};
|
|||||||
|
|
||||||
std::string ThreadName(bool DebugModeOverride) {
|
std::string ThreadName(bool DebugModeOverride) {
|
||||||
auto Lock = std::unique_lock(ThreadNameMapMutex);
|
auto Lock = std::unique_lock(ThreadNameMapMutex);
|
||||||
if (DebugModeOverride || Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
if (DebugModeOverride || Application::Settings.DebugModeEnabled) {
|
||||||
auto id = std::this_thread::get_id();
|
auto id = std::this_thread::get_id();
|
||||||
if (threadNameMap.find(id) != threadNameMap.end()) {
|
if (threadNameMap.find(id) != threadNameMap.end()) {
|
||||||
// found
|
// found
|
||||||
@@ -268,21 +269,21 @@ std::string ThreadName(bool DebugModeOverride) {
|
|||||||
|
|
||||||
TEST_CASE("ThreadName") {
|
TEST_CASE("ThreadName") {
|
||||||
RegisterThread("MyThread");
|
RegisterThread("MyThread");
|
||||||
auto OrigDebug = Application::Settings.getAsBool(Settings::Key::General_Debug);
|
auto OrigDebug = Application::Settings.DebugModeEnabled;
|
||||||
|
|
||||||
// ThreadName adds a space at the end, legacy but we need it still
|
// ThreadName adds a space at the end, legacy but we need it still
|
||||||
SUBCASE("Debug mode enabled") {
|
SUBCASE("Debug mode enabled") {
|
||||||
Application::Settings.set(Settings::Key::General_Debug, true);
|
Application::Settings.DebugModeEnabled = true;
|
||||||
CHECK(ThreadName(true) == "MyThread ");
|
CHECK(ThreadName(true) == "MyThread ");
|
||||||
CHECK(ThreadName(false) == "MyThread ");
|
CHECK(ThreadName(false) == "MyThread ");
|
||||||
}
|
}
|
||||||
SUBCASE("Debug mode disabled") {
|
SUBCASE("Debug mode disabled") {
|
||||||
Application::Settings.set(Settings::Key::General_Debug, false);
|
Application::Settings.DebugModeEnabled = false;
|
||||||
CHECK(ThreadName(true) == "MyThread ");
|
CHECK(ThreadName(true) == "MyThread ");
|
||||||
CHECK(ThreadName(false) == "");
|
CHECK(ThreadName(false) == "");
|
||||||
}
|
}
|
||||||
// cleanup
|
// cleanup
|
||||||
Application::Settings.set(Settings::Key::General_Debug, OrigDebug);
|
Application::Settings.DebugModeEnabled = OrigDebug;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterThread(const std::string& str) {
|
void RegisterThread(const std::string& str) {
|
||||||
@@ -296,7 +297,7 @@ void RegisterThread(const std::string& str) {
|
|||||||
#elif defined(BEAMMP_FREEBSD)
|
#elif defined(BEAMMP_FREEBSD)
|
||||||
ThreadId = std::to_string(getpid());
|
ThreadId = std::to_string(getpid());
|
||||||
#endif
|
#endif
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
if (Application::Settings.DebugModeEnabled) {
|
||||||
std::ofstream ThreadFile(".Threads.log", std::ios::app);
|
std::ofstream ThreadFile(".Threads.log", std::ios::app);
|
||||||
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
|
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
|
||||||
}
|
}
|
||||||
@@ -329,7 +330,7 @@ TEST_CASE("Version::AsString") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
|
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_LogChat)) {
|
if (Application::Settings.LogChat) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << ThreadName();
|
ss << ThreadName();
|
||||||
ss << "[CHAT] ";
|
ss << "[CHAT] ";
|
||||||
@@ -385,73 +386,3 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string LowerString(std::string str) {
|
|
||||||
std::ranges::transform(str, str.begin(), ::tolower);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static constexpr size_t STARTING_MAX_DECOMPRESSION_BUFFER_SIZE = 15 * 1024 * 1024;
|
|
||||||
static constexpr size_t MAX_DECOMPRESSION_BUFFER_SIZE = 30 * 1024 * 1024;
|
|
||||||
|
|
||||||
std::vector<uint8_t> DeComp(std::span<const uint8_t> input) {
|
|
||||||
beammp_debugf("got {} bytes of input data", input.size());
|
|
||||||
|
|
||||||
// start with a decompression buffer of 5x the input size, clamped to a maximum of 15 MB.
|
|
||||||
// this buffer can and will grow, but we don't want to start it too large. A 5x compression ratio
|
|
||||||
// is pretty optimistic.
|
|
||||||
std::vector<uint8_t> output_buffer(std::min<size_t>(input.size() * 5, STARTING_MAX_DECOMPRESSION_BUFFER_SIZE));
|
|
||||||
|
|
||||||
uLongf output_size = output_buffer.size();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
int res = uncompress(
|
|
||||||
reinterpret_cast<Bytef*>(output_buffer.data()),
|
|
||||||
&output_size,
|
|
||||||
reinterpret_cast<const Bytef*>(input.data()),
|
|
||||||
static_cast<uLongf>(input.size()));
|
|
||||||
if (res == Z_BUF_ERROR) {
|
|
||||||
// We assume that a reasonable maximum size for decompressed packets exists. We want to avoid
|
|
||||||
// a client effectively "zip bombing" us by sending a lot of small packets which decompress
|
|
||||||
// into huge data.
|
|
||||||
// If this limit were to be an issue, this could be made configurable, however clients have a similar
|
|
||||||
// limit. For that reason, we just reject packets which decompress into too much data.
|
|
||||||
if (output_buffer.size() >= MAX_DECOMPRESSION_BUFFER_SIZE) {
|
|
||||||
throw std::runtime_error(fmt::format("decompressed packet size of {} bytes exceeded", MAX_DECOMPRESSION_BUFFER_SIZE));
|
|
||||||
}
|
|
||||||
// if decompression fails, we double the buffer size (up to the allowed limit) and try again
|
|
||||||
output_buffer.resize(std::max<size_t>(output_buffer.size() * 2, MAX_DECOMPRESSION_BUFFER_SIZE));
|
|
||||||
beammp_warnf("zlib uncompress() failed, trying with a larger buffer size of {}", output_buffer.size());
|
|
||||||
output_size = output_buffer.size();
|
|
||||||
} else if (res != Z_OK) {
|
|
||||||
beammp_error("zlib uncompress() failed: " + std::to_string(res));
|
|
||||||
if (res == Z_DATA_ERROR) {
|
|
||||||
throw InvalidDataError {};
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("zlib uncompress() failed");
|
|
||||||
}
|
|
||||||
} else if (res == Z_OK) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output_buffer.resize(output_size);
|
|
||||||
return output_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> Comp(std::span<const uint8_t> input) {
|
|
||||||
auto max_size = compressBound(input.size());
|
|
||||||
std::vector<uint8_t> output(max_size);
|
|
||||||
uLongf output_size = output.size();
|
|
||||||
int res = compress(
|
|
||||||
reinterpret_cast<Bytef*>(output.data()),
|
|
||||||
&output_size,
|
|
||||||
reinterpret_cast<const Bytef*>(input.data()),
|
|
||||||
static_cast<uLongf>(input.size()));
|
|
||||||
if (res != Z_OK) {
|
|
||||||
beammp_error("zlib compress() failed: " + std::to_string(res));
|
|
||||||
throw std::runtime_error("zlib compress() failed");
|
|
||||||
}
|
|
||||||
beammp_debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B");
|
|
||||||
output.resize(output_size);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ TEST_CASE("init and reset termios") {
|
|||||||
CHECK_EQ(current.c_lflag, original.c_lflag);
|
CHECK_EQ(current.c_lflag, original.c_lflag);
|
||||||
#ifndef BEAMMP_FREEBSD // The 'c_line' attribute seems to only exist on Linux, so we need to omit it on other platforms
|
#ifndef BEAMMP_FREEBSD // The 'c_line' attribute seems to only exist on Linux, so we need to omit it on other platforms
|
||||||
CHECK_EQ(current.c_line, original.c_line);
|
CHECK_EQ(current.c_line, original.c_line);
|
||||||
#endif
|
#endif
|
||||||
CHECK_EQ(current.c_oflag, original.c_oflag);
|
CHECK_EQ(current.c_oflag, original.c_oflag);
|
||||||
CHECK_EQ(current.c_ospeed, original.c_ospeed);
|
CHECK_EQ(current.c_ospeed, original.c_ospeed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ 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 "";
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-67
@@ -29,88 +29,67 @@
|
|||||||
#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;
|
||||||
|
|
||||||
static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
[[nodiscard]] static std::shared_ptr<httplib::SSLClient> getClient(Connection connectionInfo) {
|
||||||
std::string* Result = reinterpret_cast<std::string*>(userp);
|
for (uint8_t i = 0; i < CONNECTION_AMOUNT; i++) {
|
||||||
std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
|
if (connectionInfo.host == connections[i].host
|
||||||
*Result += NewContents;
|
&& connectionInfo.port == connections[i].port) {
|
||||||
return size * nmemb;
|
beammp_tracef("Old client reconnected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||||
|
return clients[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t i = write_index;
|
||||||
|
write_index++;
|
||||||
|
write_index %= CONNECTION_AMOUNT;
|
||||||
|
clients[i] = std::make_shared<httplib::SSLClient>(connectionInfo.host, connectionInfo.port);
|
||||||
|
connections[i] = {connectionInfo.host, connectionInfo.port};
|
||||||
|
beammp_tracef("New client connected, with ip {} and port {}", connectionInfo.host, connectionInfo.port);
|
||||||
|
return clients[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Http::GET(const std::string& url, unsigned int* status) {
|
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
|
||||||
std::string Ret;
|
std::shared_ptr<httplib::SSLClient> client = getClient({host, port});
|
||||||
static thread_local CURL* curl = curl_easy_init();
|
client->enable_server_certificate_verification(false);
|
||||||
if (curl) {
|
client->set_address_family(AF_INET);
|
||||||
CURLcode res;
|
auto res = client->Get(target.c_str());
|
||||||
char errbuf[CURL_ERROR_SIZE];
|
if (res) {
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
||||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
|
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
|
||||||
errbuf[0] = 0;
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
if (res != CURLE_OK) {
|
|
||||||
beammp_error("GET to " + url + " failed: " + std::string(curl_easy_strerror(res)));
|
|
||||||
beammp_error("Curl error: " + std::string(errbuf));
|
|
||||||
return Http::ErrorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
|
*status = res->status;
|
||||||
}
|
}
|
||||||
|
return res->body;
|
||||||
} else {
|
} else {
|
||||||
beammp_error("Curl easy init failed");
|
|
||||||
return Http::ErrorString;
|
return Http::ErrorString;
|
||||||
}
|
}
|
||||||
return Ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Http::POST(const std::string& url, const std::string& body, const std::string& ContentType, unsigned int* status, const std::map<std::string, std::string>& headers) {
|
std::string 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 Ret;
|
std::shared_ptr<httplib::SSLClient> client = getClient({host, port});
|
||||||
static thread_local CURL* curl = curl_easy_init();
|
client->set_read_timeout(std::chrono::seconds(10));
|
||||||
if (curl) {
|
beammp_assert(client->is_valid());
|
||||||
CURLcode res;
|
client->enable_server_certificate_verification(false);
|
||||||
char errbuf[CURL_ERROR_SIZE];
|
client->set_address_family(AF_INET);
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
auto res = client->Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
|
||||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
if (res) {
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
|
|
||||||
struct curl_slist* list = nullptr;
|
|
||||||
list = curl_slist_append(list, ("Content-Type: " + ContentType).c_str());
|
|
||||||
|
|
||||||
for (auto [header, value] : headers) {
|
|
||||||
list = curl_slist_append(list, (header + ": " + value).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (status) {
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
|
*status = res->status;
|
||||||
}
|
}
|
||||||
|
return res->body;
|
||||||
} else {
|
} else {
|
||||||
beammp_error("Curl easy init failed");
|
beammp_debug("POST failed: " + httplib::to_string(res.error()));
|
||||||
return Http::ErrorString;
|
return Http::ErrorString;
|
||||||
}
|
}
|
||||||
return Ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 2616, RFC 7231
|
// RFC 2616, RFC 7231
|
||||||
@@ -220,6 +199,7 @@ Http::Server::THttpServerInstance::THttpServerInstance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Http::Server::THttpServerInstance::operator()() try {
|
void Http::Server::THttpServerInstance::operator()() try {
|
||||||
|
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort));
|
||||||
std::unique_ptr<httplib::Server> HttpLibServerInstance;
|
std::unique_ptr<httplib::Server> HttpLibServerInstance;
|
||||||
HttpLibServerInstance = std::make_unique<httplib::Server>();
|
HttpLibServerInstance = std::make_unique<httplib::Server>();
|
||||||
// todo: make this IP agnostic so people can set their own IP
|
// todo: make this IP agnostic so people can set their own IP
|
||||||
@@ -261,6 +241,10 @@ void Http::Server::THttpServerInstance::operator()() try {
|
|||||||
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
|
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
|
||||||
});
|
});
|
||||||
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
|
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
|
||||||
|
auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
|
||||||
|
if (!ret) {
|
||||||
|
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
|
||||||
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
|
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-128
@@ -20,7 +20,6 @@
|
|||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
#include "Settings.h"
|
|
||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
@@ -148,11 +147,6 @@ 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");
|
||||||
@@ -210,79 +204,6 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category) {
|
|
||||||
std::pair<bool, std::string> Result;
|
|
||||||
std::string Packet = "n" + Category + ":" + Icon + ":" + Message;
|
|
||||||
if (ID == -1) {
|
|
||||||
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
|
||||||
Result.first = true;
|
|
||||||
} else {
|
|
||||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
|
||||||
if (MaybeClient) {
|
|
||||||
auto c = MaybeClient.value().lock();
|
|
||||||
if (!c->IsSynced()) {
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Player is not synced yet";
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
|
||||||
beammp_errorf("Failed to send notification to player (id {}) - did the player disconnect?", ID);
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Failed to send packet";
|
|
||||||
}
|
|
||||||
Result.first = true;
|
|
||||||
} else {
|
|
||||||
beammp_lua_error("SendNotification invalid argument [1] invalid ID");
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Invalid Player ID";
|
|
||||||
}
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::string> LuaAPI::MP::ConfirmationDialog(int ID, const std::string& Title, const std::string& Body, const sol::table& buttons, const std::string& InteractionID, const bool& warning, const bool& reportToServer, const bool& reportToExtensions) {
|
|
||||||
std::pair<bool, std::string> Result;
|
|
||||||
|
|
||||||
const nlohmann::json PacketBody = {
|
|
||||||
{ "title", Title },
|
|
||||||
{ "body", Body },
|
|
||||||
{ "buttons", nlohmann::json::parse(JsonEncode(buttons), nullptr, false) },
|
|
||||||
{ "interactionID", InteractionID },
|
|
||||||
{ "class", warning ? "experimental" : "" },
|
|
||||||
{ "reportToServer", reportToServer },
|
|
||||||
{ "reportToExtensions", reportToExtensions }
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string Packet = "D" + PacketBody.dump();
|
|
||||||
if (ID == -1) {
|
|
||||||
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
|
|
||||||
Result.first = true;
|
|
||||||
} else {
|
|
||||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
|
||||||
if (MaybeClient) {
|
|
||||||
auto c = MaybeClient.value().lock();
|
|
||||||
if (!c->IsSynced()) {
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Player is not synced yet";
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
|
|
||||||
beammp_errorf("Failed to send confirmation dialog to player (id {}) - did the player disconnect?", ID);
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Failed to send packet";
|
|
||||||
}
|
|
||||||
Result.first = true;
|
|
||||||
} else {
|
|
||||||
beammp_lua_error("ConfirmationDialog invalid argument [1] invalid ID");
|
|
||||||
Result.first = false;
|
|
||||||
Result.second = "Invalid Player ID";
|
|
||||||
}
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
|
std::pair<bool, std::string> 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);
|
||||||
@@ -293,9 +214,8 @@ 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) != nlohmann::detail::value_t::null) {
|
if (!c->GetCarData(VID).empty()) {
|
||||||
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));
|
|
||||||
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
Engine->Network().SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||||
c->DeleteCar(VID);
|
c->DeleteCar(VID);
|
||||||
Result.first = true;
|
Result.first = true;
|
||||||
@@ -310,98 +230,66 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
|
|||||||
switch (ConfigID) {
|
switch (ConfigID) {
|
||||||
case 0: // debug
|
case 0: // debug
|
||||||
if (NewValue.is<bool>()) {
|
if (NewValue.is<bool>()) {
|
||||||
Application::Settings.set(Settings::Key::General_Debug, NewValue.as<bool>());
|
Application::Settings.DebugModeEnabled = NewValue.as<bool>();
|
||||||
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
|
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
beammp_lua_error("set invalid argument [2] expected boolean");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1: // private
|
case 1: // private
|
||||||
if (NewValue.is<bool>()) {
|
if (NewValue.is<bool>()) {
|
||||||
Application::Settings.set(Settings::Key::General_Private, NewValue.as<bool>());
|
Application::Settings.Private = NewValue.as<bool>();
|
||||||
beammp_info(std::string("Set `Private` to ") + (Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false"));
|
beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false"));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
beammp_lua_error("set invalid argument [2] expected boolean");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // max cars
|
case 2: // max cars
|
||||||
if (NewValue.is<int>()) {
|
if (NewValue.is<int>()) {
|
||||||
Application::Settings.set(Settings::Key::General_MaxCars, NewValue.as<int>());
|
Application::Settings.MaxCars = NewValue.as<int>();
|
||||||
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
|
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected integer");
|
beammp_lua_error("set invalid argument [2] expected integer");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: // max players
|
case 3: // max players
|
||||||
if (NewValue.is<int>()) {
|
if (NewValue.is<int>()) {
|
||||||
Application::Settings.set(Settings::Key::General_MaxPlayers, NewValue.as<int>());
|
Application::Settings.MaxPlayers = NewValue.as<int>();
|
||||||
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
|
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers));
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected integer");
|
beammp_lua_error("set invalid argument [2] expected integer");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 4: // Map
|
case 4: // Map
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.set(Settings::Key::General_Map, NewValue.as<std::string>());
|
Application::Settings.MapName = NewValue.as<std::string>();
|
||||||
beammp_info(std::string("Set `Map` to ") + Application::Settings.getAsString(Settings::Key::General_Map));
|
beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName);
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 5: // Name
|
case 5: // Name
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.set(Settings::Key::General_Name, NewValue.as<std::string>());
|
Application::Settings.ServerName = NewValue.as<std::string>();
|
||||||
beammp_info(std::string("Set `Name` to ") + Application::Settings.getAsString(Settings::Key::General_Name));
|
beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName);
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 6: // Desc
|
case 6: // Desc
|
||||||
if (NewValue.is<std::string>()) {
|
if (NewValue.is<std::string>()) {
|
||||||
Application::Settings.set(Settings::Key::General_Description, NewValue.as<std::string>());
|
Application::Settings.ServerDesc = NewValue.as<std::string>();
|
||||||
beammp_info(std::string("Set `Description` to ") + Application::Settings.getAsString(Settings::Key::General_Description));
|
beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc);
|
||||||
} else {
|
} else {
|
||||||
beammp_lua_error("set invalid argument [2] expected string");
|
beammp_lua_error("set invalid argument [2] expected string");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 7: // Information packet
|
|
||||||
if (NewValue.is<bool>()) {
|
|
||||||
Application::Settings.set(Settings::Key::General_InformationPacket, NewValue.as<bool>());
|
|
||||||
beammp_info(std::string("Set `InformationPacket` to ") + (Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
|
|
||||||
} else {
|
|
||||||
beammp_lua_error("set invalid argument [2] expected boolean");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
|
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TLuaValue LuaAPI::MP::Get(int ConfigID) {
|
|
||||||
switch (ConfigID) {
|
|
||||||
case 0: // debug
|
|
||||||
return Application::Settings.getAsBool(Settings::Key::General_Debug);
|
|
||||||
case 1: // private
|
|
||||||
return Application::Settings.getAsBool(Settings::Key::General_Private);
|
|
||||||
case 2: // max cars
|
|
||||||
return Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
|
||||||
case 3: // max players
|
|
||||||
return Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
|
|
||||||
case 4: // Map
|
|
||||||
return Application::Settings.getAsString(Settings::Key::General_Map);
|
|
||||||
case 5: // Name
|
|
||||||
return Application::Settings.getAsString(Settings::Key::General_Name);
|
|
||||||
case 6: // Desc
|
|
||||||
return Application::Settings.getAsString(Settings::Key::General_Description);
|
|
||||||
case 7: // Information packet
|
|
||||||
return Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
|
|
||||||
default:
|
|
||||||
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LuaAPI::MP::Sleep(size_t Ms) {
|
void LuaAPI::MP::Sleep(size_t Ms) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
|
std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
|
||||||
}
|
}
|
||||||
@@ -409,7 +297,7 @@ void LuaAPI::MP::Sleep(size_t Ms) {
|
|||||||
bool LuaAPI::MP::IsPlayerConnected(int ID) {
|
bool LuaAPI::MP::IsPlayerConnected(int ID) {
|
||||||
auto MaybeClient = GetClient(Engine->Server(), ID);
|
auto MaybeClient = GetClient(Engine->Server(), ID);
|
||||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||||
return MaybeClient.value().lock()->IsUDPConnected();
|
return MaybeClient.value().lock()->IsConnected();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,3 +57,4 @@ size_t prof::UnitExecutionTime::measurement_count() const {
|
|||||||
std::unique_lock lock(m_mtx);
|
std::unique_lock lock(m_mtx);
|
||||||
return m_total_calls;
|
return m_total_calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#include "RateLimiter.h"
|
||||||
|
|
||||||
|
RateLimiter::RateLimiter() {};
|
||||||
|
|
||||||
|
bool RateLimiter::isConnectionAllowed(const std::string& client_address) {
|
||||||
|
if (RateLimiter::isIPBlocked(client_address)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(m_connection_mutex);
|
||||||
|
auto current_time = std::chrono::high_resolution_clock::now();
|
||||||
|
auto& violations = m_connection[client_address];
|
||||||
|
|
||||||
|
// Deleting old violations (older than 5 seconds)
|
||||||
|
violations.erase(std::remove_if(violations.begin(), violations.end(),
|
||||||
|
[&](const auto& timestamp) {
|
||||||
|
return std::chrono::duration_cast<std::chrono::seconds>(current_time - timestamp).count() > 5;
|
||||||
|
}),
|
||||||
|
violations.end());
|
||||||
|
|
||||||
|
violations.push_back(current_time);
|
||||||
|
|
||||||
|
if (violations.size() >= 4) {
|
||||||
|
RateLimiter::blockIP(client_address);
|
||||||
|
beammp_errorf("[DoS Protection] Client with the IP: {} surpassed the violation treshhold and is now on the blocked list", client_address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // We allow the connection
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimiter::blockIP(const std::string& client_address) {
|
||||||
|
std::ofstream block_file("blocked_ips.txt", std::ios::app);
|
||||||
|
if (block_file.is_open()) {
|
||||||
|
block_file << client_address << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RateLimiter::isIPBlocked(const std::string& client_address) {
|
||||||
|
std::ifstream block_file("blocked_ips.txt");
|
||||||
|
std::unordered_set<std::string> blockedIPs;
|
||||||
|
|
||||||
|
if (block_file.is_open()) {
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(block_file, line)) {
|
||||||
|
blockedIPs.insert(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockedIPs.contains(client_address);
|
||||||
|
};
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
// BeamMP, the BeamNG.drive multiplayer mod.
|
|
||||||
// Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
|
|
||||||
//
|
|
||||||
// BeamMP Ltd. can be contacted by electronic mail via contact@beammp.com.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published
|
|
||||||
// by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
Settings::Settings() {
|
|
||||||
SettingsMap = std::unordered_map<Key, SettingsTypeVariant> {
|
|
||||||
// All entries which contain std::strings must be explicitly constructed, otherwise they become 'bool'
|
|
||||||
{ General_Description, std::string("BeamMP Default Description") },
|
|
||||||
{ General_Tags, std::string("Freeroam") },
|
|
||||||
{ General_MaxPlayers, 8 },
|
|
||||||
{ General_Name, std::string("BeamMP Server") },
|
|
||||||
{ General_Map, std::string("/levels/gridmap_v2/info.json") },
|
|
||||||
{ General_AuthKey, std::string("") },
|
|
||||||
{ General_Private, true },
|
|
||||||
{ General_IP, "::"},
|
|
||||||
{ General_Port, 30814 },
|
|
||||||
{ General_MaxCars, 1 },
|
|
||||||
{ General_LogChat, true },
|
|
||||||
{ General_ResourceFolder, std::string("Resources") },
|
|
||||||
{ General_Debug, false },
|
|
||||||
{ General_AllowGuests, true },
|
|
||||||
{ General_InformationPacket, true },
|
|
||||||
{ Misc_ImScaredOfUpdates, true },
|
|
||||||
{ Misc_UpdateReminderTime, "30s" }
|
|
||||||
};
|
|
||||||
|
|
||||||
InputAccessMapping = std::unordered_map<ComposedKey, SettingsAccessControl> {
|
|
||||||
{ { "General", "Description" }, { General_Description, READ_WRITE } },
|
|
||||||
{ { "General", "Tags" }, { General_Tags, READ_WRITE } },
|
|
||||||
{ { "General", "MaxPlayers" }, { General_MaxPlayers, READ_WRITE } },
|
|
||||||
{ { "General", "Name" }, { General_Name, READ_WRITE } },
|
|
||||||
{ { "General", "Map" }, { General_Map, READ_WRITE } },
|
|
||||||
{ { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } },
|
|
||||||
{ { "General", "Private" }, { General_Private, READ_ONLY } },
|
|
||||||
{ { "General", "IP" }, { General_IP, READ_ONLY } },
|
|
||||||
{ { "General", "Port" }, { General_Port, READ_ONLY } },
|
|
||||||
{ { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } },
|
|
||||||
{ { "General", "LogChat" }, { General_LogChat, READ_ONLY } },
|
|
||||||
{ { "General", "ResourceFolder" }, { General_ResourceFolder, READ_ONLY } },
|
|
||||||
{ { "General", "Debug" }, { General_Debug, READ_WRITE } },
|
|
||||||
{ { "General", "AllowGuests" }, { General_AllowGuests, READ_WRITE } },
|
|
||||||
{ { "General", "InformationPacket" }, { General_InformationPacket, READ_WRITE } },
|
|
||||||
{ { "Misc", "ImScaredOfUpdates" }, { Misc_ImScaredOfUpdates, READ_WRITE } },
|
|
||||||
{ { "Misc", "UpdateReminderTime" }, { Misc_UpdateReminderTime, READ_WRITE } }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Settings::getAsString(Key key) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined key accessed in Settings::getAsString" };
|
|
||||||
}
|
|
||||||
return std::get<std::string>(map->at(key));
|
|
||||||
}
|
|
||||||
int Settings::getAsInt(Key key) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined key accessed in Settings::getAsInt" };
|
|
||||||
}
|
|
||||||
return std::get<int>(map->at(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::getAsBool(Key key) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined key accessed in Settings::getAsBool" };
|
|
||||||
}
|
|
||||||
return std::get<bool>(map->at(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings::SettingsTypeVariant Settings::get(Key key) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined setting key accessed in Settings::get" };
|
|
||||||
}
|
|
||||||
return map->at(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::set(Key key, const std::string& value) {
|
|
||||||
auto map = SettingsMap.synchronize();
|
|
||||||
if (!map->contains(key)) {
|
|
||||||
throw std::logic_error { "Undefined setting key accessed in Settings::set(std::string)" };
|
|
||||||
}
|
|
||||||
if (!std::holds_alternative<std::string>(map->at(key))) {
|
|
||||||
throw std::logic_error { fmt::format("Wrong value type in Settings::set(std::string): index {}", map->at(key).index()) };
|
|
||||||
}
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::unordered_map<ComposedKey, Settings::SettingsAccessControl> Settings::getAccessControlMap() const {
|
|
||||||
return *InputAccessMapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings::SettingsAccessControl Settings::getConsoleInputAccessMapping(const ComposedKey& keyName) {
|
|
||||||
auto acl_map = InputAccessMapping.synchronize();
|
|
||||||
if (!acl_map->contains(keyName)) {
|
|
||||||
throw std::logic_error { "Unknown key name accessed in Settings::getConsoleInputAccessMapping" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
|
||||||
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
|
||||||
}
|
|
||||||
return acl_map->at(keyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, const std::string& value) {
|
|
||||||
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
|
||||||
if (!acl_map->contains(keyName)) {
|
|
||||||
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
|
||||||
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
|
||||||
throw std::logic_error { "Setting '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
|
||||||
}
|
|
||||||
|
|
||||||
Key key = acl_map->at(keyName).first;
|
|
||||||
|
|
||||||
if (!std::holds_alternative<std::string>(map->at(key))) {
|
|
||||||
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected std::string" };
|
|
||||||
}
|
|
||||||
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, int value) {
|
|
||||||
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
|
||||||
if (!acl_map->contains(keyName)) {
|
|
||||||
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
|
||||||
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
|
||||||
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
|
||||||
}
|
|
||||||
|
|
||||||
Key key = acl_map->at(keyName).first;
|
|
||||||
|
|
||||||
if (!std::holds_alternative<int>(map->at(key))) {
|
|
||||||
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected int" };
|
|
||||||
}
|
|
||||||
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setConsoleInputAccessMapping(const ComposedKey& keyName, bool value) {
|
|
||||||
auto [map, acl_map] = boost::synchronize(SettingsMap, InputAccessMapping);
|
|
||||||
if (!acl_map->contains(keyName)) {
|
|
||||||
throw std::logic_error { "Unknown key name accessed in Settings::setConsoleInputAccessMapping" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::NO_ACCESS) {
|
|
||||||
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not accessible from within the runtime!" };
|
|
||||||
} else if (acl_map->at(keyName).second == SettingsAccessMask::READ_ONLY) {
|
|
||||||
throw std::logic_error { "Key '" + keyName.Category + "::" + keyName.Key + "' is not writeable from within the runtime!" };
|
|
||||||
}
|
|
||||||
|
|
||||||
Key key = acl_map->at(keyName).first;
|
|
||||||
|
|
||||||
if (!std::holds_alternative<bool>(map->at(key))) {
|
|
||||||
throw std::logic_error { "Wrong value type in Settings::setConsoleInputAccessMapping: expected bool" };
|
|
||||||
}
|
|
||||||
|
|
||||||
map->at(key) = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("settings get/set") {
|
|
||||||
Settings settings;
|
|
||||||
settings.set(Settings::General_Name, "hello, world");
|
|
||||||
CHECK_EQ(settings.getAsString(Settings::General_Name), "hello, world");
|
|
||||||
settings.set(Settings::General_Name, std::string("hello, world"));
|
|
||||||
CHECK_EQ(settings.getAsString(Settings::General_Name), "hello, world");
|
|
||||||
settings.set(Settings::General_MaxPlayers, 12);
|
|
||||||
CHECK_EQ(settings.getAsInt(Settings::General_MaxPlayers), 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("settings check for exception on wrong input type") {
|
|
||||||
Settings settings;
|
|
||||||
CHECK_THROWS(settings.set(Settings::General_Debug, "hello, world"));
|
|
||||||
CHECK_NOTHROW(settings.set(Settings::General_Debug, false));
|
|
||||||
}
|
|
||||||
+157
-121
@@ -19,23 +19,18 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
#include "Env.h"
|
#include "Env.h"
|
||||||
#include "Settings.h"
|
|
||||||
#include "TConfig.h"
|
#include "TConfig.h"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <exception>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
// General
|
// General
|
||||||
static constexpr std::string_view StrDebug = "Debug";
|
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";
|
||||||
@@ -58,15 +53,13 @@ static constexpr std::string_view StrLogChat = "LogChat";
|
|||||||
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
|
static constexpr std::string_view EnvStrLogChat = "BEAMMP_LOG_CHAT";
|
||||||
static constexpr std::string_view StrAllowGuests = "AllowGuests";
|
static constexpr std::string_view StrAllowGuests = "AllowGuests";
|
||||||
static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
|
static constexpr std::string_view EnvStrAllowGuests = "BEAMMP_ALLOW_GUESTS";
|
||||||
static constexpr std::string_view StrInformationPacket = "InformationPacket";
|
|
||||||
static constexpr std::string_view EnvStrInformationPacket = "BEAMMP_INFORMATION_PACKET";
|
|
||||||
static constexpr std::string_view StrPassword = "Password";
|
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";
|
||||||
@@ -130,39 +123,39 @@ void SetComment(CommentsT& Comments, const std::string& Comment) {
|
|||||||
void TConfig::FlushToFile() {
|
void TConfig::FlushToFile() {
|
||||||
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
|
// auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
|
||||||
auto data = toml::value {};
|
auto data = toml::value {};
|
||||||
data["General"][StrAuthKey.data()] = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
data["General"][StrAuthKey.data()] = Application::Settings.Key;
|
||||||
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
|
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
|
||||||
data["General"][StrLogChat.data()] = Application::Settings.getAsBool(Settings::Key::General_LogChat);
|
data["General"][StrLogChat.data()] = Application::Settings.LogChat;
|
||||||
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
|
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
|
||||||
data["General"][StrDebug.data()] = Application::Settings.getAsBool(Settings::Key::General_Debug);
|
data["General"][StrAllowGuests.data()] = Application::Settings.AllowGuests;
|
||||||
data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private);
|
|
||||||
SetComment(data["General"][StrInformationPacket.data()].comments(), " Whether to allow unconnected clients to get the public server information without joining");
|
|
||||||
data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket);
|
|
||||||
data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests);
|
|
||||||
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
|
SetComment(data["General"][StrAllowGuests.data()].comments(), " Whether to allow guests");
|
||||||
data["General"][StrIP.data()] = Application::Settings.getAsString(Settings::Key::General_IP);
|
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
|
||||||
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"][StrPrivate.data()] = Application::Settings.Private;
|
||||||
data["General"][StrPort.data()] = Application::Settings.getAsInt(Settings::Key::General_Port);
|
data["General"][StrPort.data()] = Application::Settings.Port;
|
||||||
data["General"][StrName.data()] = Application::Settings.getAsString(Settings::Key::General_Name);
|
data["General"][StrName.data()] = Application::Settings.ServerName;
|
||||||
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.");
|
||||||
data["General"][StrTags.data()] = Application::Settings.getAsString(Settings::Key::General_Tags);
|
data["General"][StrTags.data()] = Application::Settings.ServerTags;
|
||||||
data["General"][StrMaxCars.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
data["General"][StrMaxCars.data()] = Application::Settings.MaxCars;
|
||||||
data["General"][StrMaxPlayers.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
|
data["General"][StrMaxPlayers.data()] = Application::Settings.MaxPlayers;
|
||||||
data["General"][StrMap.data()] = Application::Settings.getAsString(Settings::Key::General_Map);
|
data["General"][StrMap.data()] = Application::Settings.MapName;
|
||||||
data["General"][StrDescription.data()] = Application::Settings.getAsString(Settings::Key::General_Description);
|
data["General"][StrDescription.data()] = Application::Settings.ServerDesc;
|
||||||
data["General"][StrResourceFolder.data()] = Application::Settings.getAsString(Settings::Key::General_ResourceFolder);
|
data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
|
||||||
// data["General"][StrPassword.data()] = Application::Settings.Password;
|
// data["General"][StrPassword.data()] = Application::Settings.Password;
|
||||||
// SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password.");
|
// SetComment(data["General"][StrPassword.data()].comments(), " Sets a password on this server, which restricts people from joining. To join, a player must enter this exact password. Leave empty ("") to disable the password.");
|
||||||
// Misc
|
// Misc
|
||||||
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates);
|
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages;
|
||||||
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
|
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"][StrUpdateReminderTime.data()] = Application::Settings.getAsString(Settings::Key::Misc_UpdateReminderTime);
|
data["Misc"][StrUpdateReminderTime.data()] = Application::Settings.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.");
|
||||||
|
data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors;
|
||||||
|
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.SendErrorsMessageEnabled;
|
||||||
|
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"
|
||||||
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://keymaster.beammp.com/` on the left under \"Keys\"\n"
|
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://keymaster.beammp.com/` on the left under \"Keys\"\n"
|
||||||
<< toml::format(data);
|
<< data;
|
||||||
auto File = std::fopen(mConfigFileName.c_str(), "w+");
|
auto File = std::fopen(mConfigFileName.c_str(), "w+");
|
||||||
if (!File) {
|
if (!File) {
|
||||||
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
|
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
|
||||||
@@ -181,102 +174,94 @@ void TConfig::CreateConfigFile() {
|
|||||||
if (mDisableConfig) {
|
if (mDisableConfig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if (fs::exists("Server.cfg")) {
|
||||||
|
// parse it (this is weird and bad and should be removed in some future version)
|
||||||
|
ParseOldFormat();
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
FlushToFile();
|
FlushToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This arcane template magic is needed for using lambdas as overloaded visitors
|
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, std::string& OutValue) {
|
||||||
// See https://en.cppreference.com/w/cpp/utility/variant/visit for reference
|
|
||||||
template <class... Ts>
|
|
||||||
struct overloaded : Ts... {
|
|
||||||
using Ts::operator()...;
|
|
||||||
};
|
|
||||||
template <class... Ts>
|
|
||||||
overloaded(Ts...) -> overloaded<Ts...>;
|
|
||||||
|
|
||||||
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, Settings::Key key) {
|
|
||||||
if (!Env.empty()) {
|
if (!Env.empty()) {
|
||||||
if (const char* envp = std::getenv(Env.data());
|
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
||||||
envp != nullptr && std::strcmp(envp, "") != 0) {
|
OutValue = std::string(envp);
|
||||||
|
|
||||||
std::visit(
|
|
||||||
overloaded {
|
|
||||||
[&envp, &key](std::string) {
|
|
||||||
Application::Settings.set(key, std::string(envp));
|
|
||||||
},
|
|
||||||
[&envp, &key](int) {
|
|
||||||
Application::Settings.set(key, int(std::strtol(envp, nullptr, 10)));
|
|
||||||
},
|
|
||||||
[&envp, &key](bool) {
|
|
||||||
auto Str = std::string(envp);
|
|
||||||
Application::Settings.set(key, bool(Str == "1" || Str == "true"));
|
|
||||||
} },
|
|
||||||
|
|
||||||
Application::Settings.get(key));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mDisableConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_string()) {
|
||||||
|
OutValue = Table[Category.c_str()][Key.data()].as_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::visit([&Table, &Category, &Key, &key](auto&& arg) {
|
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, bool& OutValue) {
|
||||||
using T = std::decay_t<decltype(arg)>;
|
if (!Env.empty()) {
|
||||||
if constexpr (std::is_same_v<T, std::string>) {
|
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
||||||
if (Table[Category.c_str()][Key.data()].is_string())
|
auto Str = std::string(envp);
|
||||||
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_string());
|
OutValue = Str == "1" || Str == "true";
|
||||||
else
|
return;
|
||||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'string'", Category, Key);
|
|
||||||
} else if constexpr (std::is_same_v<T, int>) {
|
|
||||||
if (Table[Category.c_str()][Key.data()].is_integer())
|
|
||||||
Application::Settings.set(key, int(Table[Category.c_str()][Key.data()].as_integer()));
|
|
||||||
else
|
|
||||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'integer'", Category, Key);
|
|
||||||
} else if constexpr (std::is_same_v<T, bool>) {
|
|
||||||
if (Table[Category.c_str()][Key.data()].is_boolean())
|
|
||||||
Application::Settings.set(key, Table[Category.c_str()][Key.data()].as_boolean());
|
|
||||||
else
|
|
||||||
beammp_warnf("Value '{}.{}' has unexpected type, expected type 'boolean'", Category, Key);
|
|
||||||
} else {
|
|
||||||
throw std::logic_error { "Invalid type for config value during read attempt" };
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Application::Settings.get(key));
|
if (mDisableConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_boolean()) {
|
||||||
|
OutValue = Table[Category.c_str()][Key.data()].as_boolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, const std::string_view& Env, int& OutValue) {
|
||||||
|
if (!Env.empty()) {
|
||||||
|
if (const char* envp = std::getenv(Env.data()); envp != nullptr && std::strcmp(envp, "") != 0) {
|
||||||
|
OutValue = int(std::strtol(envp, nullptr, 10));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDisableConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Table[Category.c_str()][Key.data()].is_integer()) {
|
||||||
|
OutValue = int(Table[Category.c_str()][Key.data()].as_integer());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TConfig::ParseFromFile(std::string_view name) {
|
void TConfig::ParseFromFile(std::string_view name) {
|
||||||
try {
|
try {
|
||||||
toml::value data {};
|
toml::value data {};
|
||||||
if (!mDisableConfig) {
|
if (!mDisableConfig) {
|
||||||
data = toml::parse(name.data());
|
data = toml::parse<toml::preserve_comments>(name.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
// GENERAL
|
// GENERAL
|
||||||
|
TryReadValue(data, "General", StrDebug, EnvStrDebug, Application::Settings.DebugModeEnabled);
|
||||||
// Read into new Settings Singleton
|
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Application::Settings.Private);
|
||||||
TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug);
|
|
||||||
TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private);
|
|
||||||
TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket);
|
|
||||||
if (Env::Get(Env::Key::PROVIDER_PORT_ENV).has_value()) {
|
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);
|
TryReadValue(data, "General", StrPort, Env::Get(Env::Key::PROVIDER_PORT_ENV).value(), Application::Settings.Port);
|
||||||
} else {
|
} else {
|
||||||
TryReadValue(data, "General", StrPort, EnvStrPort, Settings::Key::General_Port);
|
TryReadValue(data, "General", StrPort, EnvStrPort, Application::Settings.Port);
|
||||||
}
|
}
|
||||||
if (Env::Get(Env::Key::PROVIDER_IP_ENV).has_value()) {
|
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Application::Settings.MaxCars);
|
||||||
TryReadValue(data, "General", StrIP, Env::Get(Env::Key::PROVIDER_IP_ENV).value(), Settings::Key::General_IP);
|
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Application::Settings.MaxPlayers);
|
||||||
} else {
|
TryReadValue(data, "General", StrMap, EnvStrMap, Application::Settings.MapName);
|
||||||
TryReadValue(data, "General", StrIP, EnvStrIP, Settings::Key::General_IP);
|
TryReadValue(data, "General", StrName, EnvStrName, Application::Settings.ServerName);
|
||||||
}
|
TryReadValue(data, "General", StrDescription, EnvStrDescription, Application::Settings.ServerDesc);
|
||||||
TryReadValue(data, "General", StrMaxCars, EnvStrMaxCars, Settings::Key::General_MaxCars);
|
TryReadValue(data, "General", StrTags, EnvStrTags, Application::Settings.ServerTags);
|
||||||
TryReadValue(data, "General", StrMaxPlayers, EnvStrMaxPlayers, Settings::Key::General_MaxPlayers);
|
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Application::Settings.Resource);
|
||||||
TryReadValue(data, "General", StrMap, EnvStrMap, Settings::Key::General_Map);
|
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Application::Settings.Key);
|
||||||
TryReadValue(data, "General", StrName, EnvStrName, Settings::Key::General_Name);
|
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Application::Settings.LogChat);
|
||||||
TryReadValue(data, "General", StrDescription, EnvStrDescription, Settings::Key::General_Description);
|
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Application::Settings.AllowGuests);
|
||||||
TryReadValue(data, "General", StrTags, EnvStrTags, Settings::Key::General_Tags);
|
TryReadValue(data, "General", StrPassword, "", Application::Settings.Password);
|
||||||
TryReadValue(data, "General", StrResourceFolder, EnvStrResourceFolder, Settings::Key::General_ResourceFolder);
|
|
||||||
TryReadValue(data, "General", StrAuthKey, EnvStrAuthKey, Settings::Key::General_AuthKey);
|
|
||||||
TryReadValue(data, "General", StrLogChat, EnvStrLogChat, Settings::Key::General_LogChat);
|
|
||||||
TryReadValue(data, "General", StrAllowGuests, EnvStrAllowGuests, Settings::Key::General_AllowGuests);
|
|
||||||
// Misc
|
// Misc
|
||||||
TryReadValue(data, "Misc", StrHideUpdateMessages, EnvStrHideUpdateMessages, Settings::Key::Misc_ImScaredOfUpdates);
|
TryReadValue(data, "Misc", StrSendErrors, "", Application::Settings.SendErrors);
|
||||||
TryReadValue(data, "Misc", StrUpdateReminderTime, EnvStrUpdateReminderTime, Settings::Key::Misc_UpdateReminderTime);
|
TryReadValue(data, "Misc", StrHideUpdateMessages, "", Application::Settings.HideUpdateMessages);
|
||||||
|
TryReadValue(data, "Misc", StrUpdateReminderTime, "", Application::Settings.UpdateReminderTime);
|
||||||
|
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, "", Application::Settings.SendErrorsMessageEnabled);
|
||||||
} 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()));
|
||||||
mFailed = true;
|
mFailed = true;
|
||||||
@@ -289,7 +274,7 @@ void TConfig::ParseFromFile(std::string_view name) {
|
|||||||
FlushToFile();
|
FlushToFile();
|
||||||
}
|
}
|
||||||
// all good so far, let's check if there's a key
|
// all good so far, let's check if there's a key
|
||||||
if (Application::Settings.getAsString(Settings::Key::General_AuthKey).empty()) {
|
if (Application::Settings.Key.empty()) {
|
||||||
if (mDisableConfig) {
|
if (mDisableConfig) {
|
||||||
beammp_error("No AuthKey specified in the environment.");
|
beammp_error("No AuthKey specified in the environment.");
|
||||||
} else {
|
} else {
|
||||||
@@ -300,7 +285,7 @@ void TConfig::ParseFromFile(std::string_view name) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Application::SetSubsystemStatus("Config", Application::Status::Good);
|
Application::SetSubsystemStatus("Config", Application::Status::Good);
|
||||||
if (Application::Settings.getAsString(Settings::Key::General_AuthKey).size() != 36) {
|
if (Application::Settings.Key.size() != 36) {
|
||||||
beammp_warn("AuthKey specified is the wrong length and likely isn't valid.");
|
beammp_warn("AuthKey specified is the wrong length and likely isn't valid.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,27 +294,78 @@ void TConfig::PrintDebug() {
|
|||||||
if (mDisableConfig) {
|
if (mDisableConfig) {
|
||||||
beammp_debug("Provider turned off the generation and parsing of the ServerConfig.toml");
|
beammp_debug("Provider turned off the generation and parsing of the ServerConfig.toml");
|
||||||
}
|
}
|
||||||
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false"));
|
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
|
||||||
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.Private ? "true" : "false"));
|
||||||
beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false"));
|
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
|
||||||
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)));
|
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars));
|
||||||
beammp_debug(std::string(StrIP) + ": \"" + Application::Settings.getAsString(Settings::Key::General_IP) + "\"");
|
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers));
|
||||||
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars)));
|
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
|
||||||
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)));
|
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
|
||||||
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Map) + "\"");
|
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
|
||||||
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Name) + "\"");
|
|
||||||
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.getAsString(Settings::Key::General_Description) + "\"");
|
|
||||||
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
|
beammp_debug(std::string(StrTags) + ": " + TagsAsPrettyArray());
|
||||||
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_LogChat) ? "true" : "false") + "\"");
|
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
|
||||||
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "\"");
|
beammp_debug(std::string(StrAllowGuests) + ": \"" + (Application::Settings.AllowGuests ? "true" : "false") + "\"");
|
||||||
beammp_debug(std::string(StrAllowGuests) + ": \"" + (Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false") + "\"");
|
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
|
||||||
// special!
|
// special!
|
||||||
beammp_debug("Key Length: " + std::to_string(Application::Settings.getAsString(Settings::Key::General_AuthKey).length()) + "");
|
beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
|
||||||
|
beammp_debug("Password Protected: " + std::string(Application::Settings.Password.empty() ? "false" : "true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TConfig::ParseOldFormat() {
|
||||||
|
std::ifstream File("Server.cfg");
|
||||||
|
// read all, strip comments
|
||||||
|
std::string Content;
|
||||||
|
for (;;) {
|
||||||
|
std::string Line;
|
||||||
|
std::getline(File, Line);
|
||||||
|
if (!Line.empty() && Line.at(0) != '#') {
|
||||||
|
Line = Line.substr(0, Line.find_first_of('#'));
|
||||||
|
Content += Line + "\n";
|
||||||
|
}
|
||||||
|
if (!File.good()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::stringstream Str(Content);
|
||||||
|
std::string Key, Ignore, Value;
|
||||||
|
for (;;) {
|
||||||
|
Str >> Key >> std::ws >> Ignore >> std::ws;
|
||||||
|
std::getline(Str, Value);
|
||||||
|
if (Str.eof()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::stringstream ValueStream(Value);
|
||||||
|
ValueStream >> std::ws; // strip leading whitespace if any
|
||||||
|
Value = ValueStream.str();
|
||||||
|
if (Key == "Debug") {
|
||||||
|
Application::Settings.DebugModeEnabled = Value.find("true") != std::string::npos;
|
||||||
|
} else if (Key == "Private") {
|
||||||
|
Application::Settings.Private = Value.find("true") != std::string::npos;
|
||||||
|
} else if (Key == "Port") {
|
||||||
|
ValueStream >> Application::Settings.Port;
|
||||||
|
} else if (Key == "Cars") {
|
||||||
|
ValueStream >> Application::Settings.MaxCars;
|
||||||
|
} else if (Key == "MaxPlayers") {
|
||||||
|
ValueStream >> Application::Settings.MaxPlayers;
|
||||||
|
} else if (Key == "Map") {
|
||||||
|
Application::Settings.MapName = Value.substr(1, Value.size() - 3);
|
||||||
|
} else if (Key == "Name") {
|
||||||
|
Application::Settings.ServerName = Value.substr(1, Value.size() - 3);
|
||||||
|
} else if (Key == "Desc") {
|
||||||
|
Application::Settings.ServerDesc = Value.substr(1, Value.size() - 3);
|
||||||
|
} else if (Key == "use") {
|
||||||
|
Application::Settings.Resource = Value.substr(1, Value.size() - 3);
|
||||||
|
} else if (Key == "AuthKey") {
|
||||||
|
Application::Settings.Key = Value.substr(1, Value.size() - 3);
|
||||||
|
} else {
|
||||||
|
beammp_warn("unknown key in old auth file (ignored): " + Key);
|
||||||
|
}
|
||||||
|
Str >> std::ws;
|
||||||
|
}
|
||||||
|
}
|
||||||
std::string TConfig::TagsAsPrettyArray() const {
|
std::string TConfig::TagsAsPrettyArray() const {
|
||||||
std::vector<std::string> TagsArray = {};
|
std::vector<std::string> TagsArray = {};
|
||||||
SplitString(Application::Settings.getAsString(Settings::General_Tags), ',', TagsArray);
|
SplitString(Application::Settings.ServerTags, ',', TagsArray);
|
||||||
std::string Pretty = {};
|
std::string Pretty = {};
|
||||||
for (size_t i = 0; i < TagsArray.size() - 1; ++i) {
|
for (size_t i = 0; i < TagsArray.size() - 1; ++i) {
|
||||||
Pretty += '\"' + TagsArray[i] + "\", ";
|
Pretty += '\"' + TagsArray[i] + "\", ";
|
||||||
|
|||||||
+54
-207
@@ -24,15 +24,12 @@
|
|||||||
#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>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <openssl/opensslv.h>
|
#include <openssl/opensslv.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
|
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
|
||||||
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
|
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
|
||||||
@@ -81,7 +78,7 @@ static std::string GetDate() {
|
|||||||
auto local_tm = std::localtime(&tt);
|
auto local_tm = std::localtime(&tt);
|
||||||
char buf[30];
|
char buf[30];
|
||||||
std::string date;
|
std::string date;
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
if (Application::Settings.DebugModeEnabled) {
|
||||||
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
|
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
|
||||||
date += buf;
|
date += buf;
|
||||||
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
|
||||||
@@ -109,6 +106,41 @@ void TConsole::BackupOldLog() {
|
|||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_warn(e.what());
|
beammp_warn(e.what());
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
int err = 0;
|
||||||
|
zip* z = zip_open("ServerLogs.zip", ZIP_CREATE, &err);
|
||||||
|
if (!z) {
|
||||||
|
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FILE* File = std::fopen(Path.string().c_str(), "r");
|
||||||
|
if (!File) {
|
||||||
|
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> Buffer;
|
||||||
|
Buffer.resize(fs::file_size(Path));
|
||||||
|
std::fread(Buffer.data(), 1, Buffer.size(), File);
|
||||||
|
std::fclose(File);
|
||||||
|
|
||||||
|
auto s = zip_source_buffer(z, Buffer.data(), Buffer.size(), 0);
|
||||||
|
|
||||||
|
auto TimePoint = fs::last_write_time(Path);
|
||||||
|
auto Secs = TimePoint.time_since_epoch().count();
|
||||||
|
auto MyTimeT = std::time(&Secs);
|
||||||
|
|
||||||
|
std::string NewName = Path.stem().string();
|
||||||
|
NewName += "_";
|
||||||
|
std::string Time;
|
||||||
|
Time.resize(32);
|
||||||
|
size_t n = strftime(Time.data(), Time.size(), "%F_%H.%M.%S", localtime(&MyTimeT));
|
||||||
|
Time.resize(n);
|
||||||
|
NewName += Time;
|
||||||
|
NewName += ".log";
|
||||||
|
|
||||||
|
zip_file_add(z, NewName.c_str(), s, 0);
|
||||||
|
zip_close(z);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +192,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.empty()) {
|
if (n == 0 && args.size() != 0) {
|
||||||
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) {
|
||||||
@@ -198,7 +230,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.empty()) {
|
} else if (args.size() == 0) {
|
||||||
ChangeToLuaConsole(mDefaultStateId);
|
ChangeToLuaConsole(mDefaultStateId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,18 +241,16 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,60 +290,11 @@ 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());
|
||||||
Application::Console().WriteRaw(LUA_RELEASE);
|
std::string lua_version = fmt::format("Lua: v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_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))) {
|
||||||
@@ -405,142 +386,8 @@ std::tuple<std::string, std::vector<std::string>> TConsole::ParseCommand(const s
|
|||||||
return { Command, Args };
|
return { Command, Args };
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class... Ts>
|
|
||||||
struct overloaded : Ts... {
|
|
||||||
using Ts::operator()...;
|
|
||||||
};
|
|
||||||
template <class... Ts>
|
|
||||||
overloaded(Ts...) -> overloaded<Ts...>;
|
|
||||||
|
|
||||||
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
|
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
|
||||||
|
if (!EnsureArgsCount(args, 1, 2)) {
|
||||||
static constexpr const char* sHelpString = R"(
|
|
||||||
Settings:
|
|
||||||
settings help displays this help
|
|
||||||
settings list lists all settings
|
|
||||||
settings get <category> <setting> prints current value of specified setting
|
|
||||||
settings set <category> <setting> <value> sets specified setting to value
|
|
||||||
)";
|
|
||||||
|
|
||||||
if (args.empty()) {
|
|
||||||
beammp_errorf("No arguments specified for command 'settings'!");
|
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.front() == "help") {
|
|
||||||
|
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
|
||||||
return;
|
|
||||||
} else if (args.front() == "get") {
|
|
||||||
if (args.size() < 3) {
|
|
||||||
beammp_errorf("'settings get' needs at least two arguments!");
|
|
||||||
|
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) });
|
|
||||||
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
|
||||||
|
|
||||||
std::visit(
|
|
||||||
overloaded {
|
|
||||||
[&args](std::string keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
|
||||||
},
|
|
||||||
[&args](int keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
|
||||||
},
|
|
||||||
[&args](bool keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("'{}::{}' = {}", args.at(1), args.at(2), keyValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
keyType);
|
|
||||||
|
|
||||||
} catch (std::logic_error& e) {
|
|
||||||
beammp_errorf("Error when getting key: {}", e.what());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (args.front() == "set") {
|
|
||||||
if (args.size() <= 3) {
|
|
||||||
beammp_errorf("'settings set' needs at least three arguments!");
|
|
||||||
|
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) });
|
|
||||||
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
|
||||||
|
|
||||||
std::visit(
|
|
||||||
overloaded {
|
|
||||||
[&args](std::string keyValue) {
|
|
||||||
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, std::string(args.at(3)));
|
|
||||||
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), std::string(args.at(3))));
|
|
||||||
},
|
|
||||||
[&args](int keyValue) {
|
|
||||||
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, std::stoi(args.at(3)));
|
|
||||||
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), std::stoi(args.at(3))));
|
|
||||||
},
|
|
||||||
[&args](bool keyValue) {
|
|
||||||
if (args.at(3) == "true") {
|
|
||||||
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, true);
|
|
||||||
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), "true"));
|
|
||||||
} else if (args.at(3) == "false") {
|
|
||||||
Application::Settings.setConsoleInputAccessMapping(ComposedKey { args.at(1), args.at(2) }, false);
|
|
||||||
Application::Console().WriteRaw(fmt::format("{}::{} := {}", args.at(1), args.at(2), "false"));
|
|
||||||
} else {
|
|
||||||
beammp_errorf("Error when setting key: {}::{} : Unknown literal, use either 'true', or 'false' to set boolean values.", args.at(1), args.at(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
keyType);
|
|
||||||
|
|
||||||
} catch (std::logic_error& e) {
|
|
||||||
beammp_errorf("Exception when setting settings key via console: {}", e.what());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (args.front() == "list") {
|
|
||||||
for (const auto& [composedKey, keyACL] : Application::Settings.getAccessControlMap()) {
|
|
||||||
// even though we have the value, we want to ignore it in order to make use of access
|
|
||||||
// control checks
|
|
||||||
|
|
||||||
if (keyACL.second != Settings::SettingsAccessMask::NO_ACCESS) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
Settings::SettingsAccessControl acl = Application::Settings.getConsoleInputAccessMapping(composedKey);
|
|
||||||
Settings::SettingsTypeVariant keyType = Application::Settings.get(acl.first);
|
|
||||||
|
|
||||||
std::visit(
|
|
||||||
overloaded {
|
|
||||||
[&composedKey](std::string keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
|
||||||
},
|
|
||||||
[&composedKey](int keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
|
||||||
},
|
|
||||||
[&composedKey](bool keyValue) {
|
|
||||||
Application::Console().WriteRaw(fmt::format("{} = {}", composedKey, keyValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
keyType);
|
|
||||||
} catch (std::logic_error& e) {
|
|
||||||
beammp_errorf("Error when getting key: {}", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beammp_errorf("Unknown argument for command 'settings': {}", args.front());
|
|
||||||
|
|
||||||
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,7 +396,7 @@ void TConsole::Command_Say(const std::string& FullCmd) {
|
|||||||
if (FullCmd.size() > 3) {
|
if (FullCmd.size() > 3) {
|
||||||
auto Message = FullCmd.substr(4);
|
auto Message = FullCmd.substr(4);
|
||||||
LuaAPI::MP::SendChatMessage(-1, Message);
|
LuaAPI::MP::SendChatMessage(-1, Message);
|
||||||
if (!Application::Settings.getAsBool(Settings::Key::General_LogChat)) {
|
if (!Application::Settings.LogChat) {
|
||||||
Application::Console().WriteRaw("Chat message sent!");
|
Application::Console().WriteRaw("Chat message sent!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +442,7 @@ void TConsole::Command_Status(const std::string&, const std::vector<std::string>
|
|||||||
if (!Client.expired()) {
|
if (!Client.expired()) {
|
||||||
auto Locked = Client.lock();
|
auto Locked = Client.lock();
|
||||||
CarCount += Locked->GetCarCount();
|
CarCount += Locked->GetCarCount();
|
||||||
ConnectedCount += Locked->IsUDPConnected() ? 1 : 0;
|
ConnectedCount += Locked->IsConnected() ? 1 : 0;
|
||||||
GuestCount += Locked->IsGuest() ? 1 : 0;
|
GuestCount += Locked->IsGuest() ? 1 : 0;
|
||||||
SyncedCount += Locked->IsSynced() ? 1 : 0;
|
SyncedCount += Locked->IsSynced() ? 1 : 0;
|
||||||
SyncingCount += Locked->IsSyncing() ? 1 : 0;
|
SyncingCount += Locked->IsSyncing() ? 1 : 0;
|
||||||
@@ -704,7 +551,7 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (NonNilFutures.empty()) {
|
if (NonNilFutures.size() == 0) {
|
||||||
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.");
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-48
@@ -18,14 +18,16 @@
|
|||||||
|
|
||||||
#include "THeartbeatThread.h"
|
#include "THeartbeatThread.h"
|
||||||
|
|
||||||
#include "ChronoWrapper.h"
|
|
||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
// #include "SocketIO.h"
|
#include "ChronoWrapper.h"
|
||||||
#include <nlohmann/json.hpp>
|
//#include "SocketIO.h"
|
||||||
|
#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;
|
||||||
@@ -38,8 +40,8 @@ void THeartbeatThread::operator()() {
|
|||||||
static std::chrono::high_resolution_clock::time_point LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
static std::chrono::high_resolution_clock::time_point LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
||||||
bool isAuth = false;
|
bool isAuth = false;
|
||||||
std::chrono::high_resolution_clock::duration UpdateReminderTimePassed;
|
std::chrono::high_resolution_clock::duration UpdateReminderTimePassed;
|
||||||
|
auto UpdateReminderTimeout = ChronoWrapper::TimeFromStringWithLiteral(Application::Settings.UpdateReminderTime);
|
||||||
while (!Application::IsShuttingDown()) {
|
while (!Application::IsShuttingDown()) {
|
||||||
auto UpdateReminderTimeout = ChronoWrapper::TimeFromStringWithLiteral(Application::Settings.getAsString(Settings::Key::Misc_UpdateReminderTime));
|
|
||||||
Body = GenerateCall();
|
Body = GenerateCall();
|
||||||
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
||||||
auto Now = std::chrono::high_resolution_clock::now();
|
auto Now = std::chrono::high_resolution_clock::now();
|
||||||
@@ -55,23 +57,22 @@ void THeartbeatThread::operator()() {
|
|||||||
|
|
||||||
Last = Body;
|
Last = Body;
|
||||||
LastNormalUpdateTime = Now;
|
LastNormalUpdateTime = Now;
|
||||||
|
if (!Application::Settings.CustomIP.empty()) {
|
||||||
|
Body += "&ip=" + Application::Settings.CustomIP;
|
||||||
|
}
|
||||||
|
|
||||||
auto Target = "/heartbeat";
|
auto Target = "/heartbeat";
|
||||||
unsigned int ResponseCode = 0;
|
unsigned int ResponseCode = 0;
|
||||||
|
|
||||||
nlohmann::json Doc;
|
json::Document Doc;
|
||||||
bool Ok = false;
|
bool Ok = false;
|
||||||
for (const auto& Url : Application::GetBackendUrlsInOrder()) {
|
for (const auto& Url : Application::GetBackendUrlsInOrder()) {
|
||||||
T = Http::POST(Url + Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } });
|
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
|
||||||
|
Doc.Parse(T.data(), T.size());
|
||||||
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
if (Doc.HasParseError() || !Doc.IsObject()) {
|
||||||
beammp_debug("Backend response was: `" + T + "`");
|
if (!Application::Settings.Private) {
|
||||||
}
|
|
||||||
|
|
||||||
Doc = nlohmann::json::parse(T, nullptr, false);
|
|
||||||
if (Doc.is_discarded() || !Doc.is_object()) {
|
|
||||||
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
|
||||||
beammp_trace("Backend response failed to parse as valid json");
|
beammp_trace("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 +91,18 @@ void THeartbeatThread::operator()() {
|
|||||||
const auto MessageKey = "msg";
|
const auto MessageKey = "msg";
|
||||||
|
|
||||||
if (Ok) {
|
if (Ok) {
|
||||||
if (Doc.contains(StatusKey) && Doc[StatusKey].is_string()) {
|
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) {
|
||||||
Status = Doc[StatusKey];
|
Status = Doc[StatusKey].GetString();
|
||||||
} else {
|
} else {
|
||||||
Ok = false;
|
Ok = false;
|
||||||
}
|
}
|
||||||
if (Doc.contains(CodeKey) && Doc[CodeKey].is_string()) {
|
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) {
|
||||||
Code = Doc[CodeKey];
|
Code = Doc[CodeKey].GetString();
|
||||||
} else {
|
} else {
|
||||||
Ok = false;
|
Ok = false;
|
||||||
}
|
}
|
||||||
if (Doc.contains(MessageKey) && Doc[MessageKey].is_string()) {
|
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) {
|
||||||
Message = Doc[MessageKey];
|
Message = Doc[MessageKey].GetString();
|
||||||
} else {
|
} else {
|
||||||
Ok = false;
|
Ok = false;
|
||||||
}
|
}
|
||||||
@@ -109,12 +110,12 @@ void THeartbeatThread::operator()() {
|
|||||||
beammp_error("Missing/invalid json members in backend response");
|
beammp_error("Missing/invalid json members in backend response");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
if (!Application::Settings.Private) {
|
||||||
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
|
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
if (Ok && !isAuth && !Application::Settings.Private) {
|
||||||
if (Status == "2000") {
|
if (Status == "2000") {
|
||||||
beammp_info(("Authenticated! " + Message));
|
beammp_info(("Authenticated! " + Message));
|
||||||
isAuth = true;
|
isAuth = true;
|
||||||
@@ -128,10 +129,11 @@ void THeartbeatThread::operator()() {
|
|||||||
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
|
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) {
|
if (isAuth || Application::Settings.Private) {
|
||||||
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
|
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
|
||||||
}
|
}
|
||||||
if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) {
|
// beammp_debugf("Update reminder time passed: {}, Update reminder time: {}", UpdateReminderTimePassed.count(), UpdateReminderTimeout.count());
|
||||||
|
if (!Application::Settings.HideUpdateMessages && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) {
|
||||||
LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
LastUpdateReminderTime = std::chrono::high_resolution_clock::now();
|
||||||
Application::CheckForUpdates();
|
Application::CheckForUpdates();
|
||||||
}
|
}
|
||||||
@@ -139,30 +141,26 @@ void THeartbeatThread::operator()() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string THeartbeatThread::GenerateCall() {
|
std::string THeartbeatThread::GenerateCall() {
|
||||||
nlohmann::json Ret = {
|
std::stringstream Ret;
|
||||||
{ "players", std::to_string(mServer.ClientCount()) },
|
|
||||||
{ "maxplayers", std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) },
|
|
||||||
{ "port", std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) },
|
|
||||||
{ "map", Application::Settings.getAsString(Settings::Key::General_Map) },
|
|
||||||
{ "private", Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false" },
|
|
||||||
{ "version", Application::ServerVersionString() },
|
|
||||||
{ "clientversion", Application::ClientMinimumVersion().AsString() },
|
|
||||||
{ "name", Application::Settings.getAsString(Settings::Key::General_Name) },
|
|
||||||
{ "tags", Application::Settings.getAsString(Settings::Key::General_Tags) },
|
|
||||||
{ "guests", Application::Settings.getAsBool(Settings::Key::General_AllowGuests) ? "true" : "false" },
|
|
||||||
{ "modlist", mResourceManager.TrimmedList() },
|
|
||||||
{ "modstotalsize", std::to_string(mResourceManager.MaxModSize()) },
|
|
||||||
{ "modstotal", std::to_string(mResourceManager.ModsLoaded()) },
|
|
||||||
{ "playerslist", GetPlayers() },
|
|
||||||
{ "desc", Application::Settings.getAsString(Settings::Key::General_Description) }
|
|
||||||
};
|
|
||||||
|
|
||||||
lastCall = Ret.dump();
|
Ret << "uuid=" << Application::Settings.Key
|
||||||
|
<< "&players=" << mServer.ClientCount()
|
||||||
// Add sensitive information here because value of lastCall is used for the information packet.
|
<< "&maxplayers=" << Application::Settings.MaxPlayers
|
||||||
Ret["uuid"] = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
<< "&port=" << Application::Settings.Port
|
||||||
|
<< "&map=" << Application::Settings.MapName
|
||||||
return Ret.dump();
|
<< "&private=" << (Application::Settings.Private ? "true" : "false")
|
||||||
|
<< "&version=" << Application::ServerVersionString()
|
||||||
|
<< "&clientversion=" << std::to_string(Application::ClientMajorVersion()) + ".0" // FIXME: Wtf.
|
||||||
|
<< "&name=" << Application::Settings.ServerName
|
||||||
|
<< "&tags=" << Application::Settings.ServerTags
|
||||||
|
<< "&guests=" << (Application::Settings.AllowGuests ? "true" : "false")
|
||||||
|
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||||
|
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||||
|
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||||
|
<< "&playerslist=" << GetPlayers()
|
||||||
|
<< "&desc=" << Application::Settings.ServerDesc
|
||||||
|
<< "&pass=" << (Application::Settings.Password.empty() ? "false" : "true");
|
||||||
|
return Ret.str();
|
||||||
}
|
}
|
||||||
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
||||||
: mResourceManager(ResourceManager)
|
: mResourceManager(ResourceManager)
|
||||||
|
|||||||
+17
-50
@@ -37,11 +37,11 @@
|
|||||||
TLuaEngine* LuaAPI::MP::Engine;
|
TLuaEngine* LuaAPI::MP::Engine;
|
||||||
|
|
||||||
TLuaEngine::TLuaEngine()
|
TLuaEngine::TLuaEngine()
|
||||||
: mResourceServerPath(fs::path(Application::Settings.getAsString(Settings::Key::General_ResourceFolder)) / "Server") {
|
: mResourceServerPath(fs::path(Application::Settings.Resource) / "Server") {
|
||||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
|
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
|
||||||
LuaAPI::MP::Engine = this;
|
LuaAPI::MP::Engine = this;
|
||||||
if (!fs::exists(Application::Settings.getAsString(Settings::Key::General_ResourceFolder))) {
|
if (!fs::exists(Application::Settings.Resource)) {
|
||||||
fs::create_directory(Application::Settings.getAsString(Settings::Key::General_ResourceFolder));
|
fs::create_directory(Application::Settings.Resource);
|
||||||
}
|
}
|
||||||
if (!fs::exists(mResourceServerPath)) {
|
if (!fs::exists(mResourceServerPath)) {
|
||||||
fs::create_directory(mResourceServerPath);
|
fs::create_directory(mResourceServerPath);
|
||||||
@@ -57,18 +57,17 @@ TLuaEngine::TLuaEngine()
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("TLuaEngine ctor & dtor") {
|
TEST_CASE("TLuaEngine ctor & dtor") {
|
||||||
Application::Settings.set(Settings::Key::General_ResourceFolder, "beammp_server_test_resources");
|
Application::Settings.Resource = "beammp_server_test_resources";
|
||||||
TLuaEngine engine;
|
TLuaEngine engine;
|
||||||
Application::GracefullyShutdown();
|
Application::GracefullyShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TLuaEngine::operator()() {
|
void TLuaEngine::operator()() {
|
||||||
RegisterThread("LuaEngine");
|
RegisterThread("LuaEngine");
|
||||||
// lua engine main thread
|
|
||||||
beammp_infof(LUA_RELEASE);
|
|
||||||
CollectAndInitPlugins();
|
|
||||||
|
|
||||||
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
Application::SetSubsystemStatus("LuaEngine", Application::Status::Good);
|
||||||
|
// lua engine main thread
|
||||||
|
beammp_infof("Lua v{}.{}.{}", LUA_VERSION_MAJOR, LUA_VERSION_MINOR, LUA_VERSION_RELEASE);
|
||||||
|
CollectAndInitPlugins();
|
||||||
// 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));
|
||||||
@@ -127,7 +126,7 @@ void TLuaEngine::operator()() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mLuaStates.empty()) {
|
if (mLuaStates.size() == 0) {
|
||||||
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 {
|
||||||
@@ -355,9 +354,9 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueScript(TLuaStateId StateID, const
|
|||||||
return mLuaStates.at(StateID)->EnqueueScript(Script);
|
return mLuaStates.at(StateID)->EnqueueScript(Script);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
|
std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
|
||||||
std::unique_lock Lock(mLuaStatesMutex);
|
std::unique_lock Lock(mLuaStatesMutex);
|
||||||
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args, EventName);
|
return mLuaStates.at(StateID)->EnqueueFunctionCall(FunctionName, Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TLuaEngine::CollectAndInitPlugins() {
|
void TLuaEngine::CollectAndInitPlugins() {
|
||||||
@@ -430,7 +429,7 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
|
|||||||
mLuaStates[StateId] = std::move(DataPtr);
|
mLuaStates[StateId] = std::move(DataPtr);
|
||||||
RegisterEvent("onInit", StateId, "onInit");
|
RegisterEvent("onInit", StateId, "onInit");
|
||||||
if (!DontCallOnInit) {
|
if (!DontCallOnInit) {
|
||||||
auto Res = EnqueueFunctionCall(StateId, "onInit", {}, "onInit");
|
auto Res = EnqueueFunctionCall(StateId, "onInit", {});
|
||||||
Res->WaitUntilReady();
|
Res->WaitUntilReady();
|
||||||
if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
if (Res->Error && Res->ErrorMessage != TLuaEngine::BeamMPFnNotFoundError) {
|
||||||
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
|
beammp_lua_error("Calling \"onInit\" on \"" + StateId + "\" failed: " + Res->ErrorMessage);
|
||||||
@@ -574,16 +573,6 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayerRole(int ID) {
|
|
||||||
auto MaybeClient = GetClient(mEngine->Server(), ID);
|
|
||||||
if (MaybeClient) {
|
|
||||||
return MaybeClient.value().lock()->GetRoles();
|
|
||||||
} else {
|
|
||||||
return sol::nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
|
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
|
||||||
sol::table Result = mStateView.create_table();
|
sol::table Result = mStateView.create_table();
|
||||||
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
|
||||||
@@ -661,7 +650,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.DataAsPacket(Client->GetRoles(), Client->GetName(), Client->GetID()).substr(3);
|
Result[v.ID()] = v.Data().substr(3);
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
} else
|
} else
|
||||||
@@ -858,23 +847,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
return Lua_GetPositionRaw(PID, VID);
|
return Lua_GetPositionRaw(PID, VID);
|
||||||
});
|
});
|
||||||
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
|
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
|
||||||
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
|
|
||||||
if (Args.size() == 2) {
|
|
||||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), "", Args.get<std::string>(1));
|
|
||||||
} else if (Args.size() == 3) {
|
|
||||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(1));
|
|
||||||
} else if (Args.size() == 4) {
|
|
||||||
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(3));
|
|
||||||
} else {
|
|
||||||
beammp_lua_error("SendNotification expects 2, 3 or 4 arguments.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
MPTable.set_function("ConfirmationDialog", sol::overload(
|
|
||||||
&LuaAPI::MP::ConfirmationDialog,
|
|
||||||
[&](const int& ID, const std::string& Title, const std::string& Body, const sol::table& Buttons, const std::string& InteractionID) {
|
|
||||||
LuaAPI::MP::ConfirmationDialog(ID, Title, Body, Buttons, InteractionID);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
MPTable.set_function("GetPlayers", [&]() -> sol::table {
|
MPTable.set_function("GetPlayers", [&]() -> sol::table {
|
||||||
return Lua_GetPlayers();
|
return Lua_GetPlayers();
|
||||||
});
|
});
|
||||||
@@ -889,9 +861,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
|
MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
|
||||||
return Lua_GetPlayerIdentifiers(ID);
|
return Lua_GetPlayerIdentifiers(ID);
|
||||||
});
|
});
|
||||||
MPTable.set_function("GetPlayerRole", [&](int ID) -> std::variant<std::string, sol::nil_t> {
|
|
||||||
return Lua_GetPlayerRole(ID);
|
|
||||||
});
|
|
||||||
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
|
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
|
||||||
// const std::string& EventName, size_t IntervalMS, int strategy
|
// const std::string& EventName, size_t IntervalMS, int strategy
|
||||||
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
|
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
|
||||||
@@ -919,7 +888,6 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
mEngine->CancelEventTimers(EventName, mStateId);
|
mEngine->CancelEventTimers(EventName, mStateId);
|
||||||
});
|
});
|
||||||
MPTable.set_function("Set", &LuaAPI::MP::Set);
|
MPTable.set_function("Set", &LuaAPI::MP::Set);
|
||||||
MPTable.set_function("Get", &LuaAPI::MP::Get);
|
|
||||||
|
|
||||||
auto UtilTable = StateView.create_named_table("Util");
|
auto UtilTable = StateView.create_named_table("Util");
|
||||||
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
|
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
|
||||||
@@ -928,7 +896,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
ToPrint += LuaAPI::LuaToString(static_cast<const sol::object>(arg));
|
||||||
ToPrint += "\t";
|
ToPrint += "\t";
|
||||||
}
|
}
|
||||||
if (Application::Settings.getAsBool(Settings::Key::General_Debug)) {
|
if (Application::Settings.DebugModeEnabled) {
|
||||||
beammp_lua_log("DEBUG", mStateId, ToPrint);
|
beammp_lua_log("DEBUG", mStateId, ToPrint);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1011,8 +979,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
|
|||||||
"MaxPlayers", 3,
|
"MaxPlayers", 3,
|
||||||
"Map", 4,
|
"Map", 4,
|
||||||
"Name", 5,
|
"Name", 5,
|
||||||
"Description", 6,
|
"Description", 6);
|
||||||
"InformationPacket", 7);
|
|
||||||
|
|
||||||
MPTable.create_named("CallStrategy",
|
MPTable.create_named("CallStrategy",
|
||||||
"BestEffort", CallStrategy::BestEffort,
|
"BestEffort", CallStrategy::BestEffort,
|
||||||
@@ -1068,12 +1035,12 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFrom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args, const std::string& EventName) {
|
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaValue>& Args) {
|
||||||
auto Result = std::make_shared<TLuaResult>();
|
auto Result = std::make_shared<TLuaResult>();
|
||||||
Result->StateId = mStateId;
|
Result->StateId = mStateId;
|
||||||
Result->Function = FunctionName;
|
Result->Function = FunctionName;
|
||||||
std::unique_lock Lock(mStateFunctionQueueMutex);
|
std::unique_lock Lock(mStateFunctionQueueMutex);
|
||||||
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
|
mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" });
|
||||||
mStateFunctionQueueCond.notify_all();
|
mStateFunctionQueueCond.notify_all();
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -1168,7 +1135,7 @@ void TLuaEngine::StateThreadData::operator()() {
|
|||||||
case TLuaType::Bool:
|
case TLuaType::Bool:
|
||||||
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
|
LuaArgs.push_back(sol::make_object(StateView, std::get<bool>(Arg)));
|
||||||
break;
|
break;
|
||||||
case TLuaType::StringStringMap: {
|
case TLuaType::StringStringMap: {
|
||||||
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
|
auto Map = std::get<std::unordered_map<std::string, std::string>>(Arg);
|
||||||
auto Table = StateView.create_table();
|
auto Table = StateView.create_table();
|
||||||
for (const auto& [k, v] : Map) {
|
for (const auto& [k, v] : Map) {
|
||||||
|
|||||||
+183
-237
@@ -20,21 +20,15 @@
|
|||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "LuaAPI.h"
|
#include "LuaAPI.h"
|
||||||
#include "THeartbeatThread.h"
|
#include "RateLimiter.h"
|
||||||
#include "TLuaEngine.h"
|
#include "TLuaEngine.h"
|
||||||
#include "TScopedTimer.h"
|
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include <CustomAssert.h>
|
#include <CustomAssert.h>
|
||||||
#include <Http.h>
|
#include <Http.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <boost/asio/ip/address.hpp>
|
#include <boost/asio/ip/address.hpp>
|
||||||
#include <boost/asio/ip/address_v4.hpp>
|
#include <boost/asio/ip/address_v4.hpp>
|
||||||
#include <boost/asio/ip/address_v6.hpp>
|
|
||||||
#include <boost/asio/ip/v6_only.hpp>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
|
typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option;
|
||||||
|
|
||||||
@@ -87,29 +81,14 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
|
|||||||
|
|
||||||
void TNetwork::UDPServerMain() {
|
void TNetwork::UDPServerMain() {
|
||||||
RegisterThread("UDPServer");
|
RegisterThread("UDPServer");
|
||||||
|
ip::udp::endpoint UdpListenEndpoint(ip::address::from_string("0.0.0.0"), Application::Settings.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());
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||||
Application::GracefullyShutdown();
|
Application::GracefullyShutdown();
|
||||||
}
|
}
|
||||||
// set IP_V6ONLY to false to allow both v4 and v6
|
|
||||||
boost::asio::ip::v6_only option(false);
|
|
||||||
mUDPSock.set_option(option, ec);
|
|
||||||
if (ec) {
|
|
||||||
beammp_warnf("Failed to unset IP_V6ONLY on UDP, only IPv6 will work: {}", ec.message());
|
|
||||||
}
|
|
||||||
mUDPSock.bind(UdpListenEndpoint, ec);
|
mUDPSock.bind(UdpListenEndpoint, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
beammp_error("bind() failed: " + ec.message());
|
beammp_error("bind() failed: " + ec.message());
|
||||||
@@ -117,25 +96,15 @@ 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(UdpListenEndpoint.port()) + (" with a Max of ")
|
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
|
||||||
+ std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + (" Clients"));
|
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
|
||||||
while (!Application::IsShuttingDown()) {
|
while (!Application::IsShuttingDown()) {
|
||||||
try {
|
try {
|
||||||
ip::udp::endpoint remote_client_ep {};
|
ip::udp::endpoint client {};
|
||||||
std::vector<uint8_t> Data = UDPRcvFromClient(remote_client_ep);
|
std::vector<uint8_t> Data = UDPRcvFromClient(client); // Receives any data from Socket
|
||||||
if (Data.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Data.size() == 1 && Data.at(0) == 'P') {
|
|
||||||
mUDPSock.send_to(const_buffer("P", 1), remote_client_ep, {}, ec);
|
|
||||||
// ignore errors
|
|
||||||
(void)ec;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
auto Pos = std::find(Data.begin(), Data.end(), ':');
|
||||||
if (Pos > Data.begin() + 2) {
|
if (Data.empty() || Pos > Data.begin() + 2)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||||
std::shared_ptr<TClient> Client;
|
std::shared_ptr<TClient> Client;
|
||||||
@@ -148,38 +117,16 @@ void TNetwork::UDPServerMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Client->GetID() == ID) {
|
if (Client->GetID() == ID) {
|
||||||
if (Client->GetUDPAddr() == ip::udp::endpoint {} && !Client->IsUDPConnected() && !Client->GetMagic().empty()) {
|
Client->SetUDPAddr(client);
|
||||||
if (Data.size() != 66) {
|
Client->SetIsConnected(true);
|
||||||
beammp_debugf("Invalid size for UDP value. IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
|
Data.erase(Data.begin(), Data.begin() + 2);
|
||||||
return false;
|
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this);
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector Magic(Data.begin() + 2, Data.end());
|
|
||||||
|
|
||||||
if (Magic != Client->GetMagic()) {
|
|
||||||
beammp_debugf("Invalid value for UDP IP: {} ID: {}", remote_client_ep.address().to_string(), ID);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Client->SetMagic({});
|
|
||||||
Client->SetUDPAddr(remote_client_ep);
|
|
||||||
Client->SetIsUDPConnected(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Client->GetUDPAddr() == remote_client_ep) {
|
|
||||||
Data.erase(Data.begin(), Data.begin() + 2);
|
|
||||||
mServer.GlobalParser(ClientPtr, std::move(Data), mPPSMonitor, *this, true);
|
|
||||||
} else {
|
|
||||||
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 true;
|
return true;
|
||||||
});
|
});
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_warnf("Failed to receive/parse packet via UDP: {}", e.what());
|
beammp_error(("fatal: ") + std::string(e.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,32 +134,14 @@ void TNetwork::UDPServerMain() {
|
|||||||
void TNetwork::TCPServerMain() {
|
void TNetwork::TCPServerMain() {
|
||||||
RegisterThread("TCPServer");
|
RegisterThread("TCPServer");
|
||||||
|
|
||||||
boost::system::error_code ec;
|
ip::tcp::endpoint ListenEp(ip::address::from_string("0.0.0.0"), Application::Settings.Port);
|
||||||
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());
|
ip::tcp::socket Listener(mServer.IoCtx());
|
||||||
|
boost::system::error_code ec;
|
||||||
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());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// set IP_V6ONLY to false to allow both v4 and v6
|
|
||||||
boost::asio::ip::v6_only option(false);
|
|
||||||
Listener.set_option(option, ec);
|
|
||||||
if (ec) {
|
|
||||||
beammp_warnf("Failed to unset IP_V6ONLY on TCP, only IPv6 will work: {}", ec.message());
|
|
||||||
}
|
|
||||||
#if defined(BEAMMP_FREEBSD)
|
|
||||||
beammp_warnf("WARNING: On FreeBSD, for IPv4 to work, you must run `sysctl net.inet6.ip6.v6only=0`!");
|
|
||||||
beammp_debugf("This is due to an annoying detail in the *BSDs: In the name of security, unsetting the IPV6_V6ONLY option does not work by default (but does not fail???), as it allows IPv4 mapped IPv6 like ::ffff:127.0.0.1, which they deem a security issue. For more information, see RFC 2553, section 3.7.");
|
|
||||||
#endif
|
|
||||||
socket_base::linger LingerOpt {};
|
socket_base::linger LingerOpt {};
|
||||||
LingerOpt.enabled(false);
|
LingerOpt.enabled(false);
|
||||||
Listener.set_option(LingerOpt, ec);
|
Listener.set_option(LingerOpt, ec);
|
||||||
@@ -231,7 +160,6 @@ 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 {
|
||||||
@@ -242,14 +170,13 @@ void TNetwork::TCPServerMain() {
|
|||||||
ip::tcp::endpoint ClientEp;
|
ip::tcp::endpoint ClientEp;
|
||||||
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: {}", 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();
|
ID.detach(); // TODO: Add to a queue and attempt to join periodically
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_errorf("Exception in accept routine: {}", e.what());
|
beammp_error("fatal: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
} while (!Application::IsShuttingDown());
|
} while (!Application::IsShuttingDown());
|
||||||
}
|
}
|
||||||
@@ -270,33 +197,29 @@ void TNetwork::Identify(TConnection&& RawConnection) {
|
|||||||
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::shared_ptr<TClient> Client { nullptr };
|
std::string client_address = RawConnection.SockAddr.address().to_string();
|
||||||
|
std::shared_ptr<TClient> client { nullptr };
|
||||||
|
RateLimiter ddos_protection;
|
||||||
try {
|
try {
|
||||||
if (Code == 'C') {
|
if (Code == 'C') {
|
||||||
Client = Authentication(std::move(RawConnection));
|
if (ddos_protection.isConnectionAllowed(client_address)) {
|
||||||
|
beammp_infof("[DoS Protection] Client: [{}] is authorized to connect to the server", client_address);
|
||||||
|
client = Authentication(std::move(RawConnection));
|
||||||
|
} else {
|
||||||
|
beammp_infof("[DoS Protection] Client: [{}] has been denied access to the server", client_address);
|
||||||
|
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||||
|
}
|
||||||
} else if (Code == 'D') {
|
} else if (Code == 'D') {
|
||||||
beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored");
|
HandleDownload(std::move(RawConnection));
|
||||||
return;
|
|
||||||
} else if (Code == 'P') {
|
} else if (Code == 'P') {
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
write(RawConnection.Socket, buffer("P"), ec);
|
write(RawConnection.Socket, buffer("P"), ec);
|
||||||
return;
|
return;
|
||||||
} else if (Code == 'I') {
|
|
||||||
const std::string Data = Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? THeartbeatThread::lastCall : "";
|
|
||||||
|
|
||||||
const auto Size = static_cast<int32_t>(Data.size());
|
|
||||||
std::vector<uint8_t> ToSend;
|
|
||||||
ToSend.resize(Data.size() + sizeof(Size));
|
|
||||||
std::memcpy(ToSend.data(), &Size, sizeof(Size));
|
|
||||||
std::memcpy(ToSend.data() + sizeof(Size), Data.data(), Data.size());
|
|
||||||
|
|
||||||
boost::system::error_code ec;
|
|
||||||
write(RawConnection.Socket, buffer(ToSend), ec);
|
|
||||||
} else {
|
} else {
|
||||||
beammp_errorf("Invalid code got in Identify: '{}'", Code);
|
beammp_errorf("Invalid code got in Identify: '{}'", Code);
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what());
|
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
@@ -309,7 +232,27 @@ void TNetwork::Identify(TConnection&& RawConnection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TNetwork::HandleDownload(TConnection&& Conn) {
|
||||||
|
char D;
|
||||||
|
boost::system::error_code ec;
|
||||||
|
read(Conn.Socket, buffer(&D, 1), ec);
|
||||||
|
if (ec) {
|
||||||
|
Conn.Socket.shutdown(socket_base::shutdown_both, ec);
|
||||||
|
// ignore ec
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto ID = uint8_t(D);
|
||||||
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
|
ReadLock Lock(mServer.GetClientMutex());
|
||||||
|
if (!ClientPtr.expired()) {
|
||||||
|
auto c = ClientPtr.lock();
|
||||||
|
if (c->GetID() == ID) {
|
||||||
|
c->SetDownSock(std::move(Conn.Socket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::string HashPassword(const std::string& str) {
|
std::string HashPassword(const std::string& str) {
|
||||||
std::stringstream ret;
|
std::stringstream ret;
|
||||||
@@ -322,19 +265,8 @@ std::string HashPassword(const std::string& str) {
|
|||||||
|
|
||||||
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
||||||
auto Client = CreateClient(std::move(RawConnection.Socket));
|
auto Client = CreateClient(std::move(RawConnection.Socket));
|
||||||
std::string ip = "";
|
Client->SetIdentifier("ip", RawConnection.SockAddr.address().to_string());
|
||||||
if (RawConnection.SockAddr.address().to_v6().is_v4_mapped()) {
|
beammp_tracef("This thread is ip {}", RawConnection.SockAddr.address().to_string());
|
||||||
ip = boost::asio::ip::make_address_v4(ip::v4_mapped_t::v4_mapped, RawConnection.SockAddr.address().to_v6()).to_string();
|
|
||||||
} else {
|
|
||||||
ip = RawConnection.SockAddr.address().to_string();
|
|
||||||
}
|
|
||||||
Client->SetIdentifier("ip", ip);
|
|
||||||
beammp_tracef("This thread is ip {} ({})", ip, RawConnection.SockAddr.address().to_v6().is_v4_mapped() ? "IPv4 mapped IPv6" : "IPv6");
|
|
||||||
|
|
||||||
if (Application::GetSubsystemStatuses().at("Main") == Application::Status::Starting) {
|
|
||||||
ClientKick(*Client, "The server is still starting, please try joining again later.");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
beammp_info("Identifying new ClientConnection...");
|
beammp_info("Identifying new ClientConnection...");
|
||||||
|
|
||||||
@@ -344,11 +276,10 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
if (Data.size() > 3 && std::equal(Data.begin(), Data.begin() + VC.size(), VC.begin(), VC.end())) {
|
if (Data.size() > 3 && std::equal(Data.begin(), Data.begin() + VC.size(), VC.begin(), VC.end())) {
|
||||||
std::string ClientVersionStr(reinterpret_cast<const char*>(Data.data() + 2), Data.size() - 2);
|
std::string ClientVersionStr(reinterpret_cast<const char*>(Data.data() + 2), Data.size() - 2);
|
||||||
Version ClientVersion = Application::VersionStrToInts(ClientVersionStr + ".0");
|
Version ClientVersion = Application::VersionStrToInts(ClientVersionStr + ".0");
|
||||||
Version MinClientVersion = Application::ClientMinimumVersion();
|
if (ClientVersion.major != Application::ClientMajorVersion()) {
|
||||||
if (Application::IsOutdated(ClientVersion, MinClientVersion)) {
|
beammp_errorf("Client tried to connect with version '{}', but only versions '{}.x.x' is allowed",
|
||||||
beammp_errorf("Client tried to connect with version '{}', but only versions >= {} are allowed",
|
ClientVersion.AsString(), Application::ClientMajorVersion());
|
||||||
ClientVersion.AsString(), MinClientVersion.AsString());
|
ClientKick(*Client, "Outdated Version!");
|
||||||
ClientKick(*Client, fmt::format("Outdated version, launcher version >={} required to join!", MinClientVersion.AsString()));
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -368,7 +299,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size());
|
std::string Key(reinterpret_cast<const char*>(Data.data()), Data.size());
|
||||||
std::string AuthKey = Application::Settings.getAsString(Settings::Key::General_AuthKey);
|
std::string AuthKey = Application::Settings.Key;
|
||||||
std::string ClientIp = Client->GetIdentifiers().at("ip");
|
std::string ClientIp = Client->GetIdentifiers().at("ip");
|
||||||
|
|
||||||
nlohmann::json AuthReq {};
|
nlohmann::json AuthReq {};
|
||||||
@@ -383,7 +314,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() + Target, AuthReq.dump(), "application/json", &ResponseCode);
|
AuthResStr = Http::POST(Application::GetBackendUrlForAuth(), 443, 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());
|
||||||
@@ -391,8 +322,6 @@ 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);
|
||||||
|
|
||||||
@@ -419,6 +348,22 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Application::Settings.Password.empty()) { // ask password
|
||||||
|
if (!TCPSend(*Client, StringToVector("S"))) {
|
||||||
|
// TODO: handle
|
||||||
|
}
|
||||||
|
beammp_info("Waiting for password");
|
||||||
|
Data = TCPRcv(*Client);
|
||||||
|
std::string Pass = std::string(reinterpret_cast<const char*>(Data.data()), Data.size());
|
||||||
|
if (Pass != HashPassword(Application::Settings.Password)) {
|
||||||
|
beammp_debug(Client->GetName() + " attempted to connect with a wrong password");
|
||||||
|
ClientKick(*Client, "Wrong password!");
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
beammp_debug(Client->GetName() + " used the correct password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
||||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
std::shared_ptr<TClient> Cl;
|
std::shared_ptr<TClient> Cl;
|
||||||
@@ -439,21 +384,10 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
|
|
||||||
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
||||||
TLuaEngine::WaitForAll(Futures);
|
TLuaEngine::WaitForAll(Futures);
|
||||||
bool NotAllowed = false;
|
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
|
||||||
bool BypassLimit = false;
|
[](const std::shared_ptr<TLuaResult>& Result) {
|
||||||
|
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
|
||||||
for (const auto& Result : Futures) {
|
});
|
||||||
if (!Result->Error && Result->Result.is<int>()) {
|
|
||||||
auto Res = Result->Result.as<int>();
|
|
||||||
|
|
||||||
if (Res == 1) {
|
|
||||||
NotAllowed = true;
|
|
||||||
break;
|
|
||||||
} else if (Res == 2) {
|
|
||||||
BypassLimit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string Reason;
|
std::string Reason;
|
||||||
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
|
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
|
||||||
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
|
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
|
||||||
@@ -464,30 +398,25 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!NotAllowedWithReason && !Application::Settings.getAsBool(Settings::Key::General_AllowGuests) && Client->IsGuest()) { //! NotAllowedWithReason because this message has the lowest priority
|
if (!NotAllowedWithReason && !Application::Settings.AllowGuests && Client->IsGuest()) { //! NotAllowedWithReason because this message has the lowest priority
|
||||||
NotAllowedWithReason = true;
|
NotAllowedWithReason = true;
|
||||||
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.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NotAllowed && !NotAllowedWithReason && mServer.ClientCount() >= size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) && !BypassLimit) {
|
if (NotAllowed) {
|
||||||
NotAllowedWithReason = true;
|
|
||||||
Reason = "Server full!";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NotAllowedWithReason) {
|
|
||||||
ClientKick(*Client, Reason);
|
|
||||||
} else if (NotAllowed) {
|
|
||||||
ClientKick(*Client, "you are not allowed on the server!");
|
ClientKick(*Client, "you are not allowed on the server!");
|
||||||
|
return {};
|
||||||
|
} else if (NotAllowedWithReason) {
|
||||||
|
ClientKick(*Client, Reason);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postPlayerAuth", "", NotAllowed || NotAllowedWithReason, Reason, Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
|
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
|
||||||
// the post event is not cancellable so we dont wait for it
|
|
||||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
|
||||||
|
|
||||||
if (!NotAllowed && !NotAllowedWithReason) {
|
|
||||||
beammp_info("Identification success");
|
beammp_info("Identification success");
|
||||||
mServer.InsertClient(Client);
|
mServer.InsertClient(Client);
|
||||||
TCPClient(Client);
|
TCPClient(Client);
|
||||||
|
} else {
|
||||||
|
ClientKick(*Client, "Server full!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Client;
|
return Client;
|
||||||
@@ -584,17 +513,7 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
|
|||||||
constexpr std::string_view ABG = "ABG:";
|
constexpr std::string_view ABG = "ABG:";
|
||||||
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
|
||||||
Data.erase(Data.begin(), Data.begin() + ABG.size());
|
Data.erase(Data.begin(), Data.begin() + ABG.size());
|
||||||
try {
|
return DeComp(Data);
|
||||||
return DeComp(Data);
|
|
||||||
} catch (const InvalidDataError& ) {
|
|
||||||
beammp_errorf("Failed to decompress packet from a client. The receive failed and the client may be disconnected as a result");
|
|
||||||
// return empty -> error
|
|
||||||
return std::vector<uint8_t>();
|
|
||||||
} catch (const std::runtime_error& e) {
|
|
||||||
beammp_errorf("Failed to decompress packet from a client: {}. The server may be out of RAM! The receive failed and the client may be disconnected as a result", e.what());
|
|
||||||
// return empty -> error
|
|
||||||
return std::vector<uint8_t>();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return Data;
|
return Data;
|
||||||
}
|
}
|
||||||
@@ -670,7 +589,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, false);
|
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (QueueSync.joinable())
|
if (QueueSync.joinable())
|
||||||
@@ -685,7 +604,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::UpdatePlayer(TClient& Client) {
|
void TNetwork::UpdatePlayer(TClient& Client) {
|
||||||
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) + ":";
|
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.MaxPlayers) + ":";
|
||||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||||
ReadLock Lock(mServer.GetClientMutex());
|
ReadLock Lock(mServer.GetClientMutex());
|
||||||
if (!ClientPtr.expired()) {
|
if (!ClientPtr.expired()) {
|
||||||
@@ -717,7 +636,6 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr) {
|
|||||||
VehicleData = *LockedData.VehicleData;
|
VehicleData = *LockedData.VehicleData;
|
||||||
} // End Vehicle Data Lock Scope
|
} // End Vehicle Data Lock Scope
|
||||||
for (auto& v : VehicleData) {
|
for (auto& v : VehicleData) {
|
||||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), v.ID()));
|
|
||||||
Packet = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(v.ID());
|
Packet = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(v.ID());
|
||||||
SendToAll(&c, StringToVector(Packet), false, true);
|
SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
}
|
}
|
||||||
@@ -761,19 +679,7 @@ 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);
|
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.MapName), true); // Send the Map on connect
|
||||||
int ret = RAND_bytes(buf.data(), buf.size());
|
|
||||||
if (ret != 1) {
|
|
||||||
unsigned long error = ERR_get_error();
|
|
||||||
beammp_errorf("RAND_bytes failed with error code {}", error);
|
|
||||||
beammp_assert(ret != 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LockedClient->SetMagic(buf);
|
|
||||||
buf.insert(buf.begin(), 'U');
|
|
||||||
(void)Respond(*LockedClient, buf, true);
|
|
||||||
(void)Respond(*LockedClient, StringToVector("M" + Application::Settings.getAsString(Settings::Key::General_Map)), true); // Send the Map on connect
|
|
||||||
beammp_info(LockedClient->GetName() + " : Connected");
|
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()));
|
||||||
}
|
}
|
||||||
@@ -808,11 +714,11 @@ 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.GetMods().dump();
|
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||||
beammp_debugf("Mod Info: {}", ToSend);
|
if (ToSend.empty())
|
||||||
|
ToSend = "-";
|
||||||
if (!TCPSend(c, StringToVector(ToSend))) {
|
if (!TCPSend(c, StringToVector(ToSend))) {
|
||||||
ClientKick(c, "TCP Send 'SY' failed");
|
// TODO: error
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -822,6 +728,8 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
||||||
|
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
|
||||||
|
|
||||||
if (!fs::path(UnsafeName).has_filename()) {
|
if (!fs::path(UnsafeName).has_filename()) {
|
||||||
if (!TCPSend(c, StringToVector("CO"))) {
|
if (!TCPSend(c, StringToVector("CO"))) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
@@ -830,16 +738,7 @@ 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();
|
||||||
|
FileName = Application::Settings.Resource + "/Client/" + FileName;
|
||||||
for (auto mod : mResourceManager.GetMods()) {
|
|
||||||
if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) {
|
|
||||||
beammp_warn("Client tried to access protected file " + UnsafeName);
|
|
||||||
c.Disconnect("Mod is protected thus cannot be downloaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;
|
|
||||||
|
|
||||||
if (!std::filesystem::exists(FileName)) {
|
if (!std::filesystem::exists(FileName)) {
|
||||||
if (!TCPSend(c, StringToVector("CO"))) {
|
if (!TCPSend(c, StringToVector("CO"))) {
|
||||||
@@ -853,44 +752,89 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
|
|||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Size = size_t(std::filesystem::file_size(FileName));
|
/// Wait for connections
|
||||||
|
int T = 0;
|
||||||
|
while (!c.GetDownSock().is_open() && T < 50) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
T++;
|
||||||
|
}
|
||||||
|
|
||||||
SendFileToClient(c, Size, FileName);
|
if (!c.GetDownSock().is_open()) {
|
||||||
}
|
beammp_error("Client doesn't have a download socket!");
|
||||||
|
if (!c.IsDisconnected())
|
||||||
#if defined(BEAMMP_LINUX)
|
c.Disconnect("Missing download socket");
|
||||||
#include <cerrno>
|
|
||||||
#include <cstring>
|
|
||||||
#include <sys/sendfile.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#endif
|
|
||||||
void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) {
|
|
||||||
TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID()));
|
|
||||||
#if defined(BEAMMP_LINUX)
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
// on linux, we can use sendfile(2)!
|
|
||||||
int fd = ::open(Name.c_str(), O_RDONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
beammp_errorf("Failed to open mod '{}' for sending, error: {}", Name, std::strerror(errno));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// native handle, needed in order to make native syscalls with it
|
|
||||||
int socket = c.GetTCPSock().native_handle();
|
|
||||||
|
|
||||||
ssize_t ret = 0;
|
size_t Size = size_t(std::filesystem::file_size(FileName)), MSize = Size / 2;
|
||||||
auto ToSendTotal = Size;
|
|
||||||
auto Start = 0;
|
std::thread SplitThreads[2] {
|
||||||
while (ret < ssize_t(ToSendTotal)) {
|
std::thread([&] {
|
||||||
auto SysOffset = off_t(Start + size_t(ret));
|
RegisterThread("SplitLoad_0");
|
||||||
ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
|
SplitLoad(c, 0, MSize, false, FileName);
|
||||||
if (ret < 0) {
|
}),
|
||||||
beammp_errorf("Failed to send mod '{}' to client {}: {}", Name, c.GetID(), std::strerror(errno));
|
std::thread([&] {
|
||||||
return;
|
RegisterThread("SplitLoad_1");
|
||||||
|
SplitLoad(c, MSize, Size, true, FileName);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& SplitThread : SplitThreads) {
|
||||||
|
if (SplitThread.joinable()) {
|
||||||
|
SplitThread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
static std::pair<size_t /* count */, size_t /* last chunk */> SplitIntoChunks(size_t FullSize, size_t ChunkSize) {
|
||||||
|
if (FullSize < ChunkSize) {
|
||||||
|
return { 0, FullSize };
|
||||||
|
}
|
||||||
|
size_t Count = FullSize / (FullSize / ChunkSize);
|
||||||
|
size_t LastChunkSize = FullSize - (Count * ChunkSize);
|
||||||
|
return { Count, LastChunkSize };
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SplitIntoChunks") {
|
||||||
|
size_t FullSize;
|
||||||
|
size_t ChunkSize;
|
||||||
|
SUBCASE("Normal case") {
|
||||||
|
FullSize = 1234567;
|
||||||
|
ChunkSize = 1234;
|
||||||
|
}
|
||||||
|
SUBCASE("Zero original size") {
|
||||||
|
FullSize = 0;
|
||||||
|
ChunkSize = 100;
|
||||||
|
}
|
||||||
|
SUBCASE("Equal full size and chunk size") {
|
||||||
|
FullSize = 125;
|
||||||
|
ChunkSize = 125;
|
||||||
|
}
|
||||||
|
SUBCASE("Even split") {
|
||||||
|
FullSize = 10000;
|
||||||
|
ChunkSize = 100;
|
||||||
|
}
|
||||||
|
SUBCASE("Odd split") {
|
||||||
|
FullSize = 13;
|
||||||
|
ChunkSize = 2;
|
||||||
|
}
|
||||||
|
SUBCASE("Large sizes") {
|
||||||
|
FullSize = 10 * GB;
|
||||||
|
ChunkSize = 125 * MB;
|
||||||
|
}
|
||||||
|
auto [Count, LastSize] = SplitIntoChunks(FullSize, ChunkSize);
|
||||||
|
CHECK((Count * ChunkSize) + LastSize == FullSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size) {
|
||||||
|
if (TCPSendRaw(c, Socket, DataPtr, Size)) {
|
||||||
|
return DataPtr + Size;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) {
|
||||||
std::ifstream f(Name.c_str(), std::ios::binary);
|
std::ifstream f(Name.c_str(), std::ios::binary);
|
||||||
uint32_t Split = 125 * MB;
|
uint32_t Split = 125 * MB;
|
||||||
std::vector<uint8_t> Data;
|
std::vector<uint8_t> Data;
|
||||||
@@ -898,8 +842,11 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
|
|||||||
Data.resize(Split);
|
Data.resize(Split);
|
||||||
else
|
else
|
||||||
Data.resize(Size);
|
Data.resize(Size);
|
||||||
ip::tcp::socket* TCPSock = &c.GetTCPSock();
|
ip::tcp::socket* TCPSock { nullptr };
|
||||||
std::streamsize Sent = 0;
|
if (D)
|
||||||
|
TCPSock = &c.GetDownSock();
|
||||||
|
else
|
||||||
|
TCPSock = &c.GetTCPSock();
|
||||||
while (!c.IsDisconnected() && Sent < Size) {
|
while (!c.IsDisconnected() && Sent < Size) {
|
||||||
size_t Diff = Size - Sent;
|
size_t Diff = Size - Sent;
|
||||||
if (Diff > Split) {
|
if (Diff > Split) {
|
||||||
@@ -922,7 +869,6 @@ void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name
|
|||||||
Sent += Diff;
|
Sent += Diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
|
bool TNetwork::TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size) {
|
||||||
@@ -945,7 +891,7 @@ bool TNetwork::SendLarge(TClient& c, std::vector<uint8_t> Data, bool isSync) {
|
|||||||
|
|
||||||
bool TNetwork::Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync) {
|
bool TNetwork::Respond(TClient& c, const std::vector<uint8_t>& MSG, bool Rel, bool isSync) {
|
||||||
char C = MSG.at(0);
|
char C = MSG.at(0);
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(MSG.size()) > 1024) {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||||
if (C == 'O' || C == 'T' || MSG.size() > 1000) {
|
if (C == 'O' || C == 'T' || MSG.size() > 1000) {
|
||||||
return SendLarge(c, MSG, isSync);
|
return SendLarge(c, MSG, isSync);
|
||||||
} else {
|
} else {
|
||||||
@@ -996,7 +942,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
|||||||
res = false;
|
res = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
res = Respond(*LockedClient, StringToVector(v.DataAsPacket(client->GetRoles(), client->GetName(), client->GetID())), true, true);
|
res = Respond(*LockedClient, StringToVector(v.Data()), true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +974,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
|||||||
}
|
}
|
||||||
if (Self || Client.get() != c) {
|
if (Self || Client.get() != c) {
|
||||||
if (Client->IsSynced() || Client->IsSyncing()) {
|
if (Client->IsSynced() || Client->IsSyncing()) {
|
||||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || compressBound(Data.size()) > 1024) {
|
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||||
if (C == 'O' || C == 'T' || Data.size() > 1000) {
|
if (C == 'O' || C == 'T' || Data.size() > 1000) {
|
||||||
if (Data.size() > 400) {
|
if (Data.size() > 400) {
|
||||||
auto CompressedData = Data;
|
auto CompressedData = Data;
|
||||||
@@ -1056,7 +1002,7 @@ void TNetwork::SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
|
bool TNetwork::UDPSend(TClient& Client, std::vector<uint8_t> Data) {
|
||||||
if (!Client.IsUDPConnected() || Client.IsDisconnected()) {
|
if (!Client.IsConnected() || Client.IsDisconnected()) {
|
||||||
// this can happen if we try to send a packet to a client that is either
|
// this can happen if we try to send a packet to a client that is either
|
||||||
// 1. not yet fully connected, or
|
// 1. not yet fully connected, or
|
||||||
// 2. disconnected and not yet fully removed
|
// 2. disconnected and not yet fully removed
|
||||||
|
|||||||
+2
-2
@@ -65,13 +65,13 @@ void TPPSMonitor::operator()() {
|
|||||||
V += c->GetCarCount();
|
V += c->GetCarCount();
|
||||||
}
|
}
|
||||||
// kick on "no ping"
|
// kick on "no ping"
|
||||||
if (c->SecondsSinceLastPing() > (20 * 60)) {
|
if (c->SecondsSinceLastPing() > (20 * 60) ){
|
||||||
beammp_debugf("client {} ({}) timing out: {}", c->GetID(), c->GetName(), c->SecondsSinceLastPing());
|
beammp_debugf("client {} ({}) timing out: {}", c->GetID(), c->GetName(), c->SecondsSinceLastPing());
|
||||||
TimedOutClients.push_back(c);
|
TimedOutClients.push_back(c);
|
||||||
} else if (c->IsSynced() && c->SecondsSinceLastPing() > (1 * 60)) {
|
} else if (c->IsSynced() && c->SecondsSinceLastPing() > (1 * 60)) {
|
||||||
beammp_debugf("client {} ({}) timing out: {}", c->GetName(), c->GetID(), c->SecondsSinceLastPing());
|
beammp_debugf("client {} ({}) timing out: {}", c->GetName(), c->GetID(), c->SecondsSinceLastPing());
|
||||||
TimedOutClients.push_back(c);
|
TimedOutClients.push_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
+14
-19
@@ -57,26 +57,21 @@ 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)) {
|
||||||
if (LowerString(fs::path(Pair.first).extension().string()) == ".lua") {
|
beammp_infof("File \"{}\" changed, reloading", Pair.first);
|
||||||
beammp_infof("File \"{}\" changed, reloading", Pair.first);
|
// is in root folder, so reload
|
||||||
// is in root folder, so reload
|
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
|
||||||
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
|
auto Size = std::filesystem::file_size(Pair.first);
|
||||||
auto Size = std::filesystem::file_size(Pair.first);
|
auto Contents = std::make_shared<std::string>();
|
||||||
auto Contents = std::make_shared<std::string>();
|
Contents->resize(Size);
|
||||||
Contents->resize(Size);
|
FileStream.read(Contents->data(), Contents->size());
|
||||||
FileStream.read(Contents->data(), Contents->size());
|
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
|
||||||
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
|
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
|
||||||
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
|
auto Res = mEngine->EnqueueScript(StateID, Chunk);
|
||||||
auto Res = mEngine->EnqueueScript(StateID, Chunk);
|
Res->WaitUntilReady();
|
||||||
Res->WaitUntilReady();
|
if (Res->Error) {
|
||||||
if (Res->Error) {
|
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
|
||||||
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 {
|
||||||
beammp_debugf("File \"{}\" changed, not reloading because it's not a lua file. Triggering 'onFileChanged' event instead", Pair.first);
|
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
|
||||||
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
|
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-188
@@ -17,20 +17,15 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include "TResourceManager.h"
|
#include "TResourceManager.h"
|
||||||
#include "Common.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <ios>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
TResourceManager::TResourceManager() {
|
TResourceManager::TResourceManager() {
|
||||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
|
||||||
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
|
std::string Path = Application::Settings.Resource + "/Client";
|
||||||
if (!fs::exists(Path))
|
if (!fs::exists(Path))
|
||||||
fs::create_directories(Path);
|
fs::create_directories(Path);
|
||||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||||
@@ -57,185 +52,3 @@ TResourceManager::TResourceManager() {
|
|||||||
|
|
||||||
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
|
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TResourceManager::RefreshFiles() {
|
|
||||||
mMods.clear();
|
|
||||||
std::unique_lock Lock(mModsMutex);
|
|
||||||
|
|
||||||
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
|
|
||||||
|
|
||||||
nlohmann::json modsDB;
|
|
||||||
|
|
||||||
if (std::filesystem::exists(Path + "/mods.json")) {
|
|
||||||
try {
|
|
||||||
std::ifstream stream(Path + "/mods.json");
|
|
||||||
|
|
||||||
stream >> modsDB;
|
|
||||||
|
|
||||||
stream.close();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
beammp_errorf("Failed to load mods.json: {}", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
|
||||||
std::string File(entry.path().string());
|
|
||||||
|
|
||||||
if (entry.path().filename().string() == "mods.json") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
|
|
||||||
beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modsDB.contains(entry.path().filename().string())) {
|
|
||||||
auto& dbEntry = modsDB[entry.path().filename().string()];
|
|
||||||
if (entry.last_write_time().time_since_epoch().count() > dbEntry["lastwrite"] || std::filesystem::file_size(File) != dbEntry["filesize"].get<size_t>()) {
|
|
||||||
beammp_infof("File '{}' has been modified, rehashing", File);
|
|
||||||
} else {
|
|
||||||
dbEntry["exists"] = true;
|
|
||||||
|
|
||||||
mMods.push_back(nlohmann::json {
|
|
||||||
{ "file_name", std::filesystem::path(File).filename() },
|
|
||||||
{ "file_size", std::filesystem::file_size(File) },
|
|
||||||
{ "hash_algorithm", "sha256" },
|
|
||||||
{ "hash", dbEntry["hash"] },
|
|
||||||
{ "protected", dbEntry["protected"] } });
|
|
||||||
|
|
||||||
beammp_debugf("Mod '{}' loaded from cache", File);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
EVP_MD_CTX* mdctx;
|
|
||||||
const EVP_MD* md;
|
|
||||||
uint8_t sha256_value[EVP_MAX_MD_SIZE];
|
|
||||||
md = EVP_sha256();
|
|
||||||
if (md == nullptr) {
|
|
||||||
throw std::runtime_error("EVP_sha256() failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
mdctx = EVP_MD_CTX_new();
|
|
||||||
if (mdctx == nullptr) {
|
|
||||||
throw std::runtime_error("EVP_MD_CTX_new() failed");
|
|
||||||
}
|
|
||||||
if (!EVP_DigestInit_ex2(mdctx, md, NULL)) {
|
|
||||||
EVP_MD_CTX_free(mdctx);
|
|
||||||
throw std::runtime_error("EVP_DigestInit_ex2() failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream stream(File, std::ios::binary);
|
|
||||||
|
|
||||||
const size_t FileSize = std::filesystem::file_size(File);
|
|
||||||
size_t Read = 0;
|
|
||||||
std::vector<char> Data;
|
|
||||||
while (Read < FileSize) {
|
|
||||||
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
|
|
||||||
size_t RealDataSize = Data.size();
|
|
||||||
stream.read(Data.data(), std::streamsize(Data.size()));
|
|
||||||
if (stream.eof() || stream.fail()) {
|
|
||||||
RealDataSize = size_t(stream.gcount());
|
|
||||||
}
|
|
||||||
Data.resize(RealDataSize);
|
|
||||||
if (RealDataSize == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
|
|
||||||
EVP_MD_CTX_free(mdctx);
|
|
||||||
throw std::runtime_error("EVP_DigestUpdate() failed");
|
|
||||||
}
|
|
||||||
Read += RealDataSize;
|
|
||||||
}
|
|
||||||
unsigned int sha256_len = 0;
|
|
||||||
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
|
|
||||||
EVP_MD_CTX_free(mdctx);
|
|
||||||
throw std::runtime_error("EVP_DigestFinal_ex() failed");
|
|
||||||
}
|
|
||||||
EVP_MD_CTX_free(mdctx);
|
|
||||||
|
|
||||||
stream.close();
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
for (size_t i = 0; i < sha256_len; i++) {
|
|
||||||
result += fmt::format("{:02x}", sha256_value[i]);
|
|
||||||
}
|
|
||||||
beammp_debugf("sha256('{}'): {}", File, result);
|
|
||||||
mMods.push_back(nlohmann::json {
|
|
||||||
{ "file_name", std::filesystem::path(File).filename() },
|
|
||||||
{ "file_size", std::filesystem::file_size(File) },
|
|
||||||
{ "hash_algorithm", "sha256" },
|
|
||||||
{ "hash", result },
|
|
||||||
{ "protected", false } });
|
|
||||||
|
|
||||||
modsDB[std::filesystem::path(File).filename().string()] = {
|
|
||||||
{ "lastwrite", entry.last_write_time().time_since_epoch().count() },
|
|
||||||
{ "hash", result },
|
|
||||||
{ "filesize", std::filesystem::file_size(File) },
|
|
||||||
{ "protected", false },
|
|
||||||
{ "exists", true }
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto it = modsDB.begin(); it != modsDB.end();) {
|
|
||||||
if (!it.value().contains("exists")) {
|
|
||||||
it = modsDB.erase(it);
|
|
||||||
} else {
|
|
||||||
it.value().erase("exists");
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
std::ofstream stream(Path + "/mods.json");
|
|
||||||
|
|
||||||
stream << modsDB.dump(4);
|
|
||||||
|
|
||||||
stream.close();
|
|
||||||
} catch (std::exception& e) {
|
|
||||||
beammp_error("Failed to update mod DB: " + std::string(e.what()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TResourceManager::SetProtected(const std::string& ModName, bool Protected) {
|
|
||||||
std::unique_lock Lock(mModsMutex);
|
|
||||||
|
|
||||||
for (auto& mod : mMods) {
|
|
||||||
if (mod["file_name"].get<std::string>() == ModName) {
|
|
||||||
mod["protected"] = Protected;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto modsDBPath = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/mods.json";
|
|
||||||
|
|
||||||
if (std::filesystem::exists(modsDBPath)) {
|
|
||||||
try {
|
|
||||||
nlohmann::json modsDB;
|
|
||||||
|
|
||||||
std::fstream stream(modsDBPath);
|
|
||||||
|
|
||||||
stream >> modsDB;
|
|
||||||
|
|
||||||
if (modsDB.contains(ModName)) {
|
|
||||||
modsDB[ModName]["protected"] = Protected;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.clear();
|
|
||||||
stream.seekp(0, std::ios::beg);
|
|
||||||
|
|
||||||
stream << modsDB.dump(4);
|
|
||||||
|
|
||||||
stream.close();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
beammp_errorf("Failed to update mods.json: {}", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+58
-146
@@ -20,7 +20,6 @@
|
|||||||
#include "Client.h"
|
#include "Client.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "CustomAssert.h"
|
#include "CustomAssert.h"
|
||||||
#include "TLuaEngine.h"
|
|
||||||
#include "TNetwork.h"
|
#include "TNetwork.h"
|
||||||
#include "TPPSMonitor.h"
|
#include "TPPSMonitor.h"
|
||||||
#include <TLuaPlugin.h>
|
#include <TLuaPlugin.h>
|
||||||
@@ -124,6 +123,17 @@ TEST_CASE("GetPidVid") {
|
|||||||
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
TServer::TServer(const std::vector<std::string_view>& Arguments) {
|
||||||
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
beammp_info("BeamMP Server v" + Application::ServerVersionString());
|
||||||
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
Application::SetSubsystemStatus("Server", Application::Status::Starting);
|
||||||
|
if (Arguments.size() > 1) {
|
||||||
|
Application::Settings.CustomIP = Arguments[0];
|
||||||
|
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
|
||||||
|
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
|
||||||
|
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
|
||||||
|
Application::Settings.CustomIP.clear();
|
||||||
|
beammp_warn("IP Specified is invalid! Ignoring");
|
||||||
|
} else {
|
||||||
|
beammp_info("server started with custom IP");
|
||||||
|
}
|
||||||
|
}
|
||||||
Application::SetSubsystemStatus("Server", Application::Status::Good);
|
Application::SetSubsystemStatus("Server", Application::Status::Good);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +148,7 @@ void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
|
|||||||
beammp_assert(LockedClientPtr != nullptr);
|
beammp_assert(LockedClientPtr != nullptr);
|
||||||
TClient& Client = *LockedClientPtr;
|
TClient& Client = *LockedClientPtr;
|
||||||
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
||||||
|
// TODO: Send delete packets for all cars
|
||||||
Client.ClearCars();
|
Client.ClearCars();
|
||||||
WriteLock Lock(mClientsMutex);
|
WriteLock Lock(mClientsMutex);
|
||||||
mClients.erase(WeakClientPtr.lock());
|
mClients.erase(WeakClientPtr.lock());
|
||||||
@@ -161,23 +172,11 @@ 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, bool udp) {
|
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uint8_t>&& Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
|
||||||
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());
|
||||||
try {
|
Packet = DeComp(Packet);
|
||||||
Packet = DeComp(Packet);
|
|
||||||
} catch (const InvalidDataError& ) {
|
|
||||||
auto LockedClient = Client.lock();
|
|
||||||
beammp_errorf("Failed to decompress packet from client {}. The client sent invalid data and will now be disconnected.", LockedClient->GetID());
|
|
||||||
Network.ClientKick(*LockedClient, "Sent invalid compressed packet (this is likely a bug on your end)");
|
|
||||||
return;
|
|
||||||
} catch (const std::runtime_error& e) {
|
|
||||||
auto LockedClient = Client.lock();
|
|
||||||
beammp_errorf("Failed to decompress packet from client {}: {}. The server might be out of RAM! The client will now be disconnected.", LockedClient->GetID(), e.what());
|
|
||||||
Network.ClientKick(*LockedClient, "Decompression failed (likely a server-side problem)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Packet.empty()) {
|
if (Packet.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -195,29 +194,12 @@ 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
|
||||||
}
|
}
|
||||||
@@ -231,20 +213,12 @@ 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());
|
||||||
@@ -257,57 +231,33 @@ 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));
|
||||||
bool Rejected = std::any_of(Futures.begin(), Futures.end(),
|
if (std::any_of(Futures.begin(), Futures.end(),
|
||||||
[](const std::shared_ptr<TLuaResult>& Elem) {
|
[](const std::shared_ptr<TLuaResult>& Elem) {
|
||||||
return !Elem->Error
|
return !Elem->Error
|
||||||
&& Elem->Result.is<int>()
|
&& Elem->Result.is<int>()
|
||||||
&& bool(Elem->Result.as<int>());
|
&& bool(Elem->Result.as<int>());
|
||||||
});
|
})) {
|
||||||
if (!Rejected) {
|
break;
|
||||||
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
|
||||||
Network.SendToAll(nullptr, StringToVector(SanitizedPacket), true, true);
|
|
||||||
}
|
}
|
||||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postChatMessage", "", !Rejected, LockedClient->GetID(), LockedClient->GetName(), Message);
|
std::string SanitizedPacket = fmt::format("C:{}: {}", LockedClient->GetName(), Message);
|
||||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
Network.SendToAll(nullptr, StringToVector(SanitizedPacket), true, true);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -326,15 +276,6 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +297,7 @@ bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
|
|||||||
c.SetUnicycleID(ID);
|
c.SetUnicycleID(ID);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return c.GetCarCount() < Application::Settings.getAsInt(Settings::Key::General_MaxCars);
|
return c.GetCarCount() < Application::Settings.MaxCars;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,27 +325,19 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
if (!Network.Respond(c, StringToVector(Packet), true)) {
|
if (!Network.Respond(c, StringToVector(Packet), true)) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
||||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), CarID));
|
|
||||||
if (!Network.Respond(c, StringToVector(Destroy), true)) {
|
if (!Network.Respond(c, StringToVector(Destroy), true)) {
|
||||||
// TODO: handle
|
// TODO: handle
|
||||||
}
|
}
|
||||||
beammp_debugf("{} (force : car limit/lua) removed ID {}", c.GetName(), CarID);
|
beammp_debugf("{} (force : car limit/lua) removed ID {}", c.GetName(), CarID);
|
||||||
SpawnConfirmed = false;
|
|
||||||
}
|
}
|
||||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postVehicleSpawn", "", SpawnConfirmed, c.GetID(), CarID, Packet.substr(3));
|
|
||||||
// the post event is not cancellable so we dont wait for it
|
|
||||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'c': {
|
case 'c': {
|
||||||
@@ -423,26 +356,18 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
|
|
||||||
auto FoundPos = Packet.find('{');
|
auto FoundPos = Packet.find('{');
|
||||||
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
|
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
|
||||||
bool Allowed = false;
|
|
||||||
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
|
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
|
||||||
&& !ShouldntAllow) {
|
&& !ShouldntAllow) {
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
Apply(c, VID, Packet);
|
Apply(c, VID, Packet);
|
||||||
Allowed = true;
|
|
||||||
} else {
|
} else {
|
||||||
if (c.GetUnicycleID() == VID) {
|
if (c.GetUnicycleID() == VID) {
|
||||||
c.SetUnicycleID(-1);
|
c.SetUnicycleID(-1);
|
||||||
}
|
}
|
||||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
||||||
Network.SendToAll(nullptr, StringToVector(Destroy), true, true);
|
Network.SendToAll(nullptr, StringToVector(Destroy), true, true);
|
||||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), VID));
|
|
||||||
c.DeleteCar(VID);
|
c.DeleteCar(VID);
|
||||||
Allowed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PostFutures = LuaAPI::MP::Engine->TriggerEvent("postVehicleEdited", "", Allowed, c.GetID(), VID, Packet.substr(3));
|
|
||||||
// the post event is not cancellable so we dont wait for it
|
|
||||||
LuaAPI::MP::Engine->ReportErrors(PostFutures);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -478,46 +403,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 't': {
|
case 't':
|
||||||
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||||
auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1)));
|
|
||||||
if (MaybePidVid) {
|
|
||||||
std::tie(PID, VID) = MaybePidVid.value();
|
|
||||||
}
|
|
||||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'm': {
|
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
||||||
return;
|
return;
|
||||||
}
|
case 'm':
|
||||||
case 'p': {
|
Network.SendToAll(&c, StringToVector(Packet), true, true);
|
||||||
beammp_trace(std::string(("got 'Op' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
|
||||||
auto MaybePidVid = GetPidVid(Data.substr(0, Data.find(':', 1)));
|
|
||||||
if (MaybePidVid) {
|
|
||||||
std::tie(PID, VID) = MaybePidVid.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
|
||||||
Data = Data.substr(Data.find('['));
|
|
||||||
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
|
|
||||||
Network.SendToAll(&c, StringToVector(Packet), false, true);
|
|
||||||
|
|
||||||
auto CarData = c.GetCarData(VID);
|
|
||||||
if (CarData == nlohmann::detail::value_t::null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (CarData.contains("vcf") && CarData.at("vcf").is_object())
|
|
||||||
if (CarData.at("vcf").contains("paints") && CarData.at("vcf").at("paints").is_array()) {
|
|
||||||
CarData.at("vcf")["paints"] = nlohmann::json::parse(Data);
|
|
||||||
c.SetCarData(VID, CarData);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
||||||
return;
|
return;
|
||||||
@@ -530,22 +422,42 @@ 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);
|
||||||
nlohmann::json VD = c.GetCarData(VID);
|
std::string VD = c.GetCarData(VID);
|
||||||
if (VD == nlohmann::detail::value_t::null) {
|
if (VD.empty()) {
|
||||||
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('{'));
|
||||||
|
|
||||||
nlohmann::json Pack = nlohmann::json::parse(Packet, nullptr, false);
|
FoundPos = VD.find('{');
|
||||||
|
if (FoundPos == std::string::npos) {
|
||||||
if (Pack.is_discarded()) {
|
return;
|
||||||
|
}
|
||||||
|
VD = VD.substr(FoundPos);
|
||||||
|
rapidjson::Document Veh, Pack;
|
||||||
|
Veh.Parse(VD.c_str());
|
||||||
|
if (Veh.HasParseError()) {
|
||||||
|
beammp_error("Could not get vehicle config!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pack.Parse(Packet.c_str());
|
||||||
|
if (Pack.HasParseError() || Pack.IsNull()) {
|
||||||
beammp_error("Could not get active vehicle config!");
|
beammp_error("Could not get active vehicle config!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetCarData(VID, Pack);
|
for (auto& M : Pack.GetObject()) {
|
||||||
|
if (Veh[M.name].IsNull()) {
|
||||||
|
Veh.AddMember(M.name, M.value, Veh.GetAllocator());
|
||||||
|
} else {
|
||||||
|
Veh[M.name] = Pack[M.name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rapidjson::StringBuffer Buffer;
|
||||||
|
rapidjson::Writer<rapidjson::StringBuffer> writer(Buffer);
|
||||||
|
Veh.Accept(writer);
|
||||||
|
c.SetCarData(VID, Header + Buffer.GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
||||||
|
|||||||
+1
-5
@@ -21,7 +21,7 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
TVehicleData::TVehicleData(int ID, nlohmann::json Data)
|
TVehicleData::TVehicleData(int ID, std::string 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,7 +30,3 @@ TVehicleData::TVehicleData(int ID, nlohmann::json 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();
|
|
||||||
}
|
|
||||||
+17
-23
@@ -20,7 +20,6 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Http.h"
|
#include "Http.h"
|
||||||
#include "LuaAPI.h"
|
#include "LuaAPI.h"
|
||||||
#include "Settings.h"
|
|
||||||
#include "SignalHandling.h"
|
#include "SignalHandling.h"
|
||||||
#include "TConfig.h"
|
#include "TConfig.h"
|
||||||
#include "THeartbeatThread.h"
|
#include "THeartbeatThread.h"
|
||||||
@@ -36,19 +35,19 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
static const std::string sCommandlineArguments = R"(
|
static const std::string sCommandlineArguments = R"(
|
||||||
USAGE:
|
USAGE:
|
||||||
BeamMP-Server [arguments]
|
BeamMP-Server [arguments]
|
||||||
|
|
||||||
ARGUMENTS:
|
ARGUMENTS:
|
||||||
--help
|
--help
|
||||||
Displays this help and exits.
|
Displays this help and exits.
|
||||||
--port=1234
|
--port=1234
|
||||||
Sets the server's listening TCP and
|
Sets the server's listening TCP and
|
||||||
UDP port. Overrides ENV and ServerConfig.
|
UDP port. Overrides ENV and ServerConfig.
|
||||||
--config=/path/to/ServerConfig.toml
|
--config=/path/to/ServerConfig.toml
|
||||||
Absolute or relative path to the
|
Absolute or relative path to the
|
||||||
Server Config file, including the
|
Server Config file, including the
|
||||||
filename. For paths and filenames with
|
filename. For paths and filenames with
|
||||||
spaces, put quotes around the path.
|
spaces, put quotes around the path.
|
||||||
--working-directory=/path/to/folder
|
--working-directory=/path/to/folder
|
||||||
Sets the working directory of the Server.
|
Sets the working directory of the Server.
|
||||||
@@ -59,7 +58,7 @@ ARGUMENTS:
|
|||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
BeamMP-Server --config=../MyWestCoastServerConfig.toml
|
BeamMP-Server --config=../MyWestCoastServerConfig.toml
|
||||||
Runs the BeamMP-Server and uses the server config file
|
Runs the BeamMP-Server and uses the server config file
|
||||||
which is one directory above it and is named
|
which is one directory above it and is named
|
||||||
'MyWestCoastServerConfig.toml'.
|
'MyWestCoastServerConfig.toml'.
|
||||||
)";
|
)";
|
||||||
@@ -151,7 +150,7 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
beammp_errorf("Custom port requested via --port is invalid: '{}'", Port.value());
|
beammp_errorf("Custom port requested via --port is invalid: '{}'", Port.value());
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
Application::Settings.set(Settings::Key::General_Port, P);
|
Application::Settings.Port = P;
|
||||||
beammp_info("Custom port requested via commandline arguments: " + Port.value());
|
beammp_info("Custom port requested via commandline arguments: " + Port.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,9 +165,6 @@ int BeamMPServerMain(MainArguments Arguments) {
|
|||||||
|
|
||||||
SetupSignalHandlers();
|
SetupSignalHandlers();
|
||||||
|
|
||||||
Settings settings {};
|
|
||||||
beammp_infof("Server name set in new impl: {}", settings.getAsString(Settings::Key::General_Name));
|
|
||||||
|
|
||||||
bool Shutdown = false;
|
bool Shutdown = false;
|
||||||
Application::RegisterShutdownHandler([&Shutdown] {
|
Application::RegisterShutdownHandler([&Shutdown] {
|
||||||
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
|
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
|
||||||
@@ -182,24 +178,28 @@ 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");
|
||||||
TResourceManager ResourceManager;
|
TResourceManager ResourceManager;
|
||||||
ResourceManager.RefreshFiles();
|
|
||||||
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.Resource) / "Server", LuaEngine);
|
||||||
|
|
||||||
|
if (Application::Settings.HTTPServerEnabled) {
|
||||||
|
Http::Server::THttpServerInstance HttpServerInstance {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::SetSubsystemStatus("Main", Application::Status::Good);
|
||||||
RegisterThread("Main(Waiting)");
|
RegisterThread("Main(Waiting)");
|
||||||
|
|
||||||
std::set<std::string> IgnoreSubsystems {
|
std::set<std::string> IgnoreSubsystems {
|
||||||
@@ -214,10 +214,6 @@ 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
|
||||||
}
|
}
|
||||||
@@ -231,8 +227,6 @@ 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: 5bf0c55239...6978381401
+2
-10
@@ -14,14 +14,6 @@
|
|||||||
"openssl",
|
"openssl",
|
||||||
"rapidjson",
|
"rapidjson",
|
||||||
"sol2",
|
"sol2",
|
||||||
"curl",
|
"toml11"
|
||||||
"luajit"
|
]
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"name": "sol2",
|
|
||||||
"version": "3.3.1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"builtin-baseline": "5bf0c55239da398b8c6f450818c9e28d36bf9966"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user