mirror of
https://github.com/SantaSpeen/BeamMP-Server.git
synced 2026-02-16 18:10:39 +00:00
Compare commits
239 Commits
v1.20-linu
...
v2.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b500a3da5 | ||
|
|
ade19123b7 | ||
|
|
5ee18e0576 | ||
|
|
77d23caa63 | ||
|
|
79856cde8e | ||
|
|
7bc230974a | ||
|
|
b1ab7e65da | ||
|
|
c82c53e3d1 | ||
|
|
9e861ab993 | ||
|
|
ae11ba5f0d | ||
|
|
1427966d1a | ||
|
|
db92f2867d | ||
|
|
208a41d45f | ||
|
|
f2915f9c2a | ||
|
|
1abf6d5adf | ||
|
|
f626474b4f | ||
|
|
6f3960d2c2 | ||
|
|
6cb19a7991 | ||
|
|
4fe3c50edd | ||
|
|
fe7335fb0e | ||
|
|
40cd203047 | ||
|
|
1fc03500f0 | ||
|
|
73729746ff | ||
|
|
ffdf4dce60 | ||
|
|
bb5d7fdcf4 | ||
|
|
2df2475b59 | ||
|
|
bb32b9cfea | ||
|
|
3f034264f9 | ||
|
|
c67fffed58 | ||
|
|
f50b821733 | ||
|
|
1e5d19bca4 | ||
|
|
1011de1971 | ||
|
|
7336d63f58 | ||
|
|
c5d7369088 | ||
|
|
c3a463552f | ||
|
|
ae9462898e | ||
|
|
529b7e2ae4 | ||
|
|
1bee72a175 | ||
|
|
f1e1b6cc28 | ||
|
|
573bd77bbd | ||
|
|
770e03e3c6 | ||
|
|
11d4522dc7 | ||
|
|
830bc47987 | ||
|
|
bec698fbdc | ||
|
|
60cc835daf | ||
|
|
a85ce18589 | ||
|
|
1228c2fabe | ||
|
|
3da0af37e4 | ||
|
|
baa41dd65a | ||
|
|
534b457f48 | ||
|
|
029cf94e68 | ||
|
|
15f7a6ba85 | ||
|
|
86b5d91579 | ||
|
|
31486bcb56 | ||
|
|
08660d83dc | ||
|
|
6d8f75a577 | ||
|
|
018246cea5 | ||
|
|
a584e25bf3 | ||
|
|
d4d773b769 | ||
|
|
56a02f0215 | ||
|
|
7231c69e10 | ||
|
|
3c68dfaeaf | ||
|
|
69709968fd | ||
|
|
5b9effad85 | ||
|
|
808ab94c68 | ||
|
|
e7ae71513c | ||
|
|
b825e5685b | ||
|
|
e47821416a | ||
|
|
50549f3d1a | ||
|
|
f323d50e34 | ||
|
|
24994d7dde | ||
|
|
77337204e5 | ||
|
|
dad1acbb91 | ||
|
|
8b755e6b7b | ||
|
|
7ccc5a963a | ||
|
|
ca24339c9a | ||
|
|
fc201efa4b | ||
|
|
533c8c80e1 | ||
|
|
78fb81004e | ||
|
|
942b7baa74 | ||
|
|
aa72b2507e | ||
|
|
9860240e24 | ||
|
|
cda8168c58 | ||
|
|
667bd7f7c8 | ||
|
|
b524aa67de | ||
|
|
e35d1b5457 | ||
|
|
1875c8832d | ||
|
|
61726ea3ab | ||
|
|
e4d6c86919 | ||
|
|
eaa6b5322f | ||
|
|
2c06a98e00 | ||
|
|
e73d578797 | ||
|
|
704e25636d | ||
|
|
c7cf0a733e | ||
|
|
24a34d7a97 | ||
|
|
1c98921127 | ||
|
|
2d898f8665 | ||
|
|
229914ac74 | ||
|
|
3258b04dad | ||
|
|
603663ed2c | ||
|
|
44f2fdfa7c | ||
|
|
19abb5b68c | ||
|
|
dd5b0bdd6d | ||
|
|
9c9f503e5c | ||
|
|
b4850f09a9 | ||
|
|
3094d382ff | ||
|
|
940a39ed4e | ||
|
|
23b39bb6c7 | ||
|
|
2cd6e21f8c | ||
|
|
3c6aa741ef | ||
|
|
e79318e49a | ||
|
|
e8938968f2 | ||
|
|
e6f79a8dd1 | ||
|
|
347e72ce90 | ||
|
|
a90c67f47d | ||
|
|
956b2ef5b5 | ||
|
|
31d68249a8 | ||
|
|
6d3e053c71 | ||
|
|
87a1564c0a | ||
|
|
e6109c98bd | ||
|
|
c372e63bd1 | ||
|
|
709ac1dd58 | ||
|
|
6ead6bbb1e | ||
|
|
50fb023eff | ||
|
|
08d043da8d | ||
|
|
c623bdea91 | ||
|
|
b953bae5da | ||
|
|
e039eeaab8 | ||
|
|
15cad02e13 | ||
|
|
67f22c7d76 | ||
|
|
632a5f2801 | ||
|
|
44fa68e6da | ||
|
|
33e0cac4c1 | ||
|
|
a7f1e93ca2 | ||
|
|
f8a9d01749 | ||
|
|
356354d1eb | ||
|
|
40cae31885 | ||
|
|
05c5fb047c | ||
|
|
83145d7466 | ||
|
|
3c48ac6145 | ||
|
|
3fe8d48ada | ||
|
|
f0abfcc0ef | ||
|
|
7410e31230 | ||
|
|
3bc8744b63 | ||
|
|
c076c83edc | ||
|
|
776ddcbbef | ||
|
|
9a74434bbb | ||
|
|
3e2cb3176a | ||
|
|
7cd420a1a5 | ||
|
|
4edd1ac100 | ||
|
|
8e4006fc38 | ||
|
|
ab44ac8c15 | ||
|
|
266303b09d | ||
|
|
b777781c96 | ||
|
|
714d31fb45 | ||
|
|
d481fcd3a7 | ||
|
|
df3269756c | ||
|
|
aadcd1abe5 | ||
|
|
57fc0ea74d | ||
|
|
fe4a1b28b5 | ||
|
|
30916c41c3 | ||
|
|
fab20276ff | ||
|
|
7e6d5ce359 | ||
|
|
6aed93fbf1 | ||
|
|
9b1bf071a8 | ||
|
|
f52308c439 | ||
|
|
0580ad67fd | ||
|
|
218504e674 | ||
|
|
f13523fbe5 | ||
|
|
b81ac35b37 | ||
|
|
8664522d1d | ||
|
|
c15046f8b1 | ||
|
|
c7f8b2b131 | ||
|
|
84252e892e | ||
|
|
32038046d5 | ||
|
|
e04a569e33 | ||
|
|
13f8be5d39 | ||
|
|
bca4b3f140 | ||
|
|
b3256062f7 | ||
|
|
51dbfe0482 | ||
|
|
92a67c7305 | ||
|
|
cc5a878692 | ||
|
|
5759a6f80f | ||
|
|
3dd2e1c278 | ||
|
|
4826fb5fc2 | ||
|
|
8cd35d64e4 | ||
|
|
aec6ad9c14 | ||
|
|
d360403c56 | ||
|
|
bf74b1ae32 | ||
|
|
f19a012509 | ||
|
|
72607583bf | ||
|
|
ef5db013b3 | ||
|
|
4cda6e8bc3 | ||
|
|
459814a6ec | ||
|
|
e5e447c7af | ||
|
|
6a2ce7faab | ||
|
|
ce8661159b | ||
|
|
f5ecc251e7 | ||
|
|
45b3057ed5 | ||
|
|
0476ffa990 | ||
|
|
43690d0833 | ||
|
|
f9cb9af078 | ||
|
|
e4979bb6e2 | ||
|
|
5266ac418b | ||
|
|
ffca27ae6a | ||
|
|
cd1aba7afb | ||
|
|
50b3a9ea19 | ||
|
|
d8f49edb6b | ||
|
|
ec0987e5c1 | ||
|
|
e4fa9a23fa | ||
|
|
0fe4913928 | ||
|
|
bd349556f0 | ||
|
|
dc9c3255de | ||
|
|
d5541ae154 | ||
|
|
467e5a64c3 | ||
|
|
3bb9b17d47 | ||
|
|
51f0b504b7 | ||
|
|
2e48cef647 | ||
|
|
0fe7050166 | ||
|
|
a25cc00093 | ||
|
|
e2e23635bc | ||
|
|
a20f632e3e | ||
|
|
5f3fecb92c | ||
|
|
710bb939ad | ||
|
|
00b7d1ca96 | ||
|
|
7e8b86cf57 | ||
|
|
d47e721b38 | ||
|
|
33a7d0e1a1 | ||
|
|
480c78c9f2 | ||
|
|
d0a7b56e75 | ||
|
|
f6121704df | ||
|
|
c75acbff76 | ||
|
|
d394d7b5a6 | ||
|
|
4e1d2a7ddd | ||
|
|
2571cb1478 | ||
|
|
c387cc3610 | ||
|
|
769c19b811 | ||
|
|
b62676daf4 | ||
|
|
cd19cd343a |
41
.github/workflows/cmake-linux.yml
vendored
Normal file
41
.github/workflows/cmake-linux.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: CMake Linux Build
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
linux-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev
|
||||
sudo add-apt-repository ppa:mhier/libboost-latest
|
||||
sudo apt-get install -y libboost1.70-dev libboost1.70
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-linux
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: BeamMP-Server-linux
|
||||
path: ${{github.workspace}}/build-linux/BeamMP-Server
|
||||
45
.github/workflows/cmake-windows.yml
vendored
Normal file
45
.github/workflows/cmake-windows.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: CMake Windows Build
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
windows-build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Restore artifacts, or run vcpkg, build and cache artifacts
|
||||
uses: lukka/run-vcpkg@main
|
||||
id: runvcpkg
|
||||
with:
|
||||
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
||||
vcpkgTriplet: 'x64-windows-static'
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-windows
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: BeamMP-Server.exe
|
||||
path: ${{github.workspace}}/build-windows/Release/BeamMP-Server.exe
|
||||
|
||||
|
||||
114
.github/workflows/release-build.yml
vendored
Normal file
114
.github/workflows/release-build.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Release Create & Build
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
name: Create Release
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: |
|
||||
Files included in this release:
|
||||
- `BeamMP-Server.exe` is the windows build
|
||||
- `BeamMP-Server-linux` is a ubuntu build, so you need the dependencies listed in README.md to run it. For any other distros please build from source as described in README.md.
|
||||
|
||||
upload-release-files-linux:
|
||||
name: Upload Linux Release Files
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev
|
||||
sudo add-apt-repository ppa:mhier/libboost-latest
|
||||
sudo apt-get install -y libboost1.70-dev libboost1.70
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-linux
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-linux
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ${{github.workspace}}/build-linux/BeamMP-Server
|
||||
asset_name: BeamMP-Server-linux
|
||||
asset_content_type: application/x-elf
|
||||
|
||||
upload-release-files-windows:
|
||||
name: Upload Windows Release Files
|
||||
runs-on: windows-latest
|
||||
needs: create-release
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Restore artifacts, or run vcpkg, build and cache artifacts
|
||||
uses: lukka/run-vcpkg@main
|
||||
id: runvcpkg
|
||||
with:
|
||||
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp'
|
||||
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
|
||||
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
|
||||
vcpkgTriplet: 'x64-windows-static'
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build-windows
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build-windows
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ${{github.workspace}}/build-windows/Release/BeamMP-Server.exe
|
||||
asset_name: BeamMP-Server.exe
|
||||
asset_content_type: application/vnd.microsoft.portable-executable
|
||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
.idea/
|
||||
*.toml
|
||||
boost_*
|
||||
Resources
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
@@ -22,16 +24,9 @@ mono_crash.*
|
||||
out/
|
||||
|
||||
#Clion Files
|
||||
cmake-build-debug/CMakeFiles/
|
||||
cmake-build-release/CMakeFiles/
|
||||
cmake-build-debug/Resources/
|
||||
cmake-build-release/Resources/
|
||||
cmake-build-debug/*.*
|
||||
cmake-build-release/*.*
|
||||
cmake-build-debug/Makefile
|
||||
cmake-build-release/Makefile
|
||||
!cmake-build-debug/*.lib
|
||||
!cmake-build-release/*.lib
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
.idea/
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
@@ -51,7 +46,6 @@ bld/
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
@@ -462,3 +456,23 @@ out/build/x86-Debug/.cmake/api/v1/reply/index-2020-01-28T17-35-38-0764.json
|
||||
out/build/x86-Debug/.cmake/api/v1/reply/codemodel-v2-6a61e390ef8eaf17e9f8.json
|
||||
out/build/x86-Debug/Server.cfg
|
||||
*Server.cfg*
|
||||
*.cmake
|
||||
*.make
|
||||
*.xml
|
||||
*.includecache
|
||||
cmake-build-release/include/commandline/Makefile
|
||||
*.lib
|
||||
*.cbp
|
||||
*.marks
|
||||
*.internal
|
||||
*.xml
|
||||
cmake-build-debug/include/commandline/Makefile
|
||||
*.manifest
|
||||
*.rc
|
||||
*.res
|
||||
BeamMP-Server
|
||||
*.patch
|
||||
callgrind.*
|
||||
notes/*
|
||||
compile_commands.json
|
||||
nohup.out
|
||||
|
||||
15
.gitmodules
vendored
Normal file
15
.gitmodules
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
[submodule "include/commandline"]
|
||||
path = include/commandline
|
||||
url = https://github.com/lionkor/commandline
|
||||
[submodule "socket.io-client-cpp"]
|
||||
path = socket.io-client-cpp
|
||||
url = https://github.com/socketio/socket.io-client-cpp
|
||||
[submodule "asio"]
|
||||
path = asio
|
||||
url = https://github.com/chriskohlhoff/asio
|
||||
[submodule "rapidjson"]
|
||||
path = rapidjson
|
||||
url = https://github.com/Tencent/rapidjson
|
||||
[submodule "include/tomlplusplus"]
|
||||
path = include/tomlplusplus
|
||||
url = https://github.com/marzer/tomlplusplus
|
||||
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@@ -1,2 +0,0 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
1
.idea/.name
generated
1
.idea/.name
generated
@@ -1 +0,0 @@
|
||||
Server
|
||||
2
.idea/BeamNG-MP-Server.iml
generated
2
.idea/BeamNG-MP-Server.iml
generated
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,43 +1,68 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(Server)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -g")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin")
|
||||
elseif (WIN32)
|
||||
# This might cause issues with old windows headers, but it's worth the trouble to keep the code
|
||||
# completely cross platform. For fixes to common issues arising from /permissive- visit:
|
||||
# https://docs.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /permissive-")
|
||||
if (WIN32)
|
||||
message(STATUS "MSVC -> forcing use of statically-linked runtime.")
|
||||
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
|
||||
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
|
||||
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
|
||||
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
|
||||
include_directories(${VcpkgRoot}/include)
|
||||
link_directories(${VcpkgRoot}/lib)
|
||||
elseif (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin")
|
||||
if (SANITIZE)
|
||||
message(STATUS "sanitize is ON")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
|
||||
endif (SANITIZE)
|
||||
endif ()
|
||||
|
||||
#find_package(Boost 1.70.0 REQUIRED COMPONENTS system thread)
|
||||
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
|
||||
include_directories("asio/asio/include")
|
||||
include_directories("rapidjson/include")
|
||||
include_directories("websocketpp")
|
||||
add_subdirectory("socket.io-client-cpp")
|
||||
add_subdirectory("include/commandline")
|
||||
|
||||
file(GLOB source_files "src/*.cpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "src/*/*.cpp")
|
||||
add_executable(BeamMP-Server ${source_files})
|
||||
|
||||
target_include_directories(BeamMP-Server PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS system thread)
|
||||
|
||||
add_executable(BeamMP-Server
|
||||
src/main.cpp
|
||||
include/TConsole.h src/TConsole.cpp
|
||||
include/TServer.h src/TServer.cpp
|
||||
include/Compat.h src/Compat.cpp
|
||||
include/Common.h src/Common.cpp
|
||||
include/Client.h src/Client.cpp
|
||||
include/VehicleData.h src/VehicleData.cpp
|
||||
include/TConfig.h src/TConfig.cpp
|
||||
include/TLuaEngine.h src/TLuaEngine.cpp
|
||||
include/TLuaFile.h src/TLuaFile.cpp
|
||||
include/TResourceManager.h src/TResourceManager.cpp
|
||||
include/THeartbeatThread.h src/THeartbeatThread.cpp
|
||||
include/Http.h src/Http.cpp
|
||||
#include/SocketIO.h src/SocketIO.cpp
|
||||
include/TPPSMonitor.h src/TPPSMonitor.cpp
|
||||
include/TNetwork.h src/TNetwork.cpp)
|
||||
|
||||
target_include_directories(BeamMP-Server PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/commandline")
|
||||
|
||||
find_package(Lua REQUIRED)
|
||||
target_include_directories(BeamMP-Server PUBLIC ${LUA_INCLUDE_DIR})
|
||||
target_include_directories(BeamMP-Server PUBLIC ${Boost_INCLUDE_DIRS} ${LUA_INCLUDE_DIR} "socket.io-client-cpp/src" "include/tomlplusplus")
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(BeamMP-Server z pthread stdc++fs ${Boost_LINK_DIRS} ${LUA_LIBRARIES} curl dl)
|
||||
target_link_libraries(BeamMP-Server z pthread stdc++fs ${LUA_LIBRARIES} crypto ${OPENSSL_LIBRARIES} commandline sioclient_tls)
|
||||
elseif (WIN32)
|
||||
include(FindLua)
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(CURL CONFIG REQUIRED)
|
||||
find_package(RapidJSON CONFIG REQUIRED)
|
||||
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS})
|
||||
target_link_libraries(BeamMP-Server PRIVATE ws2_32 CURL::libcurl ZLIB::ZLIB ${LUA_LIBRARIES})
|
||||
#${Boost_LINK_DIRS}
|
||||
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(BeamMP-Server PRIVATE ws2_32 ZLIB::ZLIB ${LUA_LIBRARIES} ${OPENSSL_LIBRARIES} commandline sioclient_tls)
|
||||
endif ()
|
||||
|
||||
3
LICENSE
3
LICENSE
@@ -1 +1,2 @@
|
||||
Copyright (c) 2019-present Anonymous275. BeamMP Server code is not in the public domain and is not free software. One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries, the only permission that has been granted is to use the software in its compiled form as distributed from the BeamMP.com website. Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
Copyright (c) 2019-present Anonymous275 (@Anonymous-275), Lion Kortlepel (@lionkor). BeamMP-Server code is not in the public domain and is not free software. One must be granted explicit permission by the copyright holder(s) in order to modify or distribute any part of the source or binaries. Special permission to modify the source-code is implicitly granted only for the purpose of upstreaming those changes directly to github.com/BeamMP/BeamMP-Server via a GitHub pull-request.
|
||||
Commercial usage is prohibited, unless explicit permission has been granted prior to usage.
|
||||
|
||||
81
README.md
81
README.md
@@ -1,41 +1,94 @@
|
||||
# BeamMP-Server
|
||||
|
||||
The Server is the way we link client to each other and handle authentication, compression, and data management. It also allows lua plugins, that system is always being reviewed and improved with detailed instructions on wiki.beammp.com.
|
||||
[](https://github.com/BeamMP/BeamMP-Server/actions?query=workflow%3A%22CMake+Windows+Build%22)
|
||||
[](https://github.com/BeamMP/BeamMP-Server/actions?query=workflow%3A%22CMake+Linux+Build%22)
|
||||
|
||||
This is the server for the multiplayer mod **[BeamMP](https://beammp.com/)** for the game [BeamNG.drive](https://www.beamng.com/).
|
||||
The server is the point throug which all clients communicate. You can write lua mods for the server, detailed instructions on the [BeamMP Wiki](https://wiki.beammp.com).
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
These values are guesstimated and are subject to change with each release.
|
||||
|
||||
* RAM: 50+ MiB usable (not counting OS overhead)
|
||||
* CPU: Any Hz, preferably multicore
|
||||
* OS: Windows, Linux (theoretically any POSIX)
|
||||
* GPU: None
|
||||
* HDD: 10 MiB + Mods/Plugins
|
||||
* Bandwidth: 5-10 Mb/s upload
|
||||
|
||||
## Contributing
|
||||
|
||||
TLDR; [Issues](https://github.com/BeamMP/BeamMP-Server/issues) with the "help wanted" label or with nobody assigned, any [trello](https://trello.com/b/Kw75j3zZ/beamngdrive-multiplayer) cards in the "To-Do" column.
|
||||
|
||||
To contribute, look at the active [issues](https://github.com/BeamMP/BeamMP-Server/issues) and at the [trello](https://trello.com/b/Kw75j3zZ/beamngdrive-multiplayer). Any issues that have the "help wanted" label or don't have anyone assigned and any trello cards that aren't assigned or in the "In-Progress" section are good tasks to take on. You can either contribute by programming or by testing and adding more info and ideas.
|
||||
|
||||
Fork this repository, make a new branch for your feature, implement your feature or fix, and then create a pull-request here. Even incomplete features and fixes can be pull-requested.
|
||||
|
||||
If you need support with understanding the codebase, please write us in the discord. You'll need to be proficient in modern C++.
|
||||
|
||||
## 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 `v1.20`.
|
||||
|
||||
## Supported Operating Systems
|
||||
|
||||
The code itself supports (latest stable) Linux and Windows. In terms of actual build support, for now we only distribute windows binaries and instructions to build on Debian 10 (stable). For any other distro or OS, you just have to find the same libraries listed in the Linux Build [Prerequisites](#prerequisites) further down the page, and it should build fine. We don't currently support ARM or any big-endian architectures.
|
||||
The code itself supports (latest stable) Linux and Windows. In terms of actual build support, for now we usually only distribute windows binaries and sometimes linux. For any other distro or OS, you just have to find the same libraries listed in the Linux Build [Prerequisites](#prerequisites) further down the page, and it should build fine. We don't currently support any big-endian architectures.
|
||||
|
||||
Recommended compilers: MSVC, GCC.
|
||||
Recommended compilers: MSVC, GCC, CLANG.
|
||||
|
||||
## Linux / POSIX specific build instructions
|
||||
You can find precompiled binaries under [Releases](https://github.com/BeamMP/BeamMP-Server/releases/).
|
||||
|
||||
Currently only linux and windows are supported (generally). On systems to which we do not provide binaries (so anything but windows), you are allowed to compile the program and use it. Other restrictions, such as not being allowed to distribute those binaries, still apply (see [copyright notice](#copyright)).
|
||||
## Build Instructions
|
||||
|
||||
**__Do not compile from `master`. Always build from a release tag, i.e. `tags/v2.0`!__**
|
||||
|
||||
Currently only linux and windows are supported (generally). See [Releases](https://github.com/BeamMP/BeamMP-Server/releases/) for official binary releases. On systems to which we do not provide binaries (so anything but windows), you are allowed to compile the program and use it. Other restrictions, such as not being allowed to distribute those binaries, still apply (see [copyright notice](#copyright)).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
All package names are ones found in debian's (debian 10 stable) repositories, but will exist under similar names in other distros. Feel free to PR your own guide for a different distro.
|
||||
#### Windows
|
||||
|
||||
Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/BeamMP-Server/releases/).
|
||||
|
||||
Dependencies for windows can be installed with `vcpkg`, in which case the current dependencies are the `x64-windows-static` versions of `lua`, `zlib`, `rapidjson`, `boost-beast`, `boost-asio` and `openssl`.
|
||||
|
||||
#### Linux / \*nix
|
||||
|
||||
These package names are in the debian / ubuntu style. Feel free to PR your own guide for a different distro.
|
||||
|
||||
- `git`
|
||||
- `make`
|
||||
- `cmake`
|
||||
- `g++` (must support ISO C++17)
|
||||
- `liblua5.3`
|
||||
- `g++`
|
||||
|
||||
Must support ISO C++17. If your distro's `g++` doesn't support C++17, chances are that it has a `g++-8` or `g++-10` package that does. If this is the case. you just need to run CMake with `-DCMAKE_CXX_COMPILER=g++-10` (replace `g++-10` with your compiler's name).
|
||||
- `liblua5.3`
|
||||
|
||||
Any 5.x version should work, but 5.3 is what we officially use. Any other version might break in the future.
|
||||
You can also use any version of `libluajit`, but the same applies regarding the version.
|
||||
- `libz-dev`
|
||||
- `rapidjson-dev`
|
||||
- `libcurl4-openssl-dev` (or other `libcurl4-*-dev`)
|
||||
- `libopenssl-dev`
|
||||
|
||||
**If** you're building it from source, you'll need `libboost1.70-all-dev` or `libboost1.71-all-dev` or higher as well.
|
||||
|
||||
### How to build
|
||||
|
||||
On windows. use git-bash for these commands.
|
||||
|
||||
1. Make sure you have all [prerequisites](#prerequisites) installed
|
||||
2. Clone the repository in a location of your choice with `git clone --recursive https://github.com/BeamMP/BeamMP-Server`
|
||||
3. `cd` into it with `cd BeamMP-Server`
|
||||
4. Run `cmake .` (with `.`)
|
||||
5. Run `make`
|
||||
6. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server`. Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server.
|
||||
3. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v1.20` for version 1.20.
|
||||
4. `cd` into it with `cd BeamMP-Server`
|
||||
5. Run `cmake .` (with `.`)
|
||||
6. Run `make`
|
||||
7. You will now have a `BeamMP-Server` file in your directory, which is executable with `./BeamMP-Server` (`.\BeamMP-Server.exe` for windows). Follow the (windows or linux, doesnt matter) instructions on the [wiki](https://wiki.beammp.com/en/home/Server_Mod) for further setup after installation (which we just did), such as port-forwarding and getting a key to actually run the server.
|
||||
|
||||
*tip: to run the server in the background, simply (in bash, zsh, etc) run:* `nohup ./BeamMP-Server &`*.*
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (c) 2019-present Anonymous275. BeamMP Server code is not in the public domain and is not free software. One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries, the only permission that has been granted is to use the software in its compiled form as distributed from the BeamMP.com website. Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
Copyright (c) 2019-present Anonymous275 (@Anonymous-275), Lion Kortlepel (@lionkor).
|
||||
BeamMP-Server code is not in the public domain and is not free software. One must be granted explicit permission by the copyright holder(s) in order to modify or distribute any part of the source or binaries. Special permission to modify the source-code is implicitly granted only for the purpose of upstreaming those changes directly to github.com/BeamMP/BeamMP-Server via a GitHub pull-request.
|
||||
Commercial usage is prohibited, unless explicit permission has been granted prior to usage.
|
||||
|
||||
1
asio
Submodule
1
asio
Submodule
Submodule asio added at 230c0d2ae0
94
include/Client.h
Normal file
94
include/Client.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Common.h"
|
||||
#include "Compat.h"
|
||||
#include "VehicleData.h"
|
||||
|
||||
class TServer;
|
||||
|
||||
class TClient final {
|
||||
public:
|
||||
using TSetOfVehicleData = std::vector<TVehicleData>;
|
||||
|
||||
struct TVehicleDataLockPair {
|
||||
TSetOfVehicleData* VehicleData;
|
||||
std::unique_lock<std::mutex> Lock;
|
||||
};
|
||||
|
||||
explicit TClient(TServer& Server);
|
||||
TClient(const TClient&) = delete;
|
||||
TClient& operator=(const TClient&) = delete;
|
||||
|
||||
void AddNewCar(int Ident, const std::string& Data);
|
||||
void SetCarData(int Ident, const std::string& Data);
|
||||
TVehicleDataLockPair GetAllCars();
|
||||
void SetName(const std::string& Name) { mName = Name; }
|
||||
void SetRoles(const std::string& Role) { mRole = Role; }
|
||||
void AddIdentifier(const std::string& ID) { mIdentifiers.insert(ID); };
|
||||
std::string GetCarData(int Ident);
|
||||
void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; }
|
||||
void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; }
|
||||
void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; }
|
||||
void SetStatus(int Status) { mStatus = Status; }
|
||||
// locks
|
||||
void DeleteCar(int Ident);
|
||||
[[nodiscard]] std::set<std::string> GetIdentifiers() const { return mIdentifiers; }
|
||||
[[nodiscard]] sockaddr_in GetUDPAddr() const { return mUDPAddress; }
|
||||
[[nodiscard]] SOCKET GetDownSock() const { return mSocket[1]; }
|
||||
[[nodiscard]] SOCKET GetTCPSock() const { return mSocket[0]; }
|
||||
[[nodiscard]] std::string GetRoles() const { return mRole; }
|
||||
[[nodiscard]] std::string GetName() const { return mName; }
|
||||
void SetUnicycleID(int ID) { mUnicycleID = ID; }
|
||||
void SetID(int ID) { mID = ID; }
|
||||
[[nodiscard]] int GetOpenCarID() const;
|
||||
[[nodiscard]] int GetCarCount() const;
|
||||
void ClearCars();
|
||||
[[nodiscard]] int GetStatus() const { return mStatus; }
|
||||
[[nodiscard]] int GetID() const { return mID; }
|
||||
[[nodiscard]] int GetUnicycleID() const { return mUnicycleID; }
|
||||
[[nodiscard]] bool IsConnected() const { return mIsConnected; }
|
||||
[[nodiscard]] bool IsSynced() const { return mIsSynced; }
|
||||
[[nodiscard]] bool IsSyncing() const { return mIsSyncing; }
|
||||
[[nodiscard]] bool IsGuest() const { return mIsGuest; }
|
||||
void SetIsGuest(bool NewIsGuest) { mIsGuest = NewIsGuest; }
|
||||
void SetIsSynced(bool NewIsSynced) { mIsSynced = NewIsSynced; }
|
||||
void SetIsSyncing(bool NewIsSyncing) { mIsSyncing = NewIsSyncing; }
|
||||
void EnqueuePacket(const std::string& Packet);
|
||||
[[nodiscard]] std::queue<std::string>& MissedPacketQueue() { return mPacketsSync; }
|
||||
[[nodiscard]] const std::queue<std::string>& MissedPacketQueue() const { return mPacketsSync; }
|
||||
[[nodiscard]] size_t MissedPacketQueueSize() const { return mPacketsSync.size(); }
|
||||
[[nodiscard]] std::mutex& MissedPacketQueueMutex() const { return mMissedPacketsMutex; }
|
||||
void SetIsConnected(bool NewIsConnected) { mIsConnected = NewIsConnected; }
|
||||
[[nodiscard]] TServer& Server() const;
|
||||
void UpdatePingTime();
|
||||
int SecondsSinceLastPing();
|
||||
|
||||
private:
|
||||
void InsertVehicle(int ID, const std::string& Data);
|
||||
|
||||
TServer& mServer;
|
||||
bool mIsConnected = false;
|
||||
bool mIsSynced = false;
|
||||
bool mIsSyncing = false;
|
||||
mutable std::mutex mMissedPacketsMutex;
|
||||
std::queue<std::string> mPacketsSync;
|
||||
std::set<std::string> mIdentifiers;
|
||||
bool mIsGuest = false;
|
||||
std::mutex mVehicleDataMutex;
|
||||
TSetOfVehicleData mVehicleData;
|
||||
std::string mName = "Unknown Client";
|
||||
SOCKET mSocket[2] { SOCKET(0), SOCKET(0) };
|
||||
sockaddr_in mUDPAddress {}; // is this initialization OK? yes it is
|
||||
int mUnicycleID = -1;
|
||||
std::string mRole;
|
||||
std::string mDID;
|
||||
int mStatus = 0;
|
||||
int mID = -1;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime;
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/8/2020
|
||||
///
|
||||
|
||||
#pragma once
|
||||
#ifdef WIN32
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#define SOCKET int
|
||||
#endif
|
||||
#include "CustomAssert.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct VData {
|
||||
int ID = -1;
|
||||
std::string Data;
|
||||
};
|
||||
|
||||
class Client {
|
||||
private:
|
||||
std::set<std::unique_ptr<VData>> VehicleData; //ID and Data;
|
||||
std::string Name = "Unknown Client";
|
||||
SOCKET SOCK[2] { SOCKET(-1) };
|
||||
sockaddr_in UDPADDR;
|
||||
std::string Role;
|
||||
std::string DID;
|
||||
int Status = 0;
|
||||
int ID = -1;
|
||||
|
||||
public:
|
||||
bool isConnected = false;
|
||||
bool isSynced = false;
|
||||
bool isGuest = false;
|
||||
|
||||
void AddNewCar(int ident, const std::string& Data);
|
||||
void SetCarData(int ident, const std::string& Data);
|
||||
std::set<std::unique_ptr<VData>>& GetAllCars();
|
||||
void SetName(const std::string& name) { Name = name; }
|
||||
void SetRoles(const std::string& role) { Role = role; }
|
||||
std::string GetCarData(int ident);
|
||||
void SetUDPAddr(sockaddr_in Addr) { UDPADDR = Addr; }
|
||||
void SetDownSock(SOCKET CSock) { SOCK[1] = CSock; }
|
||||
void SetTCPSock(SOCKET CSock) { SOCK[0] = CSock; }
|
||||
void SetStatus(int status) { Status = status; }
|
||||
void DeleteCar(int ident);
|
||||
sockaddr_in GetUDPAddr() { return UDPADDR; }
|
||||
std::string GetRoles() { return Role; }
|
||||
std::string GetName() { return Name; }
|
||||
SOCKET GetDownSock() { return SOCK[1]; }
|
||||
SOCKET GetTCPSock() { return SOCK[0]; }
|
||||
void SetID(int ID) { this->ID = ID; }
|
||||
int GetOpenCarID();
|
||||
int GetCarCount();
|
||||
void ClearCars();
|
||||
int GetStatus() { return Status; }
|
||||
int GetID() { return ID; }
|
||||
};
|
||||
struct ClientInterface {
|
||||
std::set<std::unique_ptr<Client>> Clients;
|
||||
void RemoveClient(Client*& c) {
|
||||
Assert(c);
|
||||
c->ClearCars();
|
||||
auto Iter = std::find_if(Clients.begin(), Clients.end(), [&](auto& ptr) {
|
||||
return c == ptr.get();
|
||||
});
|
||||
Assert(Iter != Clients.end());
|
||||
if (Iter == Clients.end()) {
|
||||
return;
|
||||
}
|
||||
Clients.erase(Iter);
|
||||
c = nullptr;
|
||||
}
|
||||
void AddClient(Client*&& c) {
|
||||
Assert(c);
|
||||
Clients.insert(std::unique_ptr<Client>(c));
|
||||
}
|
||||
int Size() {
|
||||
return int(Clients.size());
|
||||
}
|
||||
};
|
||||
|
||||
extern std::unique_ptr<ClientInterface> CI;
|
||||
123
include/Common.h
Normal file
123
include/Common.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "TConsole.h"
|
||||
|
||||
// static class handling application start, shutdown, etc.
|
||||
// yes, static classes, singletons, globals are all pretty
|
||||
// bad idioms. In this case we need a central way to access
|
||||
// stuff like graceful shutdown, global settings (its in the name),
|
||||
// etc.
|
||||
class Application final {
|
||||
public:
|
||||
// types
|
||||
struct TSettings {
|
||||
TSettings() noexcept
|
||||
: ServerName("BeamMP Server")
|
||||
, ServerDesc("BeamMP Default Description")
|
||||
, Resource("Resources")
|
||||
, MapName("/levels/gridmap_v2/info.json")
|
||||
, MaxPlayers(10)
|
||||
, Private(false)
|
||||
, MaxCars(1)
|
||||
, DebugModeEnabled(false)
|
||||
, Port(30814) { }
|
||||
std::string ServerName;
|
||||
std::string ServerDesc;
|
||||
std::string Resource;
|
||||
std::string MapName;
|
||||
std::string Key;
|
||||
int MaxPlayers;
|
||||
bool Private;
|
||||
int MaxCars;
|
||||
bool DebugModeEnabled;
|
||||
int Port;
|
||||
std::string CustomIP;
|
||||
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
|
||||
};
|
||||
using TShutdownHandler = std::function<void()>;
|
||||
|
||||
// methods
|
||||
Application() = delete;
|
||||
|
||||
// 'Handler' is called when GracefullyShutdown is called
|
||||
static void RegisterShutdownHandler(const TShutdownHandler& Handler);
|
||||
// Causes all threads to finish up and exit gracefull gracefully
|
||||
static void GracefullyShutdown();
|
||||
static TConsole& Console() { return *mConsole; }
|
||||
static std::string ServerVersion() { return "2.1.2"; }
|
||||
static std::string ClientVersion() { return "2.0"; }
|
||||
static std::string PPS() { return mPPS; }
|
||||
static void SetPPS(std::string NewPPS) { mPPS = NewPPS; }
|
||||
|
||||
static inline TSettings Settings {};
|
||||
|
||||
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
|
||||
static std::string GetBackendHostname() { return "backend.beammp.com"; }
|
||||
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
|
||||
|
||||
private:
|
||||
static inline std::string mPPS;
|
||||
static std::unique_ptr<TConsole> mConsole;
|
||||
static inline std::mutex mShutdownHandlersMutex {};
|
||||
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
|
||||
};
|
||||
|
||||
std::string ThreadName();
|
||||
void RegisterThread(const std::string str);
|
||||
#define RegisterThreadAuto() RegisterThread(__func__)
|
||||
|
||||
#define KB 1024
|
||||
#define MB (KB * 1024)
|
||||
|
||||
#define _file_basename std::filesystem::path(__FILE__).filename().string()
|
||||
#define _line std::to_string(__LINE__)
|
||||
#define _in_lambda (std::string(__func__) == "operator()")
|
||||
|
||||
// we would like the full function signature 'void a::foo() const'
|
||||
// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__,
|
||||
// feel free to add more
|
||||
#if defined(WIN32)
|
||||
#define _function_name std::string(__FUNCSIG__)
|
||||
#elif defined(__unix) || defined(__unix__)
|
||||
#define _function_name std::string(__PRETTY_FUNCTION__)
|
||||
#else
|
||||
#define _function_name std::string(__func__)
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
// if this is defined, we will show the full function signature infront of
|
||||
// each info/debug/warn... call instead of the 'filename:line' format.
|
||||
#if defined(BMP_FULL_FUNCTION_NAMES)
|
||||
#define _this_location (ThreadName() + _function_name + " ")
|
||||
#else
|
||||
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
|
||||
#endif
|
||||
|
||||
#else // !defined(DEBUG)
|
||||
|
||||
#define _this_location (ThreadName())
|
||||
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
#define warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
|
||||
#define info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
|
||||
#define error(x) Application::Console().Write(_this_location + std::string("[ERROR] ") + (x))
|
||||
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
|
||||
#define debug(x) \
|
||||
do { \
|
||||
if (Application::Settings.DebugModeEnabled) { \
|
||||
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define Biggest 30000
|
||||
std::string Comp(std::string Data);
|
||||
std::string DeComp(std::string Compressed);
|
||||
|
||||
36
include/Compat.h
Normal file
36
include/Compat.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
// ======================= UNIX ========================
|
||||
|
||||
#ifdef __unix
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
using SOCKET = int;
|
||||
using DWORD = unsigned long;
|
||||
using PDWORD = unsigned long*;
|
||||
using LPDWORD = unsigned long*;
|
||||
char _getch();
|
||||
inline void CloseSocketProper(int socket) {
|
||||
shutdown(socket, SHUT_RDWR);
|
||||
close(socket);
|
||||
}
|
||||
#endif // unix
|
||||
|
||||
// ======================= WIN32 =======================
|
||||
|
||||
#ifdef WIN32
|
||||
#include <conio.h>
|
||||
#include <winsock2.h>
|
||||
inline void CloseSocketProper(SOCKET socket) {
|
||||
shutdown(socket, SD_BOTH);
|
||||
closesocket(socket);
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
// ======================= OTHER =======================
|
||||
|
||||
#if !defined(WIN32) && !defined(__unix)
|
||||
#error "OS not supported"
|
||||
#endif
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/24/2020
|
||||
///
|
||||
#pragma once
|
||||
#include <string>
|
||||
std::string Comp(std::string Data);
|
||||
std::string DeComp(std::string Compressed);
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/18/2020
|
||||
///
|
||||
#pragma once
|
||||
#include <string>
|
||||
std::string HttpRequest(const std::string& IP, int port);
|
||||
std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json);
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Common.h"
|
||||
|
||||
static const char* const ANSI_RESET = "\u001b[0m";
|
||||
|
||||
@@ -41,6 +41,7 @@ static const char* const ANSI_BOLD = "\u001b[1m";
|
||||
static const char* const ANSI_UNDERLINE = "\u001b[4m";
|
||||
|
||||
#if DEBUG
|
||||
#include <iostream>
|
||||
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) {
|
||||
if (!result) {
|
||||
|
||||
9
include/Http.h
Normal file
9
include/Http.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Http {
|
||||
std::string GET(const std::string& host, int port, const std::string& target);
|
||||
std::string POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json);
|
||||
}
|
||||
19
include/IThreaded.h
Normal file
19
include/IThreaded.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
// pure virtual class to be inherited from by classes which intend to be threaded
|
||||
class IThreaded {
|
||||
public:
|
||||
IThreaded()
|
||||
// invokes operator() on this object
|
||||
: mThread() { }
|
||||
|
||||
virtual void Start() final {
|
||||
mThread = std::thread([this] { (*this)(); });
|
||||
}
|
||||
virtual void operator()() = 0;
|
||||
|
||||
protected:
|
||||
std::thread mThread;
|
||||
};
|
||||
@@ -1,12 +1,9 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 11/27/2020
|
||||
///
|
||||
//
|
||||
// Created by anon on 4/21/21.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/writer.h"
|
||||
namespace json = rapidjson;
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 4/2/2020.
|
||||
///
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
void InitLog();
|
||||
#define DebugPrintTID() DebugPrintTIDInternal(__func__, false)
|
||||
void DebugPrintTIDInternal(const std::string& func, bool overwrite = true); // prints the current thread id in debug mode, to make tracing of crashes and asserts easier
|
||||
void ConsoleOut(const std::string& msg);
|
||||
void except(const std::string& toPrint);
|
||||
void debug(const std::string& toPrint);
|
||||
void error(const std::string& toPrint);
|
||||
void info(const std::string& toPrint);
|
||||
void warn(const std::string& toPrint);
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/20/2020
|
||||
///
|
||||
|
||||
#pragma once
|
||||
#include <any>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <lua.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct LuaArg {
|
||||
std::vector<std::any> args;
|
||||
void PushArgs(lua_State* State) {
|
||||
for (std::any arg : args) {
|
||||
if (!arg.has_value())
|
||||
return;
|
||||
std::string Type = arg.type().name();
|
||||
if (Type.find("bool") != std::string::npos) {
|
||||
lua_pushboolean(State, std::any_cast<bool>(arg));
|
||||
}
|
||||
if (Type.find("basic_string") != std::string::npos || Type.find("char") != std::string::npos) {
|
||||
lua_pushstring(State, std::any_cast<std::string>(arg).c_str());
|
||||
}
|
||||
if (Type.find("int") != std::string::npos) {
|
||||
lua_pushinteger(State, std::any_cast<int>(arg));
|
||||
}
|
||||
if (Type.find("float") != std::string::npos) {
|
||||
lua_pushnumber(State, std::any_cast<float>(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Lua {
|
||||
private:
|
||||
std::set<std::pair<std::string, std::string>> _RegisteredEvents;
|
||||
lua_State* luaState { nullptr };
|
||||
fs::file_time_type _LastWrote;
|
||||
std::string _PluginName {};
|
||||
std::string _FileName {};
|
||||
bool _StopThread = false;
|
||||
bool _Console = false;
|
||||
|
||||
public:
|
||||
void Init();
|
||||
void RegisterEvent(const std::string& Event, const std::string& FunctionName);
|
||||
std::string GetRegistered(const std::string& Event) const;
|
||||
void UnRegisterEvent(const std::string& Event);
|
||||
void SetLastWrite(fs::file_time_type time);
|
||||
bool IsRegistered(const std::string& Event);
|
||||
void SetPluginName(const std::string& Name);
|
||||
void Execute(const std::string& Command);
|
||||
void SetFileName(const std::string& Name);
|
||||
fs::file_time_type GetLastWrite();
|
||||
std::string GetPluginName() const;
|
||||
std::string GetFileName() const;
|
||||
lua_State* GetState();
|
||||
const lua_State* GetState() const;
|
||||
std::string GetOrigin();
|
||||
std::mutex Lock;
|
||||
void Reload();
|
||||
Lua(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote, bool Console = false);
|
||||
Lua(bool Console = false);
|
||||
~Lua();
|
||||
void SetStopThread(bool StopThread) { _StopThread = StopThread; }
|
||||
bool GetStopThread() const { return _StopThread; }
|
||||
};
|
||||
std::any CallFunction(Lua* lua, const std::string& FuncName, std::shared_ptr<LuaArg> args);
|
||||
std::any TriggerLuaEvent(const std::string& Event, bool local, Lua* Caller, std::shared_ptr<LuaArg> arg, bool Wait);
|
||||
extern std::set<std::unique_ptr<Lua>> PluginEngine;
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/31/2020
|
||||
///
|
||||
#pragma once
|
||||
#include "Client.hpp"
|
||||
#include <string>
|
||||
void TCPServerMain();
|
||||
void UpdatePlayers();
|
||||
void OnConnect(Client* c);
|
||||
void TCPClient(Client* c);
|
||||
std::string TCPRcv(Client* c);
|
||||
void SyncResources(Client* c);
|
||||
[[noreturn]] void UDPServerMain();
|
||||
void OnDisconnect(Client* c, bool kicked);
|
||||
void UDPSend(Client* c, std::string Data);
|
||||
void SendLarge(Client* c, std::string Data);
|
||||
bool TCPSend(Client* c, const std::string& Data);
|
||||
void GParser(Client* c, const std::string& Packet);
|
||||
std::string StaticReason(bool Set, const std::string& R);
|
||||
void Respond(Client* c, const std::string& MSG, bool Rel);
|
||||
void SendToAll(Client* c, const std::string& Data, bool Self, bool Rel);
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
|
||||
// Use ReadLock(m) and WriteLock(m) to lock it.
|
||||
using RWMutex = std::shared_mutex;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#pragma once
|
||||
#ifdef __linux
|
||||
#define EXCEPTION_POINTERS void
|
||||
#else
|
||||
#include <WS2tcpip.h>
|
||||
#endif
|
||||
#include <string>
|
||||
int Handle(EXCEPTION_POINTERS* ep, char* Origin);
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#pragma once
|
||||
#include <string>
|
||||
extern std::string ServerName;
|
||||
extern std::string ServerDesc;
|
||||
extern std::string StatReport;
|
||||
extern std::string FileSizes;
|
||||
extern std::string Resource;
|
||||
extern std::string FileList;
|
||||
extern std::string CustomIP;
|
||||
extern std::string MapName;
|
||||
extern uint64_t MaxModSize;
|
||||
extern std::string Key;
|
||||
std::string GetSVer();
|
||||
std::string GetCVer();
|
||||
extern int MaxPlayers;
|
||||
extern int ModsLoaded;
|
||||
extern bool Private;
|
||||
extern int MaxCars;
|
||||
extern bool Debug;
|
||||
extern int Port;
|
||||
extern int PPS;
|
||||
69
include/SocketIO.h
Normal file
69
include/SocketIO.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <sio_client.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
|
||||
/*
|
||||
* We send relevant server events over socket.io to the backend.
|
||||
*
|
||||
* We send all events to `backend.beammp.com`, to the room `/key`
|
||||
* where `key` is the currently active auth-key.
|
||||
*/
|
||||
|
||||
enum class SocketIOEvent {
|
||||
ConsoleOut,
|
||||
CPUUsage,
|
||||
MemoryUsage,
|
||||
NetworkUsage,
|
||||
PlayerList,
|
||||
};
|
||||
|
||||
enum class SocketIORoom {
|
||||
None,
|
||||
Stats,
|
||||
Player,
|
||||
Info,
|
||||
Console,
|
||||
};
|
||||
|
||||
class SocketIO final {
|
||||
private:
|
||||
struct Event;
|
||||
|
||||
public:
|
||||
enum class EventType {
|
||||
};
|
||||
|
||||
// Singleton pattern
|
||||
static SocketIO& Get();
|
||||
|
||||
void Emit(SocketIOEvent Event, const std::string& Data);
|
||||
|
||||
~SocketIO();
|
||||
|
||||
void SetAuthenticated(bool auth) { mAuthenticated = auth; }
|
||||
|
||||
private:
|
||||
SocketIO() noexcept;
|
||||
|
||||
void ThreadMain();
|
||||
|
||||
struct Event {
|
||||
std::string Name;
|
||||
std::string Data;
|
||||
};
|
||||
|
||||
bool mAuthenticated { false };
|
||||
sio::client mClient;
|
||||
std::thread mThread;
|
||||
std::atomic_bool mCloseThread { false };
|
||||
std::mutex mQueueMutex;
|
||||
std::deque<Event> mQueue;
|
||||
|
||||
friend std::unique_ptr<SocketIO> std::make_unique<SocketIO>();
|
||||
};
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#pragma once
|
||||
void InitServer(int argc, char* argv[]);
|
||||
void ConsoleInit();
|
||||
void InitConfig();
|
||||
void InitLua();
|
||||
void InitRes();
|
||||
void HBInit();
|
||||
void StatInit();
|
||||
void NetMain();
|
||||
21
include/TConfig.h
Normal file
21
include/TConfig.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class TConfig {
|
||||
public:
|
||||
explicit TConfig();
|
||||
|
||||
bool Failed() const { return mFailed; }
|
||||
|
||||
private:
|
||||
void CreateConfigFile(std::string_view name);
|
||||
void ParseFromFile(std::string_view name);
|
||||
void PrintDebug();
|
||||
|
||||
void ParseOldFormat();
|
||||
|
||||
bool mFailed { false };
|
||||
};
|
||||
19
include/TConsole.h
Normal file
19
include/TConsole.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "commandline/commandline.h"
|
||||
#include "TLuaFile.h"
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
|
||||
class TConsole {
|
||||
public:
|
||||
TConsole();
|
||||
|
||||
void Write(const std::string& str);
|
||||
void WriteRaw(const std::string& str);
|
||||
void InitializeLuaConsole(TLuaEngine& Engine);
|
||||
|
||||
private:
|
||||
std::unique_ptr<TLuaFile> mLuaConsole { nullptr };
|
||||
Commandline mCommandline;
|
||||
};
|
||||
21
include/THeartbeatThread.h
Normal file
21
include/THeartbeatThread.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "IThreaded.h"
|
||||
#include "TResourceManager.h"
|
||||
#include "TServer.h"
|
||||
|
||||
class THeartbeatThread : public IThreaded {
|
||||
public:
|
||||
THeartbeatThread(TResourceManager& ResourceManager, TServer& Server);
|
||||
//~THeartbeatThread();
|
||||
void operator()() override;
|
||||
|
||||
private:
|
||||
std::string GenerateCall();
|
||||
std::string GetPlayers();
|
||||
|
||||
bool mShutdown = false;
|
||||
TResourceManager& mResourceManager;
|
||||
TServer& mServer;
|
||||
};
|
||||
38
include/TLuaEngine.h
Normal file
38
include/TLuaEngine.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "IThreaded.h"
|
||||
#include "TLuaFile.h"
|
||||
#include "TServer.h"
|
||||
#include <lua.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
class TLuaEngine : public IThreaded {
|
||||
public:
|
||||
explicit TLuaEngine(TServer& Server, TNetwork& Network);
|
||||
|
||||
using TSetOfLuaFile = std::set<std::unique_ptr<TLuaFile>>;
|
||||
|
||||
void operator()() override;
|
||||
|
||||
[[nodiscard]] const TSetOfLuaFile& LuaFiles() const { return mLuaFiles; }
|
||||
[[nodiscard]] TServer& Server() { return mServer; }
|
||||
[[nodiscard]] const TServer& Server() const { return mServer; }
|
||||
[[nodiscard]] TNetwork& Network() { return mNetwork; }
|
||||
[[nodiscard]] const TNetwork& Network() const { return mNetwork; }
|
||||
|
||||
std::optional<std::reference_wrapper<TLuaFile>> GetScript(lua_State* L);
|
||||
|
||||
private:
|
||||
void FolderList(const std::string& Path, bool HotSwap);
|
||||
void RegisterFiles(const std::string& Path, bool HotSwap);
|
||||
bool NewFile(const std::string& Path);
|
||||
|
||||
TNetwork& mNetwork;
|
||||
TServer& mServer;
|
||||
std::string mPath;
|
||||
bool mShutdown { false };
|
||||
TSetOfLuaFile mLuaFiles;
|
||||
};
|
||||
61
include/TLuaFile.h
Normal file
61
include/TLuaFile.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef TLUAFILE_H
|
||||
#define TLUAFILE_H
|
||||
|
||||
#include <any>
|
||||
#include <filesystem>
|
||||
#include <lua.hpp>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct TLuaArg {
|
||||
std::vector<std::any> args;
|
||||
void PushArgs(lua_State* State);
|
||||
};
|
||||
|
||||
class TLuaEngine;
|
||||
|
||||
class TLuaFile {
|
||||
public:
|
||||
void RegisterEvent(const std::string& Event, const std::string& FunctionName);
|
||||
void UnRegisterEvent(const std::string& Event);
|
||||
void SetLastWrite(fs::file_time_type time);
|
||||
bool IsRegistered(const std::string& Event);
|
||||
void SetPluginName(const std::string& Name);
|
||||
void Execute(const std::string& Command);
|
||||
void SetFileName(const std::string& Name);
|
||||
fs::file_time_type GetLastWrite();
|
||||
lua_State* GetState();
|
||||
std::string GetOrigin();
|
||||
std::mutex Lock;
|
||||
void Reload();
|
||||
void Init(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote);
|
||||
explicit TLuaFile(TLuaEngine& Engine, bool Console = false);
|
||||
~TLuaFile();
|
||||
void SetStopThread(bool StopThread) { mStopThread = StopThread; }
|
||||
TLuaEngine& Engine() { return mEngine; }
|
||||
[[nodiscard]] std::string GetPluginName() const;
|
||||
[[nodiscard]] std::string GetFileName() const;
|
||||
[[nodiscard]] const lua_State* GetState() const;
|
||||
[[nodiscard]] bool GetStopThread() const { return mStopThread; }
|
||||
[[nodiscard]] const TLuaEngine& Engine() const { return mEngine; }
|
||||
[[nodiscard]] std::string GetRegistered(const std::string& Event) const;
|
||||
|
||||
private:
|
||||
TLuaEngine& mEngine;
|
||||
std::set<std::pair<std::string, std::string>> mRegisteredEvents;
|
||||
lua_State* mLuaState { nullptr };
|
||||
fs::file_time_type mLastWrote;
|
||||
std::string mPluginName {};
|
||||
std::string mFileName {};
|
||||
bool mStopThread = false;
|
||||
bool mConsole = false;
|
||||
void Load();
|
||||
};
|
||||
|
||||
std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
|
||||
|
||||
#endif // TLUAFILE_H
|
||||
49
include/TNetwork.h
Normal file
49
include/TNetwork.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "Compat.h"
|
||||
#include "TResourceManager.h"
|
||||
#include "TServer.h"
|
||||
|
||||
class TNetwork {
|
||||
public:
|
||||
TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager);
|
||||
|
||||
[[nodiscard]] bool TCPSend(TClient& c, const std::string& Data, bool IsSync = false);
|
||||
[[nodiscard]] bool SendLarge(TClient& c, std::string Data, bool isSync = false);
|
||||
[[nodiscard]] bool Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync = false);
|
||||
std::shared_ptr<TClient> CreateClient(SOCKET TCPSock);
|
||||
std::string TCPRcv(TClient& c);
|
||||
void ClientKick(TClient& c, const std::string& R);
|
||||
[[nodiscard]] bool SyncClient(const std::weak_ptr<TClient>& c);
|
||||
void Identify(SOCKET TCPSock);
|
||||
void Authentication(SOCKET TCPSock);
|
||||
[[nodiscard]] bool CheckBytes(TClient& c, int32_t BytesRcv);
|
||||
void SyncResources(TClient& c);
|
||||
[[nodiscard]] bool UDPSend(TClient& Client, std::string Data) const;
|
||||
void SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel);
|
||||
void UpdatePlayer(TClient& Client);
|
||||
|
||||
private:
|
||||
void UDPServerMain();
|
||||
void TCPServerMain();
|
||||
|
||||
TServer& mServer;
|
||||
TPPSMonitor& mPPSMonitor;
|
||||
SOCKET mUDPSock {};
|
||||
bool mShutdown { false };
|
||||
TResourceManager& mResourceManager;
|
||||
std::thread mUDPThread;
|
||||
std::thread mTCPThread;
|
||||
|
||||
std::string UDPRcvFromClient(sockaddr_in& client) const;
|
||||
void HandleDownload(SOCKET TCPSock);
|
||||
void OnConnect(const std::weak_ptr<TClient>& c);
|
||||
void TCPClient(const std::weak_ptr<TClient>& c);
|
||||
void Looper(const std::weak_ptr<TClient>& c);
|
||||
int OpenID();
|
||||
void OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked);
|
||||
void Parse(TClient& c, const std::string& Packet);
|
||||
void SendFile(TClient& c, const std::string& Name);
|
||||
static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size);
|
||||
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
|
||||
};
|
||||
27
include/TPPSMonitor.h
Normal file
27
include/TPPSMonitor.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "TServer.h"
|
||||
#include <optional>
|
||||
|
||||
class TNetwork;
|
||||
|
||||
class TPPSMonitor : public IThreaded {
|
||||
public:
|
||||
explicit TPPSMonitor(TServer& Server);
|
||||
|
||||
void operator()() override;
|
||||
|
||||
void SetInternalPPS(int NewPPS) { mInternalPPS = NewPPS; }
|
||||
void IncrementInternalPPS() { ++mInternalPPS; }
|
||||
[[nodiscard]] int InternalPPS() const { return mInternalPPS; }
|
||||
void SetNetwork(TNetwork& Server) { mNetwork = std::ref(Server); }
|
||||
|
||||
private:
|
||||
TNetwork& Network() { return mNetwork->get(); }
|
||||
|
||||
TServer& mServer;
|
||||
std::optional<std::reference_wrapper<TNetwork>> mNetwork { std::nullopt };
|
||||
bool mShutdown { false };
|
||||
int mInternalPPS { 0 };
|
||||
};
|
||||
21
include/TResourceManager.h
Normal file
21
include/TResourceManager.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
class TResourceManager {
|
||||
public:
|
||||
TResourceManager();
|
||||
|
||||
[[nodiscard]] size_t MaxModSize() const { return mMaxModSize; }
|
||||
[[nodiscard]] std::string FileList() const { return mFileList; }
|
||||
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
|
||||
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
|
||||
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
|
||||
|
||||
private:
|
||||
size_t mMaxModSize = 0;
|
||||
std::string mFileSizes;
|
||||
std::string mFileList;
|
||||
std::string mTrimmedList;
|
||||
int mModsLoaded = 0;
|
||||
};
|
||||
37
include/TServer.h
Normal file
37
include/TServer.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "IThreaded.h"
|
||||
#include "RWMutex.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
class TClient;
|
||||
class TNetwork;
|
||||
class TPPSMonitor;
|
||||
|
||||
class TServer final {
|
||||
public:
|
||||
using TClientSet = std::unordered_set<std::shared_ptr<TClient>>;
|
||||
|
||||
TServer(int argc, char** argv);
|
||||
|
||||
void InsertClient(const std::shared_ptr<TClient>& Ptr);
|
||||
std::weak_ptr<TClient> InsertNewClient();
|
||||
void RemoveClient(const std::weak_ptr<TClient>&);
|
||||
// in Fn, return true to continue, return false to break
|
||||
void ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn);
|
||||
size_t ClientCount() const;
|
||||
|
||||
static void GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network);
|
||||
static void HandleEvent(TClient& c, const std::string& Data);
|
||||
RWMutex& GetClientMutex() const { return mClientsMutex; }
|
||||
private:
|
||||
TClientSet mClients;
|
||||
mutable RWMutex mClientsMutex;
|
||||
static void ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network);
|
||||
static bool ShouldSpawn(TClient& c, const std::string& CarJson, int ID);
|
||||
static bool IsUnicycle(TClient& c, const std::string& CarJson);
|
||||
static void Apply(TClient& c, int VID, const std::string& pckt);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
// Author: lionkor
|
||||
|
||||
#pragma once
|
||||
|
||||
// This header defines unix equivalents of common win32 functions.
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
#include "CustomAssert.h"
|
||||
#include <cstring>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// ZeroMemory is just a {0} or a memset(addr, 0, len), and it's a macro on MSVC
|
||||
inline void ZeroMemory([[maybe_unused]] void* dst, [[maybe_unused]] size_t len) {
|
||||
[[maybe_unused]] auto res = std::memset(dst, 0, len);
|
||||
Assert(res != nullptr);
|
||||
}
|
||||
// provides unix equivalent of closesocket call in win32
|
||||
inline void CloseSocketProper(int socket) {
|
||||
shutdown(socket, SHUT_RDWR);
|
||||
close(socket);
|
||||
}
|
||||
|
||||
#ifndef __try
|
||||
#define __try
|
||||
#endif
|
||||
|
||||
#ifndef __except
|
||||
#define __except (x) /**/
|
||||
#endif
|
||||
|
||||
#else // win32
|
||||
inline void CloseSocketProper(uint64_t socket) {
|
||||
shutdown(socket, SD_BOTH);
|
||||
closesocket(socket);
|
||||
}
|
||||
#endif // WIN32
|
||||
35
include/VehicleData.h
Normal file
35
include/VehicleData.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class TVehicleData final {
|
||||
public:
|
||||
TVehicleData(int ID, std::string Data);
|
||||
~TVehicleData();
|
||||
// We cannot delete this, since vector needs to be able to copy when it resizes.
|
||||
// Deleting this causes some wacky template errors which are hard to decipher,
|
||||
// and end up making no sense, so let's just leave the copy ctor.
|
||||
// TVehicleData(const TVehicleData&) = delete;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return mID == -1; }
|
||||
[[nodiscard]] int ID() const { return mID; }
|
||||
|
||||
[[nodiscard]] std::string Data() const { return mData; }
|
||||
void SetData(const std::string& Data) { mData = Data; }
|
||||
|
||||
bool operator==(const TVehicleData& v) const { return mID == v.mID; }
|
||||
|
||||
private:
|
||||
int mID { -1 };
|
||||
std::string mData;
|
||||
};
|
||||
|
||||
// TODO: unused now, remove?
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<TVehicleData> {
|
||||
std::size_t operator()(const TVehicleData& s) const noexcept {
|
||||
return s.ID();
|
||||
}
|
||||
};
|
||||
}
|
||||
1
include/commandline
Submodule
1
include/commandline
Submodule
Submodule include/commandline added at c34703df11
1
include/tomlplusplus
Submodule
1
include/tomlplusplus
Submodule
Submodule include/tomlplusplus added at bc6891e1fb
1
rapidjson
Submodule
1
rapidjson
Submodule
Submodule rapidjson added at 13dfc96c9c
1
socket.io-client-cpp
Submodule
1
socket.io-client-cpp
Submodule
Submodule socket.io-client-cpp added at b196fa7537
102
src/Client.cpp
Normal file
102
src/Client.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "Client.h"
|
||||
|
||||
#include "CustomAssert.h"
|
||||
#include <memory>
|
||||
|
||||
// FIXME: add debug prints
|
||||
|
||||
void TClient::DeleteCar(int Ident) {
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) {
|
||||
return Ident == elem.ID();
|
||||
});
|
||||
if (iter != mVehicleData.end()) {
|
||||
mVehicleData.erase(iter);
|
||||
} else {
|
||||
debug("tried to erase a vehicle that doesn't exist (not an error)");
|
||||
}
|
||||
}
|
||||
|
||||
void TClient::ClearCars() {
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
mVehicleData.clear();
|
||||
}
|
||||
|
||||
int TClient::GetOpenCarID() const {
|
||||
int OpenID = 0;
|
||||
bool found;
|
||||
do {
|
||||
found = true;
|
||||
for (auto& v : mVehicleData) {
|
||||
if (v.ID() == OpenID) {
|
||||
OpenID++;
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
} while (!found);
|
||||
return OpenID;
|
||||
}
|
||||
|
||||
void TClient::AddNewCar(int Ident, const std::string& Data) {
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
mVehicleData.emplace_back(Ident, Data);
|
||||
}
|
||||
|
||||
TClient::TVehicleDataLockPair TClient::GetAllCars() {
|
||||
return { &mVehicleData, std::unique_lock(mVehicleDataMutex) };
|
||||
}
|
||||
|
||||
std::string TClient::GetCarData(int Ident) {
|
||||
{ // lock
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
for (auto& v : mVehicleData) {
|
||||
if (v.ID() == Ident) {
|
||||
return v.Data();
|
||||
}
|
||||
}
|
||||
} // unlock
|
||||
DeleteCar(Ident);
|
||||
return "";
|
||||
}
|
||||
|
||||
void TClient::SetCarData(int Ident, const std::string& Data) {
|
||||
{ // lock
|
||||
std::unique_lock lock(mVehicleDataMutex);
|
||||
for (auto& v : mVehicleData) {
|
||||
if (v.ID() == Ident) {
|
||||
v.SetData(Data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // unlock
|
||||
DeleteCar(Ident);
|
||||
}
|
||||
|
||||
int TClient::GetCarCount() const {
|
||||
return int(mVehicleData.size());
|
||||
}
|
||||
|
||||
TServer& TClient::Server() const {
|
||||
return mServer;
|
||||
}
|
||||
|
||||
void TClient::EnqueuePacket(const std::string& Packet) {
|
||||
std::unique_lock Lock(mMissedPacketsMutex);
|
||||
mPacketsSync.push(Packet);
|
||||
}
|
||||
|
||||
TClient::TClient(TServer& Server)
|
||||
: mServer(Server)
|
||||
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
|
||||
}
|
||||
|
||||
void TClient::UpdatePingTime() {
|
||||
mLastPingTime = std::chrono::high_resolution_clock::now();
|
||||
//debug(GetName() + ": " + std::string("ping time updated!: ") + ((SecondsSinceLastPing() == 0) ? "OK" : "ERR"));
|
||||
}
|
||||
int TClient::SecondsSinceLastPing() {
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::high_resolution_clock::now() - mLastPingTime)
|
||||
.count();
|
||||
return int(seconds);
|
||||
}
|
||||
@@ -1,17 +1,29 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/15/2020
|
||||
///
|
||||
#include "Common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "TConsole.h"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <zlib.h>
|
||||
|
||||
#define Biggest 30000
|
||||
std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>();
|
||||
|
||||
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
|
||||
std::unique_lock Lock(mShutdownHandlersMutex);
|
||||
if (Handler) {
|
||||
mShutdownHandlers.push_front(Handler);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::GracefullyShutdown() {
|
||||
info("please wait while all subsystems are shutting down...");
|
||||
std::unique_lock Lock(mShutdownHandlersMutex);
|
||||
for (auto& Handler : mShutdownHandlers) {
|
||||
Handler();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Comp(std::string Data) {
|
||||
std::array<char, Biggest> C {};
|
||||
// obsolete
|
||||
@@ -33,6 +45,7 @@ std::string Comp(std::string Data) {
|
||||
std::copy_n(C.begin(), TO, Ret.begin());
|
||||
return Ret;
|
||||
}
|
||||
|
||||
std::string DeComp(std::string Compressed) {
|
||||
std::array<char, Biggest> C {};
|
||||
// not needed
|
||||
@@ -54,3 +67,22 @@ std::string DeComp(std::string Compressed) {
|
||||
std::copy_n(C.begin(), TO, Ret.begin());
|
||||
return Ret;
|
||||
}
|
||||
|
||||
// thread name stuff
|
||||
|
||||
std::map<std::thread::id, std::string> threadNameMap;
|
||||
|
||||
std::string ThreadName() {
|
||||
if (Application::Settings.DebugModeEnabled) {
|
||||
auto id = std::this_thread::get_id();
|
||||
if (threadNameMap.find(id) != threadNameMap.end()) {
|
||||
// found
|
||||
return threadNameMap.at(id) + " ";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void RegisterThread(const std::string str) {
|
||||
threadNameMap[std::this_thread::get_id()] = str;
|
||||
}
|
||||
35
src/Compat.cpp
Normal file
35
src/Compat.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "Compat.h"
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
static struct termios old, current;
|
||||
|
||||
void initTermios(int echo) {
|
||||
tcgetattr(0, &old); /* grab old terminal i/o settings */
|
||||
current = old; /* make new settings same as old settings */
|
||||
current.c_lflag &= ~ICANON; /* disable buffered i/o */
|
||||
if (echo) {
|
||||
current.c_lflag |= ECHO; /* set echo mode */
|
||||
} else {
|
||||
current.c_lflag &= ~ECHO; /* set no echo mode */
|
||||
}
|
||||
tcsetattr(0, TCSANOW, ¤t); /* use these new terminal i/o settings now */
|
||||
}
|
||||
|
||||
void resetTermios(void) {
|
||||
tcsetattr(0, TCSANOW, &old);
|
||||
}
|
||||
|
||||
char getch_(int echo) {
|
||||
char ch;
|
||||
initTermios(echo);
|
||||
read(STDIN_FILENO, &ch, 1);
|
||||
resetTermios();
|
||||
return ch;
|
||||
}
|
||||
|
||||
char _getch(void) {
|
||||
return getch_(0);
|
||||
}
|
||||
|
||||
#endif // !WIN32
|
||||
263
src/Console.cpp
263
src/Console.cpp
@@ -1,263 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 10/29/2020
|
||||
///
|
||||
|
||||
#include "Lua/LuaSystem.hpp"
|
||||
#ifdef WIN32
|
||||
#include <conio.h>
|
||||
#include <windows.h>
|
||||
#else // *nix
|
||||
typedef unsigned long DWORD, *PDWORD, *LPDWORD;
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#endif // WIN32
|
||||
#include "Logger.h"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
std::vector<std::string> QConsoleOut;
|
||||
std::string CInputBuff;
|
||||
std::mutex MLock;
|
||||
std::unique_ptr<Lua> LuaConsole;
|
||||
void HandleInput(const std::string& cmd) {
|
||||
std::cout << std::endl;
|
||||
if (cmd == ("exit")) {
|
||||
_Exit(0);
|
||||
} else if (cmd == ("clear") || cmd == ("cls")) {
|
||||
// 2J is clearscreen, H is reset position to top-left
|
||||
ConsoleOut(("\x1b[2J\x1b[H"));
|
||||
} else {
|
||||
LuaConsole->Execute(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessOut() {
|
||||
printf("%c[2K\r", 27);
|
||||
for (const std::string& msg : QConsoleOut)
|
||||
if (!msg.empty())
|
||||
std::cout << msg;
|
||||
MLock.lock();
|
||||
QConsoleOut.clear();
|
||||
MLock.unlock();
|
||||
std::cout << "> " << CInputBuff << std::flush;
|
||||
}
|
||||
|
||||
void ConsoleOut(const std::string& msg) {
|
||||
MLock.lock();
|
||||
QConsoleOut.emplace_back(msg);
|
||||
MLock.unlock();
|
||||
}
|
||||
|
||||
[[noreturn]] void OutputRefresh() {
|
||||
DebugPrintTID();
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
ProcessOut();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
static struct termios old, current;
|
||||
|
||||
void initTermios(int echo) {
|
||||
tcgetattr(0, &old); /* grab old terminal i/o settings */
|
||||
current = old; /* make new settings same as old settings */
|
||||
current.c_lflag &= ~ICANON; /* disable buffered i/o */
|
||||
if (echo) {
|
||||
current.c_lflag |= ECHO; /* set echo mode */
|
||||
} else {
|
||||
current.c_lflag &= ~ECHO; /* set no echo mode */
|
||||
}
|
||||
tcsetattr(0, TCSANOW, ¤t); /* use these new terminal i/o settings now */
|
||||
}
|
||||
|
||||
void resetTermios(void) {
|
||||
tcsetattr(0, TCSANOW, &old);
|
||||
}
|
||||
|
||||
char getch_(int echo) {
|
||||
char ch;
|
||||
initTermios(echo);
|
||||
read(STDIN_FILENO, &ch, 1);
|
||||
resetTermios();
|
||||
return ch;
|
||||
}
|
||||
|
||||
char _getch(void) {
|
||||
return getch_(0);
|
||||
}
|
||||
|
||||
#endif // WIN32
|
||||
|
||||
void SetupConsole() {
|
||||
#if defined(WIN32)
|
||||
DWORD outMode = 0;
|
||||
HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (stdoutHandle == INVALID_HANDLE_VALUE) {
|
||||
error("Invalid console handle! Inputs will not work properly");
|
||||
return;
|
||||
}
|
||||
if (!GetConsoleMode(stdoutHandle, &outMode)) {
|
||||
error("Invalid console mode! Inputs will not work properly");
|
||||
return;
|
||||
}
|
||||
// Enable ANSI escape codes
|
||||
outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
if (!SetConsoleMode(stdoutHandle, outMode)) {
|
||||
error("failed to set console mode! Inputs will not work properly");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
static std::vector<std::string> ConsoleHistory {};
|
||||
// buffer used to revert back to what we were writing when we go all the way forward in history
|
||||
static std::string LastInputBuffer {};
|
||||
static size_t ConsoleHistoryReadIndex { 0 };
|
||||
|
||||
static inline void ConsoleHistoryAdd(const std::string& cmd) {
|
||||
LastInputBuffer.clear();
|
||||
ConsoleHistory.push_back(cmd);
|
||||
ConsoleHistoryReadIndex = ConsoleHistory.size();
|
||||
}
|
||||
|
||||
static inline void ConsoleHistoryEnsureBounds() {
|
||||
if (ConsoleHistoryReadIndex >= ConsoleHistory.size()) {
|
||||
ConsoleHistoryReadIndex = ConsoleHistory.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ConsoleHistoryGoBack() {
|
||||
if (ConsoleHistoryReadIndex > 0) {
|
||||
--ConsoleHistoryReadIndex;
|
||||
ConsoleHistoryEnsureBounds();
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ConsoleHistoryGoForward() {
|
||||
++ConsoleHistoryReadIndex;
|
||||
ConsoleHistoryEnsureBounds();
|
||||
}
|
||||
|
||||
static std::string CompositeInput;
|
||||
static bool CompositeInputExpected { false };
|
||||
|
||||
static void ProcessCompositeInput() {
|
||||
#ifdef WIN32
|
||||
if (CompositeInput.size() == 1 && memcmp(CompositeInput.data(), std::array<char, 1> { 72 }.data(), 1) == 0) {
|
||||
#else // unix
|
||||
if (CompositeInput.size() == 2 && memcmp(CompositeInput.data(), std::array<char, 2> { 91, 65 }.data(), 2) == 0) {
|
||||
#endif // WIN32
|
||||
|
||||
// UP ARROW
|
||||
if (!ConsoleHistory.empty()) {
|
||||
ConsoleHistoryGoBack();
|
||||
CInputBuff = ConsoleHistory.at(ConsoleHistoryReadIndex);
|
||||
}
|
||||
#ifdef WIN32
|
||||
} else if (CompositeInput.size() == 1 && memcmp(CompositeInput.data(), std::array<char, 1> { 80 }.data(), 1) == 0) {
|
||||
#else // unix
|
||||
} else if (CompositeInput.size() == 2 && memcmp(CompositeInput.data(), std::array<char, 2> { 91, 66 }.data(), 2) == 0) {
|
||||
#endif // WIN32
|
||||
|
||||
// DOWN ARROW
|
||||
if (!ConsoleHistory.empty()) {
|
||||
if (ConsoleHistoryReadIndex == ConsoleHistory.size() - 1) {
|
||||
CInputBuff = LastInputBuffer;
|
||||
} else {
|
||||
ConsoleHistoryGoForward();
|
||||
CInputBuff = ConsoleHistory.at(ConsoleHistoryReadIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// not composite input, we made a mistake, so lets just add it to the buffer like usual
|
||||
CInputBuff += CompositeInput;
|
||||
}
|
||||
// ensure history doesnt grow too far beyond a max
|
||||
static constexpr size_t MaxHistory = 10;
|
||||
if (ConsoleHistory.size() > 2 * MaxHistory) {
|
||||
decltype(ConsoleHistory) NewHistory(ConsoleHistory.begin() + ConsoleHistory.size() - MaxHistory, ConsoleHistory.end());
|
||||
ConsoleHistory = std::move(NewHistory);
|
||||
ConsoleHistoryReadIndex = ConsoleHistory.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadCin() {
|
||||
DebugPrintTID();
|
||||
size_t null_byte_counter = 0;
|
||||
while (true) {
|
||||
int In = _getch();
|
||||
//info(std::to_string(In));
|
||||
if (In == 0) {
|
||||
++null_byte_counter;
|
||||
if (null_byte_counter > 50) {
|
||||
info(("too many null bytes in input, this is now assumed to be a background thread - console input is now disabled"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (CompositeInputExpected) {
|
||||
CompositeInput += char(In);
|
||||
#ifdef WIN32
|
||||
if (CompositeInput.size() == 1) {
|
||||
#else // unix
|
||||
if (CompositeInput.size() == 2) {
|
||||
#endif // WIN32
|
||||
CompositeInputExpected = false;
|
||||
ProcessCompositeInput();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (In == 13 || In == '\n') {
|
||||
if (!CInputBuff.empty()) {
|
||||
HandleInput(CInputBuff);
|
||||
ConsoleHistoryAdd(CInputBuff);
|
||||
CInputBuff.clear();
|
||||
}
|
||||
} else if (In == 8 || In == 127) {
|
||||
if (!CInputBuff.empty())
|
||||
CInputBuff.pop_back();
|
||||
} else if (In == 4) {
|
||||
CInputBuff = "exit";
|
||||
HandleInput(CInputBuff);
|
||||
CInputBuff.clear();
|
||||
} else if (In == 12) {
|
||||
CInputBuff = "clear";
|
||||
HandleInput(CInputBuff);
|
||||
CInputBuff.clear();
|
||||
#ifdef WIN32
|
||||
} else if (In == 224) {
|
||||
#else // unix
|
||||
} else if (In == 27) {
|
||||
#endif // WIN32
|
||||
|
||||
// escape char, assume stuff follows
|
||||
CompositeInputExpected = true;
|
||||
CompositeInput.clear();
|
||||
} else if (!isprint(In)) {
|
||||
// ignore
|
||||
} else {
|
||||
CInputBuff += char(In);
|
||||
LastInputBuffer = CInputBuff;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ConsoleInit() {
|
||||
SetupConsole();
|
||||
LuaConsole = std::make_unique<Lua>(true);
|
||||
LuaConsole->Init();
|
||||
printf("> ");
|
||||
std::thread In(ReadCin);
|
||||
In.detach();
|
||||
std::thread Out(OutputRefresh);
|
||||
Out.detach();
|
||||
}
|
||||
27
src/Enc.cpp
27
src/Enc.cpp
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#include "Security/Enc.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "Logger.h"
|
||||
#include <sstream>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <WS2tcpip.h>
|
||||
int Handle(EXCEPTION_POINTERS* ep, char* Origin) {
|
||||
Assert(false);
|
||||
std::stringstream R;
|
||||
R << ("Code : ") << std::hex
|
||||
<< ep->ExceptionRecord->ExceptionCode
|
||||
<< std::dec << (" Origin : ") << Origin;
|
||||
except(R.str());
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
// stub
|
||||
int Handle(EXCEPTION_POINTERS*, char*) { return 1; }
|
||||
#endif // WIN32
|
||||
145
src/Http.cpp
Normal file
145
src/Http.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "Http.h"
|
||||
|
||||
#include "Common.h"
|
||||
#undef error
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
namespace net = boost::asio; // from <boost/asio.hpp>
|
||||
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
|
||||
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||
|
||||
std::string Http::GET(const std::string& host, int port, const std::string& target) {
|
||||
// FIXME: doesn't support https
|
||||
// if it causes issues, yell at me and I'll fix it asap. - Lion
|
||||
try {
|
||||
net::io_context io;
|
||||
tcp::resolver resolver(io);
|
||||
beast::tcp_stream stream(io);
|
||||
auto const results = resolver.resolve(host, std::to_string(port));
|
||||
stream.connect(results);
|
||||
|
||||
http::request<http::string_body> req { http::verb::get, target, 11 /* http 1.1 */ };
|
||||
|
||||
req.set(http::field::host, host);
|
||||
// tell the server what we are (boost beast)
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
http::write(stream, req);
|
||||
|
||||
// used for reading
|
||||
beast::flat_buffer buffer;
|
||||
http::response<http::string_body> response;
|
||||
|
||||
http::read(stream, buffer, response);
|
||||
|
||||
std::string result(response.body());
|
||||
|
||||
beast::error_code ec;
|
||||
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
|
||||
if (ec && ec != beast::errc::not_connected) {
|
||||
throw beast::system_error { ec }; // goes down to `return "-1"` anyways
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Application::Console().Write(e.what());
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Http::POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json) {
|
||||
try {
|
||||
net::io_context io;
|
||||
|
||||
// The SSL context is required, and holds certificates
|
||||
ssl::context ctx(ssl::context::tlsv13);
|
||||
|
||||
ctx.set_verify_mode(ssl::verify_none);
|
||||
|
||||
tcp::resolver resolver(io);
|
||||
beast::ssl_stream<beast::tcp_stream> stream(io, ctx);
|
||||
decltype(resolver)::results_type results;
|
||||
auto try_connect_with_protocol = [&](tcp protocol) {
|
||||
try {
|
||||
results = resolver.resolve(protocol, host, std::to_string(443));
|
||||
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
|
||||
boost::system::error_code ec { static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
|
||||
// FIXME: we could throw and crash, if we like
|
||||
// throw boost::system::system_error { ec };
|
||||
//debug("POST " + host + target + " failed.");
|
||||
return false;
|
||||
}
|
||||
beast::get_lowest_layer(stream).connect(results);
|
||||
} catch (const boost::system::system_error&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
//bool ok = try_connect_with_protocol(tcp::v6());
|
||||
//if (!ok) {
|
||||
//debug("IPv6 connect failed, trying IPv4");
|
||||
bool ok = try_connect_with_protocol(tcp::v4());
|
||||
if (!ok) {
|
||||
//error("failed to resolve or connect in POST " + host + target);
|
||||
return "-1";
|
||||
}
|
||||
//}
|
||||
stream.handshake(ssl::stream_base::client);
|
||||
http::request<http::string_body> req { http::verb::post, target, 11 /* http 1.1 */ };
|
||||
|
||||
req.set(http::field::host, host);
|
||||
if (!body.empty()) {
|
||||
if (json) {
|
||||
// FIXME: json is untested.
|
||||
req.set(http::field::content_type, "application/json");
|
||||
} else {
|
||||
req.set(http::field::content_type, "application/x-www-form-urlencoded");
|
||||
}
|
||||
req.set(http::field::content_length, std::to_string(body.size()));
|
||||
req.body() = body;
|
||||
// info("body is " + body + " (" + req.body() + ")");
|
||||
// info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast<std::string>(body.size()) + ")");
|
||||
}
|
||||
for (const auto& pair : fields) {
|
||||
// info("setting " + pair.first + " to " + pair.second);
|
||||
req.set(pair.first, pair.second);
|
||||
}
|
||||
|
||||
std::stringstream oss;
|
||||
oss << req;
|
||||
|
||||
beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5));
|
||||
|
||||
http::write(stream, req);
|
||||
|
||||
// used for reading
|
||||
beast::flat_buffer buffer;
|
||||
http::response<http::string_body> response;
|
||||
|
||||
http::read(stream, buffer, response);
|
||||
|
||||
std::stringstream result;
|
||||
result << response;
|
||||
|
||||
beast::error_code ec;
|
||||
stream.shutdown(ec);
|
||||
// IGNORING ec
|
||||
|
||||
// info(result.str());
|
||||
std::string debug_response_str;
|
||||
std::getline(result, debug_response_str);
|
||||
//debug("POST " + host + target + ": " + debug_response_str);
|
||||
return std::string(response.body());
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Application::Console().Write(e.what());
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#include "CustomAssert.h"
|
||||
#include "Logger.h"
|
||||
#include "Security/Enc.h"
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
std::string ServerName;
|
||||
std::string ServerDesc;
|
||||
std::string Resource;
|
||||
std::string MapName;
|
||||
std::string Key;
|
||||
int MaxPlayers;
|
||||
bool Private;
|
||||
int MaxCars;
|
||||
bool Debug;
|
||||
int Port;
|
||||
|
||||
void SetValues(const std::string& Line, int Index) {
|
||||
int state = 0;
|
||||
std::string Data;
|
||||
bool Switch = false;
|
||||
if (Index > 5)
|
||||
Switch = true;
|
||||
for (char c : Line) {
|
||||
if (Switch) {
|
||||
if (c == '\"')
|
||||
state++;
|
||||
if (state > 0 && state < 2)
|
||||
Data += c;
|
||||
} else {
|
||||
if (c == ' ')
|
||||
state++;
|
||||
if (state > 1)
|
||||
Data += c;
|
||||
}
|
||||
}
|
||||
Data = Data.substr(1);
|
||||
std::string::size_type sz;
|
||||
bool FoundTrue = std::string(Data).find("true") != std::string::npos; //searches for "true"
|
||||
switch (Index) {
|
||||
case 1:
|
||||
Debug = FoundTrue; //checks and sets the Debug Value
|
||||
break;
|
||||
case 2:
|
||||
Private = FoundTrue; //checks and sets the Private Value
|
||||
break;
|
||||
case 3:
|
||||
Port = std::stoi(Data, &sz); //sets the Port
|
||||
break;
|
||||
case 4:
|
||||
MaxCars = std::stoi(Data, &sz); //sets the Max Car amount
|
||||
break;
|
||||
case 5:
|
||||
MaxPlayers = std::stoi(Data, &sz); //sets the Max Amount of player
|
||||
break;
|
||||
case 6:
|
||||
MapName = Data; //Map
|
||||
break;
|
||||
case 7:
|
||||
ServerName = Data; //Name
|
||||
break;
|
||||
case 8:
|
||||
ServerDesc = Data; //desc
|
||||
break;
|
||||
case 9:
|
||||
Resource = Data; //File name
|
||||
break;
|
||||
case 10:
|
||||
Key = Data; //File name
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string RemoveComments(const std::string& Line) {
|
||||
std::string Return;
|
||||
for (char c : Line) {
|
||||
if (c == '#')
|
||||
break;
|
||||
Return += c;
|
||||
}
|
||||
return Return;
|
||||
}
|
||||
void LoadConfig(std::ifstream& IFS) {
|
||||
Assert(IFS.is_open());
|
||||
std::string line;
|
||||
int index = 1;
|
||||
while (getline(IFS, line)) {
|
||||
index++;
|
||||
}
|
||||
if (index - 1 < 11) {
|
||||
error(("Outdated/Incorrect config please remove it server will close in 5 secs"));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
_Exit(0);
|
||||
}
|
||||
IFS.close();
|
||||
IFS.open(("Server.cfg"));
|
||||
info(("Config found updating values"));
|
||||
index = 1;
|
||||
while (getline(IFS, line)) {
|
||||
if (line.rfind('#', 0) != 0 && line.rfind(' ', 0) != 0) { //Checks if it starts as Comment
|
||||
std::string CleanLine = RemoveComments(line); //Cleans it from the Comments
|
||||
SetValues(CleanLine, index); //sets the values
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
void GenerateConfig() {
|
||||
std::ofstream FileStream;
|
||||
FileStream.open(("Server.cfg"));
|
||||
FileStream << ("# This is the BeamMP Server Configuration File v0.60\n"
|
||||
"Debug = false # true or false to enable debug console output\n"
|
||||
"Private = true # Private?\n"
|
||||
"Port = 30814 # Port to run the server on UDP and TCP\n"
|
||||
"Cars = 1 # Max cars for every player\n"
|
||||
"MaxPlayers = 10 # Maximum Amount of Clients\n"
|
||||
"Map = \"/levels/gridmap/info.json\" # Default Map\n"
|
||||
"Name = \"BeamMP New Server\" # Server Name\n"
|
||||
"Desc = \"BeamMP Default Description\" # Server Description\n"
|
||||
"use = \"Resources\" # Resource file name\n"
|
||||
"AuthKey = \"\" # Auth Key");
|
||||
FileStream.close();
|
||||
}
|
||||
void Default() {
|
||||
info(("Config not found generating default"));
|
||||
GenerateConfig();
|
||||
error(("You are required to input the AuthKey"));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
_Exit(0);
|
||||
}
|
||||
void DebugData() {
|
||||
debug(std::string("Debug : ") + (Debug ? "true" : "false"));
|
||||
debug(std::string("Private : ") + (Private ? "true" : "false"));
|
||||
debug("Port : " + std::to_string(Port));
|
||||
debug("Max Cars : " + std::to_string(MaxCars));
|
||||
debug("MaxPlayers : " + std::to_string(MaxPlayers));
|
||||
debug("MapName : " + MapName);
|
||||
debug("ServerName : " + ServerName);
|
||||
debug("ServerDesc : " + ServerDesc);
|
||||
debug("File : " + Resource);
|
||||
debug("Key length: " + std::to_string(Key.length()));
|
||||
}
|
||||
void InitConfig() {
|
||||
////TODO: Move to json after update 4
|
||||
|
||||
std::ifstream IFS;
|
||||
IFS.open(("Server.cfg"));
|
||||
if (IFS.good())
|
||||
LoadConfig(IFS);
|
||||
else
|
||||
Default();
|
||||
if (IFS.is_open())
|
||||
IFS.close();
|
||||
if (Key.empty()) {
|
||||
error(("No AuthKey was found"));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
_Exit(0);
|
||||
}
|
||||
if (Debug)
|
||||
DebugData();
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#include "Client.hpp"
|
||||
#include "Curl/Http.h"
|
||||
#include "Logger.h"
|
||||
#include "Settings.h"
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
void WebsocketInit();
|
||||
std::string GetPlayers() {
|
||||
std::string Return;
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr) {
|
||||
Return += c->GetName() + ";";
|
||||
}
|
||||
}
|
||||
return Return;
|
||||
}
|
||||
std::string GenerateCall() {
|
||||
std::stringstream Ret;
|
||||
Ret << "uuid=" << Key << "&players=" << CI->Size()
|
||||
<< "&maxplayers=" << MaxPlayers << "&port=" << Port
|
||||
<< "&map=" << MapName << "&private=" << (Private ? "true" : "false")
|
||||
<< "&version=" << GetSVer() << "&clientversion=" << GetCVer()
|
||||
<< "&name=" << ServerName << "&pps=" << StatReport
|
||||
<< "&modlist=" << FileList << "&modstotalsize=" << MaxModSize
|
||||
<< "&modstotal=" << ModsLoaded << "&playerslist=" << GetPlayers()
|
||||
<< "&desc=" << ServerDesc;
|
||||
return Ret.str();
|
||||
}
|
||||
std::string RunPromise(const std::string& IP, const std::string& R) {
|
||||
std::packaged_task<std::string()> task([&] { return PostHTTP(IP, R, false); });
|
||||
std::future<std::string> f1 = task.get_future();
|
||||
std::thread t(std::move(task));
|
||||
t.detach();
|
||||
auto status = f1.wait_for(std::chrono::seconds(15));
|
||||
if (status != std::future_status::timeout)
|
||||
return f1.get();
|
||||
error("Backend system Timeout please try again later");
|
||||
return "";
|
||||
}
|
||||
|
||||
[[noreturn]] void Heartbeat() {
|
||||
DebugPrintTID();
|
||||
std::string R, T;
|
||||
bool isAuth = false;
|
||||
while (true) {
|
||||
R = GenerateCall();
|
||||
if (!CustomIP.empty())
|
||||
R += "&ip=" + CustomIP;
|
||||
std::string link = "https://beammp.com/heartbeatv2";
|
||||
T = RunPromise(link, R);
|
||||
|
||||
if (T.substr(0, 2) != "20") {
|
||||
//Backend system refused server startup!
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
std::string Backup = "https://backup1.beammp.com/heartbeatv2";
|
||||
T = RunPromise(Backup, R);
|
||||
if (T.substr(0, 2) != "20") {
|
||||
warn("Backend system refused server! Server might not show in the public list");
|
||||
}
|
||||
}
|
||||
//Server Authenticated
|
||||
if (!isAuth) {
|
||||
WebsocketInit();
|
||||
if (T.length() == 4)info(("Authenticated!"));
|
||||
else info(("Resumed authenticated session!"));
|
||||
isAuth = true;
|
||||
}
|
||||
//std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
}
|
||||
}
|
||||
void HBInit() {
|
||||
std::thread HB(Heartbeat);
|
||||
HB.detach();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#include "Logger.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
uint64_t MaxModSize = 0;
|
||||
std::string FileSizes;
|
||||
std::string FileList;
|
||||
int ModsLoaded = 0;
|
||||
|
||||
void InitRes() {
|
||||
std::string Path = Resource + "/Client";
|
||||
if (!fs::exists(Path))
|
||||
fs::create_directory(Path);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
auto pos = entry.path().string().find(".zip");
|
||||
if (pos != std::string::npos) {
|
||||
if (entry.path().string().length() - pos == 4) {
|
||||
FileList += entry.path().string() + ";";
|
||||
FileSizes += std::to_string(uint64_t(fs::file_size(entry.path()))) + ";";
|
||||
MaxModSize += uint64_t(fs::file_size(entry.path()));
|
||||
ModsLoaded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::replace(FileList.begin(), FileList.end(), '\\', '/');
|
||||
if (ModsLoaded)
|
||||
info("Loaded " + std::to_string(ModsLoaded) + " Mods");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/28/2020
|
||||
///
|
||||
#include "Client.hpp"
|
||||
#include "Logger.h"
|
||||
#include "Security/Enc.h"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
std::string CustomIP;
|
||||
std::string GetSVer() {
|
||||
return "1.20";
|
||||
}
|
||||
std::string GetCVer() {
|
||||
return "1.80";
|
||||
}
|
||||
void Args(int argc, char* argv[]) {
|
||||
info("BeamMP Server Running version " + GetSVer());
|
||||
if (argc > 1) {
|
||||
CustomIP = argv[1];
|
||||
size_t n = std::count(CustomIP.begin(), CustomIP.end(), '.');
|
||||
auto p = CustomIP.find_first_not_of((".0123456789"));
|
||||
if (p != std::string::npos || n != 3 || CustomIP.substr(0, 3) == ("127")) {
|
||||
CustomIP.clear();
|
||||
warn("IP Specified is invalid! Ignoring");
|
||||
} else
|
||||
info("Server started with custom IP");
|
||||
}
|
||||
}
|
||||
void InitServer(int argc, char* argv[]) {
|
||||
InitLog();
|
||||
Args(argc, argv);
|
||||
CI = std::make_unique<ClientInterface>();
|
||||
}
|
||||
@@ -1,705 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/19/2020
|
||||
///
|
||||
|
||||
#include "Lua/LuaSystem.hpp"
|
||||
#include "Client.hpp"
|
||||
#include "Logger.h"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
std::shared_ptr<LuaArg> CreateArg(lua_State* L, int T, int S) {
|
||||
if (S > T)
|
||||
return nullptr;
|
||||
std::shared_ptr<LuaArg> temp(new LuaArg);
|
||||
for (int C = S; C <= T; C++) {
|
||||
if (lua_isstring(L, C)) {
|
||||
temp->args.emplace_back(std::string(lua_tostring(L, C)));
|
||||
} else if (lua_isinteger(L, C)) {
|
||||
temp->args.emplace_back(int(lua_tointeger(L, C)));
|
||||
} else if (lua_isboolean(L, C)) {
|
||||
temp->args.emplace_back(bool(lua_toboolean(L, C)));
|
||||
} else if (lua_isnumber(L, C)) {
|
||||
temp->args.emplace_back(float(lua_tonumber(L, C)));
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
void ClearStack(lua_State* L) {
|
||||
lua_settop(L, 0);
|
||||
}
|
||||
std::optional<std::reference_wrapper<Lua>> GetScript(lua_State* L) {
|
||||
for (auto& Script : PluginEngine) {
|
||||
if (Script->GetState() == L)
|
||||
return *Script;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
void SendError(lua_State* L, const std::string& msg) {
|
||||
Assert(L);
|
||||
auto MaybeS = GetScript(L);
|
||||
std::string a;
|
||||
if (!MaybeS.has_value()) {
|
||||
a = ("_Console");
|
||||
} else {
|
||||
Lua& S = MaybeS.value();
|
||||
a = fs::path(S.GetFileName()).filename().string();
|
||||
}
|
||||
warn(a + (" | Incorrect Call of ") + msg);
|
||||
}
|
||||
std::any Trigger(Lua* lua, const std::string& R, std::shared_ptr<LuaArg> arg) {
|
||||
std::lock_guard<std::mutex> lockGuard(lua->Lock);
|
||||
std::packaged_task<std::any(std::shared_ptr<LuaArg>)> task([lua, R](std::shared_ptr<LuaArg> arg) { return CallFunction(lua, R, arg); });
|
||||
std::future<std::any> f1 = task.get_future();
|
||||
std::thread t(std::move(task), arg);
|
||||
t.detach();
|
||||
auto status = f1.wait_for(std::chrono::seconds(5));
|
||||
if (status != std::future_status::timeout)
|
||||
return f1.get();
|
||||
SendError(lua->GetState(), R + " took too long to respond");
|
||||
return 0;
|
||||
}
|
||||
std::any FutureWait(Lua* lua, const std::string& R, std::shared_ptr<LuaArg> arg, bool Wait) {
|
||||
Assert(lua);
|
||||
std::packaged_task<std::any(std::shared_ptr<LuaArg>)> task([lua, R](std::shared_ptr<LuaArg> arg) { return Trigger(lua, R, arg); });
|
||||
std::future<std::any> f1 = task.get_future();
|
||||
std::thread t(std::move(task), arg);
|
||||
t.detach();
|
||||
int T = 0;
|
||||
if (Wait)
|
||||
T = 6;
|
||||
auto status = f1.wait_for(std::chrono::seconds(T));
|
||||
if (status != std::future_status::timeout)
|
||||
return f1.get();
|
||||
return 0;
|
||||
}
|
||||
std::any TriggerLuaEvent(const std::string& Event, bool local, Lua* Caller, std::shared_ptr<LuaArg> arg, bool Wait) {
|
||||
std::any R;
|
||||
std::string Type;
|
||||
int Ret = 0;
|
||||
for (auto& Script : PluginEngine) {
|
||||
if (Script->IsRegistered(Event)) {
|
||||
if (local) {
|
||||
if (Script->GetPluginName() == Caller->GetPluginName()) {
|
||||
R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait);
|
||||
Type = R.type().name();
|
||||
if (Type.find("int") != std::string::npos) {
|
||||
if (std::any_cast<int>(R))
|
||||
Ret++;
|
||||
} else if (Event == "onPlayerAuth")
|
||||
return R;
|
||||
}
|
||||
} else {
|
||||
R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait);
|
||||
Type = R.type().name();
|
||||
if (Type.find("int") != std::string::npos) {
|
||||
if (std::any_cast<int>(R))
|
||||
Ret++;
|
||||
} else if (Event == "onPlayerAuth")
|
||||
return R;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
bool ConsoleCheck(lua_State* L, int r) {
|
||||
if (r != LUA_OK) {
|
||||
std::string msg = lua_tostring(L, -1);
|
||||
warn(("_Console | ") + msg);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool CheckLua(lua_State* L, int r) {
|
||||
if (r != LUA_OK) {
|
||||
std::string msg = lua_tostring(L, -1);
|
||||
auto MaybeS = GetScript(L);
|
||||
if (MaybeS.has_value()) {
|
||||
Lua& S = MaybeS.value();
|
||||
std::string a = fs::path(S.GetFileName()).filename().string();
|
||||
warn(a + " | " + msg);
|
||||
return false;
|
||||
}
|
||||
// What the fuck, what do we do?!
|
||||
AssertNotReachable();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int lua_RegisterEvent(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
Lua& Script = MaybeScript.value();
|
||||
if (Args == 2 && lua_isstring(L, 1) && lua_isstring(L, 2)) {
|
||||
Script.RegisterEvent(lua_tostring(L, 1), lua_tostring(L, 2));
|
||||
} else
|
||||
SendError(L, ("RegisterEvent invalid argument count expected 2 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
int lua_TriggerEventL(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
Lua& Script = MaybeScript.value();
|
||||
if (Args > 0) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
TriggerLuaEvent(lua_tostring(L, 1), true, &Script, CreateArg(L, Args, 2), false);
|
||||
} else
|
||||
SendError(L, ("TriggerLocalEvent wrong argument [1] need string"));
|
||||
} else {
|
||||
SendError(L, ("TriggerLocalEvent not enough arguments expected 1 got 0"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_TriggerEventG(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
Lua& Script = MaybeScript.value();
|
||||
if (Args > 0) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
TriggerLuaEvent(lua_tostring(L, 1), false, &Script, CreateArg(L, Args, 2), false);
|
||||
} else
|
||||
SendError(L, ("TriggerGlobalEvent wrong argument [1] need string"));
|
||||
} else
|
||||
SendError(L, ("TriggerGlobalEvent not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* ThreadOrigin(Lua* lua) {
|
||||
std::string T = "Thread in " + fs::path(lua->GetFileName()).filename().string();
|
||||
char* Data = new char[T.size() + 1];
|
||||
ZeroMemory(Data, T.size() + 1);
|
||||
memcpy(Data, T.c_str(), T.size());
|
||||
return Data;
|
||||
}
|
||||
void SafeExecution(Lua* lua, const std::string& FuncName) {
|
||||
lua_State* luaState = lua->GetState();
|
||||
lua_getglobal(luaState, FuncName.c_str());
|
||||
if (lua_isfunction(luaState, -1)) {
|
||||
char* Origin = ThreadOrigin(lua);
|
||||
#ifdef WIN32
|
||||
__try {
|
||||
int R = lua_pcall(luaState, 0, 0, 0);
|
||||
CheckLua(luaState, R);
|
||||
} __except (Handle(GetExceptionInformation(), Origin)) {
|
||||
}
|
||||
#else // unix
|
||||
int R = lua_pcall(luaState, 0, 0, 0);
|
||||
CheckLua(luaState, R);
|
||||
#endif // WIN32
|
||||
delete[] Origin;
|
||||
}
|
||||
ClearStack(luaState);
|
||||
}
|
||||
|
||||
void ExecuteAsync(Lua* lua, const std::string& FuncName) {
|
||||
std::lock_guard<std::mutex> lockGuard(lua->Lock);
|
||||
SafeExecution(lua, FuncName);
|
||||
}
|
||||
void CallAsync(Lua* lua, const std::string& Func, int U) {
|
||||
DebugPrintTID();
|
||||
lua->SetStopThread(false);
|
||||
int D = 1000 / U;
|
||||
while (!lua->GetStopThread()) {
|
||||
ExecuteAsync(lua, Func);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(D));
|
||||
}
|
||||
}
|
||||
int lua_StopThread(lua_State* L) {
|
||||
auto MaybeScript = GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
// ugly, but whatever, this is safe as fuck
|
||||
MaybeScript.value().get().SetStopThread(true);
|
||||
return 0;
|
||||
}
|
||||
int lua_CreateThread(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args > 1) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
std::string STR = lua_tostring(L, 1);
|
||||
if (lua_isinteger(L, 2) || lua_isnumber(L, 2)) {
|
||||
int U = int(lua_tointeger(L, 2));
|
||||
if (U > 0 && U < 501) {
|
||||
auto MaybeScript = GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
Lua& Script = MaybeScript.value();
|
||||
std::thread t1(CallAsync, &Script, STR, U);
|
||||
t1.detach();
|
||||
} else
|
||||
SendError(L, ("CreateThread wrong argument [2] number must be between 1 and 500"));
|
||||
} else
|
||||
SendError(L, ("CreateThread wrong argument [2] need number"));
|
||||
} else
|
||||
SendError(L, ("CreateThread wrong argument [1] need string"));
|
||||
} else
|
||||
SendError(L, ("CreateThread not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
int lua_Sleep(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int t = int(lua_tonumber(L, 1));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(t));
|
||||
} else {
|
||||
SendError(L, ("Sleep not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Client* GetClient(int ID) {
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr && c->GetID() == ID) {
|
||||
return c.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
int lua_isConnected(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c != nullptr)
|
||||
lua_pushboolean(L, c->isConnected);
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(L, ("isConnected not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int lua_GetPlayerName(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c != nullptr)
|
||||
lua_pushstring(L, c->GetName().c_str());
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(L, ("GetPlayerName not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int lua_GetPlayerCount(lua_State* L) {
|
||||
lua_pushinteger(L, CI->Size());
|
||||
return 1;
|
||||
}
|
||||
int lua_GetGuest(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c != nullptr)
|
||||
lua_pushboolean(L, c->isGuest);
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(L, "GetGuest not enough arguments");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int lua_GetAllPlayers(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
int i = 1;
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c == nullptr)
|
||||
continue;
|
||||
lua_pushinteger(L, c->GetID());
|
||||
lua_pushstring(L, c->GetName().c_str());
|
||||
lua_settable(L, -3);
|
||||
i++;
|
||||
}
|
||||
if (CI->Clients.empty())
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
int lua_GetCars(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c != nullptr) {
|
||||
int i = 1;
|
||||
for (auto& v : c->GetAllCars()) {
|
||||
if (v != nullptr) {
|
||||
lua_pushinteger(L, v->ID);
|
||||
lua_pushstring(L, v->Data.substr(3).c_str());
|
||||
lua_settable(L, -3);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (c->GetAllCars().empty())
|
||||
return 0;
|
||||
} else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(L, ("GetPlayerVehicles not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
int lua_dropPlayer(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c == nullptr)
|
||||
return 0;
|
||||
std::string Reason;
|
||||
if (Args > 1 && lua_isstring(L, 2)) {
|
||||
Reason = std::string((" Reason : ")) + lua_tostring(L, 2);
|
||||
}
|
||||
Respond(c, "C:Server:You have been Kicked from the server! " + Reason, true);
|
||||
c->SetStatus(-2);
|
||||
info(("Closing socket due to kick"));
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
} else
|
||||
SendError(L, ("DropPlayer not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
int lua_sendChat(lua_State* L) {
|
||||
if (lua_isinteger(L, 1) || lua_isnumber(L, 1)) {
|
||||
if (lua_isstring(L, 2)) {
|
||||
int ID = int(lua_tointeger(L, 1));
|
||||
if (ID == -1) {
|
||||
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
} else {
|
||||
Client* c = GetClient(ID);
|
||||
if (c != nullptr) {
|
||||
if (!c->isSynced)
|
||||
return 0;
|
||||
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
|
||||
Respond(c, Packet, true);
|
||||
} else
|
||||
SendError(L, ("SendChatMessage invalid argument [1] invalid ID"));
|
||||
}
|
||||
} else
|
||||
SendError(L, ("SendChatMessage invalid argument [2] expected string"));
|
||||
} else
|
||||
SendError(L, ("SendChatMessage invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
int lua_RemoveVehicle(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 2) {
|
||||
SendError(L, ("RemoveVehicle invalid argument count expected 2 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if ((lua_isinteger(L, 1) || lua_isnumber(L, 1)) && (lua_isinteger(L, 2) || lua_isnumber(L, 2))) {
|
||||
int PID = int(lua_tointeger(L, 1));
|
||||
int VID = int(lua_tointeger(L, 2));
|
||||
Client* c = GetClient(PID);
|
||||
if (c == nullptr) {
|
||||
SendError(L, ("RemoveVehicle invalid Player ID"));
|
||||
return 0;
|
||||
}
|
||||
if (!c->GetCarData(VID).empty()) {
|
||||
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
||||
SendToAll(nullptr, Destroy, true, true);
|
||||
c->DeleteCar(VID);
|
||||
}
|
||||
} else
|
||||
SendError(L, ("RemoveVehicle invalid argument expected number"));
|
||||
return 0;
|
||||
}
|
||||
int lua_HWID(lua_State* L) {
|
||||
lua_pushinteger(L, -1);
|
||||
return 1;
|
||||
}
|
||||
int lua_RemoteEvent(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 3) {
|
||||
SendError(L, ("TriggerClientEvent invalid argument count expected 3 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isnumber(L, 1)) {
|
||||
SendError(L, ("TriggerClientEvent invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isstring(L, 2)) {
|
||||
SendError(L, ("TriggerClientEvent invalid argument [2] expected string"));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isstring(L, 3)) {
|
||||
SendError(L, ("TriggerClientEvent invalid argument [3] expected string"));
|
||||
return 0;
|
||||
}
|
||||
int ID = int(lua_tointeger(L, 1));
|
||||
std::string Packet = "E:" + std::string(lua_tostring(L, 2)) + ":" + std::string(lua_tostring(L, 3));
|
||||
if (ID == -1)
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
else {
|
||||
Client* c = GetClient(ID);
|
||||
if (c == nullptr) {
|
||||
SendError(L, ("TriggerClientEvent invalid Player ID"));
|
||||
return 0;
|
||||
}
|
||||
Respond(c, Packet, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int lua_ServerExit(lua_State* L) {
|
||||
if (lua_gettop(L) > 0) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
_Exit(int(lua_tointeger(L, 1)));
|
||||
}
|
||||
}
|
||||
_Exit(0);
|
||||
}
|
||||
int lua_Set(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 2) {
|
||||
SendError(L, ("set invalid argument count expected 2 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isnumber(L, 1)) {
|
||||
SendError(L, ("set invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
auto MaybeSrc = GetScript(L);
|
||||
std::string Name;
|
||||
if (!MaybeSrc.has_value()) {
|
||||
Name = ("_Console");
|
||||
} else {
|
||||
Name = MaybeSrc.value().get().GetPluginName();
|
||||
}
|
||||
int C = int(lua_tointeger(L, 1));
|
||||
switch (C) {
|
||||
case 0: //debug
|
||||
if (lua_isboolean(L, 2)) {
|
||||
Debug = lua_toboolean(L, 2);
|
||||
info(Name + (" | Debug -> ") + (Debug ? "true" : "false"));
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected boolean for ID : 0"));
|
||||
break;
|
||||
case 1: //private
|
||||
if (lua_isboolean(L, 2)) {
|
||||
Private = lua_toboolean(L, 2);
|
||||
info(Name + (" | Private -> ") + (Private ? "true" : "false"));
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected boolean for ID : 1"));
|
||||
break;
|
||||
case 2: //max cars
|
||||
if (lua_isnumber(L, 2)) {
|
||||
MaxCars = int(lua_tointeger(L, 2));
|
||||
info(Name + (" | MaxCars -> ") + std::to_string(MaxCars));
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected number for ID : 2"));
|
||||
break;
|
||||
case 3: //max players
|
||||
if (lua_isnumber(L, 2)) {
|
||||
MaxPlayers = int(lua_tointeger(L, 2));
|
||||
info(Name + (" | MaxPlayers -> ") + std::to_string(MaxPlayers));
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected number for ID : 3"));
|
||||
break;
|
||||
case 4: //Map
|
||||
if (lua_isstring(L, 2)) {
|
||||
MapName = lua_tostring(L, 2);
|
||||
info(Name + (" | MapName -> ") + MapName);
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected string for ID : 4"));
|
||||
break;
|
||||
case 5: //Name
|
||||
if (lua_isstring(L, 2)) {
|
||||
ServerName = lua_tostring(L, 2);
|
||||
info(Name + (" | ServerName -> ") + ServerName);
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected string for ID : 5"));
|
||||
break;
|
||||
case 6: //Desc
|
||||
if (lua_isstring(L, 2)) {
|
||||
ServerDesc = lua_tostring(L, 2);
|
||||
info(Name + (" | ServerDesc -> ") + ServerDesc);
|
||||
} else
|
||||
SendError(L, ("set invalid argument [2] expected string for ID : 6"));
|
||||
break;
|
||||
default:
|
||||
warn(("Invalid config ID : ") + std::to_string(C));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
extern "C" {
|
||||
int lua_Print(lua_State* L) {
|
||||
int Arg = lua_gettop(L);
|
||||
for (int i = 1; i <= Arg; i++) {
|
||||
auto str = lua_tostring(L, i);
|
||||
if (str != nullptr) {
|
||||
ConsoleOut(str + std::string(("\n")));
|
||||
} else {
|
||||
ConsoleOut(("nil\n"));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Lua::Lua(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote, bool Console)
|
||||
: luaState(luaL_newstate()) {
|
||||
Assert(luaState);
|
||||
if (!PluginName.empty()) {
|
||||
SetPluginName(PluginName);
|
||||
}
|
||||
if (!FileName.empty()) {
|
||||
SetFileName(FileName);
|
||||
}
|
||||
SetLastWrite(LastWrote);
|
||||
_Console = Console;
|
||||
}
|
||||
|
||||
Lua::Lua(bool Console)
|
||||
: luaState(luaL_newstate()) {
|
||||
_Console = Console;
|
||||
}
|
||||
|
||||
void Lua::Execute(const std::string& Command) {
|
||||
if (ConsoleCheck(luaState, luaL_dostring(luaState, Command.c_str()))) {
|
||||
lua_settop(luaState, 0);
|
||||
}
|
||||
}
|
||||
void Lua::Reload() {
|
||||
if (CheckLua(luaState, luaL_dofile(luaState, _FileName.c_str()))) {
|
||||
CallFunction(this, ("onInit"), nullptr);
|
||||
}
|
||||
}
|
||||
std::string Lua::GetOrigin() {
|
||||
return fs::path(GetFileName()).filename().string();
|
||||
}
|
||||
|
||||
std::any CallFunction(Lua* lua, const std::string& FuncName, std::shared_ptr<LuaArg> Arg) {
|
||||
lua_State* luaState = lua->GetState();
|
||||
lua_getglobal(luaState, FuncName.c_str());
|
||||
if (lua_isfunction(luaState, -1)) {
|
||||
int Size = 0;
|
||||
if (Arg != nullptr) {
|
||||
Size = int(Arg->args.size());
|
||||
Arg->PushArgs(luaState);
|
||||
}
|
||||
int R = lua_pcall(luaState, Size, 1, 0);
|
||||
if (CheckLua(luaState, R)) {
|
||||
if (lua_isnumber(luaState, -1)) {
|
||||
return int(lua_tointeger(luaState, -1));
|
||||
} else if (lua_isstring(luaState, -1)) {
|
||||
return std::string(lua_tostring(luaState, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
ClearStack(luaState);
|
||||
return 0;
|
||||
}
|
||||
void Lua::SetPluginName(const std::string& Name) {
|
||||
_PluginName = Name;
|
||||
}
|
||||
void Lua::SetFileName(const std::string& Name) {
|
||||
_FileName = Name;
|
||||
}
|
||||
int lua_TempFix(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
Client* c = GetClient(ID);
|
||||
if (c == nullptr)
|
||||
return 0;
|
||||
std::string Ret;
|
||||
if (c->isGuest) {
|
||||
Ret = "Guest-" + c->GetName();
|
||||
} else
|
||||
Ret = c->GetName();
|
||||
lua_pushstring(L, Ret.c_str());
|
||||
} else
|
||||
SendError(L, "GetDID not enough arguments");
|
||||
return 1;
|
||||
}
|
||||
void Lua::Init() {
|
||||
Assert(luaState);
|
||||
luaL_openlibs(luaState);
|
||||
lua_register(luaState, "TriggerGlobalEvent", lua_TriggerEventG);
|
||||
lua_register(luaState, "TriggerLocalEvent", lua_TriggerEventL);
|
||||
lua_register(luaState, "TriggerClientEvent", lua_RemoteEvent);
|
||||
lua_register(luaState, "GetPlayerCount", lua_GetPlayerCount);
|
||||
lua_register(luaState, "isPlayerConnected", lua_isConnected);
|
||||
lua_register(luaState, "RegisterEvent", lua_RegisterEvent);
|
||||
lua_register(luaState, "GetPlayerName", lua_GetPlayerName);
|
||||
lua_register(luaState, "RemoveVehicle", lua_RemoveVehicle);
|
||||
lua_register(luaState, "GetPlayerDiscordID", lua_TempFix);
|
||||
lua_register(luaState, "CreateThread", lua_CreateThread);
|
||||
lua_register(luaState, "GetPlayerVehicles", lua_GetCars);
|
||||
lua_register(luaState, "SendChatMessage", lua_sendChat);
|
||||
lua_register(luaState, "GetPlayers", lua_GetAllPlayers);
|
||||
lua_register(luaState, "GetPlayerGuest", lua_GetGuest);
|
||||
lua_register(luaState, "StopThread", lua_StopThread);
|
||||
lua_register(luaState, "DropPlayer", lua_dropPlayer);
|
||||
lua_register(luaState, "GetPlayerHWID", lua_HWID);
|
||||
lua_register(luaState, "exit", lua_ServerExit);
|
||||
lua_register(luaState, "Sleep", lua_Sleep);
|
||||
lua_register(luaState, "print", lua_Print);
|
||||
lua_register(luaState, "Set", lua_Set);
|
||||
if (!_Console)
|
||||
Reload();
|
||||
}
|
||||
|
||||
void Lua::RegisterEvent(const std::string& Event, const std::string& FunctionName) {
|
||||
_RegisteredEvents.insert(std::make_pair(Event, FunctionName));
|
||||
}
|
||||
void Lua::UnRegisterEvent(const std::string& Event) {
|
||||
for (const std::pair<std::string, std::string>& a : _RegisteredEvents) {
|
||||
if (a.first == Event) {
|
||||
_RegisteredEvents.erase(a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Lua::IsRegistered(const std::string& Event) {
|
||||
for (const std::pair<std::string, std::string>& a : _RegisteredEvents) {
|
||||
if (a.first == Event)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::string Lua::GetRegistered(const std::string& Event) const {
|
||||
for (const std::pair<std::string, std::string>& a : _RegisteredEvents) {
|
||||
if (a.first == Event)
|
||||
return a.second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string Lua::GetFileName() const {
|
||||
return _FileName;
|
||||
}
|
||||
std::string Lua::GetPluginName() const {
|
||||
return _PluginName;
|
||||
}
|
||||
lua_State* Lua::GetState() {
|
||||
return luaState;
|
||||
}
|
||||
|
||||
const lua_State* Lua::GetState() const {
|
||||
return luaState;
|
||||
}
|
||||
|
||||
void Lua::SetLastWrite(fs::file_time_type time) {
|
||||
_LastWrote = time;
|
||||
}
|
||||
fs::file_time_type Lua::GetLastWrite() {
|
||||
return _LastWrote;
|
||||
}
|
||||
|
||||
Lua::~Lua() {
|
||||
info("closing lua state");
|
||||
lua_close(luaState);
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/31/2020
|
||||
///
|
||||
|
||||
#include "Curl/Http.h"
|
||||
#include "Json.h"
|
||||
#include "Logger.h"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <Lua/LuaSystem.hpp>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
std::string GetClientInfo(const std::string& PK) {
|
||||
if (!PK.empty()) {
|
||||
return PostHTTP("https://auth.beammp.com/pkToUser", R"({"key":")" + PK + "\"}", true);
|
||||
;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Client* CreateClient(SOCKET TCPSock) {
|
||||
auto* c = new Client;
|
||||
c->SetTCPSock(TCPSock);
|
||||
return c;
|
||||
}
|
||||
|
||||
void ClientKick(Client* c, const std::string& R) {
|
||||
info("Client kicked: " + R);
|
||||
TCPSend(c, "E" + R);
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
}
|
||||
|
||||
void Authentication(SOCKET TCPSock) {
|
||||
DebugPrintTID();
|
||||
auto* c = CreateClient(TCPSock);
|
||||
|
||||
info("Identifying new client...");
|
||||
std::string Rc = TCPRcv(c);
|
||||
|
||||
if (Rc.size() > 3 && Rc.substr(0, 2) == "VC") {
|
||||
Rc = Rc.substr(2);
|
||||
if (Rc.length() > 4 || Rc != GetCVer()) {
|
||||
ClientKick(c, "Outdated Version!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ClientKick(c, "Invalid version header!");
|
||||
return;
|
||||
}
|
||||
TCPSend(c, "S");
|
||||
|
||||
Rc = TCPRcv(c);
|
||||
|
||||
if (Rc.size() > 50) {
|
||||
ClientKick(c, "Invalid Key!");
|
||||
return;
|
||||
}
|
||||
|
||||
Rc = GetClientInfo(Rc);
|
||||
json::Document d;
|
||||
d.Parse(Rc.c_str());
|
||||
if (Rc == "-1" || d.HasParseError()) {
|
||||
ClientKick(c, "Invalid key! Please restart your game.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (d["username"].IsString() && d["roles"].IsString() && d["guest"].IsBool()) {
|
||||
c->SetName(d["username"].GetString());
|
||||
c->SetRoles(d["roles"].GetString());
|
||||
c->isGuest = d["guest"].GetBool();
|
||||
} else {
|
||||
ClientKick(c, "Invalid authentication data!");
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Name -> " + c->GetName() + ", Guest -> " + std::to_string(c->isGuest) + ", Roles -> " + c->GetRoles());
|
||||
for (auto& Cl : CI->Clients) {
|
||||
if (Cl != nullptr) {
|
||||
if (Cl->GetName() == c->GetName() && Cl->isGuest == c->isGuest) {
|
||||
info("Old client (" + Cl->GetName() + ") kicked: Reconnecting");
|
||||
CloseSocketProper(Cl->GetTCPSock());
|
||||
Cl->SetStatus(-2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto arg = std::make_unique<LuaArg>(LuaArg { { c->GetName(), c->GetRoles(), c->isGuest } });
|
||||
std::any Res = TriggerLuaEvent("onPlayerAuth", false, nullptr, std::move(arg), true);
|
||||
std::string Type = Res.type().name();
|
||||
if (Type.find("int") != std::string::npos && std::any_cast<int>(Res)) {
|
||||
ClientKick(c, "you are not allowed on the server!");
|
||||
return;
|
||||
} else if (Type.find("string") != std::string::npos) {
|
||||
ClientKick(c, std::any_cast<std::string>(Res));
|
||||
return;
|
||||
}
|
||||
if (CI->Size() < MaxPlayers) {
|
||||
info("Identification success");
|
||||
Client& Client = *c;
|
||||
CI->AddClient(std::move(c));
|
||||
TCPClient(&Client);
|
||||
} else
|
||||
ClientKick(c, "Server full!");
|
||||
}
|
||||
|
||||
void HandleDownload(SOCKET TCPSock) {
|
||||
char D;
|
||||
if (recv(TCPSock, &D, 1, 0) != 1) {
|
||||
CloseSocketProper(TCPSock);
|
||||
return;
|
||||
}
|
||||
auto ID = uint8_t(D);
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c->GetID() == ID) {
|
||||
c->SetDownSock(TCPSock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Identify(SOCKET TCPSock) {
|
||||
char Code;
|
||||
if (recv(TCPSock, &Code, 1, 0) != 1) {
|
||||
CloseSocketProper(TCPSock);
|
||||
return;
|
||||
}
|
||||
if (Code == 'C') {
|
||||
Authentication(TCPSock);
|
||||
} else if (Code == 'D') {
|
||||
HandleDownload(TCPSock);
|
||||
} else
|
||||
CloseSocketProper(TCPSock);
|
||||
}
|
||||
|
||||
void TCPServerMain() {
|
||||
DebugPrintTID();
|
||||
#ifdef WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(514, &wsaData)) {
|
||||
error("Can't start Winsock!");
|
||||
return;
|
||||
}
|
||||
SOCKET client, Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
sockaddr_in addr {};
|
||||
addr.sin_addr.S_un.S_addr = ADDR_ANY;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(Port);
|
||||
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
error("Can't bind socket! " + std::to_string(WSAGetLastError()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
_Exit(-1);
|
||||
}
|
||||
if (Listener == -1) {
|
||||
error("Invalid listening socket");
|
||||
return;
|
||||
}
|
||||
if (listen(Listener, SOMAXCONN)) {
|
||||
error("listener failed " + std::to_string(GetLastError()));
|
||||
return;
|
||||
}
|
||||
info("Vehicle event network online");
|
||||
do {
|
||||
try {
|
||||
client = accept(Listener, nullptr, nullptr);
|
||||
if (client == -1) {
|
||||
warn("Got an invalid client socket on connect! Skipping...");
|
||||
continue;
|
||||
}
|
||||
std::thread ID(Identify, client);
|
||||
ID.detach();
|
||||
} catch (const std::exception& e) {
|
||||
error("fatal: " + std::string(e.what()));
|
||||
}
|
||||
} while (client);
|
||||
|
||||
CloseSocketProper(client);
|
||||
WSACleanup();
|
||||
#else // unix
|
||||
// wondering why we need slightly different implementations of this?
|
||||
// ask ms.
|
||||
SOCKET client = -1, Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
int optval = 1;
|
||||
setsockopt(Listener, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
|
||||
// TODO: check optval or return value idk
|
||||
sockaddr_in addr {};
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(uint16_t(Port));
|
||||
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
error(("Can't bind socket! ") + std::string(strerror(errno)));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
_Exit(-1);
|
||||
}
|
||||
if (Listener == -1) {
|
||||
error(("Invalid listening socket"));
|
||||
return;
|
||||
}
|
||||
if (listen(Listener, SOMAXCONN)) {
|
||||
error(("listener failed ") + std::string(strerror(errno)));
|
||||
return;
|
||||
}
|
||||
info(("Vehicle event network online"));
|
||||
do {
|
||||
try {
|
||||
client = accept(Listener, nullptr, nullptr);
|
||||
if (client == -1) {
|
||||
warn(("Got an invalid client socket on connect! Skipping..."));
|
||||
continue;
|
||||
}
|
||||
std::thread ID(Identify, client);
|
||||
ID.detach();
|
||||
} catch (const std::exception& e) {
|
||||
error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
} while (client);
|
||||
|
||||
debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
|
||||
CloseSocketProper(client);
|
||||
#endif // WIN32
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/8/2020
|
||||
///
|
||||
#include "Client.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
void Client::DeleteCar(int ident) {
|
||||
for (auto& v : VehicleData) {
|
||||
if (v != nullptr && v->ID == ident) {
|
||||
VehicleData.erase(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Client::ClearCars() {
|
||||
VehicleData.clear();
|
||||
}
|
||||
int Client::GetOpenCarID() {
|
||||
int OpenID = 0;
|
||||
bool found;
|
||||
do {
|
||||
found = true;
|
||||
for (auto& v : VehicleData) {
|
||||
if (v != nullptr && v->ID == OpenID) {
|
||||
OpenID++;
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
} while (!found);
|
||||
return OpenID;
|
||||
}
|
||||
void Client::AddNewCar(int ident, const std::string& Data) {
|
||||
VehicleData.insert(std::make_unique<VData>(VData { ident, Data }));
|
||||
}
|
||||
|
||||
std::set<std::unique_ptr<VData>>& Client::GetAllCars() {
|
||||
return VehicleData;
|
||||
}
|
||||
|
||||
std::string Client::GetCarData(int ident) {
|
||||
for (auto& v : VehicleData) {
|
||||
if (v != nullptr && v->ID == ident) {
|
||||
return v->Data;
|
||||
}
|
||||
}
|
||||
DeleteCar(ident);
|
||||
return "";
|
||||
}
|
||||
|
||||
void Client::SetCarData(int ident, const std::string& Data) {
|
||||
for (auto& v : VehicleData) {
|
||||
if (v != nullptr && v->ID == ident) {
|
||||
v->Data = Data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
DeleteCar(ident);
|
||||
}
|
||||
int Client::GetCarCount() {
|
||||
return int(VehicleData.size());
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 8/1/2020
|
||||
///
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "Logger.h"
|
||||
#include "Lua/LuaSystem.hpp"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#undef GetObject //to fix microsoft bs
|
||||
#include "Json.h"
|
||||
|
||||
void Apply(Client* c, int VID, const std::string& pckt) {
|
||||
Assert(c);
|
||||
std::string Packet = pckt.substr(pckt.find('{')), VD = c->GetCarData(VID);
|
||||
std::string Header = VD.substr(0, VD.find('{'));
|
||||
VD = VD.substr(VD.find('{'));
|
||||
rapidjson::Document Veh, Pack;
|
||||
Veh.Parse(VD.c_str());
|
||||
if (Veh.HasParseError()) {
|
||||
error("Could not get vehicle config!");
|
||||
return;
|
||||
}
|
||||
Pack.Parse(Packet.c_str());
|
||||
if (Pack.HasParseError() || Pack.IsNull()) {
|
||||
error("Could not get active vehicle config!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& M : Pack.GetObject()) {
|
||||
if (Veh[M.name].IsNull()) {
|
||||
Veh.AddMember(M.name, M.value, Veh.GetAllocator());
|
||||
} else {
|
||||
Veh[M.name] = Pack[M.name];
|
||||
}
|
||||
}
|
||||
rapidjson::StringBuffer Buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(Buffer);
|
||||
Veh.Accept(writer);
|
||||
c->SetCarData(VID, Header + Buffer.GetString());
|
||||
}
|
||||
|
||||
void VehicleParser(Client* c, const std::string& Pckt) {
|
||||
Assert(c);
|
||||
if (c == nullptr || Pckt.length() < 4)
|
||||
return;
|
||||
std::string Packet = Pckt;
|
||||
char Code = Packet.at(1);
|
||||
int PID = -1;
|
||||
int VID = -1;
|
||||
std::string Data = Packet.substr(3), pid, vid;
|
||||
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
|
||||
case 's':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
if (Data.at(0) == '0') {
|
||||
int CarID = c->GetOpenCarID();
|
||||
debug(c->GetName() + (" created a car with ID ") + std::to_string(CarID));
|
||||
Packet = "Os:" + c->GetRoles() + ":" + c->GetName() + ":" + std::to_string(c->GetID()) + "-" + std::to_string(CarID) + Packet.substr(4);
|
||||
auto Res = TriggerLuaEvent(("onVehicleSpawn"), false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID(), CarID, Packet.substr(3) } }), true);
|
||||
if (c->GetCarCount() >= MaxCars || std::any_cast<int>(Res)) {
|
||||
Respond(c, Packet, true);
|
||||
std::string Destroy = "Od:" + std::to_string(c->GetID()) + "-" + std::to_string(CarID);
|
||||
Respond(c, Destroy, true);
|
||||
debug(c->GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
|
||||
} else {
|
||||
c->AddNewCar(CarID, Packet);
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'c':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
pid = Data.substr(0, Data.find('-'));
|
||||
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1);
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
PID = stoi(pid);
|
||||
VID = stoi(vid);
|
||||
}
|
||||
if (PID != -1 && VID != -1 && PID == c->GetID()) {
|
||||
auto Res = TriggerLuaEvent(("onVehicleEdited"), false, nullptr,
|
||||
std::make_unique<LuaArg>(LuaArg { { c->GetID(), VID, Packet.substr(3) } }),
|
||||
true);
|
||||
if (!std::any_cast<int>(Res)) {
|
||||
SendToAll(c, Packet, false, true);
|
||||
Apply(c, VID, Packet);
|
||||
} else {
|
||||
std::string Destroy = "Od:" + std::to_string(c->GetID()) + "-" + std::to_string(VID);
|
||||
Respond(c, Destroy, true);
|
||||
c->DeleteCar(VID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'd':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
pid = Data.substr(0, Data.find('-'));
|
||||
vid = Data.substr(Data.find('-') + 1);
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
PID = stoi(pid);
|
||||
VID = stoi(vid);
|
||||
}
|
||||
if (PID != -1 && VID != -1 && PID == c->GetID()) {
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
TriggerLuaEvent(("onVehicleDeleted"), false, nullptr,
|
||||
std::make_unique<LuaArg>(LuaArg { { c->GetID(), VID } }), false);
|
||||
c->DeleteCar(VID);
|
||||
debug(c->GetName() + (" deleted car with ID ") + std::to_string(VID));
|
||||
}
|
||||
return;
|
||||
case 'r':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
SendToAll(c, Packet, false, true);
|
||||
return;
|
||||
case 't':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
SendToAll(c, Packet, false, true);
|
||||
return;
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
warn(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
||||
#endif // DEBUG
|
||||
return;
|
||||
}
|
||||
}
|
||||
void SyncClient(Client* c) {
|
||||
Assert(c);
|
||||
if (c->isSynced)
|
||||
return;
|
||||
c->isSynced = true;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
Respond(c, ("Sn") + c->GetName(), true);
|
||||
SendToAll(c, ("JWelcome ") + c->GetName() + "!", false, true);
|
||||
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID() } }), false);
|
||||
for (auto& client : CI->Clients) {
|
||||
if (client != nullptr) {
|
||||
if (client.get() != c) {
|
||||
for (auto& v : client->GetAllCars()) {
|
||||
if (v != nullptr) {
|
||||
if (c->GetStatus() < 0)
|
||||
return;
|
||||
Respond(c, v->Data, true);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info(c->GetName() + (" is now synced!"));
|
||||
}
|
||||
void ParseVeh(Client* c, const std::string& Packet) {
|
||||
Assert(c);
|
||||
#ifdef WIN32
|
||||
__try {
|
||||
VehicleParser(c, Packet);
|
||||
} __except (Handle(GetExceptionInformation(), ("Vehicle Handler"))) { }
|
||||
#else // unix
|
||||
VehicleParser(c, Packet);
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
void HandleEvent(Client* c, const std::string& Data) {
|
||||
Assert(c);
|
||||
std::stringstream ss(Data);
|
||||
std::string t, Name;
|
||||
int a = 0;
|
||||
while (std::getline(ss, t, ':')) {
|
||||
switch (a) {
|
||||
case 1:
|
||||
Name = t;
|
||||
break;
|
||||
case 2:
|
||||
TriggerLuaEvent(Name, false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID(), t } }), false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (a == 2)
|
||||
break;
|
||||
a++;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalParser(Client* c, const std::string& Packet) {
|
||||
Assert(c);
|
||||
if (Packet.empty() || c == nullptr)
|
||||
return;
|
||||
std::any Res;
|
||||
char Code = Packet.at(0);
|
||||
|
||||
//V to Z
|
||||
if (Code <= 90 && Code >= 86) {
|
||||
PPS++;
|
||||
SendToAll(c, Packet, false, false);
|
||||
return;
|
||||
}
|
||||
switch (Code) {
|
||||
case 'H': // initial connection
|
||||
#ifdef DEBUG
|
||||
debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
|
||||
#endif
|
||||
SyncClient(c);
|
||||
return;
|
||||
case 'p':
|
||||
Respond(c, ("p"), false);
|
||||
UpdatePlayers();
|
||||
return;
|
||||
case 'O':
|
||||
if (Packet.length() > 1000) {
|
||||
debug(("Received data from: ") + c->GetName() + (" Size: ") + std::to_string(Packet.length()));
|
||||
}
|
||||
ParseVeh(c, Packet);
|
||||
return;
|
||||
case 'J':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
SendToAll(c, Packet, false, true);
|
||||
return;
|
||||
case 'C':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
|
||||
break;
|
||||
Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID(), c->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true);
|
||||
if (std::any_cast<int>(Res))
|
||||
break;
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
return;
|
||||
case 'E':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
HandleEvent(c, Packet);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GParser(Client* c, const std::string& Packet) {
|
||||
Assert(c);
|
||||
if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
|
||||
abort();
|
||||
}
|
||||
#ifdef WIN32
|
||||
__try {
|
||||
GlobalParser(c, Packet);
|
||||
} __except (Handle(GetExceptionInformation(), ("Global Handler"))) { }
|
||||
#else
|
||||
GlobalParser(c, Packet);
|
||||
#endif // WIN32
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 4/9/2020
|
||||
///
|
||||
|
||||
#include "CustomAssert.h"
|
||||
#include <curl/curl.h>
|
||||
#include <iostream>
|
||||
|
||||
class CurlManager{
|
||||
public:
|
||||
CurlManager(){
|
||||
curl = curl_easy_init();
|
||||
}
|
||||
~CurlManager(){
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
inline CURL* Get(){
|
||||
return curl;
|
||||
}
|
||||
private:
|
||||
CURL *curl;
|
||||
};
|
||||
|
||||
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
|
||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
std::string HttpRequest(const std::string& IP, int port) {
|
||||
static thread_local CurlManager M;
|
||||
std::string readBuffer;
|
||||
CURL* curl = M.Get();
|
||||
CURLcode res;
|
||||
Assert(curl);
|
||||
if (curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_PORT, port);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
return "-1";
|
||||
}
|
||||
return readBuffer;
|
||||
}
|
||||
|
||||
std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json) {
|
||||
auto header = curl_slist { (char*)"Content-Type: application/json" };
|
||||
static thread_local CurlManager M;
|
||||
static std::mutex Lock;
|
||||
std::scoped_lock Guard(Lock);
|
||||
CURL* curl = M.Get();
|
||||
CURLcode res;
|
||||
std::string readBuffer;
|
||||
|
||||
Assert(curl);
|
||||
if (curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
|
||||
if (json)
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
|
||||
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
return "-1";
|
||||
}
|
||||
return readBuffer;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 8/1/2020
|
||||
///
|
||||
#include "Client.hpp"
|
||||
#include "Logger.h"
|
||||
#include "Lua/LuaSystem.hpp"
|
||||
#include "Network.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <memory>
|
||||
|
||||
int OpenID() {
|
||||
int ID = 0;
|
||||
bool found;
|
||||
do {
|
||||
found = true;
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr) {
|
||||
if (c->GetID() == ID) {
|
||||
found = false;
|
||||
ID++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!found);
|
||||
return ID;
|
||||
}
|
||||
void Respond(Client* c, const std::string& MSG, bool Rel) {
|
||||
Assert(c);
|
||||
char C = MSG.at(0);
|
||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||
if (C == 'O' || C == 'T' || MSG.length() > 1000) {
|
||||
SendLarge(c, MSG);
|
||||
} else {
|
||||
TCPSend(c, MSG);
|
||||
}
|
||||
} else {
|
||||
UDPSend(c, MSG);
|
||||
}
|
||||
}
|
||||
void SendToAll(Client* c, const std::string& Data, bool Self, bool Rel) {
|
||||
if (!Self)
|
||||
Assert(c);
|
||||
char C = Data.at(0);
|
||||
for (auto& client : CI->Clients) {
|
||||
if (client != nullptr) {
|
||||
if (Self || client.get() != c) {
|
||||
if (client->isSynced) {
|
||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||
if (C == 'O' || C == 'T' || Data.length() > 1000)
|
||||
SendLarge(client.get(), Data);
|
||||
else
|
||||
TCPSend(client.get(), Data);
|
||||
} else
|
||||
UDPSend(client.get(), Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void UpdatePlayers() {
|
||||
std::string Packet = ("Ss") + std::to_string(CI->Size()) + "/" + std::to_string(MaxPlayers) + ":";
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr)
|
||||
Packet += c->GetName() + ",";
|
||||
}
|
||||
Packet = Packet.substr(0, Packet.length() - 1);
|
||||
SendToAll(nullptr, Packet, true, true);
|
||||
}
|
||||
void OnDisconnect(Client* c, bool kicked) {
|
||||
Assert(c);
|
||||
info(c->GetName() + (" Connection Terminated"));
|
||||
std::string Packet;
|
||||
for (auto& v : c->GetAllCars()) {
|
||||
if (v != nullptr) {
|
||||
Packet = "Od:" + std::to_string(c->GetID()) + "-" + std::to_string(v->ID);
|
||||
SendToAll(c, Packet, false, true);
|
||||
}
|
||||
}
|
||||
if (kicked)
|
||||
Packet = ("L") + c->GetName() + (" was kicked!");
|
||||
else
|
||||
Packet = ("L") + c->GetName() + (" left the server!");
|
||||
SendToAll(c, Packet, false, true);
|
||||
Packet.clear();
|
||||
TriggerLuaEvent(("onPlayerDisconnect"), false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID() } }), false);
|
||||
if (c->GetTCPSock())
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
if (c->GetDownSock())
|
||||
CloseSocketProper(c->GetDownSock());
|
||||
CI->RemoveClient(c);
|
||||
}
|
||||
void OnConnect(Client* c) {
|
||||
Assert(c);
|
||||
info("Client connected");
|
||||
c->SetID(OpenID());
|
||||
info("Assigned ID " + std::to_string(c->GetID()) + " to " + c->GetName());
|
||||
TriggerLuaEvent("onPlayerConnecting", false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID() } }), false);
|
||||
SyncResources(c);
|
||||
if (c->GetStatus() < 0)
|
||||
return;
|
||||
Respond(c, "M" + MapName, true); //Send the Map on connect
|
||||
info(c->GetName() + " : Connected");
|
||||
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<LuaArg>(LuaArg { { c->GetID() } }), false);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#include "Network.h"
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
std::unique_ptr<ClientInterface> CI;
|
||||
void NetMain() {
|
||||
std::thread TCP(TCPServerMain);
|
||||
TCP.detach();
|
||||
UDPServerMain();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 6/18/2020
|
||||
///
|
||||
#include "Client.hpp"
|
||||
#include "Security/Enc.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
std::string StatReport;
|
||||
int PPS = 0;
|
||||
void Monitor() {
|
||||
int C = 0, V = 0;
|
||||
if (CI->Clients.empty()) {
|
||||
StatReport = "-";
|
||||
return;
|
||||
}
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr && c->GetCarCount() > 0) {
|
||||
C++;
|
||||
V += c->GetCarCount();
|
||||
}
|
||||
}
|
||||
if (C == 0 || PPS == 0) {
|
||||
StatReport = "-";
|
||||
} else {
|
||||
int R = (PPS / C) / V;
|
||||
StatReport = std::to_string(R);
|
||||
}
|
||||
PPS = 0;
|
||||
}
|
||||
|
||||
[[noreturn]] void Stat() {
|
||||
DebugPrintTID();
|
||||
while (true) {
|
||||
Monitor();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void StatInit() {
|
||||
StatReport = "-";
|
||||
std::thread Init(Stat);
|
||||
Init.detach();
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 8/1/2020
|
||||
///
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "Logger.h"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
bool TCPSendRaw(SOCKET C, char* Data, int32_t Size) {
|
||||
int64_t Sent = 0;
|
||||
do {
|
||||
int64_t Temp = send(C, &Data[Sent], int(Size - Sent), 0);
|
||||
if (Temp < 1) {
|
||||
info("Socket Closed! " + std::to_string(C));
|
||||
CloseSocketProper(C);
|
||||
return false;
|
||||
}
|
||||
Sent += Temp;
|
||||
} while (Sent < Size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SplitLoad(Client* c, int64_t Sent, int64_t Size, bool D, const std::string& Name) {
|
||||
std::ifstream f(Name.c_str(), std::ios::binary);
|
||||
int32_t Split = 0x7735940; //125MB
|
||||
char* Data;
|
||||
if (Size > Split)
|
||||
Data = new char[Split];
|
||||
else
|
||||
Data = new char[Size];
|
||||
SOCKET TCPSock;
|
||||
if (D)
|
||||
TCPSock = c->GetDownSock();
|
||||
else
|
||||
TCPSock = c->GetTCPSock();
|
||||
info("Split load Socket " + std::to_string(TCPSock));
|
||||
while (c->GetStatus() > -1 && Sent < Size) {
|
||||
int64_t Diff = Size - Sent;
|
||||
if (Diff > Split) {
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(Data, Split);
|
||||
if (!TCPSendRaw(TCPSock, Data, Split)) {
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
break;
|
||||
}
|
||||
Sent += Split;
|
||||
} else {
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(Data, Diff);
|
||||
if (!TCPSendRaw(TCPSock, Data, int32_t(Diff))) {
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
break;
|
||||
}
|
||||
Sent += Diff;
|
||||
}
|
||||
}
|
||||
delete[] Data;
|
||||
f.close();
|
||||
}
|
||||
|
||||
void SendFile(Client* c, const std::string& Name) {
|
||||
Assert(c);
|
||||
info(c->GetName() + " requesting : " + Name.substr(Name.find_last_of('/')));
|
||||
|
||||
if (!std::filesystem::exists(Name)) {
|
||||
TCPSend(c, "CO");
|
||||
warn("File " + Name + " could not be accessed!");
|
||||
return;
|
||||
} else
|
||||
TCPSend(c, "AG");
|
||||
|
||||
///Wait for connections
|
||||
int T = 0;
|
||||
while (c->GetDownSock() < 1 && T < 50) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
T++;
|
||||
}
|
||||
|
||||
if (c->GetDownSock() < 1) {
|
||||
error("Client doesn't have a download socket!");
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t Size = std::filesystem::file_size(Name), MSize = Size / 2;
|
||||
|
||||
std::thread Dt(SplitLoad, c, 0, MSize, false, Name);
|
||||
Dt.detach();
|
||||
|
||||
SplitLoad(c, MSize, Size, true, Name);
|
||||
|
||||
if (Dt.joinable())
|
||||
Dt.join();
|
||||
}
|
||||
|
||||
void Parse(Client* c, const std::string& Packet) {
|
||||
Assert(c);
|
||||
if (c == nullptr || Packet.empty())
|
||||
return;
|
||||
char Code = Packet.at(0), SubCode = 0;
|
||||
if (Packet.length() > 1)
|
||||
SubCode = Packet.at(1);
|
||||
switch (Code) {
|
||||
case 'f':
|
||||
SendFile(c, Packet.substr(1));
|
||||
return;
|
||||
case 'S':
|
||||
if (SubCode == 'R') {
|
||||
debug("Sending Mod Info");
|
||||
std::string ToSend = FileList + FileSizes;
|
||||
if (ToSend.empty())
|
||||
ToSend = "-";
|
||||
TCPSend(c, ToSend);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SyncResources(Client* c) {
|
||||
Assert(c);
|
||||
if (c == nullptr)
|
||||
return;
|
||||
#ifndef DEBUG
|
||||
try {
|
||||
#endif
|
||||
TCPSend(c, "P" + std::to_string(c->GetID()));
|
||||
std::string Data;
|
||||
while (c->GetStatus() > -1) {
|
||||
Data = TCPRcv(c);
|
||||
if (Data == "Done")
|
||||
break;
|
||||
Parse(c, Data);
|
||||
}
|
||||
#ifndef DEBUG
|
||||
} catch (std::exception& e) {
|
||||
except("Exception! : " + std::string(e.what()));
|
||||
c->SetStatus(-1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 8/1/2020
|
||||
///
|
||||
#include "Compressor.h"
|
||||
#include "Logger.h"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <thread>
|
||||
|
||||
bool TCPSend(Client* c, const std::string& Data) {
|
||||
Assert(c);
|
||||
if (c == nullptr)
|
||||
return false;
|
||||
|
||||
int32_t Size, Sent;
|
||||
std::string Send(4, 0);
|
||||
Size = int32_t(Data.size());
|
||||
memcpy(&Send[0], &Size, sizeof(Size));
|
||||
Send += Data;
|
||||
Sent = 0;
|
||||
Size += 4;
|
||||
do {
|
||||
int32_t Temp = send(c->GetTCPSock(), &Send[Sent], Size - Sent, 0);
|
||||
if (Temp == 0) {
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
return false;
|
||||
} else if (Temp < 0) {
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
return false;
|
||||
}
|
||||
Sent += Temp;
|
||||
} while (Sent < Size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckBytes(Client* c, int32_t BytesRcv) {
|
||||
Assert(c);
|
||||
if (BytesRcv == 0) {
|
||||
debug("(TCP) Connection closing...");
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
return false;
|
||||
} else if (BytesRcv < 0) {
|
||||
#ifdef WIN32
|
||||
debug(("(TCP) recv failed with error: ") + std::to_string(WSAGetLastError()));
|
||||
#else // unix
|
||||
debug(("(TCP) recv failed with error: ") + std::string(strerror(errno)));
|
||||
#endif // WIN32
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
info(("Closing socket in CheckBytes, BytesRcv < 0"));
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string TCPRcv(Client* c) {
|
||||
Assert(c);
|
||||
int32_t Header, BytesRcv = 0, Temp;
|
||||
if (c == nullptr || c->GetStatus() < 0)
|
||||
return "";
|
||||
|
||||
std::vector<char> Data(sizeof(Header));
|
||||
do {
|
||||
Temp = recv(c->GetTCPSock(), &Data[BytesRcv], 4 - BytesRcv, 0);
|
||||
if (!CheckBytes(c, Temp)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < 4)"));
|
||||
#endif // DEBUG
|
||||
return "";
|
||||
}
|
||||
BytesRcv += Temp;
|
||||
} while (size_t(BytesRcv) < sizeof(Header));
|
||||
memcpy(&Header, &Data[0], sizeof(Header));
|
||||
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": expecting ") + std::to_string(Header) + (" bytes."));
|
||||
#endif // DEBUG
|
||||
if (!CheckBytes(c, BytesRcv)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes"));
|
||||
#endif // DEBUG
|
||||
return "";
|
||||
}
|
||||
Data.resize(Header);
|
||||
BytesRcv = 0;
|
||||
do {
|
||||
Temp = recv(c->GetTCPSock(), &Data[BytesRcv], Header - BytesRcv, 0);
|
||||
if (!CheckBytes(c, Temp)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < Header)"));
|
||||
#endif // DEBUG
|
||||
|
||||
return "";
|
||||
}
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
|
||||
#endif // DEBUG
|
||||
BytesRcv += Temp;
|
||||
} while (BytesRcv < Header);
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": finished recv with Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
|
||||
#endif // DEBUG
|
||||
std::string Ret(Data.data(), Header);
|
||||
|
||||
if (Ret.substr(0, 4) == "ABG:") {
|
||||
Ret = DeComp(Ret.substr(4));
|
||||
}
|
||||
#ifdef DEBUG
|
||||
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
|
||||
#endif
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void TCPClient(Client* c) {
|
||||
DebugPrintTIDInternal("Client(" + c->GetName() + ")", true);
|
||||
Assert(c);
|
||||
if (c->GetTCPSock() == -1) {
|
||||
CI->RemoveClient(c);
|
||||
return;
|
||||
}
|
||||
OnConnect(c);
|
||||
while (c->GetStatus() > -1) {
|
||||
GParser(c, TCPRcv(c));
|
||||
}
|
||||
OnDisconnect(c, c->GetStatus() == -2);
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/8/2020
|
||||
///
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "Compressor.h"
|
||||
#include "Logger.h"
|
||||
#include "Network.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include "UnixCompat.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
SOCKET UDPSock;
|
||||
void UDPSend(Client* c, std::string Data) {
|
||||
Assert(c);
|
||||
if (c == nullptr || !c->isConnected || c->GetStatus() < 0)
|
||||
return;
|
||||
sockaddr_in Addr = c->GetUDPAddr();
|
||||
socklen_t AddrSize = sizeof(c->GetUDPAddr());
|
||||
if (Data.length() > 400) {
|
||||
std::string CMP(Comp(Data));
|
||||
Data = "ABG:" + CMP;
|
||||
}
|
||||
#ifdef WIN32
|
||||
int sendOk;
|
||||
int len = static_cast<int>(Data.size());
|
||||
#else
|
||||
int64_t sendOk;
|
||||
size_t len = Data.size();
|
||||
#endif // WIN32
|
||||
|
||||
sendOk = sendto(UDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, AddrSize);
|
||||
#ifdef WIN32
|
||||
if (sendOk == -1) {
|
||||
debug(("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
} else if (sendOk == 0) {
|
||||
debug(("(UDP) sendto returned 0"));
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
}
|
||||
#else // unix
|
||||
if (sendOk == -1) {
|
||||
debug(("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
} else if (sendOk == 0) {
|
||||
debug(("(UDP) sendto returned 0"));
|
||||
if (c->GetStatus() > -1)
|
||||
c->SetStatus(-1);
|
||||
}
|
||||
#endif // WIN32
|
||||
}
|
||||
|
||||
void SendLarge(Client* c, std::string Data) {
|
||||
Assert(c);
|
||||
if (Data.length() > 400) {
|
||||
std::string CMP(Comp(Data));
|
||||
Data = "ABG:" + CMP;
|
||||
}
|
||||
TCPSend(c, Data);
|
||||
}
|
||||
|
||||
std::string UDPRcvFromClient(sockaddr_in& client) {
|
||||
size_t clientLength = sizeof(client);
|
||||
std::array<char, 1024> Ret{};
|
||||
int64_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size(), 0, (sockaddr*)&client, (socklen_t*)&clientLength);
|
||||
if (Rcv == -1) {
|
||||
#ifdef WIN32
|
||||
error(("(UDP) Error receiving from Client! Code : ") + std::to_string(WSAGetLastError()));
|
||||
#else // unix
|
||||
error(("(UDP) Error receiving from Client! Code : ") + std::string(strerror(errno)));
|
||||
#endif // WIN32
|
||||
return "";
|
||||
}
|
||||
return std::string(Ret.begin(), Ret.begin() + Rcv);
|
||||
}
|
||||
|
||||
void UDPParser(Client* c, std::string Packet) {
|
||||
if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
|
||||
abort();
|
||||
}
|
||||
Assert(c);
|
||||
if (Packet.substr(0, 4) == "ABG:") {
|
||||
Packet = DeComp(Packet.substr(4));
|
||||
}
|
||||
GParser(c, Packet);
|
||||
}
|
||||
|
||||
[[noreturn]] void UDPServerMain() {
|
||||
#ifdef WIN32
|
||||
WSADATA data;
|
||||
if (WSAStartup(514, &data)) {
|
||||
error(("Can't start Winsock!"));
|
||||
//return;
|
||||
}
|
||||
|
||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
// Create a server hint structure for the server
|
||||
sockaddr_in serverAddr {};
|
||||
serverAddr.sin_addr.S_un.S_addr = ADDR_ANY; //Any Local
|
||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||
serverAddr.sin_port = htons(Port); // Convert from little to big endian
|
||||
|
||||
// Try and bind the socket to the IP and port
|
||||
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
|
||||
error(("Can't bind socket!") + std::to_string(WSAGetLastError()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
_Exit(-1);
|
||||
//return;
|
||||
}
|
||||
|
||||
info(("Vehicle data network online on port ") + std::to_string(Port) + (" with a Max of ") + std::to_string(MaxPlayers) + (" Clients"));
|
||||
while (true) {
|
||||
try {
|
||||
sockaddr_in client {};
|
||||
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
||||
auto Pos = Data.find(':');
|
||||
if (Data.empty() || Pos == std::string::npos || Pos > 2)
|
||||
continue;
|
||||
/*char clientIp[256];
|
||||
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
||||
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
||||
uint8_t ID = Data.at(0) - 1;
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr && c->GetID() == ID) {
|
||||
c->SetUDPAddr(client);
|
||||
c->isConnected = true;
|
||||
UDPParser(c.get(), Data.substr(2));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
/*CloseSocketProper(UDPSock);
|
||||
WSACleanup();
|
||||
return;*/
|
||||
#else // unix
|
||||
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
// Create a server hint structure for the server
|
||||
sockaddr_in serverAddr {};
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local
|
||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||
serverAddr.sin_port = htons(uint16_t(Port)); // Convert from little to big endian
|
||||
|
||||
// Try and bind the socket to the IP and port
|
||||
if (bind(UDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
|
||||
error(("Can't bind socket!") + std::string(strerror(errno)));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
_Exit(-1);
|
||||
//return;
|
||||
}
|
||||
|
||||
info(("Vehicle data network online on port ") + std::to_string(Port) + (" with a Max of ") + std::to_string(MaxPlayers) + (" Clients"));
|
||||
while (true) {
|
||||
try {
|
||||
sockaddr_in client {};
|
||||
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
||||
size_t Pos = Data.find(':');
|
||||
if (Data.empty() || Pos > 2)
|
||||
continue;
|
||||
/*char clientIp[256];
|
||||
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
||||
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||
for (auto& c : CI->Clients) {
|
||||
if (c != nullptr && c->GetID() == ID) {
|
||||
c->SetUDPAddr(client);
|
||||
c->isConnected = true;
|
||||
UDPParser(c.get(), Data.substr(2));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
/*CloseSocketProper(UDPSock); // TODO: Why not this? We did this in TCPServerMain?
|
||||
return;
|
||||
*/
|
||||
#endif // WIN32
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 11/6/2020
|
||||
///
|
||||
/*#include <boost/beast/core.hpp>
|
||||
#include "Logger.h"
|
||||
#include "Security/Enc.h"
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>*/
|
||||
|
||||
/*namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace websocket = beast::websocket;
|
||||
namespace net = boost::asio;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
std::string GetRes(const beast::flat_buffer& buff) {
|
||||
return (char*)buff.data().data();
|
||||
}*/
|
||||
|
||||
void SyncData() {
|
||||
/*DebugPrintTID();
|
||||
try {
|
||||
std::string const host = ("95.216.35.232");
|
||||
|
||||
net::io_context ioc;
|
||||
tcp::resolver r(ioc);
|
||||
|
||||
websocket::stream<tcp::socket> ws(ioc);
|
||||
auto const results = r.resolve(host, ("3600"));
|
||||
net::connect(ws.next_layer(), results.begin(), results.end());
|
||||
|
||||
|
||||
ws.handshake(host, "/");
|
||||
beast::flat_buffer buffer;
|
||||
ws.write(boost::asio::buffer("Hello, world!"));
|
||||
ws.read(buffer);
|
||||
|
||||
std::cout << GetRes(buffer) << std::endl;
|
||||
|
||||
ws.close(websocket::close_code::normal);
|
||||
|
||||
}catch(std::exception const& e){
|
||||
error(e.what());
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
_Exit(0);
|
||||
}*/
|
||||
}
|
||||
|
||||
void WebsocketInit() {
|
||||
/*std::thread t1(SyncData);
|
||||
t1.detach();*/
|
||||
}
|
||||
98
src/SocketIO.cpp
Normal file
98
src/SocketIO.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "SocketIO.h"
|
||||
#include "Common.h"
|
||||
#include <iostream>
|
||||
|
||||
|
||||
//TODO Default disabled with config option
|
||||
static std::unique_ptr<SocketIO> SocketIOInstance = std::make_unique<SocketIO>();
|
||||
|
||||
SocketIO& SocketIO::Get() {
|
||||
return *SocketIOInstance;
|
||||
}
|
||||
|
||||
SocketIO::SocketIO() noexcept
|
||||
: mThread([this] { ThreadMain(); }) {
|
||||
|
||||
mClient.socket()->on("network", [&](sio::event&e) {
|
||||
if(e.get_message()->get_string() == "Welcome"){
|
||||
info("SocketIO Authenticated!");
|
||||
mAuthenticated = true;
|
||||
}
|
||||
});
|
||||
|
||||
mClient.socket()->on("welcome", [&](sio::event&) {
|
||||
info("Got welcome from backend! Authenticating SocketIO...");
|
||||
mClient.socket()->emit("onInitConnection", Application::Settings.Key);
|
||||
});
|
||||
|
||||
mClient.set_logs_quiet();
|
||||
mClient.set_reconnect_delay(10000);
|
||||
mClient.connect(Application::GetBackendUrlForSocketIO());
|
||||
}
|
||||
|
||||
SocketIO::~SocketIO() {
|
||||
mCloseThread.store(true);
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
|
||||
static constexpr auto EventNameFromEnum(SocketIOEvent Event) {
|
||||
switch (Event) {
|
||||
case SocketIOEvent::CPUUsage:
|
||||
return "cpu usage";
|
||||
case SocketIOEvent::MemoryUsage:
|
||||
return "memory usage";
|
||||
case SocketIOEvent::ConsoleOut:
|
||||
return "console out";
|
||||
case SocketIOEvent::NetworkUsage:
|
||||
return "network usage";
|
||||
case SocketIOEvent::PlayerList:
|
||||
return "player list";
|
||||
default:
|
||||
error("unreachable code reached (developer error)");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void SocketIO::Emit(SocketIOEvent Event, const std::string& Data) {
|
||||
if (!mAuthenticated) {
|
||||
debug("trying to emit a socket.io event when not yet authenticated");
|
||||
return;
|
||||
}
|
||||
std::string EventName = EventNameFromEnum(Event);
|
||||
debug("emitting event \"" + EventName + "\" with data: \"" + Data);
|
||||
std::unique_lock Lock(mQueueMutex);
|
||||
mQueue.push_back({EventName, Data });
|
||||
debug("queue now has " + std::to_string(mQueue.size()) + " events");
|
||||
}
|
||||
|
||||
void SocketIO::ThreadMain() {
|
||||
while (!mCloseThread.load()) {
|
||||
bool empty;
|
||||
{ // queue lock scope
|
||||
std::unique_lock Lock(mQueueMutex);
|
||||
empty = mQueue.empty();
|
||||
} // end queue lock scope
|
||||
if (empty || !mClient.opened()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
} else {
|
||||
Event TheEvent;
|
||||
{ // queue lock scope
|
||||
std::unique_lock Lock(mQueueMutex);
|
||||
TheEvent = mQueue.front();
|
||||
mQueue.pop_front();
|
||||
} // end queue lock scope
|
||||
debug("sending \"" + TheEvent.Name + "\" event");
|
||||
mClient.socket()->emit(TheEvent.Name, TheEvent.Data);
|
||||
debug("sent \"" + TheEvent.Name + "\" event");
|
||||
}
|
||||
}
|
||||
// using std::cout as this happens during static destruction and the logger might be dead already
|
||||
std::cout << "closing " + std::string(__func__) << std::endl;
|
||||
|
||||
mClient.sync_close();
|
||||
mClient.clear_con_listeners();
|
||||
|
||||
std::cout << "closed" << std::endl;
|
||||
}
|
||||
213
src/TConfig.cpp
Normal file
213
src/TConfig.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include <toml.hpp> // header-only version of TOML++
|
||||
|
||||
#include "TConfig.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
|
||||
static const char* ConfigFileName = static_cast<const char*>("ServerConfig.toml");
|
||||
|
||||
static constexpr std::string_view StrDebug = "Debug";
|
||||
static constexpr std::string_view StrPrivate = "Private";
|
||||
static constexpr std::string_view StrPort = "Port";
|
||||
static constexpr std::string_view StrMaxCars = "MaxCars";
|
||||
static constexpr std::string_view StrMaxPlayers = "MaxPlayers";
|
||||
static constexpr std::string_view StrMap = "Map";
|
||||
static constexpr std::string_view StrName = "Name";
|
||||
static constexpr std::string_view StrDescription = "Description";
|
||||
static constexpr std::string_view StrResourceFolder = "ResourceFolder";
|
||||
static constexpr std::string_view StrAuthKey = "AuthKey";
|
||||
|
||||
TConfig::TConfig() {
|
||||
if (!fs::exists(ConfigFileName) || !fs::is_regular_file(ConfigFileName)) {
|
||||
info("No config file found! Generating one...");
|
||||
CreateConfigFile(ConfigFileName);
|
||||
}
|
||||
if (!mFailed) {
|
||||
if (fs::exists("Server.cfg")) {
|
||||
warn("An old \"Server.cfg\" file still exists. Please note that this is no longer used. Instead, \"" + std::string(ConfigFileName) + "\" is used. You can safely delete the \"Server.cfg\".");
|
||||
}
|
||||
ParseFromFile(ConfigFileName);
|
||||
}
|
||||
}
|
||||
|
||||
void TConfig::CreateConfigFile(std::string_view name) {
|
||||
// build from old config Server.cfg
|
||||
|
||||
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) {
|
||||
error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
toml::table tbl { {
|
||||
|
||||
{ "General",
|
||||
toml::table { {
|
||||
|
||||
{ StrDebug, Application::Settings.DebugModeEnabled },
|
||||
{ StrPrivate, Application::Settings.Private },
|
||||
{ StrPort, Application::Settings.Port },
|
||||
{ StrMaxCars, Application::Settings.MaxCars },
|
||||
{ StrMaxPlayers, Application::Settings.MaxPlayers },
|
||||
{ StrMap, Application::Settings.MapName },
|
||||
{ StrName, Application::Settings.ServerName },
|
||||
{ StrDescription, Application::Settings.ServerDesc },
|
||||
{ StrResourceFolder, Application::Settings.Resource },
|
||||
{ StrAuthKey, Application::Settings.Key },
|
||||
|
||||
} } },
|
||||
|
||||
} };
|
||||
std::ofstream ofs { std::string(name) };
|
||||
if (ofs.good()) {
|
||||
ofs << "# This is the BeamMP-Server config file.\n"
|
||||
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
|
||||
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
|
||||
<< '\n';
|
||||
ofs << tbl << '\n';
|
||||
error("There was no \"" + std::string(ConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
|
||||
mFailed = true;
|
||||
} else {
|
||||
error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work.");
|
||||
mFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TConfig::ParseFromFile(std::string_view name) {
|
||||
try {
|
||||
toml::table FullTable = toml::parse_file(name);
|
||||
toml::table GeneralTable = *FullTable["General"].as_table();
|
||||
if (auto val = GeneralTable[StrDebug].value<bool>(); val.has_value()) {
|
||||
Application::Settings.DebugModeEnabled = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrDebug));
|
||||
}
|
||||
if (auto val = GeneralTable[StrPrivate].value<bool>(); val.has_value()) {
|
||||
Application::Settings.Private = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrPrivate));
|
||||
}
|
||||
if (auto val = GeneralTable[StrPort].value<int>(); val.has_value()) {
|
||||
Application::Settings.Port = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrPort));
|
||||
}
|
||||
if (auto val = GeneralTable[StrMaxCars].value<int>(); val.has_value()) {
|
||||
Application::Settings.MaxCars = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrMaxCars));
|
||||
}
|
||||
if (auto val = GeneralTable[StrMaxPlayers].value<int>(); val.has_value()) {
|
||||
Application::Settings.MaxPlayers = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrMaxPlayers));
|
||||
}
|
||||
if (auto val = GeneralTable[StrMap].value<std::string>(); val.has_value()) {
|
||||
Application::Settings.MapName = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrMap));
|
||||
}
|
||||
if (auto val = GeneralTable[StrName].value<std::string>(); val.has_value()) {
|
||||
Application::Settings.ServerName = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrName));
|
||||
}
|
||||
if (auto val = GeneralTable[StrDescription].value<std::string>(); val.has_value()) {
|
||||
Application::Settings.ServerDesc = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrDescription));
|
||||
}
|
||||
if (auto val = GeneralTable[StrResourceFolder].value<std::string>(); val.has_value()) {
|
||||
Application::Settings.Resource = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrResourceFolder));
|
||||
}
|
||||
if (auto val = GeneralTable[StrAuthKey].value<std::string>(); val.has_value()) {
|
||||
Application::Settings.Key = val.value();
|
||||
} else {
|
||||
throw std::runtime_error(std::string(StrAuthKey));
|
||||
}
|
||||
} catch (const std::exception& err) {
|
||||
error("Error parsing config file value: " + std::string(err.what()));
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
PrintDebug();
|
||||
// all good so far, let's check if there's a key
|
||||
if (Application::Settings.Key.empty()) {
|
||||
error("No AuthKey specified in the \"" + std::string(ConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
|
||||
mFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TConfig::PrintDebug() {
|
||||
debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
|
||||
debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false"));
|
||||
debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
|
||||
debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars));
|
||||
debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers));
|
||||
debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
|
||||
debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
|
||||
debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
|
||||
debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
|
||||
// special!
|
||||
debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
|
||||
}
|
||||
|
||||
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 {
|
||||
warn("unknown key in old auth file (ignored): " + Key);
|
||||
}
|
||||
Str >> std::ws;
|
||||
}
|
||||
}
|
||||
78
src/TConsole.cpp
Normal file
78
src/TConsole.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "TConsole.h"
|
||||
#include "Common.h"
|
||||
#include "Compat.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
std::string GetDate() {
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
time_t tt = std::chrono::system_clock::to_time_t(now);
|
||||
tm local_tm {};
|
||||
#ifdef WIN32
|
||||
localtime_s(&local_tm, &tt);
|
||||
#else // unix
|
||||
localtime_r(&tt, &local_tm);
|
||||
#endif // WIN32
|
||||
std::stringstream date;
|
||||
int S = local_tm.tm_sec;
|
||||
int M = local_tm.tm_min;
|
||||
int H = local_tm.tm_hour;
|
||||
std::string Secs = (S > 9 ? std::to_string(S) : "0" + std::to_string(S));
|
||||
std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M));
|
||||
std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H));
|
||||
date
|
||||
<< "["
|
||||
<< local_tm.tm_mday << "/"
|
||||
<< local_tm.tm_mon + 1 << "/"
|
||||
<< local_tm.tm_year + 1900 << " "
|
||||
<< Hour << ":"
|
||||
<< Min << ":"
|
||||
<< Secs
|
||||
<< "] ";
|
||||
/* TODO
|
||||
if (Debug) {
|
||||
date << ThreadName()
|
||||
<< " ";
|
||||
}
|
||||
*/
|
||||
return date.str();
|
||||
}
|
||||
|
||||
TConsole::TConsole() {
|
||||
mCommandline.enable_history();
|
||||
mCommandline.set_history_limit(20);
|
||||
mCommandline.set_prompt("> ");
|
||||
bool success = mCommandline.enable_write_to_file("Server.log");
|
||||
if (!success) {
|
||||
error("unable to open file for writing: \"Server.log\"");
|
||||
}
|
||||
mCommandline.on_command = [this](Commandline& c) {
|
||||
auto cmd = c.get_command();
|
||||
mCommandline.write("> " + cmd);
|
||||
if (cmd == "exit") {
|
||||
info("gracefully shutting down");
|
||||
Application::GracefullyShutdown();
|
||||
} else if (cmd == "clear" || cmd == "cls") {
|
||||
// TODO: clear screen
|
||||
} else {
|
||||
if (mLuaConsole) {
|
||||
mLuaConsole->Execute(cmd);
|
||||
} else {
|
||||
error("Lua subsystem not yet initialized, please wait a few seconds and try again");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void TConsole::Write(const std::string& str) {
|
||||
auto ToWrite = GetDate() + str;
|
||||
mCommandline.write(ToWrite);
|
||||
// TODO write to logfile, too
|
||||
}
|
||||
void TConsole::InitializeLuaConsole(TLuaEngine& Engine) {
|
||||
mLuaConsole = std::make_unique<TLuaFile>(Engine, true);
|
||||
}
|
||||
void TConsole::WriteRaw(const std::string& str) {
|
||||
mCommandline.write(str);
|
||||
}
|
||||
111
src/THeartbeatThread.cpp
Normal file
111
src/THeartbeatThread.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "THeartbeatThread.h"
|
||||
|
||||
#include "Client.h"
|
||||
#include "Http.h"
|
||||
//#include "SocketIO.h"
|
||||
#include <sstream>
|
||||
|
||||
void THeartbeatThread::operator()() {
|
||||
RegisterThread("Heartbeat");
|
||||
std::string Body;
|
||||
std::string T;
|
||||
|
||||
// these are "hot-change" related variables
|
||||
static std::string Last;
|
||||
|
||||
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
|
||||
bool isAuth = false;
|
||||
while (!mShutdown) {
|
||||
Body = GenerateCall();
|
||||
// a hot-change occurs when a setting has changed, to update the backend of that change.
|
||||
auto Now = std::chrono::high_resolution_clock::now();
|
||||
bool Unchanged = Last == Body;
|
||||
auto TimePassed = (Now - LastNormalUpdateTime);
|
||||
auto Threshold = Unchanged ? 30 : 5;
|
||||
if (TimePassed < std::chrono::seconds(Threshold)) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
debug("heartbeat @ " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()));
|
||||
#endif // DEBUG
|
||||
|
||||
Last = Body;
|
||||
LastNormalUpdateTime = Now;
|
||||
if (!Application::Settings.CustomIP.empty())
|
||||
Body += "&ip=" + Application::Settings.CustomIP;
|
||||
|
||||
Body += "&pps=" + Application::PPS();
|
||||
|
||||
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, false);
|
||||
|
||||
if (T.substr(0, 2) != "20") {
|
||||
//Backend system refused server startup!
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, false);
|
||||
// TODO backup2 + HTTP flag (no TSL)
|
||||
if (T.substr(0, 2) != "20") {
|
||||
warn("Backend system refused server! Server might not show in the public list");
|
||||
debug("server returned \"" + T + "\"");
|
||||
isAuth = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAuth) {
|
||||
if (T == "2000") {
|
||||
info(("Authenticated!"));
|
||||
isAuth = true;
|
||||
} else if (T == "200") {
|
||||
info(("Resumed authenticated session!"));
|
||||
isAuth = true;
|
||||
}
|
||||
}
|
||||
|
||||
//SocketIO::Get().SetAuthenticated(isAuth);
|
||||
}
|
||||
}
|
||||
std::string THeartbeatThread::GenerateCall() {
|
||||
std::stringstream Ret;
|
||||
|
||||
Ret << "uuid=" << Application::Settings.Key
|
||||
<< "&players=" << mServer.ClientCount()
|
||||
<< "&maxplayers=" << Application::Settings.MaxPlayers
|
||||
<< "&port=" << Application::Settings.Port
|
||||
<< "&map=" << Application::Settings.MapName
|
||||
<< "&private=" << (Application::Settings.Private ? "true" : "false")
|
||||
<< "&version=" << Application::ServerVersion()
|
||||
<< "&clientversion=" << Application::ClientVersion()
|
||||
<< "&name=" << Application::Settings.ServerName
|
||||
<< "&modlist=" << mResourceManager.TrimmedList()
|
||||
<< "&modstotalsize=" << mResourceManager.MaxModSize()
|
||||
<< "&modstotal=" << mResourceManager.ModsLoaded()
|
||||
<< "&playerslist=" << GetPlayers()
|
||||
<< "&desc=" << Application::Settings.ServerDesc;
|
||||
return Ret.str();
|
||||
}
|
||||
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
|
||||
: mResourceManager(ResourceManager)
|
||||
, mServer(Server) {
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
if (mThread.joinable()) {
|
||||
debug("shutting down Heartbeat");
|
||||
mShutdown = true;
|
||||
mThread.join();
|
||||
debug("shut down Heartbeat");
|
||||
}
|
||||
});
|
||||
Start();
|
||||
}
|
||||
std::string THeartbeatThread::GetPlayers() {
|
||||
std::string Return;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Return += ClientPtr.lock()->GetName() + ";";
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return Return;
|
||||
}
|
||||
/*THeartbeatThread::~THeartbeatThread() {
|
||||
}*/
|
||||
@@ -1,66 +1,46 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 5/20/2020
|
||||
///
|
||||
#include "TLuaEngine.h"
|
||||
#include "TLuaFile.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Lua/LuaSystem.hpp"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include <thread>
|
||||
|
||||
#ifdef __linux
|
||||
// we need this for `struct stat`
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
#endif // __linux
|
||||
|
||||
std::set<std::unique_ptr<Lua>> PluginEngine;
|
||||
bool NewFile(const std::string& Path) {
|
||||
for (auto& Script : PluginEngine) {
|
||||
if (Path == Script->GetFileName())
|
||||
return false;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// necessary as lua relies on global state
|
||||
TLuaEngine* TheEngine;
|
||||
|
||||
TLuaEngine::TLuaEngine(TServer& Server, TNetwork& Network)
|
||||
: mNetwork(Network)
|
||||
, mServer(Server) {
|
||||
TheEngine = this;
|
||||
if (!fs::exists(Application::Settings.Resource)) {
|
||||
fs::create_directory(Application::Settings.Resource);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void RegisterFiles(const std::string& Path, bool HotSwap) {
|
||||
std::string Name = Path.substr(Path.find_last_of('\\') + 1);
|
||||
if (!HotSwap)
|
||||
info(("Loading plugin : ") + Name);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
auto pos = entry.path().string().find((".lua"));
|
||||
if (pos != std::string::npos && entry.path().string().length() - pos == 4) {
|
||||
if (!HotSwap || NewFile(entry.path().string())) {
|
||||
auto FileName = entry.path().string();
|
||||
std::unique_ptr<Lua> ScriptToInsert(new Lua(Name, FileName, fs::last_write_time(FileName)));
|
||||
auto& Script = *ScriptToInsert;
|
||||
PluginEngine.insert(std::move(ScriptToInsert));
|
||||
Script.Init();
|
||||
if (HotSwap)
|
||||
info(("[HOTSWAP] Added : ") + Script.GetFileName().substr(Script.GetFileName().find('\\')));
|
||||
}
|
||||
}
|
||||
std::string Path = Application::Settings.Resource + ("/Server");
|
||||
if (!fs::exists(Path)) {
|
||||
fs::create_directory(Path);
|
||||
}
|
||||
FolderList(Path, false);
|
||||
mPath = Path;
|
||||
Application::RegisterShutdownHandler([&] {if (mThread.joinable()) {
|
||||
debug("shutting down LuaEngine");
|
||||
mShutdown = true;
|
||||
mThread.join();
|
||||
debug("shut down LuaEngine");
|
||||
} });
|
||||
Start();
|
||||
}
|
||||
void FolderList(const std::string& Path, bool HotSwap) {
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
auto pos = entry.path().filename().string().find('.');
|
||||
if (pos == std::string::npos) {
|
||||
RegisterFiles(entry.path().string(), HotSwap);
|
||||
}
|
||||
}
|
||||
}
|
||||
[[noreturn]] void HotSwaps(const std::string& path) {
|
||||
DebugPrintTID();
|
||||
while (true) {
|
||||
if (!PluginEngine.empty()) {
|
||||
for (auto& Script : PluginEngine) {
|
||||
|
||||
void TLuaEngine::operator()() {
|
||||
RegisterThread("LuaEngine");
|
||||
info("Lua system online");
|
||||
while (!mShutdown) {
|
||||
if (!mLuaFiles.empty()) {
|
||||
for (auto& Script : mLuaFiles) {
|
||||
struct stat Info { };
|
||||
if (stat(Script->GetFileName().c_str(), &Info) != 0) {
|
||||
Script->SetStopThread(true);
|
||||
PluginEngine.erase(Script);
|
||||
mLuaFiles.erase(Script);
|
||||
info(("[HOTSWAP] Removed removed script due to delete"));
|
||||
break;
|
||||
}
|
||||
@@ -72,21 +52,52 @@ void FolderList(const std::string& Path, bool HotSwap) {
|
||||
}
|
||||
}
|
||||
}
|
||||
FolderList(path, true);
|
||||
FolderList(mPath, true);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
void InitLua() {
|
||||
if (!fs::exists(Resource)) {
|
||||
fs::create_directory(Resource);
|
||||
std::optional<std::reference_wrapper<TLuaFile>> TLuaEngine::GetScript(lua_State* L) {
|
||||
for (auto& Script : mLuaFiles) {
|
||||
if (Script->GetState() == L)
|
||||
return *Script;
|
||||
}
|
||||
std::string Path = Resource + ("/Server");
|
||||
if (!fs::exists(Path)) {
|
||||
fs::create_directory(Path);
|
||||
}
|
||||
FolderList(Path, false);
|
||||
std::thread t1(HotSwaps, Path);
|
||||
t1.detach();
|
||||
info(("Lua system online"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void TLuaEngine::FolderList(const std::string& Path, bool HotSwap) {
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
auto pos = entry.path().filename().string().find('.');
|
||||
if (pos == std::string::npos) {
|
||||
RegisterFiles(entry.path().string(), HotSwap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TLuaEngine::RegisterFiles(const std::string& Path, bool HotSwap) {
|
||||
std::string Name = Path.substr(Path.find_last_of('\\') + 1);
|
||||
if (!HotSwap)
|
||||
info(("Loading plugin : ") + Name);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
auto pos = entry.path().string().find((".lua"));
|
||||
if (pos != std::string::npos && entry.path().string().length() - pos == 4) {
|
||||
if (!HotSwap || NewFile(entry.path().string())) {
|
||||
auto FileName = entry.path().string();
|
||||
std::unique_ptr<TLuaFile> ScriptToInsert(new TLuaFile(*this));
|
||||
auto& Script = *ScriptToInsert;
|
||||
mLuaFiles.insert(std::move(ScriptToInsert));
|
||||
Script.Init(Name, FileName, fs::last_write_time(FileName));
|
||||
if (HotSwap)
|
||||
info(("[HOTSWAP] Added : ") + Script.GetFileName().substr(Script.GetFileName().find('\\')));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TLuaEngine::NewFile(const std::string& Path) {
|
||||
for (auto& Script : mLuaFiles) {
|
||||
if (Path == Script->GetFileName())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
826
src/TLuaFile.cpp
Normal file
826
src/TLuaFile.cpp
Normal file
@@ -0,0 +1,826 @@
|
||||
#include "TLuaFile.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "CustomAssert.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TServer.h"
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
// TODO: REWRITE
|
||||
|
||||
void SendError(TLuaEngine& Engine, lua_State* L, const std::string& msg);
|
||||
std::any CallFunction(TLuaFile* lua, const std::string& FuncName, std::shared_ptr<TLuaArg> Arg);
|
||||
std::any TriggerLuaEvent(TLuaEngine& Engine, const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
|
||||
|
||||
extern TLuaEngine* TheEngine;
|
||||
|
||||
static TLuaEngine& Engine() {
|
||||
Assert(TheEngine);
|
||||
return *TheEngine;
|
||||
}
|
||||
|
||||
std::shared_ptr<TLuaArg> CreateArg(lua_State* L, int T, int S) {
|
||||
if (S > T)
|
||||
return nullptr;
|
||||
std::shared_ptr<TLuaArg> temp(new TLuaArg);
|
||||
for (int C = S; C <= T; C++) {
|
||||
if (lua_isstring(L, C)) {
|
||||
temp->args.emplace_back(std::string(lua_tostring(L, C)));
|
||||
} else if (lua_isinteger(L, C)) {
|
||||
temp->args.emplace_back(int(lua_tointeger(L, C)));
|
||||
} else if (lua_isboolean(L, C)) {
|
||||
temp->args.emplace_back(bool(lua_toboolean(L, C)));
|
||||
} else if (lua_isnumber(L, C)) {
|
||||
temp->args.emplace_back(float(lua_tonumber(L, C)));
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
void ClearStack(lua_State* L) {
|
||||
lua_settop(L, 0);
|
||||
}
|
||||
|
||||
std::any Trigger(TLuaFile* lua, const std::string& R, std::shared_ptr<TLuaArg> arg) {
|
||||
RegisterThread(lua->GetFileName());
|
||||
std::lock_guard<std::mutex> lockGuard(lua->Lock);
|
||||
std::packaged_task<std::any(std::shared_ptr<TLuaArg>)> task([lua, R](std::shared_ptr<TLuaArg> arg) { return CallFunction(lua, R, arg); });
|
||||
std::future<std::any> f1 = task.get_future();
|
||||
std::thread t(std::move(task), arg);
|
||||
t.detach();
|
||||
auto status = f1.wait_for(std::chrono::seconds(5));
|
||||
if (status != std::future_status::timeout)
|
||||
return f1.get();
|
||||
SendError(lua->Engine(), lua->GetState(), R + " took too long to respond");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::any FutureWait(TLuaFile* lua, const std::string& R, std::shared_ptr<TLuaArg> arg, bool Wait) {
|
||||
Assert(lua);
|
||||
std::packaged_task<std::any(std::shared_ptr<TLuaArg>)> task([lua, R](std::shared_ptr<TLuaArg> arg) { return Trigger(lua, R, arg); });
|
||||
std::future<std::any> f1 = task.get_future();
|
||||
std::thread t(std::move(task), arg);
|
||||
t.detach();
|
||||
int T = 0;
|
||||
if (Wait)
|
||||
T = 6;
|
||||
auto status = f1.wait_for(std::chrono::seconds(T));
|
||||
if (status != std::future_status::timeout)
|
||||
return f1.get();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr<TLuaArg> arg, bool Wait) {
|
||||
std::any R;
|
||||
std::string Type;
|
||||
int Ret = 0;
|
||||
for (auto& Script : Engine().LuaFiles()) {
|
||||
if (Script->IsRegistered(Event)) {
|
||||
if (local) {
|
||||
if (Script->GetPluginName() == Caller->GetPluginName()) {
|
||||
R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait);
|
||||
Type = R.type().name();
|
||||
if (Type.find("int") != std::string::npos) {
|
||||
if (std::any_cast<int>(R))
|
||||
Ret++;
|
||||
} else if (Event == "onPlayerAuth")
|
||||
return R;
|
||||
}
|
||||
} else {
|
||||
R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait);
|
||||
Type = R.type().name();
|
||||
if (Type.find("int") != std::string::npos) {
|
||||
if (std::any_cast<int>(R))
|
||||
Ret++;
|
||||
} else if (Event == "onPlayerAuth")
|
||||
return R;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
bool ConsoleCheck(lua_State* L, int r) {
|
||||
if (r != LUA_OK) {
|
||||
std::string msg = lua_tostring(L, -1);
|
||||
warn(("_Console | ") + msg);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckLua(lua_State* L, int r) {
|
||||
if (r != LUA_OK) {
|
||||
std::string msg = lua_tostring(L, -1);
|
||||
auto MaybeS = Engine().GetScript(L);
|
||||
if (MaybeS.has_value()) {
|
||||
TLuaFile& S = MaybeS.value();
|
||||
std::string a = fs::path(S.GetFileName()).filename().string();
|
||||
warn(a + " | " + msg);
|
||||
return false;
|
||||
}
|
||||
// What the fuck, what do we do?!
|
||||
AssertNotReachable();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int lua_RegisterEvent(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = Engine().GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
TLuaFile& Script = MaybeScript.value();
|
||||
if (Args == 2 && lua_isstring(L, 1) && lua_isstring(L, 2)) {
|
||||
Script.RegisterEvent(lua_tostring(L, 1), lua_tostring(L, 2));
|
||||
} else
|
||||
SendError(Engine(), L, "RegisterEvent invalid argument count expected 2 got " + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_TriggerEventL(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = Engine().GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
TLuaFile& Script = MaybeScript.value();
|
||||
if (Args > 0) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
TriggerLuaEvent(lua_tostring(L, 1), true, &Script, CreateArg(L, Args, 2), false);
|
||||
} else
|
||||
SendError(Engine(), L, ("TriggerLocalEvent wrong argument [1] need string"));
|
||||
} else {
|
||||
SendError(Engine(), L, ("TriggerLocalEvent not enough arguments expected 1 got 0"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_TriggerEventG(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
auto MaybeScript = Engine().GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
TLuaFile& Script = MaybeScript.value();
|
||||
if (Args > 0) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
TriggerLuaEvent(lua_tostring(L, 1), false, &Script, CreateArg(L, Args, 2), false);
|
||||
} else
|
||||
SendError(Engine(), L, ("TriggerGlobalEvent wrong argument [1] need string"));
|
||||
} else
|
||||
SendError(Engine(), L, ("TriggerGlobalEvent not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SafeExecution(TLuaFile* lua, const std::string& FuncName) {
|
||||
lua_State* luaState = lua->GetState();
|
||||
lua_getglobal(luaState, FuncName.c_str());
|
||||
if (lua_isfunction(luaState, -1)) {
|
||||
int R = lua_pcall(luaState, 0, 0, 0);
|
||||
CheckLua(luaState, R);
|
||||
}
|
||||
ClearStack(luaState);
|
||||
}
|
||||
|
||||
void ExecuteAsync(TLuaFile* lua, const std::string& FuncName) {
|
||||
std::lock_guard<std::mutex> lockGuard(lua->Lock);
|
||||
SafeExecution(lua, FuncName);
|
||||
}
|
||||
|
||||
void CallAsync(TLuaFile* lua, const std::string& Func, int U) {
|
||||
RegisterThread(lua->GetFileName());
|
||||
lua->SetStopThread(false);
|
||||
int D = 1000 / U;
|
||||
while (!lua->GetStopThread()) {
|
||||
ExecuteAsync(lua, Func);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(D));
|
||||
}
|
||||
}
|
||||
|
||||
int lua_StopThread(lua_State* L) {
|
||||
auto MaybeScript = Engine().GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
// ugly, but whatever, this is safe as fuck
|
||||
MaybeScript.value().get().SetStopThread(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_CreateThread(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args > 1) {
|
||||
if (lua_isstring(L, 1)) {
|
||||
std::string STR = lua_tostring(L, 1);
|
||||
if (lua_isinteger(L, 2) || lua_isnumber(L, 2)) {
|
||||
int U = int(lua_tointeger(L, 2));
|
||||
if (U > 0 && U < 501) {
|
||||
auto MaybeScript = Engine().GetScript(L);
|
||||
Assert(MaybeScript.has_value());
|
||||
TLuaFile& Script = MaybeScript.value();
|
||||
std::thread t1(CallAsync, &Script, STR, U);
|
||||
t1.detach();
|
||||
} else
|
||||
SendError(Engine(), L, ("CreateThread wrong argument [2] number must be between 1 and 500"));
|
||||
} else
|
||||
SendError(Engine(), L, ("CreateThread wrong argument [2] need number"));
|
||||
} else
|
||||
SendError(Engine(), L, ("CreateThread wrong argument [1] need string"));
|
||||
} else
|
||||
SendError(Engine(), L, ("CreateThread not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_Sleep(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int t = int(lua_tonumber(L, 1));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(t));
|
||||
} else {
|
||||
SendError(Engine(), L, ("Sleep not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::optional<std::weak_ptr<TClient>> GetClient(TServer& Server, int ID) {
|
||||
std::optional<std::weak_ptr<TClient>> MaybeClient { std::nullopt };
|
||||
Server.ForEachClient([&](std::weak_ptr<TClient> CPtr) -> bool {
|
||||
ReadLock Lock(Server.GetClientMutex());
|
||||
if (!CPtr.expired()) {
|
||||
auto C = CPtr.lock();
|
||||
if (C->GetID() == ID) {
|
||||
MaybeClient = CPtr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return MaybeClient;
|
||||
}
|
||||
|
||||
int lua_isConnected(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired())
|
||||
lua_pushboolean(L, MaybeClient.value().lock()->IsConnected());
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(Engine(), L, ("isConnected not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetPlayerName(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired())
|
||||
lua_pushstring(L, MaybeClient.value().lock()->GetName().c_str());
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(Engine(), L, ("GetPlayerName not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetPlayerCount(lua_State* L) {
|
||||
lua_pushinteger(L, Engine().Server().ClientCount());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetGuest(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired())
|
||||
lua_pushboolean(L, MaybeClient.value().lock()->IsGuest());
|
||||
else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(Engine(), L, "GetGuest not enough arguments");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetAllPlayers(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
Engine().Server().ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
{
|
||||
ReadLock Lock(Engine().Server().GetClientMutex());
|
||||
if (ClientPtr.expired())
|
||||
return true;
|
||||
Client = ClientPtr.lock();
|
||||
}
|
||||
lua_pushinteger(L, Client->GetID());
|
||||
lua_pushstring(L, Client->GetName().c_str());
|
||||
lua_settable(L, -3);
|
||||
return true;
|
||||
});
|
||||
if (Engine().Server().ClientCount() == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetIdentifiers(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
auto MaybeClient = GetClient(Engine().Server(), int(lua_tonumber(L, 1)));
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto IDs = MaybeClient.value().lock()->GetIdentifiers();
|
||||
if (IDs.empty())
|
||||
return 0;
|
||||
lua_newtable(L);
|
||||
for (const std::string& ID : IDs) {
|
||||
lua_pushstring(L, ID.substr(0, ID.find(':')).c_str());
|
||||
lua_pushstring(L, ID.c_str());
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
} else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(Engine(), L, "lua_GetIdentifiers wrong arguments");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_GetCars(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto Client = MaybeClient.value().lock();
|
||||
TClient::TSetOfVehicleData VehicleData;
|
||||
{ // Vehicle Data Lock Scope
|
||||
auto LockedData = Client->GetAllCars();
|
||||
VehicleData = *LockedData.VehicleData;
|
||||
} // End Vehicle Data Lock Scope
|
||||
if (VehicleData.empty())
|
||||
return 0;
|
||||
lua_newtable(L);
|
||||
for (const auto& v : VehicleData) {
|
||||
lua_pushinteger(L, v.ID());
|
||||
lua_pushstring(L, v.Data().substr(3).c_str());
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
} else
|
||||
return 0;
|
||||
} else {
|
||||
SendError(Engine(), L, ("GetPlayerVehicles wrong arguments"));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_dropPlayer(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (!MaybeClient || MaybeClient.value().expired())
|
||||
return 0;
|
||||
std::string Reason;
|
||||
if (Args > 1 && lua_isstring(L, 2)) {
|
||||
Reason = std::string((" Reason : ")) + lua_tostring(L, 2);
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
Engine().Network().Respond(*c, "C:Server:You have been Kicked from the server! " + Reason, true);
|
||||
c->SetStatus(-2);
|
||||
info(("Closing socket due to kick"));
|
||||
CloseSocketProper(c->GetTCPSock());
|
||||
} else
|
||||
SendError(Engine(), L, ("DropPlayer not enough arguments"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_sendChat(lua_State* L) {
|
||||
if (lua_isinteger(L, 1) || lua_isnumber(L, 1)) {
|
||||
if (lua_isstring(L, 2)) {
|
||||
int ID = int(lua_tointeger(L, 1));
|
||||
if (ID == -1) {
|
||||
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
|
||||
Engine().Network().SendToAll(nullptr, Packet, true, true);
|
||||
} else {
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (MaybeClient && !MaybeClient.value().expired()) {
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->IsSynced())
|
||||
return 0;
|
||||
std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2));
|
||||
Engine().Network().Respond(*c, Packet, true);
|
||||
} else
|
||||
SendError(Engine(), L, ("SendChatMessage invalid argument [1] invalid ID"));
|
||||
}
|
||||
} else
|
||||
SendError(Engine(), L, ("SendChatMessage invalid argument [2] expected string"));
|
||||
} else
|
||||
SendError(Engine(), L, ("SendChatMessage invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_RemoveVehicle(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 2) {
|
||||
SendError(Engine(), L, ("RemoveVehicle invalid argument count expected 2 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if ((lua_isinteger(L, 1) || lua_isnumber(L, 1)) && (lua_isinteger(L, 2) || lua_isnumber(L, 2))) {
|
||||
int PID = int(lua_tointeger(L, 1));
|
||||
int VID = int(lua_tointeger(L, 2));
|
||||
auto MaybeClient = GetClient(Engine().Server(), PID);
|
||||
if (!MaybeClient || MaybeClient.value().expired()) {
|
||||
SendError(Engine(), L, ("RemoveVehicle invalid Player ID"));
|
||||
return 0;
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (!c->GetCarData(VID).empty()) {
|
||||
std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID);
|
||||
Engine().Network().SendToAll(nullptr, Destroy, true, true);
|
||||
c->DeleteCar(VID);
|
||||
}
|
||||
} else
|
||||
SendError(Engine(), L, ("RemoveVehicle invalid argument expected number"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_HWID(lua_State* L) {
|
||||
lua_pushinteger(L, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int lua_RemoteEvent(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 3) {
|
||||
SendError(Engine(), L, ("TriggerClientEvent invalid argument count expected 3 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isnumber(L, 1)) {
|
||||
SendError(Engine(), L, ("TriggerClientEvent invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isstring(L, 2)) {
|
||||
SendError(Engine(), L, ("TriggerClientEvent invalid argument [2] expected string"));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isstring(L, 3)) {
|
||||
SendError(Engine(), L, ("TriggerClientEvent invalid argument [3] expected string"));
|
||||
return 0;
|
||||
}
|
||||
int ID = int(lua_tointeger(L, 1));
|
||||
std::string Packet = "E:" + std::string(lua_tostring(L, 2)) + ":" + std::string(lua_tostring(L, 3));
|
||||
if (ID == -1)
|
||||
Engine().Network().SendToAll(nullptr, Packet, true, true);
|
||||
else {
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (!MaybeClient || MaybeClient.value().expired()) {
|
||||
SendError(Engine(), L, ("TriggerClientEvent invalid Player ID"));
|
||||
return 0;
|
||||
}
|
||||
auto c = MaybeClient.value().lock();
|
||||
Engine().Network().Respond(*c, Packet, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_ServerExit(lua_State*) {
|
||||
Application::GracefullyShutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lua_Set(lua_State* L) {
|
||||
int Args = lua_gettop(L);
|
||||
if (Args != 2) {
|
||||
SendError(Engine(), L, ("set invalid argument count expected 2 got ") + std::to_string(Args));
|
||||
return 0;
|
||||
}
|
||||
if (!lua_isnumber(L, 1)) {
|
||||
SendError(Engine(), L, ("set invalid argument [1] expected number"));
|
||||
return 0;
|
||||
}
|
||||
auto MaybeSrc = Engine().GetScript(L);
|
||||
std::string Name;
|
||||
if (!MaybeSrc.has_value()) {
|
||||
Name = ("_Console");
|
||||
} else {
|
||||
Name = MaybeSrc.value().get().GetPluginName();
|
||||
}
|
||||
int C = int(lua_tointeger(L, 1));
|
||||
switch (C) {
|
||||
case 0: //debug
|
||||
if (lua_isboolean(L, 2)) {
|
||||
Application::Settings.DebugModeEnabled = lua_toboolean(L, 2);
|
||||
info(Name + (" | Debug -> ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected boolean for ID : 0"));
|
||||
break;
|
||||
case 1: //private
|
||||
if (lua_isboolean(L, 2)) {
|
||||
Application::Settings.Private = lua_toboolean(L, 2);
|
||||
info(Name + (" | Private -> ") + (Application::Settings.Private ? "true" : "false"));
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected boolean for ID : 1"));
|
||||
break;
|
||||
case 2: //max cars
|
||||
if (lua_isnumber(L, 2)) {
|
||||
Application::Settings.MaxCars = int(lua_tointeger(L, 2));
|
||||
info(Name + (" | MaxCars -> ") + std::to_string(Application::Settings.MaxCars));
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected number for ID : 2"));
|
||||
break;
|
||||
case 3: //max players
|
||||
if (lua_isnumber(L, 2)) {
|
||||
Application::Settings.MaxPlayers = int(lua_tointeger(L, 2));
|
||||
info(Name + (" | MaxPlayers -> ") + std::to_string(Application::Settings.MaxPlayers));
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected number for ID : 3"));
|
||||
break;
|
||||
case 4: //Map
|
||||
if (lua_isstring(L, 2)) {
|
||||
Application::Settings.MapName = lua_tostring(L, 2);
|
||||
info(Name + (" | MapName -> ") + Application::Settings.MapName);
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected string for ID : 4"));
|
||||
break;
|
||||
case 5: //Name
|
||||
if (lua_isstring(L, 2)) {
|
||||
Application::Settings.ServerName = lua_tostring(L, 2);
|
||||
info(Name + (" | ServerName -> ") + Application::Settings.ServerName);
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected string for ID : 5"));
|
||||
break;
|
||||
case 6: //Desc
|
||||
if (lua_isstring(L, 2)) {
|
||||
Application::Settings.ServerDesc = lua_tostring(L, 2);
|
||||
info(Name + (" | ServerDesc -> ") + Application::Settings.ServerDesc);
|
||||
} else
|
||||
SendError(Engine(), L, ("set invalid argument [2] expected string for ID : 6"));
|
||||
break;
|
||||
default:
|
||||
warn(("Invalid config ID : ") + std::to_string(C));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
int lua_Print(lua_State* L) {
|
||||
int Arg = lua_gettop(L);
|
||||
std::string to_print;
|
||||
for (int i = 1; i <= Arg; i++) {
|
||||
if (lua_isstring(L, i)) {
|
||||
to_print += lua_tostring(L, i);
|
||||
} else if (lua_isinteger(L, i)) {
|
||||
to_print += std::to_string(lua_tointeger(L, 1));
|
||||
} else if (lua_isnumber(L, i)) {
|
||||
to_print += std::to_string(lua_tonumber(L, 1));
|
||||
} else if (lua_isboolean(L, i)) {
|
||||
to_print += lua_toboolean(L, i) ? "true" : "false";
|
||||
} else if (lua_isfunction(L, i)) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << reinterpret_cast<const void*>(lua_tocfunction(L, i));
|
||||
to_print += "function: " + ss.str();
|
||||
} else if (lua_istable(L, i)) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << reinterpret_cast<const void*>(lua_topointer(L, i));
|
||||
to_print += "table: " + ss.str();
|
||||
} else if (lua_isnoneornil(L, i)) {
|
||||
to_print += "nil";
|
||||
} else if (lua_isthread(L, i)) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << reinterpret_cast<const void*>(lua_tothread(L, i));
|
||||
to_print += "thread: " + ss.str();
|
||||
} else {
|
||||
to_print += "(unknown)";
|
||||
}
|
||||
if (i + 1 <= Arg) {
|
||||
to_print += "\t";
|
||||
}
|
||||
}
|
||||
luaprint(to_print);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int lua_TempFix(lua_State* L);
|
||||
|
||||
void TLuaFile::Init(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote) {
|
||||
// set global engine for lua_* functions
|
||||
if (!TheEngine) {
|
||||
TheEngine = &mEngine;
|
||||
}
|
||||
Assert(mLuaState);
|
||||
if (!PluginName.empty()) {
|
||||
SetPluginName(PluginName);
|
||||
}
|
||||
if (!FileName.empty()) {
|
||||
SetFileName(FileName);
|
||||
}
|
||||
SetLastWrite(LastWrote);
|
||||
Load();
|
||||
}
|
||||
|
||||
TLuaFile::TLuaFile(TLuaEngine& Engine, bool Console)
|
||||
: mEngine(Engine)
|
||||
, mLuaState(luaL_newstate()) {
|
||||
if (Console) {
|
||||
mConsole = Console;
|
||||
Load();
|
||||
}
|
||||
}
|
||||
|
||||
void TLuaFile::Execute(const std::string& Command) {
|
||||
if (ConsoleCheck(mLuaState, luaL_dostring(mLuaState, Command.c_str()))) {
|
||||
lua_settop(mLuaState, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void TLuaFile::Reload() {
|
||||
if (CheckLua(mLuaState, luaL_dofile(mLuaState, mFileName.c_str()))) {
|
||||
CallFunction(this, ("onInit"), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string TLuaFile::GetOrigin() {
|
||||
return fs::path(GetFileName()).filename().string();
|
||||
}
|
||||
|
||||
std::any CallFunction(TLuaFile* lua, const std::string& FuncName, std::shared_ptr<TLuaArg> Arg) {
|
||||
RegisterThread(lua->GetFileName());
|
||||
lua_State* luaState = lua->GetState();
|
||||
lua_getglobal(luaState, FuncName.c_str());
|
||||
if (lua_isfunction(luaState, -1)) {
|
||||
int Size = 0;
|
||||
if (Arg != nullptr) {
|
||||
Size = int(Arg->args.size());
|
||||
Arg->PushArgs(luaState);
|
||||
}
|
||||
int R = lua_pcall(luaState, Size, 1, 0);
|
||||
if (CheckLua(luaState, R)) {
|
||||
if (lua_isnumber(luaState, -1)) {
|
||||
auto ret = int(lua_tointeger(luaState, -1));
|
||||
ClearStack(luaState);
|
||||
return ret;
|
||||
} else if (lua_isstring(luaState, -1)) {
|
||||
auto ret = std::string(lua_tostring(luaState, -1));
|
||||
ClearStack(luaState);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClearStack(luaState);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TLuaFile::SetPluginName(const std::string& Name) {
|
||||
mPluginName = Name;
|
||||
}
|
||||
|
||||
void TLuaFile::SetFileName(const std::string& Name) {
|
||||
mFileName = Name;
|
||||
}
|
||||
|
||||
void TLuaFile::Load() {
|
||||
Assert(mLuaState);
|
||||
luaL_openlibs(mLuaState);
|
||||
lua_register(mLuaState, "GetPlayerIdentifiers", lua_GetIdentifiers);
|
||||
lua_register(mLuaState, "TriggerGlobalEvent", lua_TriggerEventG);
|
||||
lua_register(mLuaState, "TriggerLocalEvent", lua_TriggerEventL);
|
||||
lua_register(mLuaState, "TriggerClientEvent", lua_RemoteEvent);
|
||||
lua_register(mLuaState, "GetPlayerCount", lua_GetPlayerCount);
|
||||
lua_register(mLuaState, "isPlayerConnected", lua_isConnected);
|
||||
lua_register(mLuaState, "RegisterEvent", lua_RegisterEvent);
|
||||
lua_register(mLuaState, "GetPlayerName", lua_GetPlayerName);
|
||||
lua_register(mLuaState, "RemoveVehicle", lua_RemoveVehicle);
|
||||
lua_register(mLuaState, "GetPlayerDiscordID", lua_TempFix);
|
||||
lua_register(mLuaState, "CreateThread", lua_CreateThread);
|
||||
lua_register(mLuaState, "GetPlayerVehicles", lua_GetCars);
|
||||
lua_register(mLuaState, "SendChatMessage", lua_sendChat);
|
||||
lua_register(mLuaState, "GetPlayers", lua_GetAllPlayers);
|
||||
lua_register(mLuaState, "GetPlayerGuest", lua_GetGuest);
|
||||
lua_register(mLuaState, "StopThread", lua_StopThread);
|
||||
lua_register(mLuaState, "DropPlayer", lua_dropPlayer);
|
||||
lua_register(mLuaState, "GetPlayerHWID", lua_HWID);
|
||||
lua_register(mLuaState, "exit", lua_ServerExit);
|
||||
lua_register(mLuaState, "Sleep", lua_Sleep);
|
||||
lua_register(mLuaState, "print", lua_Print);
|
||||
lua_register(mLuaState, "Set", lua_Set);
|
||||
if (!mConsole)
|
||||
Reload();
|
||||
}
|
||||
|
||||
void TLuaFile::RegisterEvent(const std::string& Event, const std::string& FunctionName) {
|
||||
mRegisteredEvents.insert(std::make_pair(Event, FunctionName));
|
||||
}
|
||||
|
||||
void TLuaFile::UnRegisterEvent(const std::string& Event) {
|
||||
for (const std::pair<std::string, std::string>& a : mRegisteredEvents) {
|
||||
if (a.first == Event) {
|
||||
mRegisteredEvents.erase(a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TLuaFile::IsRegistered(const std::string& Event) {
|
||||
for (const std::pair<std::string, std::string>& a : mRegisteredEvents) {
|
||||
if (a.first == Event)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string TLuaFile::GetRegistered(const std::string& Event) const {
|
||||
for (const std::pair<std::string, std::string>& a : mRegisteredEvents) {
|
||||
if (a.first == Event)
|
||||
return a.second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string TLuaFile::GetFileName() const {
|
||||
return mFileName;
|
||||
}
|
||||
|
||||
std::string TLuaFile::GetPluginName() const {
|
||||
return mPluginName;
|
||||
}
|
||||
|
||||
lua_State* TLuaFile::GetState() {
|
||||
return mLuaState;
|
||||
}
|
||||
|
||||
const lua_State* TLuaFile::GetState() const {
|
||||
return mLuaState;
|
||||
}
|
||||
|
||||
void TLuaFile::SetLastWrite(fs::file_time_type time) {
|
||||
mLastWrote = time;
|
||||
}
|
||||
fs::file_time_type TLuaFile::GetLastWrite() {
|
||||
return mLastWrote;
|
||||
}
|
||||
|
||||
TLuaFile::~TLuaFile() {
|
||||
info("closing lua state");
|
||||
lua_close(mLuaState);
|
||||
}
|
||||
|
||||
void SendError(TLuaEngine& Engine, lua_State* L, const std::string& msg) {
|
||||
Assert(L);
|
||||
auto MaybeS = Engine.GetScript(L);
|
||||
std::string a;
|
||||
if (!MaybeS.has_value()) {
|
||||
a = ("_Console");
|
||||
} else {
|
||||
TLuaFile& S = MaybeS.value();
|
||||
a = fs::path(S.GetFileName()).filename().string();
|
||||
}
|
||||
warn(a + (" | Incorrect Call of ") + msg);
|
||||
}
|
||||
|
||||
int lua_TempFix(lua_State* L) {
|
||||
if (lua_isnumber(L, 1)) {
|
||||
int ID = int(lua_tonumber(L, 1));
|
||||
auto MaybeClient = GetClient(Engine().Server(), ID);
|
||||
if (!MaybeClient || MaybeClient.value().expired())
|
||||
return 0;
|
||||
std::string Ret;
|
||||
auto c = MaybeClient.value().lock();
|
||||
if (c->IsGuest()) {
|
||||
Ret = "Guest-" + c->GetName();
|
||||
} else
|
||||
Ret = c->GetName();
|
||||
lua_pushstring(L, Ret.c_str());
|
||||
} else
|
||||
SendError(Engine(), L, "GetDID not enough arguments");
|
||||
return 1;
|
||||
}
|
||||
|
||||
void TLuaArg::PushArgs(lua_State* State) {
|
||||
for (std::any arg : args) {
|
||||
if (!arg.has_value()) {
|
||||
error("arg didn't have a value, this is not expected, bad");
|
||||
return;
|
||||
}
|
||||
const auto& Type = arg.type();
|
||||
if (Type == typeid(bool)) {
|
||||
lua_pushboolean(State, std::any_cast<bool>(arg));
|
||||
} else if (Type == typeid(std::string)) {
|
||||
lua_pushstring(State, std::any_cast<std::string>(arg).c_str());
|
||||
} else if (Type == typeid(const char*)) {
|
||||
lua_pushstring(State, std::any_cast<const char*>(arg));
|
||||
} else if (Type == typeid(int)) {
|
||||
lua_pushinteger(State, std::any_cast<int>(arg));
|
||||
} else if (Type == typeid(float)) {
|
||||
lua_pushnumber(State, std::any_cast<float>(arg));
|
||||
} else if (Type == typeid(double)) {
|
||||
lua_pushnumber(State, std::any_cast<double>(arg));
|
||||
} else {
|
||||
// if this happens, implement a sane behavior for that value
|
||||
error("what in the hell is " + std::string(arg.type().name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
986
src/TNetwork.cpp
Normal file
986
src/TNetwork.cpp
Normal file
@@ -0,0 +1,986 @@
|
||||
#include "TNetwork.h"
|
||||
#include "Client.h"
|
||||
#include <CustomAssert.h>
|
||||
#include <Http.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager)
|
||||
: mServer(Server)
|
||||
, mPPSMonitor(PPSMonitor)
|
||||
, mResourceManager(ResourceManager) {
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
if (mUDPThread.joinable()) {
|
||||
debug("shutting down TCPServer");
|
||||
mShutdown = true;
|
||||
mUDPThread.detach();
|
||||
debug("shut down TCPServer");
|
||||
}
|
||||
});
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
if (mUDPThread.joinable()) {
|
||||
debug("shutting down TCPServer");
|
||||
mShutdown = true;
|
||||
mTCPThread.detach();
|
||||
debug("shut down TCPServer");
|
||||
}
|
||||
});
|
||||
mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
|
||||
mUDPThread = std::thread(&TNetwork::UDPServerMain, this);
|
||||
}
|
||||
|
||||
void TNetwork::UDPServerMain() {
|
||||
RegisterThread("UDPServer");
|
||||
#ifdef WIN32
|
||||
WSADATA data;
|
||||
if (WSAStartup(514, &data)) {
|
||||
error(("Can't start Winsock!"));
|
||||
//return;
|
||||
}
|
||||
|
||||
mUDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
// Create a server hint structure for the server
|
||||
sockaddr_in serverAddr {};
|
||||
serverAddr.sin_addr.S_un.S_addr = ADDR_ANY; //Any Local
|
||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||
serverAddr.sin_port = htons(Application::Settings.Port); // Convert from little to big endian
|
||||
|
||||
// Try and bind the socket to the IP and port
|
||||
if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
|
||||
error(("Can't bind socket!") + std::to_string(WSAGetLastError()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
exit(-1);
|
||||
//return;
|
||||
}
|
||||
#else // unix
|
||||
mUDPSock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
// Create a server hint structure for the server
|
||||
sockaddr_in serverAddr {};
|
||||
serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local
|
||||
serverAddr.sin_family = AF_INET; // Address format is IPv4
|
||||
serverAddr.sin_port = htons(uint16_t(Application::Settings.Port)); // Convert from little to big endian
|
||||
|
||||
// Try and bind the socket to the IP and port
|
||||
if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
|
||||
error(("Can't bind socket!") + std::string(strerror(errno)));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
exit(-1);
|
||||
//return;
|
||||
}
|
||||
#endif
|
||||
|
||||
info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
|
||||
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
|
||||
while (!mShutdown) {
|
||||
try {
|
||||
sockaddr_in client {};
|
||||
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket
|
||||
size_t Pos = Data.find(':');
|
||||
if (Data.empty() || Pos > 2)
|
||||
continue;
|
||||
/*char clientIp[256];
|
||||
ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet
|
||||
inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/
|
||||
uint8_t ID = uint8_t(Data.at(0)) - 1;
|
||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Client->GetID() == ID) {
|
||||
Client->SetUDPAddr(client);
|
||||
Client->SetIsConnected(true);
|
||||
TServer::GlobalParser(ClientPtr, Data.substr(2), mPPSMonitor, *this);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::TCPServerMain() {
|
||||
RegisterThread("TCPServer");
|
||||
#ifdef WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(514, &wsaData)) {
|
||||
error("Can't start Winsock!");
|
||||
return;
|
||||
}
|
||||
SOCKET client, Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
sockaddr_in addr {};
|
||||
addr.sin_addr.S_un.S_addr = ADDR_ANY;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(Application::Settings.Port);
|
||||
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
error("Can't bind socket! " + std::to_string(WSAGetLastError()));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
exit(-1);
|
||||
}
|
||||
if (Listener == -1) {
|
||||
error("Invalid listening socket");
|
||||
return;
|
||||
}
|
||||
|
||||
if (listen(Listener, SOMAXCONN)) {
|
||||
error("listener failed " + std::to_string(GetLastError()));
|
||||
//TODO Fix me leak for Listener socket
|
||||
return;
|
||||
}
|
||||
info("Vehicle event network online");
|
||||
do {
|
||||
try {
|
||||
client = accept(Listener, nullptr, nullptr);
|
||||
if (client == -1) {
|
||||
warn("Got an invalid client socket on connect! Skipping...");
|
||||
continue;
|
||||
}
|
||||
std::thread ID(&TNetwork::Identify, this, client);
|
||||
ID.detach();
|
||||
} catch (const std::exception& e) {
|
||||
error("fatal: " + std::string(e.what()));
|
||||
}
|
||||
} while (client);
|
||||
|
||||
CloseSocketProper(client);
|
||||
WSACleanup();
|
||||
#else // unix
|
||||
// wondering why we need slightly different implementations of this?
|
||||
// ask ms.
|
||||
SOCKET client = -1;
|
||||
SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
int optval = 1;
|
||||
setsockopt(Listener, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
|
||||
// TODO: check optval or return value idk
|
||||
sockaddr_in addr {};
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(uint16_t(Application::Settings.Port));
|
||||
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
error(("Can't bind socket! ") + std::string(strerror(errno)));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
exit(-1);
|
||||
}
|
||||
if (Listener == -1) {
|
||||
error(("Invalid listening socket"));
|
||||
return;
|
||||
}
|
||||
if (listen(Listener, SOMAXCONN)) {
|
||||
error(("listener failed ") + std::string(strerror(errno)));
|
||||
//TODO fix me leak Listener
|
||||
return;
|
||||
}
|
||||
info(("Vehicle event network online"));
|
||||
do {
|
||||
try {
|
||||
if (mShutdown) {
|
||||
debug("shutdown during TCP wait for accept loop");
|
||||
break;
|
||||
}
|
||||
client = accept(Listener, nullptr, nullptr);
|
||||
if (client == -1) {
|
||||
warn(("Got an invalid client socket on connect! Skipping..."));
|
||||
continue;
|
||||
}
|
||||
std::thread ID(&TNetwork::Identify, this, client);
|
||||
ID.detach(); // TODO: Add to a queue and attempt to join periodically
|
||||
} catch (const std::exception& e) {
|
||||
error(("fatal: ") + std::string(e.what()));
|
||||
}
|
||||
} while (client);
|
||||
|
||||
debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
|
||||
|
||||
CloseSocketProper(client);
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef GetObject //Fixes Windows
|
||||
|
||||
#include "Json.h"
|
||||
namespace json = rapidjson;
|
||||
|
||||
void TNetwork::Identify(SOCKET TCPSock) {
|
||||
RegisterThreadAuto();
|
||||
char Code;
|
||||
if (recv(TCPSock, &Code, 1, 0) != 1) {
|
||||
CloseSocketProper(TCPSock);
|
||||
return;
|
||||
}
|
||||
if (Code == 'C') {
|
||||
Authentication(TCPSock);
|
||||
} else if (Code == 'D') {
|
||||
HandleDownload(TCPSock);
|
||||
} else {
|
||||
CloseSocketProper(TCPSock);
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::HandleDownload(SOCKET TCPSock) {
|
||||
char D;
|
||||
if (recv(TCPSock, &D, 1, 0) != 1) {
|
||||
CloseSocketProper(TCPSock);
|
||||
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(TCPSock);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void TNetwork::Authentication(SOCKET TCPSock) {
|
||||
auto Client = CreateClient(TCPSock);
|
||||
|
||||
std::string Rc;
|
||||
info("Identifying new client...");
|
||||
|
||||
Rc = TCPRcv(*Client);
|
||||
|
||||
if (Rc.size() > 3 && Rc.substr(0, 2) == "VC") {
|
||||
Rc = Rc.substr(2);
|
||||
if (Rc.length() > 4 || Rc != Application::ClientVersion()) {
|
||||
ClientKick(*Client, "Outdated Version!");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ClientKick(*Client, "Invalid version header!");
|
||||
return;
|
||||
}
|
||||
if (!TCPSend(*Client, "S")) {
|
||||
// TODO: handle
|
||||
}
|
||||
|
||||
Rc = TCPRcv(*Client);
|
||||
|
||||
if (Rc.size() > 50) {
|
||||
ClientKick(*Client, "Invalid Key!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Rc.empty()) {
|
||||
Rc = Http::POST(Application::GetBackendUrlForAuth(), "/pkToUser", {}, R"({"key":")" + Rc + "\"}", true);
|
||||
}
|
||||
|
||||
json::Document AuthResponse;
|
||||
AuthResponse.Parse(Rc.c_str());
|
||||
if (Rc == "-1" || AuthResponse.HasParseError()) {
|
||||
ClientKick(*Client, "Invalid key! Please restart your game.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AuthResponse.IsObject()) {
|
||||
ClientKick(*Client, "Backend returned invalid auth response format.");
|
||||
error("Backend returned invalid auth response format. This should never happen.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AuthResponse["username"].IsString() && AuthResponse["roles"].IsString()
|
||||
&& AuthResponse["guest"].IsBool() && AuthResponse["identifiers"].IsArray()) {
|
||||
|
||||
Client->SetName(AuthResponse["username"].GetString());
|
||||
Client->SetRoles(AuthResponse["roles"].GetString());
|
||||
Client->SetIsGuest(AuthResponse["guest"].GetBool());
|
||||
for (const auto& ID : AuthResponse["identifiers"].GetArray()) {
|
||||
Client->AddIdentifier(ID.GetString());
|
||||
}
|
||||
} else {
|
||||
ClientKick(*Client, "Invalid authentication data!");
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
|
||||
debug("There are " + std::to_string(mServer.ClientCount()) + " known clients");
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Cl;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Cl = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
info("Client Iteration: Name -> " + Cl->GetName() + ", Guest -> " + std::to_string(Cl->IsGuest()) + ", Roles -> " + Cl->GetRoles());
|
||||
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
|
||||
info("New client matched with current iteration");
|
||||
info("Old client (" + Cl->GetName() + ") kicked: Reconnecting");
|
||||
CloseSocketProper(Cl->GetTCPSock());
|
||||
Cl->SetStatus(-2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
auto arg = std::make_unique<TLuaArg>(TLuaArg { { Client->GetName(), Client->GetRoles(), Client->IsGuest() } });
|
||||
std::any Res = TriggerLuaEvent("onPlayerAuth", false, nullptr, std::move(arg), true);
|
||||
std::string Type = Res.type().name();
|
||||
if (Type.find("int") != std::string::npos && std::any_cast<int>(Res)) {
|
||||
ClientKick(*Client, "you are not allowed on the server!");
|
||||
return;
|
||||
} else if (Type.find("string") != std::string::npos) {
|
||||
ClientKick(*Client, std::any_cast<std::string>(Res));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
|
||||
info("Identification success");
|
||||
mServer.InsertClient(Client);
|
||||
TCPClient(Client);
|
||||
} else
|
||||
ClientKick(*Client, "Server full!");
|
||||
}
|
||||
|
||||
std::shared_ptr<TClient> TNetwork::CreateClient(SOCKET TCPSock) {
|
||||
auto c = std::make_shared<TClient>(mServer);
|
||||
c->SetTCPSock(TCPSock);
|
||||
return c;
|
||||
}
|
||||
|
||||
bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
|
||||
if (!IsSync) {
|
||||
if (c.IsSyncing()) {
|
||||
if (!Data.empty()) {
|
||||
if (Data.at(0) == 'O' || Data.at(0) == 'A' || Data.at(0) == 'C' || Data.at(0) == 'E') {
|
||||
c.EnqueuePacket(Data);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Size, Sent;
|
||||
std::string Send(4, 0);
|
||||
Size = int32_t(Data.size());
|
||||
memcpy(&Send[0], &Size, sizeof(Size));
|
||||
Send += Data;
|
||||
Sent = 0;
|
||||
Size += 4;
|
||||
do {
|
||||
#ifdef WIN32
|
||||
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, 0);
|
||||
#else //WIN32
|
||||
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, MSG_NOSIGNAL);
|
||||
#endif //WIN32
|
||||
if (Temp == 0) {
|
||||
debug("send() == 0: " + std::string(std::strerror(errno)));
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
return false;
|
||||
} else if (Temp < 0) {
|
||||
debug("send() < 0: " + std::string(std::strerror(errno))); //TODO fix it was spamming yet everyone stayed on the server
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
CloseSocketProper(c.GetTCPSock());
|
||||
return false;
|
||||
}
|
||||
Sent += Temp;
|
||||
c.UpdatePingTime();
|
||||
} while (Sent < Size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) {
|
||||
if (BytesRcv == 0) {
|
||||
debug("(TCP) Connection closing...");
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
return false;
|
||||
} else if (BytesRcv < 0) {
|
||||
#ifdef WIN32
|
||||
debug(("(TCP) recv failed with error: ") + std::to_string(WSAGetLastError()));
|
||||
#else // unix
|
||||
debug(("(TCP) recv failed with error: ") + std::string(strerror(errno)));
|
||||
#endif // WIN32
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
info(("Closing socket in CheckBytes, BytesRcv < 0"));
|
||||
CloseSocketProper(c.GetTCPSock());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string TNetwork::TCPRcv(TClient& c) {
|
||||
int32_t Header, BytesRcv = 0, Temp;
|
||||
if (c.GetStatus() < 0)
|
||||
return "";
|
||||
|
||||
std::vector<char> Data(sizeof(Header));
|
||||
do {
|
||||
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], 4 - BytesRcv, 0);
|
||||
if (!CheckBytes(c, Temp)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < 4)"));
|
||||
#endif // DEBUG
|
||||
return "";
|
||||
}
|
||||
BytesRcv += Temp;
|
||||
} while (size_t(BytesRcv) < sizeof(Header));
|
||||
memcpy(&Header, &Data[0], sizeof(Header));
|
||||
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": expecting ") + std::to_string(Header) + (" bytes."));
|
||||
#endif // DEBUG
|
||||
if (!CheckBytes(c, BytesRcv)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes"));
|
||||
#endif // DEBUG
|
||||
return "";
|
||||
}
|
||||
if (Header < 100 * MB) {
|
||||
Data.resize(Header);
|
||||
} else {
|
||||
ClientKick(c, "Header size limit exceeded");
|
||||
warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client.");
|
||||
return "";
|
||||
}
|
||||
BytesRcv = 0;
|
||||
do {
|
||||
Temp = recv(c.GetTCPSock(), &Data[BytesRcv], Header - BytesRcv, 0);
|
||||
if (!CheckBytes(c, Temp)) {
|
||||
#ifdef DEBUG
|
||||
error(std::string(__func__) + (": failed on CheckBytes in while(BytesRcv < Header)"));
|
||||
#endif // DEBUG
|
||||
|
||||
return "";
|
||||
}
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
|
||||
#endif // DEBUG
|
||||
BytesRcv += Temp;
|
||||
} while (BytesRcv < Header);
|
||||
#ifdef DEBUG
|
||||
//debug(std::string(__func__) + (": finished recv with Temp: ") + std::to_string(Temp) + (", BytesRcv: ") + std::to_string(BytesRcv));
|
||||
#endif // DEBUG
|
||||
std::string Ret(Data.data(), Header);
|
||||
|
||||
if (Ret.substr(0, 4) == "ABG:") {
|
||||
Ret = DeComp(Ret.substr(4));
|
||||
}
|
||||
#ifdef DEBUG
|
||||
//debug("Parsing from " + c->GetName() + " -> " +std::to_string(Ret.size()));
|
||||
#endif
|
||||
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void TNetwork::ClientKick(TClient& c, const std::string& R) {
|
||||
info("Client kicked: " + R);
|
||||
if (!TCPSend(c, "E" + R)) {
|
||||
// TODO handle
|
||||
}
|
||||
c.SetStatus(-2);
|
||||
|
||||
if (c.GetTCPSock())
|
||||
CloseSocketProper(c.GetTCPSock());
|
||||
|
||||
if (c.GetDownSock())
|
||||
CloseSocketProper(c.GetDownSock());
|
||||
}
|
||||
void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
|
||||
while (!c.expired()) {
|
||||
auto Client = c.lock();
|
||||
if (Client->GetStatus() < 0) {
|
||||
debug("client status < 0, breaking client loop");
|
||||
break;
|
||||
}
|
||||
if (!Client->IsSyncing() && Client->IsSynced() && Client->MissedPacketQueueSize() != 0) {
|
||||
//debug("sending " + std::to_string(Client->MissedPacketQueueSize()) + " queued packets");
|
||||
while (Client->MissedPacketQueueSize() > 0) {
|
||||
std::string QData {};
|
||||
{ // locked context
|
||||
std::unique_lock lock(Client->MissedPacketQueueMutex());
|
||||
if (Client->MissedPacketQueueSize() <= 0) {
|
||||
break;
|
||||
}
|
||||
QData = Client->MissedPacketQueue().front();
|
||||
Client->MissedPacketQueue().pop();
|
||||
} // end locked context
|
||||
// debug("sending a missed packet: " + QData);
|
||||
if (!TCPSend(*Client, QData, true)) {
|
||||
if (Client->GetStatus() > -1)
|
||||
Client->SetStatus(-1);
|
||||
{
|
||||
std::unique_lock lock(Client->MissedPacketQueueMutex());
|
||||
while (!Client->MissedPacketQueue().empty()) {
|
||||
Client->MissedPacketQueue().pop();
|
||||
}
|
||||
}
|
||||
CloseSocketProper(Client->GetTCPSock());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
|
||||
// TODO: the c.expired() might cause issues here, remove if you end up here with your debugger
|
||||
if (c.expired() || c.lock()->GetTCPSock() == -1) {
|
||||
mServer.RemoveClient(c);
|
||||
return;
|
||||
}
|
||||
OnConnect(c);
|
||||
RegisterThread("(" + std::to_string(c.lock()->GetID()) + ") \"" + c.lock()->GetName() + "\"");
|
||||
|
||||
std::thread QueueSync(&TNetwork::Looper, this, c);
|
||||
|
||||
while (true) {
|
||||
if (c.expired())
|
||||
break;
|
||||
auto Client = c.lock();
|
||||
if (Client->GetStatus() < 0) {
|
||||
debug("client status < 0, breaking client loop");
|
||||
break;
|
||||
}
|
||||
|
||||
auto res = TCPRcv(*Client);
|
||||
if (res == "") {
|
||||
debug("TCPRcv error, break client loop");
|
||||
break;
|
||||
}
|
||||
TServer::GlobalParser(c, res, mPPSMonitor, *this);
|
||||
}
|
||||
if (QueueSync.joinable())
|
||||
QueueSync.join();
|
||||
|
||||
if (!c.expired()) {
|
||||
auto Client = c.lock();
|
||||
OnDisconnect(c, Client->GetStatus() == -2);
|
||||
} else {
|
||||
warn("client expired in TCPClient, should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::UpdatePlayer(TClient& Client) {
|
||||
std::string Packet = ("Ss") + std::to_string(mServer.ClientCount()) + "/" + std::to_string(Application::Settings.MaxPlayers) + ":";
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
Packet += c->GetName() + ",";
|
||||
}
|
||||
return true;
|
||||
});
|
||||
Packet = Packet.substr(0, Packet.length() - 1);
|
||||
Client.EnqueuePacket(Packet);
|
||||
//(void)Respond(Client, Packet, true);
|
||||
}
|
||||
|
||||
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) {
|
||||
Assert(!ClientPtr.expired());
|
||||
auto LockedClientPtr = ClientPtr.lock();
|
||||
TClient& c = *LockedClientPtr;
|
||||
info(c.GetName() + (" Connection Terminated"));
|
||||
std::string Packet;
|
||||
TClient::TSetOfVehicleData VehicleData;
|
||||
{ // Vehicle Data Lock Scope
|
||||
auto LockedData = c.GetAllCars();
|
||||
VehicleData = *LockedData.VehicleData;
|
||||
} // End Vehicle Data Lock Scope
|
||||
for (auto& v : VehicleData) {
|
||||
Packet = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(v.ID());
|
||||
SendToAll(&c, Packet, false, true);
|
||||
}
|
||||
if (kicked)
|
||||
Packet = ("L") + c.GetName() + (" was kicked!");
|
||||
else
|
||||
Packet = ("L") + c.GetName() + (" left the server!");
|
||||
SendToAll(&c, Packet, false, true);
|
||||
Packet.clear();
|
||||
TriggerLuaEvent(("onPlayerDisconnect"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID() } }), false);
|
||||
if (c.GetTCPSock())
|
||||
CloseSocketProper(c.GetTCPSock());
|
||||
if (c.GetDownSock())
|
||||
CloseSocketProper(c.GetDownSock());
|
||||
mServer.RemoveClient(ClientPtr);
|
||||
}
|
||||
|
||||
int TNetwork::OpenID() {
|
||||
int ID = 0;
|
||||
bool found;
|
||||
do {
|
||||
found = true;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
auto c = ClientPtr.lock();
|
||||
if (c->GetID() == ID) {
|
||||
found = false;
|
||||
ID++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} while (!found);
|
||||
return ID;
|
||||
}
|
||||
|
||||
void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
|
||||
Assert(!c.expired());
|
||||
info("Client connected");
|
||||
auto LockedClient = c.lock();
|
||||
LockedClient->SetID(OpenID());
|
||||
info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
|
||||
TriggerLuaEvent("onPlayerConnecting", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
||||
SyncResources(*LockedClient);
|
||||
if (LockedClient->GetStatus() < 0)
|
||||
return;
|
||||
(void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); //Send the Map on connect
|
||||
info(LockedClient->GetName() + " : Connected");
|
||||
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
||||
}
|
||||
|
||||
void TNetwork::SyncResources(TClient& c) {
|
||||
#ifndef DEBUG
|
||||
try {
|
||||
#endif
|
||||
if (!TCPSend(c, "P" + std::to_string(c.GetID()))) {
|
||||
// TODO handle
|
||||
}
|
||||
std::string Data;
|
||||
while (c.GetStatus() > -1) {
|
||||
Data = TCPRcv(c);
|
||||
if (Data == "Done")
|
||||
break;
|
||||
Parse(c, Data);
|
||||
}
|
||||
#ifndef DEBUG
|
||||
} catch (std::exception& e) {
|
||||
error("Exception! : " + std::string(e.what()));
|
||||
c.SetStatus(-1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TNetwork::Parse(TClient& c, const std::string& Packet) {
|
||||
if (Packet.empty())
|
||||
return;
|
||||
char Code = Packet.at(0), SubCode = 0;
|
||||
if (Packet.length() > 1)
|
||||
SubCode = Packet.at(1);
|
||||
switch (Code) {
|
||||
case 'f':
|
||||
SendFile(c, Packet.substr(1));
|
||||
return;
|
||||
case 'S':
|
||||
if (SubCode == 'R') {
|
||||
debug("Sending Mod Info");
|
||||
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
|
||||
if (ToSend.empty())
|
||||
ToSend = "-";
|
||||
if (!TCPSend(c, ToSend)) {
|
||||
// TODO: error
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TNetwork::SendFile(TClient& c, const std::string& Name) {
|
||||
info(c.GetName() + " requesting : " + Name.substr(Name.find_last_of('/')));
|
||||
|
||||
if (!std::filesystem::exists(Name)) {
|
||||
if (!TCPSend(c, "CO")) {
|
||||
// TODO: handle
|
||||
}
|
||||
warn("File " + Name + " could not be accessed!");
|
||||
return;
|
||||
} else {
|
||||
if (!TCPSend(c, "AG")) {
|
||||
// TODO: handle
|
||||
}
|
||||
}
|
||||
|
||||
///Wait for connections
|
||||
int T = 0;
|
||||
while (c.GetDownSock() < 1 && T < 50) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
T++;
|
||||
}
|
||||
|
||||
if (c.GetDownSock() < 1) {
|
||||
error("Client doesn't have a download socket!");
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t Size = size_t(std::filesystem::file_size(Name)), MSize = Size / 2;
|
||||
|
||||
std::thread SplitThreads[2] {
|
||||
std::thread([&] {
|
||||
SplitLoad(c, 0, MSize, false, Name);
|
||||
}),
|
||||
std::thread([&] {
|
||||
SplitLoad(c, MSize, Size, true, Name);
|
||||
})
|
||||
};
|
||||
|
||||
for (auto& SplitThread : SplitThreads) {
|
||||
if (SplitThread.joinable()) {
|
||||
SplitThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
uint32_t Split = 0x7735940; //125MB
|
||||
char* Data;
|
||||
if (Size > Split)
|
||||
Data = new char[Split];
|
||||
else
|
||||
Data = new char[Size];
|
||||
SOCKET TCPSock;
|
||||
if (D)
|
||||
TCPSock = c.GetDownSock();
|
||||
else
|
||||
TCPSock = c.GetTCPSock();
|
||||
info("Split load Socket " + std::to_string(TCPSock));
|
||||
while (c.GetStatus() > -1 && Sent < Size) {
|
||||
size_t Diff = Size - Sent;
|
||||
if (Diff > Split) {
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(Data, Split);
|
||||
if (!TCPSendRaw(c, TCPSock, Data, Split)) {
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
break;
|
||||
}
|
||||
Sent += Split;
|
||||
} else {
|
||||
f.seekg(Sent, std::ios_base::beg);
|
||||
f.read(Data, Diff);
|
||||
if (!TCPSendRaw(c, TCPSock, Data, int32_t(Diff))) {
|
||||
if (c.GetStatus() > -1)
|
||||
c.SetStatus(-1);
|
||||
break;
|
||||
}
|
||||
Sent += Diff;
|
||||
}
|
||||
}
|
||||
delete[] Data;
|
||||
f.close();
|
||||
}
|
||||
|
||||
bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) {
|
||||
intmax_t Sent = 0;
|
||||
do {
|
||||
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), 0);
|
||||
if (Temp < 1) {
|
||||
info("Socket Closed! " + std::to_string(socket));
|
||||
CloseSocketProper(socket);
|
||||
return false;
|
||||
}
|
||||
Sent += Temp;
|
||||
C.UpdatePingTime();
|
||||
} while (Sent < Size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TNetwork::SendLarge(TClient& c, std::string Data, bool isSync) {
|
||||
if (Data.length() > 400) {
|
||||
std::string CMP(Comp(Data));
|
||||
Data = "ABG:" + CMP;
|
||||
}
|
||||
return TCPSend(c, Data, isSync);
|
||||
}
|
||||
|
||||
bool TNetwork::Respond(TClient& c, const std::string& MSG, bool Rel, bool isSync) {
|
||||
char C = MSG.at(0);
|
||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||
if (C == 'O' || C == 'T' || MSG.length() > 1000) {
|
||||
return SendLarge(c, MSG, isSync);
|
||||
} else {
|
||||
return TCPSend(c, MSG, isSync);
|
||||
}
|
||||
} else {
|
||||
return UDPSend(c, MSG);
|
||||
}
|
||||
}
|
||||
|
||||
bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
|
||||
if (c.expired()) {
|
||||
return false;
|
||||
}
|
||||
auto LockedClient = c.lock();
|
||||
if (LockedClient->IsSynced())
|
||||
return true;
|
||||
// Syncing, later set isSynced
|
||||
// after syncing is done, we apply all packets they missed
|
||||
if (!Respond(*LockedClient, ("Sn") + LockedClient->GetName(), true)) {
|
||||
return false;
|
||||
}
|
||||
// ignore error
|
||||
(void)SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true);
|
||||
|
||||
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false);
|
||||
LockedClient->SetIsSyncing(true);
|
||||
bool Return = false;
|
||||
bool res = true;
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
TClient::TSetOfVehicleData VehicleData;
|
||||
{ // Vehicle Data Lock Scope
|
||||
auto LockedData = client->GetAllCars();
|
||||
VehicleData = *LockedData.VehicleData;
|
||||
} // End Vehicle Data Lock Scope
|
||||
if (client != LockedClient) {
|
||||
for (auto& v : VehicleData) {
|
||||
if (LockedClient->GetStatus() < 0) {
|
||||
Return = true;
|
||||
res = false;
|
||||
return false;
|
||||
}
|
||||
res = Respond(*LockedClient, v.Data(), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
LockedClient->SetIsSyncing(false);
|
||||
if (Return) {
|
||||
return res;
|
||||
}
|
||||
LockedClient->SetIsSynced(true);
|
||||
info(LockedClient->GetName() + (" is now synced!"));
|
||||
return true;
|
||||
}
|
||||
|
||||
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
|
||||
if (!Self)
|
||||
Assert(c);
|
||||
char C = Data.at(0);
|
||||
bool ret = true;
|
||||
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> Client;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
Client = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
if (Self || Client.get() != c) {
|
||||
if (Client->IsSynced() || Client->IsSyncing()) {
|
||||
if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') {
|
||||
if (C == 'O' || C == 'T' || Data.length() > 1000) {
|
||||
if (Data.length() > 400) {
|
||||
std::string CMP(Comp(Data));
|
||||
Client->EnqueuePacket("ABG:" + CMP);
|
||||
} else {
|
||||
Client->EnqueuePacket(Data);
|
||||
}
|
||||
//ret = SendLarge(*Client, Data);
|
||||
} else {
|
||||
Client->EnqueuePacket(Data);
|
||||
//ret = TCPSend(*Client, Data);
|
||||
}
|
||||
} else {
|
||||
ret = UDPSend(*Client, Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!ret) {
|
||||
// TODO: handle
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool TNetwork::UDPSend(TClient& Client, std::string Data) const {
|
||||
if (!Client.IsConnected() || Client.GetStatus() < 0) {
|
||||
// this can happen if we try to send a packet to a client that is either
|
||||
// 1. not yet fully connected, or
|
||||
// 2. disconnected and not yet fully removed
|
||||
// this is fine can can be ignored :^)
|
||||
return true;
|
||||
}
|
||||
sockaddr_in Addr = Client.GetUDPAddr();
|
||||
auto AddrSize = sizeof(Client.GetUDPAddr());
|
||||
if (Data.length() > 400) {
|
||||
std::string CMP(Comp(Data));
|
||||
Data = "ABG:" + CMP;
|
||||
}
|
||||
#ifdef WIN32
|
||||
int sendOk;
|
||||
int len = static_cast<int>(Data.size());
|
||||
#else
|
||||
int64_t sendOk;
|
||||
size_t len = Data.size();
|
||||
#endif // WIN32
|
||||
|
||||
sendOk = sendto(mUDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, int(AddrSize));
|
||||
#ifdef WIN32
|
||||
if (sendOk == -1) {
|
||||
debug(("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError()));
|
||||
if (Client.GetStatus() > -1)
|
||||
Client.SetStatus(-1);
|
||||
return false;
|
||||
} else if (sendOk == 0) {
|
||||
debug(("(UDP) sendto returned 0"));
|
||||
if (Client.GetStatus() > -1)
|
||||
Client.SetStatus(-1);
|
||||
return false;
|
||||
}
|
||||
#else // unix
|
||||
if (sendOk == -1) {
|
||||
debug(("(UDP) Send Failed Code : ") + std::string(strerror(errno)));
|
||||
if (Client.GetStatus() > -1)
|
||||
Client.SetStatus(-1);
|
||||
return false;
|
||||
} else if (sendOk == 0) {
|
||||
debug(("(UDP) sendto returned 0"));
|
||||
if (Client.GetStatus() > -1)
|
||||
Client.SetStatus(-1);
|
||||
return false;
|
||||
}
|
||||
#endif // WIN32
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string TNetwork::UDPRcvFromClient(sockaddr_in& client) const {
|
||||
size_t clientLength = sizeof(client);
|
||||
std::array<char, 1024> Ret {};
|
||||
#ifdef WIN32
|
||||
auto Rcv = recvfrom(mUDPSock, Ret.data(), int(Ret.size()), 0, (sockaddr*)&client, (int*)&clientLength);
|
||||
#else // unix
|
||||
int64_t Rcv = recvfrom(mUDPSock, Ret.data(), Ret.size(), 0, (sockaddr*)&client, (socklen_t*)&clientLength);
|
||||
#endif // WIN32
|
||||
|
||||
if (Rcv == -1) {
|
||||
#ifdef WIN32
|
||||
error(("(UDP) Error receiving from Client! Code : ") + std::to_string(WSAGetLastError()));
|
||||
#else // unix
|
||||
error(("(UDP) Error receiving from Client! Code : ") + std::string(strerror(errno)));
|
||||
#endif // WIN32
|
||||
return "";
|
||||
}
|
||||
return std::string(Ret.begin(), Ret.begin() + Rcv);
|
||||
}
|
||||
66
src/TPPSMonitor.cpp
Normal file
66
src/TPPSMonitor.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "TPPSMonitor.h"
|
||||
#include "Client.h"
|
||||
#include "TNetwork.h"
|
||||
|
||||
TPPSMonitor::TPPSMonitor(TServer& Server)
|
||||
: mServer(Server) {
|
||||
Application::SetPPS("-");
|
||||
Application::RegisterShutdownHandler([&] {
|
||||
if (mThread.joinable()) {
|
||||
debug("shutting down PPSMonitor");
|
||||
mShutdown = true;
|
||||
mThread.join();
|
||||
debug("shut down PPSMonitor");
|
||||
}
|
||||
});
|
||||
Start();
|
||||
}
|
||||
void TPPSMonitor::operator()() {
|
||||
RegisterThread("PPSMonitor");
|
||||
while (!mNetwork) {
|
||||
// hard spi
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
info("PPSMonitor starting");
|
||||
std::vector<std::shared_ptr<TClient>> TimedOutClients;
|
||||
while (!mShutdown) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
int C = 0, V = 0;
|
||||
if (mServer.ClientCount() == 0) {
|
||||
Application::SetPPS("-");
|
||||
continue;
|
||||
}
|
||||
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
|
||||
std::shared_ptr<TClient> c;
|
||||
{
|
||||
ReadLock Lock(mServer.GetClientMutex());
|
||||
if (!ClientPtr.expired()) {
|
||||
c = ClientPtr.lock();
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
if (c->GetCarCount() > 0) {
|
||||
C++;
|
||||
V += c->GetCarCount();
|
||||
}
|
||||
// kick on "no ping"
|
||||
if (c->SecondsSinceLastPing() > (5 * 60)) {
|
||||
debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS());
|
||||
TimedOutClients.push_back(c);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
for (auto& ClientToKick : TimedOutClients) {
|
||||
Network().ClientKick(*ClientToKick, "Timeout (no ping for >5 min)");
|
||||
}
|
||||
TimedOutClients.clear();
|
||||
if (C == 0 || mInternalPPS == 0) {
|
||||
Application::SetPPS("-");
|
||||
} else {
|
||||
int R = (mInternalPPS / C) / V;
|
||||
Application::SetPPS(std::to_string(R));
|
||||
}
|
||||
mInternalPPS = 0;
|
||||
}
|
||||
}
|
||||
32
src/TResourceManager.cpp
Normal file
32
src/TResourceManager.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "TResourceManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
TResourceManager::TResourceManager() {
|
||||
std::string Path = Application::Settings.Resource + "/Client";
|
||||
if (!fs::exists(Path))
|
||||
fs::create_directories(Path);
|
||||
for (const auto& entry : fs::directory_iterator(Path)) {
|
||||
std::string File(entry.path().string());
|
||||
if (auto pos = File.find(".zip"); pos != std::string::npos) {
|
||||
if (File.length() - pos == 4) {
|
||||
std::replace(File.begin(), File.end(),'\\','/');
|
||||
mFileList += File + ';';
|
||||
if(auto i = File.find_last_of('/'); i != std::string::npos){
|
||||
++i;
|
||||
File = File.substr(i,pos-i);
|
||||
}
|
||||
mTrimmedList += File + ';';
|
||||
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
|
||||
mMaxModSize += size_t(fs::file_size(entry.path()));
|
||||
mModsLoaded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mModsLoaded)
|
||||
info("Loaded " + std::to_string(mModsLoaded) + " Mods");
|
||||
}
|
||||
348
src/TServer.cpp
Normal file
348
src/TServer.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
#include "TServer.h"
|
||||
#include "Client.h"
|
||||
#include "Common.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TPPSMonitor.h"
|
||||
#include <TLuaFile.h>
|
||||
#include <any>
|
||||
#include <sstream>
|
||||
|
||||
#undef GetObject //Fixes Windows
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
namespace json = rapidjson;
|
||||
|
||||
TServer::TServer(int argc, char** argv) {
|
||||
info("BeamMP Server v" + Application::ServerVersion());
|
||||
if (argc > 1) {
|
||||
Application::Settings.CustomIP = argv[1];
|
||||
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();
|
||||
warn("IP Specified is invalid! Ignoring");
|
||||
} else {
|
||||
info("server started with custom IP");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
|
||||
if (!WeakClientPtr.expired()) {
|
||||
TClient& Client = *WeakClientPtr.lock();
|
||||
debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
|
||||
Client.ClearCars();
|
||||
WriteLock Lock(mClientsMutex);
|
||||
mClients.erase(WeakClientPtr.lock());
|
||||
}
|
||||
}
|
||||
|
||||
std::weak_ptr<TClient> TServer::InsertNewClient() {
|
||||
debug("inserting new client (" + std::to_string(ClientCount()) + ")");
|
||||
WriteLock Lock(mClientsMutex);
|
||||
auto [Iter, Replaced] = mClients.insert(std::make_shared<TClient>(*this));
|
||||
return *Iter;
|
||||
}
|
||||
|
||||
void TServer::ForEachClient(const std::function<bool(std::weak_ptr<TClient>)>& Fn) {
|
||||
decltype(mClients) Clients;
|
||||
{
|
||||
ReadLock lock(mClientsMutex);
|
||||
Clients = mClients;
|
||||
}
|
||||
for (auto& Client : Clients) {
|
||||
if (!Fn(Client)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t TServer::ClientCount() const {
|
||||
ReadLock Lock(mClientsMutex);
|
||||
return mClients.size();
|
||||
}
|
||||
|
||||
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) {
|
||||
if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
|
||||
abort();
|
||||
}
|
||||
if (Packet.substr(0, 4) == "ABG:") {
|
||||
Packet = DeComp(Packet.substr(4));
|
||||
}
|
||||
if (Packet.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Client.expired()) {
|
||||
return;
|
||||
}
|
||||
auto LockedClient = Client.lock();
|
||||
|
||||
std::any Res;
|
||||
char Code = Packet.at(0);
|
||||
|
||||
//V to Z
|
||||
if (Code <= 90 && Code >= 86) {
|
||||
PPSMonitor.IncrementInternalPPS();
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, false);
|
||||
return;
|
||||
}
|
||||
switch (Code) {
|
||||
case 'H': // initial connection
|
||||
#ifdef DEBUG
|
||||
debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
|
||||
#endif
|
||||
if (!Network.SyncClient(Client)) {
|
||||
// TODO handle
|
||||
}
|
||||
return;
|
||||
case 'p':
|
||||
if (!Network.Respond(*LockedClient, ("p"), false)) {
|
||||
// failed to send
|
||||
if (LockedClient->GetStatus() > -1) {
|
||||
LockedClient->SetStatus(-1);
|
||||
}
|
||||
} else {
|
||||
Network.UpdatePlayer(*LockedClient);
|
||||
}
|
||||
return;
|
||||
case 'O':
|
||||
if (Packet.length() > 1000) {
|
||||
debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.length()));
|
||||
}
|
||||
ParseVehicle(*LockedClient, Packet, Network);
|
||||
return;
|
||||
case 'J':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||
return;
|
||||
case 'C':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
|
||||
break;
|
||||
Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true);
|
||||
if (std::any_cast<int>(Res))
|
||||
break;
|
||||
Network.SendToAll(nullptr, Packet, true, true);
|
||||
return;
|
||||
case 'E':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
HandleEvent(*LockedClient, Packet);
|
||||
return;
|
||||
case 'N':
|
||||
debug("got 'N' packet (" + std::to_string(Packet.size()) + ")");
|
||||
Network.SendToAll(LockedClient.get(), Packet, false, true);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TServer::HandleEvent(TClient& c, const std::string& Data) {
|
||||
std::stringstream ss(Data);
|
||||
std::string t, Name;
|
||||
int a = 0;
|
||||
while (std::getline(ss, t, ':')) {
|
||||
switch (a) {
|
||||
case 1:
|
||||
Name = t;
|
||||
break;
|
||||
case 2:
|
||||
TriggerLuaEvent(Name, false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), t } }), false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (a == 2)
|
||||
break;
|
||||
a++;
|
||||
}
|
||||
}
|
||||
bool TServer::IsUnicycle(TClient &c, const std::string &CarJson) {
|
||||
rapidjson::Document Car;
|
||||
Car.Parse(CarJson.c_str(), CarJson.size());
|
||||
if(Car.HasParseError()){
|
||||
error("Failed to parse vehicle data -> " + CarJson);
|
||||
} else if (Car["jbm"].IsString() && std::string(Car["jbm"].GetString()) == "unicycle") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
|
||||
|
||||
if(c.GetUnicycleID() > -1 && (c.GetCarCount() - 1) < Application::Settings.MaxCars){
|
||||
return true;
|
||||
}
|
||||
|
||||
if(IsUnicycle(c,CarJson)){
|
||||
c.SetUnicycleID(ID);
|
||||
return true;
|
||||
}
|
||||
|
||||
return Application::Settings.MaxCars > c.GetCarCount();
|
||||
}
|
||||
|
||||
void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network) {
|
||||
if (Pckt.length() < 4)
|
||||
return;
|
||||
std::string Packet = Pckt;
|
||||
char Code = Packet.at(1);
|
||||
int PID = -1;
|
||||
int VID = -1, Pos;
|
||||
std::string Data = Packet.substr(3), pid, vid;
|
||||
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset
|
||||
case 's':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
if (Data.at(0) == '0') {
|
||||
int CarID = c.GetOpenCarID();
|
||||
debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID));
|
||||
|
||||
std::string CarJson = Packet.substr(5);
|
||||
Packet = "Os:" + c.GetRoles() + ":" + c.GetName() + ":" + std::to_string(c.GetID()) + "-" + std::to_string(CarID) + ":" + CarJson;
|
||||
auto Res = TriggerLuaEvent(("onVehicleSpawn"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), CarID, Packet.substr(3) } }), true);
|
||||
|
||||
if (ShouldSpawn(c, CarJson, CarID) && std::any_cast<int>(Res) == 0) {
|
||||
c.AddNewCar(CarID, Packet);
|
||||
Network.SendToAll(nullptr, Packet, true, true);
|
||||
} else {
|
||||
if (!Network.Respond(c, Packet, true)) {
|
||||
// TODO: handle
|
||||
}
|
||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(CarID);
|
||||
if (!Network.Respond(c, Destroy, true)) {
|
||||
// TODO: handle
|
||||
}
|
||||
debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'c':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
pid = Data.substr(0, Data.find('-'));
|
||||
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1);
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
PID = stoi(pid);
|
||||
VID = stoi(vid);
|
||||
}
|
||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||
auto Res = TriggerLuaEvent(("onVehicleEdited"), false, nullptr,
|
||||
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID, Packet.substr(3) } }),
|
||||
true);
|
||||
|
||||
if ((c.GetUnicycleID() != VID || IsUnicycle(c,Packet.substr(Packet.find('{'))))
|
||||
&& std::any_cast<int>(Res) == 0) {
|
||||
Network.SendToAll(&c, Packet, false, true);
|
||||
Apply(c, VID, Packet);
|
||||
} else {
|
||||
if(c.GetUnicycleID() == VID){
|
||||
c.SetUnicycleID(-1);
|
||||
}
|
||||
std::string Destroy = "Od:" + std::to_string(c.GetID()) + "-" + std::to_string(VID);
|
||||
if (!Network.Respond(c, Destroy, true)) {
|
||||
// TODO: handle
|
||||
}
|
||||
c.DeleteCar(VID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 'd':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
pid = Data.substr(0, Data.find('-'));
|
||||
vid = Data.substr(Data.find('-') + 1);
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
PID = stoi(pid);
|
||||
VID = stoi(vid);
|
||||
}
|
||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||
if(c.GetUnicycleID() == VID){
|
||||
c.SetUnicycleID(-1);
|
||||
}
|
||||
Network.SendToAll(nullptr, Packet, true, true);
|
||||
TriggerLuaEvent(("onVehicleDeleted"), false, nullptr,
|
||||
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID } }), false);
|
||||
c.DeleteCar(VID);
|
||||
debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID));
|
||||
}
|
||||
return;
|
||||
case 'r':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
Pos = int(Data.find('-'));
|
||||
pid = Data.substr(0, Pos++);
|
||||
vid = Data.substr(Pos, Data.find(':') - Pos);
|
||||
|
||||
if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
|
||||
PID = stoi(pid);
|
||||
VID = stoi(vid);
|
||||
}
|
||||
|
||||
if (PID != -1 && VID != -1 && PID == c.GetID()) {
|
||||
Data = Data.substr(Data.find('{'));
|
||||
TriggerLuaEvent("onVehicleReset", false, nullptr,
|
||||
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID, Data } }),
|
||||
false);
|
||||
Network.SendToAll(&c, Packet, false, true);
|
||||
}
|
||||
return;
|
||||
case 't':
|
||||
#ifdef DEBUG
|
||||
debug(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
|
||||
#endif
|
||||
Network.SendToAll(&c, Packet, false, true);
|
||||
return;
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
warn(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
|
||||
#endif // DEBUG
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
|
||||
std::string Packet = pckt.substr(pckt.find('{')), VD = c.GetCarData(VID);
|
||||
std::string Header = VD.substr(0, VD.find('{'));
|
||||
|
||||
VD = VD.substr(VD.find('{'));
|
||||
rapidjson::Document Veh, Pack;
|
||||
Veh.Parse(VD.c_str());
|
||||
if (Veh.HasParseError()) {
|
||||
error("Could not get vehicle config!");
|
||||
return;
|
||||
}
|
||||
Pack.Parse(Packet.c_str());
|
||||
if (Pack.HasParseError() || Pack.IsNull()) {
|
||||
error("Could not get active vehicle config!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& M : Pack.GetObject()) {
|
||||
if (Veh[M.name].IsNull()) {
|
||||
Veh.AddMember(M.name, M.value, Veh.GetAllocator());
|
||||
} else {
|
||||
Veh[M.name] = Pack[M.name];
|
||||
}
|
||||
}
|
||||
rapidjson::StringBuffer Buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(Buffer);
|
||||
Veh.Accept(writer);
|
||||
c.SetCarData(VID, Header + Buffer.GetString());
|
||||
}
|
||||
|
||||
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
|
||||
debug("inserting client (" + std::to_string(ClientCount()) + ")");
|
||||
WriteLock Lock(mClientsMutex); //TODO why is there 30+ threads locked here
|
||||
(void)mClients.insert(NewClient);
|
||||
}
|
||||
18
src/VehicleData.cpp
Normal file
18
src/VehicleData.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "VehicleData.h"
|
||||
|
||||
#include <utility>
|
||||
#include "Common.h"
|
||||
|
||||
TVehicleData::TVehicleData(int ID, std::string Data)
|
||||
: mID(ID)
|
||||
, mData(std::move(Data)) {
|
||||
#ifdef DEBUG
|
||||
debug("vehicle " + std::to_string(mID) + " constructed");
|
||||
#endif
|
||||
}
|
||||
|
||||
TVehicleData::~TVehicleData() {
|
||||
#ifdef DEBUG
|
||||
debug("vehicle " + std::to_string(mID) + " destroyed");
|
||||
#endif
|
||||
}
|
||||
133
src/logger.cpp
133
src/logger.cpp
@@ -1,133 +0,0 @@
|
||||
// Copyright (c) 2019-present Anonymous275.
|
||||
// BeamMP Server code is not in the public domain and is not free software.
|
||||
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
|
||||
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
|
||||
///
|
||||
/// Created by Anonymous275 on 7/17/2020
|
||||
///
|
||||
#include "Logger.h"
|
||||
#include "RWMutex.h"
|
||||
#include "Security/Enc.h"
|
||||
#include "Settings.h"
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
static RWMutex ThreadNameMapMutex;
|
||||
static std::unordered_map<std::thread::id, std::string> ThreadNameMap;
|
||||
|
||||
std::string ThreadName() {
|
||||
ReadLock lock(ThreadNameMapMutex);
|
||||
std::string Name;
|
||||
if (ThreadNameMap.find(std::this_thread::get_id()) != ThreadNameMap.end()) {
|
||||
Name = ThreadNameMap.at(std::this_thread::get_id());
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
Name = ss.str();
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
|
||||
void SetThreadName(const std::string& Name, bool overwrite) {
|
||||
WriteLock lock(ThreadNameMapMutex);
|
||||
if (overwrite || ThreadNameMap.find(std::this_thread::get_id()) == ThreadNameMap.end()) {
|
||||
ThreadNameMap[std::this_thread::get_id()] = Name;
|
||||
}
|
||||
}
|
||||
|
||||
std::string getDate() {
|
||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||
time_t tt = std::chrono::system_clock::to_time_t(now);
|
||||
tm local_tm {};
|
||||
#ifdef WIN32
|
||||
localtime_s(&local_tm, &tt);
|
||||
#else // unix
|
||||
localtime_r(&tt, &local_tm);
|
||||
#endif // WIN32
|
||||
std::stringstream date;
|
||||
int S = local_tm.tm_sec;
|
||||
int M = local_tm.tm_min;
|
||||
int H = local_tm.tm_hour;
|
||||
std::string Secs = (S > 9 ? std::to_string(S) : "0" + std::to_string(S));
|
||||
std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M));
|
||||
std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H));
|
||||
date
|
||||
<< "["
|
||||
<< local_tm.tm_mday << "/"
|
||||
<< local_tm.tm_mon + 1 << "/"
|
||||
<< local_tm.tm_year + 1900 << " "
|
||||
<< Hour << ":"
|
||||
<< Min << ":"
|
||||
<< Secs
|
||||
<< "] ";
|
||||
if (Debug) {
|
||||
date << ThreadName()
|
||||
<< " ";
|
||||
}
|
||||
return date.str();
|
||||
}
|
||||
|
||||
void InitLog() {
|
||||
std::ofstream LFS;
|
||||
LFS.open(("Server.log"));
|
||||
if (!LFS.is_open()) {
|
||||
error(("logger file init failed!"));
|
||||
} else
|
||||
LFS.close();
|
||||
}
|
||||
std::mutex LogLock;
|
||||
|
||||
void DebugPrintTIDInternal(const std::string& func, bool overwrite) {
|
||||
// we need to print to cout here as we might crash before all console output is handled,
|
||||
// due to segfaults or asserts.
|
||||
SetThreadName(func, overwrite);
|
||||
#ifdef DEBUG
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::stringstream Print;
|
||||
Print << "(debug build) Thread '" << std::this_thread::get_id() << "' is " << func << "\n";
|
||||
ConsoleOut(Print.str());
|
||||
#endif // DEBUG
|
||||
}
|
||||
|
||||
void addToLog(const std::string& Line) {
|
||||
std::ofstream LFS;
|
||||
LFS.open(("Server.log"), std::ios_base::app);
|
||||
LFS << Line.c_str();
|
||||
LFS.close();
|
||||
}
|
||||
void info(const std::string& toPrint) {
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::string Print = getDate() + ("[INFO] ") + toPrint + "\n";
|
||||
ConsoleOut(Print);
|
||||
addToLog(Print);
|
||||
}
|
||||
void debug(const std::string& toPrint) {
|
||||
if (!Debug)
|
||||
return;
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::string Print = getDate() + ("[DEBUG] ") + toPrint + "\n";
|
||||
ConsoleOut(Print);
|
||||
addToLog(Print);
|
||||
}
|
||||
void warn(const std::string& toPrint) {
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::string Print = getDate() + ("[WARN] ") + toPrint + "\n";
|
||||
ConsoleOut(Print);
|
||||
addToLog(Print);
|
||||
}
|
||||
void error(const std::string& toPrint) {
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::string Print = getDate() + ("[ERROR] ") + toPrint + "\n";
|
||||
ConsoleOut(Print);
|
||||
addToLog(Print);
|
||||
}
|
||||
void except(const std::string& toPrint) {
|
||||
std::scoped_lock Guard(LogLock);
|
||||
std::string Print = getDate() + ("[EXCEP] ") + toPrint + "\n";
|
||||
ConsoleOut(Print);
|
||||
addToLog(Print);
|
||||
}
|
||||
102
src/main.cpp
102
src/main.cpp
@@ -1,54 +1,74 @@
|
||||
#include "CustomAssert.h"
|
||||
|
||||
#include "Startup.h"
|
||||
#include <curl/curl.h>
|
||||
#include <iostream>
|
||||
#include "Common.h"
|
||||
#include "Http.h"
|
||||
#include "TConfig.h"
|
||||
#include "THeartbeatThread.h"
|
||||
#include "TLuaEngine.h"
|
||||
#include "TNetwork.h"
|
||||
#include "TPPSMonitor.h"
|
||||
#include "TResourceManager.h"
|
||||
#include "TServer.h"
|
||||
#include <thread>
|
||||
#ifndef WIN32
|
||||
|
||||
#ifdef __unix
|
||||
#include <csignal>
|
||||
|
||||
void UnixSignalHandler(int sig) {
|
||||
switch (sig) {
|
||||
case SIGPIPE:
|
||||
warn(("ignored signal SIGPIPE: Pipe broken"));
|
||||
warn("ignoring SIGPIPE");
|
||||
break;
|
||||
case SIGTERM:
|
||||
info("gracefully shutting down via SIGTERM");
|
||||
Application::GracefullyShutdown();
|
||||
break;
|
||||
case SIGINT:
|
||||
info("gracefully shutting down via SIGINT");
|
||||
Application::GracefullyShutdown();
|
||||
break;
|
||||
default:
|
||||
error(("Signal arrived in handler but was not handled: ") + std::to_string(sig));
|
||||
debug("unhandled signal: " + std::to_string(sig));
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // WIN32
|
||||
#endif // __unix
|
||||
|
||||
[[noreturn]] void loop() {
|
||||
DebugPrintTID();
|
||||
while (true) {
|
||||
std::cout.flush();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(600));
|
||||
int main(int argc, char** argv) {
|
||||
#ifdef __unix
|
||||
#if DEBUG
|
||||
info("registering handlers for SIGINT, SIGTERM, SIGPIPE");
|
||||
#endif // DEBUG
|
||||
signal(SIGPIPE, UnixSignalHandler);
|
||||
signal(SIGTERM, UnixSignalHandler);
|
||||
#ifndef DEBUG
|
||||
signal(SIGINT, UnixSignalHandler);
|
||||
#endif // DEBUG
|
||||
#endif // __unix
|
||||
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
bool Shutdown = false;
|
||||
Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; });
|
||||
|
||||
TServer Server(argc, argv);
|
||||
TConfig Config;
|
||||
|
||||
if (Config.Failed()) {
|
||||
info("Closing in 10 seconds");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||
return 1;
|
||||
}
|
||||
|
||||
RegisterThread("Main");
|
||||
TResourceManager ResourceManager;
|
||||
TPPSMonitor PPSMonitor(Server);
|
||||
THeartbeatThread Heartbeat(ResourceManager, Server);
|
||||
TNetwork Network(Server, PPSMonitor, ResourceManager);
|
||||
TLuaEngine LuaEngine(Server, Network);
|
||||
PPSMonitor.SetNetwork(Network);
|
||||
Application::Console().InitializeLuaConsole(LuaEngine);
|
||||
|
||||
// TODO: replace
|
||||
while (!Shutdown) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifndef WIN32
|
||||
// ignore SIGPIPE, the signal that is sent for example when a client
|
||||
// disconnects while data is being sent to him ("broken pipe").
|
||||
signal(SIGPIPE, UnixSignalHandler);
|
||||
#endif // WIN32
|
||||
DebugPrintTID();
|
||||
// curl needs to be initialized to properly deallocate its resources later
|
||||
[[maybe_unused]] auto ret = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
Assert(ret == CURLE_OK);
|
||||
#ifdef DEBUG
|
||||
std::thread t1(loop);
|
||||
t1.detach();
|
||||
#endif
|
||||
ConsoleInit();
|
||||
InitServer(argc, argv);
|
||||
InitConfig();
|
||||
InitLua();
|
||||
InitRes();
|
||||
HBInit();
|
||||
StatInit();
|
||||
NetMain();
|
||||
// clean up curl at the end to be sure
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user