Compare commits

..

2 Commits

Author SHA1 Message Date
Starystars67 6247191a3e Further Tidy of unused files 2020-01-28 23:16:33 +00:00
Starystars67 d752ac5acb Creation of NodeJS Branch 2020-01-28 23:04:21 +00:00
75 changed files with 1708 additions and 8286 deletions
-5
View File
@@ -1,5 +0,0 @@
---
BasedOnStyle: WebKit
BreakBeforeBraces: Attach
...
-29
View File
@@ -1,29 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] Enter issue title here"
labels: bug
assignees: ''
---
**Fill out general information**
OS (windows, linux, ...):
BeamMP-Server Version:
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Do x ...
2. Do y ...
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Please attach the `Server.log` from the run in which the issue appeared, preferably with Debug turned on in the `ServerConfig.toml`.
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. For example: "I'm always frustrated when ...".
**Describe the solution you'd like**
A clear and concise description of what you want to happen. Also supply OS information if relevant, for example "*On Linux*, I would like to be able to...".
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context about the feature request here.
-44
View File
@@ -1,44 +0,0 @@
name: CMake Linux Build
on: [push]
env:
BUILD_TYPE: Release
jobs:
linux-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Install Dependencies
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: |
echo ${#beammp_sentry_url}
sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-linux
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-linux
shell: bash
run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Archive artifacts
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-linux
path: ${{github.workspace}}/build-linux/BeamMP-Server
-58
View File
@@ -1,58 +0,0 @@
name: CMake Windows Build
on: [push]
env:
BUILD_TYPE: Release
jobs:
windows-build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@v7
id: runvcpkg
with:
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
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
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- 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
- name: Build debug
working-directory: ${{github.workspace}}/build-windows
shell: bash
run: |
cmake --build . --config Debug -t BeamMP-Server --parallel
- name: Archive debug artifacts
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-debug.exe
path: ${{github.workspace}}/build-windows/Debug/BeamMP-Server.exe
-115
View File
@@ -1,115 +0,0 @@
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: true
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: 'recursive'
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-linux
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-linux
shell: bash
run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- 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: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@v7
id: runvcpkg
with:
vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
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
env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build
working-directory: ${{github.workspace}}/build-windows
shell: bash
run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- 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
+74 -451
View File
@@ -1,481 +1,104 @@
.idea/
.sentry-native/
*.orig
*.toml
boost_*
Resources
run-in-env.sh
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
#VS Files
out/
#Clion Files
cmake-build-debug/
cmake-build-release/
.idea/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
# Logs
logs
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Chutzpah Test files
_Chutzpah*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Visual Studio Trace Files
*.e2e
# Coverage directory used by tools like istanbul
coverage
*.lcov
# TFS 2012 Local Workspace
$tf/
# nyc test coverage
.nyc_output
# Guidance Automation Toolkit
*.gpState
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# Bower dependency directory (https://bower.io/)
bower_components
# TeamCity is a build add-in
_TeamCity*
# node-waf configuration
.lock-wscript
# DotCover is a Code Coverage Tool
*.dotCover
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Dependency directories
node_modules/
jspm_packages/
# Visual Studio 6 build log
*.plg
# TypeScript v1 declaration files
typings/
# Visual Studio 6 workspace options file
*.opt
# TypeScript cache
*.tsbuildinfo
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Optional npm cache directory
.npm
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Optional eslint cache
.eslintcache
# Paket dependency manager
.paket/paket.exe
paket-files/
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# FAKE - F# Make
.fake/
# Optional REPL history
.node_repl_history
# CodeRush personal settings
.cr/personal
# Output of 'npm pack'
*.tgz
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Yarn Integrity file
.yarn-integrity
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# dotenv environment variables file
.env
.env.test
# Tabs Studio
*.tss
# parcel-bundler cache (https://parceljs.org/)
.cache
# Telerik's JustMock configuration file
*.jmconfig
# Next.js build output
.next
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# Nuxt.js build / generate output
.nuxt
dist
# OpenCover UI analysis results
OpenCover/
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# Azure Stream Analytics local run output
ASALocalRun/
# vuepress build output
.vuepress/dist
# MSBuild Binary and Structured Log
*.binlog
# Serverless directories
.serverless/
# NVidia Nsight GPU debugger configuration file
*.nvuser
# FuseBox cache
.fusebox/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# DynamoDB Local files
.dynamodb/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
out/build/x86-Debug/VSInheritEnvironments.txt
out/build/x86-Debug/rules.ninja
out/build/x86-Debug/CMakeFiles/untitled.dir/manifest.res
out/build/x86-Debug/CMakeFiles/untitled.dir/manifest.rc
out/build/x86-Debug/CMakeFiles/untitled.dir/intermediate.manifest
out/build/x86-Debug/CMakeFiles/untitled.dir/embed.manifest
out/build/x86-Debug/CMakeFiles/TargetDirectories.txt
out/build/x86-Debug/CMakeFiles/ShowIncludes/main.c
out/build/x86-Debug/CMakeFiles/ShowIncludes/foo.h
out/build/x86-Debug/CMakeFiles/cmake.check_cache
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdCXX/CMakeCXXCompilerId.exe
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdCXX/CMakeCXXCompilerId.cpp
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdC/CMakeCCompilerId.exe
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdC/CMakeCCompilerId.c
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeSystem.cmake
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeRCCompiler.cmake
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeDetermineCompilerABI_CXX.bin
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeDetermineCompilerABI_C.bin
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeCXXCompiler.cmake
out/build/x86-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeCCompiler.cmake
out/build/x86-Debug/CMakeCache.txt
out/build/x86-Debug/cmake_install.cmake
out/build/x86-Debug/build.ninja
out/build/x86-Debug/.ninja_log
out/build/x86-Debug/.ninja_deps
out/build/x86-Debug/.cmake/api/v1/reply/target-untitled-Debug-03f16c5921ae0bd48990.json
out/build/x86-Debug/.cmake/api/v1/reply/index-2020-01-27T23-42-34-0718.json
out/build/x86-Debug/.cmake/api/v1/reply/codemodel-v2-2b93246ca751d7a49cd1.json
out/build/x86-Debug/.cmake/api/v1/reply/cmakeFiles-v1-d93b224eb363971ee2c4.json
out/build/x86-Debug/.cmake/api/v1/reply/cache-v2-43521627501184045d13.json
out/build/x86-Debug/.cmake/api/v1/query/client-MicrosoftVS/query.json
out/build/x64-Debug/VSInheritEnvironments.txt
out/build/x64-Debug/rules.ninja
out/build/x64-Debug/CMakeFiles/untitled.dir/manifest.res
out/build/x64-Debug/CMakeFiles/untitled.dir/manifest.rc
out/build/x64-Debug/CMakeFiles/untitled.dir/intermediate.manifest
out/build/x64-Debug/CMakeFiles/untitled.dir/embed.manifest
out/build/x64-Debug/CMakeFiles/TargetDirectories.txt
out/build/x64-Debug/CMakeFiles/ShowIncludes/main.c
out/build/x64-Debug/CMakeFiles/ShowIncludes/foo.h
out/build/x64-Debug/CMakeFiles/cmake.check_cache
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdCXX/CMakeCXXCompilerId.exe
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdCXX/CMakeCXXCompilerId.cpp
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdC/CMakeCCompilerId.exe
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CompilerIdC/CMakeCCompilerId.c
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeSystem.cmake
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeRCCompiler.cmake
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeDetermineCompilerABI_CXX.bin
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeDetermineCompilerABI_C.bin
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeCXXCompiler.cmake
out/build/x64-Debug/CMakeFiles/3.15.19101501-MSVC_2/CMakeCCompiler.cmake
out/build/x64-Debug/CMakeCache.txt
out/build/x64-Debug/cmake_install.cmake
out/build/x64-Debug/build.ninja
out/build/x64-Debug/.ninja_log
out/build/x64-Debug/.ninja_deps
out/build/x64-Debug/.cmake/api/v1/reply/target-untitled-Debug-8918286eee207fe0275b.json
out/build/x64-Debug/.cmake/api/v1/reply/index-2020-01-27T23-42-03-0431.json
out/build/x64-Debug/.cmake/api/v1/reply/codemodel-v2-ee254da00ecbf4b22928.json
out/build/x64-Debug/.cmake/api/v1/reply/cmakeFiles-v1-ee926bd040d82c324cbe.json
out/build/x64-Debug/.cmake/api/v1/reply/cache-v2-6f7cb0917968b934d35c.json
out/build/x64-Debug/.cmake/api/v1/query/client-MicrosoftVS/query.json
CMakeSettings.json
cmake-build-debug/Resource1.resx
out/build/x64-Debug/.cmake/api/v1/reply/index-2020-01-28T10-34-19-0746.json
out/build/x64-Debug/untitled.exe
out/build/x64-Debug/.cmake/api/v1/reply/index-2020-01-28T11-19-41-0061.json
out/build/x64-Debug/.cmake/api/v1/reply/cmakeFiles-v1-52c1d9386071c1490278.json
out/build/x86-Debug/CMakeFiles/cmake-main.dir/manifest.res
out/build/x86-Debug/CMakeFiles/cmake-main.dir/manifest.rc
out/build/x86-Debug/CMakeFiles/cmake-main.dir/intermediate.manifest
out/build/x86-Debug/CMakeFiles/cmake-main.dir/embed.manifest
out/build/x86-Debug/cmake-main.exe
out/build/x86-Debug/.cmake/api/v1/reply/target-enet-Debug-1cc90e5d191bd520f961.json
out/build/x86-Debug/.cmake/api/v1/reply/target-cmake-main-Debug-0c9bd3464adc40e447af.json
out/build/x86-Debug/.cmake/api/v1/reply/index-2020-01-28T11-21-26-0362.json
out/build/x86-Debug/.cmake/api/v1/reply/codemodel-v2-1850e17888d5bab56568.json
out/build/x86-Debug/.cmake/api/v1/reply/cmakeFiles-v1-7c376457f6b736ad28ac.json
out/build/x64-Debug/CMakeFiles/cmake-main.dir/manifest.res
out/build/x64-Debug/CMakeFiles/cmake-main.dir/manifest.rc
out/build/x64-Debug/CMakeFiles/cmake-main.dir/intermediate.manifest
out/build/x64-Debug/CMakeFiles/cmake-main.dir/embed.manifest
out/build/x64-Debug/cmake-main.exe
out/build/x64-Debug/.cmake/api/v1/reply/target-enet-Debug-645fd45f5bd4fab53aa9.json
out/build/x64-Debug/.cmake/api/v1/reply/target-cmake-main-Debug-b124668c8fb6ca5df286.json
out/build/x64-Debug/.cmake/api/v1/reply/index-2020-01-28T11-21-06-0706.json
out/build/x64-Debug/.cmake/api/v1/reply/codemodel-v2-63a6ab1789a1732f4563.json
out/build/x86-Debug/.cmake/api/v1/reply/target-enet-Debug-d301abed48f6c38bdcfb.json
out/build/x86-Debug/.cmake/api/v1/reply/index-2020-01-28T12-30-44-0946.json
out/build/x86-Debug/.cmake/api/v1/reply/codemodel-v2-3eaabe43603befc605b1.json
out/build/x86-Debug/.cmake/api/v1/reply/target-cmake-main-Debug-70eff68e1e381b42992e.json
*.xml
out/build/x86-Debug/.cmake/api/v1/reply/target-enet-Debug-48db38ae5d08e27876b8.json
out/build/x86-Debug/.cmake/api/v1/reply/target-cmake-main-Debug-540e487569703b71c785.json
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
# TernJS port file
.tern-port
-30
View File
@@ -1,30 +0,0 @@
[submodule "deps/commandline"]
path = deps/commandline
url = https://github.com/lionkor/commandline
[submodule "deps/fmt"]
path = deps/fmt
url = https://github.com/fmtlib/fmt
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/Tencent/rapidjson
[submodule "deps/toml11"]
path = deps/toml11
url = https://github.com/ToruNiina/toml11
[submodule "deps/sentry-native"]
path = deps/sentry-native
url = https://github.com/getsentry/sentry-native
[submodule "deps/sol2"]
path = deps/sol2
url = https://github.com/ThePhD/sol2
[submodule "deps/libzip"]
path = deps/libzip
url = https://github.com/nih-at/libzip
[submodule "deps/cpp-httplib"]
path = deps/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json
-153
View File
@@ -1,153 +0,0 @@
# 3.4 is required for imported targets.
cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
message(STATUS "You can find build instructions and a list of dependencies in the README at \
https://github.com/BeamMP/BeamMP-Server")
project(BeamMP-Server
DESCRIPTION "Server for BeamMP - The Multiplayer Mod for BeamNG.drive"
HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C)
find_package(Git REQUIRED)
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
set(HTTPLIB_REQUIRE_OPENSSL ON)
set(SENTRY_BUILD_SHARED_LIBS OFF)
include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
include_directories("${PROJECT_SOURCE_DIR}/deps/sol2/include")
include_directories("${PROJECT_SOURCE_DIR}/deps/cpp-httplib")
include_directories("${PROJECT_SOURCE_DIR}/deps/toml11")
include_directories("${PROJECT_SOURCE_DIR}/deps/json/single_include")
include_directories("${PROJECT_SOURCE_DIR}/deps")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
# ------------------------ APPLE ---------------------------------
if(APPLE)
set(LUA_INCLUDE_DIR /usr/local/Cellar/lua@5.3/5.3.6/include/lua5.3)
set(LUA_LIBRARIES lua)
include_directories(/usr/local/opt/openssl@1.1/include)
link_directories(/usr/local/Cellar/lua@5.3/5.3.6/lib)
link_directories(/usr/local/opt/openssl@1.1/lib)
# ------------------------ WINDOWS ---------------------------------
elseif (WIN32)
# this has to happen before sentry, so that crashpad on windows links with these settings.
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})
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif ()
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
# ------------------------ LINUX ---------------------------------
elseif (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++ -static-libgcc")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
if (SANITIZE)
message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
endif (SANITIZE)
endif ()
include_directories("include/sentry-native/include")
# ------------------------ SENTRY ---------------------------------
message(STATUS "Checking for Sentry URL")
# this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty
if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \
This is not an error, and if you're building the BeamMP-Server yourself, this is expected and can be ignored.")
set(BEAMMP_SECRET_SENTRY_URL "")
set(SENTRY_BACKEND none)
else()
set(SENTRY_BACKEND breakpad)
endif()
add_subdirectory("deps/sentry-native")
# ------------------------ C++ SETUP ---------------------------------
set(CMAKE_CXX_STANDARD 17)
# ------------------------ DEPENDENCIES ------------------------------
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
# ------------------------ BEAMMP SERVER -----------------------------
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/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp
include/Environment.h)
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}")
include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline")
include(FindLua)
include(FindOpenSSL)
include(FindThreads)
include(FindZLIB)
target_include_directories(BeamMP-Server PUBLIC
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include")
target_link_libraries(BeamMP-Server
OpenSSL::SSL
OpenSSL::Crypto
sol2::sol2
fmt::fmt
Threads::Threads
ZLIB::ZLIB
${LUA_LIBRARIES}
commandline
sentry)
if (WIN32)
target_link_libraries(BeamMP-Server wsock32 ws2_32)
endif ()
-130
View File
@@ -1,130 +0,0 @@
# v3.1.0
- ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console
- ADDED lua debug facilities (type `:help` when attached to lua via `lua`)
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
- ADDED FS.ListFiles and FS.ListDirectories
- FIXED issue with client->server events which contain ':'
# v3.0.2
- ADDED Periodic update message if a new server is released
- ADDED Config setting for the IP the http server listens on
- CHANGED Default MaxPlayers to 8
- CHANGED Default http server listen IP to localhost
- FIXED `MP.CreateEventTimer` filling up the queue (see <https://wiki.beammp.com/en/Scripting/new-lua-scripting#mpcreateeventtimerevent_name-string-interval_ms-number-strategy-number-since-v302>)
- FIXED `MP.TriggerClientEvent` not kicking the client if it failed
- FIXED Lua result queue handling not checking all results
# v3.0.1
- ADDED Backup URLs to UpdateCheck (will fail less often now)
- ADDED console cursor left and right movement (with arrow keys) and working HOME and END key (via github.com/lionkor/commandline)
- FIXED infinite snowmen / infinite unicycle spawning bug
- FIXED a bug where, when run with --working-directory, the Server.log would still be in the original directory
- FIXED a bug which could cause the plugin reload thread to spin at 100% if the reloaded plugin's didn't terminate
- FIXED an issue which would cause servers to crash on mod download via SIGPIPE on POSIX
- FIXED an issue which would cause servers to crash when checking if a vehicle is a unicycle
# v3.0.0
- CHANGED entire plugin Lua implementation (rewrite)
- CHANGED moved *almost all* Lua functions into MP.\*
- CHANGED console to use a custom language (type `help`, `list`, or `status`!)
- CHANGED all files of a Lua plugin to share a Lua state (no more state-per-file)
- ADDED many new Lua API functions, which can be found at <https://wiki.beammp.com/en/Scripting/functions>
- ADDED Commandline options. Run with `--help` to see all options.
- ADDED HTTP(S) Server (OpenAPI spec coming soon!)
- ADDED plugin directories to `package.path` and `package.cpath` before `onInit`
- ADDED ability to add `PluginConfig.toml` to your plugin folder to change some settings
- ADDED ability to share a lua state with other plugins via `StateId` setting in `PluginConfig.toml`
- ADDED ability to see name-to-thread-ID association in debug mode
- ADDED dumping tables with `print()` (try it with `print(MP)`)
- ADDED `MP.GetOSName()`, `MP.CreateTimer()`, `MP.GetLuaMemoryUsage()` and many more (see <https://wiki.beammp.com/en/Scripting/functions>)
- ADDED `MP.Settings` table to make usage of `MP.Set()` easier
- ADDED `FS.*` table with common filesystem operations (do `print(FS)` to see them!)
- FIXED i/o thread spin when stdout is /dev/null on linux
- FIXED removed extra whitespace infront of onChatMessage message
# v2.3.3
- CHANGED servers to be private by default
# v2.3.2
- ADDED Ctrl+C causes a graceful shutdown on windows (did already on linux)
- ADDED more meaningful shutdown messages
- ADDED even better backend connection error reporting
- ADDED `SendErrors` config in `ServerConfig.toml` to opt-out of error reporting
- ADDED hard-shutdown if Ctrl+C pressed 3 times
- FIXED issue with shells like bash being unusable after server exit
# v2.3.1
- CHANGED join/sync timeout to 20 minutes, players wont drop if loading takes >5 mins
# v2.3.0
- ADDED version check - the server will now let you know when a new release is out
- ADDED logging of various errors, crashes and exceptions to the backend
- ADDED chat messages are now logged to the server console as [CHAT]
- ADDED debug message telling you when the server heartbeats to the backend
- REMOVED various [DEBUG] messages which were confusing (such as "breaking client loop")
- FIXED various crashes and issues with handling unexpected backend responses
- FIXED minor bugs due to code correctness
# v2.2.0
- FIXED major security flaw
- FIXED minor bugs
# v2.1.4
- ADDED debug heartbeat print
- ADDED kicking every player before shutdown
- FIXED rare bug which led to violent crash
- FIXED minor bugs
# v2.1.3
- FIXED Lua events not cancelling properly on Linux
# v2.1.2
- CHANGED default map to gridmap v2
- FIXED version number display
# v2.1.1
# v2.1.0 (pre-v2.1.1)
# v2.0.4 (pre-v2.1.0)
- REMOVED boost as a runtime dependency
- FIXED Lua plugins on Linux
- FIXED console history on Windows
- CHANGED to new config format TOML
# v2.0.3
- WORKAROUND for timeout bug / ghost player bug
- FIXED 100% CPU spin when stdin is /dev/null.
# v2.0.2
- ADDED fully new commandline
- ADDED new backend
- ADDED automated build system
- ADDED lua GetPlayerIdentifiers
- ADDED lots of debug info
- ADDED better POSTing and GETing
- ADDED a license
- FIXED ghost players in player list issue
- FIXED ghost vehicle after joining issue
- FIXED missing vehicle after joining issue
- FIXED a lot of desync issues
- FIXED some memory leaks
- FIXED various crashes
- FIXED various data-races
- FIXED some linux-specific crashes
- FIXED some linux-specific issues
- FIXED bug which caused kicking to be logged as leaving
- FIXED various internal developer quality-of-life things
-2
View File
@@ -1,2 +0,0 @@
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 -123
View File
@@ -1,123 +1 @@
# BeamMP-Server
[![CMake Windows Build](https://github.com/BeamMP/BeamMP-Server/workflows/CMake%20Windows%20Build/badge.svg?branch=master)](https://github.com/BeamMP/BeamMP-Server/actions?query=workflow%3A%22CMake+Windows+Build%22)
[![CMake Linux Build](https://github.com/BeamMP/BeamMP-Server/workflows/CMake%20Linux%20Build/badge.svg?branch=master)](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).
**For Linux, you __need__ the runtime dependencies, listed below under "prerequisites".**
## Support + Contact
Feel free to ask any questions via the following channels:
- **IRC**: `#beammp` on [irc.libera.chat](https://web.libera.chat/)
- **Discord**: [click for invite](https://discord.gg/beammp)
## Minimum Requirements
These values are guesstimated and are subject to change with each release.
* RAM: 50+ MiB usable (not counting OS overhead)
* CPU: >1GHz, 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 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, CLANG.
You can find precompiled binaries under [Releases](https://github.com/BeamMP/BeamMP-Server/releases/).
## Build Instructions
**__Do not compile from `master`. Always build from a release tag, i.e. `tags/v2.3.3`!__**
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
#### Windows
Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/BeamMP-Server/releases/).
Dependencies for **windows** can be installed with `vcpkg`.
These are:
```
lua
zlib
rapidjson
openssl
websocketpp
curl
```
#### Linux
These package names are in the debian / ubuntu style. Feel free to PR your own guide for a different distro.
Runtime dependencies for **linux** are (debian/ubuntu):
```
libz-dev
rapidjson-dev
liblua5.3
libssl-dev
libwebsocketpp-dev
libcurl4-openssl-dev
```
Build-time dependencies for **linux** are:
```
git
make
cmake
g++
```
For other distributions (e.g. Arch) you want to find packages for:
- libz
- rapidjson
- lua5.3
- ssl / openssl
- websocketpp
- curl (with ssl support)
- \+ the build time dependencies from above
### How to build
On windows, use git-bash for these commands. On Linux, these should work in your shell.
1. Make sure you have all [prerequisites](#prerequisites) installed
2. Clone the repository in a location of your choice with `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Server`.
3. Ensure that all submodules are initialized by running `git submodule update --init --recursive`. Then change into the cloned directory by running `cd BeamMP-Server`.
4. Checkout the branch of the release you want to compile (`master` is often unstable), for example `git checkout tags/v2.3.3` for version 2.3.3. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags).
5. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (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 (@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.
# BeamNG-MP-Server
-7
View File
@@ -1,7 +0,0 @@
include_directories("${PROJECT_SOURCE_DIR}/deps")
include_directories("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2")
Vendored
-1
Submodule deps/asio deleted from d038fb3c2f
-1
Submodule deps/commandline deleted from d6b1c32c8a
-1
Submodule deps/cpp-httplib deleted from b324921c1a
Vendored
-1
Submodule deps/fmt deleted from 17dda58391
Vendored
-1
Submodule deps/json deleted from eb21824147
Vendored
-1
Submodule deps/libzip deleted from 76df02f86b
-1
Submodule deps/rapidjson deleted from 00dbcf2c6e
Submodule deps/sentry-native deleted from 90966cc102
Vendored
-1
Submodule deps/sol2 deleted from c068aefbed
Vendored
-1
Submodule deps/toml11 deleted from fda0a2b9ab
-49
View File
@@ -1,49 +0,0 @@
#pragma once
#include <initializer_list>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
/*
* Allows syntax:
* --help : long flags
* --path=/home/lion : long assignments
*/
class ArgsParser {
public:
enum Flags : int {
NONE = 0,
REQUIRED = 1, // argument is required
HAS_VALUE = 2, // argument must have a value
};
ArgsParser() = default;
void Parse(const std::vector<std::string_view>& ArgList);
// prints errors if any errors occurred, in that case also returns false
bool Verify();
void RegisterArgument(std::vector<std::string>&& ArgumentNames, int Flags);
// pass all possible names for this argument (short, long, etc)
bool FoundArgument(const std::vector<std::string>& Names);
std::optional<std::string> GetValueOfArgument(const std::vector<std::string>& Names);
private:
void ConsumeLongAssignment(const std::string& Arg);
void ConsumeLongFlag(const std::string& Arg);
bool IsRegistered(const std::string& Name);
struct Argument {
std::string Name;
std::optional<std::string> Value;
};
struct RegisteredArgument {
std::vector<std::string> Names;
int Flags;
};
std::vector<RegisteredArgument> mRegisteredArguments;
std::vector<Argument> mFoundArgs;
};
-108
View File
@@ -1,108 +0,0 @@
#pragma once
#include <chrono>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <unordered_set>
#include "Common.h"
#include "Compat.h"
#include "VehicleData.h"
class TServer;
#ifdef BEAMMP_WINDOWS
// for socklen_t
#include <WS2tcpip.h>
#endif // WINDOWS
struct TConnection final {
SOCKET Socket;
struct sockaddr SockAddr;
socklen_t SockAddrLen;
};
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 SetIdentifier(const std::string& key, const std::string& value) { mIdentifiers[key] = value; }
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]] const std::unordered_map<std::string, 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::unordered_map<std::string, std::string> mIdentifiers;
bool mIsGuest = false;
mutable 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;
};
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
-281
View File
@@ -1,281 +0,0 @@
#pragma once
#include "TSentry.h"
extern TSentry Sentry;
#include <array>
#include <atomic>
#include <cstring>
#include <deque>
#include <filesystem>
#include <fmt/format.h>
#include <functional>
#include <memory>
#include <mutex>
#include <sstream>
#include <zlib.h>
#include "Compat.h"
#include "TConsole.h"
struct Version {
uint8_t major;
uint8_t minor;
uint8_t patch;
Version(uint8_t major, uint8_t minor, uint8_t patch);
Version(const std::array<uint8_t, 3>& v);
std::string AsString();
};
// 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 {
std::string ServerName { "BeamMP Server" };
std::string ServerDesc { "BeamMP Default Description" };
std::string Resource { "Resources" };
std::string MapName { "/levels/gridmap_v2/info.json" };
std::string Key {};
bool HTTPServerEnabled { false };
int MaxPlayers { 8 };
bool Private { true };
int MaxCars { 1 };
bool DebugModeEnabled { false };
int Port { 30814 };
std::string CustomIP {};
bool LogChat { true };
bool SendErrors { true };
bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 };
std::string HTTPServerIP { "127.0.0.1" };
bool HTTPServerUseSSL { false };
bool HideUpdateMessages { false };
[[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 ServerVersionString();
static const Version& ServerVersion() { return mVersion; }
static std::string ClientVersionString() { return "2.0"; }
static std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
static TSettings Settings;
static std::vector<std::string> GetBackendUrlsInOrder() {
return {
"backend.beammp.com",
"backup1.beammp.com",
"backup2.beammp.com"
};
}
static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
static bool IsOutdated(const Version& Current, const Version& Newest);
static void InitializeConsole() {
if (!mConsole) {
mConsole = std::make_unique<TConsole>();
}
}
enum class Status {
Starting,
Good,
Bad,
ShuttingDown,
Shutdown,
};
using SystemStatusMap = std::unordered_map<std::string /* system name */, Status /* status */>;
static const SystemStatusMap& GetSubsystemStatuses() {
std::unique_lock Lock(mSystemStatusMapMutex);
return mSystemStatusMap;
}
static void SetSubsystemStatus(const std::string& Subsystem, Status status);
private:
static inline SystemStatusMap mSystemStatusMap {};
static inline std::mutex mSystemStatusMapMutex {};
static inline std::string mPPS;
static inline std::unique_ptr<TConsole> mConsole;
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 1, 0 };
};
std::string ThreadName(bool DebugModeOverride = false);
void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024
#define MB (KB * 1024)
#define SSU_UNRAW SECRET_SENTRY_URL
#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
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
// each info/debug/warn... call instead of the 'filename:line' format.
#if defined(BMP_FULL_FUNCTION_NAMES)
#define _this_location (ThreadName() + _function_name + " ")
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#define SU_RAW SSU_UNRAW
#else // !defined(DEBUG)
#define SU_RAW RAWIFY(SSU_UNRAW)
#define _this_location (ThreadName())
#endif // defined(DEBUG)
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define beammp_lua_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// for those times when you just need to ignore something :^)
// explicity disables a [[nodiscard]] warning
#define beammp_ignore(x) (void)x
// trace() is a debug-build debug()
#if defined(DEBUG)
#define beammp_trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else
#define beammp_trace(x)
#endif // defined(DEBUG)
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(fmt::format(__VA_ARGS__))
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
#define beammp_tracef(...) beammp_trace(fmt::format(__VA_ARGS__))
#define beammp_lua_errorf(...) beammp_lua_error(fmt::format(__VA_ARGS__))
#define beammp_lua_warnf(...) beammp_lua_warn(fmt::format(__VA_ARGS__))
void LogChatMessage(const std::string& name, int id, const std::string& msg);
#define Biggest 30000
template <typename T>
inline T Comp(const T& Data) {
std::array<char, Biggest> C {};
// obsolete
C.fill(0);
z_stream defstream;
defstream.zalloc = Z_NULL;
defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)Data.size();
defstream.next_in = (Bytef*)&Data[0];
defstream.avail_out = Biggest;
defstream.next_out = reinterpret_cast<Bytef*>(C.data());
deflateInit(&defstream, Z_BEST_COMPRESSION);
deflate(&defstream, Z_SYNC_FLUSH);
deflate(&defstream, Z_FINISH);
deflateEnd(&defstream);
size_t TotalOut = defstream.total_out;
T Ret;
Ret.resize(TotalOut);
std::fill(Ret.begin(), Ret.end(), 0);
std::copy_n(C.begin(), TotalOut, Ret.begin());
return Ret;
}
template <typename T>
inline T DeComp(const T& Compressed) {
std::array<char, Biggest> C {};
// not needed
C.fill(0);
z_stream infstream;
infstream.zalloc = Z_NULL;
infstream.zfree = Z_NULL;
infstream.opaque = Z_NULL;
infstream.avail_in = Biggest;
infstream.next_in = (Bytef*)(&Compressed[0]);
infstream.avail_out = Biggest;
infstream.next_out = (Bytef*)(C.data());
inflateInit(&infstream);
inflate(&infstream, Z_SYNC_FLUSH);
inflate(&infstream, Z_FINISH);
inflateEnd(&infstream);
size_t TotalOut = infstream.total_out;
T Ret;
Ret.resize(TotalOut);
std::fill(Ret.begin(), Ret.end(), 0);
std::copy_n(C.begin(), TotalOut, Ret.begin());
return Ret;
}
std::string GetPlatformAgnosticErrorString();
#define S_DSN SU_RAW
void LogChatMessage(const std::string& name, int id, const std::string& msg);
-58
View File
@@ -1,58 +0,0 @@
#pragma once
#include "Environment.h"
// ======================= UNIX ========================
#ifdef BEAMMP_LINUX
#include <arpa/inet.h>
#include <errno.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 TheSocket) {
shutdown(TheSocket, SHUT_RDWR);
close(TheSocket);
}
#endif // unix
// ======================= APPLE ========================
#ifdef BEAMMP_APPLE
#include <arpa/inet.h>
#include <errno.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 TheSocket) {
shutdown(TheSocket, SHUT_RDWR);
close(TheSocket);
}
#endif // unix
// ======================= WINDOWS =======================
#ifdef BEAMMP_WINDOWS
#include <conio.h>
#include <winsock2.h>
inline void CloseSocketProper(SOCKET TheSocket) {
shutdown(TheSocket, 2); // 2 == SD_BOTH
closesocket(TheSocket);
}
#endif // WIN32
#ifdef INVALID_SOCKET
static inline constexpr int BEAMMP_INVALID_SOCKET = INVALID_SOCKET;
#else
static inline constexpr int BEAMMP_INVALID_SOCKET = -1;
#endif
-114
View File
@@ -1,114 +0,0 @@
// Copyright Anonymous275 8/11/2020
#pragma once
#include <array>
#include <cstdarg>
#include <string>
namespace Crypto {
constexpr auto time = __TIME__;
constexpr auto seed = static_cast<int>(time[7]) + static_cast<int>(time[6]) * 10 + static_cast<int>(time[4]) * 60 + static_cast<int>(time[3]) * 600 + static_cast<int>(time[1]) * 3600 + static_cast<int>(time[0]) * 36000;
// 1988, Stephen Park and Keith Miller
// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"
// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:
// with 32-bit math and without division
template <int N>
struct RandomGenerator {
private:
static constexpr unsigned a = 16807; // 7^5
static constexpr unsigned m = 2147483647; // 2^31 - 1
static constexpr unsigned s = RandomGenerator<N - 1>::value;
static constexpr unsigned lo = a * (s & 0xFFFFu); // Multiply lower 16 bits by 16807
static constexpr unsigned hi = a * (s >> 16u); // Multiply higher 16 bits by 16807
static constexpr unsigned lo2 = lo + ((hi & 0x7FFFu) << 16u); // Combine lower 15 bits of hi with lo's upper bits
static constexpr unsigned hi2 = hi >> 15u; // Discard lower 15 bits of hi
static constexpr unsigned lo3 = lo2 + hi;
public:
static constexpr unsigned max = m;
static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;
};
template <>
struct RandomGenerator<0> {
static constexpr unsigned value = seed;
};
template <int N, int M>
struct RandomInt {
static constexpr auto value = RandomGenerator<N + 1>::value % M;
};
template <int N>
struct RandomChar {
static const char value = static_cast<char>(1 + RandomInt<N, 0x7F - 1>::value);
};
template <size_t N, int K, typename Char>
struct MangleString {
private:
const char _key;
std::array<Char, N + 1> _encrypted;
constexpr Char enc(Char c) const {
return c ^ _key;
}
Char dec(Char c) const {
return c ^ _key;
}
public:
template <size_t... Is>
constexpr MangleString(const Char* str, std::index_sequence<Is...>)
: _key(RandomChar<K>::value)
, _encrypted { enc(str[Is])... } { }
decltype(auto) decrypt() {
for (size_t i = 0; i < N; ++i) {
_encrypted[i] = dec(_encrypted[i]);
}
_encrypted[N] = '\0';
return _encrypted.data();
}
};
static auto w_printf = [](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
};
static auto w_printf_s = [](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
};
static auto w_sprintf_s = [](char* buf, size_t, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vsprintf(buf, fmt, args);
va_end(args);
};
static auto w_sprintf_s_ret = [](char* buf, size_t, const char* fmt, ...) {
int ret;
va_list args;
va_start(args, fmt);
ret = vsprintf(buf, fmt, args);
va_end(args);
return ret;
};
#define XOR_C(s) [] { constexpr Crypto::MangleString< sizeof(s)/sizeof(char) - 1, __COUNTER__, char > expr( s, std::make_index_sequence< sizeof(s)/sizeof(char) - 1>() ); return expr; }().decrypt()
#define XOR_W(s) [] { constexpr Crypto::MangleString< sizeof(s)/sizeof(wchar_t) - 1, __COUNTER__, wchar_t > expr( s, std::make_index_sequence< sizeof(s)/sizeof(wchar_t) - 1>() ); return expr; }().decrypt()
#define RAWIFY(s) XOR_C(s)
}
-73
View File
@@ -1,73 +0,0 @@
// Author: lionkor
/*
* Asserts are to be used anywhere where assumptions about state are made
* implicitly. AssertNotReachable is used where code should never go, like in
* default switch cases which shouldn't trigger. They make it explicit
* that a place cannot normally be reached and make it an error if they do.
*/
#pragma once
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <thread>
#include "Common.h"
static const char* const ANSI_RESET = "\u001b[0m";
static const char* const ANSI_BLACK = "\u001b[30m";
static const char* const ANSI_RED = "\u001b[31m";
static const char* const ANSI_GREEN = "\u001b[32m";
static const char* const ANSI_YELLOW = "\u001b[33m";
static const char* const ANSI_BLUE = "\u001b[34m";
static const char* const ANSI_MAGENTA = "\u001b[35m";
static const char* const ANSI_CYAN = "\u001b[36m";
static const char* const ANSI_WHITE = "\u001b[37m";
static const char* const ANSI_BLACK_BOLD = "\u001b[30;1m";
static const char* const ANSI_RED_BOLD = "\u001b[31;1m";
static const char* const ANSI_GREEN_BOLD = "\u001b[32;1m";
static const char* const ANSI_YELLOW_BOLD = "\u001b[33;1m";
static const char* const ANSI_BLUE_BOLD = "\u001b[34;1m";
static const char* const ANSI_MAGENTA_BOLD = "\u001b[35;1m";
static const char* const ANSI_CYAN_BOLD = "\u001b[36;1m";
static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m";
static const char* const ANSI_BOLD = "\u001b[1m";
static const char* const ANSI_UNDERLINE = "\u001b[4m";
#ifdef 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) {
std::cout << std::flush << "(debug build) TID "
<< std::this_thread::get_id() << ": ASSERTION FAILED: at "
<< file << ":" << line << " \n\t-> in "
<< function << ", Line " << line << ": \n\t\t-> "
<< "Failed Condition: " << condition_string << std::endl;
std::cout << "... terminating ..." << std::endl;
abort();
}
}
#define beammp_assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond))
#define beammp_assert_not_reachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false)
#else
// In release build, these macros turn into NOPs. The compiler will optimize these out.
#define beammp_assert(cond) \
do { \
bool result = (cond); \
if (!result) { \
Sentry.LogAssert(#cond, _file_basename, _line, __func__); \
} \
} while (false)
#define beammp_assert_not_reachable() \
do { \
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
} while (false)
#endif // DEBUG
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include <functional>
template <typename FnT>
class Defer final {
public:
Defer(FnT fn)
: mFunction([&fn] { (void)fn(); }) { }
~Defer() {
if (mFunction) {
mFunction();
}
}
private:
std::function<void()> mFunction;
};
-17
View File
@@ -1,17 +0,0 @@
#pragma once
// one of BEAMMP_{WINDOWS,LINUX,APPLE} will be set at the end of this
// clang-format off
#if !defined(BEAMMP_WINDOWS) && !defined(BEAMMP_UNIX) && !defined(BEAMMP_APPLE)
#if defined(_WIN32) || defined(__CYGWIN__)
#define BEAMMP_WINDOWS
#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__unix__) || defined(__unix) || defined(unix)
#define BEAMMP_LINUX
#elif defined(__APPLE__) || defined(__MACH__)
#define BEAMMP_APPLE
#else
#error "This platform is not known. Please define one of the above for your OS."
#endif
#endif
// clang-format on
-50
View File
@@ -1,50 +0,0 @@
#pragma once
#include <Common.h>
#include <IThreaded.h>
#include <filesystem>
#include <string>
#include <unordered_map>
#if defined(BEAMMP_LINUX)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include <httplib.h>
#if defined(BEAMMP_LINUX)
#pragma GCC diagnostic pop
#endif
namespace fs = std::filesystem;
class TServer;
class TNetwork;
class TResourceManager;
namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {});
namespace Status {
std::string ToString(int code);
}
const std::string ErrorString = "-1";
namespace Server {
class THttpServerInstance {
public:
THttpServerInstance(TServer&, TNetwork&, TResourceManager&);
~THttpServerInstance();
static fs::path KeyFilePath;
static fs::path CertFilePath;
protected:
void operator()();
private:
std::thread mThread;
TServer& mServer;
TNetwork& mNetwork;
TResourceManager& mResourceManager;
};
}
}
-24
View File
@@ -1,24 +0,0 @@
#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() { }
~IThreaded() noexcept {
if (mThread.joinable()) {
mThread.join();
}
}
virtual void Start() final {
mThread = std::thread([this] { (*this)(); });
}
virtual void operator()() = 0;
protected:
std::thread mThread;
};
-9
View File
@@ -1,9 +0,0 @@
//
// Created by anon on 4/21/21.
//
#pragma once
#include "rapidjson/stringbuffer.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include "TLuaEngine.h"
#include <tuple>
namespace LuaAPI {
int PanicHandler(lua_State* State);
std::string LuaToString(const sol::object Value, size_t Indent = 1, bool QuoteStrings = false);
void Print(sol::variadic_args);
namespace MP {
extern TLuaEngine* Engine;
std::string GetOSName();
std::tuple<int, int, int> GetServerVersion();
bool TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& Data);
bool TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data);
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
void DropPlayer(int ID, std::optional<std::string> MaybeReason);
void SendChatMessage(int ID, const std::string& Message);
void RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue);
bool IsPlayerGuest(int ID);
bool IsPlayerConnected(int ID);
void Sleep(size_t Ms);
void PrintRaw(sol::variadic_args);
std::string JsonEncode(const sol::table& object);
std::string JsonDiff(const std::string& a, const std::string& b);
std::string JsonDiffApply(const std::string& data, const std::string& patch);
std::string JsonPrettify(const std::string& json);
std::string JsonMinify(const std::string& json);
std::string JsonFlatten(const std::string& json);
std::string JsonUnflatten(const std::string& json);
}
namespace FS {
std::pair<bool, std::string> CreateDirectory(const std::string& Path);
std::pair<bool, std::string> Remove(const std::string& Path);
std::pair<bool, std::string> Rename(const std::string& Path, const std::string& NewPath);
std::pair<bool, std::string> Copy(const std::string& Path, const std::string& NewPath);
std::string GetFilename(const std::string& Path);
std::string GetExtension(const std::string& Path);
std::string GetParentFolder(const std::string& Path);
bool Exists(const std::string& Path);
bool IsDirectory(const std::string& Path);
bool IsFile(const std::string& Path);
std::string ConcatPaths(sol::variadic_args Args);
}
}
-20
View File
@@ -1,20 +0,0 @@
// Author: lionkor
#pragma once
/*
* An RWMutex allows multiple simultaneous readlocks but only one writelock at a time,
* and write locks and read locks are mutually exclusive.
*/
#include <shared_mutex>
#include <mutex>
// Use ReadLock(m) and WriteLock(m) to lock it.
using RWMutex = std::shared_mutex;
// Construct with an RWMutex as a non-const reference.
// locks the mutex in lock_shared mode (for reading). Locking in a thread that already owns a lock
// i.e. locking multiple times successively is UB. Construction may be blocking. Destruction is guaranteed to release the lock.
using ReadLock = std::shared_lock<RWMutex>;
// Construct with an RWMutex as a non-const reference.
// locks the mutex for writing. Construction may be blocking. Destruction is guaranteed to release the lock.
using WriteLock = std::unique_lock<RWMutex>;
-3
View File
@@ -1,3 +0,0 @@
#pragma once
void SetupSignalHandlers();
-33
View File
@@ -1,33 +0,0 @@
#pragma once
#include "Common.h"
#include <atomic>
#include <filesystem>
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
#include <toml.hpp> // header-only version of TOML++
namespace fs = std::filesystem;
class TConfig {
public:
explicit TConfig(const std::string& ConfigFileName);
[[nodiscard]] bool Failed() const { return mFailed; }
void FlushToFile();
private:
void CreateConfigFile(std::string_view name);
void ParseFromFile(std::string_view name);
void PrintDebug();
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, bool& OutValue);
void TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, int& OutValue);
void ParseOldFormat();
bool IsDefault();
bool mFailed { false };
std::string mConfigFileName;
};
-66
View File
@@ -1,66 +0,0 @@
#pragma once
#include "Cryptography.h"
#include "commandline.h"
#include <atomic>
#include <fstream>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
class TLuaEngine;
class TConsole {
public:
TConsole();
void Write(const std::string& str);
void WriteRaw(const std::string& str);
void InitializeLuaConsole(TLuaEngine& Engine);
void BackupOldLog();
void StartLoggingToFile();
Commandline& Internal() { return mCommandline; }
private:
void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false);
void ChangeToLuaConsole(const std::string& LuaStateId);
void ChangeToRegularConsole();
void HandleLuaInternalCommand(const std::string& cmd);
void Command_Lua(const std::string& cmd, const std::vector<std::string>& args);
void Command_Help(const std::string& cmd, const std::vector<std::string>& args);
void Command_Kick(const std::string& cmd, const std::vector<std::string>& args);
void Command_List(const std::string& cmd, const std::vector<std::string>& args);
void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t min, size_t max);
static std::tuple<std::string, std::vector<std::string>> ParseCommand(const std::string& cmd);
static std::string ConcatArgs(const std::vector<std::string>& args, char space = ' ');
std::unordered_map<std::string, std::function<void(const std::string&, const std::vector<std::string>&)>> mCommandMap = {
{ "lua", [this](const auto& a, const auto& b) { Command_Lua(a, b); } },
{ "help", [this](const auto& a, const auto& b) { Command_Help(a, b); } },
{ "kick", [this](const auto& a, const auto& b) { Command_Kick(a, b); } },
{ "list", [this](const auto& a, const auto& b) { Command_List(a, b); } },
{ "status", [this](const auto& a, const auto& b) { Command_Status(a, b); } },
{ "settings", [this](const auto& a, const auto& b) { Command_Settings(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
};
Commandline mCommandline;
std::vector<std::string> mCachedLuaHistory;
std::vector<std::string> mCachedRegularHistory;
TLuaEngine* mLuaEngine { nullptr };
bool mIsLuaConsole { false };
bool mFirstTime { true };
std::string mStateId;
const std::string mDefaultStateId = "BEAMMP_SERVER_CONSOLE";
std::ofstream mLogFileStream;
std::mutex mLogFileStreamMtx;
};
-21
View File
@@ -1,21 +0,0 @@
#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;
};
-262
View File
@@ -1,262 +0,0 @@
#pragma once
#include "TNetwork.h"
#include "TServer.h"
#include <any>
#include <condition_variable>
#include <filesystem>
#include <initializer_list>
#include <list>
#include <lua.hpp>
#include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <set>
#include <toml.hpp>
#include <unordered_map>
#include <vector>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
using TLuaStateId = std::string;
namespace fs = std::filesystem;
/**
* std::variant means, that TLuaArgTypes may be one of the Types listed as template args
*/
using TLuaArgTypes = std::variant<std::string, int, sol::variadic_args, bool>;
static constexpr size_t TLuaArgTypes_String = 0;
static constexpr size_t TLuaArgTypes_Int = 1;
static constexpr size_t TLuaArgTypes_VariadicArgs = 2;
static constexpr size_t TLuaArgTypes_Bool = 3;
class TLuaPlugin;
struct TLuaResult {
bool Ready;
bool Error;
std::string ErrorMessage;
sol::object Result { sol::lua_nil };
TLuaStateId StateId;
std::string Function;
// TODO: Add condition_variable
void WaitUntilReady();
};
struct TLuaPluginConfig {
static inline const std::string FileName = "PluginConfig.toml";
TLuaStateId StateId;
// TODO: Add execute list
};
struct TLuaChunk {
TLuaChunk(std::shared_ptr<std::string> Content,
std::string FileName,
std::string PluginPath);
std::shared_ptr<std::string> Content;
std::string FileName;
std::string PluginPath;
};
class TPluginMonitor : IThreaded {
public:
TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown);
void operator()();
private:
TLuaEngine& mEngine;
fs::path mPath;
std::atomic_bool& mShutdown;
std::unordered_map<std::string, fs::file_time_type> mFileTimes;
};
class TLuaEngine : IThreaded {
public:
enum CallStrategy : int {
BestEffort,
Precise,
};
struct QueuedFunction {
std::string FunctionName;
std::shared_ptr<TLuaResult> Result;
std::vector<TLuaArgTypes> Args;
std::string EventName; // optional, may be empty
};
TLuaEngine();
~TLuaEngine() noexcept {
beammp_debug("Lua Engine terminated");
}
void operator()() override;
TNetwork& Network() { return *mNetwork; }
TServer& Server() { return *mServer; }
void SetNetwork(TNetwork* Network) { mNetwork = Network; }
void SetServer(TServer* Server) { mServer = Server; }
size_t GetResultsToCheckSize() {
std::unique_lock Lock(mResultsToCheckMutex);
return mResultsToCheck.size();
}
size_t GetLuaStateCount() {
std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.size();
}
size_t GetTimedEventsCount() {
std::unique_lock Lock(mTimedEventsMutex);
return mTimedEvents.size();
}
size_t GetRegisteredEventHandlerCount() {
std::unique_lock Lock(mLuaEventsMutex);
size_t LuaEventsCount = 0;
for (const auto& State : mLuaEvents) {
for (const auto& Events : State.second) {
LuaEventsCount += Events.second.size();
}
}
return LuaEventsCount - GetLuaStateCount();
}
static void WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results,
const std::optional<std::chrono::high_resolution_clock::duration>& Max = std::nullopt);
void ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results);
bool HasState(TLuaStateId StateId);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(TLuaStateId StateID, const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
void EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
template <typename... ArgsT>
/**
*
* @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means
* @param EventName Name of the event
* @param IgnoreId
* @param Args
* @return
*/
[[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) {
std::unique_lock Lock(mLuaEventsMutex);
beammp_event(EventName);
if (mLuaEvents.find(EventName) == mLuaEvents.end()) { // if no event handler is defined for 'EventName', return immediately
return {};
}
std::vector<std::shared_ptr<TLuaResult>> Results;
std::vector<TLuaArgTypes> Arguments { TLuaArgTypes { std::forward<ArgsT>(Args) }... };
for (const auto& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) {
Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments));
}
}
}
return Results; //
}
std::set<std::string> GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId);
void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy);
void CancelEventTimers(const std::string& EventName, TLuaStateId StateId);
sol::state_view GetStateForPlugin(const fs::path& PluginPath);
TLuaStateId GetStateIDForPlugin(const fs::path& PluginPath);
void AddResultToCheck(const std::shared_ptr<TLuaResult>& Result);
static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND";
std::vector<std::string> GetStateGlobalKeysForState(TLuaStateId StateId);
// Debugging functions (slow)
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
private:
void CollectAndInitPlugins();
void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
void FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config);
size_t CalculateMemoryUsage();
class StateThreadData : IThreaded {
public:
StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine);
StateThreadData(const StateThreadData&) = delete;
~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy);
void RegisterEvent(const std::string& EventName, const std::string& FunctionName);
void AddPath(const fs::path& Path); // to be added to path and cpath
void operator()() override;
sol::state_view State() { return sol::state_view(mState); }
std::vector<std::string> GetStateGlobalKeys();
// Debug functions, slow
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueue();
std::vector<TLuaEngine::QueuedFunction> Debug_GetStateFunctionQueue();
private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_GetPlayerIdentifiers(int ID);
sol::table Lua_GetPlayers();
std::string Lua_GetPlayerName(int ID);
sol::table Lua_GetPlayerVehicles(int ID);
sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port);
sol::table Lua_JsonDecode(const std::string& str);
int Lua_GetPlayerIDByName(const std::string& Name);
sol::table Lua_FS_ListFiles(const std::string& Path);
sol::table Lua_FS_ListDirectories(const std::string& Path);
std::string mName;
std::atomic_bool& mShutdown;
TLuaStateId mStateId;
lua_State* mState;
std::thread mThread;
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> mStateExecuteQueue;
std::recursive_mutex mStateExecuteQueueMutex;
std::vector<QueuedFunction> mStateFunctionQueue;
std::mutex mStateFunctionQueueMutex;
std::condition_variable mStateFunctionQueueCond;
TLuaEngine* mEngine;
sol::state_view mStateView { mState };
std::queue<fs::path> mPaths;
std::recursive_mutex mPathsMutex;
std::mt19937 mMersenneTwister;
std::uniform_real_distribution<double> mUniformRealDistribution01;
};
struct TimedEvent {
std::chrono::high_resolution_clock::duration Duration {};
std::chrono::high_resolution_clock::time_point LastCompletion {};
std::string EventName;
TLuaStateId StateId;
CallStrategy Strategy;
bool Expired();
void Reset();
};
TNetwork* mNetwork;
TServer* mServer;
TPluginMonitor mPluginMonitor;
std::atomic_bool mShutdown { false };
fs::path mResourceServerPath;
std::vector<std::shared_ptr<TLuaPlugin>> mLuaPlugins;
std::unordered_map<TLuaStateId, std::unique_ptr<StateThreadData>> mLuaStates;
std::recursive_mutex mLuaStatesMutex;
std::unordered_map<std::string /* event name */, std::unordered_map<TLuaStateId, std::set<std::string>>> mLuaEvents;
std::recursive_mutex mLuaEventsMutex;
std::vector<TimedEvent> mTimedEvents;
std::recursive_mutex mTimedEventsMutex;
std::list<std::shared_ptr<TLuaResult>> mResultsToCheck;
std::mutex mResultsToCheckMutex;
std::condition_variable mResultsToCheckCond;
};
// std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
-19
View File
@@ -1,19 +0,0 @@
#include "TLuaEngine.h"
class TLuaPlugin {
public:
TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder);
TLuaPlugin(const TLuaPlugin&) = delete;
TLuaPlugin& operator=(const TLuaPlugin&) = delete;
~TLuaPlugin() noexcept = default;
const TLuaPluginConfig& GetConfig() const { return mConfig; }
fs::path GetFolder() const { return mFolder; }
private:
TLuaPluginConfig mConfig;
TLuaEngine& mEngine;
fs::path mFolder;
std::string mPluginName;
std::unordered_map<std::string, std::shared_ptr<std::string>> mFileContents;
};
-51
View File
@@ -1,51 +0,0 @@
#pragma once
#include "Compat.h"
#include "TResourceManager.h"
#include "TServer.h"
struct TConnection;
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(const TConnection& client);
void Authentication(const TConnection& ClientConnection);
[[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
View File
@@ -1,27 +0,0 @@
#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
View File
@@ -1,21 +0,0 @@
#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;
};
-25
View File
@@ -1,25 +0,0 @@
#pragma once
#include <chrono>
#include <functional>
#include <string>
class TScopedTimer {
public:
TScopedTimer();
TScopedTimer(const std::string& Name);
TScopedTimer(std::function<void(size_t)> OnDestroy);
~TScopedTimer();
auto GetElapsedTime() const {
auto EndTime = std::chrono::high_resolution_clock::now();
auto Delta = EndTime - mStartTime;
size_t TimeDelta = Delta / std::chrono::milliseconds(1);
return TimeDelta;
}
std::function<void(size_t /* time_ms */)> OnDestroy { nullptr };
private:
std::chrono::high_resolution_clock::time_point mStartTime;
std::string Name;
};
-38
View File
@@ -1,38 +0,0 @@
#ifndef SENTRY_H
#define SENTRY_H
#include <mutex>
#include <string>
#include <unordered_map>
enum class SentryLevel {
Debug = -1,
Info = 0,
Warning = 1,
Error = 2,
Fatal = 3,
};
// singleton, dont make this twice
class TSentry final {
public:
TSentry();
~TSentry();
void PrintWelcome();
void SetupUser();
void Log(SentryLevel level, const std::string& logger, const std::string& text);
void LogError(const std::string& text, const std::string& file, const std::string& line);
void SetContext(const std::string& context_name, const std::unordered_map<std::string, std::string>& map);
void LogException(const std::exception& e, const std::string& file, const std::string& line);
void LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function);
void AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line);
// cleared when Logged
void SetTransaction(const std::string& id);
[[nodiscard]] std::unique_lock<std::mutex> CreateExclusiveContext();
private:
bool mValid { true };
std::mutex mMutex;
};
#endif // SENTRY_H
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include "IThreaded.h"
#include "RWMutex.h"
#include "TScopedTimer.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(const std::vector<std::string_view>& Arguments);
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; }
const TScopedTimer UptimeTimer;
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);
};
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
class TVehicleData final {
public:
TVehicleData(int ID, const 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; }
const json& Json() const;
private:
int mID { -1 };
std::string mData;
json mJson;
};
// TODO: unused now, remove?
namespace std {
template <>
struct hash<TVehicleData> {
std::size_t operator()(const TVehicleData& s) const noexcept {
return s.ID();
}
};
}
+345
View File
@@ -0,0 +1,345 @@
// Server Settings!
var map = "";
let _VERSION = "0.0.3"
let UDPExpireTime = 30// In Seconds
const net = require('net');
const uuidv4 = require('uuid/v4');
const args = require('minimist')(process.argv.slice(2));
const chalk = require("chalk")
//console.log(args.port)
if (args.port) {
var tcpport = args.port;
} else {
var tcpport = 30813;
}
var udpport = tcpport + 1;
var wsport = tcpport + 2;
const host = '0.0.0.0';
//==========================================================
// WebSocket Server
//==========================================================
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: wsport });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('[WS] received: %s', message);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.send('Welcome!');
});
console.log(chalk.cyan('[WS]')+' Server listening on 0.0.0.0:' + wsport);
//==========================================================
// TCP Server
//==========================================================
const TCPserver = net.createServer();
TCPserver.listen(tcpport, () => {
console.log(chalk.green('[TCP]')+' Server listening on 0.0.0.0:' + tcpport);
});
let sockets = [];
let players = [];
let names = [];
let vehicles = [];
TCPserver.on('connection', function(sock) {
console.log(chalk.green('[TCP]')+' CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
sockets.push(sock);
var player = {};
player.remoteAddress = sock.remoteAddress;
player.remotePort = sock.remotePort;
player.nickname = "New User, Loading...";
player.id = uuidv4();
player.currentVehID = 0;
players.push(player);
sock.write('HOLA'+player.id+'\n');
if (map == "") {
sock.write("MAPS\n");
} else {
sock.write("MAPC"+map+'\n')
}
sock.write("VCHK"+_VERSION+'\n')
sock.on('data', function(data) {
// Write the data back to all the connected, the client will receive it as data from the server
var str = data.toString();
data = str.trim(); //replace(/\r?\n|\r/g, "");
var code = data.substring(0, 4);
var message = data.substr(4);
if (code != "PING") {
//console.log(code)
}
//if (data.length > 4) {
//console.log(data.length)
//}
switch (code) {
case "PING":
//console.log("Ping Received")
sock.write('PONG\n');
break;
case "CHAT":
sockets.forEach(function(socket, index, array) { // Send update to all clients
socket.write(data+'\n');
});
break;
case "MAPS":
map = message;
console.log("Setting map to: "+map);
sock.write("MAPC"+map+'\n');
break;
case "USER":
players.forEach(function(player, index, array) {
if (player.remoteAddress == sock.remoteAddress && player.remotePort == sock.remotePort) {
console.log("Player Found ("+player.id+"), setting nickname("+data.substr(4)+")");
player.nickname = ""+data.substr(4)+"";
sockets.forEach(function(socket, index, array) { // Send update to all clients
socket.write('PLST'+JSON.stringify(players)+'\n');
socket.write('SMSG'+data.substr(4)+' Just Joined the Session.\n');
});
}
});
break;
case "QUIT":
case "2001":
let index = sockets.findIndex(function(o) {
return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
})
if (index !== -1) sockets.splice(index, 1);
console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
break;
case "U-VI":
case "U-VE":
case "U-VN":
case "U-VP":
case "U-VL":
case "U-VR":
case "U-VV":
//console.log(data)
//players.forEach(function(player, index, array) {
//if (player.remoteAddress != sock.remoteAddress) {
//console.log(player.remoteAddress+' != '+sock.remoteAddress+' Is not the same so we should send?')
//console.log("Got Update to send!")
sockets.forEach(function(socket, index, array) { // Send update to all clients
//console.log(socket.remotePort+' != '+sock.remotePort+' Is not the same so we should send?')
if ((sock.remoteAddress != socket.remoteAddress && sock.remotePort != socket.remotePort) || (sock.remoteAddress == socket.remoteAddress && sock.remotePort != socket.remotePort)) {
socket.write(data+'\n');
}
});
//}
//});
break;
case "U-VC":
sockets.forEach(function(socket, index, array) { // Send update to all clients
socket.write(data+'\n');
});
break;
case "U-NV":
console.log(message)
var vid = uuidv4();
break;
case "C-VS": // Client has changed vehicle. lets update our records.
console.log(message)
players.forEach(function(player, index, array) {
if (player.currentVehID != message && player.remoteAddress == sock.remoteAddress && player.remotePort == sock.remotePort) {
console.log(chalk.green('[TCP]')+" Player Found ("+player.id+"), updating current vehile("+message+")");
player.currentVehID = message;
}
});
break;
default:
console.log(chalk.green('[TCP]')+' Unknown / unhandled data from:' + sock.remoteAddress);
console.log(chalk.green('[TCP]')+' Data -> ' + data);
sockets.forEach(function(socket, index, array) { // Send update to all clients
//if ((sock.remoteAddress != socket.remoteAddress && sock.remotePort != socket.remotePort) || (sock.remoteAddress == socket.remoteAddress && sock.remotePort != socket.remotePort)) {
socket.write(data+'\n');
//}
});
break;
}
sockets.forEach(function(sock, index, array) {
//sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
});
});
// Add a 'close' event handler to this instance of socket
sock.on('close', function(data) {
var index = players.findIndex(function(o) {
return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
})
if (index !== -1) sockets.splice(index, 1);
index = sockets.findIndex(function(o) {
return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
})
if (index !== -1) sockets.splice(index, 1);
console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
players = removePlayer(players, sock.remoteAddress);
console.log("Player list now holds: "+JSON.stringify(players));
sockets.forEach(function(socket, index, array) { // Send update to all clients
socket.write('PLST'+JSON.stringify(players)+'\n');
});
});
sock.on('error', (err) => {
// handle errors here
if (err.code == "ECONNRESET") {
console.error(chalk.red("ERROR ")+"Connection Reset for player: ");
players.forEach(function(player, index, array) {
if (player.remoteAddress == sock.remoteAddress && player.remotePort == sock.remotePort) {
console.error(+player.nickname+" ("+player.id+")");
console.error(chalk.red("ERROR ")+"End Error.");
}
});
} else {
console.error("Sock Error");
console.error(err);
throw err;
}
});
});
TCPserver.on('error', (err) => {
// handle errors here
console.error("TCPserver Error");
console.error(err);
throw err;
});
function removePlayer(array, ip) {
return array.filter(player => player.remoteAddress != ip);
}
//==========================================================
// UDP Server
//==========================================================
var clients = {};
var intervalTime = UDPExpireTime*2*1000;
setInterval(function() {
console.log(clients);
for (var client in clients) {
var lastupdate = clients[client];
var millis = Date.now() - lastupdate;
var t = Math.floor(millis/1000)
if (t > UDPExpireTime) {
console.warn("Found Old Client: "+client)
delete clients[client];
}
}
}, intervalTime);
function updateClient (rinfo) {
for (var client in clients) {
client = JSON.parse(client);
var address = client[0];
var port = client[1];
if (port == rinfo.port && address == rinfo.address) {
client
}
}
}
var dgram = require('dgram');
var UDPserver = dgram.createSocket('udp4');
function UDPsend(message, info) {
UDPserver.send(message, 0, message.length, info.port, info.address, function(error){
if (error) {
console.log("ERROR");
console.log(error);
client.close();
};
});
}
UDPserver.on('listening', function() {
var address = UDPserver.address();
console.log(chalk.rgb(123, 45, 67)('[UDP]')+' Server listening on ' + address.address + ':' + address.port);
});
UDPserver.on('message',function(msg,rinfo){
clients[JSON.stringify([rinfo.address, rinfo.port])] = Date.now();
//sending msg
var str = msg.toString();
data = str.trim(); //replace(/\r?\n|\r/g, "");
var code = data.substring(0, 4);
//if (code != "PING") {
//console.log(msg.toString());
//}
switch (code) {
case "PING":
UDPsend("PONG", rinfo)
break;
case "U-VC":
for (var client in clients) {
client = JSON.parse(client);
var port = client[1];
var address = client[0];
UDPserver.send(msg, 0, msg.length, port, address, function(error){
if (error) {
console.log("ERROR");
console.log(error);
client.close();
};
});
}
break;
case "U-VR":
case "U-VL":
case "U-VP":
case "U-VN":
case "U-VE":
case "U-VI":
for (var client in clients) {
client = JSON.parse(client);
var port = client[1];
var address = client[0];
if (port != rinfo.port && address != rinfo.address) {
UDPserver.send(msg, 0, msg.length, port, address, function(error){
if (error) {
console.log("ERROR");
console.log(error);
client.close();
};
});
}
}
break;
default:
// Unhandled data, this at the moment includes the extra packets of vehicles so it needs to be here until it is handled correctly
for (var client in clients) {
client = JSON.parse(client);
var port = client[1];
var address = client[0];
UDPserver.send(msg, 0, msg.length, port, address, function(error){
if (error) {
console.log("ERROR");
console.log(error);
client.close();
};
});
}
//console.log(chalk.rgb(123, 45, 67)('[UDP]')+' Data received from client : ' + msg.toString());
//console.log(chalk.rgb(123, 45, 67)('[UDP]')+' Received %d bytes from %s:%d\n',msg.length, rinfo.address, rinfo.port);
}
});
UDPserver.bind(Number(udpport));
-135
View File
@@ -1,135 +0,0 @@
openapi: "3.0.2"
info:
title: BeamMP-Server
version: "api-v1"
description: |
The BeamMP-Server optionally runs an HTTP server
which can be used to query information about the server's
status, health, players, etc.
servers:
- url: http://localhost:{port}/api/v1
description: BeamMP-Server API v1
variables:
port:
default: "8000"
paths:
/player/{name}/vehicles:
parameters:
- name: "name"
in: path
required: true
schema:
type: string
example: "LionKor"
get:
summary: vehicles this player has spawend
responses:
"200":
description: OK
content:
"application/json":
schema:
type: array
items:
type: object
properties:
jbm:
type: string
example: "bolide"
/version:
get:
summary: server version
description: |
Server version in 'major.minor.patch' semver format
responses:
"200":
description: OK
content:
"text/plain":
schema:
type: string
example: "3.1.0"
/players:
get:
summary: list of players on this server
responses:
"200":
description: OK
content:
"application/json":
schema:
type: array
example:
- "LionKor"
- "Titch"
items:
type: string
/config:
get:
summary: describes server configuration
description: |
An overview over the configuration currently
used by this server.
responses:
"200":
description: OK
content:
"application/json":
schema:
type: object
properties:
name:
type: string
example: "my awesome beammp server"
description:
type: string
example: "join!"
max_players:
type: integer
example: 7
max_cars:
type: integer
example: 2
map:
type: string
example: "/levels/gridmap_v2/info.json"
private:
type: boolean
/ready:
get:
summary: whether the server has started fully
description: |
True once all subsystems have started up.
This doesn't tell you whether they're healthy,
check /health for that information.
responses:
"200":
description: OK
content:
"text/plain":
schema:
type: boolean
/health:
get:
summary: health of the server's systems
responses:
"200":
description: OK
content:
"application/json":
schema:
type: object
properties:
healthy:
type: boolean
good:
type: array
items:
type: string
example: "Heartbeat"
bad:
type: array
items:
type: string
example: "ResourceManager"
+1268
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
{
"name": "beamng.drive-mp-server",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"author": "Mitch Durso",
"license": "ISC",
"devDependencies": {
"electron": "^6.0.7"
},
"dependencies": {
"chalk": "^3.0.0",
"minimist": "^1.2.0",
"uuid": "^3.3.3",
"ws": "^7.2.1"
}
}
-94
View File
@@ -1,94 +0,0 @@
#include "ArgsParser.h"
#include "Common.h"
#include <algorithm>
void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
for (const auto& Arg : ArgList) {
if (Arg.size() > 2 && Arg.substr(0, 2) == "--") {
// long arg
if (Arg.find("=") != Arg.npos) {
ConsumeLongAssignment(std::string(Arg));
} else {
ConsumeLongFlag(std::string(Arg));
}
} else {
beammp_errorf("Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg);
}
}
}
bool ArgsParser::Verify() {
bool Ok = true;
for (const auto& RegisteredArg : mRegisteredArguments) {
if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) {
beammp_errorf("Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0));
Ok = false;
continue;
} else if (FoundArgument(RegisteredArg.Names)) {
if (RegisteredArg.Flags & Flags::HAS_VALUE) {
if (!GetValueOfArgument(RegisteredArg.Names).has_value()) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' expects a value, but no value was given.");
Ok = false;
}
} else if (GetValueOfArgument(RegisteredArg.Names).has_value()) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' does not expect a value, but one was given.");
Ok = false;
}
}
}
return Ok;
}
void ArgsParser::RegisterArgument(std::vector<std::string>&& ArgumentNames, int Flags) {
mRegisteredArguments.push_back({ ArgumentNames, Flags });
}
bool ArgsParser::FoundArgument(const std::vector<std::string>& Names) {
// if any of the found args match any of the names
return std::any_of(mFoundArgs.begin(), mFoundArgs.end(),
[&Names](const Argument& Arg) -> bool {
// if any of the names match this arg's name
return std::any_of(Names.begin(), Names.end(), [&Arg](const std::string& Name) -> bool {
return Arg.Name == Name;
});
});
}
std::optional<std::string> ArgsParser::GetValueOfArgument(const std::vector<std::string>& Names) {
// finds an entry which has a name that is any of the names in 'Names'
auto Found = std::find_if(mFoundArgs.begin(), mFoundArgs.end(), [&Names](const Argument& Arg) -> bool {
return std::any_of(Names.begin(), Names.end(), [&Arg](const std::string_view& Name) -> bool {
return Arg.Name == Name;
});
});
if (Found != mFoundArgs.end()) {
// found
return Found->Value;
} else {
return std::nullopt;
}
}
bool ArgsParser::IsRegistered(const std::string& Name) {
return std::any_of(mRegisteredArguments.begin(), mRegisteredArguments.end(), [&Name](const RegisteredArgument& Arg) {
auto Iter = std::find(Arg.Names.begin(), Arg.Names.end(), Name);
return Iter != Arg.Names.end();
});
}
void ArgsParser::ConsumeLongAssignment(const std::string& Arg) {
auto Value = Arg.substr(Arg.rfind("=") + 1);
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
if (!IsRegistered(Name)) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
}
mFoundArgs.push_back({ Name, Value });
}
void ArgsParser::ConsumeLongFlag(const std::string& Arg) {
auto Name = Arg.substr(2, Arg.rfind("=") - 2);
mFoundArgs.push_back({ Name, std::nullopt });
if (!IsRegistered(Name)) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
}
}
-118
View File
@@ -1,118 +0,0 @@
#include "Client.h"
#include "CustomAssert.h"
#include "TServer.h"
#include <memory>
#include <optional>
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 {
beammp_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;
std::unique_lock lock(mVehicleDataMutex);
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();
}
int TClient::SecondsSinceLastPing() {
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - mLastPingTime)
.count();
return int(seconds);
}
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;
}
-225
View File
@@ -1,225 +0,0 @@
#include "Common.h"
#include "TConsole.h"
#include <array>
#include <charconv>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <thread>
#include "CustomAssert.h"
#include "Http.h"
Application::TSettings Application::Settings = {};
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
std::unique_lock Lock(mShutdownHandlersMutex);
if (Handler) {
mShutdownHandlers.push_front(Handler);
}
}
void Application::GracefullyShutdown() {
static bool AlreadyShuttingDown = false;
static uint8_t ShutdownAttempts = 0;
if (AlreadyShuttingDown) {
++ShutdownAttempts;
// hard shutdown at 2 additional tries
if (ShutdownAttempts == 2) {
beammp_info("hard shutdown forced by multiple shutdown requests");
std::exit(0);
}
beammp_info("already shutting down!");
return;
} else {
AlreadyShuttingDown = true;
}
beammp_trace("waiting for lock release");
std::unique_lock Lock(mShutdownHandlersMutex);
beammp_info("please wait while all subsystems are shutting down...");
for (size_t i = 0; i < mShutdownHandlers.size(); ++i) {
beammp_info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down");
mShutdownHandlers[i]();
}
// std::exit(-1);
}
std::string Application::ServerVersionString() {
return mVersion.AsString();
}
std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
std::array<uint8_t, 3> Version;
std::stringstream ss(str);
for (uint8_t& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
bool Application::IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
} else if (Newest.major == Current.major && Newest.minor > Current.minor) {
return true;
} else if (Newest.major == Current.major && Newest.minor == Current.minor && Newest.patch > Current.patch) {
return true;
} else {
return false;
}
}
void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) {
switch (status) {
case Status::Good:
beammp_trace("Subsystem '" + Subsystem + "': Good");
break;
case Status::Bad:
beammp_trace("Subsystem '" + Subsystem + "': Bad");
break;
case Status::Starting:
beammp_trace("Subsystem '" + Subsystem + "': Starting");
break;
case Status::ShuttingDown:
beammp_trace("Subsystem '" + Subsystem + "': Shutting down");
break;
case Status::Shutdown:
beammp_trace("Subsystem '" + Subsystem + "': Shutdown");
break;
}
std::unique_lock Lock(mSystemStatusMapMutex);
mSystemStatusMap[Subsystem] = status;
}
void Application::CheckForUpdates() {
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting);
static bool FirstTime = true;
// checks current version against latest version
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
for (const auto& url : GetBackendUrlsInOrder()) {
auto Response = Http::GET(GetBackendUrlsInOrder().at(0), 443, "/v/s");
bool Matches = std::regex_match(Response, VersionRegex);
if (Matches) {
auto MyVersion = ServerVersion();
auto RemoteVersion = Version(VersionStrToInts(Response));
if (IsOutdated(MyVersion, RemoteVersion)) {
std::string RealVersionString = RemoteVersion.AsString();
beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION IS OUT! Please update to the new version (v" + RealVersionString + ") of the BeamMP-Server! Download it here: https://beammp.com/! For a guide on how to update, visit: https://wiki.beammp.com/en/home/server-maintenance#updating-the-server" + std::string(ANSI_RESET));
} else {
if (FirstTime) {
beammp_info("Server up-to-date!");
}
}
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good);
break;
} else {
if (FirstTime) {
beammp_debug("Failed to fetch version from: " + url);
beammp_trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad);
}
}
}
if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) {
if (FirstTime) {
beammp_warn("Unable to fetch version info from backend.");
}
}
FirstTime = false;
}
// thread name stuff
static std::map<std::thread::id, std::string> threadNameMap {};
static std::mutex ThreadNameMapMutex {};
std::string ThreadName(bool DebugModeOverride) {
auto Lock = std::unique_lock(ThreadNameMapMutex);
if (DebugModeOverride || 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) {
std::string ThreadId;
#ifdef BEAMMP_WINDOWS
ThreadId = std::to_string(GetCurrentThreadId());
#elif defined(BEAMMP_APPLE)
ThreadId = std::to_string(getpid()); // todo: research if 'getpid()' is a valid, posix compliant alternative to 'gettid()'
#elif defined(BEAMMP_LINUX)
ThreadId = std::to_string(gettid());
#endif
if (Application::Settings.DebugModeEnabled) {
std::ofstream ThreadFile(".Threads.log", std::ios::app);
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
}
auto Lock = std::unique_lock(ThreadNameMapMutex);
threadNameMap[std::this_thread::get_id()] = str;
}
Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
: major(major)
, minor(minor)
, patch(patch) { }
Version::Version(const std::array<uint8_t, 3>& v)
: Version(v[0], v[1], v[2]) {
}
std::string Version::AsString() {
return fmt::format("{:d}.{:d}.{:d}", major, minor, patch);
}
void LogChatMessage(const std::string& name, int id, const std::string& msg) {
if (Application::Settings.LogChat) {
std::stringstream ss;
ss << ThreadName();
ss << "[CHAT] ";
if (id != -1) {
ss << "(" << id << ") <" << name << "> ";
} else {
ss << name << "";
}
ss << msg;
Application::Console().Write(ss.str());
}
}
std::string GetPlatformAgnosticErrorString() {
#ifdef BEAMMP_WINDOWS
// This will provide us with the error code and an error message, all in one.
int err;
char msgbuf[256];
msgbuf[0] = '\0';
err = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
msgbuf,
sizeof(msgbuf),
nullptr);
if (*msgbuf) {
return std::to_string(GetLastError()) + " - " + std::string(msgbuf);
} else {
return std::to_string(GetLastError());
}
#elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
return std::strerror(errno);
#endif
}
-35
View File
@@ -1,35 +0,0 @@
#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, &current); /* 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
-307
View File
@@ -1,307 +0,0 @@
#include "Http.h"
#include "Client.h"
#include "Common.h"
#include "CustomAssert.h"
#include "LuaAPI.h"
#include "TNetwork.h"
#include "TResourceManager.h"
#include "TServer.h"
#include "httplib.h"
#include <map>
#include <nlohmann/json.hpp>
#include <random>
#include <stdexcept>
// TODO: Add sentry error handling back
using json = nlohmann::json;
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
httplib::SSLClient client(host, port);
client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET);
auto res = client.Get(target.c_str());
if (res) {
if (status) {
*status = res->status;
}
return res->body;
} else {
return Http::ErrorString;
}
}
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
httplib::SSLClient client(host, port);
client.set_read_timeout(std::chrono::seconds(10));
beammp_assert(client.is_valid());
client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET);
auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
if (res) {
if (status) {
*status = res->status;
}
return res->body;
} else {
beammp_debug("POST failed: " + httplib::to_string(res.error()));
return Http::ErrorString;
}
}
// RFC 2616, RFC 7231
static std::map<size_t, const char*> Map = {
{ -1, "Invalid Response Code" },
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 102, "Processing" },
{ 103, "Early Hints" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 207, "Multi-Status" },
{ 208, "Already Reported" },
{ 226, "IM Used" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 306, "(Unused)" },
{ 307, "Temporary Redirect" },
{ 308, "Permanent Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Timeout" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Payload Too Large" },
{ 414, "URI Too Long" },
{ 415, "Unsupported Media Type" },
{ 416, "Range Not Satisfiable" },
{ 417, "Expectation Failed" },
{ 421, "Misdirected Request" },
{ 422, "Unprocessable Entity" },
{ 423, "Locked" },
{ 424, "Failed Dependency" },
{ 425, "Too Early" },
{ 426, "Upgrade Required" },
{ 428, "Precondition Required" },
{ 429, "Too Many Requests" },
{ 431, "Request Header Fields Too Large" },
{ 451, "Unavailable For Legal Reasons" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Timeout" },
{ 505, "HTTP Version Not Supported" },
{ 506, "Variant Also Negotiates" },
{ 507, "Insufficient Storage" },
{ 508, "Loop Detected" },
{ 510, "Not Extended" },
{ 511, "Network Authentication Required" },
// cloudflare status codes
{ 520, "(CDN) Web Server Returns An Unknown Error" },
{ 521, "(CDN) Web Server Is Down" },
{ 522, "(CDN) Connection Timed Out" },
{ 523, "(CDN) Origin Is Unreachable" },
{ 524, "(CDN) A Timeout Occurred" },
{ 525, "(CDN) SSL Handshake Failed" },
{ 526, "(CDN) Invalid SSL Certificate" },
{ 527, "(CDN) Railgun Listener To Origin Error" },
{ 530, "(CDN) 1XXX Internal Error" },
};
static const char Magic[] = {
0x20, 0x2f, 0x5c, 0x5f,
0x2f, 0x5c, 0x0a, 0x28,
0x20, 0x6f, 0x2e, 0x6f,
0x20, 0x29, 0x0a, 0x20,
0x3e, 0x20, 0x5e, 0x20,
0x3c, 0x0a, 0x00
};
std::string Http::Status::ToString(int Code) {
if (Map.find(Code) != Map.end()) {
return Map.at(Code);
} else {
return std::to_string(Code);
}
}
#define API_V1 "/api/v1"
Http::Server::THttpServerInstance::THttpServerInstance(TServer& Server, TNetwork& Network, TResourceManager& ResMan)
: mServer(Server)
, mNetwork(Network)
, mResourceManager(ResMan) {
Application::SetSubsystemStatus("HTTPServer", Application::Status::Starting);
mThread = std::thread(&Http::Server::THttpServerInstance::operator(), this);
}
Http::Server::THttpServerInstance::~THttpServerInstance() {
mThread.detach();
}
void Http::Server::THttpServerInstance::operator()() try {
beammp_infof("HTTP Server starting on [{}]:{}", Application::Settings.HTTPServerIP, Application::Settings.HTTPServerPort);
if (Application::Settings.Private
&& Application::Settings.HTTPServerIP != "::"
&& Application::Settings.HTTPServerIP != "localhost"
&& Application::Settings.HTTPServerIP != "127.0.0.1") {
beammp_warnf("HTTP Server is running on a possibly public IP ({}), but the server is private. The server's HTTP API supports viewing config, players, and other information, even though the server is private. (this is not an error)", Application::Settings.HTTPServerIP);
}
std::unique_ptr<httplib::Server> Server = std::make_unique<httplib::Server>();
Server->Get(API_V1 R"(/player/(.+)/vehicles)", [this](const httplib::Request& Req, httplib::Response& Res) {
if (Req.matches.empty()) {
Res.status = 404;
} else {
const std::string PlayerName = Req.matches[1];
bool Found = false;
json Result = json::array();
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) {
if (!ClientPtr.expired()) {
const auto Client = ClientPtr.lock();
if (Client->GetName() == PlayerName) {
const auto [Cars, Lock] = Client->GetAllCars();
for (const auto& Car : *Cars) {
Result.emplace_back().at("jbm") = Car.Json().at("jbm");
}
Found = true;
}
}
return true;
});
if (!Found) {
Res.status = 404;
} else {
Res.status = 200;
Res.set_content(Result.dump(), "application/json");
}
}
});
Server->Get(API_V1 "/players", [this](const httplib::Request&, httplib::Response& res) {
res.status = 200;
json Players = json::array();
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) {
if (!ClientPtr.expired()) {
Players.push_back(ClientPtr.lock()->GetName());
}
return true;
});
res.set_content(Players.dump(),
"application/json");
});
Server->Get(API_V1 "/version", [](const httplib::Request&, httplib::Response& res) {
res.status = 200;
res.set_content(Application::ServerVersionString(), "text/plain");
});
Server->Get(API_V1 "/config", [](const httplib::Request&, httplib::Response& res) {
res.status = 200;
res.set_content(json {
{ "name", Application::Settings.ServerName },
{ "description", Application::Settings.ServerDesc },
{ "max_players", Application::Settings.MaxPlayers },
{ "max_cars", Application::Settings.MaxCars },
{ "map", Application::Settings.MapName },
{ "private", Application::Settings.Private },
}
.dump(),
"application/json");
});
Server->Get(API_V1 "/ready", [](const httplib::Request&, httplib::Response& res) {
auto Statuses = Application::GetSubsystemStatuses();
bool Started = true;
for (const auto& NameStatusPair : Statuses) {
switch (NameStatusPair.second) {
case Application::Status::Starting:
case Application::Status::ShuttingDown:
case Application::Status::Shutdown:
Started = false;
break;
case Application::Status::Good:
case Application::Status::Bad:
break;
}
}
res.status = 200;
res.set_content(Started ? "true" : "false", "text/plain");
});
Server->Get(API_V1 "/health", [](const httplib::Request&, httplib::Response& res) {
size_t SystemsGood = 0;
size_t SystemsBad = 0;
json Good = json::array();
json Bad = json::array();
auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) {
switch (NameStatusPair.second) {
case Application::Status::Starting:
case Application::Status::ShuttingDown:
case Application::Status::Shutdown:
case Application::Status::Good:
Good.push_back(NameStatusPair.first);
SystemsGood++;
break;
case Application::Status::Bad:
Bad.push_back(NameStatusPair.first);
SystemsBad++;
break;
}
}
res.set_content(
json {
{ "healthy", SystemsBad == 0 },
{ "good", Good },
{ "bad", Bad },
}
.dump(),
"application/json");
res.status = 200;
});
// magic endpoint
Server->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) {
res.set_content(std::string(Magic), "text/plain");
});
Server->set_logger([](const httplib::Request& Req, const httplib::Response& Res) {
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
});
Server->set_error_handler([](const httplib::Request&, httplib::Response& Res) {
if (Res.status >= 400 && Res.body.empty()) {
Res.set_content("Error: " + std::to_string(Res.status) + " " + Map.at(Res.status), "text/plain");
}
});
Server->set_exception_handler([](const httplib::Request& Req, httplib::Response& Res, std::exception& e) {
Res.status = 500;
Res.set_content("Internal Server Error", "text/plain");
beammp_errorf("Exception in http server serving '{}': {}", Req.target, e.what());
});
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
auto ret = Server->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
if (!ret) {
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
}
} catch (const std::exception& e) {
beammp_error("Failed to start http server. Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it. Error: " + std::string(e.what()));
}
-515
View File
@@ -1,515 +0,0 @@
#include "LuaAPI.h"
#include "Client.h"
#include "Common.h"
#include "TLuaEngine.h"
#include <nlohmann/json.hpp>
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>
std::string LuaAPI::LuaToString(const sol::object Value, size_t Indent, bool QuoteStrings) {
if (Indent > 80) {
return "[[possible recursion, refusing to keep printing]]";
}
switch (Value.get_type()) {
case sol::type::userdata: {
std::stringstream ss;
ss << "[[userdata: " << Value.as<sol::userdata>().pointer() << "]]";
return ss.str();
}
case sol::type::thread: {
std::stringstream ss;
ss << "[[thread: " << Value.as<sol::thread>().pointer() << "]] {"
<< "\n";
for (size_t i = 0; i < Indent; ++i) {
ss << "\t";
}
ss << "status: " << std::to_string(int(Value.as<sol::thread>().status())) << "\n}";
return ss.str();
}
case sol::type::lightuserdata: {
std::stringstream ss;
ss << "[[lightuserdata: " << Value.as<sol::lightuserdata>().pointer() << "]]";
return ss.str();
}
case sol::type::string:
if (QuoteStrings) {
return "\"" + Value.as<std::string>() + "\"";
} else {
return Value.as<std::string>();
}
case sol::type::number: {
std::stringstream ss;
ss << Value.as<float>();
return ss.str();
}
case sol::type::lua_nil:
case sol::type::none:
return "<nil>";
case sol::type::boolean:
return Value.as<bool>() ? "true" : "false";
case sol::type::table: {
std::stringstream Result;
auto Table = Value.as<sol::table>();
Result << "[[table: " << Table.pointer() << "]]: {";
if (!Table.empty()) {
for (const auto& Entry : Table) {
Result << "\n";
for (size_t i = 0; i < Indent; ++i) {
Result << "\t";
}
Result << LuaToString(Entry.first, Indent + 1) << ": " << LuaToString(Entry.second, Indent + 1, true) << ",";
}
Result << "\n";
}
for (size_t i = 0; i < Indent - 1; ++i) {
Result << "\t";
}
Result << "}";
return Result.str();
}
case sol::type::function: {
std::stringstream ss;
ss << "[[function: " << Value.as<sol::function>().pointer() << "]]";
return ss.str();
}
default:
return "((unprintable type))";
}
}
std::string LuaAPI::MP::GetOSName() {
#if WIN32
return "Windows";
#elif __linux
return "Linux";
#else
return "Other";
#endif
}
std::tuple<int, int, int> LuaAPI::MP::GetServerVersion() {
return { Application::ServerVersion().major, Application::ServerVersion().minor, Application::ServerVersion().patch };
}
void LuaAPI::Print(sol::variadic_args Args) {
std::string ToPrint = "";
for (const auto& Arg : Args) {
ToPrint += LuaToString(static_cast<const sol::object>(Arg));
ToPrint += "\t";
}
luaprint(ToPrint);
}
static inline bool InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) {
std::string Packet = "E:" + EventName + ":" + Data;
if (PlayerID == -1)
LuaAPI::MP::Engine->Network().SendToAll(nullptr, Packet, true, true);
else {
auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
if (!MaybeClient || MaybeClient.value().expired()) {
beammp_lua_error("TriggerClientEvent invalid Player ID");
return false;
}
auto c = MaybeClient.value().lock();
if (!LuaAPI::MP::Engine->Network().Respond(*c, Packet, true)) {
beammp_lua_error("Respond failed, dropping client " + std::to_string(PlayerID));
LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets");
return false;
}
}
return true;
}
bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& DataObj) {
std::string Data = DataObj.as<std::string>();
return InternalTriggerClientEvent(PlayerID, EventName, Data);
}
void LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (!MaybeClient || MaybeClient.value().expired()) {
beammp_lua_error("Tried to drop client with id " + std::to_string(ID) + ", who doesn't exist");
return;
}
auto c = MaybeClient.value().lock();
LuaAPI::MP::Engine->Network().ClientKick(*c, MaybeReason.value_or("No reason"));
}
void LuaAPI::MP::SendChatMessage(int ID, const std::string& Message) {
std::string Packet = "C:Server: " + Message;
if (ID == -1) {
LogChatMessage("<Server> (to everyone) ", -1, Message);
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;
LogChatMessage("<Server> (to \"" + c->GetName() + "\")", -1, Message);
Engine->Network().Respond(*c, Packet, true);
} else {
beammp_lua_error("SendChatMessage invalid argument [1] invalid ID");
}
}
}
void LuaAPI::MP::RemoveVehicle(int PID, int VID) {
auto MaybeClient = GetClient(Engine->Server(), PID);
if (!MaybeClient || MaybeClient.value().expired()) {
beammp_lua_error("RemoveVehicle invalid Player ID");
return;
}
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);
}
}
void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
switch (ConfigID) {
case 0: // debug
if (NewValue.is<bool>()) {
Application::Settings.DebugModeEnabled = NewValue.as<bool>();
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
} else
beammp_lua_error("set invalid argument [2] expected boolean");
break;
case 1: // private
if (NewValue.is<bool>()) {
Application::Settings.Private = NewValue.as<bool>();
beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false"));
} else
beammp_lua_error("set invalid argument [2] expected boolean");
break;
case 2: // max cars
if (NewValue.is<int>()) {
Application::Settings.MaxCars = NewValue.as<int>();
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars));
} else
beammp_lua_error("set invalid argument [2] expected integer");
break;
case 3: // max players
if (NewValue.is<int>()) {
Application::Settings.MaxPlayers = NewValue.as<int>();
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers));
} else
beammp_lua_error("set invalid argument [2] expected integer");
break;
case 4: // Map
if (NewValue.is<std::string>()) {
Application::Settings.MapName = NewValue.as<std::string>();
beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName);
} else
beammp_lua_error("set invalid argument [2] expected string");
break;
case 5: // Name
if (NewValue.is<std::string>()) {
Application::Settings.ServerName = NewValue.as<std::string>();
beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName);
} else
beammp_lua_error("set invalid argument [2] expected string");
break;
case 6: // Desc
if (NewValue.is<std::string>()) {
Application::Settings.ServerDesc = NewValue.as<std::string>();
beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc);
} else
beammp_lua_error("set invalid argument [2] expected string");
break;
default:
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
break;
}
}
void LuaAPI::MP::Sleep(size_t Ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
}
bool LuaAPI::MP::IsPlayerConnected(int ID) {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient && !MaybeClient.value().expired()) {
return MaybeClient.value().lock()->IsConnected();
} else {
return false;
}
}
bool LuaAPI::MP::IsPlayerGuest(int ID) {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient && !MaybeClient.value().expired()) {
return MaybeClient.value().lock()->IsGuest();
} else {
return false;
}
}
void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
std::string ToPrint = "";
for (const auto& Arg : Args) {
ToPrint += LuaToString(static_cast<const sol::object>(Arg));
ToPrint += "\t";
}
Application::Console().WriteRaw(ToPrint);
}
int LuaAPI::PanicHandler(lua_State* State) {
beammp_lua_error("PANIC: " + sol::stack::get<std::string>(State, 1));
return 0;
}
template <typename FnT, typename... ArgsT>
static std::pair<bool, std::string> FSWrapper(FnT Fn, ArgsT&&... Args) {
std::error_code errc;
std::pair<bool, std::string> Result;
Fn(std::forward<ArgsT>(Args)..., errc);
Result.first = errc == std::error_code {};
if (!Result.first) {
Result.second = errc.message();
}
return Result;
}
std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path) {
std::error_code errc;
std::pair<bool, std::string> Result;
fs::create_directories(fs::relative(Path), errc);
Result.first = errc == std::error_code {};
if (!Result.first) {
Result.second = errc.message();
}
return Result;
}
std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) {
std::error_code errc;
std::pair<bool, std::string> Result;
fs::remove(fs::relative(Path), errc);
Result.first = errc == std::error_code {};
if (!Result.first) {
Result.second = errc.message();
}
return Result;
}
std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const std::string& NewPath) {
std::error_code errc;
std::pair<bool, std::string> Result;
fs::rename(fs::relative(Path), fs::relative(NewPath), errc);
Result.first = errc == std::error_code {};
if (!Result.first) {
Result.second = errc.message();
}
return Result;
}
std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std::string& NewPath) {
std::error_code errc;
std::pair<bool, std::string> Result;
fs::copy(fs::relative(Path), fs::relative(NewPath), fs::copy_options::recursive, errc);
Result.first = errc == std::error_code {};
if (!Result.first) {
Result.second = errc.message();
}
return Result;
}
bool LuaAPI::FS::Exists(const std::string& Path) {
return fs::exists(fs::relative(Path));
}
std::string LuaAPI::FS::GetFilename(const std::string& Path) {
return fs::path(Path).filename().string();
}
std::string LuaAPI::FS::GetExtension(const std::string& Path) {
return fs::path(Path).extension().string();
}
std::string LuaAPI::FS::GetParentFolder(const std::string& Path) {
return fs::path(Path).parent_path().string();
}
bool LuaAPI::FS::IsDirectory(const std::string& Path) {
return fs::is_directory(Path);
}
bool LuaAPI::FS::IsFile(const std::string& Path) {
return fs::is_regular_file(Path);
}
std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
fs::path Path;
for (size_t i = 0; i < Args.size(); ++i) {
auto Obj = Args[i];
if (!Obj.is<std::string>()) {
beammp_lua_error("FS.Concat called with non-string argument");
return "";
}
Path += Obj.as<std::string>();
if (i < Args.size() - 1 && !Path.empty()) {
Path += fs::path::preferred_separator;
}
}
auto Result = Path.lexically_normal().string();
return Result;
}
static void JsonEncodeRecursive(nlohmann::json& json, const sol::object& left, const sol::object& right, bool is_array, size_t depth = 0) {
if (depth > 100) {
beammp_lua_error("json serialize will not go deeper than 100 nested tables, internal references assumed, aborted this path");
return;
}
std::string key;
switch (left.get_type()) {
case sol::type::lua_nil:
case sol::type::none:
case sol::type::poly:
case sol::type::boolean:
case sol::type::lightuserdata:
case sol::type::userdata:
case sol::type::thread:
case sol::type::function:
case sol::type::table:
beammp_lua_error("JsonEncode: left side of table field is unexpected type");
return;
case sol::type::string:
key = left.as<std::string>();
break;
case sol::type::number:
key = std::to_string(left.as<double>());
break;
}
nlohmann::json value;
switch (right.get_type()) {
case sol::type::lua_nil:
case sol::type::none:
return;
case sol::type::poly:
beammp_lua_warn("unsure what to do with poly type in JsonEncode, ignoring");
return;
case sol::type::boolean:
value = right.as<bool>();
break;
case sol::type::lightuserdata:
beammp_lua_warn("unsure what to do with lightuserdata in JsonEncode, ignoring");
return;
case sol::type::userdata:
beammp_lua_warn("unsure what to do with userdata in JsonEncode, ignoring");
return;
case sol::type::thread:
beammp_lua_warn("unsure what to do with thread in JsonEncode, ignoring");
return;
case sol::type::string:
value = right.as<std::string>();
break;
case sol::type::number:
value = right.as<double>();
break;
case sol::type::function:
beammp_lua_warn("unsure what to do with function in JsonEncode, ignoring");
return;
case sol::type::table: {
bool local_is_array = true;
for (const auto& pair : right.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
local_is_array = false;
}
}
for (const auto& pair : right.as<sol::table>()) {
JsonEncodeRecursive(value, pair.first, pair.second, local_is_array, depth + 1);
}
break;
}
}
if (is_array) {
json.push_back(value);
} else {
json[key] = value;
}
}
std::string LuaAPI::MP::JsonEncode(const sol::table& object) {
nlohmann::json json;
// table
bool is_array = true;
for (const auto& pair : object.as<sol::table>()) {
if (pair.first.get_type() != sol::type::number) {
is_array = false;
}
}
for (const auto& entry : object) {
JsonEncodeRecursive(json, entry.first, entry.second, is_array);
}
return json.dump();
}
std::string LuaAPI::MP::JsonDiff(const std::string& a, const std::string& b) {
if (!nlohmann::json::accept(a)) {
beammp_lua_error("JsonDiff first argument is not valid json: `" + a + "`");
return "";
}
if (!nlohmann::json::accept(b)) {
beammp_lua_error("JsonDiff second argument is not valid json: `" + b + "`");
return "";
}
auto a_json = nlohmann::json::parse(a);
auto b_json = nlohmann::json::parse(b);
return nlohmann::json::diff(a_json, b_json).dump();
}
std::string LuaAPI::MP::JsonDiffApply(const std::string& data, const std::string& patch) {
if (!nlohmann::json::accept(data)) {
beammp_lua_error("JsonDiffApply first argument is not valid json: `" + data + "`");
return "";
}
if (!nlohmann::json::accept(patch)) {
beammp_lua_error("JsonDiffApply second argument is not valid json: `" + patch + "`");
return "";
}
auto a_json = nlohmann::json::parse(data);
auto b_json = nlohmann::json::parse(patch);
a_json.patch(b_json);
return a_json.dump();
}
std::string LuaAPI::MP::JsonPrettify(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonPrettify argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).dump(4);
}
std::string LuaAPI::MP::JsonMinify(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonMinify argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).dump(-1);
}
std::string LuaAPI::MP::JsonFlatten(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonFlatten argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).flatten().dump(-1);
}
std::string LuaAPI::MP::JsonUnflatten(const std::string& json) {
if (!nlohmann::json::accept(json)) {
beammp_lua_error("JsonUnflatten argument is not valid json: `" + json + "`");
return "";
}
return nlohmann::json::parse(json).unflatten().dump(-1);
}
bool LuaAPI::MP::TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data) {
return InternalTriggerClientEvent(PlayerID, EventName, JsonEncode(Data));
}
-62
View File
@@ -1,62 +0,0 @@
#include "SignalHandling.h"
#include "Common.h"
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
#include <csignal>
static void UnixSignalHandler(int sig) {
switch (sig) {
case SIGPIPE:
beammp_warn("ignoring SIGPIPE");
break;
case SIGTERM:
beammp_info("gracefully shutting down via SIGTERM");
Application::GracefullyShutdown();
break;
case SIGINT:
beammp_info("gracefully shutting down via SIGINT");
Application::GracefullyShutdown();
break;
default:
beammp_debug("unhandled signal: " + std::to_string(sig));
break;
}
}
#endif // UNIX
#ifdef BEAMMP_WINDOWS
#include <windows.h>
// return TRUE if handled, FALSE if not
BOOL WINAPI Win32CtrlC_Handler(DWORD CtrlType) {
switch (CtrlType) {
case CTRL_C_EVENT:
beammp_info("gracefully shutting down via CTRL+C");
Application::GracefullyShutdown();
return TRUE;
case CTRL_BREAK_EVENT:
beammp_info("gracefully shutting down via CTRL+BREAK");
Application::GracefullyShutdown();
return TRUE;
case CTRL_CLOSE_EVENT:
beammp_info("gracefully shutting down via close");
Application::GracefullyShutdown();
return TRUE;
}
// we dont care for any others like CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT
return FALSE;
}
#endif // WINDOWS
void SetupSignalHandlers() {
// signal handlers for unix#include <windows.h>
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
beammp_trace("registering handlers for signals");
signal(SIGPIPE, UnixSignalHandler);
signal(SIGTERM, UnixSignalHandler);
#ifndef DEBUG
signal(SIGINT, UnixSignalHandler);
#endif // DEBUG
#elif defined(BEAMMP_WINDOWS)
beammp_trace("registering handlers for CTRL_*_EVENTs");
SetConsoleCtrlHandler(Win32CtrlC_Handler, TRUE);
#endif
}
-267
View File
@@ -1,267 +0,0 @@
#include "Common.h"
#include "TConfig.h"
#include <fstream>
#include <iostream>
#include <istream>
#include <sstream>
// General
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";
static constexpr std::string_view StrLogChat = "LogChat";
// Misc
static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
// HTTP
static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled";
static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL";
static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort";
static constexpr std::string_view StrHTTPServerIP = "HTTPServerIP";
TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) {
Application::SetSubsystemStatus("Config", Application::Status::Starting);
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
beammp_info("No config file found! Generating one...");
CreateConfigFile(mConfigFileName);
}
if (!mFailed) {
if (fs::exists("Server.cfg")) {
beammp_warn("An old \"Server.cfg\" file still exists. Please note that this is no longer used. Instead, \"" + std::string(mConfigFileName) + "\" is used. You can safely delete the \"Server.cfg\".");
}
ParseFromFile(mConfigFileName);
}
}
template <typename CommentsT>
void SetComment(CommentsT& Comments, const std::string& Comment) {
Comments.clear();
Comments.push_back(Comment);
}
/**
* @brief Writes out the loaded application state into ServerConfig.toml
*
* This writes out the current state of application settings that are
* applied to the server instance (i.e. the current application settings loaded in the server).
* If the state of the application settings changes during runtime,
* call this function whenever something about the config changes
* whether it is in TConfig.cpp or the configuration file.
*/
void TConfig::FlushToFile() {
auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
data["General"][StrAuthKey.data()] = Application::Settings.Key;
SetComment(data["General"][StrAuthKey.data()].comments(), " AuthKey has to be filled out in order to run the server");
data["General"][StrLogChat.data()] = Application::Settings.LogChat;
SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log");
data["General"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
data["General"][StrPrivate.data()] = Application::Settings.Private;
data["General"][StrPort.data()] = Application::Settings.Port;
data["General"][StrName.data()] = Application::Settings.ServerName;
data["General"][StrMaxCars.data()] = Application::Settings.MaxCars;
data["General"][StrMaxPlayers.data()] = Application::Settings.MaxPlayers;
data["General"][StrMap.data()] = Application::Settings.MapName;
data["General"][StrDescription.data()] = Application::Settings.ServerDesc;
data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
// Misc
data["Misc"][StrHideUpdateMessages.data()] = Application::Settings.HideUpdateMessages;
SetComment(data["Misc"][StrHideUpdateMessages.data()].comments(), " Hides the periodic update message which notifies you of a new server version. You should really keep this on and always update as soon as possible. For more information visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server. An update message will always appear at startup regardless.");
data["Misc"][StrSendErrors.data()] = Application::Settings.SendErrors;
SetComment(data["Misc"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here");
data["Misc"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled;
SetComment(data["Misc"][StrSendErrorsMessageEnabled.data()].comments(), " If SendErrors is `true`, the server will send helpful info about crashes and other issues back to the BeamMP developers. This info may include your config, who is on your server at the time of the error, and similar general information. This kind of data is vital in helping us diagnose and fix issues faster. This has no impact on server performance. You can opt-out of this system by setting this to `false`");
// HTTP
data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort;
SetComment(data["HTTP"][StrHTTPServerIP.data()].comments(), " Which IP to listen on. Pick 0.0.0.0 for a public-facing server with no specific IP, and 127.0.0.1 or 'localhost' for a local server.");
data["HTTP"][StrHTTPServerIP.data()] = Application::Settings.HTTPServerIP;
data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled;
SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server");
std::ofstream Stream(mConfigFileName, std::ios::trunc | std::ios::out);
Stream << data << std::flush;
}
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) {
beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
}
{ // create file context
std::ofstream ofs(name.data());
}
FlushToFile();
size_t FileSize = fs::file_size(name);
std::fstream ofs { std::string(name), std::ios::in | std::ios::out };
if (ofs.good()) {
std::string Contents {};
Contents.resize(FileSize);
ofs.readsome(Contents.data(), FileSize);
ofs.seekp(0);
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'
<< Contents;
beammp_error("There was no \"" + std::string(mConfigFileName) + "\" 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;
ofs.close();
} else {
beammp_error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work.");
Application::SetSubsystemStatus("Config", Application::Status::Bad);
mFailed = true;
}
}
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue) {
if (Table[Category.c_str()][Key.data()].is_string()) {
OutValue = Table[Category.c_str()][Key.data()].as_string();
}
}
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, bool& OutValue) {
if (Table[Category.c_str()][Key.data()].is_boolean()) {
OutValue = Table[Category.c_str()][Key.data()].as_boolean();
}
}
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, int& OutValue) {
if (Table[Category.c_str()][Key.data()].is_integer()) {
OutValue = Table[Category.c_str()][Key.data()].as_integer();
}
}
void TConfig::ParseFromFile(std::string_view name) {
try {
toml::value data = toml::parse<toml::preserve_comments>(name.data());
// GENERAL
TryReadValue(data, "General", StrDebug, Application::Settings.DebugModeEnabled);
TryReadValue(data, "General", StrPrivate, Application::Settings.Private);
TryReadValue(data, "General", StrPort, Application::Settings.Port);
TryReadValue(data, "General", StrMaxCars, Application::Settings.MaxCars);
TryReadValue(data, "General", StrMaxPlayers, Application::Settings.MaxPlayers);
TryReadValue(data, "General", StrMap, Application::Settings.MapName);
TryReadValue(data, "General", StrName, Application::Settings.ServerName);
TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc);
TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource);
TryReadValue(data, "General", StrAuthKey, Application::Settings.Key);
TryReadValue(data, "General", StrLogChat, Application::Settings.LogChat);
// Misc
TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors);
TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled);
// HTTP
TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort);
TryReadValue(data, "HTTP", StrHTTPServerIP, Application::Settings.HTTPServerIP);
TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled);
} catch (const std::exception& err) {
beammp_error("Error parsing config file value: " + std::string(err.what()));
mFailed = true;
Application::SetSubsystemStatus("Config", Application::Status::Bad);
return;
}
PrintDebug();
// Update in any case
FlushToFile();
// all good so far, let's check if there's a key
if (Application::Settings.Key.empty()) {
beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server.");
Application::SetSubsystemStatus("Config", Application::Status::Bad);
mFailed = true;
return;
}
Application::SetSubsystemStatus("Config", Application::Status::Good);
if (Application::Settings.Key.size() != 36) {
beammp_warn("AuthKey specified is the wrong length and likely isn't valid.");
}
}
void TConfig::PrintDebug() {
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false"));
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars));
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers));
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
beammp_debug(std::string(StrLogChat) + ": \"" + (Application::Settings.LogChat ? "true" : "false") + "\"");
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\"");
beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\"");
beammp_debug(std::string(StrHTTPServerIP) + ": \"" + Application::Settings.HTTPServerIP + "\"");
// special!
beammp_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 {
beammp_warn("unknown key in old auth file (ignored): " + Key);
}
Str >> std::ws;
}
}
-641
View File
@@ -1,641 +0,0 @@
#include "TConsole.h"
#include "Common.h"
#include "Compat.h"
#include "Client.h"
#include "CustomAssert.h"
#include "LuaAPI.h"
#include "TLuaEngine.h"
#include <ctime>
#include <sstream>
static inline bool StringStartsWith(const std::string& What, const std::string& StartsWith) {
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
}
static inline std::string TrimString(std::string S) {
S.erase(S.begin(), std::find_if(S.begin(), S.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
S.erase(std::find_if(S.rbegin(), S.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(),
S.end());
return S;
}
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);
auto local_tm = std::localtime(&tt);
char buf[30];
std::string date;
if (Application::Settings.DebugModeEnabled) {
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
date += buf;
auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
auto fraction = now - seconds;
size_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(fraction).count();
char fracstr[5];
std::sprintf(fracstr, "%03lu", ms);
date += fracstr;
date += "] ";
} else {
std::strftime(buf, sizeof(buf), "[%d/%m/%y %T] ", local_tm);
date += buf;
}
return date;
}
void TConsole::BackupOldLog() {
fs::path Path = "Server.log";
if (fs::exists(Path)) {
auto OldLog = Path.filename().stem().string() + ".old.log";
try {
fs::rename(Path, OldLog);
beammp_debug("renamed old log file to '" + OldLog + "'");
} catch (const std::exception& e) {
beammp_warn(e.what());
}
/*
int err = 0;
zip* z = zip_open("ServerLogs.zip", ZIP_CREATE, &err);
if (!z) {
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
return;
}
FILE* File = std::fopen(Path.string().c_str(), "r");
if (!File) {
std::cerr << GetPlatformAgnosticErrorString() << std::endl;
return;
}
std::vector<uint8_t> Buffer;
Buffer.resize(fs::file_size(Path));
std::fread(Buffer.data(), 1, Buffer.size(), File);
std::fclose(File);
auto s = zip_source_buffer(z, Buffer.data(), Buffer.size(), 0);
auto TimePoint = fs::last_write_time(Path);
auto Secs = TimePoint.time_since_epoch().count();
auto MyTimeT = std::time(&Secs);
std::string NewName = Path.stem().string();
NewName += "_";
std::string Time;
Time.resize(32);
size_t n = strftime(Time.data(), Time.size(), "%F_%H.%M.%S", localtime(&MyTimeT));
Time.resize(n);
NewName += Time;
NewName += ".log";
zip_file_add(z, NewName.c_str(), s, 0);
zip_close(z);
*/
}
}
void TConsole::StartLoggingToFile() {
mLogFileStream.open("Server.log");
Application::Console().Internal().on_write = [this](const std::string& ToWrite) {
// TODO: Sanitize by removing all ansi escape codes (vt100)
std::unique_lock Lock(mLogFileStreamMtx);
mLogFileStream.write(ToWrite.c_str(), ToWrite.size());
mLogFileStream.write("\n", 1);
mLogFileStream.flush();
};
}
void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
if (!mIsLuaConsole) {
if (!mLuaEngine) {
beammp_error("Lua engine not initialized yet, please wait and try again");
return;
}
mLuaEngine->EnsureStateExists(mDefaultStateId, "Console");
mStateId = LuaStateId;
mIsLuaConsole = true;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Attached to Lua state '" + mStateId + "'. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua @" + LuaStateId + "> ");
} else {
Application::Console().WriteRaw("Attached to Lua. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua> ");
}
mCachedRegularHistory = mCommandline.history();
mCommandline.set_history(mCachedLuaHistory);
}
}
void TConsole::ChangeToRegularConsole() {
if (mIsLuaConsole) {
mIsLuaConsole = false;
if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Detached from Lua state '" + mStateId + "'.");
} else {
Application::Console().WriteRaw("Detached from Lua.");
}
mCachedLuaHistory = mCommandline.history();
mCommandline.set_history(mCachedRegularHistory);
mCommandline.set_prompt("> ");
mStateId = mDefaultStateId;
}
}
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (n == 0 && args.size() != 0) {
Application::Console().WriteRaw("This command expects no arguments.");
return false;
} else if (args.size() != n) {
Application::Console().WriteRaw("Expected " + std::to_string(n) + " argument(s), instead got " + std::to_string(args.size()));
return false;
} else {
return true;
}
}
bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t min, size_t max) {
if (min == max) {
return EnsureArgsCount(args, min);
} else {
if (args.size() > max) {
Application::Console().WriteRaw("Too many arguments. At most " + std::to_string(max) + " arguments expected, got " + std::to_string(args.size()) + " instead.");
return false;
} else if (args.size() < min) {
Application::Console().WriteRaw("Too few arguments. At least " + std::to_string(max) + " arguments expected, got " + std::to_string(args.size()) + " instead.");
return false;
}
}
return true;
}
void TConsole::Command_Lua(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0, 1)) {
return;
}
if (args.size() == 1) {
auto NewStateId = args.at(0);
beammp_assert(!NewStateId.empty());
if (mLuaEngine->HasState(NewStateId)) {
ChangeToLuaConsole(NewStateId);
} else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
}
} else if (args.size() == 0) {
ChangeToLuaConsole(mDefaultStateId);
}
}
void TConsole::Command_Help(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
static constexpr const char* sHelpString = R"(
Commands:
help displays this help
exit shuts down the server
kick <name> [reason] kicks specified player with an optional reason
list lists all players and info about them
say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua
settings [command] sets or gets settings for the server, run `settings help` for more info
status how the server is doing and what it's up to)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
}
std::string TConsole::ConcatArgs(const std::vector<std::string>& args, char space) {
std::string Result;
for (const auto& arg : args) {
Result += arg + space;
}
Result = Result.substr(0, Result.size() - 1); // strip trailing space
return Result;
}
void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 1, size_t(-1))) {
return;
}
auto Name = args.at(0);
std::string Reason = "Kicked by server console";
if (args.size() > 1) {
Reason = ConcatArgs({ args.begin() + 1, args.end() });
}
beammp_trace("attempt to kick '" + Name + "' for '" + Reason + "'");
bool Kicked = false;
auto NameCompare = [](std::string Name1, std::string Name2) -> bool {
std::for_each(Name1.begin(), Name1.end(), [](char& c) { c = tolower(c); });
std::for_each(Name2.begin(), Name2.end(), [](char& c) { c = tolower(c); });
return StringStartsWith(Name1, Name2) || StringStartsWith(Name2, Name1);
};
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (!Client.expired()) {
auto locked = Client.lock();
if (NameCompare(locked->GetName(), Name)) {
mLuaEngine->Network().ClientKick(*locked, Reason);
Kicked = true;
return false;
}
}
return true;
});
if (!Kicked) {
Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found.");
} else {
Application::Console().WriteRaw("Kicked player '" + Name + "' for reason: '" + Reason + "'.");
}
}
std::tuple<std::string, std::vector<std::string>> TConsole::ParseCommand(const std::string& CommandWithArgs) {
// Algorithm designed and implemented by Lion Kortlepel (c) 2022
// It correctly splits arguments, including respecting single and double quotes, as well as backticks
auto End_i = CommandWithArgs.find_first_of(' ');
std::string Command = CommandWithArgs.substr(0, End_i);
std::string ArgsStr {};
if (End_i != std::string::npos) {
ArgsStr = CommandWithArgs.substr(End_i);
}
std::vector<std::string> Args;
char* PrevPtr = ArgsStr.data();
char* Ptr = ArgsStr.data();
const char* End = ArgsStr.data() + ArgsStr.size();
while (Ptr != End) {
std::string Arg = "";
// advance while space
while (Ptr != End && std::isspace(*Ptr))
++Ptr;
PrevPtr = Ptr;
// advance while NOT space, also handle quotes
while (Ptr != End && !std::isspace(*Ptr)) {
// TODO: backslash escaping quotes
for (char Quote : { '"', '\'', '`' }) {
if (*Ptr == Quote) {
// seek if there's a closing quote
// if there is, go there and continue, otherwise ignore
char* Seeker = Ptr + 1;
while (Seeker != End && *Seeker != Quote)
++Seeker;
if (Seeker != End) {
// found closing quote
Ptr = Seeker;
}
break; // exit for loop
}
}
++Ptr;
}
Arg = std::string(PrevPtr, Ptr - PrevPtr);
// remove quotes if enclosed in quotes
for (char Quote : { '"', '\'', '`' }) {
if (!Arg.empty() && Arg.at(0) == Quote && Arg.at(Arg.size() - 1) == Quote) {
Arg = Arg.substr(1, Arg.size() - 2);
break;
}
}
if (!Arg.empty()) {
Args.push_back(Arg);
}
}
return { Command, Args };
}
void TConsole::Command_Settings(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
}
void TConsole::Command_Say(const std::string& FullCmd) {
if (FullCmd.size() > 3) {
auto Message = FullCmd.substr(4);
LuaAPI::MP::SendChatMessage(-1, Message);
if (!Application::Settings.LogChat) {
Application::Console().WriteRaw("Chat message sent!");
}
}
}
void TConsole::Command_List(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
if (mLuaEngine->Server().ClientCount() == 0) {
Application::Console().WriteRaw("No players online.");
} else {
std::stringstream ss;
ss << std::left << std::setw(25) << "Name" << std::setw(6) << "ID" << std::setw(6) << "Cars" << std::endl;
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (!Client.expired()) {
auto locked = Client.lock();
ss << std::left << std::setw(25) << locked->GetName()
<< std::setw(6) << locked->GetID()
<< std::setw(6) << locked->GetCarCount() << "\n";
}
return true;
});
auto Str = ss.str();
Application::Console().WriteRaw(Str.substr(0, Str.size() - 1));
}
}
void TConsole::Command_Status(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
std::stringstream Status;
size_t CarCount = 0;
size_t ConnectedCount = 0;
size_t GuestCount = 0;
size_t SyncedCount = 0;
size_t SyncingCount = 0;
size_t MissedPacketQueueSum = 0;
int LargestSecondsSinceLastPing = 0;
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (!Client.expired()) {
auto Locked = Client.lock();
CarCount += Locked->GetCarCount();
ConnectedCount += Locked->IsConnected() ? 1 : 0;
GuestCount += Locked->IsGuest() ? 1 : 0;
SyncedCount += Locked->IsSynced() ? 1 : 0;
SyncingCount += Locked->IsSyncing() ? 1 : 0;
MissedPacketQueueSum += Locked->MissedPacketQueueSize();
if (Locked->SecondsSinceLastPing() < LargestSecondsSinceLastPing) {
LargestSecondsSinceLastPing = Locked->SecondsSinceLastPing();
}
}
return true;
});
size_t SystemsStarting = 0;
size_t SystemsGood = 0;
size_t SystemsBad = 0;
size_t SystemsShuttingDown = 0;
size_t SystemsShutdown = 0;
std::string SystemsBadList {};
std::string SystemsGoodList {};
std::string SystemsStartingList {};
std::string SystemsShuttingDownList {};
std::string SystemsShutdownList {};
auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) {
switch (NameStatusPair.second) {
case Application::Status::Good:
SystemsGood++;
SystemsGoodList += NameStatusPair.first + ", ";
break;
case Application::Status::Bad:
SystemsBad++;
SystemsBadList += NameStatusPair.first + ", ";
break;
case Application::Status::Starting:
SystemsStarting++;
SystemsStartingList += NameStatusPair.first + ", ";
break;
case Application::Status::ShuttingDown:
SystemsShuttingDown++;
SystemsShuttingDownList += NameStatusPair.first + ", ";
break;
case Application::Status::Shutdown:
SystemsShutdown++;
SystemsShutdownList += NameStatusPair.first + ", ";
break;
}
}
// remove ", " at the end
SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2);
SystemsGoodList = SystemsGoodList.substr(0, SystemsGoodList.size() - 2);
SystemsStartingList = SystemsStartingList.substr(0, SystemsStartingList.size() - 2);
SystemsShuttingDownList = SystemsShuttingDownList.substr(0, SystemsShuttingDownList.size() - 2);
SystemsShutdownList = SystemsShutdownList.substr(0, SystemsShutdownList.size() - 2);
auto ElapsedTime = mLuaEngine->Server().UptimeTimer.GetElapsedTime();
Status << "BeamMP-Server Status:\n"
<< "\tTotal Players: " << mLuaEngine->Server().ClientCount() << "\n"
<< "\tSyncing Players: " << SyncingCount << "\n"
<< "\tSynced Players: " << SyncedCount << "\n"
<< "\tConnected Players: " << ConnectedCount << "\n"
<< "\tGuests: " << GuestCount << "\n"
<< "\tCars: " << CarCount << "\n"
<< "\tUptime: " << ElapsedTime << "ms (~" << size_t(ElapsedTime / 1000.0 / 60.0 / 60.0) << "h) \n"
<< "\tLua:\n"
<< "\t\tQueued results to check: " << mLuaEngine->GetResultsToCheckSize() << "\n"
<< "\t\tStates: " << mLuaEngine->GetLuaStateCount() << "\n"
<< "\t\tEvent timers: " << mLuaEngine->GetTimedEventsCount() << "\n"
<< "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n"
<< "\tSubsystems:\n"
<< "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n"
<< "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n"
<< "\t\tGood: [ " << SystemsGoodList << " ]\n"
<< "\t\tStarting: [ " << SystemsStartingList << " ]\n"
<< "\t\tBad: [ " << SystemsBadList << " ]\n"
<< "\t\tShutting down: [ " << SystemsShuttingDownList << " ]\n"
<< "\t\tShut down: [ " << SystemsShutdownList << " ]\n"
<< "";
Application::Console().WriteRaw(Status.str());
}
void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
auto FutureIsNonNil =
[](const std::shared_ptr<TLuaResult>& Future) {
if (!Future->Error && Future->Result.valid()) {
auto Type = Future->Result.get_type();
return Type != sol::type::lua_nil && Type != sol::type::none;
}
return false;
};
std::vector<std::shared_ptr<TLuaResult>> NonNilFutures;
{ // Futures scope
auto Futures = mLuaEngine->TriggerEvent("onConsoleInput", "", cmd);
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
size_t Count = 0;
for (auto& Future : Futures) {
if (!Future->Error) {
++Count;
}
}
for (const auto& Future : Futures) {
if (FutureIsNonNil(Future)) {
NonNilFutures.push_back(Future);
}
}
}
if (NonNilFutures.size() == 0) {
if (!IgnoreNotACommand) {
Application::Console().WriteRaw("Error: Unknown command: '" + cmd + "'. Type 'help' to see a list of valid commands.");
}
} else {
std::stringstream Reply;
if (NonNilFutures.size() > 1) {
for (size_t i = 0; i < NonNilFutures.size(); ++i) {
Reply << NonNilFutures[i]->StateId << ": \n"
<< LuaAPI::LuaToString(NonNilFutures[i]->Result);
if (i < NonNilFutures.size() - 1) {
Reply << "\n";
}
}
} else {
Reply << LuaAPI::LuaToString(NonNilFutures[0]->Result);
}
Application::Console().WriteRaw(Reply.str());
}
}
void TConsole::HandleLuaInternalCommand(const std::string& cmd) {
if (cmd == "exit") {
ChangeToRegularConsole();
} else if (cmd == "queued") {
auto QueuedFunctions = LuaAPI::MP::Engine->Debug_GetStateFunctionQueueForState(mStateId);
Application::Console().WriteRaw("Pending functions in State '" + mStateId + "'");
std::unordered_map<std::string, size_t> FunctionsCount;
std::vector<std::string> FunctionsInOrder;
while (!QueuedFunctions.empty()) {
auto Tuple = QueuedFunctions.front();
QueuedFunctions.erase(QueuedFunctions.begin());
FunctionsInOrder.push_back(Tuple.FunctionName);
FunctionsCount[Tuple.FunctionName] += 1;
}
std::set<std::string> Uniques;
for (const auto& Function : FunctionsInOrder) {
if (Uniques.count(Function) == 0) {
Uniques.insert(Function);
if (FunctionsCount.at(Function) > 1) {
Application::Console().WriteRaw(" " + Function + " (" + std::to_string(FunctionsCount.at(Function)) + "x)");
} else {
Application::Console().WriteRaw(" " + Function);
}
}
}
Application::Console().WriteRaw("Executed functions waiting to be checked in State '" + mStateId + "'");
for (const auto& Function : LuaAPI::MP::Engine->Debug_GetResultsToCheckForState(mStateId)) {
Application::Console().WriteRaw(" '" + Function.Function + "' (Ready? " + (Function.Ready ? "Yes" : "No") + ", Error? " + (Function.Error ? "Yes: '" + Function.ErrorMessage + "'" : "No") + ")");
}
} else if (cmd == "events") {
auto Events = LuaAPI::MP::Engine->Debug_GetEventsForState(mStateId);
Application::Console().WriteRaw("Registered Events + Handlers for State '" + mStateId + "'");
for (const auto& EventHandlerPair : Events) {
Application::Console().WriteRaw(" Event '" + EventHandlerPair.first + "'");
for (const auto& Handler : EventHandlerPair.second) {
Application::Console().WriteRaw(" " + Handler);
}
}
} else if (cmd == "help") {
Application::Console().WriteRaw(R"(BeamMP Lua Debugger
All commands must be prefixed with a `:`. Non-prefixed commands are interpreted as Lua.
Commands
:exit detaches (exits) from this Lua console
:help displays this help
:events shows a list of currently registered events
:queued shows a list of all pending and queued functions)");
} else {
beammp_error("internal command '" + cmd + "' is not known");
}
}
TConsole::TConsole() {
mCommandline.enable_history();
mCommandline.set_history_limit(20);
mCommandline.set_prompt("> ");
BackupOldLog();
mCommandline.on_command = [this](Commandline& c) {
try {
auto TrimmedCmd = c.get_command();
TrimmedCmd = TrimString(TrimmedCmd);
auto [cmd, args] = ParseCommand(TrimmedCmd);
mCommandline.write(mCommandline.prompt() + TrimmedCmd);
if (mIsLuaConsole) {
if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second");
} else if (!cmd.empty() && cmd.at(0) == ':') {
HandleLuaInternalCommand(cmd.substr(1));
} else {
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" });
while (!Future->Ready) {
std::this_thread::yield(); // TODO: Add a timeout
}
if (Future->Error) {
beammp_lua_error("error in " + mStateId + ": " + Future->ErrorMessage);
}
}
} else {
if (!mLuaEngine) {
beammp_error("Attempted to run a command before Lua engine started. Please wait and try again.");
} else if (cmd == "exit") {
beammp_info("gracefully shutting down");
Application::GracefullyShutdown();
} else if (cmd == "say") {
RunAsCommand(TrimmedCmd, true);
Command_Say(TrimmedCmd);
} else {
if (mCommandMap.find(cmd) != mCommandMap.end()) {
mCommandMap.at(cmd)(cmd, args);
RunAsCommand(TrimmedCmd, true);
} else {
RunAsCommand(TrimmedCmd);
}
}
}
} catch (const std::exception& e) {
beammp_error("Console died with: " + std::string(e.what()) + ". This could be a fatal error and could cause the server to terminate.");
}
};
mCommandline.on_autocomplete = [this](Commandline&, std::string stub, int) {
std::vector<std::string> suggestions;
try {
auto cmd = TrimString(stub);
// beammp_error("yes 1");
// beammp_error(stub);
if (mIsLuaConsole) { // if lua
if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second");
} else {
std::string prefix {};
for (size_t i = stub.length(); i > 0; i--) {
if (!std::isalnum(stub[i - 1]) && stub[i - 1] != '_') {
prefix = stub.substr(0, i);
stub = stub.substr(i);
break;
}
}
auto keys = mLuaEngine->GetStateGlobalKeysForState(mStateId);
for (const auto& key : keys) {
std::string::size_type n = key.find(stub);
if (n == 0) {
suggestions.push_back(prefix + key);
// beammp_warn(cmd_name);
}
}
}
} else { // if not lua
for (const auto& [cmd_name, cmd_fn] : mCommandMap) {
std::string::size_type n = cmd_name.find(stub);
if (n == 0) {
suggestions.push_back(cmd_name);
// beammp_warn(cmd_name);
}
}
}
} catch (const std::exception& e) {
beammp_error("Console died with: " + std::string(e.what()) + ". This could be a fatal error and could cause the server to terminate.");
}
return suggestions;
};
}
void TConsole::Write(const std::string& str) {
auto ToWrite = GetDate() + str;
mCommandline.write(ToWrite);
}
void TConsole::WriteRaw(const std::string& str) {
mCommandline.write(str);
}
void TConsole::InitializeLuaConsole(TLuaEngine& Engine) {
mLuaEngine = &Engine;
}
-185
View File
@@ -1,185 +0,0 @@
#include "THeartbeatThread.h"
#include "Client.h"
#include "Http.h"
//#include "SocketIO.h"
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.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;
size_t UpdateReminderCounter = 0;
while (!mShutdown) {
++UpdateReminderCounter;
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;
}
beammp_debug("heartbeat (after " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()) + "s)");
Last = Body;
LastNormalUpdateTime = Now;
if (!Application::Settings.CustomIP.empty()) {
Body += "&ip=" + Application::Settings.CustomIP;
}
Body += "&pps=" + Application::PPS();
beammp_trace("heartbeat body: '" + Body + "'");
auto SentryReportError = [&](const std::string& transaction, int status) {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("heartbeat",
{ { "response-body", T },
{ "request-body", Body } });
Sentry.SetTransaction(transaction);
beammp_trace("sending log to sentry: " + std::to_string(status) + " for " + transaction);
Sentry.Log(SentryLevel::Error, "default", Http::Status::ToString(status) + " (" + std::to_string(status) + ")");
};
auto Target = "/heartbeat";
unsigned int ResponseCode = 0;
rapidjson::Document Doc;
bool Ok = false;
for (const auto& Url : Application::GetBackendUrlsInOrder()) {
T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
beammp_trace(T);
Doc.Parse(T.data(), T.size());
if (Doc.HasParseError() || !Doc.IsObject()) {
if (!Application::Settings.Private) {
beammp_error("Backend response failed to parse as valid json");
beammp_trace("Response was: `" + T + "`");
}
Sentry.SetContext("JSON Response", { { "reponse", T } });
SentryReportError(Url + Target, ResponseCode);
} else if (ResponseCode != 200) {
SentryReportError(Url + Target, ResponseCode);
} else {
// all ok
Ok = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::string Status {};
std::string Code {};
std::string Message {};
const auto StatusKey = "status";
const auto CodeKey = "code";
const auto MessageKey = "msg";
if (Ok) {
if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) {
Status = Doc[StatusKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { StatusKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) {
Code = Doc[CodeKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { CodeKey, "invalid string / missing" } });
Ok = false;
}
if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) {
Message = Doc[MessageKey].GetString();
} else {
Sentry.SetContext("JSON Response", { { MessageKey, "invalid string / missing" } });
Ok = false;
}
if (!Ok) {
beammp_error("Missing/invalid json members in backend response");
Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__));
}
} else {
if (!Application::Settings.Private) {
beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work.");
}
}
if (Ok && !isAuth && !Application::Settings.Private) {
if (Status == "2000") {
beammp_info(("Authenticated! " + Message));
isAuth = true;
} else if (Status == "200") {
beammp_info(("Resumed authenticated session! " + Message));
isAuth = true;
} else {
if (Message.empty()) {
Message = "Backend didn't provide a reason.";
}
beammp_error("Backend REFUSED the auth key. Reason: " + Message);
}
}
if (isAuth || Application::Settings.Private) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
Application::CheckForUpdates();
}
}
}
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::ServerVersionString()
<< "&clientversion=" << Application::ClientVersionString()
<< "&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::SetSubsystemStatus("Heartbeat", Application::Status::Starting);
Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown);
if (mThread.joinable()) {
mShutdown = true;
mThread.join();
}
Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown);
});
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() {
}*/
-1049
View File
File diff suppressed because it is too large Load Diff
-52
View File
@@ -1,52 +0,0 @@
#include "TLuaPlugin.h"
#include <chrono>
#include <functional>
#include <random>
#include <utility>
TLuaPlugin::TLuaPlugin(TLuaEngine& Engine, const TLuaPluginConfig& Config, const fs::path& MainFolder)
: mConfig(Config)
, mEngine(Engine)
, mFolder(MainFolder)
, mPluginName(MainFolder.stem().string())
, mFileContents(0) {
beammp_debug("Lua plugin \"" + mPluginName + "\" starting in \"" + mFolder.string() + "\"");
std::vector<fs::path> Entries;
for (const auto& Entry : fs::directory_iterator(mFolder)) {
if (Entry.is_regular_file() && Entry.path().extension() == ".lua") {
Entries.push_back(Entry);
}
}
// sort alphabetically (not needed if config is used to determine call order)
// TODO: Use config to figure out what to run in which order
std::sort(Entries.begin(), Entries.end(), [](const fs::path& first, const fs::path& second) {
auto firstStr = first.string();
auto secondStr = second.string();
std::transform(firstStr.begin(), firstStr.end(), firstStr.begin(), ::tolower);
std::transform(secondStr.begin(), secondStr.end(), secondStr.begin(), ::tolower);
return firstStr < secondStr;
});
std::vector<std::pair<fs::path, std::shared_ptr<TLuaResult>>> ResultsToCheck;
for (const auto& Entry : Entries) {
// read in entire file
try {
std::ifstream FileStream(Entry.string(), std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Entry);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
mFileContents[fs::relative(Entry).string()] = Contents;
// Execute first time
auto Result = mEngine.EnqueueScript(mConfig.StateId, TLuaChunk(Contents, Entry.string(), MainFolder.string()));
ResultsToCheck.emplace_back(Entry.string(), std::move(Result));
} catch (const std::exception& e) {
beammp_error("Error loading file \"" + Entry.string() + "\": " + e.what());
}
}
for (auto& Result : ResultsToCheck) {
Result.second->WaitUntilReady();
if (Result.second->Error) {
beammp_lua_error("Failed: \"" + Result.first.string() + "\": " + Result.second->ErrorMessage);
}
}
}
-991
View File
@@ -1,991 +0,0 @@
#include "TNetwork.h"
#include "Client.h"
#include "LuaAPI.h"
#include "TLuaEngine.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::SetSubsystemStatus("TCPNetwork", Application::Status::Starting);
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting);
Application::RegisterShutdownHandler([&] {
beammp_debug("Kicking all players due to shutdown");
Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool {
if (!client.expired()) {
ClientKick(*client.lock(), "Server shutdown");
}
return true;
});
});
Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
if (mUDPThread.joinable()) {
mShutdown = true;
mUDPThread.detach();
}
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown);
});
Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown);
if (mTCPThread.joinable()) {
mShutdown = true;
mTCPThread.detach();
}
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown);
});
mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
mUDPThread = std::thread(&TNetwork::UDPServerMain, this);
}
void TNetwork::UDPServerMain() {
RegisterThread("UDPServer");
#if defined(BEAMMP_WINDOWS)
WSADATA data;
if (WSAStartup(514, &data)) {
beammp_error(("Can't start Winsock!"));
// return;
}
#endif // WINDOWS
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) {
beammp_error("bind() failed: " + GetPlatformAgnosticErrorString());
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(-1); // TODO: Wtf.
// return;
}
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
beammp_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) {
beammp_error(("fatal: ") + std::string(e.what()));
}
}
}
void TNetwork::TCPServerMain() {
RegisterThread("TCPServer");
#if defined(BEAMMP_WINDOWS)
WSADATA wsaData;
if (WSAStartup(514, &wsaData)) {
beammp_error("Can't start Winsock! Shutting down");
Application::GracefullyShutdown();
}
#endif // WINDOWS
TConnection client {};
SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (Listener == BEAMMP_INVALID_SOCKET) {
beammp_error("Failed to create socket: " + GetPlatformAgnosticErrorString()
+ ". This is a fatal error, as a socket is needed for the server to operate. Shutting down.");
Application::GracefullyShutdown();
}
#if defined(BEAMMP_WINDOWS)
const char optval = 0;
int ret = ::setsockopt(Listener, SOL_SOCKET, SO_DONTLINGER, &optval, sizeof(optval));
#elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
int optval = true;
int ret = ::setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void*>(&optval), sizeof(optval));
#endif
// not a fatal error
if (ret < 0) {
beammp_error("Failed to set up listening socket to not linger / reuse address. "
"This may cause the socket to refuse to bind(). Error: "
+ GetPlatformAgnosticErrorString());
}
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) {
beammp_error("bind() failed, the server cannot operate and will shut down now. "
"Error: "
+ GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
}
if (listen(Listener, SOMAXCONN) < 0) {
beammp_error("listen() failed, which is needed for the server to operate. "
"Shutting down. Error: "
+ GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
}
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
beammp_info("Vehicle event network online");
do {
try {
if (mShutdown) {
beammp_debug("shutdown during TCP wait for accept loop");
break;
}
client.SockAddrLen = sizeof(client.SockAddr);
client.Socket = accept(Listener, &client.SockAddr, &client.SockAddrLen);
if (client.Socket == -1) {
beammp_warn(("Got an invalid client socket on connect! Skipping..."));
continue;
}
// set timeout
size_t SendTimeoutMS = 30 * 1000;
#if defined(BEAMMP_WINDOWS)
int ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&SendTimeoutMS), sizeof(SendTimeoutMS));
#else // POSIX
struct timeval optval;
optval.tv_sec = (int)(SendTimeoutMS / 1000);
optval.tv_usec = (SendTimeoutMS % 1000) * 1000;
ret = ::setsockopt(client.Socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<void*>(&optval), sizeof(optval));
#endif
if (ret < 0) {
throw std::runtime_error("setsockopt recv timeout: " + GetPlatformAgnosticErrorString());
}
std::thread ID(&TNetwork::Identify, this, client);
ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) {
beammp_error("fatal: " + std::string(e.what()));
}
} while (client.Socket != BEAMMP_INVALID_SOCKET);
beammp_debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
CloseSocketProper(client.Socket);
#ifdef BEAMMP_WINDOWS
CloseSocketProper(client.Socket);
WSACleanup();
#endif // WINDOWS
}
#undef GetObject // Fixes Windows
#include "Json.h"
void TNetwork::Identify(const TConnection& client) {
RegisterThreadAuto();
char Code;
if (recv(client.Socket, &Code, 1, 0) != 1) {
CloseSocketProper(client.Socket);
return;
}
if (Code == 'C') {
Authentication(client);
} else if (Code == 'D') {
HandleDownload(client.Socket);
} else if (Code == 'P') {
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
send(client.Socket, "P", 1, MSG_NOSIGNAL);
#else
send(client.Socket, "P", 1, 0);
#endif
CloseSocketProper(client.Socket);
return;
} else {
CloseSocketProper(client.Socket);
}
}
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(const TConnection& ClientConnection) {
auto Client = CreateClient(ClientConnection.Socket);
char AddrBuf[64];
// TODO: IPv6 would need this to be changed
auto str = inet_ntop(AF_INET, reinterpret_cast<const void*>(&ClientConnection.SockAddr), AddrBuf, sizeof(ClientConnection.SockAddr));
beammp_trace("This thread is ip " + std::string(str));
Client->SetIdentifier("ip", str);
std::string Rc; // TODO: figure out why this is not default constructed
beammp_info("Identifying new ClientConnection...");
Rc = TCPRcv(*Client);
if (Rc.size() > 3 && Rc.substr(0, 2) == "VC") {
Rc = Rc.substr(2);
if (Rc.length() > 4 || Rc != Application::ClientVersionString()) {
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;
}
auto RequestString = R"({"key":")" + Rc + "\"}";
auto Target = "/pkToUser";
unsigned int ResponseCode = 0;
if (!Rc.empty()) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, RequestString, "application/json", &ResponseCode);
}
rapidjson::Document AuthResponse;
AuthResponse.Parse(Rc.c_str());
if (Rc == Http::ErrorString || AuthResponse.HasParseError()) {
ClientKick(*Client, "Invalid key! Please restart your game.");
return;
}
if (!AuthResponse.IsObject()) {
if (Rc == "0") {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Info, "default", "backend returned 0 instead of json (" + std::to_string(ResponseCode) + ")");
} else { // Rc != "0"
ClientKick(*Client, "Backend returned invalid auth response format.");
beammp_error("Backend returned invalid auth response format. This should never happen.");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth",
{ { "response-body", Rc },
{ "key", RequestString } });
Sentry.SetTransaction(Application::GetBackendUrlForAuth() + Target);
Sentry.Log(SentryLevel::Error, "default", "unexpected backend response (" + std::to_string(ResponseCode) + ")");
}
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()) {
auto Raw = std::string(ID.GetString());
auto SepIndex = Raw.find(':');
Client->SetIdentifier(Raw.substr(0, SepIndex), Raw.substr(SepIndex + 1));
}
} else {
ClientKick(*Client, "Invalid authentication data!");
return;
}
beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
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;
}
if (Cl->GetName() == Client->GetName() && Cl->IsGuest() == Client->IsGuest()) {
CloseSocketProper(Cl->GetTCPSock());
Cl->SetStatus(-2);
return false;
}
return true;
});
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest());
TLuaEngine::WaitForAll(Futures);
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) {
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
});
std::string Reason;
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
if (!Result->Error && Result->Result.is<std::string>()) {
Reason = Result->Result.as<std::string>();
return true;
}
return false;
});
if (NotAllowed) {
ClientKick(*Client, "you are not allowed on the server!");
return;
} else if (NotAllowedWithReason) {
ClientKick(*Client, Reason);
return;
}
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
beammp_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 {
#if defined(BEAMMP_WINDOWS)
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, 0);
#elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, MSG_NOSIGNAL);
#endif
if (Temp == 0) {
beammp_debug("send() == 0: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1)
c.SetStatus(-1);
return false;
} else if (Temp < 0) {
beammp_debug("send() < 0: " + GetPlatformAgnosticErrorString()); // 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) {
beammp_trace("(TCP) Connection closing...");
if (c.GetStatus() > -1)
c.SetStatus(-1);
return false;
} else if (BytesRcv < 0) {
beammp_debug("(TCP) recv() failed: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1)
c.SetStatus(-1);
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)) {
return "";
}
BytesRcv += Temp;
} while (size_t(BytesRcv) < sizeof(Header));
memcpy(&Header, &Data[0], sizeof(Header));
if (!CheckBytes(c, BytesRcv)) {
return "";
}
if (Header < 100 * MB) {
Data.resize(Header);
} else {
ClientKick(c, "Header size limit exceeded");
beammp_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)) {
return "";
}
BytesRcv += Temp;
} while (BytesRcv < Header);
std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") {
Ret = DeComp(Ret.substr(4));
}
return Ret;
}
void TNetwork::ClientKick(TClient& c, const std::string& R) {
beammp_info("Client kicked: " + R);
if (!TCPSend(c, "K" + R)) {
beammp_warn("tried to kick player '" + c.GetName() + "' (id " + std::to_string(c.GetID()) + "), but was already disconnected");
}
c.SetStatus(-2);
if (c.GetTCPSock())
CloseSocketProper(c.GetTCPSock());
if (c.GetDownSock())
CloseSocketProper(c.GetDownSock());
}
void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
RegisterThreadAuto();
while (!c.expired()) {
auto Client = c.lock();
if (Client->GetStatus() < 0) {
beammp_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
// beammp_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) {
beammp_debug("client status < 0, breaking client loop");
break;
}
auto res = TCPRcv(*Client);
if (res == "") {
beammp_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 {
beammp_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) {
beammp_assert(!ClientPtr.expired());
auto LockedClientPtr = ClientPtr.lock();
TClient& c = *LockedClientPtr;
beammp_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();
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID());
LuaAPI::MP::Engine->ReportErrors(Futures);
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) {
beammp_assert(!c.expired());
beammp_info("Client connected");
auto LockedClient = c.lock();
LockedClient->SetID(OpenID());
beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", "", LockedClient->GetID()));
SyncResources(*LockedClient);
if (LockedClient->GetStatus() < 0)
return;
(void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); // Send the Map on connect
beammp_info(LockedClient->GetName() + " : Connected");
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
}
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) {
beammp_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') {
beammp_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& UnsafeName) {
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
beammp_warn("File " + UnsafeName + " is not a file!");
return;
}
auto FileName = fs::path(UnsafeName).filename().string();
FileName = Application::Settings.Resource + "/Client/" + FileName;
if (!std::filesystem::exists(FileName)) {
if (!TCPSend(c, "CO")) {
// TODO: handle
}
beammp_warn("File " + UnsafeName + " could not be accessed!");
return;
}
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) {
beammp_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(FileName)), MSize = Size / 2;
std::thread SplitThreads[2] {
std::thread([&] {
RegisterThread("SplitLoad_0");
SplitLoad(c, 0, MSize, false, FileName);
}),
std::thread([&] {
RegisterThread("SplitLoad_1");
SplitLoad(c, MSize, Size, true, FileName);
})
};
for (auto& SplitThread : SplitThreads) {
if (SplitThread.joinable()) {
SplitThread.join();
}
}
}
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 = 125 * MB;
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();
beammp_debug("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 {
#if defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), MSG_NOSIGNAL);
#else
intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), 0);
#endif
if (Temp < 1) {
beammp_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);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoin", "", LockedClient->GetID()));
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);
beammp_info(LockedClient->GetName() + (" is now synced!"));
return true;
}
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
if (!Self)
beammp_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));
if (sendOk == -1) {
beammp_debug("(UDP) sendto() failed: " + GetPlatformAgnosticErrorString());
if (Client.GetStatus() > -1)
Client.SetStatus(-1);
return false;
} else if (sendOk == 0) {
beammp_debug(("(UDP) sendto() returned 0"));
if (Client.GetStatus() > -1)
Client.SetStatus(-1);
return false;
}
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) {
beammp_error("(UDP) Error receiving from client! recvfrom() failed: " + GetPlatformAgnosticErrorString());
return "";
}
return std::string(Ret.begin(), Ret.begin() + Rcv);
}
-70
View File
@@ -1,70 +0,0 @@
#include "TPPSMonitor.h"
#include "Client.h"
#include "TNetwork.h"
TPPSMonitor::TPPSMonitor(TServer& Server)
: mServer(Server) {
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Starting);
Application::SetPPS("-");
Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown);
if (mThread.joinable()) {
beammp_debug("shutting down PPSMonitor");
mShutdown = true;
mThread.join();
beammp_debug("shut down PPSMonitor");
}
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Shutdown);
});
Start();
}
void TPPSMonitor::operator()() {
RegisterThread("PPSMonitor");
while (!mNetwork) {
// hard(-ish) spin
std::this_thread::yield();
}
beammp_debug("PPSMonitor starting");
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good);
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() > (20 * 60)) {
beammp_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 way too long)");
}
TimedOutClients.clear();
if (C == 0 || mInternalPPS == 0) {
Application::SetPPS("-");
} else {
int R = (mInternalPPS / C) / V;
Application::SetPPS(std::to_string(R));
}
mInternalPPS = 0;
}
}
-35
View File
@@ -1,35 +0,0 @@
#include "TResourceManager.h"
#include <algorithm>
#include <filesystem>
namespace fs = std::filesystem;
TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
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 += "/" + fs::path(File).filename().string() + ';';
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
mMaxModSize += size_t(fs::file_size(entry.path()));
mModsLoaded++;
}
}
}
if (mModsLoaded)
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
}
-27
View File
@@ -1,27 +0,0 @@
#include "TScopedTimer.h"
#include "Common.h"
TScopedTimer::TScopedTimer()
: mStartTime(std::chrono::high_resolution_clock::now()) {
}
TScopedTimer::TScopedTimer(const std::string& mName)
: mStartTime(std::chrono::high_resolution_clock::now())
, Name(mName) {
}
TScopedTimer::TScopedTimer(std::function<void(size_t)> OnDestroy)
: OnDestroy(OnDestroy)
, mStartTime(std::chrono::high_resolution_clock::now()) {
}
TScopedTimer::~TScopedTimer() {
auto EndTime = std::chrono::high_resolution_clock::now();
auto Delta = EndTime - mStartTime;
size_t TimeDelta = Delta / std::chrono::milliseconds(1);
if (OnDestroy) {
OnDestroy(TimeDelta);
} else {
beammp_info("Scoped timer: \"" + Name + "\" took " + std::to_string(TimeDelta) + "ms ");
}
}
-138
View File
@@ -1,138 +0,0 @@
#include "TSentry.h"
#include "Common.h"
#include <cstring>
#include <sentry.h>
#include <sstream>
TSentry::TSentry() {
if (std::strlen(S_DSN) == 0) {
mValid = false;
} else {
mValid = true;
sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, S_DSN);
auto ReleaseString = "BeamMP-Server@" + Application::ServerVersionString();
sentry_options_set_symbolize_stacktraces(options, true);
sentry_options_set_release(options, ReleaseString.c_str());
sentry_options_set_max_breadcrumbs(options, 10);
sentry_init(options);
}
}
TSentry::~TSentry() {
if (mValid) {
sentry_close();
}
}
void TSentry::PrintWelcome() {
if (mValid) {
if (!Application::Settings.SendErrors) {
mValid = false;
if (Application::Settings.SendErrorsMessageEnabled) {
beammp_info("Opted out of error reporting (SendErrors), Sentry disabled.");
} else {
beammp_info("Sentry disabled");
}
} else {
if (Application::Settings.SendErrorsMessageEnabled) {
beammp_info("Sentry started! Reporting errors automatically. This sends data to the developers in case of errors and crashes. You can learn more, turn this message off or opt-out of this in the ServerConfig.toml.");
} else {
beammp_info("Sentry started");
}
}
} else {
if (Application::Settings.SendErrorsMessageEnabled) {
beammp_info("Sentry disabled in unofficial build. Automatic error reporting disabled.");
} else {
beammp_info("Sentry disabled in unofficial build");
}
}
}
void TSentry::SetupUser() {
if (!mValid) {
return;
}
Application::SetSubsystemStatus("Sentry", Application::Status::Good);
sentry_value_t user = sentry_value_new_object();
if (Application::Settings.Key.size() == 36) {
sentry_value_set_by_key(user, "id", sentry_value_new_string(Application::Settings.Key.c_str()));
} else {
sentry_value_set_by_key(user, "id", sentry_value_new_string("unauthenticated"));
}
sentry_set_user(user);
}
void TSentry::Log(SentryLevel level, const std::string& logger, const std::string& text) {
if (!mValid) {
return;
}
SetContext("threads", { { "thread-name", ThreadName(true) } });
auto Msg = sentry_value_new_message_event(sentry_level_t(level), logger.c_str(), text.c_str());
sentry_capture_event(Msg);
sentry_set_transaction(nullptr);
}
void TSentry::LogError(const std::string& text, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line);
Log(SentryLevel::Error, "default", file + ": " + text);
}
void TSentry::SetContext(const std::string& context_name, const std::unordered_map<std::string, std::string>& map) {
if (!mValid) {
return;
}
auto ctx = sentry_value_new_object();
for (const auto& pair : map) {
std::string key = pair.first;
if (key == "type") {
// `type` is reserved
key = "_type";
}
sentry_value_set_by_key(ctx, key.c_str(), sentry_value_new_string(pair.second.c_str()));
}
sentry_set_context(context_name.c_str(), ctx);
}
void TSentry::LogException(const std::exception& e, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line);
Log(SentryLevel::Fatal, "exceptions", std::string(e.what()) + " @ " + file + ":" + line);
}
void TSentry::LogAssert(const std::string& condition_string, const std::string& file, const std::string& line, const std::string& function) {
if (!mValid) {
return;
}
SetTransaction(file + ":" + line + ":" + function);
std::stringstream ss;
ss << "\"" << condition_string << "\" failed @ " << file << ":" << line;
Log(SentryLevel::Fatal, "asserts", ss.str());
}
void TSentry::AddErrorBreadcrumb(const std::string& msg, const std::string& file, const std::string& line) {
if (!mValid) {
return;
}
auto crumb = sentry_value_new_breadcrumb("default", (msg + " @ " + file + ":" + line).c_str());
sentry_value_set_by_key(crumb, "level", sentry_value_new_string("error"));
sentry_add_breadcrumb(crumb);
}
void TSentry::SetTransaction(const std::string& id) {
if (!mValid) {
return;
}
sentry_set_transaction(id.c_str());
}
std::unique_lock<std::mutex> TSentry::CreateExclusiveContext() {
return std::unique_lock<std::mutex>(mMutex);
}
-367
View File
@@ -1,367 +0,0 @@
#include "TServer.h"
#include "Client.h"
#include "Common.h"
#include "TNetwork.h"
#include "TPPSMonitor.h"
#include <TLuaPlugin.h>
#include <any>
#include <sstream>
#include <nlohmann/json.hpp>
#include "LuaAPI.h"
#undef GetObject // Fixes Windows
#include "Json.h"
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting);
if (Arguments.size() > 1) {
Application::Settings.CustomIP = Arguments[0];
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
Application::Settings.CustomIP.clear();
beammp_warn("IP Specified is invalid! Ignoring");
} else {
beammp_info("server started with custom IP");
}
}
Application::SetSubsystemStatus("Server", Application::Status::Good);
}
void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
if (!WeakClientPtr.expired()) {
TClient& Client = *WeakClientPtr.lock();
beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
Client.ClearCars();
WriteLock Lock(mClientsMutex);
mClients.erase(WeakClientPtr.lock());
}
}
std::weak_ptr<TClient> TServer::InsertNewClient() {
beammp_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
beammp_trace(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
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) {
beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.length()));
}
ParseVehicle(*LockedClient, Packet, Network);
return;
case 'J':
beammp_trace(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
case 'C': {
beammp_trace(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
break;
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 2));
TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1));
if (std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) {
return !Elem->Error
&& Elem->Result.is<int>()
&& bool(Elem->Result.as<int>());
})) {
break;
}
Network.SendToAll(nullptr, Packet, true, true);
return;
}
case 'E':
beammp_trace(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
HandleEvent(*LockedClient, Packet);
return;
case 'N':
beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
Network.SendToAll(LockedClient.get(), Packet, false, true);
return;
default:
return;
}
}
void TServer::HandleEvent(TClient& c, const std::string& RawData) {
// E:Name:Data
// Data is allowed to have ':'
auto NameDataSep = RawData.find(':', 2);
if (NameDataSep == std::string::npos) {
beammp_warn("received event in invalid format (missing ':'), got: '" + RawData + "'");
}
std::string Name = RawData.substr(2, NameDataSep - 2);
std::string Data = RawData.substr(NameDataSep + 1);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), Data));
}
bool TServer::IsUnicycle(TClient& c, const std::string& CarJson) {
try {
auto Car = nlohmann::json::parse(CarJson);
const std::string jbm = "jbm";
if (Car.contains(jbm) && Car[jbm].is_string() && Car[jbm] == "unicycle") {
return true;
}
} catch (const std::exception& e) {
beammp_error("Failed to parse vehicle data as json for client " + std::to_string(c.GetID()) + ": '" + CarJson + "'");
}
return false;
}
bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
if (IsUnicycle(c, CarJson) && c.GetUnicycleID() < 0) {
c.SetUnicycleID(ID);
return true;
} else {
return c.GetCarCount() < Application::Settings.MaxCars;
}
}
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':
beammp_trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Data.at(0) == '0') {
int CarID = c.GetOpenCarID();
beammp_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 Futures = LuaAPI::MP::Engine->TriggerEvent("onVehicleSpawn", "", c.GetID(), CarID, Packet.substr(3));
TLuaEngine::WaitForAll(Futures);
bool ShouldntSpawn = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) {
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
});
if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
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
}
beammp_debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
}
}
return;
case 'c':
beammp_trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
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 Futures = LuaAPI::MP::Engine->TriggerEvent("onVehicleEdited", "", c.GetID(), VID, Packet.substr(3));
TLuaEngine::WaitForAll(Futures);
bool ShouldntAllow = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) {
return !Result->Error && Result->Result.is<int>() && Result->Result.as<int>() != 0;
});
auto FoundPos = Packet.find('{');
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
&& !ShouldntAllow) {
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':
beammp_trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
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);
// TODO: should this trigger on all vehicle deletions?
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), VID));
c.DeleteCar(VID);
beammp_debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID));
}
return;
case 'r':
beammp_trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
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('{'));
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
Network.SendToAll(&c, Packet, false, true);
}
return;
case 't':
beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(&c, Packet, false, true);
return;
case 'm':
Network.SendToAll(&c, Packet, true, true);
return;
default:
beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
return;
}
}
void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
auto FoundPos = pckt.find('{');
if (FoundPos == std::string::npos) {
beammp_error("Malformed packet received, no '{' found");
return;
}
std::string Packet = pckt.substr(FoundPos);
std::string VD = c.GetCarData(VID);
if (VD.empty()) {
beammp_error("Tried to apply change to vehicle that does not exist");
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change",
{ { "packet", Packet },
{ "vehicle-id", std::to_string(VID) },
{ "client-car-count", std::to_string(c.GetCarCount()) } });
Sentry.LogError("attempt to apply change to nonexistent vehicle", _file_basename, _line);
return;
}
std::string Header = VD.substr(0, VD.find('{'));
FoundPos = VD.find('{');
if (FoundPos == std::string::npos) {
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change-packet",
{ { "packet", VD } });
Sentry.LogError("malformed packet", _file_basename, _line);
return;
}
VD = VD.substr(FoundPos);
rapidjson::Document Veh, Pack;
Veh.Parse(VD.c_str());
if (Veh.HasParseError()) {
beammp_error("Could not get vehicle config!");
return;
}
Pack.Parse(Packet.c_str());
if (Pack.HasParseError() || Pack.IsNull()) {
beammp_error("Could not get active vehicle config!");
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) {
beammp_debug("inserting client (" + std::to_string(ClientCount()) + ")");
WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here
(void)mClients.insert(NewClient);
}
-30
View File
@@ -1,30 +0,0 @@
#include "VehicleData.h"
#include "Common.h"
#include <regex>
#include <utility>
TVehicleData::TVehicleData(int ID, const std::string& Data)
: mID(ID)
, mData(Data) {
try {
std::regex Reg(R"(^[a-zA-Z0-9_\-.]+:[a-zA-Z0-9_\-.]+:[a-zA-Z0-9_\-.]+:\d+\-\d+:(\{.+\}))", std::regex::ECMAScript);
std::smatch Match;
std::string Result;
if (std::regex_search(Data, Match, Reg) && Match.size() > 1) {
Result = Match.str(1);
mJson = json::parse(Result);
}
} catch (const std::exception& e) {
beammp_debugf("Failed to parse vehicle data for vehicle {} as json, this isn't expected: {}", ID, e.what());
}
beammp_trace("vehicle " + std::to_string(mID) + " constructed");
}
TVehicleData::~TVehicleData() {
beammp_trace("vehicle " + std::to_string(mID) + " destroyed");
}
const json& TVehicleData::Json() const {
return mJson;
}
-214
View File
@@ -1,214 +0,0 @@
#include "TSentry.h"
#include "ArgsParser.h"
#include "Common.h"
#include "CustomAssert.h"
#include "Http.h"
#include "LuaAPI.h"
#include "SignalHandling.h"
#include "TConfig.h"
#include "THeartbeatThread.h"
#include "TLuaEngine.h"
#include "TNetwork.h"
#include "TPPSMonitor.h"
#include "TResourceManager.h"
#include "TScopedTimer.h"
#include "TServer.h"
#include <iostream>
#include <thread>
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
static const std::string sCommandlineArguments = R"(
USAGE:
BeamMP-Server [arguments]
ARGUMENTS:
--help
Displays this help and exits.
--config=/path/to/ServerConfig.toml
Absolute or relative path to the
Server Config file, including the
filename. For paths and filenames with
spaces, put quotes around the path.
--working-directory=/path/to/folder
Sets the working directory of the Server.
All paths are considered relative to this,
including the path given in --config.
--version
Prints version info and exits.
EXAMPLES:
BeamMP-Server --config=../MyWestCoastServerConfig.toml
Runs the BeamMP-Server and uses the server config file
which is one directory above it and is named
'MyWestCoastServerConfig.toml'.
)";
// this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
struct MainArguments {
int argc {};
char** argv {};
std::vector<std::string_view> List;
std::string InvokedAs;
};
int BeamMPServerMain(MainArguments Arguments);
int main(int argc, char** argv) {
MainArguments Args { argc, argv, {}, argv[0] };
Args.List.reserve(argc);
for (int i = 1; i < argc; ++i) {
Args.List.push_back(argv[i]);
}
int MainRet = 0;
try {
MainRet = BeamMPServerMain(std::move(Args));
} catch (const std::exception& e) {
beammp_error("A fatal exception has occurred and the server is forcefully shutting down.");
beammp_error(e.what());
Sentry.LogException(e, _file_basename, _line);
MainRet = -1;
}
return MainRet;
}
int BeamMPServerMain(MainArguments Arguments) {
setlocale(LC_ALL, "C");
Application::InitializeConsole();
ArgsParser Parser;
Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
Parser.RegisterArgument({ "config" }, ArgsParser::HAS_VALUE);
Parser.RegisterArgument({ "working-directory" }, ArgsParser::HAS_VALUE);
Parser.Parse(Arguments.List);
if (!Parser.Verify()) {
return 1;
}
if (Parser.FoundArgument({ "help" })) {
Application::Console().Internal().set_prompt("");
Application::Console().WriteRaw(sCommandlineArguments);
return 0;
}
if (Parser.FoundArgument({ "version" })) {
Application::Console().Internal().set_prompt("");
Application::Console().WriteRaw("BeamMP-Server v" + Application::ServerVersionString());
return 0;
}
std::string ConfigPath = "ServerConfig.toml";
if (Parser.FoundArgument({ "config" })) {
auto MaybeConfigPath = Parser.GetValueOfArgument({ "config" });
if (MaybeConfigPath.has_value()) {
ConfigPath = MaybeConfigPath.value();
beammp_info("Custom config requested via commandline arguments: '" + ConfigPath + "'");
}
}
if (Parser.FoundArgument({ "working-directory" })) {
auto MaybeWorkingDirectory = Parser.GetValueOfArgument({ "working-directory" });
if (MaybeWorkingDirectory.has_value()) {
beammp_info("Custom working directory requested via commandline arguments: '" + MaybeWorkingDirectory.value() + "'");
try {
fs::current_path(fs::path(MaybeWorkingDirectory.value()));
} catch (const std::exception& e) {
beammp_errorf("Could not set working directory to '{}': {}", MaybeWorkingDirectory.value(), e.what());
}
}
}
Application::SetSubsystemStatus("Main", Application::Status::Starting);
Application::Console().StartLoggingToFile();
SetupSignalHandlers();
bool Shutdown = false;
Application::RegisterShutdownHandler([&Shutdown] {
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown);
Shutdown = true;
});
Application::RegisterShutdownHandler([] {
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
});
TServer Server(Arguments.List);
TConfig Config(ConfigPath);
TLuaEngine LuaEngine;
LuaEngine.SetServer(&Server);
Application::Console().InitializeLuaConsole(LuaEngine);
if (Config.Failed()) {
beammp_info("Closing in 10 seconds");
// loop to make it possible to ctrl+c instead
for (size_t i = 0; i < 20; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 1;
}
RegisterThread("Main");
beammp_trace("Running in debug mode on a debug build");
Sentry.SetupUser();
Sentry.PrintWelcome();
TResourceManager ResourceManager;
TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager);
LuaEngine.SetNetwork(&Network);
PPSMonitor.SetNetwork(Network);
Application::CheckForUpdates();
std::unique_ptr<Http::Server::THttpServerInstance> HttpServer;
if (Application::Settings.HTTPServerEnabled) {
HttpServer = std::make_unique<Http::Server::THttpServerInstance>(
Server,
Network,
ResourceManager);
}
Application::SetSubsystemStatus("Main", Application::Status::Good);
RegisterThread("Main(Waiting)");
std::set<std::string> IgnoreSubsystems {
"UpdateCheck" // Ignore as not to confuse users (non-vital system)
};
bool FullyStarted = false;
while (!Shutdown) {
if (!FullyStarted) {
FullyStarted = true;
bool WithErrors = false;
std::string SystemsBadList {};
auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) {
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
continue; // ignore
}
if (NameStatusPair.second == Application::Status::Starting) {
FullyStarted = false;
} else if (NameStatusPair.second == Application::Status::Bad) {
SystemsBadList += NameStatusPair.first + ", ";
WithErrors = true;
}
}
// remove ", "
SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2);
if (FullyStarted) {
if (!WithErrors) {
beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY");
} else {
beammp_error("STARTUP NOT SUCCESSFUL, SYSTEMS " + SystemsBadList + " HAD ERRORS. THIS MAY OR MAY NOT CAUSE ISSUES.");
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
Application::SetSubsystemStatus("Main", Application::Status::Shutdown);
beammp_info("Shutdown.");
return 0;
}
-18
View File
@@ -1,18 +0,0 @@
include(FetchContent)
message(STATUS "Getting, checking and running vcpkg, this may take a while")
FetchContent_Declare(
vcpkg
GIT_REPOSITORY https://github.com/microsoft/vcpkg.git
)
FetchContent_GetProperties(vcpkg)
if(NOT vcpkg_POPULATED)
FetchContent_Populate(vcpkg)
execute_process(COMMAND ./${vcpkg_SOURCE_DIR}/bootstrap-vcpkg.sh)
endif()
set(CMAKE_TOOLCHAIN_FILE ${vcpkg_SOURCE_DIR}/scripts/buildsystems/vcpkg.cmake
CACHE STRING "Vcpkg toolchain file")
-17
View File
@@ -1,17 +0,0 @@
{
"name": "beammp-server",
"version-string": "3.0.2",
"homepage": "https://beammp.com",
"dependencies": [
"sentry-native",
"lua",
"zlib",
"rapidjson",
"nlohmann-json",
"openssl",
"curl",
"sol2",
"cpp-httplib",
"toml11"
]
}