Compare commits

...

380 Commits

Author SHA1 Message Date
Lion Kortlepel 5c8826a337 rename HttpLibServerInstance to Server 2022-05-24 21:13:55 +02:00
Lion Kortlepel 9fccc27741 http: add /player/{name}/vehicles 2022-05-24 21:05:10 +02:00
Lion Kortlepel b0021d42b5 add /player/{name}/ draft 2022-05-23 23:17:17 +02:00
Lion Kortlepel 5598d75fd5 fix lifetime of http server, add /version 2022-05-23 23:16:53 +02:00
Lion Kortlepel 48bb1373e1 fix toml11 include path 2022-05-23 19:03:51 +02:00
Lion Kortlepel 8fce2fc67f http: add /players 2022-05-23 19:01:03 +02:00
Lion Kortlepel f7acd1e819 http: add /config 2022-05-23 18:44:42 +02:00
Lion Kortlepel b46dc664c6 http: add /ready 2022-05-23 18:30:47 +02:00
Lion Kortlepel 18b4c698fc add openapi specification for http server 2022-05-23 18:02:07 +02:00
Lion Kortlepel 62c300f285 http: add error, exception handlers 2022-05-23 17:41:44 +02:00
Lion Kortlepel d8e974429d ci: use parallel build, only build server 2022-05-23 17:30:23 +02:00
Lion Kortlepel 634955660d Revert "Remove unneeded submodules"
This reverts commit a5153e4bc1.
2022-05-23 17:27:01 +02:00
Lion Kortlepel 1e9e068aec Merge branch 'rc-v3.1.0' into feature-9 2022-05-23 17:26:32 +02:00
Lion Kortlepel 87ecfdbc72 use v1 endpoint 2022-05-23 17:21:44 +02:00
Lion Kortlepel 2f1881bbe7 Merge branch 'rc-v3.0.2' into feature-9 2022-05-23 17:06:23 +02:00
Lion Kortlepel 1e60ac5ba2 remove ssl options from config 2022-05-23 17:05:43 +02:00
Lion Kortlepel cd2752a525 remove ssl code
@jimkoen

This was removed because, as useful and as much work as this was, we
can't reasonably take responsibility for this. Instead, a server like
this should *always* be localhost only, and if it's not, it should be
behind an nginx reverse proxy anyways. We're removing the config options
regarding this in one of the next commits.
2022-05-23 16:59:31 +02:00
Lion Kortlepel 66b10f19c2 update lionkor/commandline 2022-05-23 16:59:18 +02:00
Lion Kortlepel a5153e4bc1 Remove unneeded submodules 2022-04-28 16:28:23 +02:00
Lion Kortlepel 8d7505956d let's try vcpkg 2022-04-28 16:26:30 +02:00
Lion Kortlepel 3b2016d09f Windows moment
Windows deprecated when
2022-04-28 14:59:41 +02:00
Lion Kortlepel ed03096cf5 Windows moment
Windows deprecated when
2022-04-28 14:58:07 +02:00
Lion Kortlepel 88f1976668 Merge branch 'rc-v3.0.2' into rc-v3.1.0 2022-04-28 14:40:57 +02:00
Lion Kortlepel ca52d233c0 Use another git commit id for vcpkg
this should really be done properly, yikes
2022-04-28 14:34:05 +02:00
Lion Kortlepel 34b39aad4d add message to shutdown suggesting Ctrl+C if it takes too long
This is an ongoing issue that needs to be resolved properly,
but I'm not sure what's hanging it.
2022-04-28 14:30:44 +02:00
Lion Kortlepel f4eb492d91 Actions: try to use master as vcpkgGitCommitId 2022-04-28 14:24:02 +02:00
Lion Kortlepel 7f41a2a574 Update Changelog to reflect latest changes 2022-04-28 14:18:10 +02:00
Lion Kortlepel 11c53e0b3a Remove unused error, as Sentry's code fails to build otherwise
Thanks, sentry.
2022-04-28 14:13:51 +02:00
Lion Kortlepel 974dda9f8b HTTPServer: Add config value to specify listen IP
Change default IP to localhost,
Set default SSL to false due to this.
2022-04-28 14:12:26 +02:00
Lion Kortlepel 0979c8b1e4 HTTPServer: Attempt to catch more errors 2022-04-28 14:04:54 +02:00
Lion Kortlepel 0761036c8c TConsole::StartLoggingToFile: implement 2022-04-28 13:46:25 +02:00
Lion Kortlepel 5ded713b4b Application::CheckForUpdates: only print status the first time 2022-04-28 13:33:38 +02:00
Lion Kortlepel 668cc496b6 update commandline, unused are now errors 2022-04-28 13:29:16 +02:00
Lion Kortlepel 056d20292a Make "unable to fetch version" a trace message 2022-04-28 13:14:28 +02:00
Lion Kortlepel d8c33c03ee start work on new logger 2022-04-05 22:27:45 +02:00
Lion Kortlepel 1bab3276e9 fix setsockopt SO_SNDTIMEO for windows, bump version number to 3.0.2, update commandline 2022-04-05 10:59:16 +02:00
Lion Kortlepel 4ff69528bd fix some missing declaration 2022-03-31 23:56:02 +02:00
Lion Kortlepel 5e4c7eac51 add send timeout to client tcp socket 2022-03-31 23:53:10 +02:00
Lion Kortlepel 952631bb80 add send timeout to client tcp socket 2022-03-31 23:48:07 +02:00
Lion Kortlepel 23af76dba1 Only warn once about event handlers taking >60s 2022-03-31 23:12:50 +02:00
Lion Kortlepel 5755ead9be Change :detach to :exit
@20dka
2022-03-31 22:18:55 +02:00
Lion Kortlepel 450f0a6875 Fixup merge 2022-03-31 22:17:10 +02:00
Lion Kortlepel 104737571c Merge branch 'rc-v3.0.2' into rc-v3.1.0
This is a periodic merge to keep 3.1.0 up to date with 3.0.2
2022-03-31 22:10:49 +02:00
Lion Kortlepel d01d79a49a update changelog 2022-03-31 20:27:08 +02:00
Lion Kortlepel d4b30a2583 CreateEventTimer: Implement CallStrategy
There are two CallStrategies:

- BestEffort (default): Will try to get your event to trigger at the specified
  interval, but will refuse to queue handlers if a handler takes too
  long.
- Precise: Will enqueue event handlers at the exact interval specified.
  Can lead to the queue filling up if the handler takes longer than the
  interval.
2022-03-31 20:13:59 +02:00
Lion Kortlepel 81dbf747d5 Kick client if we fail to send them a client event 2022-03-31 16:50:00 +02:00
Lion Kortlepel b97397132d TLuaEngine: improve result queue handling 2022-03-31 15:59:31 +02:00
Lion Kortlepel d86efabb1a Modernize CMakeLists, automatically update submodules
CMake will now find packages in a modern way (include(Find*)), and will
also ensure that submodules are updated, unless told otherwise.

Also removed some apple-specific workarounds, we will need to look at
that again.
2022-03-30 12:14:13 +02:00
Lion Kortlepel de82caef33 Add HideUpdateMessages setting ("ImScaredOfUpdates") and periodic update reminders (every 5th heartbeat) 2022-03-25 13:34:28 +01:00
Lion Kortlepel f8c58f363a Change default MaxPlayers to 8 2022-03-25 13:32:41 +01:00
Lion Kortlepel 71c2d4b859 Simplify "Backend heartbeat response" error (closes #97) 2022-03-25 12:55:35 +01:00
Lion Kortlepel b2f27c21be update changelog 2022-03-24 16:53:39 +01:00
Lion Kortlepel b780a08f73 use MB constant 2022-03-24 15:16:24 +01:00
Lion Kortlepel cd4332b790 prepend . to Threads.log to make it invisible on *nix 2022-03-24 14:47:15 +01:00
Lion Kortlepel 7a814ed35e use fmt properly in beammp_*f logging functions 2022-03-24 14:45:53 +01:00
Lion Kortlepel 9e0d02c6db add fmt library, add beammp_*f 2022-03-24 14:36:39 +01:00
Lion Kortlepel d0bb32ec63 cleanup fixme's, todo's 2022-03-24 14:26:02 +01:00
Lion Kortlepel 5180c96e2b TServer::HandleEvent: Fix mistreatment of ':' in event data 2022-03-24 14:06:50 +01:00
Lion Kortlepel dbfe4a4d11 Fix inconsistencies with handling errors in early network startup
In most cases, when socket creation, bind, listen, or similar fails,
it's best to gracefully shutdown. We do that now.
2022-03-24 14:06:03 +01:00
Lion Kortlepel 4cb299061e add pos argument to on_autocomplete 2022-03-23 16:10:51 +01:00
Lion Kortlepel ef902a03f3 update lionkor/commandline to v2.0.0 2022-03-23 12:06:35 +01:00
Lion Kortlepel c1e216957b move Json* to Util, add Random, RandomRange, RandomIntRange, catch
errors in TPluginMonitor
2022-03-18 01:52:31 +01:00
Lion Kortlepel 39db1a5e42 Fix JsonEncode with mixed key/value index/value tables 2022-03-17 19:27:17 +01:00
Lion Kortlepel be498be661 change TriggerClientEvent to take object, not string, and add TriggerClientEventJson 2022-03-17 18:48:50 +01:00
Lion 0466ae55a4 Merge pull request #96 from 20dka/feature-supressBackend
Ignore backend response if server is Private
2022-03-17 09:57:16 +01:00
20dka 6a43694c0f Ignore backend response if server is Private 2022-03-17 01:30:24 +01:00
Lion Kortlepel fe06726d75 update commandline 2022-03-16 16:03:23 +01:00
20dka 3c08e54471 Add basic autocomplete (fix #95) 2022-03-15 01:58:26 +01:00
Lion a97763a94f Merge pull request #91 from BeamMP/rc-v3.0.1
Release Candidate v3.0.1
2022-03-10 17:03:43 +01:00
Lion Kortlepel daa674f448 add infinite snowmen bug to changelog 2022-03-10 12:31:02 +01:00
Lion Kortlepel 710b15535e Merge branch 'rc-v3.0.1' into rc-v3.1.0 2022-03-10 12:24:44 +01:00
Lion Kortlepel a85fef15c2 only use sentry if URL is specified, possibly fix stupid microsoft compiler error
hey @microsoft, maybe don't have a limit on the size of obj files.
2022-03-10 12:23:24 +01:00
Lion Kortlepel 359faee696 Fix Unicycle "infinite spawning" bug 2022-03-10 12:22:11 +01:00
Lion Kortlepel 299004b14e fix msvc /bigobj issue 2022-03-10 12:21:08 +01:00
Lion Kortlepel b09f5a401d update lionkor/commandline to 1.0.0 (adds cursor left- and right
movement)
2022-03-10 12:19:01 +01:00
Lion Kortlepel 2caa74d913 update lionkor/commandline to 1.0.0 (adds cursor left- and right
movement)
2022-03-10 12:17:46 +01:00
Lion Kortlepel e3d9d11bbd only use sentry if URL is specified, possibly fix stupid microsoft compiler error
hey @microsoft, maybe don't have a limit on the size of obj files.
2022-03-10 01:40:47 +01:00
Lion Kortlepel 09c9b24cbf Fix Unicycle "infinite spawning" bug 2022-03-10 01:27:16 +01:00
Lion Kortlepel a450531b8a Merge branch 'rc-v3.0.1' into rc-v3.1.0 2022-03-05 23:39:02 +01:00
Lion Kortlepel ace7aaada7 Fix issue which caused assignments and similar lua code to not work in the console 2022-03-03 12:38:23 +01:00
Lion Kortlepel ca3314b416 add JsonDiffApply, JsonMinify, JsonPrettify, JsonFlatten, JsonUnflatten 2022-03-03 12:25:06 +01:00
Lion Kortlepel 7d97c3b560 add FS.ListFiles, FS.ListDirectories 2022-03-02 23:41:32 +01:00
Lion Kortlepel dd5cf1a4af move some things in changelog from 3.0.1 to 3.1.0 2022-03-02 13:30:56 +01:00
Lion Kortlepel 9a0cdc6517 rename JsonSerialize to JsonEncode, JsonDeserialize to JsonDecode 2022-03-02 13:26:53 +01:00
Lion Kortlepel 965935a0e6 catch invalid json to JsonDeserialize, use lua_nil instead of nil 2022-03-02 13:25:29 +01:00
Lion Kortlepel 96a0e4b6fb update changelog 2022-03-02 13:23:08 +01:00
Lion Kortlepel b52677e585 Add JsonDeserialize 2022-03-02 13:22:26 +01:00
Lion Kortlepel 95b417bb36 Add MP.JsonSerialize 2022-03-02 11:43:48 +01:00
Lion Kortlepel 8ce3be03a3 make bigobj a linker flag? 2022-02-17 12:00:03 +01:00
Lion Kortlepel 687b4e4235 add bigobj flag 2022-02-17 11:47:28 +01:00
Lion Kortlepel 86ad28abdc Microsoft (R) Windows (TM) needs deprecation 2022-02-17 11:29:51 +01:00
Lion Kortlepel a982d54202 add debug build to windows github actions 2022-02-17 11:13:07 +01:00
Lion Kortlepel 588c68ebe1 Use proper argument parser 2022-02-17 11:08:48 +01:00
Lion Kortlepel 548b2512cc proper command parsing 2022-02-15 16:06:59 +01:00
Lion Kortlepel 8ff94a57d7 Add ParseCommand implementation 2022-02-15 15:34:33 +01:00
Lion Kortlepel a44684f6e7 Add backend provided message to all auth loggings 2022-02-15 15:20:25 +01:00
Lion Kortlepel 944b68c6d5 format backend refused message nicer 2022-02-15 15:19:41 +01:00
Lion Kortlepel 01268821dc Merge branch 'rc-v3.0.1' into rc-v3.1.0 2022-02-15 15:15:51 +01:00
Lion Kortlepel 5553aca0bb set heartbeat status properly 2022-02-12 22:08:49 +01:00
Lion Kortlepel beaea4f624 Fix crash when giving commandline arguments with no console initialized 2022-02-11 11:57:01 +01:00
Lion a4f07c9a4d Merge pull request #86 from BeamMP/rc-v3.0.1
Release Candidate v3.0.1
2022-02-11 11:07:39 +01:00
Lion Kortlepel dada8fe6bf update changelog 2022-02-11 11:01:39 +01:00
Lion Kortlepel 36853ca683 add MSG_NOSIGNAL to all calls to send() to get rid of useless SIGPIPE signals 2022-02-11 10:59:17 +01:00
Lion Kortlepel d969c4a2c2 Use nlohmann/json for IsUnicycle 2022-02-11 10:36:10 +01:00
Lion Kortlepel f26ca6b40d add nlohmann/json
this will conflict with a change in the http branch, but i dont care
right now
2022-02-11 10:19:10 +01:00
Lion Kortlepel 17c571811a Add config option to turn off chat logging
When LogChat is disabled, using the `say` command in the console will
trigger a "chat message sent!" reply, as UX feedback.
2022-02-06 21:46:51 +01:00
Lion b72de4bd0a Merge pull request #82 from BeamMP/rc-v3.0.1
Release Candidate v3.0.1
2022-02-04 04:37:09 +01:00
Lion Kortlepel fdb7c9ce71 update version to 3.1.0 2022-02-03 19:57:25 +01:00
Lion Kortlepel c391abcc93 Merge branch 'rc-v3.0.1' into rc-v3.1.0 2022-02-03 19:56:33 +01:00
Lion Kortlepel 144ccf14ec Add AddResultToCheck 2022-02-03 19:52:19 +01:00
Lion Kortlepel 40b23cbbe6 update changelog 2022-02-03 19:52:19 +01:00
Lion Kortlepel 7b458e3e27 Use yield() where possible
Replaced calls of this_thread::sleep_* with this_thread::yield(), which
yields the thread to the OS' scheduler.
2022-02-03 19:52:18 +01:00
Lion Kortlepel 69656f95db Move commandline initialization after cwd setting
This fixes an issue where the log file is written to the original
directory, even if --working-directory=path was used. This can obviously
be pretty bad.
2022-02-03 19:51:59 +01:00
Lion Kortlepel 0f5c476d09 update libraries 2022-02-03 19:51:59 +01:00
Lion Kortlepel 86b6aed350 UpdateCheck: Try all URLs 2022-02-03 19:51:59 +01:00
Lion Kortlepel 81780294f8 advance to 3.0.1 2022-02-03 19:51:59 +01:00
Lion Kortlepel 7c1fb12625 add api-v header to heartbeat post 2022-02-03 19:51:59 +01:00
Lion Kortlepel 9f892af997 start fixing backend heartbeat 2022-02-03 19:51:59 +01:00
Lion Kortlepel 9494bc70fb fix changelog 2022-02-03 19:46:31 +01:00
Lion Kortlepel 5c44a307bc update changelog 2022-02-03 19:03:21 +01:00
Lion Kortlepel 754053e73f Use yield() where possible
Replaced calls of this_thread::sleep_* with this_thread::yield(), which
yields the thread to the OS' scheduler.
2022-02-03 18:57:52 +01:00
Lion Kortlepel 29f8d29e33 Move commandline initialization after cwd setting
This fixes an issue where the log file is written to the original
directory, even if --working-directory=path was used. This can obviously
be pretty bad.
2022-02-03 18:31:18 +01:00
Lion Kortlepel e043361abb update libraries 2022-02-03 18:30:00 +01:00
Lion Kortlepel fd12ee672d Add various debug functions 2022-01-26 20:33:12 +01:00
Lion Kortlepel fca5bbcec9 UpdateCheck: Try all URLs 2022-01-21 14:26:42 +01:00
Lion Kortlepel 2a588954be advance to 3.0.1 2022-01-20 21:31:00 +01:00
Lion Kortlepel cd4129e05d add api-v header to heartbeat post 2022-01-20 16:09:08 +01:00
Lion Kortlepel c42c748b37 start fixing backend heartbeat 2022-01-20 15:46:13 +01:00
Lion 179b33a7ab Merge pull request #76 from BeamMP/patch-http-server-default
HTTP Server disabled by default
2021-12-23 14:28:33 +01:00
Lion Kortlepel 1b14206a3c HTTP Server disabled by default 2021-12-23 14:24:00 +01:00
Lion 0cf81cf3f4 Merge pull request #73 from BeamMP/lionkor-readme-patch
Patch README Dependency List, fix Changelog
2021-12-16 23:54:33 +01:00
Lion 801780bcb1 Update Changelog.md 2021-12-16 16:02:31 +01:00
Lion 84d52578e0 Merge pull request #75 from BeamMP/release-3-0-0-patch
Release v3.0.0 final patch
2021-12-15 18:26:08 +01:00
Lion Kortlepel 3555cec5fe Simplify fix for event argument bug 2021-12-13 13:48:18 +01:00
Lion Kortlepel 71efe30345 Bump to 3.0.0, possible fix for event argument bug 2021-12-13 13:22:58 +01:00
Lion 5df8aedf08 Update Changelog.md 2021-12-11 10:35:11 +01:00
Lion c3ab4b729a Update README.md 2021-12-09 14:15:50 +01:00
Lion 464aa3b59f Update README.md 2021-12-09 14:13:05 +01:00
Lion cfe63198c7 Merge pull request #50 from BeamMP/rewrite-lua
Rewrite Lua + New Lua Features + New Console + Commandline Args
2021-12-09 13:04:48 +01:00
Lion Kortlepel 435d73f0c1 Remove message thing from master, reimplement this later 2021-12-09 12:09:55 +01:00
Lion Kortlepel a5c23f8dde Add ping packet support 2021-12-09 12:08:41 +01:00
Lion 6c5b2cbeb5 Merge branch 'master' into rewrite-lua 2021-12-09 12:04:33 +01:00
Lion Kortlepel ca5b3956a1 Http: Dont generate ssl key/cert with ssl off, reinterpret_cast for
windows
2021-12-08 17:39:13 +01:00
Lion Kortlepel 924e18c163 Remove /status placeholder 2021-12-08 17:39:13 +01:00
Rouven Himmelstein bbe92dfa0f fix: broken .gitmodules file 2021-12-08 17:35:29 +01:00
Lion Kortlepel a19943c96d Example impl for /status 2021-12-07 15:34:01 +01:00
Lion Kortlepel cd19ae0836 Add shutting down / shutdown states to state keeper 2021-12-07 14:25:04 +01:00
Lion Kortlepel 3ee83904a1 Windows rewrite when
jfc since when is `c_str()` not convertible to `const char*`?
2021-12-06 15:13:55 +01:00
Lion Kortlepel fe652cbf42 Fix server paths 2021-12-06 15:08:01 +01:00
Lion Kortlepel 2c115a2b2c Fix GCC pragma to not appear on windows 2021-12-06 15:04:28 +01:00
Lion Kortlepel 1172420b77 Add https to changelog 2021-12-06 14:21:25 +01:00
Lion Kortlepel 92d9857902 Re-add commandline
Bruh @jimkoen
2021-12-06 14:16:40 +01:00
Lion Kortlepel 816d3d5df8 Re-add split history (?) 2021-12-06 14:12:54 +01:00
Lion Kortlepel 817a146699 Add state id to lua prompt 2021-12-06 13:57:40 +01:00
Lion Kortlepel 3cce875fbb Add UseSSL option to server config 2021-12-06 13:47:07 +01:00
Lion Kortlepel a1335e8c7d Add statuses, status messages 2021-12-06 13:17:54 +01:00
Lion Kortlepel 0f74eca2ee Fix various issues and crashes 2021-12-06 12:28:52 +01:00
Lion Kortlepel 279c93179c Fix segfault in http 2021-12-06 10:22:52 +01:00
Lion Kortlepel 62cc1e9ce4 Http: Add config value to turn it off, move all http settings into a category in the config 2021-12-06 09:53:13 +01:00
Lion Kortlepel bd41382233 Application::TSettings: Improve default initialisation 2021-12-06 09:00:57 +01:00
awesome_milou 9d283738aa Add preliminary work for HTTP health endpoint (#68)
* Add preliminary work for HTTP health endpoint

* Http: Fix infinite loop bug in Tx509KeypairGenerator::generateKey()

* update commandline

* Add TLS Support class for use with http server

* Add preliminary HTTP Server; TLS still broken; fix in later commit

* Fix TLS handshake, due to server being unable to serve key/certfile in 'Http.h/Http.cpp'; Cause was httlib not being threadsafe due to being a blocking http library

* Run clang format

* Add option to configure http server port via ServerConfig

* TConfig: add HTTPServerPort to config parsing step

* Fix SSL Cert / Key path not auto generating when not existing

* Add health endpoint; Fix SSL Cert serial no. not refreshing when regenerating

* Switch arround status codes in /health route

* Run clang format

Co-authored-by: Lion Kortlepel <development@kortlepel.com>
2021-12-05 18:24:55 +01:00
Lion Kortlepel b33d50361c fix typo in --help 2021-12-05 01:40:39 +01:00
Lion Kortlepel 479bb9f931 TLuaEngine: Make WaitForAll timeout optional 2021-12-05 01:32:15 +01:00
Lion Kortlepel 672c7d02d1 Common: Add lua warn 2021-12-05 01:31:44 +01:00
Lion Kortlepel a289d0e872 Add timeout to WaitForAll 2021-12-05 01:09:41 +01:00
Lion Kortlepel 86169ad0fa TConsole: Add notice about help command on wrong command 2021-12-05 00:55:42 +01:00
Lion Kortlepel f477570a1c Add changelog about new console 2021-12-05 00:54:24 +01:00
Lion Kortlepel 7b99ccb08e Add --working-directory flag 2021-12-05 00:42:50 +01:00
Lion Kortlepel 265dd710cf add status command 2021-12-02 01:25:17 +01:00
Lion Kortlepel eb67f483b2 Another possible fix for deadlock 2021-11-29 03:07:20 +01:00
Lion Kortlepel 98c7fea139 Possible fix for event handler timeouts 2021-11-29 02:56:41 +01:00
Lion Kortlepel ccdc5dae17 update commandline 2021-11-29 02:09:05 +01:00
Lion Kortlepel 1bc9a5293e Add kick response 2021-11-29 02:03:20 +01:00
Lion Kortlepel c3151093e2 CMake: remove __linux completely 2021-11-29 01:42:23 +01:00
Lion Kortlepel 8f77f1c8c0 Add kick, fix cmakelists pretending to be on linux all the time
lol
2021-11-29 01:34:35 +01:00
Lion Kortlepel 19d67dee95 Add GetPlayerIDByName, kick, say 2021-11-29 01:14:37 +01:00
Lion Kortlepel fc440bea2a Add ability to switch into other lua states 2021-11-29 00:37:00 +01:00
Lion Kortlepel c91f3ee33c Print entering and leaving lua as raw 2021-11-29 00:17:09 +01:00
Lion Kortlepel 297b646d33 update commandline 2021-11-29 00:17:03 +01:00
Lion Kortlepel 768d0466f4 Add simple command interface 2021-11-28 23:45:03 +01:00
Lion Kortlepel 1e0ab6bbb3 Update commandline 2021-11-28 23:44:56 +01:00
Lion Kortlepel 472ec3390c Update changelog 2021-11-27 12:25:31 +01:00
Lion Kortlepel a4123d7d7c Remove --ip example 2021-11-27 03:28:35 +01:00
Lion Kortlepel 6fd54f7907 Fix server message printing bug
There was a space missing
2021-11-27 02:31:21 +01:00
Lion Kortlepel fd7bea0f36 Add BEAMMP_{WINDOWS,LINUX,APPLE} preprocessor defines instead of platform specific ones 2021-11-27 02:11:22 +01:00
Lion Kortlepel 9d2d4bb221 Remove --ip
This should be re-added later as a ServerConfig value
2021-11-26 19:08:05 +01:00
Lion Kortlepel e1756298af Fix doubled consent settings in ServerConfig 2021-11-26 19:07:08 +01:00
Lion Kortlepel eae27633db Add commandline arguments, implement --config, --version, --help 2021-11-26 19:04:21 +01:00
Lion Kortlepel 938774618c Fix apple + linux compatibility 2021-11-26 13:25:09 +01:00
Lucca Jiménez Könings 1a2a123d87 Fix various macOS compatibility issues 2021-11-25 02:01:44 +01:00
Lucca Jiménez Könings 6247061d09 Replace gettid() call with POSIX compliant getpid() call in 'Common.cpp' 2021-11-25 02:01:44 +01:00
Lucca Jiménez Könings 08a62261e7 Add temporary cmake mods to make project compile on macOS, add ifdefs in 'Compat.h' for target darwin 2021-11-25 02:01:44 +01:00
Lion Kortlepel a7db41ebaa write thread ids to file 2021-11-21 03:31:35 +01:00
Lion Kortlepel 66d4e34a75 print always 2021-11-21 03:29:22 +01:00
Lion Kortlepel 7169e65305 clarify thread names 2021-11-21 03:19:02 +01:00
Lion Kortlepel 097e52674b update commandline 2021-11-21 03:04:50 +01:00
Lion Kortlepel 1ec47252f2 update commandline 2021-11-21 02:31:52 +01:00
Lion Kortlepel 2ddac7f138 update commandline 2021-11-21 02:26:02 +01:00
Lion Kortlepel cf08dee84f Common: Make TID work on Windows 2021-11-19 13:12:27 +01:00
Lion Kortlepel f1148ed1c4 LuaAPI: Show quotes around strings in table dumps (#60) 2021-11-19 13:03:27 +01:00
Lion Kortlepel b6fa3574fd RegisterThread: Add TID print (#63) 2021-11-19 12:46:43 +01:00
Lion Kortlepel 7079e80b71 Lua: Remove leading space in onChatMessage (fix #35) 2021-11-19 12:43:27 +01:00
Lion Kortlepel 48b9aa72dc update commandline 2021-11-10 00:38:03 +01:00
Lion Kortlepel c36ea52f60 Http: Use ipv4 2021-11-08 23:45:35 +01:00
Lion Kortlepel 87f23427a1 CMake: Nail down lua to be 5.3 2021-11-08 23:13:47 +01:00
Lion Kortlepel 7197c23632 TNetwork: Possible ip address fix 2021-11-08 23:10:24 +01:00
Lion Kortlepel b5ea084c9b update commandline 2021-11-08 22:25:23 +01:00
Lion Kortlepel f8af134dc9 start writing http lua stuff, also heartbeat debug printing 2021-11-08 22:08:07 +01:00
Lion Kortlepel 3e7aa763ed fix release build 2021-11-08 00:01:41 +01:00
Lion Kortlepel 5fdd7beac8 remove stray zip.h include 2021-11-07 23:56:22 +01:00
Lion Kortlepel 701a7feee3 remove boost, add httplib, temporarily remove http* lua 2021-11-07 23:54:33 +01:00
Lion Kortlepel 1e305c3c90 Http: cleanup 2021-11-07 23:09:06 +01:00
Lion Kortlepel 9f5fa8fb9b Remove libzip 2021-11-06 13:03:20 +01:00
Lion Kortlepel fdc205f521 Add ScopedTimer, Remove some comments 2021-10-31 01:27:21 +02:00
Lion a18abd6c8b Update README.md 2021-10-27 18:33:50 +01:00
Lion 1b2ea88a71 README: add irc and discord links 2021-10-27 18:33:50 +01:00
Lion Kortlepel 50589fbe3d send 'Om' to self 2021-10-26 17:39:59 +02:00
Lion Kortlepel 7287fce341 possible actions fix
thank you, anon!
2021-10-26 02:22:03 +02:00
Lion Kortlepel 6de625682d fix compiler error, empty content_type does nothing now 2021-10-26 02:12:17 +02:00
Lion Kortlepel 6189aed6e7 broadcast Om packets 2021-10-26 02:08:02 +02:00
Lion Kortlepel c4d6aab08b fix MP.HttpGET (was not passing contentType) 2021-10-08 08:44:20 +02:00
Lion Kortlepel 577d4c429d TNetwork: Use 'K' packet instead of 'E' to kick players 2021-10-02 01:44:13 +02:00
Lion Kortlepel d027f7f29f Lua: Kick properly (with ClientKick), add chat message printing 2021-10-02 01:28:58 +02:00
Lion Kortlepel af14188ec0 remove spammy debug 2021-10-01 04:22:33 +02:00
Lion Kortlepel 1ee45c9d1a Ignore BEAMMP_FN_NOT_FOUND errors 2021-10-01 04:09:40 +02:00
Lion Kortlepel d1f890752a Report errors on WaitForAll 2021-10-01 03:56:18 +02:00
Lion Kortlepel 9eabd19e17 Report more errors, better 2021-10-01 03:52:12 +02:00
Lion Kortlepel ae7a63669f detect recursion in LuaToString 2021-10-01 03:35:13 +02:00
Lion Kortlepel 243e96d503 Check all futures 2021-10-01 03:27:24 +02:00
Lion Kortlepel 932fbe2b2f reintroduce waiting for results 2021-10-01 03:01:28 +02:00
Lion Kortlepel 26ec50b199 Build libzip statically 2021-10-01 02:31:48 +02:00
Lion Kortlepel 87ecc3f9f6 fix readme invalid cmake invocation, show ms in debug output 2021-10-01 02:03:11 +02:00
Lion Kortlepel 7f1d37a0e6 Fix libzip 2021-10-01 01:57:29 +02:00
Lion Kortlepel 255feb4f8e CMake: Fix minor mistake in cmakelists 2021-10-01 01:52:13 +02:00
Lion Kortlepel 013ca14ab8 add libzip module path 2021-10-01 01:51:22 +02:00
Lion Kortlepel e7f29ce04f Fix server event timing 2021-10-01 01:40:37 +02:00
Lion Kortlepel e948edca8d Add zip files with past logs 2021-09-30 23:24:23 +02:00
Lion Kortlepel d0431c0b9d Use v2 api 2021-09-27 15:46:37 +02:00
Lion Kortlepel 0961f86662 Add heartbeat-api-v API version header 2021-09-27 15:46:37 +02:00
Lion Kortlepel 7f2ca025f8 Start using new heartbeat response format 2021-09-27 15:46:37 +02:00
Lion Kortlepel 33ebfa82f0 Remove spammy TRACE 2021-09-22 21:08:01 +02:00
Lion Kortlepel 9d0caf2c7d Lua: Implement Hot-Reload 2021-09-21 16:21:09 +02:00
Lion Kortlepel fe3ccafc1d Lua: Add various FS functions 2021-09-21 00:27:09 +02:00
Lion Kortlepel 23ffa25d78 Lua: Add FS.GetParentFolder 2021-09-20 23:44:17 +02:00
Lion Kortlepel 908f67a799 Fix compile error 2021-09-20 23:40:28 +02:00
Lion Kortlepel 27b5c6d850 Add GetFilename, GetExtension, 2021-09-20 22:45:12 +02:00
Lion Kortlepel 32756ccc4a Use read instead of ifstream rdbuf 2021-09-20 17:19:38 +02:00
Lion Kortlepel 3626f4108e Use filestreams instead of c-lib 2021-09-20 17:05:28 +02:00
Lion Kortlepel d7a4322313 More windows fixes 2021-09-20 17:00:30 +02:00
Lion Kortlepel d84051bdd3 possible fix for windows path issue 2021-09-20 16:59:42 +02:00
Lion Kortlepel 238577a4f7 Remove debug symbols 2021-09-20 16:36:37 +02:00
Lion Kortlepel 041db23a69 Lua: info,debug -> trace 2021-09-20 16:23:38 +02:00
Lion Kortlepel a3a18a3b56 Lua: Add CancelEventTimer 2021-09-20 16:09:17 +02:00
Lion Kortlepel 9efe352e7a Lua: Working MP.CreateEventTimer 2021-09-20 15:41:40 +03:00
Lion Kortlepel 3edb9322d4 Lua: Simple CreateEventTimer improvements 2021-09-20 13:39:14 +03:00
Lion Kortlepel 4bf89706b4 Lua: Add MP.CreateTimedEvent as CreateThread replacement 2021-09-20 12:43:00 +03:00
Lion Kortlepel a97791d4ee Please MSVC stop being so bad 2021-09-20 00:46:13 +02:00
Lion Kortlepel 323184911d Add new FS functions to Changelog 2021-09-20 00:39:12 +02:00
Lion Kortlepel 6f9f790c5b Lua: Add FS.Remove, FS.Rename (move), FS.Copy, FS.Exists 2021-09-20 00:36:22 +02:00
Lion Kortlepel 4de80e0c7a Lua: Remove debug prints, add GetLuaMemoryUsage 2021-09-19 23:55:24 +02:00
Lion Kortlepel 366a7dc9a6 Lua: Add Lua panic handler 2021-09-19 23:34:27 +02:00
Lion Kortlepel 785b858651 Same as last one
yikes.
2021-09-19 12:29:02 +02:00
Lion Kortlepel a4ff9488c5 Satisfy MSVC's weird attraction to using .string() when its not necessary
I cannot bear another minute of msvc, i swear
2021-09-19 12:23:26 +02:00
Lion Kortlepel 60b86e2be6 Create TLuaChunk constructor to satisfy MSVC 2021-09-19 12:12:38 +02:00
Lion Kortlepel 701e613990 Lua: Fix float printing
Now prints 0, not 0.000000, etc.
2021-09-19 12:10:38 +02:00
Lion Kortlepel 7dbf859529 Lua: Implement event arguments 2021-09-19 11:45:58 +02:00
Lion Kortlepel 8419ca2234 possible windows compiler fix 2021-09-19 01:32:30 +02:00
Lion Kortlepel 7f63dd8d7d Lua: Add FS, FS.CreateDirectory 2021-09-19 01:30:59 +02:00
Lion Kortlepel da78d77e6c Changelog: add MP.PrintRaw 2021-09-19 01:21:30 +02:00
Lion Kortlepel 1ff5107707 Lua: Add MP.PrintRaw 2021-09-19 01:20:17 +02:00
Lion Kortlepel dd70e88e4c SignalHandling: ensure that a signal handler is present on compilation 2021-09-18 22:08:20 +02:00
Lion Kortlepel 38dffc5462 Lua: Pass plugin path and filename to queue for later 2021-09-18 01:20:26 +02:00
Lion Kortlepel f98ef7d41c Lua: Call local eventhandlers synchronously when TriggerGlobalEvent is called from inside a handling state 2021-09-18 00:05:51 +02:00
Lion Kortlepel 29a858e74a Network: Fix TConnection related compiler issue 2021-09-17 15:33:16 +02:00
Lion Kortlepel e53f2d9877 Changelog: Add MP.Https* documentation 2021-09-17 15:32:01 +02:00
Lion Kortlepel be3ac45abb add MP.HttpsGET, MP.HttpsPOST 2021-09-17 15:30:30 +02:00
Lion Kortlepel 246f8289b6 CMake: Fix compiler error with mismatching visibility of target_link_libraries 2021-09-17 15:22:06 +02:00
Lion Kortlepel 3c7109b23f Update Changelog, fix assert formatting 2021-09-17 15:21:06 +02:00
Lion Kortlepel 82a5fc3999 Assert: Fix compiler error 2021-09-17 15:06:13 +02:00
Lion Kortlepel 11d4d9ff91 remove old commandline path 2021-09-17 15:02:53 +02:00
Lion Kortlepel 48caae25fd Finalize master&new-lua-features merge 2021-09-17 14:58:40 +02:00
Lion Kortlepel 9dfe9f659a Update cmakelists to remove socket.io 2021-09-17 14:30:33 +02:00
Lion Kortlepel a07b5062de Reintroduce gitmodules 2021-09-17 14:28:45 +02:00
Lion Kortlepel fcbb188ee0 update modules, cmakelists 2021-09-17 14:25:11 +02:00
Lion Kortlepel 883d69ba27 Merge remote-tracking branch 'origin/master' into rewrite-lua 2021-09-17 14:24:12 +02:00
Lion Kortlepel 7459779363 update cmakelists 2021-09-17 14:21:20 +02:00
Lion Kortlepel a0c47d9947 move sentry-native 2021-09-17 14:17:27 +02:00
Lion Kortlepel 7db345cfbc update submodules 2021-09-17 13:36:16 +02:00
Lion Kortlepel 1d3958817f Merge remote-tracking branch 'origin/new-lua-features' into rewrite-lua
This is the first of a few commits to merge the new lua features and the
rewrite
2021-09-17 13:29:44 +02:00
Lion Kortlepel fd3088c78f Re-add SendChatMessage 2021-09-17 12:58:07 +02:00
Lion Kortlepel c2b73d93b5 Lua: Implement more core functions 2021-09-17 12:56:08 +02:00
Lion Kortlepel 9a37ed4341 Lua: Add GetPlayerName 2021-09-17 12:45:34 +02:00
Lion Kortlepel e64114e4fa Possible compiler fix 2021-09-17 02:44:49 +02:00
Lion Kortlepel 79531334dd another nice fix 2021-09-17 02:38:14 +02:00
Lion Kortlepel ed6f5282d9 Nice fix. 2021-09-17 02:36:06 +02:00
Lion Kortlepel 62fd369625 Lua: Fix more compile errors for windows 2021-09-17 02:30:20 +02:00
Lion Kortlepel 1880536276 Lua: Call onInit properly 2021-09-17 02:26:49 +02:00
Lion Kortlepel bac476ec34 Lua: Set package.path and package.cpath before onInit is called 2021-09-17 02:19:45 +02:00
Lion Kortlepel e75e65815c Lua: change a static_cast to a reinterpret_cast, fun times
:^)
2021-09-17 01:27:45 +02:00
Lion Kortlepel b0c467f971 Lua: Add timer 2021-09-17 01:25:52 +02:00
Lion Kortlepel 6b17990d4d Add GetOSTimeMS 2021-09-17 00:57:43 +02:00
Lion Kortlepel c73d56c143 Possible compiler fix 2021-09-17 00:54:41 +02:00
Lion Kortlepel a44050f0f1 Lua: Almost Working events, all triggers working 2021-09-17 00:21:43 +02:00
Lion Kortlepel cb1eb40def Lua: remove unimplemented 2021-09-16 22:31:54 +02:00
Lion Kortlepel 4c03a90157 Lua: Add more old API 2021-09-16 22:31:09 +02:00
Lion Kortlepel be61511bdf Fix CMake, Add more Lua API 2021-09-16 19:38:31 +02:00
Lion Kortlepel 968d9ff999 Lua: Implement most API functions 2021-09-16 19:00:13 +02:00
Lion Kortlepel 1c80a4deb7 Lua: working events, global and local 2021-09-16 18:14:11 +02:00
Lion Kortlepel e602decb96 Lua: Fix multiple issues with events 2021-09-16 14:58:47 +02:00
Lion Kortlepel dca573b15c Fix more compile issues with lua, add TriggerGlobalEvent 2021-09-16 13:06:04 +02:00
Lion Kortlepel 26231c6272 Fix compile issue with asio, implement Lua events 2021-09-16 13:03:00 +02:00
Lion Kortlepel ebe3630ec8 LuaAPI: Implement GetOSName 2021-09-16 12:22:49 +02:00
Lion Kortlepel d7f7a81cb0 LuaAPI: Print: dump tables properly and recursively 2021-09-16 12:21:11 +02:00
Lion Kortlepel 9ef6c32864 CMake: fix include paths 2021-09-16 11:59:00 +02:00
Lion Kortlepel 9b9c18a4c1 Lua: Add variadic print, LuaAPI 2021-09-16 11:54:52 +02:00
Lion Kortlepel 5978665ad6 Lua: Fix threading related crash 2021-09-16 10:07:04 +02:00
Lion Kortlepel 2cf368c2b0 First working console 2021-09-16 03:40:24 +02:00
Lion Kortlepel ba0678dade Continue Lua Rewrite 2021-09-16 03:21:00 +02:00
Lion Kortlepel c309fa28c6 update sol2 to v3.2.3 2021-09-16 01:09:24 +02:00
Lion Kortlepel f5b2be0a03 rename Assert to beammp_assert 2021-09-16 01:04:38 +02:00
Lion Kortlepel dd4e4c4467 Start rewrite of lua, rename all print functions 2021-09-16 01:04:01 +02:00
Lion Kortlepel d082620525 add sol2 2021-09-16 00:34:09 +02:00
Lion Kortlepel be90a8a2c0 add sol2 2021-09-16 00:33:13 +02:00
Lion Kortlepel 8b69127cdd Finish moving deps to deps/ 2021-09-16 00:22:33 +02:00
Lion Kortlepel bb34378b8e Move all dependencies to deps/ 2021-09-16 00:09:39 +02:00
Lion Kortlepel 2355327c21 CMake: Fix typo in SANITIZE codepath 2021-09-15 17:55:34 +02:00
Lion Kortlepel 3837e101e2 CMake: Use gzipped debug info on linux 2021-09-15 17:55:34 +02:00
Lion Kortlepel fa19ba08e3 Sentry: Properly store DSN 2021-09-15 17:55:34 +02:00
Lion Kortlepel 57d0eb735e Add cryptography header for the future 2021-09-15 17:55:34 +02:00
Lion Kortlepel 15704abf6c Http, Heartbeat: Process status < 0 differently, report as "Invalid
Response Code"
2021-09-15 17:55:34 +02:00
Lion Kortlepel 6883c96d33 Http: Add Sentry error breadcrumbs on internal https POST errors 2021-09-15 17:55:34 +02:00
Lion Kortlepel f632606d76 Heartbeat: Dont report 200 + INVALID_KEY to Sentry 2021-09-15 17:55:34 +02:00
Lion Kortlepel c70ada2926 Config: private by default 2021-09-15 17:55:34 +02:00
Lion Kortlepel 80aebcb9a7 Actions: prerelease by default 2021-09-13 11:58:01 +03:00
Lion Kortlepel 3fc397814c Move update check to after initialization (since its blocking) 2021-09-11 11:38:06 +03:00
Lion Kortlepel 6542be09ee Clarify what sentry sends, add a way to turn off the warning 2021-09-11 11:38:06 +03:00
Lion Kortlepel 38b934bc0f Move signal handling into its own translation unit to limit overlap 2021-09-11 11:38:06 +03:00
Lion Kortlepel a2f92b5791 Update changelog, use std::exit instead of exit 2021-09-11 11:38:06 +03:00
Lion Kortlepel 30624c77a2 Update commandline; reset terminal before exit 2021-09-11 11:38:06 +03:00
Lion Kortlepel b1664bb184 Application: Perform hard-shutdown after 3 Ctrl+C's 2021-09-11 11:38:06 +03:00
Lion Kortlepel ffac000cd2 Config: Add basic opt-out for Sentry 2021-09-11 11:38:06 +03:00
Lion Kortlepel 3cd94380e2 Changelog: Add recent additions 2021-09-11 11:38:06 +03:00
Lion Kortlepel b055fd8bda GracefullyShutdown: Add "subsystem x/y shutting down" message
Remove old "X shutting down", "X shut down" messages, they were bad and
confusing
2021-09-11 11:38:06 +03:00
Lion Kortlepel d43ee4b7b6 Bump version to 2.3.2 2021-09-11 11:38:06 +03:00
Lion Kortlepel a514591650 Main: Add Ctrl+C handler for windows 2021-09-11 11:38:06 +03:00
Lion Kortlepel 0f9f81e9fa Http: Add cloudflare 5XX status code strings 2021-09-11 11:38:06 +03:00
Lion Kortlepel 11e94e91a7 Update Changelog.md to reflect latest changes in recent merge from #44 2021-09-10 13:41:45 +03:00
Lion Kortlepel 35a3dab1ce TNetwork: clarify error messages 2021-08-07 23:51:56 +02:00
Lion Kortlepel 131ade02cd TNetwork: setsockopt: cast optval to void* 2021-08-02 14:23:20 +02:00
Lion Kortlepel 2c2f76b340 TNetwork: reuseaddr instead of reuseport 2021-08-02 14:15:30 +02:00
Lion Kortlepel 106d8e5863 Common: missed semicolon 2021-08-02 14:06:00 +02:00
Lion Kortlepel 1fb7cb6bc1 improve error reporting, remove duplicate code 2021-08-02 14:01:20 +02:00
Lion Kortlepel 9666fff622 CMake: remove mentions of luasocket again 2021-08-02 14:01:20 +02:00
Lion Kortlepel 5d5f155f0c remove luasocket-cmake 2021-08-02 14:01:20 +02:00
Lion Kortlepel cfe348770c Common: Add sstream include for std::stringstream 2021-08-02 14:01:20 +02:00
Lion Kortlepel 206120dcef CMake: include luasocket after finding lua 2021-08-02 14:01:20 +02:00
Lion Kortlepel 2a3bb1bef8 add luasocket 2021-08-02 14:01:20 +02:00
Lion Kortlepel 0faa46d48c remove luasocket again 2021-08-02 14:01:20 +02:00
Lion Kortlepel dae52a71fd add CMakeLists for lib/ 2021-08-02 14:01:20 +02:00
Lion Kortlepel c46c36bf09 add luasocket submodule 2021-08-02 14:01:20 +02:00
Lion Kortlepel ed3d0834e6 add ws2tcpip.h 2021-08-02 14:01:20 +02:00
Lion Kortlepel 9d4c6e880b fix version printing 2021-08-02 14:01:20 +02:00
Lion Kortlepel d39b5d7c77 use inet_ntop instead of inet_ntoa (STILL BROKEN THOUGH) 2021-08-02 14:01:20 +02:00
Lion Kortlepel b071906db5 clarify installation 2021-08-02 14:01:20 +02:00
Lion Kortlepel 8420cdb5bf Add Settings enum, better print 2021-08-02 14:01:20 +02:00
Lion Kortlepel e11211f201 add GetServerVersion 2021-08-02 14:01:20 +02:00
Lion Kortlepel 9595ef164e use fake version for lua update for now 2021-08-02 14:01:17 +02:00
Lion Kortlepel d18afdf84b Lua: add onShutdown 2021-08-02 14:01:01 +02:00
Lion Kortlepel 0acbb70d10 add ip to identifiers, changed value format 2021-08-02 14:01:01 +02:00
Lion Kortlepel 7a3848e640 fix luatable in GetPlayerIdentifiers 2021-08-02 14:01:01 +02:00
Lion Kortlepel 261aa8f320 fix comment 2021-08-02 14:01:01 +02:00
Lion Kortlepel e994cdd8a2 Add printRaw
Same as print() but does not prefix with time, date, filename, etc.
Use with care.
2021-08-02 14:01:01 +02:00
Lion Kortlepel 44e0f3aa21 add MP.HttpsGET, MP.HttpsPOST 2021-08-02 14:01:00 +02:00
Anonymous-275 8853cef809 TODO edit 2021-08-02 14:01:00 +02:00
Anonymous-275 fb76b8309a Fully working lua_Register 2021-08-02 14:01:00 +02:00
Lion Kortlepel a23946dddf switch to toml11
it's better, believe me
2021-08-02 14:01:00 +02:00
Anonymous-275 55ee1d3747 simpler lua_Register 2021-08-02 14:01:00 +02:00
Lion Kortlepel 4cd0093687 rebase 2021-08-02 14:01:00 +02:00
Lion Kortlepel cc88734279 implement GetOSName, start working on HttpsPOST 2021-08-02 14:01:00 +02:00
Anonymous-275 a865c95e2a lua Register 2021-08-02 14:01:00 +02:00
65 changed files with 4682 additions and 2070 deletions
+1 -3
View File
@@ -21,8 +21,6 @@ jobs:
echo ${#beammp_sentry_url} echo ${#beammp_sentry_url}
sudo apt-get update sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux run: cmake -E make_directory ${{github.workspace}}/build-linux
@@ -37,7 +35,7 @@ jobs:
- name: Build - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
+14 -3
View File
@@ -15,12 +15,12 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts - name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main uses: lukka/run-vcpkg@v7
id: runvcpkg id: runvcpkg
with: with:
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp curl' vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment - name: Create Build Environment
@@ -44,4 +44,15 @@ jobs:
name: BeamMP-Server.exe name: BeamMP-Server.exe
path: ${{github.workspace}}/build-windows/Release/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
+6 -8
View File
@@ -24,7 +24,7 @@ jobs:
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
release_name: ${{ github.ref }} release_name: ${{ github.ref }}
draft: false draft: false
prerelease: false prerelease: true
body: | body: |
Files included in this release: Files included in this release:
- `BeamMP-Server.exe` is the windows build - `BeamMP-Server.exe` is the windows build
@@ -43,8 +43,6 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev sudo apt-get install -y libz-dev rapidjson-dev liblua5.3 libssl-dev libwebsocketpp-dev libcurl4-openssl-dev
sudo add-apt-repository ppa:mhier/libboost-latest
sudo apt-get install -y libboost1.70-dev libboost1.70
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux run: cmake -E make_directory ${{github.workspace}}/build-linux
@@ -59,7 +57,7 @@ jobs:
- name: Build - name: Build
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
@@ -82,12 +80,12 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
- name: Restore artifacts, or run vcpkg, build and cache artifacts - name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main uses: lukka/run-vcpkg@v7
id: runvcpkg id: runvcpkg
with: with:
vcpkgArguments: 'lua zlib rapidjson boost-beast boost-asio openssl websocketpp curl' vcpkgArguments: 'lua zlib rapidjson openssl websocketpp curl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '8dddc6c899ce6fdbeab38b525a31e7f23cb2d5bb' vcpkgGitCommitId: 'a106de33bbee694e3be6243718aa2a549a692832'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment - name: Create Build Environment
@@ -103,7 +101,7 @@ jobs:
- name: Build - name: Build
working-directory: ${{github.workspace}}/build-windows working-directory: ${{github.workspace}}/build-windows
shell: bash shell: bash
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server --parallel
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
+27 -12
View File
@@ -1,15 +1,30 @@
[submodule "include/commandline"] [submodule "deps/commandline"]
path = include/commandline path = deps/commandline
url = https://github.com/lionkor/commandline url = https://github.com/lionkor/commandline
[submodule "asio"] [submodule "deps/fmt"]
path = asio path = deps/fmt
url = https://github.com/fmtlib/fmt
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio url = https://github.com/chriskohlhoff/asio
[submodule "rapidjson"] [submodule "deps/rapidjson"]
path = rapidjson path = deps/rapidjson
url = https://github.com/Tencent/rapidjson url = https://github.com/Tencent/rapidjson
[submodule "include/toml11"] [submodule "deps/toml11"]
path = include/toml11 path = deps/toml11
url = https://github.com/ToruNiina/toml11 url = https://github.com/ToruNiina/toml11
[submodule "include/sentry-native"] [submodule "deps/sentry-native"]
path = include/sentry-native path = deps/sentry-native
url = https://github.com/getsentry/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
+85 -73
View File
@@ -1,9 +1,5 @@
cmake_minimum_required(VERSION 3.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 \ message(STATUS "You can find build instructions and a list of dependencies in the README at \
https://github.com/BeamMP/BeamMP-Server") https://github.com/BeamMP/BeamMP-Server")
@@ -13,38 +9,69 @@ project(BeamMP-Server
HOMEPAGE_URL https://beammp.com HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C) LANGUAGES CXX C)
if (WIN32) 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. # this has to happen before sentry, so that crashpad on windows links with these settings.
message(STATUS "MSVC -> forcing use of statically-linked runtime.") message(STATUS "MSVC -> forcing use of statically-linked runtime.")
STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
endif() if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
include_directories("include/sentry-native/include") set(SENTRY_BUILD_RUNTIMESTATIC ON)
set(SENTRY_BUILD_SHARED_LIBS OFF) endif ()
if (MSVC)
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif()
set(SENTRY_BACKEND breakpad)
add_subdirectory("include/sentry-native")
message(STATUS "Setting compiler flags")
if (WIN32)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}) set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include) include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib) link_directories(${VcpkgRoot}/lib)
# ------------------------ LINUX ---------------------------------
elseif (UNIX) elseif (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -static-libstdc++") 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_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s -fno-builtin") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
if (SANITIZE) if (SANITIZE)
message(STATUS "sanitize is ON") message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLanAGS} -fsanitize=undefined,thread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,thread")
endif (SANITIZE) endif (SANITIZE)
endif () endif ()
include_directories("include/sentry-native/include")
# ------------------------ SENTRY ---------------------------------
message(STATUS "Checking for Sentry URL") message(STATUS "Checking for Sentry URL")
# this is set by the build system. # this is set by the build system.
# IMPORTANT: if you're building from source, just leave this empty # IMPORTANT: if you're building from source, just leave this empty
@@ -52,26 +79,21 @@ if (NOT DEFINED BEAMMP_SECRET_SENTRY_URL)
message(WARNING "No sentry URL configured. Sentry logging is disabled for this build. \ 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.") 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(BEAMMP_SECRET_SENTRY_URL "")
set(SENTRY_BACKEND none)
else() else()
string(LENGTH ${BEAMMP_SECRET_SENTRY_URL} URL_LEN) set(SENTRY_BACKEND breakpad)
message(STATUS "Sentry URL is length ${URL_LEN}")
endif() endif()
add_subdirectory("deps/sentry-native")
message(STATUS "Adding local source dependencies") # ------------------------ C++ SETUP ---------------------------------
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
include_directories("asio/asio/include")
include_directories("rapidjson/include")
include_directories("websocketpp")
add_subdirectory("include/commandline")
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") # ------------------------ DEPENDENCIES ------------------------------
message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps)
message(STATUS "Looking for Boost") # ------------------------ BEAMMP SERVER -----------------------------
find_package(Boost REQUIRED COMPONENTS system thread)
add_executable(BeamMP-Server add_executable(BeamMP-Server
src/main.cpp src/main.cpp
@@ -83,59 +105,49 @@ add_executable(BeamMP-Server
include/VehicleData.h src/VehicleData.cpp include/VehicleData.h src/VehicleData.cpp
include/TConfig.h src/TConfig.cpp include/TConfig.h src/TConfig.cpp
include/TLuaEngine.h src/TLuaEngine.cpp include/TLuaEngine.h src/TLuaEngine.cpp
include/TLuaFile.h src/TLuaFile.cpp include/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.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}") 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 target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline") "${CMAKE_CURRENT_SOURCE_DIR}/commandline")
message(STATUS "Looking for Lua") include(FindLua)
find_package(Lua REQUIRED) include(FindOpenSSL)
include(FindThreads)
include(FindZLIB)
target_include_directories(BeamMP-Server PUBLIC target_include_directories(BeamMP-Server PUBLIC
${Boost_INCLUDE_DIRS}
${LUA_INCLUDE_DIR} ${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS}
"include/tomlplusplus" "include/tomlplusplus"
"include/sentry-native/include" "include/sentry-native/include"
"include/curl/include") "include/curl/include")
message(STATUS "Looking for SSL") target_link_libraries(BeamMP-Server
OpenSSL::SSL
OpenSSL::Crypto
sol2::sol2
fmt::fmt
Threads::Threads
ZLIB::ZLIB
${LUA_LIBRARIES}
commandline
sentry)
if (WIN32)
find_package(OpenSSL REQUIRED) target_link_libraries(BeamMP-Server wsock32 ws2_32)
message(STATUS "CURL IS ${CURL_LIBRARIES}")
if (UNIX)
target_link_libraries(BeamMP-Server
z
pthread
stdc++fs
${LUA_LIBRARIES}
crypto
${OPENSSL_LIBRARIES}
commandline
sentry)
elseif (WIN32)
include(FindLua)
message(STATUS "Looking for libz")
find_package(ZLIB REQUIRED)
message(STATUS "Looking for RapidJSON")
find_package(RapidJSON CONFIG REQUIRED)
target_include_directories(BeamMP-Server PRIVATE ${RAPIDJSON_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
target_link_libraries(BeamMP-Server PRIVATE
ws2_32
ZLIB::ZLIB
${LUA_LIBRARIES}
${OPENSSL_LIBRARIES}
commandline
sentry)
endif () endif ()
+65
View File
@@ -1,3 +1,68 @@
# 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 # v2.3.0
- ADDED version check - the server will now let you know when a new release is out - ADDED version check - the server will now let you know when a new release is out
+48 -27
View File
@@ -6,6 +6,15 @@
This is the server for the multiplayer mod **[BeamMP](https://beammp.com/)** for the game [BeamNG.drive](https://www.beamng.com/). 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). 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 ## Minimum Requirements
These values are guesstimated and are subject to change with each release. These values are guesstimated and are subject to change with each release.
@@ -41,7 +50,7 @@ You can find precompiled binaries under [Releases](https://github.com/BeamMP/Bea
## Build Instructions ## Build Instructions
**__Do not compile from `master`. Always build from a release tag, i.e. `tags/v2.0`!__** **__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)). 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)).
@@ -51,35 +60,47 @@ Currently only linux and windows are supported (generally). See [Releases](https
Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/BeamMP-Server/releases/). Please use the prepackaged binaries in [Releases](https://github.com/BeamMP/BeamMP-Server/releases/).
Dependencies for windows can be installed with `vcpkg`, in which case the current dependencies are the `x64-windows-static` versions of `lua`, `zlib`, `rapidjson`, `boost-beast`, `boost-asio` and `openssl`. Dependencies for **windows** can be installed with `vcpkg`.
These are:
```
lua
zlib
rapidjson
openssl
websocketpp
curl
```
#### Linux / \*nix #### Linux
These package names are in the debian / ubuntu style. Feel free to PR your own guide for a different distro. These package names are in the debian / ubuntu style. Feel free to PR your own guide for a different distro.
- `git` Runtime dependencies for **linux** are (debian/ubuntu):
- `make`
- `cmake`
- `g++`
- `libcurl4-openssl-dev` or similar (search for `libcurl` and look for one with `-dev`)
Must support ISO C++17. If your distro's `g++` doesn't support C++17, chances are that it has a `g++-8` or `g++-10` package that does. If this is the case. you just need to run CMake with `-DCMAKE_CXX_COMPILER=g++-10` (replace `g++-10` with your compiler's name).
- `liblua5.3-dev`
Any 5.x version should work, but 5.3 is what we officially use. Any other version might break in the future.
You can also use any version of `libluajit`, but the same applies regarding the version.
- `libz-dev`
- `rapidjson-dev`
- `libopenssl-dev` or `libssl-dev`
**If** you're building it from source, you'll need `libboost1.70-all-dev` or `libboost1.71-all-dev` or higher as well.
If you can't find this version of boost (only 1.6x, for example), you can either update to a newer version of your distro, build boost yourself, or use an unstable rolling release (like Debian `sid` aka `unstable`).
In the end you should end up with a command something like this:
```sh
sudo apt install git make cmake g++-10 liblua5.3 libz-dev rapidjson-dev libopenssl-dev libboost1.71-all-dev
``` ```
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 ### How to build
@@ -88,8 +109,8 @@ On windows, use git-bash for these commands. On Linux, these should work in your
1. Make sure you have all [prerequisites](#prerequisites) installed 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`. 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`. 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/v1.20` for version 1.20. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags). 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 .` (with `.`) 5. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (with `.`)
6. Run `make` 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. 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.
Submodule asio deleted from 230c0d2ae0
+7
View File
@@ -0,0 +1,7 @@
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 Submodule
+1
Submodule deps/asio added at d038fb3c2f
Vendored Submodule
+1
Submodule deps/commandline added at d6b1c32c8a
Vendored Submodule
+1
Submodule deps/cpp-httplib added at b324921c1a
Vendored Submodule
+1
Submodule deps/fmt added at 17dda58391
Vendored Submodule
+1
Submodule deps/json added at eb21824147
Vendored Submodule
+1
Submodule deps/libzip added at 76df02f86b
Vendored Submodule
+1
Submodule deps/rapidjson added at 00dbcf2c6e
Vendored Submodule
+1
Submodule deps/sentry-native added at 90966cc102
Vendored Submodule
+1
Submodule deps/sol2 added at c068aefbed
Vendored Submodule
+1
Submodule deps/toml11 added at fda0a2b9ab
+49
View File
@@ -0,0 +1,49 @@
#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;
};
+6 -3
View File
@@ -2,6 +2,7 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <optional>
#include <queue> #include <queue>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
@@ -12,10 +13,10 @@
class TServer; class TServer;
#ifdef WIN32 #ifdef BEAMMP_WINDOWS
// for socklen_t // for socklen_t
#include <WS2tcpip.h> #include <WS2tcpip.h>
#endif // WIN32 #endif // WINDOWS
struct TConnection final { struct TConnection final {
SOCKET Socket; SOCKET Socket;
@@ -91,7 +92,7 @@ private:
std::queue<std::string> mPacketsSync; std::queue<std::string> mPacketsSync;
std::unordered_map<std::string, std::string> mIdentifiers; std::unordered_map<std::string, std::string> mIdentifiers;
bool mIsGuest = false; bool mIsGuest = false;
std::mutex mVehicleDataMutex; mutable std::mutex mVehicleDataMutex;
TSetOfVehicleData mVehicleData; TSetOfVehicleData mVehicleData;
std::string mName = "Unknown Client"; std::string mName = "Unknown Client";
SOCKET mSocket[2] { SOCKET(0), SOCKET(0) }; SOCKET mSocket[2] { SOCKET(0), SOCKET(0) };
@@ -103,3 +104,5 @@ private:
int mID = -1; int mID = -1;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime; std::chrono::time_point<std::chrono::high_resolution_clock> mLastPingTime;
}; };
std::optional<std::weak_ptr<TClient>> GetClient(class TServer& Server, int ID);
+149 -35
View File
@@ -7,10 +7,13 @@ extern TSentry Sentry;
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
#include <deque> #include <deque>
#include <filesystem>
#include <fmt/format.h>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <sstream> #include <sstream>
#include <zlib.h>
#include "Compat.h" #include "Compat.h"
@@ -34,27 +37,25 @@ class Application final {
public: public:
// types // types
struct TSettings { struct TSettings {
TSettings() noexcept std::string ServerName { "BeamMP Server" };
: ServerName("BeamMP Server") std::string ServerDesc { "BeamMP Default Description" };
, ServerDesc("BeamMP Default Description") std::string Resource { "Resources" };
, Resource("Resources") std::string MapName { "/levels/gridmap_v2/info.json" };
, MapName("/levels/gridmap_v2/info.json") std::string Key {};
, MaxPlayers(10) bool HTTPServerEnabled { false };
, Private(false) int MaxPlayers { 8 };
, MaxCars(1) bool Private { true };
, DebugModeEnabled(false) int MaxCars { 1 };
, Port(30814) { } bool DebugModeEnabled { false };
std::string ServerName; int Port { 30814 };
std::string ServerDesc; std::string CustomIP {};
std::string Resource; bool LogChat { true };
std::string MapName; bool SendErrors { true };
std::string Key; bool SendErrorsMessageEnabled { true };
int MaxPlayers; int HTTPServerPort { 8080 };
bool Private; std::string HTTPServerIP { "127.0.0.1" };
int MaxCars; bool HTTPServerUseSSL { false };
bool DebugModeEnabled; bool HideUpdateMessages { false };
int Port;
std::string CustomIP;
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
}; };
@@ -74,24 +75,54 @@ public:
static std::string PPS() { return mPPS; } static std::string PPS() { return mPPS; }
static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; } static void SetPPS(const std::string& NewPPS) { mPPS = NewPPS; }
static inline TSettings Settings {}; 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 GetBackendUrlForAuth() { return "auth.beammp.com"; }
static std::string GetBackendHostname() { return "backend.beammp.com"; }
static std::string GetBackup1Hostname() { return "backup1.beammp.com"; }
static std::string GetBackup2Hostname() { return "backup2.beammp.com"; }
static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; } static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; }
static void CheckForUpdates(); static void CheckForUpdates();
static std::array<uint8_t, 3> VersionStrToInts(const std::string& str); static std::array<uint8_t, 3> VersionStrToInts(const std::string& str);
static bool IsOutdated(const Version& Current, const Version& Newest); 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: private:
static inline SystemStatusMap mSystemStatusMap {};
static inline std::mutex mSystemStatusMapMutex {};
static inline std::string mPPS; static inline std::string mPPS;
static std::unique_ptr<TConsole> mConsole; static inline std::unique_ptr<TConsole> mConsole;
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 2, 4, 0 }; static inline Version mVersion { 3, 1, 0 };
}; };
std::string ThreadName(bool DebugModeOverride = false); std::string ThreadName(bool DebugModeOverride = false);
@@ -100,6 +131,7 @@ void RegisterThread(const std::string& str);
#define KB 1024 #define KB 1024
#define MB (KB * 1024) #define MB (KB * 1024)
#define SSU_UNRAW SECRET_SENTRY_URL
#define _file_basename std::filesystem::path(__FILE__).filename().string() #define _file_basename std::filesystem::path(__FILE__).filename().string()
#define _line std::to_string(__LINE__) #define _line std::to_string(__LINE__)
@@ -116,6 +148,10 @@ void RegisterThread(const std::string& str);
#define _function_name std::string(__func__) #define _function_name std::string(__func__)
#endif #endif
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG) #if defined(DEBUG)
// if this is defined, we will show the full function signature infront of // if this is defined, we will show the full function signature infront of
@@ -125,43 +161,121 @@ void RegisterThread(const std::string& str);
#else #else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ") #define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif #endif
#define SU_RAW SSU_UNRAW
#else // !defined(DEBUG) #else // !defined(DEBUG)
#define SU_RAW RAWIFY(SSU_UNRAW)
#define _this_location (ThreadName()) #define _this_location (ThreadName())
#endif // defined(DEBUG) #endif // defined(DEBUG)
#define warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x)) #define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x)) #define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define error(x) \ #define beammp_error(x) \
do { \ do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \ Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \ Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false) } 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 luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define debug(x) \ #define beammp_debug(x) \
do { \ do { \
if (Application::Settings.DebugModeEnabled) { \ if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \ Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \ } \
} while (false) } while (false)
#define beammp_event(x) \
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() // trace() is a debug-build debug()
#if defined(DEBUG) #if defined(DEBUG)
#define trace(x) \ #define beammp_trace(x) \
do { \ do { \
if (Application::Settings.DebugModeEnabled) { \ if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \ Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \ } \
} while (false) } while (false)
#else #else
#define trace(x) #define beammp_trace(x)
#endif // defined(DEBUG) #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); void LogChatMessage(const std::string& name, int id, const std::string& msg);
#define Biggest 30000 #define Biggest 30000
std::string Comp(std::string Data);
std::string DeComp(std::string Compressed); 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(); std::string GetPlatformAgnosticErrorString();
#define S_DSN SU_RAW
void LogChatMessage(const std::string& name, int id, const std::string& msg);
+30 -10
View File
@@ -1,13 +1,15 @@
#pragma once #pragma once
#include "Environment.h"
// ======================= UNIX ======================== // ======================= UNIX ========================
#ifdef __unix #ifdef BEAMMP_LINUX
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
using SOCKET = int; using SOCKET = int;
using DWORD = unsigned long; using DWORD = unsigned long;
using PDWORD = unsigned long*; using PDWORD = unsigned long*;
@@ -19,20 +21,38 @@ inline void CloseSocketProper(int TheSocket) {
} }
#endif // unix #endif // unix
// ======================= WIN32 ======================= // ======================= APPLE ========================
#ifdef WIN32 #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 <conio.h>
#include <winsock2.h> #include <winsock2.h>
inline void CloseSocketProper(SOCKET TheSocket) { inline void CloseSocketProper(SOCKET TheSocket) {
shutdown(TheSocket, 2); // 2 == SD_BOTH shutdown(TheSocket, 2); // 2 == SD_BOTH
closesocket(TheSocket); closesocket(TheSocket);
} }
#endif // WIN32 #endif // WIN32
// ======================= OTHER ======================= #ifdef INVALID_SOCKET
static inline constexpr int BEAMMP_INVALID_SOCKET = INVALID_SOCKET;
#if !defined(WIN32) && !defined(__unix) #else
#error "OS not supported" static inline constexpr int BEAMMP_INVALID_SOCKET = -1;
#endif #endif
+114
View File
@@ -0,0 +1,114 @@
// 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)
}
+4 -4
View File
@@ -55,18 +55,18 @@ inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const ch
} }
} }
#define Assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond)) #define beammp_assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond))
#define AssertNotReachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false) #define beammp_assert_not_reachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false)
#else #else
// In release build, these macros turn into NOPs. The compiler will optimize these out. // In release build, these macros turn into NOPs. The compiler will optimize these out.
#define Assert(cond) \ #define beammp_assert(cond) \
do { \ do { \
bool result = (cond); \ bool result = (cond); \
if (!result) { \ if (!result) { \
Sentry.LogAssert(#cond, _file_basename, _line, __func__); \ Sentry.LogAssert(#cond, _file_basename, _line, __func__); \
} \ } \
} while (false) } while (false)
#define AssertNotReachable() \ #define beammp_assert_not_reachable() \
do { \ do { \
Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \ Sentry.LogAssert("code is unreachable", _file_basename, _line, __func__); \
} while (false) } while (false)
+17
View File
@@ -0,0 +1,17 @@
#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
+38 -1
View File
@@ -1,13 +1,50 @@
#pragma once #pragma once
#include <Common.h>
#include <IThreaded.h>
#include <filesystem>
#include <string> #include <string>
#include <unordered_map> #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 { namespace Http {
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); std::string GET(const std::string& 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::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, 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 { namespace Status {
std::string ToString(int code); std::string ToString(int code);
} }
const std::string ErrorString = "-1"; 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;
};
}
} }
+5
View File
@@ -8,6 +8,11 @@ public:
IThreaded() IThreaded()
// invokes operator() on this object // invokes operator() on this object
: mThread() { } : mThread() { }
~IThreaded() noexcept {
if (mThread.joinable()) {
mThread.join();
}
}
virtual void Start() final { virtual void Start() final {
mThread = std::thread([this] { (*this)(); }); mThread = std::thread([this] { (*this)(); });
+48
View File
@@ -0,0 +1,48 @@
#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);
}
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void SetupSignalHandlers();
+12 -2
View File
@@ -3,10 +3,16 @@
#include "Common.h" #include "Common.h"
#include <atomic> #include <atomic>
#include <filesystem>
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
#include <toml.hpp> // header-only version of TOML++
namespace fs = std::filesystem;
class TConfig { class TConfig {
public: public:
explicit TConfig(); explicit TConfig(const std::string& ConfigFileName);
[[nodiscard]] bool Failed() const { return mFailed; } [[nodiscard]] bool Failed() const { return mFailed; }
@@ -16,8 +22,12 @@ private:
void CreateConfigFile(std::string_view name); void CreateConfigFile(std::string_view name);
void ParseFromFile(std::string_view name); void ParseFromFile(std::string_view name);
void PrintDebug(); 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(); void ParseOldFormat();
bool IsDefault();
bool mFailed { false }; bool mFailed { false };
std::string mConfigFileName;
}; };
+50 -3
View File
@@ -1,9 +1,16 @@
#pragma once #pragma once
#include "commandline/commandline.h" #include "Cryptography.h"
#include "TLuaFile.h" #include "commandline.h"
#include <atomic> #include <atomic>
#include <fstream> #include <fstream>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
class TLuaEngine;
class TConsole { class TConsole {
public: public:
@@ -12,8 +19,48 @@ public:
void Write(const std::string& str); void Write(const std::string& str);
void WriteRaw(const std::string& str); void WriteRaw(const std::string& str);
void InitializeLuaConsole(TLuaEngine& Engine); void InitializeLuaConsole(TLuaEngine& Engine);
void BackupOldLog();
void StartLoggingToFile();
Commandline& Internal() { return mCommandline; }
private: private:
std::unique_ptr<TLuaFile> mLuaConsole { nullptr }; 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; 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;
}; };
+244 -23
View File
@@ -1,41 +1,262 @@
#pragma once #pragma once
#include "Common.h" #include "TNetwork.h"
#include "IThreaded.h"
#include "TLuaFile.h"
#include "TServer.h" #include "TServer.h"
#include <optional> #include <any>
#include <condition_variable>
#include <filesystem>
#include <initializer_list>
#include <list>
#include <lua.hpp> #include <lua.hpp>
#include <memory> #include <memory>
#include <mutex>
#include <queue>
#include <random>
#include <set> #include <set>
#include <toml.hpp>
#include <unordered_map> #include <unordered_map>
#include <vector>
class TLuaEngine : public IThreaded { #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: public:
explicit TLuaEngine(TServer& Server, TNetwork& Network); TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown);
using TSetOfLuaFile = std::set<std::unique_ptr<TLuaFile>>; 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; void operator()() override;
[[nodiscard]] const TSetOfLuaFile& LuaFiles() const { return mLuaFiles; } TNetwork& Network() { return *mNetwork; }
[[nodiscard]] TServer& Server() { return mServer; } TServer& Server() { return *mServer; }
[[nodiscard]] const TServer& Server() const { return mServer; }
[[nodiscard]] TNetwork& Network() { return mNetwork; }
[[nodiscard]] const TNetwork& Network() const { return mNetwork; }
std::optional<std::reference_wrapper<TLuaFile>> GetScript(lua_State* L); 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);
static std::unordered_map<std::string, lua_State*> mGlobals;
private: private:
void FolderList(const std::string& Path, bool HotSwap); void CollectAndInitPlugins();
void RegisterFiles(const fs::path& Path, bool HotSwap); void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
bool IsNewFile(const std::string& Path); void FindAndParseConfig(const fs::path& Folder, TLuaPluginConfig& Config);
size_t CalculateMemoryUsage();
TNetwork& mNetwork; class StateThreadData : IThreaded {
TServer& mServer; public:
std::string mPath; StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine);
bool mShutdown { false }; StateThreadData(const StateThreadData&) = delete;
TSetOfLuaFile mLuaFiles; ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
std::mutex mListMutex; [[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);
-63
View File
@@ -1,63 +0,0 @@
#ifndef TLUAFILE_H
#define TLUAFILE_H
#include <any>
#include <filesystem>
#include <lua.hpp>
#include <mutex>
#include <set>
#include <string>
#include <vector>
namespace fs = std::filesystem;
struct TLuaArg {
std::vector<std::any> args;
void PushArgs(lua_State* State);
};
class TLuaEngine;
class TLuaFile {
public:
void RegisterEvent(const std::string& Event, const std::string& FunctionName);
void UnRegisterEvent(const std::string& Event);
void SetLastWrite(fs::file_time_type time);
bool IsRegistered(const std::string& Event);
void SetPluginName(const std::string& Name);
void Execute(const std::string& Command);
void SetFileName(const std::string& Name);
fs::file_time_type GetLastWrite();
lua_State* GetState();
std::string GetOrigin();
std::mutex Lock;
void Reload();
void Init(const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote);
explicit TLuaFile(TLuaEngine& Engine, bool Console = false);
~TLuaFile();
void SetStopThread(bool StopThread) { mStopThread = StopThread; }
TLuaEngine& Engine() { return mEngine; }
std::string GetPluginPath() const;
std::string GetPluginName() const;
std::string GetFileName() const;
const lua_State* GetState() const;
bool GetStopThread() const { return mStopThread; }
const TLuaEngine& Engine() const { return mEngine; }
std::string GetRegistered(const std::string& Event) const;
private:
TLuaEngine& mEngine;
std::set<std::pair<std::string, std::string>> mRegisteredEvents;
lua_State* mLuaState { nullptr };
fs::file_time_type mLastWrote;
std::string mPluginName {};
std::string mFileName {};
bool mStopThread = false;
bool mConsole = false;
void Load();
std::mutex mInitMutex;
};
std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
#endif // TLUAFILE_H
+19
View File
@@ -0,0 +1,19 @@
#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;
};
+25
View File
@@ -0,0 +1,25 @@
#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;
};
+5 -1
View File
@@ -2,6 +2,7 @@
#include "IThreaded.h" #include "IThreaded.h"
#include "RWMutex.h" #include "RWMutex.h"
#include "TScopedTimer.h"
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -15,7 +16,7 @@ class TServer final {
public: public:
using TClientSet = std::unordered_set<std::shared_ptr<TClient>>; using TClientSet = std::unordered_set<std::shared_ptr<TClient>>;
TServer(int argc, char** argv); TServer(const std::vector<std::string_view>& Arguments);
void InsertClient(const std::shared_ptr<TClient>& Ptr); void InsertClient(const std::shared_ptr<TClient>& Ptr);
std::weak_ptr<TClient> InsertNewClient(); std::weak_ptr<TClient> InsertNewClient();
@@ -27,6 +28,9 @@ public:
static void GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network); 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); static void HandleEvent(TClient& c, const std::string& Data);
RWMutex& GetClientMutex() const { return mClientsMutex; } RWMutex& GetClientMutex() const { return mClientsMutex; }
const TScopedTimer UptimeTimer;
private: private:
TClientSet mClients; TClientSet mClients;
mutable RWMutex mClientsMutex; mutable RWMutex mClientsMutex;
+7 -1
View File
@@ -1,10 +1,13 @@
#pragma once #pragma once
#include <nlohmann/json.hpp>
#include <string> #include <string>
using json = nlohmann::json;
class TVehicleData final { class TVehicleData final {
public: public:
TVehicleData(int ID, std::string Data); TVehicleData(int ID, const std::string& Data);
~TVehicleData(); ~TVehicleData();
// We cannot delete this, since vector needs to be able to copy when it resizes. // We cannot delete this, since vector needs to be able to copy when it resizes.
// Deleting this causes some wacky template errors which are hard to decipher, // Deleting this causes some wacky template errors which are hard to decipher,
@@ -19,9 +22,12 @@ public:
bool operator==(const TVehicleData& v) const { return mID == v.mID; } bool operator==(const TVehicleData& v) const { return mID == v.mID; }
const json& Json() const;
private: private:
int mID { -1 }; int mID { -1 };
std::string mData; std::string mData;
json mJson;
}; };
// TODO: unused now, remove? // TODO: unused now, remove?
Submodule include/toml11 deleted from 647381020e
+135
View File
@@ -0,0 +1,135 @@
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"
Submodule rapidjson deleted from 13dfc96c9c
+94
View File
@@ -0,0 +1,94 @@
#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.");
}
}
+20 -3
View File
@@ -1,9 +1,9 @@
#include "Client.h" #include "Client.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "TServer.h"
#include <memory> #include <memory>
#include <optional>
// FIXME: add debug prints
void TClient::DeleteCar(int Ident) { void TClient::DeleteCar(int Ident) {
std::unique_lock lock(mVehicleDataMutex); std::unique_lock lock(mVehicleDataMutex);
@@ -13,7 +13,7 @@ void TClient::DeleteCar(int Ident) {
if (iter != mVehicleData.end()) { if (iter != mVehicleData.end()) {
mVehicleData.erase(iter); mVehicleData.erase(iter);
} else { } else {
debug("tried to erase a vehicle that doesn't exist (not an error)"); beammp_debug("tried to erase a vehicle that doesn't exist (not an error)");
} }
} }
@@ -25,6 +25,7 @@ void TClient::ClearCars() {
int TClient::GetOpenCarID() const { int TClient::GetOpenCarID() const {
int OpenID = 0; int OpenID = 0;
bool found; bool found;
std::unique_lock lock(mVehicleDataMutex);
do { do {
found = true; found = true;
for (auto& v : mVehicleData) { for (auto& v : mVehicleData) {
@@ -99,3 +100,19 @@ int TClient::SecondsSinceLastPing() {
.count(); .count();
return int(seconds); 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;
}
+102 -78
View File
@@ -8,12 +8,11 @@
#include <regex> #include <regex>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include <zlib.h>
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
std::unique_ptr<TConsole> Application::mConsole = std::make_unique<TConsole>(); Application::TSettings Application::Settings = {};
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
std::unique_lock Lock(mShutdownHandlersMutex); std::unique_lock Lock(mShutdownHandlersMutex);
@@ -23,11 +22,28 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
} }
void Application::GracefullyShutdown() { void Application::GracefullyShutdown() {
info("please wait while all subsystems are shutting down..."); static bool AlreadyShuttingDown = false;
std::unique_lock Lock(mShutdownHandlersMutex); static uint8_t ShutdownAttempts = 0;
for (auto& Handler : mShutdownHandlers) { if (AlreadyShuttingDown) {
Handler(); ++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() { std::string Application::ServerVersionString() {
@@ -45,7 +61,6 @@ std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
return Version; return Version;
} }
// FIXME: This should be used by operator< on Version
bool Application::IsOutdated(const Version& Current, const Version& Newest) { bool Application::IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) { if (Newest.major > Current.major) {
return true; return true;
@@ -58,71 +73,66 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) {
} }
} }
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() { void Application::CheckForUpdates() {
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting);
static bool FirstTime = true;
// checks current version against latest version // checks current version against latest version
std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" };
auto Response = Http::GET(GetBackendHostname(), 443, "/v/s"); for (const auto& url : GetBackendUrlsInOrder()) {
bool Matches = std::regex_match(Response, VersionRegex); auto Response = Http::GET(GetBackendUrlsInOrder().at(0), 443, "/v/s");
if (Matches) { bool Matches = std::regex_match(Response, VersionRegex);
auto MyVersion = ServerVersion(); if (Matches) {
auto RemoteVersion = Version(VersionStrToInts(Response)); auto MyVersion = ServerVersion();
if (IsOutdated(MyVersion, RemoteVersion)) { auto RemoteVersion = Version(VersionStrToInts(Response));
std::string RealVersionString = RemoteVersion.AsString(); if (IsOutdated(MyVersion, RemoteVersion)) {
warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For info on how to update your server, visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET)); 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 { } else {
info("Server up-to-date!"); 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);
}
} }
} else {
warn("Unable to fetch version from backend.");
trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
} }
} if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) {
if (FirstTime) {
std::string Comp(std::string Data) { beammp_warn("Unable to fetch version info from backend.");
std::array<char, Biggest> C {}; }
// obsolete }
C.fill(0); FirstTime = false;
z_stream defstream;
defstream.zalloc = Z_NULL;
defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)Data.length();
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 TO = defstream.total_out;
std::string Ret(TO, 0);
std::copy_n(C.begin(), TO, Ret.begin());
return Ret;
}
std::string DeComp(std::string 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 TO = infstream.total_out;
std::string Ret(TO, 0);
std::copy_n(C.begin(), TO, Ret.begin());
return Ret;
} }
// thread name stuff // thread name stuff
@@ -143,6 +153,18 @@ std::string ThreadName(bool DebugModeOverride) {
} }
void RegisterThread(const std::string& str) { 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); auto Lock = std::unique_lock(ThreadNameMapMutex);
threadNameMap[std::this_thread::get_id()] = str; threadNameMap[std::this_thread::get_id()] = str;
} }
@@ -157,24 +179,26 @@ Version::Version(const std::array<uint8_t, 3>& v)
} }
std::string Version::AsString() { std::string Version::AsString() {
std::stringstream ss {}; return fmt::format("{:d}.{:d}.{:d}", major, minor, patch);
ss << int(major) << "." << int(minor) << "." << int(patch);
return ss.str();
} }
void LogChatMessage(const std::string& name, int id, const std::string& msg) { void LogChatMessage(const std::string& name, int id, const std::string& msg) {
std::stringstream ss; if (Application::Settings.LogChat) {
ss << "[CHAT] "; std::stringstream ss;
if (id != -1) { ss << ThreadName();
ss << "(" << id << ") <" << name << ">"; ss << "[CHAT] ";
} else { if (id != -1) {
ss << name << ""; ss << "(" << id << ") <" << name << "> ";
} else {
ss << name << "";
}
ss << msg;
Application::Console().Write(ss.str());
} }
ss << msg;
} }
std::string GetPlatformAgnosticErrorString() { std::string GetPlatformAgnosticErrorString() {
#ifdef WIN32 #ifdef BEAMMP_WINDOWS
// This will provide us with the error code and an error message, all in one. // This will provide us with the error code and an error message, all in one.
int err; int err;
char msgbuf[256]; char msgbuf[256];
@@ -195,7 +219,7 @@ std::string GetPlatformAgnosticErrorString() {
} else { } else {
return std::to_string(GetLastError()); return std::to_string(GetLastError());
} }
#else // posix #elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
return std::strerror(errno); return std::strerror(errno);
#endif #endif
} }
+222 -129
View File
@@ -1,146 +1,59 @@
#include "Http.h" #include "Http.h"
#include "Client.h"
#include "Common.h" #include "Common.h"
#undef error #include "CustomAssert.h"
#include "LuaAPI.h"
#include "TNetwork.h"
#include "TResourceManager.h"
#include "TServer.h"
#include "httplib.h"
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <map> #include <map>
#include <nlohmann/json.hpp>
#include <random>
#include <stdexcept>
namespace beast = boost::beast; // from <boost/beast.hpp> // TODO: Add sentry error handling back
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
std::string GenericRequest(http::verb verb, const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) { using json = nlohmann::json;
try {
net::io_context io;
// The SSL context is required, and holds certificates std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
ssl::context ctx(ssl::context::tlsv13); httplib::SSLClient client(host, port);
client.enable_server_certificate_verification(false);
ctx.set_verify_mode(ssl::verify_none); client.set_address_family(AF_INET);
auto res = client.Get(target.c_str());
tcp::resolver resolver(io); if (res) {
beast::ssl_stream<beast::tcp_stream> stream(io, ctx);
decltype(resolver)::results_type results;
auto try_connect_with_protocol = [&](tcp protocol) {
try {
results = resolver.resolve(protocol, host, std::to_string(port));
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
boost::system::error_code ec { static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
// FIXME: we could throw and crash, if we like
// throw boost::system::system_error { ec };
//debug("POST " + host + target + " failed.");
return false;
}
beast::get_lowest_layer(stream).connect(results);
} catch (const boost::system::system_error&) {
return false;
}
return true;
};
//bool ok = try_connect_with_protocol(tcp::v6());
//if (!ok) {
//debug("IPv6 connect failed, trying IPv4");
bool ok = try_connect_with_protocol(tcp::v4());
if (!ok) {
Application::Console().Write("[ERROR] failed to resolve or connect in POST " + host + target);
return "-1";
}
//}
stream.handshake(ssl::stream_base::client);
http::request<http::string_body> req { verb, target, 11 /* http 1.1 */ };
req.set(http::field::host, host);
if (!body.empty()) {
req.set(http::field::content_type, ContentType); // "application/json"
// "application/x-www-form-urlencoded"
req.set(http::field::content_length, std::to_string(body.size()));
req.body() = body;
// info("body is " + body + " (" + req.body() + ")");
// info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast<std::string>(body.size()) + ")");
}
for (const auto& pair : fields) {
// info("setting " + pair.first + " to " + pair.second);
req.set(pair.first, pair.second);
}
std::unordered_map<std::string, std::string> request_data;
for (const auto& header : req.base()) {
// need to do explicit casts to convert string_view to string
// since string_view may not be null-terminated (and in fact isn't, here)
std::string KeyString(header.name_string());
std::string ValueString(header.value());
request_data[KeyString] = ValueString;
}
Sentry.SetContext("https-post-request-data", request_data);
std::stringstream oss;
oss << req;
beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5));
http::write(stream, req);
// used for reading
beast::flat_buffer buffer;
http::response<http::string_body> response;
http::read(stream, buffer, response);
std::unordered_map<std::string, std::string> response_data;
response_data["reponse-code"] = std::to_string(response.result_int());
if (status) { if (status) {
*status = response.result_int(); *status = res->status;
} }
for (const auto& header : response.base()) { return res->body;
// need to do explicit casts to convert string_view to string } else {
// since string_view may not be null-terminated (and in fact isn't, here)
std::string KeyString(header.name_string());
std::string ValueString(header.value());
response_data[KeyString] = ValueString;
}
Sentry.SetContext("https-post-response-data", response_data);
if (status) {
*status = response.base().result_int();
}
std::stringstream result;
result << response;
beast::error_code ec;
stream.shutdown(ec);
// IGNORING ec
// info(result.str());
std::string debug_response_str;
std::getline(result, debug_response_str);
//debug("POST " + host + target + ": " + debug_response_str);
return std::string(response.body());
} catch (const std::exception& e) {
Application::Console().Write(__func__ + std::string(": ") + e.what());
return Http::ErrorString; return Http::ErrorString;
} }
} }
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { 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) {
return GenericRequest(http::verb::get, host, port, target, {}, {}, {}, status); httplib::SSLClient client(host, port);
} client.set_read_timeout(std::chrono::seconds(10));
beammp_assert(client.is_valid());
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) { client.enable_server_certificate_verification(false);
return GenericRequest(http::verb::post, host, port, target, fields, body, ContentType, status); 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 // RFC 2616, RFC 7231
static std::map<size_t, const char*> Map = { static std::map<size_t, const char*> Map = {
{ -1, "Invalid Response Code" },
{ 100, "Continue" }, { 100, "Continue" },
{ 101, "Switching Protocols" }, { 101, "Switching Protocols" },
{ 102, "Processing" }, { 102, "Processing" },
@@ -203,12 +116,192 @@ static std::map<size_t, const char*> Map = {
{ 508, "Loop Detected" }, { 508, "Loop Detected" },
{ 510, "Not Extended" }, { 510, "Not Extended" },
{ 511, "Network Authentication Required" }, { 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" },
}; };
std::string Http::Status::ToString(int code) { static const char Magic[] = {
if (Map.find(code) != Map.end()) { 0x20, 0x2f, 0x5c, 0x5f,
return Map.at(code); 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 { } else {
return "Unassigned"; 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
@@ -0,0 +1,515 @@
#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
@@ -0,0 +1,62 @@
#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
}
+133 -56
View File
@@ -1,6 +1,4 @@
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT #include "Common.h"
#include <toml11/toml.hpp> // header-only version of TOML++
#include "TConfig.h" #include "TConfig.h"
#include <fstream> #include <fstream>
@@ -8,8 +6,7 @@
#include <istream> #include <istream>
#include <sstream> #include <sstream>
static const char* ConfigFileName = static_cast<const char*>("ServerConfig.toml"); // General
static constexpr std::string_view StrDebug = "Debug"; static constexpr std::string_view StrDebug = "Debug";
static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view StrPrivate = "Private";
static constexpr std::string_view StrPort = "Port"; static constexpr std::string_view StrPort = "Port";
@@ -20,24 +17,55 @@ static constexpr std::string_view StrName = "Name";
static constexpr std::string_view StrDescription = "Description"; static constexpr std::string_view StrDescription = "Description";
static constexpr std::string_view StrResourceFolder = "ResourceFolder"; static constexpr std::string_view StrResourceFolder = "ResourceFolder";
static constexpr std::string_view StrAuthKey = "AuthKey"; static constexpr std::string_view StrAuthKey = "AuthKey";
static constexpr std::string_view StrLogChat = "LogChat";
TConfig::TConfig() { // Misc
if (!fs::exists(ConfigFileName) || !fs::is_regular_file(ConfigFileName)) { static constexpr std::string_view StrSendErrors = "SendErrors";
info("No config file found! Generating one..."); static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
CreateConfigFile(ConfigFileName); 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 (!mFailed) {
if (fs::exists("Server.cfg")) { if (fs::exists("Server.cfg")) {
warn("An old \"Server.cfg\" file still exists. Please note that this is no longer used. Instead, \"" + std::string(ConfigFileName) + "\" is used. You can safely delete the \"Server.cfg\"."); 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(ConfigFileName); 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() { void TConfig::FlushToFile() {
auto data = toml::parse(ConfigFileName); auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
data["General"] = toml::table();
data["General"][StrAuthKey.data()] = Application::Settings.Key; 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"][StrDebug.data()] = Application::Settings.DebugModeEnabled;
data["General"][StrPrivate.data()] = Application::Settings.Private; data["General"][StrPrivate.data()] = Application::Settings.Private;
data["General"][StrPort.data()] = Application::Settings.Port; data["General"][StrPort.data()] = Application::Settings.Port;
@@ -47,7 +75,20 @@ void TConfig::FlushToFile() {
data["General"][StrMap.data()] = Application::Settings.MapName; data["General"][StrMap.data()] = Application::Settings.MapName;
data["General"][StrDescription.data()] = Application::Settings.ServerDesc; data["General"][StrDescription.data()] = Application::Settings.ServerDesc;
data["General"][StrResourceFolder.data()] = Application::Settings.Resource; data["General"][StrResourceFolder.data()] = Application::Settings.Resource;
std::ofstream Stream(ConfigFileName); // 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; Stream << data << std::flush;
} }
@@ -60,80 +101,116 @@ void TConfig::CreateConfigFile(std::string_view name) {
ParseOldFormat(); ParseOldFormat();
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
error("an error occurred and was ignored during config transfer: " + std::string(e.what())); beammp_error("an error occurred and was ignored during config transfer: " + std::string(e.what()));
} }
{ // create file context { // create file context
std::ofstream ofs(name.data()); std::ofstream ofs(name.data());
} }
auto data = toml::parse<toml::preserve_comments>(name.data()); FlushToFile();
data["General"] = toml::table(); size_t FileSize = fs::file_size(name);
data["General"][StrAuthKey.data()] = Application::Settings.Key; std::fstream ofs { std::string(name), std::ios::in | std::ios::out };
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;
std::ofstream ofs { std::string(name) };
if (ofs.good()) { 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" ofs << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\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" "# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< '\n'; << '\n'
ofs << data << '\n'; << Contents;
error("There was no \"" + std::string(ConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on."); 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; mFailed = true;
ofs.close();
} else { } else {
error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work."); 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; 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) { void TConfig::ParseFromFile(std::string_view name) {
try { try {
toml::value data = toml::parse<toml::preserve_comments>(name.data()); toml::value data = toml::parse<toml::preserve_comments>(name.data());
Application::Settings.DebugModeEnabled = data["General"][StrDebug.data()].as_boolean(); // GENERAL
Application::Settings.Private = data["General"][StrPrivate.data()].as_boolean(); TryReadValue(data, "General", StrDebug, Application::Settings.DebugModeEnabled);
Application::Settings.Port = data["General"][StrPort.data()].as_integer(); TryReadValue(data, "General", StrPrivate, Application::Settings.Private);
Application::Settings.MaxCars = data["General"][StrMaxCars.data()].as_integer(); TryReadValue(data, "General", StrPort, Application::Settings.Port);
Application::Settings.MaxPlayers = data["General"][StrMaxPlayers.data()].as_integer(); TryReadValue(data, "General", StrMaxCars, Application::Settings.MaxCars);
Application::Settings.MapName = data["General"][StrMap.data()].as_string(); TryReadValue(data, "General", StrMaxPlayers, Application::Settings.MaxPlayers);
Application::Settings.ServerName = data["General"][StrName.data()].as_string(); TryReadValue(data, "General", StrMap, Application::Settings.MapName);
Application::Settings.ServerDesc = data["General"][StrDescription.data()].as_string(); TryReadValue(data, "General", StrName, Application::Settings.ServerName);
Application::Settings.Resource = data["General"][StrResourceFolder.data()].as_string(); TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc);
Application::Settings.Key = data["General"][StrAuthKey.data()].as_string(); 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) { } catch (const std::exception& err) {
error("Error parsing config file value: " + std::string(err.what())); beammp_error("Error parsing config file value: " + std::string(err.what()));
mFailed = true; mFailed = true;
Application::SetSubsystemStatus("Config", Application::Status::Bad);
return; return;
} }
PrintDebug(); PrintDebug();
// Update in any case
FlushToFile();
// all good so far, let's check if there's a key // all good so far, let's check if there's a key
if (Application::Settings.Key.empty()) { if (Application::Settings.Key.empty()) {
error("No AuthKey specified in the \"" + std::string(ConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server."); 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; 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() { void TConfig::PrintDebug() {
debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false")); beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false"));
debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false")); beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false"));
debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port)); beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port));
debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars)); beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars));
debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers)); beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers));
debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\""); beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\"");
debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\""); beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\"");
debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\""); beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\"");
debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\""); 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! // special!
debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + ""); beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + "");
} }
void TConfig::ParseOldFormat() { void TConfig::ParseOldFormat() {
@@ -183,7 +260,7 @@ void TConfig::ParseOldFormat() {
} else if (Key == "AuthKey") { } else if (Key == "AuthKey") {
Application::Settings.Key = Value.substr(1, Value.size() - 3); Application::Settings.Key = Value.substr(1, Value.size() - 3);
} else { } else {
warn("unknown key in old auth file (ignored): " + Key); beammp_warn("unknown key in old auth file (ignored): " + Key);
} }
Str >> std::ws; Str >> std::ws;
} }
+609 -46
View File
@@ -2,77 +2,640 @@
#include "Common.h" #include "Common.h"
#include "Compat.h" #include "Compat.h"
#include "Client.h"
#include "CustomAssert.h"
#include "LuaAPI.h"
#include "TLuaEngine.h"
#include <ctime> #include <ctime>
#include <sstream> #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::string GetDate() {
std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
time_t tt = std::chrono::system_clock::to_time_t(now); time_t tt = std::chrono::system_clock::to_time_t(now);
tm local_tm {}; auto local_tm = std::localtime(&tt);
#ifdef WIN32 char buf[30];
localtime_s(&local_tm, &tt); std::string date;
#else // unix if (Application::Settings.DebugModeEnabled) {
localtime_r(&tt, &local_tm); std::strftime(buf, sizeof(buf), "[%d/%m/%y %T.", local_tm);
#endif // WIN32 date += buf;
std::stringstream date; auto seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
int S = local_tm.tm_sec; auto fraction = now - seconds;
int M = local_tm.tm_min; size_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(fraction).count();
int H = local_tm.tm_hour; char fracstr[5];
std::string Secs = (S > 9 ? std::to_string(S) : "0" + std::to_string(S)); std::sprintf(fracstr, "%03lu", ms);
std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M)); date += fracstr;
std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H)); date += "] ";
date } else {
<< "[" std::strftime(buf, sizeof(buf), "[%d/%m/%y %T] ", local_tm);
<< local_tm.tm_mday << "/" date += buf;
<< local_tm.tm_mon + 1 << "/"
<< local_tm.tm_year + 1900 << " "
<< Hour << ":"
<< Min << ":"
<< Secs
<< "] ";
/* TODO
if (Debug) {
date << ThreadName()
<< " ";
} }
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);
*/ */
return date.str(); }
}
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() { TConsole::TConsole() {
mCommandline.enable_history(); mCommandline.enable_history();
mCommandline.set_history_limit(20); mCommandline.set_history_limit(20);
mCommandline.set_prompt("> "); mCommandline.set_prompt("> ");
bool success = mCommandline.enable_write_to_file("Server.log"); BackupOldLog();
if (!success) {
error("unable to open file for writing: \"Server.log\"");
}
mCommandline.on_command = [this](Commandline& c) { mCommandline.on_command = [this](Commandline& c) {
auto cmd = c.get_command(); try {
mCommandline.write("> " + cmd); auto TrimmedCmd = c.get_command();
if (cmd == "exit") { TrimmedCmd = TrimString(TrimmedCmd);
info("gracefully shutting down"); auto [cmd, args] = ParseCommand(TrimmedCmd);
Application::GracefullyShutdown(); mCommandline.write(mCommandline.prompt() + TrimmedCmd);
} else if (cmd == "clear" || cmd == "cls") { if (mIsLuaConsole) {
// TODO: clear screen if (!mLuaEngine) {
} else { beammp_info("Lua not started yet, please try again in a second");
if (mLuaConsole) { } else if (!cmd.empty() && cmd.at(0) == ':') {
mLuaConsole->Execute(cmd); 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 { } else {
error("Lua subsystem not yet initialized, please wait a few seconds and try again"); 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) { void TConsole::Write(const std::string& str) {
auto ToWrite = GetDate() + str; auto ToWrite = GetDate() + str;
mCommandline.write(ToWrite); mCommandline.write(ToWrite);
// TODO write to logfile, too
}
void TConsole::InitializeLuaConsole(TLuaEngine& Engine) {
mLuaConsole = std::make_unique<TLuaFile>(Engine, true);
} }
void TConsole::WriteRaw(const std::string& str) { void TConsole::WriteRaw(const std::string& str) {
mCommandline.write(str); mCommandline.write(str);
} }
void TConsole::InitializeLuaConsole(TLuaEngine& Engine) {
mLuaEngine = &Engine;
}
+84 -31
View File
@@ -3,8 +3,11 @@
#include "Client.h" #include "Client.h"
#include "Http.h" #include "Http.h"
//#include "SocketIO.h" //#include "SocketIO.h"
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
#include <sstream> #include <sstream>
void THeartbeatThread::operator()() { void THeartbeatThread::operator()() {
RegisterThread("Heartbeat"); RegisterThread("Heartbeat");
std::string Body; std::string Body;
@@ -15,7 +18,9 @@ void THeartbeatThread::operator()() {
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now(); static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
bool isAuth = false; bool isAuth = false;
size_t UpdateReminderCounter = 0;
while (!mShutdown) { while (!mShutdown) {
++UpdateReminderCounter;
Body = GenerateCall(); Body = GenerateCall();
// a hot-change occurs when a setting has changed, to update the backend of that change. // a hot-change occurs when a setting has changed, to update the backend of that change.
auto Now = std::chrono::high_resolution_clock::now(); auto Now = std::chrono::high_resolution_clock::now();
@@ -26,62 +31,109 @@ void THeartbeatThread::operator()() {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue; continue;
} }
debug("heartbeat (after " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()) + "s)"); beammp_debug("heartbeat (after " + std::to_string(std::chrono::duration_cast<std::chrono::seconds>(TimePassed).count()) + "s)");
Last = Body; Last = Body;
LastNormalUpdateTime = Now; LastNormalUpdateTime = Now;
if (!Application::Settings.CustomIP.empty()) if (!Application::Settings.CustomIP.empty()) {
Body += "&ip=" + Application::Settings.CustomIP; Body += "&ip=" + Application::Settings.CustomIP;
}
Body += "&pps=" + Application::PPS(); Body += "&pps=" + Application::PPS();
auto SentryReportError = [&](const std::string& transaction, int status) { beammp_trace("heartbeat body: '" + Body + "'");
if (status < 0) {
status = 0;
}
auto SentryReportError = [&](const std::string& transaction, int status) {
auto Lock = Sentry.CreateExclusiveContext(); auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("heartbeat", Sentry.SetContext("heartbeat",
{ { "response-body", T }, { { "response-body", T },
{ "request-body", Body } }); { "request-body", Body } });
Sentry.SetTransaction(transaction); Sentry.SetTransaction(transaction);
trace("sending log to sentry: " + std::to_string(status) + " for " + 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) + ")"); Sentry.Log(SentryLevel::Error, "default", Http::Status::ToString(status) + " (" + std::to_string(status) + ")");
}; };
auto Target = "/heartbeat"; auto Target = "/heartbeat";
unsigned int ResponseCode = 0; unsigned int ResponseCode = 0;
T = Http::POST(Application::GetBackendHostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode);
if (T.substr(0, 2) != "20" || ResponseCode != 200) { rapidjson::Document Doc;
trace("got " + T + " from backend"); bool Ok = false;
SentryReportError(Application::GetBackendHostname() + Target, ResponseCode); for (const auto& Url : Application::GetBackendUrlsInOrder()) {
std::this_thread::sleep_for(std::chrono::milliseconds(500)); T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
T = Http::POST(Application::GetBackup1Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode); beammp_trace(T);
if (T.substr(0, 2) != "20" || ResponseCode != 200) { Doc.Parse(T.data(), T.size());
SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode); if (Doc.HasParseError() || !Doc.IsObject()) {
std::this_thread::sleep_for(std::chrono::milliseconds(500)); if (!Application::Settings.Private) {
T = Http::POST(Application::GetBackup2Hostname(), 443, Target, {}, Body, "application/x-www-form-urlencoded", &ResponseCode); beammp_error("Backend response failed to parse as valid json");
if (T.substr(0, 2) != "20" || ResponseCode != 200) { beammp_trace("Response was: `" + T + "`");
warn("Backend system refused server! Server will not show in the public server list.");
isAuth = false;
SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode);
} }
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 (!isAuth) { if (Ok && !isAuth && !Application::Settings.Private) {
if (T == "2000") { if (Status == "2000") {
info(("Authenticated!")); beammp_info(("Authenticated! " + Message));
isAuth = true; isAuth = true;
} else if (T == "200") { } else if (Status == "200") {
info(("Resumed authenticated session!")); beammp_info(("Resumed authenticated session! " + Message));
isAuth = true; 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) {
//SocketIO::Get().SetAuthenticated(isAuth); Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
Application::CheckForUpdates();
}
} }
} }
@@ -107,13 +159,14 @@ std::string THeartbeatThread::GenerateCall() {
THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server) THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server)
: mResourceManager(ResourceManager) : mResourceManager(ResourceManager)
, mServer(Server) { , mServer(Server) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Starting);
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
debug("shutting down Heartbeat");
mShutdown = true; mShutdown = true;
mThread.join(); mThread.join();
debug("shut down Heartbeat");
} }
Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown);
}); });
Start(); Start();
} }
+1011 -77
View File
File diff suppressed because it is too large Load Diff
-1107
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
#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);
}
}
}
+169 -111
View File
@@ -1,5 +1,7 @@
#include "TNetwork.h" #include "TNetwork.h"
#include "Client.h" #include "Client.h"
#include "LuaAPI.h"
#include "TLuaEngine.h"
#include <CustomAssert.h> #include <CustomAssert.h>
#include <Http.h> #include <Http.h>
#include <array> #include <array>
@@ -9,8 +11,10 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
: mServer(Server) : mServer(Server)
, mPPSMonitor(PPSMonitor) , mPPSMonitor(PPSMonitor)
, mResourceManager(ResourceManager) { , mResourceManager(ResourceManager) {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Starting);
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting);
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
debug("Kicking all players due to shutdown"); beammp_debug("Kicking all players due to shutdown");
Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool { Server.ForEachClient([&](std::weak_ptr<TClient> client) -> bool {
if (!client.expired()) { if (!client.expired()) {
ClientKick(*client.lock(), "Server shutdown"); ClientKick(*client.lock(), "Server shutdown");
@@ -19,20 +23,20 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
}); });
}); });
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
if (mUDPThread.joinable()) { if (mUDPThread.joinable()) {
debug("shutting down TCPServer");
mShutdown = true; mShutdown = true;
mUDPThread.detach(); mUDPThread.detach();
debug("shut down TCPServer");
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown);
}); });
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown);
if (mTCPThread.joinable()) { if (mTCPThread.joinable()) {
debug("shutting down TCPServer");
mShutdown = true; mShutdown = true;
mTCPThread.detach(); mTCPThread.detach();
debug("shut down TCPServer");
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown);
}); });
mTCPThread = std::thread(&TNetwork::TCPServerMain, this); mTCPThread = std::thread(&TNetwork::TCPServerMain, this);
mUDPThread = std::thread(&TNetwork::UDPServerMain, this); mUDPThread = std::thread(&TNetwork::UDPServerMain, this);
@@ -40,34 +44,34 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
void TNetwork::UDPServerMain() { void TNetwork::UDPServerMain() {
RegisterThread("UDPServer"); RegisterThread("UDPServer");
#ifdef WIN32 #if defined(BEAMMP_WINDOWS)
WSADATA data; WSADATA data;
if (WSAStartup(514, &data)) { if (WSAStartup(514, &data)) {
error(("Can't start Winsock!")); beammp_error(("Can't start Winsock!"));
//return; // return;
} }
#endif // WIN32 #endif // WINDOWS
mUDPSock = socket(AF_INET, SOCK_DGRAM, 0); mUDPSock = socket(AF_INET, SOCK_DGRAM, 0);
// Create a server hint structure for the server // Create a server hint structure for the server
sockaddr_in serverAddr {}; sockaddr_in serverAddr {};
serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local serverAddr.sin_addr.s_addr = INADDR_ANY; // Any Local
serverAddr.sin_family = AF_INET; // Address format is IPv4 serverAddr.sin_family = AF_INET; // Address format is IPv4
serverAddr.sin_port = htons(uint16_t(Application::Settings.Port)); // Convert from little to big endian 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 // Try and bind the socket to the IP and port
if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) {
error("bind() failed: " + GetPlatformAgnosticErrorString()); beammp_error("bind() failed: " + GetPlatformAgnosticErrorString());
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
exit(-1); exit(-1); // TODO: Wtf.
//return; // return;
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ") beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ")
+ std::to_string(Application::Settings.MaxPlayers) + (" Clients")); + std::to_string(Application::Settings.MaxPlayers) + (" Clients"));
while (!mShutdown) { while (!mShutdown) {
try { try {
sockaddr_in client {}; sockaddr_in client {};
std::string Data = UDPRcvFromClient(client); //Receives any data from Socket std::string Data = UDPRcvFromClient(client); // Receives any data from Socket
size_t Pos = Data.find(':'); size_t Pos = Data.find(':');
if (Data.empty() || Pos > 2) if (Data.empty() || Pos > 2)
continue; continue;
@@ -94,81 +98,102 @@ void TNetwork::UDPServerMain() {
return true; return true;
}); });
} catch (const std::exception& e) { } catch (const std::exception& e) {
error(("fatal: ") + std::string(e.what())); beammp_error(("fatal: ") + std::string(e.what()));
} }
} }
} }
void TNetwork::TCPServerMain() { void TNetwork::TCPServerMain() {
RegisterThread("TCPServer"); RegisterThread("TCPServer");
#ifdef WIN32 #if defined(BEAMMP_WINDOWS)
WSADATA wsaData; WSADATA wsaData;
if (WSAStartup(514, &wsaData)) { if (WSAStartup(514, &wsaData)) {
error("Can't start Winsock!"); beammp_error("Can't start Winsock! Shutting down");
return; Application::GracefullyShutdown();
} }
#endif // WIN32 #endif // WINDOWS
TConnection client {}; TConnection client {};
SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKET Listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int optval = 1; if (Listener == BEAMMP_INVALID_SOCKET) {
#ifdef WIN32 beammp_error("Failed to create socket: " + GetPlatformAgnosticErrorString()
const char* optval_ptr = reinterpret_cast<const char*>(&optval); + ". This is a fatal error, as a socket is needed for the server to operate. Shutting down.");
#else Application::GracefullyShutdown();
void* optval_ptr = reinterpret_cast<void*>(&optval); }
#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 #endif
setsockopt(Listener, SOL_SOCKET, SO_REUSEADDR, optval_ptr, sizeof(optval)); // not a fatal error
// TODO: check optval or return value idk 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 {}; sockaddr_in addr {};
addr.sin_addr.s_addr = INADDR_ANY; addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_port = htons(uint16_t(Application::Settings.Port)); addr.sin_port = htons(uint16_t(Application::Settings.Port));
if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) { if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) < 0) {
error("bind() failed: " + GetPlatformAgnosticErrorString()); beammp_error("bind() failed, the server cannot operate and will shut down now. "
std::this_thread::sleep_for(std::chrono::seconds(5)); "Error: "
exit(-1); + GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
} }
if (Listener == -1) { if (listen(Listener, SOMAXCONN) < 0) {
error("Invalid listening socket"); beammp_error("listen() failed, which is needed for the server to operate. "
return; "Shutting down. Error: "
+ GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
} }
if (listen(Listener, SOMAXCONN)) { Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
error("listen() failed: " + GetPlatformAgnosticErrorString()); beammp_info("Vehicle event network online");
// FIXME leak Listener
return;
}
info(("Vehicle event network online"));
do { do {
try { try {
if (mShutdown) { if (mShutdown) {
debug("shutdown during TCP wait for accept loop"); beammp_debug("shutdown during TCP wait for accept loop");
break; break;
} }
client.SockAddrLen = sizeof(client.SockAddr); client.SockAddrLen = sizeof(client.SockAddr);
client.Socket = accept(Listener, &client.SockAddr, &client.SockAddrLen); client.Socket = accept(Listener, &client.SockAddr, &client.SockAddrLen);
if (client.Socket == -1) { if (client.Socket == -1) {
warn(("Got an invalid client socket on connect! Skipping...")); beammp_warn(("Got an invalid client socket on connect! Skipping..."));
continue; 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); std::thread ID(&TNetwork::Identify, this, client);
ID.detach(); // TODO: Add to a queue and attempt to join periodically ID.detach(); // TODO: Add to a queue and attempt to join periodically
} catch (const std::exception& e) { } catch (const std::exception& e) {
error(("fatal: ") + std::string(e.what())); beammp_error("fatal: " + std::string(e.what()));
} }
} while (client.Socket); } while (client.Socket != BEAMMP_INVALID_SOCKET);
debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__)); beammp_debug("all ok, arrived at " + std::string(__func__) + ":" + std::to_string(__LINE__));
CloseSocketProper(client.Socket); CloseSocketProper(client.Socket);
#ifdef WIN32 #ifdef BEAMMP_WINDOWS
CloseSocketProper(client); CloseSocketProper(client.Socket);
WSACleanup(); WSACleanup();
#endif // WIN32 #endif // WINDOWS
} }
#undef GetObject //Fixes Windows #undef GetObject // Fixes Windows
#include "Json.h" #include "Json.h"
namespace json = rapidjson;
void TNetwork::Identify(const TConnection& client) { void TNetwork::Identify(const TConnection& client) {
RegisterThreadAuto(); RegisterThreadAuto();
@@ -181,6 +206,14 @@ void TNetwork::Identify(const TConnection& client) {
Authentication(client); Authentication(client);
} else if (Code == 'D') { } else if (Code == 'D') {
HandleDownload(client.Socket); 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 { } else {
CloseSocketProper(client.Socket); CloseSocketProper(client.Socket);
} }
@@ -207,13 +240,14 @@ void TNetwork::HandleDownload(SOCKET TCPSock) {
void TNetwork::Authentication(const TConnection& ClientConnection) { void TNetwork::Authentication(const TConnection& ClientConnection) {
auto Client = CreateClient(ClientConnection.Socket); auto Client = CreateClient(ClientConnection.Socket);
char AddrBuf[30]; char AddrBuf[64];
inet_ntoa(reinterpret_cast<const struct sockaddr_in*>(&ClientConnection.SockAddr)->sin_addr); // TODO: IPv6 would need this to be changed
auto str = inet_ntop(AF_INET, static_cast<const void*>(reinterpret_cast<const struct sockaddr_in*>(&ClientConnection.SockAddr)), AddrBuf, sizeof(struct sockaddr_in)); 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); Client->SetIdentifier("ip", str);
std::string Rc; std::string Rc; // TODO: figure out why this is not default constructed
info("Identifying new ClientConnection..."); beammp_info("Identifying new ClientConnection...");
Rc = TCPRcv(*Client); Rc = TCPRcv(*Client);
@@ -243,10 +277,10 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
auto Target = "/pkToUser"; auto Target = "/pkToUser";
unsigned int ResponseCode = 0; unsigned int ResponseCode = 0;
if (!Rc.empty()) { if (!Rc.empty()) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, {}, RequestString, "application/json", &ResponseCode); Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, Target, RequestString, "application/json", &ResponseCode);
} }
json::Document AuthResponse; rapidjson::Document AuthResponse;
AuthResponse.Parse(Rc.c_str()); AuthResponse.Parse(Rc.c_str());
if (Rc == Http::ErrorString || AuthResponse.HasParseError()) { if (Rc == Http::ErrorString || AuthResponse.HasParseError()) {
ClientKick(*Client, "Invalid key! Please restart your game."); ClientKick(*Client, "Invalid key! Please restart your game.");
@@ -263,7 +297,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
Sentry.Log(SentryLevel::Info, "default", "backend returned 0 instead of json (" + std::to_string(ResponseCode) + ")"); Sentry.Log(SentryLevel::Info, "default", "backend returned 0 instead of json (" + std::to_string(ResponseCode) + ")");
} else { // Rc != "0" } else { // Rc != "0"
ClientKick(*Client, "Backend returned invalid auth response format."); ClientKick(*Client, "Backend returned invalid auth response format.");
error("Backend returned invalid auth response format. This should never happen."); beammp_error("Backend returned invalid auth response format. This should never happen.");
auto Lock = Sentry.CreateExclusiveContext(); auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("auth", Sentry.SetContext("auth",
{ { "response-body", Rc }, { { "response-body", Rc },
@@ -290,7 +324,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
return; return;
} }
debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles()); beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles());
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool { mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
std::shared_ptr<TClient> Cl; std::shared_ptr<TClient> Cl;
{ {
@@ -309,18 +343,32 @@ void TNetwork::Authentication(const TConnection& ClientConnection) {
return true; return true;
}); });
auto arg = std::make_unique<TLuaArg>(TLuaArg { { Client->GetName(), Client->GetRoles(), Client->IsGuest() } }); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest());
std::any Res = TriggerLuaEvent("onPlayerAuth", false, nullptr, std::move(arg), true); TLuaEngine::WaitForAll(Futures);
if (Res.type() == typeid(int) && std::any_cast<int>(Res)) { 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!"); ClientKick(*Client, "you are not allowed on the server!");
return; return;
} else if (Res.type() == typeid(std::string)) { } else if (NotAllowedWithReason) {
ClientKick(*Client, std::any_cast<std::string>(Res)); ClientKick(*Client, Reason);
return; return;
} }
if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) { if (mServer.ClientCount() < size_t(Application::Settings.MaxPlayers)) {
info("Identification success"); beammp_info("Identification success");
mServer.InsertClient(Client); mServer.InsertClient(Client);
TCPClient(Client); TCPClient(Client);
} else } else
@@ -353,18 +401,18 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
Sent = 0; Sent = 0;
Size += 4; Size += 4;
do { do {
#ifdef WIN32 #if defined(BEAMMP_WINDOWS)
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, 0); int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, 0);
#else //WIN32 #elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, MSG_NOSIGNAL); int32_t Temp = send(c.GetTCPSock(), &Send[Sent], Size - Sent, MSG_NOSIGNAL);
#endif //WIN32 #endif
if (Temp == 0) { if (Temp == 0) {
debug("send() == 0: " + GetPlatformAgnosticErrorString()); beammp_debug("send() == 0: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
return false; return false;
} else if (Temp < 0) { } else if (Temp < 0) {
debug("send() < 0: " + GetPlatformAgnosticErrorString()); //TODO fix it was spamming yet everyone stayed on the server beammp_debug("send() < 0: " + GetPlatformAgnosticErrorString()); // TODO fix it was spamming yet everyone stayed on the server
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
CloseSocketProper(c.GetTCPSock()); CloseSocketProper(c.GetTCPSock());
@@ -378,12 +426,12 @@ bool TNetwork::TCPSend(TClient& c, const std::string& Data, bool IsSync) {
bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) { bool TNetwork::CheckBytes(TClient& c, int32_t BytesRcv) {
if (BytesRcv == 0) { if (BytesRcv == 0) {
trace("(TCP) Connection closing..."); beammp_trace("(TCP) Connection closing...");
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
return false; return false;
} else if (BytesRcv < 0) { } else if (BytesRcv < 0) {
debug("(TCP) recv() failed: " + GetPlatformAgnosticErrorString()); beammp_debug("(TCP) recv() failed: " + GetPlatformAgnosticErrorString());
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
CloseSocketProper(c.GetTCPSock()); CloseSocketProper(c.GetTCPSock());
@@ -414,7 +462,7 @@ std::string TNetwork::TCPRcv(TClient& c) {
Data.resize(Header); Data.resize(Header);
} else { } else {
ClientKick(c, "Header size limit exceeded"); ClientKick(c, "Header size limit exceeded");
warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client."); beammp_warn("Client " + c.GetName() + " (" + std::to_string(c.GetID()) + ") sent header of >100MB - assuming malicious intent and disconnecting the client.");
return ""; return "";
} }
BytesRcv = 0; BytesRcv = 0;
@@ -434,9 +482,9 @@ std::string TNetwork::TCPRcv(TClient& c) {
} }
void TNetwork::ClientKick(TClient& c, const std::string& R) { void TNetwork::ClientKick(TClient& c, const std::string& R) {
info("Client kicked: " + R); beammp_info("Client kicked: " + R);
if (!TCPSend(c, "E" + R)) { if (!TCPSend(c, "K" + R)) {
// TODO handle beammp_warn("tried to kick player '" + c.GetName() + "' (id " + std::to_string(c.GetID()) + "), but was already disconnected");
} }
c.SetStatus(-2); c.SetStatus(-2);
@@ -446,15 +494,17 @@ void TNetwork::ClientKick(TClient& c, const std::string& R) {
if (c.GetDownSock()) if (c.GetDownSock())
CloseSocketProper(c.GetDownSock()); CloseSocketProper(c.GetDownSock());
} }
void TNetwork::Looper(const std::weak_ptr<TClient>& c) { void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
RegisterThreadAuto();
while (!c.expired()) { while (!c.expired()) {
auto Client = c.lock(); auto Client = c.lock();
if (Client->GetStatus() < 0) { if (Client->GetStatus() < 0) {
debug("client status < 0, breaking client loop"); beammp_debug("client status < 0, breaking client loop");
break; break;
} }
if (!Client->IsSyncing() && Client->IsSynced() && Client->MissedPacketQueueSize() != 0) { if (!Client->IsSyncing() && Client->IsSynced() && Client->MissedPacketQueueSize() != 0) {
//debug("sending " + std::to_string(Client->MissedPacketQueueSize()) + " queued packets"); // debug("sending " + std::to_string(Client->MissedPacketQueueSize()) + " queued packets");
while (Client->MissedPacketQueueSize() > 0) { while (Client->MissedPacketQueueSize() > 0) {
std::string QData {}; std::string QData {};
{ // locked context { // locked context
@@ -465,7 +515,7 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
QData = Client->MissedPacketQueue().front(); QData = Client->MissedPacketQueue().front();
Client->MissedPacketQueue().pop(); Client->MissedPacketQueue().pop();
} // end locked context } // end locked context
// debug("sending a missed packet: " + QData); // beammp_debug("sending a missed packet: " + QData);
if (!TCPSend(*Client, QData, true)) { if (!TCPSend(*Client, QData, true)) {
if (Client->GetStatus() > -1) if (Client->GetStatus() > -1)
Client->SetStatus(-1); Client->SetStatus(-1);
@@ -484,6 +534,7 @@ void TNetwork::Looper(const std::weak_ptr<TClient>& c) {
} }
} }
} }
void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) { 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 // TODO: the c.expired() might cause issues here, remove if you end up here with your debugger
if (c.expired() || c.lock()->GetTCPSock() == -1) { if (c.expired() || c.lock()->GetTCPSock() == -1) {
@@ -500,13 +551,13 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
break; break;
auto Client = c.lock(); auto Client = c.lock();
if (Client->GetStatus() < 0) { if (Client->GetStatus() < 0) {
debug("client status < 0, breaking client loop"); beammp_debug("client status < 0, breaking client loop");
break; break;
} }
auto res = TCPRcv(*Client); auto res = TCPRcv(*Client);
if (res == "") { if (res == "") {
debug("TCPRcv error, break client loop"); beammp_debug("TCPRcv error, break client loop");
break; break;
} }
TServer::GlobalParser(c, res, mPPSMonitor, *this); TServer::GlobalParser(c, res, mPPSMonitor, *this);
@@ -518,7 +569,7 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
auto Client = c.lock(); auto Client = c.lock();
OnDisconnect(c, Client->GetStatus() == -2); OnDisconnect(c, Client->GetStatus() == -2);
} else { } else {
warn("client expired in TCPClient, should never happen"); beammp_warn("client expired in TCPClient, should never happen");
} }
} }
@@ -538,10 +589,10 @@ void TNetwork::UpdatePlayer(TClient& Client) {
} }
void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) { void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked) {
Assert(!ClientPtr.expired()); beammp_assert(!ClientPtr.expired());
auto LockedClientPtr = ClientPtr.lock(); auto LockedClientPtr = ClientPtr.lock();
TClient& c = *LockedClientPtr; TClient& c = *LockedClientPtr;
info(c.GetName() + (" Connection Terminated")); beammp_info(c.GetName() + (" Connection Terminated"));
std::string Packet; std::string Packet;
TClient::TSetOfVehicleData VehicleData; TClient::TSetOfVehicleData VehicleData;
{ // Vehicle Data Lock Scope { // Vehicle Data Lock Scope
@@ -558,7 +609,8 @@ void TNetwork::OnDisconnect(const std::weak_ptr<TClient>& ClientPtr, bool kicked
Packet = ("L") + c.GetName() + (" left the server!"); Packet = ("L") + c.GetName() + (" left the server!");
SendToAll(&c, Packet, false, true); SendToAll(&c, Packet, false, true);
Packet.clear(); Packet.clear();
TriggerLuaEvent(("onPlayerDisconnect"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID() } }), false); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerDisconnect", "", c.GetID());
LuaAPI::MP::Engine->ReportErrors(Futures);
if (c.GetTCPSock()) if (c.GetTCPSock())
CloseSocketProper(c.GetTCPSock()); CloseSocketProper(c.GetTCPSock());
if (c.GetDownSock()) if (c.GetDownSock())
@@ -587,18 +639,18 @@ int TNetwork::OpenID() {
} }
void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) { void TNetwork::OnConnect(const std::weak_ptr<TClient>& c) {
Assert(!c.expired()); beammp_assert(!c.expired());
info("Client connected"); beammp_info("Client connected");
auto LockedClient = c.lock(); auto LockedClient = c.lock();
LockedClient->SetID(OpenID()); LockedClient->SetID(OpenID());
info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName()); beammp_info("Assigned ID " + std::to_string(LockedClient->GetID()) + " to " + LockedClient->GetName());
TriggerLuaEvent("onPlayerConnecting", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerConnecting", "", LockedClient->GetID()));
SyncResources(*LockedClient); SyncResources(*LockedClient);
if (LockedClient->GetStatus() < 0) if (LockedClient->GetStatus() < 0)
return; return;
(void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); //Send the Map on connect (void)Respond(*LockedClient, "M" + Application::Settings.MapName, true); // Send the Map on connect
info(LockedClient->GetName() + " : Connected"); beammp_info(LockedClient->GetName() + " : Connected");
TriggerLuaEvent("onPlayerJoining", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoining", "", LockedClient->GetID()));
} }
void TNetwork::SyncResources(TClient& c) { void TNetwork::SyncResources(TClient& c) {
@@ -617,7 +669,7 @@ void TNetwork::SyncResources(TClient& c) {
} }
#ifndef DEBUG #ifndef DEBUG
} catch (std::exception& e) { } catch (std::exception& e) {
error("Exception! : " + std::string(e.what())); beammp_error("Exception! : " + std::string(e.what()));
c.SetStatus(-1); c.SetStatus(-1);
} }
#endif #endif
@@ -635,7 +687,7 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
return; return;
case 'S': case 'S':
if (SubCode == 'R') { if (SubCode == 'R') {
debug("Sending Mod Info"); beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes(); std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
if (ToSend.empty()) if (ToSend.empty())
ToSend = "-"; ToSend = "-";
@@ -650,13 +702,13 @@ void TNetwork::Parse(TClient& c, const std::string& Packet) {
} }
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) { void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/'))); beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
if (!fs::path(UnsafeName).has_filename()) { if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, "CO")) { if (!TCPSend(c, "CO")) {
// TODO: handle // TODO: handle
} }
warn("File " + UnsafeName + " is not a file!"); beammp_warn("File " + UnsafeName + " is not a file!");
return; return;
} }
auto FileName = fs::path(UnsafeName).filename().string(); auto FileName = fs::path(UnsafeName).filename().string();
@@ -666,7 +718,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
if (!TCPSend(c, "CO")) { if (!TCPSend(c, "CO")) {
// TODO: handle // TODO: handle
} }
warn("File " + UnsafeName + " could not be accessed!"); beammp_warn("File " + UnsafeName + " could not be accessed!");
return; return;
} }
@@ -674,7 +726,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
// TODO: handle // TODO: handle
} }
///Wait for connections /// Wait for connections
int T = 0; int T = 0;
while (c.GetDownSock() < 1 && T < 50) { while (c.GetDownSock() < 1 && T < 50) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -682,7 +734,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
} }
if (c.GetDownSock() < 1) { if (c.GetDownSock() < 1) {
error("Client doesn't have a download socket!"); beammp_error("Client doesn't have a download socket!");
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
return; return;
@@ -692,9 +744,11 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
std::thread SplitThreads[2] { std::thread SplitThreads[2] {
std::thread([&] { std::thread([&] {
RegisterThread("SplitLoad_0");
SplitLoad(c, 0, MSize, false, FileName); SplitLoad(c, 0, MSize, false, FileName);
}), }),
std::thread([&] { std::thread([&] {
RegisterThread("SplitLoad_1");
SplitLoad(c, MSize, Size, true, FileName); SplitLoad(c, MSize, Size, true, FileName);
}) })
}; };
@@ -708,7 +762,7 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) { void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) {
std::ifstream f(Name.c_str(), std::ios::binary); std::ifstream f(Name.c_str(), std::ios::binary);
uint32_t Split = 0x7735940; //125MB uint32_t Split = 125 * MB;
char* Data; char* Data;
if (Size > Split) if (Size > Split)
Data = new char[Split]; Data = new char[Split];
@@ -719,7 +773,7 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
TCPSock = c.GetDownSock(); TCPSock = c.GetDownSock();
else else
TCPSock = c.GetTCPSock(); TCPSock = c.GetTCPSock();
info("Split load Socket " + std::to_string(TCPSock)); beammp_debug("Split load Socket " + std::to_string(TCPSock));
while (c.GetStatus() > -1 && Sent < Size) { while (c.GetStatus() > -1 && Sent < Size) {
size_t Diff = Size - Sent; size_t Diff = Size - Sent;
if (Diff > Split) { if (Diff > Split) {
@@ -749,9 +803,13 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) { bool TNetwork::TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size) {
intmax_t Sent = 0; intmax_t Sent = 0;
do { 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); intmax_t Temp = send(socket, &Data[Sent], int(Size - Sent), 0);
#endif
if (Temp < 1) { if (Temp < 1) {
info("Socket Closed! " + std::to_string(socket)); beammp_info("Socket Closed! " + std::to_string(socket));
CloseSocketProper(socket); CloseSocketProper(socket);
return false; return false;
} }
@@ -797,7 +855,7 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
// ignore error // ignore error
(void)SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true); (void)SendToAll(LockedClient.get(), ("JWelcome ") + LockedClient->GetName() + "!", false, true);
TriggerLuaEvent(("onPlayerJoin"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID() } }), false); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onPlayerJoin", "", LockedClient->GetID()));
LockedClient->SetIsSyncing(true); LockedClient->SetIsSyncing(true);
bool Return = false; bool Return = false;
bool res = true; bool res = true;
@@ -833,13 +891,13 @@ bool TNetwork::SyncClient(const std::weak_ptr<TClient>& c) {
return res; return res;
} }
LockedClient->SetIsSynced(true); LockedClient->SetIsSynced(true);
info(LockedClient->GetName() + (" is now synced!")); beammp_info(LockedClient->GetName() + (" is now synced!"));
return true; return true;
} }
void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) { void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) {
if (!Self) if (!Self)
Assert(c); beammp_assert(c);
char C = Data.at(0); char C = Data.at(0);
bool ret = true; bool ret = true;
mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool { mServer.ForEachClient([&](std::weak_ptr<TClient> ClientPtr) -> bool {
@@ -861,10 +919,10 @@ void TNetwork::SendToAll(TClient* c, const std::string& Data, bool Self, bool Re
} else { } else {
Client->EnqueuePacket(Data); Client->EnqueuePacket(Data);
} }
//ret = SendLarge(*Client, Data); // ret = SendLarge(*Client, Data);
} else { } else {
Client->EnqueuePacket(Data); Client->EnqueuePacket(Data);
//ret = TCPSend(*Client, Data); // ret = TCPSend(*Client, Data);
} }
} else { } else {
ret = UDPSend(*Client, Data); ret = UDPSend(*Client, Data);
@@ -903,12 +961,12 @@ bool TNetwork::UDPSend(TClient& Client, std::string Data) const {
sendOk = sendto(mUDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, int(AddrSize)); sendOk = sendto(mUDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, int(AddrSize));
if (sendOk == -1) { if (sendOk == -1) {
debug("(UDP) sendto() failed: " + GetPlatformAgnosticErrorString()); beammp_debug("(UDP) sendto() failed: " + GetPlatformAgnosticErrorString());
if (Client.GetStatus() > -1) if (Client.GetStatus() > -1)
Client.SetStatus(-1); Client.SetStatus(-1);
return false; return false;
} else if (sendOk == 0) { } else if (sendOk == 0) {
debug(("(UDP) sendto() returned 0")); beammp_debug(("(UDP) sendto() returned 0"));
if (Client.GetStatus() > -1) if (Client.GetStatus() > -1)
Client.SetStatus(-1); Client.SetStatus(-1);
return false; return false;
@@ -926,7 +984,7 @@ std::string TNetwork::UDPRcvFromClient(sockaddr_in& client) const {
#endif // WIN32 #endif // WIN32
if (Rcv == -1) { if (Rcv == -1) {
error("(UDP) Error receiving from client! recvfrom() failed: " + GetPlatformAgnosticErrorString()); beammp_error("(UDP) Error receiving from client! recvfrom() failed: " + GetPlatformAgnosticErrorString());
return ""; return "";
} }
return std::string(Ret.begin(), Ret.begin() + Rcv); return std::string(Ret.begin(), Ret.begin() + Rcv);
+10 -6
View File
@@ -4,24 +4,28 @@
TPPSMonitor::TPPSMonitor(TServer& Server) TPPSMonitor::TPPSMonitor(TServer& Server)
: mServer(Server) { : mServer(Server) {
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Starting);
Application::SetPPS("-"); Application::SetPPS("-");
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
debug("shutting down PPSMonitor"); beammp_debug("shutting down PPSMonitor");
mShutdown = true; mShutdown = true;
mThread.join(); mThread.join();
debug("shut down PPSMonitor"); beammp_debug("shut down PPSMonitor");
} }
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Shutdown);
}); });
Start(); Start();
} }
void TPPSMonitor::operator()() { void TPPSMonitor::operator()() {
RegisterThread("PPSMonitor"); RegisterThread("PPSMonitor");
while (!mNetwork) { while (!mNetwork) {
// hard spi // hard(-ish) spin
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield();
} }
info("PPSMonitor starting"); beammp_debug("PPSMonitor starting");
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good);
std::vector<std::shared_ptr<TClient>> TimedOutClients; std::vector<std::shared_ptr<TClient>> TimedOutClients;
while (!mShutdown) { while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -45,7 +49,7 @@ void TPPSMonitor::operator()() {
} }
// kick on "no ping" // kick on "no ping"
if (c->SecondsSinceLastPing() > (20 * 60)) { if (c->SecondsSinceLastPing() > (20 * 60)) {
debug("client " + std::string("(") + std::to_string(c->GetID()) + ")" + c->GetName() + " timing out: " + std::to_string(c->SecondsSinceLastPing()) + ", pps: " + Application::PPS()); 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); TimedOutClients.push_back(c);
} }
+7 -4
View File
@@ -6,6 +6,7 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
TResourceManager::TResourceManager() { TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting);
std::string Path = Application::Settings.Resource + "/Client"; std::string Path = Application::Settings.Resource + "/Client";
if (!fs::exists(Path)) if (!fs::exists(Path))
fs::create_directories(Path); fs::create_directories(Path);
@@ -13,11 +14,11 @@ TResourceManager::TResourceManager() {
std::string File(entry.path().string()); std::string File(entry.path().string());
if (auto pos = File.find(".zip"); pos != std::string::npos) { if (auto pos = File.find(".zip"); pos != std::string::npos) {
if (File.length() - pos == 4) { if (File.length() - pos == 4) {
std::replace(File.begin(), File.end(),'\\','/'); std::replace(File.begin(), File.end(), '\\', '/');
mFileList += File + ';'; mFileList += File + ';';
if(auto i = File.find_last_of('/'); i != std::string::npos){ if (auto i = File.find_last_of('/'); i != std::string::npos) {
++i; ++i;
File = File.substr(i,pos-i); File = File.substr(i, pos - i);
} }
mTrimmedList += "/" + fs::path(File).filename().string() + ';'; mTrimmedList += "/" + fs::path(File).filename().string() + ';';
mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';'; mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';';
@@ -28,5 +29,7 @@ TResourceManager::TResourceManager() {
} }
if (mModsLoaded) if (mModsLoaded)
info("Loaded " + std::to_string(mModsLoaded) + " Mods"); beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
} }
+27
View File
@@ -0,0 +1,27 @@
#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 ");
}
}
+24 -11
View File
@@ -1,22 +1,17 @@
#include "TSentry.h" #include "TSentry.h"
#include "Common.h" #include "Common.h"
#include <cstring>
#include <sentry.h> #include <sentry.h>
#include <sstream> #include <sstream>
// compile-time length of a string/array
template <size_t N>
constexpr size_t ConstexprLength(char const (&)[N]) {
return N - 1;
}
TSentry::TSentry() { TSentry::TSentry() {
if constexpr (ConstexprLength(SECRET_SENTRY_URL) == 0) { if (std::strlen(S_DSN) == 0) {
mValid = false; mValid = false;
} else { } else {
mValid = true; mValid = true;
sentry_options_t* options = sentry_options_new(); sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, SECRET_SENTRY_URL); sentry_options_set_dsn(options, S_DSN);
auto ReleaseString = "BeamMP-Server@" + Application::ServerVersionString(); auto ReleaseString = "BeamMP-Server@" + Application::ServerVersionString();
sentry_options_set_symbolize_stacktraces(options, true); sentry_options_set_symbolize_stacktraces(options, true);
sentry_options_set_release(options, ReleaseString.c_str()); sentry_options_set_release(options, ReleaseString.c_str());
@@ -33,9 +28,26 @@ TSentry::~TSentry() {
void TSentry::PrintWelcome() { void TSentry::PrintWelcome() {
if (mValid) { if (mValid) {
info("Sentry started"); 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 { } else {
info("Sentry disabled in unofficial build"); if (Application::Settings.SendErrorsMessageEnabled) {
beammp_info("Sentry disabled in unofficial build. Automatic error reporting disabled.");
} else {
beammp_info("Sentry disabled in unofficial build");
}
} }
} }
@@ -43,6 +55,7 @@ void TSentry::SetupUser() {
if (!mValid) { if (!mValid) {
return; return;
} }
Application::SetSubsystemStatus("Sentry", Application::Status::Good);
sentry_value_t user = sentry_value_new_object(); sentry_value_t user = sentry_value_new_object();
if (Application::Settings.Key.size() == 36) { if (Application::Settings.Key.size() == 36) {
sentry_value_set_by_key(user, "id", sentry_value_new_string(Application::Settings.Key.c_str())); sentry_value_set_by_key(user, "id", sentry_value_new_string(Application::Settings.Key.c_str()));
@@ -59,7 +72,7 @@ void TSentry::Log(SentryLevel level, const std::string& logger, const std::strin
SetContext("threads", { { "thread-name", ThreadName(true) } }); SetContext("threads", { { "thread-name", ThreadName(true) } });
auto Msg = sentry_value_new_message_event(sentry_level_t(level), logger.c_str(), text.c_str()); auto Msg = sentry_value_new_message_event(sentry_level_t(level), logger.c_str(), text.c_str());
sentry_capture_event(Msg); sentry_capture_event(Msg);
sentry_remove_transaction(); sentry_set_transaction(nullptr);
} }
void TSentry::LogError(const std::string& text, const std::string& file, const std::string& line) { void TSentry::LogError(const std::string& text, const std::string& file, const std::string& line) {
+93 -83
View File
@@ -3,35 +3,39 @@
#include "Common.h" #include "Common.h"
#include "TNetwork.h" #include "TNetwork.h"
#include "TPPSMonitor.h" #include "TPPSMonitor.h"
#include <TLuaFile.h> #include <TLuaPlugin.h>
#include <any> #include <any>
#include <sstream> #include <sstream>
#include <nlohmann/json.hpp>
#include "LuaAPI.h"
#undef GetObject // Fixes Windows #undef GetObject // Fixes Windows
#include "Json.h" #include "Json.h"
namespace json = rapidjson; TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
TServer::TServer(int argc, char** argv) { Application::SetSubsystemStatus("Server", Application::Status::Starting);
info("BeamMP Server v" + Application::ServerVersionString()); if (Arguments.size() > 1) {
if (argc > 1) { Application::Settings.CustomIP = Arguments[0];
Application::Settings.CustomIP = argv[1];
size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.'); size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.');
auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789"); auto p = Application::Settings.CustomIP.find_first_not_of(".0123456789");
if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") { if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == "127") {
Application::Settings.CustomIP.clear(); Application::Settings.CustomIP.clear();
warn("IP Specified is invalid! Ignoring"); beammp_warn("IP Specified is invalid! Ignoring");
} else { } else {
info("server started with custom IP"); beammp_info("server started with custom IP");
} }
} }
Application::SetSubsystemStatus("Server", Application::Status::Good);
} }
void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) { void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
if (!WeakClientPtr.expired()) { if (!WeakClientPtr.expired()) {
TClient& Client = *WeakClientPtr.lock(); TClient& Client = *WeakClientPtr.lock();
debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")"); beammp_debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")");
Client.ClearCars(); Client.ClearCars();
WriteLock Lock(mClientsMutex); WriteLock Lock(mClientsMutex);
mClients.erase(WeakClientPtr.lock()); mClients.erase(WeakClientPtr.lock());
@@ -39,7 +43,7 @@ void TServer::RemoveClient(const std::weak_ptr<TClient>& WeakClientPtr) {
} }
std::weak_ptr<TClient> TServer::InsertNewClient() { std::weak_ptr<TClient> TServer::InsertNewClient() {
debug("inserting new client (" + std::to_string(ClientCount()) + ")"); beammp_debug("inserting new client (" + std::to_string(ClientCount()) + ")");
WriteLock Lock(mClientsMutex); WriteLock Lock(mClientsMutex);
auto [Iter, Replaced] = mClients.insert(std::make_shared<TClient>(*this)); auto [Iter, Replaced] = mClients.insert(std::make_shared<TClient>(*this));
return *Iter; return *Iter;
@@ -65,7 +69,7 @@ size_t TServer::ClientCount() const {
void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Packet, TPPSMonitor& PPSMonitor, TNetwork& Network) { 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) { if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) {
//abort(); // abort();
} }
if (Packet.substr(0, 4) == "ABG:") { if (Packet.substr(0, 4) == "ABG:") {
Packet = DeComp(Packet.substr(4)); Packet = DeComp(Packet.substr(4));
@@ -82,7 +86,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
std::any Res; std::any Res;
char Code = Packet.at(0); char Code = Packet.at(0);
//V to Z // V to Z
if (Code <= 90 && Code >= 86) { if (Code <= 90 && Code >= 86) {
PPSMonitor.IncrementInternalPPS(); PPSMonitor.IncrementInternalPPS();
Network.SendToAll(LockedClient.get(), Packet, false, false); Network.SendToAll(LockedClient.get(), Packet, false, false);
@@ -90,7 +94,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
} }
switch (Code) { switch (Code) {
case 'H': // initial connection case 'H': // initial connection
trace(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")"); beammp_trace(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")");
if (!Network.SyncClient(Client)) { if (!Network.SyncClient(Client)) {
// TODO handle // TODO handle
} }
@@ -107,30 +111,38 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
return; return;
case 'O': case 'O':
if (Packet.length() > 1000) { if (Packet.length() > 1000) {
debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.length())); beammp_debug(("Received data from: ") + LockedClient->GetName() + (" Size: ") + std::to_string(Packet.length()));
} }
ParseVehicle(*LockedClient, Packet, Network); ParseVehicle(*LockedClient, Packet, Network);
return; return;
case 'J': case 'J':
trace(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(LockedClient.get(), Packet, false, true); Network.SendToAll(LockedClient.get(), Packet, false, true);
return; return;
case 'C': case 'C': {
trace(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos) if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos)
break; break;
Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 2));
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged TLuaEngine::WaitForAll(Futures);
if (std::any_cast<int>(Res)) 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; break;
}
Network.SendToAll(nullptr, Packet, true, true); Network.SendToAll(nullptr, Packet, true, true);
return; return;
}
case 'E': case 'E':
trace(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
HandleEvent(*LockedClient, Packet); HandleEvent(*LockedClient, Packet);
return; return;
case 'N': case 'N':
trace("got 'N' packet (" + std::to_string(Packet.size()) + ")"); beammp_trace("got 'N' packet (" + std::to_string(Packet.size()) + ")");
Network.SendToAll(LockedClient.get(), Packet, false, true); Network.SendToAll(LockedClient.get(), Packet, false, true);
return; return;
default: default:
@@ -138,48 +150,38 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
} }
} }
void TServer::HandleEvent(TClient& c, const std::string& Data) { void TServer::HandleEvent(TClient& c, const std::string& RawData) {
std::stringstream ss(Data); // E:Name:Data
std::string t, Name; // Data is allowed to have ':'
int a = 0; auto NameDataSep = RawData.find(':', 2);
while (std::getline(ss, t, ':')) { if (NameDataSep == std::string::npos) {
switch (a) { beammp_warn("received event in invalid format (missing ':'), got: '" + RawData + "'");
case 1:
Name = t;
break;
case 2:
TriggerLuaEvent(Name, false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), t } }), false);
break;
default:
break;
}
if (a == 2)
break;
a++;
} }
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) { bool TServer::IsUnicycle(TClient& c, const std::string& CarJson) {
rapidjson::Document Car; try {
Car.Parse(CarJson.c_str(), CarJson.size()); auto Car = nlohmann::json::parse(CarJson);
if (Car.HasParseError()) { const std::string jbm = "jbm";
error("Failed to parse vehicle data -> " + CarJson); if (Car.contains(jbm) && Car[jbm].is_string() && Car[jbm] == "unicycle") {
} else if (Car["jbm"].IsString() && std::string(Car["jbm"].GetString()) == "unicycle") { return true;
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; return false;
} }
bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) { bool TServer::ShouldSpawn(TClient& c, const std::string& CarJson, int ID) {
if (IsUnicycle(c, CarJson) && c.GetUnicycleID() < 0) {
if (c.GetUnicycleID() > -1 && (c.GetCarCount() - 1) < Application::Settings.MaxCars) {
return true;
}
if (IsUnicycle(c, CarJson)) {
c.SetUnicycleID(ID); c.SetUnicycleID(ID);
return true; return true;
} else {
return c.GetCarCount() < Application::Settings.MaxCars;
} }
return Application::Settings.MaxCars > c.GetCarCount();
} }
void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network) { void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Network) {
@@ -190,18 +192,23 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
int PID = -1; int PID = -1;
int VID = -1, Pos; int VID = -1, Pos;
std::string Data = Packet.substr(3), pid, vid; std::string Data = Packet.substr(3), pid, vid;
switch (Code) { //Spawned Destroyed Switched/Moved NotFound Reset switch (Code) { // Spawned Destroyed Switched/Moved NotFound Reset
case 's': case 's':
trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'Os' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
if (Data.at(0) == '0') { if (Data.at(0) == '0') {
int CarID = c.GetOpenCarID(); int CarID = c.GetOpenCarID();
debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID)); beammp_debug(c.GetName() + (" created a car with ID ") + std::to_string(CarID));
std::string CarJson = Packet.substr(5); std::string CarJson = Packet.substr(5);
Packet = "Os:" + c.GetRoles() + ":" + c.GetName() + ":" + std::to_string(c.GetID()) + "-" + std::to_string(CarID) + ":" + CarJson; Packet = "Os:" + c.GetRoles() + ":" + c.GetName() + ":" + std::to_string(c.GetID()) + "-" + std::to_string(CarID) + ":" + CarJson;
auto Res = TriggerLuaEvent(("onVehicleSpawn"), false, nullptr, std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), CarID, Packet.substr(3) } }), true); 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) && std::any_cast<int>(Res) == 0) { if (ShouldSpawn(c, CarJson, CarID) && !ShouldntSpawn) {
c.AddNewCar(CarID, Packet); c.AddNewCar(CarID, Packet);
Network.SendToAll(nullptr, Packet, true, true); Network.SendToAll(nullptr, Packet, true, true);
} else { } else {
@@ -212,12 +219,12 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
if (!Network.Respond(c, Destroy, true)) { if (!Network.Respond(c, Destroy, true)) {
// TODO: handle // TODO: handle
} }
debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID)); beammp_debug(c.GetName() + (" (force : car limit/lua) removed ID ") + std::to_string(CarID));
} }
} }
return; return;
case 'c': case 'c':
trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'Oc' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-')); pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1, Data.find(':', 1) - Data.find('-') - 1); 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) { if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@@ -225,14 +232,17 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
VID = stoi(vid); VID = stoi(vid);
} }
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
auto Res = TriggerLuaEvent(("onVehicleEdited"), false, nullptr, auto Futures = LuaAPI::MP::Engine->TriggerEvent("onVehicleEdited", "", c.GetID(), VID, Packet.substr(3));
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID, Packet.substr(3) } }), TLuaEngine::WaitForAll(Futures);
true); 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('{'); auto FoundPos = Packet.find('{');
FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this FoundPos = FoundPos == std::string::npos ? 0 : FoundPos; // attempt at sanitizing this
if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos))) if ((c.GetUnicycleID() != VID || IsUnicycle(c, Packet.substr(FoundPos)))
&& std::any_cast<int>(Res) == 0) { && !ShouldntAllow) {
Network.SendToAll(&c, Packet, false, true); Network.SendToAll(&c, Packet, false, true);
Apply(c, VID, Packet); Apply(c, VID, Packet);
} else { } else {
@@ -248,7 +258,7 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
} }
return; return;
case 'd': case 'd':
trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'Od' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
pid = Data.substr(0, Data.find('-')); pid = Data.substr(0, Data.find('-'));
vid = Data.substr(Data.find('-') + 1); 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) { if (pid.find_first_not_of("0123456789") == std::string::npos && vid.find_first_not_of("0123456789") == std::string::npos) {
@@ -260,14 +270,14 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
c.SetUnicycleID(-1); c.SetUnicycleID(-1);
} }
Network.SendToAll(nullptr, Packet, true, true); Network.SendToAll(nullptr, Packet, true, true);
TriggerLuaEvent(("onVehicleDeleted"), false, nullptr, // TODO: should this trigger on all vehicle deletions?
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID } }), false); LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleDeleted", "", c.GetID(), VID));
c.DeleteCar(VID); c.DeleteCar(VID);
debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID)); beammp_debug(c.GetName() + (" deleted car with ID ") + std::to_string(VID));
} }
return; return;
case 'r': case 'r':
trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'Or' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Pos = int(Data.find('-')); Pos = int(Data.find('-'));
pid = Data.substr(0, Pos++); pid = Data.substr(0, Pos++);
vid = Data.substr(Pos, Data.find(':') - Pos); vid = Data.substr(Pos, Data.find(':') - Pos);
@@ -279,18 +289,19 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
if (PID != -1 && VID != -1 && PID == c.GetID()) { if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('{')); Data = Data.substr(Data.find('{'));
TriggerLuaEvent("onVehicleReset", false, nullptr, LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
std::make_unique<TLuaArg>(TLuaArg { { c.GetID(), VID, Data } }),
false);
Network.SendToAll(&c, Packet, false, true); Network.SendToAll(&c, Packet, false, true);
} }
return; return;
case 't': case 't':
trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); beammp_trace(std::string(("got 'Ot' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")"));
Network.SendToAll(&c, Packet, false, true); Network.SendToAll(&c, Packet, false, true);
return; return;
case 'm':
Network.SendToAll(&c, Packet, true, true);
return;
default: default:
trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")"))); beammp_trace(std::string(("possibly not implemented: '") + Packet + ("' (") + std::to_string(Packet.size()) + (")")));
return; return;
} }
} }
@@ -298,13 +309,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
void TServer::Apply(TClient& c, int VID, const std::string& pckt) { void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
auto FoundPos = pckt.find('{'); auto FoundPos = pckt.find('{');
if (FoundPos == std::string::npos) { if (FoundPos == std::string::npos) {
error("Malformed packet received, no '{' found"); beammp_error("Malformed packet received, no '{' found");
return; return;
} }
std::string Packet = pckt.substr(FoundPos); std::string Packet = pckt.substr(FoundPos);
std::string VD = c.GetCarData(VID); std::string VD = c.GetCarData(VID);
if (VD.empty()) { if (VD.empty()) {
error("Tried to apply change to vehicle that does not exist"); beammp_error("Tried to apply change to vehicle that does not exist");
auto Lock = Sentry.CreateExclusiveContext(); auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("vehicle-change", Sentry.SetContext("vehicle-change",
{ { "packet", Packet }, { { "packet", Packet },
@@ -321,19 +332,18 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
Sentry.SetContext("vehicle-change-packet", Sentry.SetContext("vehicle-change-packet",
{ { "packet", VD } }); { { "packet", VD } });
Sentry.LogError("malformed packet", _file_basename, _line); Sentry.LogError("malformed packet", _file_basename, _line);
error("Malformed packet received, no '{' found");
return; return;
} }
VD = VD.substr(FoundPos); VD = VD.substr(FoundPos);
rapidjson::Document Veh, Pack; rapidjson::Document Veh, Pack;
Veh.Parse(VD.c_str()); Veh.Parse(VD.c_str());
if (Veh.HasParseError()) { if (Veh.HasParseError()) {
error("Could not get vehicle config!"); beammp_error("Could not get vehicle config!");
return; return;
} }
Pack.Parse(Packet.c_str()); Pack.Parse(Packet.c_str());
if (Pack.HasParseError() || Pack.IsNull()) { if (Pack.HasParseError() || Pack.IsNull()) {
error("Could not get active vehicle config!"); beammp_error("Could not get active vehicle config!");
return; return;
} }
@@ -351,7 +361,7 @@ void TServer::Apply(TClient& c, int VID, const std::string& pckt) {
} }
void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) { void TServer::InsertClient(const std::shared_ptr<TClient>& NewClient) {
debug("inserting client (" + std::to_string(ClientCount()) + ")"); beammp_debug("inserting client (" + std::to_string(ClientCount()) + ")");
WriteLock Lock(mClientsMutex); //TODO why is there 30+ threads locked here WriteLock Lock(mClientsMutex); // TODO why is there 30+ threads locked here
(void)mClients.insert(NewClient); (void)mClients.insert(NewClient);
} }
+20 -4
View File
@@ -1,14 +1,30 @@
#include "VehicleData.h" #include "VehicleData.h"
#include "Common.h" #include "Common.h"
#include <regex>
#include <utility> #include <utility>
TVehicleData::TVehicleData(int ID, std::string Data) TVehicleData::TVehicleData(int ID, const std::string& Data)
: mID(ID) : mID(ID)
, mData(std::move(Data)) { , mData(Data) {
trace("vehicle " + std::to_string(mID) + " constructed"); 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() { TVehicleData::~TVehicleData() {
trace("vehicle " + std::to_string(mID) + " destroyed"); beammp_trace("vehicle " + std::to_string(mID) + " destroyed");
}
const json& TVehicleData::Json() const {
return mJson;
} }
+170 -47
View File
@@ -1,91 +1,214 @@
#include "TSentry.h" #include "TSentry.h"
#include "ArgsParser.h"
#include "Common.h" #include "Common.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
#include "LuaAPI.h"
#include "SignalHandling.h"
#include "TConfig.h" #include "TConfig.h"
#include "THeartbeatThread.h" #include "THeartbeatThread.h"
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include "TNetwork.h" #include "TNetwork.h"
#include "TPPSMonitor.h" #include "TPPSMonitor.h"
#include "TResourceManager.h" #include "TResourceManager.h"
#include "TScopedTimer.h"
#include "TServer.h" #include "TServer.h"
#include <iostream>
#include <thread> #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.
#ifdef __unix EXAMPLES:
#include <csignal> BeamMP-Server --config=../MyWestCoastServerConfig.toml
Runs the BeamMP-Server and uses the server config file
void UnixSignalHandler(int sig) { which is one directory above it and is named
switch (sig) { 'MyWestCoastServerConfig.toml'.
case SIGPIPE: )";
warn("ignoring SIGPIPE");
break;
case SIGTERM:
info("gracefully shutting down via SIGTERM");
Application::GracefullyShutdown();
break;
case SIGINT:
info("gracefully shutting down via SIGINT");
Application::GracefullyShutdown();
break;
default:
debug("unhandled signal: " + std::to_string(sig));
break;
}
}
#endif // __unix
// this is provided by the build system, leave empty for source builds // this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way // global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {}; TSentry Sentry {};
#include <iostream> struct MainArguments {
int argc {};
char** argv {};
std::vector<std::string_view> List;
std::string InvokedAs;
};
int main(int argc, char** argv) try { int BeamMPServerMain(MainArguments Arguments);
#ifdef __unix
trace("registering handlers for SIGINT, SIGTERM, SIGPIPE"); int main(int argc, char** argv) {
signal(SIGPIPE, UnixSignalHandler); MainArguments Args { argc, argv, {}, argv[0] };
signal(SIGTERM, UnixSignalHandler); Args.List.reserve(argc);
#ifndef DEBUG for (int i = 1; i < argc; ++i) {
signal(SIGINT, UnixSignalHandler); Args.List.push_back(argv[i]);
#endif // DEBUG }
#endif // __unix 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"); 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; bool Shutdown = false;
Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; }); Application::RegisterShutdownHandler([&Shutdown] {
Application::RegisterShutdownHandler([] { TriggerLuaEvent("onShutdown", false, nullptr, {}, true); }); 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(argc, argv); TServer Server(Arguments.List);
TConfig Config; TConfig Config(ConfigPath);
TLuaEngine LuaEngine;
LuaEngine.SetServer(&Server);
Application::Console().InitializeLuaConsole(LuaEngine);
if (Config.Failed()) { if (Config.Failed()) {
info("Closing in 10 seconds"); beammp_info("Closing in 10 seconds");
std::this_thread::sleep_for(std::chrono::seconds(10)); // 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; return 1;
} }
RegisterThread("Main"); RegisterThread("Main");
trace("Running in debug mode on a debug build"); beammp_trace("Running in debug mode on a debug build");
Sentry.SetupUser(); Sentry.SetupUser();
Sentry.PrintWelcome(); Sentry.PrintWelcome();
Application::CheckForUpdates();
TResourceManager ResourceManager; TResourceManager ResourceManager;
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);
TLuaEngine LuaEngine(Server, Network); LuaEngine.SetNetwork(&Network);
PPSMonitor.SetNetwork(Network); PPSMonitor.SetNetwork(Network);
Application::Console().InitializeLuaConsole(LuaEngine); Application::CheckForUpdates();
// TODO: replace 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) { 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)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
} catch (const std::exception& e) { Application::SetSubsystemStatus("Main", Application::Status::Shutdown);
error(e.what()); beammp_info("Shutdown.");
Sentry.LogException(e, _file_basename, _line); return 0;
} }
+18
View File
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,17 @@
{
"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"
]
}