Compare commits

..

183 Commits

Author SHA1 Message Date
Lion Kortlepel b209e70d62 update dlhttp 2022-07-27 09:20:21 +02:00
Lion Kortlepel 5394122d77 add /modlist endpoint, DownloadThreads config option 2022-07-26 17:57:19 +02:00
Lion Kortlepel f48b8ff1a1 update dlhttp, make / work 2022-07-24 15:29:20 +02:00
Lion Kortlepel de2a3795cc update dlhttp 2022-07-24 14:19:49 +02:00
Lion Kortlepel 054016a099 remove debug print 2022-07-20 16:08:56 +02:00
Lion Kortlepel 8b57f6e35a fix missing Lua error messages in ResultCheckThread 2022-07-20 16:07:00 +02:00
Lion Kortlepel f21d3d0389 fix bug which caused Lua hot-reload not to report syntax errors 2022-07-20 14:56:58 +02:00
Lion Kortlepel 2ed92c4aa1 fix plugin monitor eating a whole cpu core
i must have messed this up when i changed the plugin monitor behavior
recently
2022-07-20 14:42:07 +02:00
Lion Kortlepel fd7b11f436 fix event loop timing issue
The event loop tries to run no faster than every 10ms. If it detects
that it goes faster, it would incorrectly calculate the difference, and
then wait (what I assume was) way too long or too short.
Either way, now it's fixed and it correctly works, even when introducing
new lua states.
2022-07-20 14:33:19 +02:00
Lion Kortlepel 6a94060970 update changelog 2022-07-14 02:08:28 +02:00
Lion Kortlepel 51ccf31373 add beammp_debugf 2022-07-14 01:38:35 +02:00
Lion Kortlepel a2cc629153 add onFileChanged (fixes #116) 2022-07-14 01:18:50 +02:00
Lion Kortlepel ad414ec5c9 call onInit on hot-reload, cleanup, remove warnings
onInit is now called on hot-reload, for the whole plugin. Arguably, this
is not expected behavior, since only one file is being reloaded, but
this is the easiest way to do it, and the entire hot-reload process is
only for development purposes. Open an issue if this breaks your stuff
:^)
2022-07-14 00:37:47 +02:00
Lion Kortlepel 0a8e7d8e50 add TriggerLocalEvent 2022-07-14 00:27:44 +02:00
Lion bbd026e399 Merge pull request #114 from Mack29446/rc-v3.1.0
Add support to properly parse IPs
2022-07-13 00:02:40 +02:00
Mackenzie 04bbdff6b7 Add code from EvanMulawski 2022-07-12 22:59:41 +01:00
Lion Kortlepel 2d8ce09b2c Merge remote-tracking branch 'origin/master' into rc-v3.1.0 2022-07-09 23:03:46 +02:00
Lion 05251efc06 Merge pull request #98 from BeamMP/rc-v3.0.2
RC v3.0.2
2022-07-09 22:55:16 +02:00
Lion Kortlepel f8d622352f generate toml from scratch 2022-07-09 22:42:38 +02:00
Lion Kortlepel 6c1d02a425 add fclose 2022-07-09 22:29:12 +02:00
Lion Kortlepel 38eeec39b4 another attempt to fix #105 2022-07-09 22:27:05 +02:00
Lion Kortlepel 696e080e1c fix #105 2022-07-09 22:01:53 +02:00
Lion Kortlepel 98681254e6 update toml11 2022-07-09 21:50:23 +02:00
Lion Kortlepel 420e6c3533 roll back to an ancient version of sentry
sentry-native deprecated compiling without error somewhere around one of
the next version
2022-07-09 21:40:19 +02:00
Lion Kortlepel 06f8ba5a0e update sentry-native to 0.4.18 2022-07-09 21:11:34 +02:00
Lion Kortlepel 9420d8a7a0 update changelog 2022-06-30 21:30:56 +02:00
Lion Kortlepel dfa90da8af check if Resources/Server directory exists, and only create it if it doesnt
While this doesn't make a difference with usual setups, it does if the
server path exists but is a symlink. In that case, the
create_directories call fails, and the server aborts.

this fixes that. :^)
2022-06-30 20:19:48 +02:00
Lion Kortlepel 6e46d5aca9 start building the clear command 2022-06-28 03:29:01 +02:00
Lion Kortlepel 817bd4b588 start fixing mod download 2022-06-28 03:20:42 +02:00
Lion Kortlepel 25391fa0c7 thank you toml11 maintainer for naming your header some super generic name that clashes with every single other toml library B) 2022-06-04 16:51:34 +03:00
Lion 28270072d4 Merge pull request #102 from Mack29446/master
update version number in git checkout command
2022-05-30 19:16:10 +02:00
Lion Kortlepel 758d5b2c96 Revert "let's try vcpkg"
This reverts commit 8d7505956d.
2022-05-29 14:37:50 +02:00
Lion Kortlepel 1970d97ea4 Revert "Remove unneeded submodules"
This reverts commit a5153e4bc1.
2022-05-29 14:37:41 +02:00
Lion Kortlepel d8526f0649 TNetwork::SplitLoad: Use managed memory 2022-05-29 14:30:57 +02:00
Lion Kortlepel 0621c0bf82 rebase fixup 2022-05-26 21:13:18 +02:00
Lion Kortlepel 36547d1e9e Move PluginMonitor out of TLuaEngine 2022-05-26 20:59:53 +02:00
Lion Kortlepel f06f31c2a0 add moar tests!!! 2022-05-26 16:58:13 +02:00
Lion Kortlepel 46b92b4992 only run termios test if stdin is a tty 2022-05-26 16:14:05 +02:00
Lion Kortlepel 70e53c2a70 fix dependencies
ubuntu's package naming is confusing me
2022-05-26 16:00:36 +02:00
Lion Kortlepel cc35d83834 make linux workflow parallel, add runtime dependencies 2022-05-26 15:54:23 +02:00
Lion Kortlepel b4f97a6da0 make tests executable before running
:^)
2022-05-26 15:44:39 +02:00
Lion Kortlepel 3d7db6d0bc windows
me waiting for MSVC to stop making me write properly, deepls retarded
code: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.istockphoto.com%2Fphotos%2Fskeleton-reaching-for-a-window-picture-id155154794%3Fk%3D6%26m%3D155154794%26s%3D612x612%26w%3D0%26h%3DKNIrAKdhAsI1FCoMtSLXd99ZnP9MO1zZj8VkThTvwVs%3D&f=1&nofb=1
2022-05-26 15:41:52 +02:00
Lion Kortlepel 1c3b1ecc76 fix workflow test run path 2022-05-26 15:37:33 +02:00
Lion Kortlepel 303da19f48 add -t to linux workflow 2022-05-26 14:43:38 +02:00
Lion Kortlepel c455d4855c possibly fix workflow 2022-05-26 14:40:56 +02:00
Lion Kortlepel 8df61fe44c add g++ to workflow 2022-05-26 13:58:29 +02:00
Lion Kortlepel 7224e90c68 add test workflow 2022-05-26 13:56:58 +02:00
Lion Kortlepel fde5bc7fb6 remove tests from linux action 2022-05-26 13:50:50 +02:00
Lion Kortlepel 22b63220c7 add more tests to LuaAPI::FS, minor fixes to LuaAPI::FS
test config file creation, too
2022-05-26 13:49:13 +02:00
Lion Kortlepel 811ace1999 fix Application::IsOutdated test case types 2022-05-26 13:36:02 +02:00
Lion Kortlepel 2cf083a7e4 run tests in github actions 2022-05-26 13:34:29 +02:00
Lion Kortlepel 28c43a51ee add some tests for LuaAPI FS, termios 2022-05-26 13:33:08 +02:00
Lion Kortlepel 00f156cb86 start adding tests 2022-05-26 13:02:09 +02:00
Lion Kortlepel 67b8565a4d update json 2022-05-26 12:06:36 +02:00
Lion Kortlepel ed8d8a6419 remove unused code 2022-05-26 12:03:03 +02:00
Lion Kortlepel 895058f85f revert sentry to 0.4.9 2022-05-26 12:01:27 +02:00
Lion Kortlepel a0876ed58c update submodules, remove ssl crap 2022-05-26 11:59:36 +02:00
Lion Kortlepel 4f69ca1ad0 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-26 11:54:19 +02:00
Lion Kortlepel bc1628afeb fix some sentry and linking related issues 2022-05-26 11:51:26 +02:00
Lion Kortlepel 019c5202ea add tests executable 2022-05-26 11:44:20 +02:00
Lion Kortlepel 2dd181d492 gracefully shutdown on bind() failure 2022-05-26 11:19:48 +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
Mackenzie 58e2383b2e update version number in git checkout command 2022-04-13 19:48:12 +01: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
45 changed files with 2333 additions and 941 deletions
+60 -26
View File
@@ -7,38 +7,72 @@ env:
jobs: jobs:
linux-build: linux-build:
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: "recursive"
- name: Install Dependencies - name: Install Dependencies
env: env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: | run: |
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 cmake g++-10
- 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
- name: Configure CMake - name: Configure CMake
shell: bash shell: bash
working-directory: ${{github.workspace}}/build-linux working-directory: ${{github.workspace}}/build-linux
env: env:
beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }} beammp_sentry_url: ${{ secrets.BEAMMP_SECRET_SENTRY_URL }}
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url" run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=g++-10 -DBEAMMP_SECRET_SENTRY_URL="$beammp_sentry_url"
- name: Build - name: Build Server
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: Build Tests
uses: actions/upload-artifact@v2 working-directory: ${{github.workspace}}/build-linux
with: shell: bash
name: BeamMP-Server-linux run: cmake --build . --config $BUILD_TYPE -t BeamMP-Server-tests --parallel
path: ${{github.workspace}}/build-linux/BeamMP-Server
- name: Archive server artifact
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-linux
path: ${{github.workspace}}/build-linux/BeamMP-Server
- name: Archive test artifact
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-linux-tests
path: ${{github.workspace}}/build-linux/BeamMP-Server-tests
run-tests:
needs: linux-build
runs-on: ubuntu-20.04
steps:
- uses: actions/download-artifact@master
with:
name: BeamMP-Server-linux-tests
path: ${{github.workspace}}
- name: Install Runtime Dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y liblua5.3 openssl
- name: Test
working-directory: ${{github.workspace}}
shell: bash
run: |
chmod +x ./BeamMP-Server-tests
./BeamMP-Server-tests
+12 -1
View File
@@ -20,7 +20,7 @@ jobs:
with: with:
vcpkgArguments: 'lua zlib rapidjson 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
- name: Archive debug artifacts
uses: actions/upload-artifact@v2
with:
name: BeamMP-Server-debug.exe
path: ${{github.workspace}}/build-windows/Debug/BeamMP-Server.exe
+1 -1
View File
@@ -85,7 +85,7 @@ jobs:
with: with:
vcpkgArguments: 'lua zlib rapidjson 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
+13 -1
View File
@@ -21,4 +21,16 @@
url = https://github.com/nih-at/libzip url = https://github.com/nih-at/libzip
[submodule "deps/cpp-httplib"] [submodule "deps/cpp-httplib"]
path = deps/cpp-httplib path = deps/cpp-httplib
url = https://github.com/yhirose/cpp-httplib url = https://github.com/yhirose/cpp-httplib
[submodule "deps/json"]
path = deps/json
url = https://github.com/nlohmann/json
[submodule "deps/fmt"]
path = deps/fmt
url = https://github.com/fmtlib/fmt
[submodule "deps/doctest"]
path = deps/doctest
url = https://github.com/doctest/doctest
[submodule "deps/dlhttp"]
path = deps/dlhttp
url = https://github.com/lionkor/dlhttp
+149 -106
View File
@@ -1,4 +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")
@@ -8,56 +9,66 @@ project(BeamMP-Server
HOMEPAGE_URL https://beammp.com HOMEPAGE_URL https://beammp.com
LANGUAGES CXX C) LANGUAGES CXX C)
include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") option(BEAMMP_RUN_GIT "Run git to make sure submodules are updated (leave this on unless you know what you're doing)" ON)
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")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT) if (BEAMMP_RUN_GIT)
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()
endif()
set(HTTPLIB_REQUIRE_OPENSSL ON)
set(SENTRY_BUILD_SHARED_LIBS OFF)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/rapidjson/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/commandline")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/sol2/include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/cpp-httplib")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps/json/single_include")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/deps")
add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT=1)
# ------------------------ APPLE ---------------------------------
if(APPLE) if(APPLE)
set(LUA_INCLUDE_DIR /usr/local/Cellar/lua@5.3/5.3.6/include/lua5.3) set(LUA_INCLUDE_DIR /usr/local/Cellar/lua@5.3/5.3.6/include/lua5.3)
set(LUA_LIBRARIES lua) set(LUA_LIBRARIES lua)
include_directories(/usr/local/opt/openssl@1.1/include) 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/Cellar/lua@5.3/5.3.6/lib)
link_directories(/usr/local/opt/openssl@1.1/lib) link_directories(/usr/local/opt/openssl@1.1/lib)
endif() # ------------------------ WINDOWS ---------------------------------
elseif (WIN32)
if (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() # ------------------------ LINUX ---------------------------------
include_directories("include/sentry-native/include")
set(SENTRY_BUILD_SHARED_LIBS OFF)
if (MSVC)
set(SENTRY_BUILD_RUNTIMESTATIC ON)
endif()
set(SENTRY_BACKEND breakpad)
add_subdirectory("deps/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})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
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")
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 -fno-builtin") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fno-builtin")
option(SANITIZE "Turns on thread and UB sanitizers" OFF)
if (SANITIZE) if (SANITIZE)
message(STATUS "sanitize is ON") message(STATUS "sanitize is ON")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -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")
set(BUILD_SHARED_LIBS OFF)
# ------------------------ 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
@@ -65,96 +76,128 @@ 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")
# ------------------------ C++ SETUP ---------------------------------
set(CMAKE_CXX_STANDARD 17)
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif ()
# ------------------------ DEPENDENCIES ------------------------------
message(STATUS "Adding local source dependencies") message(STATUS "Adding local source dependencies")
# this has to happen before -DDEBUG since it wont compile properly with -DDEBUG # this has to happen before -DDEBUG since it wont compile properly with -DDEBUG
add_subdirectory(deps) add_subdirectory(deps)
set(CMAKE_CXX_STANDARD 17) # ------------------------ VARIABLES ---------------------------------
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") include(FindLua)
include(FindOpenSSL)
include(FindThreads)
include(FindZLIB)
set(BeamMP_Sources
include/TConsole.h src/TConsole.cpp
include/TServer.h src/TServer.cpp
include/Compat.h src/Compat.cpp
include/Common.h src/Common.cpp
include/Client.h src/Client.cpp
include/VehicleData.h src/VehicleData.cpp
include/TConfig.h src/TConfig.cpp
include/TLuaEngine.h src/TLuaEngine.cpp
include/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp
include/TPluginMonitor.h src/TPluginMonitor.cpp
include/Environment.h
)
set(BeamMP_Includes
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/commandline"
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include"
)
set(BeamMP_Definitions
SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}"
)
set(BeamMP_Libraries
doctest::doctest
OpenSSL::SSL
OpenSSL::Crypto
sol2::sol2
fmt::fmt
Threads::Threads
ZLIB::ZLIB
${LUA_LIBRARIES}
commandline
sentry
dlhttp
)
if (WIN32)
set(BeamMP_PlatformLibs wsock32 ws2_32)
endif ()
# ------------------------ BEAMMP SERVER -----------------------------
add_executable(BeamMP-Server add_executable(BeamMP-Server
src/main.cpp src/main.cpp
include/TConsole.h src/TConsole.cpp ${BeamMP_Sources}
include/TServer.h src/TServer.cpp )
include/Compat.h src/Compat.cpp
include/Common.h src/Common.cpp
include/Client.h src/Client.cpp
include/VehicleData.h src/VehicleData.cpp
include/TConfig.h src/TConfig.cpp
include/TLuaEngine.h src/TLuaEngine.cpp
include/TLuaPlugin.h src/TLuaPlugin.cpp
include/TResourceManager.h src/TResourceManager.cpp
include/THeartbeatThread.h src/THeartbeatThread.cpp
include/Http.h src/Http.cpp
include/TSentry.h src/TSentry.cpp
include/TPPSMonitor.h src/TPPSMonitor.cpp
include/TNetwork.h src/TNetwork.cpp
include/LuaAPI.h src/LuaAPI.cpp
include/TScopedTimer.h src/TScopedTimer.cpp
include/SignalHandling.h src/SignalHandling.cpp
include/ArgsParser.h src/ArgsParser.cpp
include/Environment.h)
target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}") target_compile_definitions(BeamMP-Server PRIVATE
include_directories(BeamMP-Server PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) ${BeamMP_Definitions}
DOCTEST_CONFIG_DISABLE
)
target_include_directories(BeamMP-Server PUBLIC target_include_directories(BeamMP-Server PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include" ${BeamMP_Includes}
"${CMAKE_CURRENT_SOURCE_DIR}/commandline") )
if (APPLE) target_link_libraries(BeamMP-Server
message(STATUS "NOT looking for Lua on APPLE") ${BeamMP_Libraries}
else() ${BeamMP_PlatformLibs}
message(STATUS "Looking for Lua") )
find_package(Lua REQUIRED VERSION 5.3)
# ------------------------ BEAMMP SERVER TESTS -----------------------
option(BUILD_TESTS "Build BeamMP-Server tests" ON)
if(BUILD_TESTS)
add_executable(BeamMP-Server-tests
test/test_main.cpp
${BeamMP_Sources}
)
target_compile_definitions(BeamMP-Server-tests PRIVATE
${BeamMP_Definitions}
)
target_include_directories(BeamMP-Server-tests PUBLIC
${BeamMP_Includes}
)
target_link_libraries(BeamMP-Server-tests
${BeamMP_Libraries}
${BeamMP_PlatformLibs}
)
endif() endif()
target_include_directories(BeamMP-Server PUBLIC
${LUA_INCLUDE_DIR}
${CURL_INCLUDE_DIRS}
"include/tomlplusplus"
"include/sentry-native/include"
"include/curl/include")
message(STATUS "Looking for SSL")
if (APPLE)
set(OPENSSL_LIBRARIES ssl crypto)
else()
find_package(OpenSSL REQUIRED)
endif()
target_link_libraries(BeamMP-Server sol2::sol2 ${LUA_LIBRARIES})
message(STATUS "CURL IS ${CURL_LIBRARIES}")
if (UNIX)
target_link_libraries(BeamMP-Server
z
pthread
${LUA_LIBRARIES}
crypto
${OPENSSL_LIBRARIES}
commandline
sentry
ssl)
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})
target_link_libraries(BeamMP-Server
ws2_32
ZLIB::ZLIB
${LUA_LIBRARIES}
${OPENSSL_LIBRARIES}
commandline
sentry)
endif ()
+40 -3
View File
@@ -1,7 +1,44 @@
# v2.4.0
# v3.1.0
- ADDED Tab autocomplete in console, smart tab autocomplete (understands lua tables and types) in the lua console
- ADDED lua debug facilities (type `:help` when attached to lua via `lua`)
- ADDED MP.JsonEncode() and MP.JsonDecode(), which turn lua tables into json and vice-versa
- ADDED FS.ListFiles and FS.ListDirectories
- ADDED onFileChanged event, triggered when a server plugin file changes
- FIXED `ip` in MP.GetIdentifiers
- FIXED issue with client->server events which contain ':'
- FIXED a fatal exception on LuaEngine startup if Resources/Server is a symlink
- FIXED onInit not being called on hot-reload
- FIXED incorrect timing calculation of Lua EventTimer loop
- FIXED bug which caused hot-reload not to report syntax errors
- FIXED missing error messages on some event handler calls
# 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
- FIXED bug which caused ServerConfig.toml to generate incorrectly
# 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 entire plugin Lua implementation (rewrite)
- CHANGED moved *almost all* functions into MP.\* - CHANGED moved *almost all* Lua functions into MP.\*
- CHANGED console to use a custom language (type `help`, `list`, or `status`!) - 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) - 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 many new Lua API functions, which can be found at <https://wiki.beammp.com/en/Scripting/functions>
@@ -14,7 +51,7 @@
- ADDED dumping tables with `print()` (try it with `print(MP)`) - 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.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 `MP.Settings` table to make usage of `MP.Set()` easier
- ADDED `FS.*` table with common filesystem operations - 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 i/o thread spin when stdout is /dev/null on linux
- FIXED removed extra whitespace infront of onChatMessage message - FIXED removed extra whitespace infront of onChatMessage message
+38 -27
View File
@@ -6,6 +6,8 @@
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 ## Support + Contact
Feel free to ask any questions via the following channels: Feel free to ask any questions via the following channels:
@@ -48,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)).
@@ -58,38 +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`, 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` libz-dev
- `g++` rapidjson-dev
- `libcurl4-openssl-dev` or similar (search for `libcurl` and look for one with `-dev`) liblua5.3
libssl-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). libwebsocketpp-dev
- `liblua5.3-dev` libcurl4-openssl-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`
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
``` ```
In the end you should end up with a command something like this: Build-time dependencies for **linux** are:
```sh
sudo apt install git make cmake g++-10 liblua5.3 libz-dev rapidjson-dev libopenssl-dev
``` ```
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
@@ -98,7 +109,7 @@ 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/v3.0.1` for version 3.0.1. You can find the latest version [here](https://github.com/BeamMP/BeamMP-Server/tags).
5. Run `cmake . -DCMAKE_BUILD_TYPE=Release` (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.
+11 -6
View File
@@ -1,9 +1,14 @@
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") 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/commandline")
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2") if (NOT TARGET fmt)
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/fmt")
endif()
if (NOT TARGET sol2)
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/sol2")
endif()
if (NOT TARGET doctest)
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/doctest")
endif()
add_subdirectory("${PROJECT_SOURCE_DIR}/deps/dlhttp")
Vendored Submodule
+1
Submodule deps/dlhttp added at f507c6c4e6
Vendored Submodule
+1
Submodule deps/doctest added at 7b98851331
Vendored Submodule
+1
Submodule deps/fmt added at ce246aaf74
Vendored Submodule
+1
Submodule deps/json added at ede6667858
Vendored
+1 -1
+1 -1
View File
@@ -92,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) };
+138 -75
View File
@@ -7,12 +7,19 @@ 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 <shared_mutex>
#include <sstream> #include <sstream>
#include <zlib.h> #include <zlib.h>
#include <doctest/doctest.h>
#include <filesystem>
namespace fs = std::filesystem;
#include "Compat.h" #include "Compat.h"
#include "TConsole.h" #include "TConsole.h"
@@ -42,17 +49,21 @@ public:
std::string Key {}; std::string Key {};
std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" }; std::string SSLKeyPath { "./.ssl/HttpServer/key.pem" };
std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" }; std::string SSLCertPath { "./.ssl/HttpServer/cert.pem" };
bool HTTPServerEnabled { true }; bool HTTPServerEnabled { false };
int MaxPlayers { 10 }; int MaxPlayers { 8 };
bool Private { true }; bool Private { true };
int MaxCars { 1 }; int MaxCars { 1 };
bool DebugModeEnabled { false }; bool DebugModeEnabled { false };
int Port { 30814 }; int Port { 30814 };
int DownloadThreads { 4 };
std::string CustomIP {}; std::string CustomIP {};
bool LogChat { true };
bool SendErrors { true }; bool SendErrors { true };
bool SendErrorsMessageEnabled { true }; bool SendErrorsMessageEnabled { true };
int HTTPServerPort { 8080 }; int HTTPServerPort { 8080 };
bool HTTPServerUseSSL { true }; std::string HTTPServerIP { "127.0.0.1" };
bool HTTPServerUseSSL { false };
bool HideUpdateMessages { false };
[[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); } [[nodiscard]] bool HasCustomIP() const { return !CustomIP.empty(); }
}; };
@@ -74,14 +85,21 @@ public:
static 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 bool IsShuttingDown();
static void SleepSafeSeconds(size_t Seconds);
static void InitializeConsole() { static void InitializeConsole() {
if (!mConsole) { if (!mConsole) {
@@ -107,99 +125,144 @@ public:
static void SetSubsystemStatus(const std::string& Subsystem, Status status); static void SetSubsystemStatus(const std::string& Subsystem, Status status);
private: private:
static void SetShutdown(bool Val);
static inline SystemStatusMap mSystemStatusMap {}; static inline SystemStatusMap mSystemStatusMap {};
static inline std::mutex mSystemStatusMapMutex {}; static inline std::mutex mSystemStatusMapMutex {};
static inline std::string mPPS; static inline std::string mPPS;
static inline std::unique_ptr<TConsole> mConsole; static inline std::unique_ptr<TConsole> mConsole;
static inline std::shared_mutex mShutdownMtx {};
static inline bool mShutdown { false };
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);
void RegisterThread(const std::string& str); void RegisterThread(const std::string& str);
#define RegisterThreadAuto() RegisterThread(__func__) #define RegisterThreadAuto() RegisterThread(__func__)
#define KB 1024 #define KB 1024llu
#define MB (KB * 1024) #define MB (KB * 1024llu)
#define GB (MB * 1024llu)
#define SSU_UNRAW SECRET_SENTRY_URL #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__)
#define _in_lambda (std::string(__func__) == "operator()") #define _in_lambda (std::string(__func__) == "operator()")
// we would like the full function signature 'void a::foo() const'
// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__,
// feel free to add more
#if defined(WIN32)
#define _function_name std::string(__FUNCSIG__)
#elif defined(__unix) || defined(__unix__)
#define _function_name std::string(__PRETTY_FUNCTION__)
#else
#define _function_name std::string(__func__)
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
// each info/debug/warn... call instead of the 'filename:line' format.
#if defined(BMP_FULL_FUNCTION_NAMES)
#define _this_location (ThreadName() + _function_name + " ")
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#define SU_RAW SSU_UNRAW
#else // !defined(DEBUG)
#define SU_RAW RAWIFY(SSU_UNRAW)
#define _this_location (ThreadName())
#endif // defined(DEBUG)
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define beammp_lua_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// for those times when you just need to ignore something :^) // for those times when you just need to ignore something :^)
// explicity disables a [[nodiscard]] warning // explicity disables a [[nodiscard]] warning
#define beammp_ignore(x) (void)x #define beammp_ignore(x) (void)x
// trace() is a debug-build debug()
// clang-format off
#ifdef DOCTEST_CONFIG_DISABLE
// we would like the full function signature 'void a::foo() const'
// on windows this is __FUNCSIG__, on GCC it's __PRETTY_FUNCTION__,
// feel free to add more
#if defined(WIN32)
#define _function_name std::string(__FUNCSIG__)
#elif defined(__unix) || defined(__unix__)
#define _function_name std::string(__PRETTY_FUNCTION__)
#else
#define _function_name std::string(__func__)
#endif
#ifndef NDEBUG
#define DEBUG
#endif
#if defined(DEBUG)
// if this is defined, we will show the full function signature infront of
// each info/debug/warn... call instead of the 'filename:line' format.
#if defined(BMP_FULL_FUNCTION_NAMES)
#define _this_location (ThreadName() + _function_name + " ")
#else
#define _this_location (ThreadName() + _file_basename + ":" + _line + " ")
#endif
#endif // defined(DEBUG)
#define beammp_warn(x) Application::Console().Write(_this_location + std::string("[WARN] ") + (x))
#define beammp_info(x) Application::Console().Write(_this_location + std::string("[INFO] ") + (x))
#define beammp_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[ERROR] ") + (x)); \
Sentry.AddErrorBreadcrumb((x), _file_basename, _line); \
} while (false)
#define beammp_lua_error(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA ERROR] ") + (x)); \
} while (false)
#define beammp_lua_warn(x) \
do { \
Application::Console().Write(_this_location + std::string("[LUA WARN] ") + (x)); \
} while (false)
#define luaprint(x) Application::Console().Write(_this_location + std::string("[LUA] ") + (x))
#define beammp_debug(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[DEBUG] ") + (x)); \
} \
} while (false)
#define beammp_event(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[EVENT] ") + (x)); \
} \
} while (false)
// trace() is a debug-build debug()
#if defined(DEBUG)
#define beammp_trace(x) \
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else
#define beammp_trace(x)
#endif // defined(DEBUG)
#define beammp_errorf(...) beammp_error(fmt::format(__VA_ARGS__))
#define beammp_infof(...) beammp_info(fmt::format(__VA_ARGS__))
#define beammp_debugf(...) beammp_debug(fmt::format(__VA_ARGS__))
#define beammp_warnf(...) beammp_warn(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__))
#else // DOCTEST_CONFIG_DISABLE
#define beammp_error(x) /* x */
#define beammp_lua_error(x) /* x */
#define beammp_warn(x) /* x */
#define beammp_lua_warn(x) /* x */
#define beammp_info(x) /* x */
#define beammp_event(x) /* x */
#define beammp_debug(x) /* x */
#define beammp_trace(x) /* x */
#define luaprint(x) /* x */
#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__))
#endif // DOCTEST_CONFIG_DISABLE
#if defined(DEBUG) #if defined(DEBUG)
#define beammp_trace(x) \ #define SU_RAW SSU_UNRAW
do { \
if (Application::Settings.DebugModeEnabled) { \
Application::Console().Write(_this_location + std::string("[TRACE] ") + (x)); \
} \
} while (false)
#else #else
#define beammp_trace(x) #define SU_RAW RAWIFY(SSU_UNRAW)
#endif // defined(DEBUG) #define _this_location (ThreadName())
#endif
// clang-format on
void LogChatMessage(const std::string& name, int id, const std::string& msg); void LogChatMessage(const std::string& name, int id, const std::string& msg);
+8 -3
View File
@@ -6,10 +6,10 @@
#ifdef BEAMMP_LINUX #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*;
@@ -25,10 +25,10 @@ inline void CloseSocketProper(int TheSocket) {
#ifdef BEAMMP_APPLE #ifdef BEAMMP_APPLE
#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*;
@@ -48,6 +48,11 @@ inline void CloseSocketProper(int TheSocket) {
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
#ifdef INVALID_SOCKET
static inline constexpr int BEAMMP_INVALID_SOCKET = INVALID_SOCKET;
#else
static inline constexpr int BEAMMP_INVALID_SOCKET = -1;
#endif
+1 -21
View File
@@ -3,8 +3,6 @@
#include <Common.h> #include <Common.h>
#include <IThreaded.h> #include <IThreaded.h>
#include <filesystem> #include <filesystem>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -19,26 +17,18 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace Crypto {
constexpr size_t RSA_DEFAULT_KEYLENGTH { 2048 };
}
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::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 { namespace Server {
void SetupEnvironment();
// todo: Add non TLS Server Instance, this one is TLS only
class THttpServerInstance { class THttpServerInstance {
public: public:
THttpServerInstance(); THttpServerInstance();
static fs::path KeyFilePath;
static fs::path CertFilePath;
protected: protected:
void operator()(); void operator()();
@@ -46,15 +36,5 @@ namespace Server {
private: private:
std::thread mThread; std::thread mThread;
}; };
// todo: all of these functions are likely unsafe,
// todo: replace with something that's managed by a domain specific crypto library
class Tx509KeypairGenerator {
public:
static long GenerateRandomId();
static bool EnsureTLSConfigExists();
static X509* GenerateCertificate(EVP_PKEY& pkey);
static EVP_PKEY* GenerateKey();
static void GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath);
};
} }
} }
+10 -1
View File
@@ -12,7 +12,8 @@ namespace MP {
std::string GetOSName(); std::string GetOSName();
std::tuple<int, int, int> GetServerVersion(); std::tuple<int, int, int> GetServerVersion();
bool TriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data); 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(); } inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
void DropPlayer(int ID, std::optional<std::string> MaybeReason); void DropPlayer(int ID, std::optional<std::string> MaybeReason);
void SendChatMessage(int ID, const std::string& Message); void SendChatMessage(int ID, const std::string& Message);
@@ -22,7 +23,15 @@ namespace MP {
bool IsPlayerConnected(int ID); bool IsPlayerConnected(int ID);
void Sleep(size_t Ms); void Sleep(size_t Ms);
void PrintRaw(sol::variadic_args); 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 { namespace FS {
std::pair<bool, std::string> CreateDirectory(const std::string& Path); 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> Remove(const std::string& Path);
+2 -1
View File
@@ -3,6 +3,7 @@
#include "Common.h" #include "Common.h"
#include <atomic> #include <atomic>
#include <filesystem>
#define TOML11_PRESERVE_COMMENTS_BY_DEFAULT #define TOML11_PRESERVE_COMMENTS_BY_DEFAULT
#include <toml11/toml.hpp> // header-only version of TOML++ #include <toml11/toml.hpp> // header-only version of TOML++
@@ -18,7 +19,7 @@ public:
void FlushToFile(); void FlushToFile();
private: private:
void CreateConfigFile(std::string_view name); void CreateConfigFile();
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, std::string& OutValue);
+34 -6
View File
@@ -4,6 +4,11 @@
#include "commandline.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 TLuaEngine;
@@ -15,19 +20,40 @@ public:
void WriteRaw(const std::string& str); void WriteRaw(const std::string& str);
void InitializeLuaConsole(TLuaEngine& Engine); void InitializeLuaConsole(TLuaEngine& Engine);
void BackupOldLog(); void BackupOldLog();
void StartLoggingToFile();
Commandline& Internal() { return mCommandline; } Commandline& Internal() { return mCommandline; }
private: private:
void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false); void RunAsCommand(const std::string& cmd, bool IgnoreNotACommand = false);
void ChangeToLuaConsole(const std::string& LuaStateId); void ChangeToLuaConsole(const std::string& LuaStateId);
void ChangeToRegularConsole(); void ChangeToRegularConsole();
void HandleLuaInternalCommand(const std::string& cmd);
void Command_Lua(const std::string& cmd); void Command_Lua(const std::string& cmd, const std::vector<std::string>& args);
void Command_Help(const std::string& cmd); void Command_Help(const std::string& cmd, const std::vector<std::string>& args);
void Command_Kick(const std::string& cmd); void Command_Kick(const std::string& cmd, const std::vector<std::string>& args);
void Command_Say(const std::string& cmd); void Command_List(const std::string& cmd, const std::vector<std::string>& args);
void Command_List(const std::string& cmd); void Command_Status(const std::string& cmd, const std::vector<std::string>& args);
void Command_Status(const std::string& cmd); void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
void Command_Clear(const std::string&, const std::vector<std::string>& args);
void Command_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); } },
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
};
Commandline mCommandline; Commandline mCommandline;
std::vector<std::string> mCachedLuaHistory; std::vector<std::string> mCachedLuaHistory;
@@ -37,4 +63,6 @@ private:
bool mFirstTime { true }; bool mFirstTime { true };
std::string mStateId; std::string mStateId;
const std::string mDefaultStateId = "BEAMMP_SERVER_CONSOLE"; const std::string mDefaultStateId = "BEAMMP_SERVER_CONSOLE";
std::ofstream mLogFileStream;
std::mutex mLogFileStreamMtx;
}; };
-1
View File
@@ -15,7 +15,6 @@ private:
std::string GenerateCall(); std::string GenerateCall();
std::string GetPlayers(); std::string GetPlayers();
bool mShutdown = false;
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
TServer& mServer; TServer& mServer;
}; };
+65 -25
View File
@@ -6,10 +6,12 @@
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <initializer_list> #include <initializer_list>
#include <list>
#include <lua.hpp> #include <lua.hpp>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <queue> #include <queue>
#include <random>
#include <set> #include <set>
#include <toml11/toml.hpp> #include <toml11/toml.hpp>
#include <unordered_map> #include <unordered_map>
@@ -32,8 +34,8 @@ static constexpr size_t TLuaArgTypes_Bool = 3;
class TLuaPlugin; class TLuaPlugin;
struct TLuaResult { struct TLuaResult {
std::atomic_bool Ready; bool Ready;
std::atomic_bool Error; bool Error;
std::string ErrorMessage; std::string ErrorMessage;
sol::object Result { sol::lua_nil }; sol::object Result { sol::lua_nil };
TLuaStateId StateId; TLuaStateId StateId;
@@ -46,6 +48,7 @@ struct TLuaPluginConfig {
static inline const std::string FileName = "PluginConfig.toml"; static inline const std::string FileName = "PluginConfig.toml";
TLuaStateId StateId; TLuaStateId StateId;
// TODO: Add execute list // TODO: Add execute list
// TODO: Build a better toml serializer, or some way to do this in an easier way
}; };
struct TLuaChunk { struct TLuaChunk {
@@ -57,21 +60,20 @@ struct TLuaChunk {
std::string PluginPath; std::string PluginPath;
}; };
class TPluginMonitor : IThreaded { class TLuaEngine : public std::enable_shared_from_this<TLuaEngine>, IThreaded {
public: public:
TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown); enum CallStrategy : int {
BestEffort,
Precise,
};
void operator()(); struct QueuedFunction {
std::string FunctionName;
std::shared_ptr<TLuaResult> Result;
std::vector<TLuaArgTypes> Args;
std::string EventName; // optional, may be empty
};
private:
TLuaEngine& mEngine;
fs::path mPath;
std::atomic_bool& mShutdown;
std::unordered_map<std::string, fs::file_time_type> mFileTimes;
};
class TLuaEngine : IThreaded {
public:
TLuaEngine(); TLuaEngine();
~TLuaEngine() noexcept { ~TLuaEngine() noexcept {
beammp_debug("Lua Engine terminated"); beammp_debug("Lua Engine terminated");
@@ -89,6 +91,7 @@ public:
std::unique_lock Lock(mResultsToCheckMutex); std::unique_lock Lock(mResultsToCheckMutex);
return mResultsToCheck.size(); return mResultsToCheck.size();
} }
size_t GetLuaStateCount() { size_t GetLuaStateCount() {
std::unique_lock Lock(mLuaStatesMutex); std::unique_lock Lock(mLuaStatesMutex);
return mLuaStates.size(); return mLuaStates.size();
@@ -116,7 +119,6 @@ public:
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(TLuaStateId StateID, const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args); [[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 EnsureStateExists(TLuaStateId StateId, const std::string& Name, bool DontCallOnInit = false);
void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName); void RegisterEvent(const std::string& EventName, TLuaStateId StateId, const std::string& FunctionName);
template <typename... ArgsT>
/** /**
* *
* @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means * @tparam ArgsT Template Arguments for the event (Metadata) todo: figure out what this means
@@ -125,6 +127,7 @@ public:
* @param Args * @param Args
* @return * @return
*/ */
template <typename... ArgsT>
[[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) { [[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerEvent(const std::string& EventName, TLuaStateId IgnoreId, ArgsT&&... Args) {
std::unique_lock Lock(mLuaEventsMutex); std::unique_lock Lock(mLuaEventsMutex);
beammp_event(EventName); beammp_event(EventName);
@@ -133,23 +136,49 @@ public:
} }
std::vector<std::shared_ptr<TLuaResult>> Results; 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& Event : mLuaEvents.at(EventName)) {
for (const auto& Function : Event.second) { for (const auto& Function : Event.second) {
if (Event.first != IgnoreId) { if (Event.first != IgnoreId) {
Results.push_back(EnqueueFunctionCall(Event.first, Function, { TLuaArgTypes { std::forward<ArgsT>(Args) }... })); Results.push_back(EnqueueFunctionCall(Event.first, Function, Arguments));
} }
} }
} }
return Results; // return Results; //
} }
template <typename... ArgsT>
[[nodiscard]] std::vector<std::shared_ptr<TLuaResult>> TriggerLocalEvent(const TLuaStateId& StateId, const std::string& EventName, ArgsT&&... Args) {
std::unique_lock Lock(mLuaEventsMutex);
beammp_event(EventName + " in '" + StateId + "'");
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) }... };
const auto Handlers = GetEventHandlersForState(EventName, StateId);
for (const auto& Handler : Handlers) {
Results.push_back(EnqueueFunctionCall(StateId, Handler, Arguments));
}
return Results;
}
std::set<std::string> GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId); std::set<std::string> GetEventHandlersForState(const std::string& EventName, TLuaStateId StateId);
void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS); void CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy);
void CancelEventTimers(const std::string& EventName, TLuaStateId StateId); void CancelEventTimers(const std::string& EventName, TLuaStateId StateId);
sol::state_view GetStateForPlugin(const fs::path& PluginPath); sol::state_view GetStateForPlugin(const fs::path& PluginPath);
TLuaStateId GetStateIDForPlugin(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"; static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND";
std::vector<std::string> GetStateGlobalKeysForState(TLuaStateId StateId);
// Debugging functions (slow)
std::unordered_map<std::string /*event name */, std::vector<std::string> /* handlers */> Debug_GetEventsForState(TLuaStateId StateId);
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Debug_GetStateExecuteQueueForState(TLuaStateId StateId);
std::vector<QueuedFunction> Debug_GetStateFunctionQueueForState(TLuaStateId StateId);
std::vector<TLuaResult> Debug_GetResultsToCheckForState(TLuaStateId StateId);
private: private:
void CollectAndInitPlugins(); void CollectAndInitPlugins();
void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config); void InitializePlugin(const fs::path& Folder, const TLuaPluginConfig& Config);
@@ -158,16 +187,23 @@ private:
class StateThreadData : IThreaded { class StateThreadData : IThreaded {
public: public:
StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine); StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine);
StateThreadData(const StateThreadData&) = delete; StateThreadData(const StateThreadData&) = delete;
~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); } ~StateThreadData() noexcept { beammp_debug("\"" + mStateId + "\" destroyed"); }
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script); [[nodiscard]] std::shared_ptr<TLuaResult> EnqueueScript(const TLuaChunk& Script);
[[nodiscard]] std::shared_ptr<TLuaResult> EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args); [[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 RegisterEvent(const std::string& EventName, const std::string& FunctionName);
void AddPath(const fs::path& Path); // to be added to path and cpath void AddPath(const fs::path& Path); // to be added to path and cpath
void operator()() override; void operator()() override;
sol::state_view State() { return sol::state_view(mState); } 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: private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs); sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs); sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
@@ -176,22 +212,26 @@ private:
std::string Lua_GetPlayerName(int ID); std::string Lua_GetPlayerName(int ID);
sol::table Lua_GetPlayerVehicles(int ID); sol::table Lua_GetPlayerVehicles(int ID);
sol::table Lua_HttpCreateConnection(const std::string& host, uint16_t port); 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); 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::string mName;
std::atomic_bool& mShutdown;
TLuaStateId mStateId; TLuaStateId mStateId;
lua_State* mState; lua_State* mState;
std::thread mThread; std::thread mThread;
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> mStateExecuteQueue; std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> mStateExecuteQueue;
std::recursive_mutex mStateExecuteQueueMutex; std::recursive_mutex mStateExecuteQueueMutex;
std::queue<std::tuple<std::string, std::shared_ptr<TLuaResult>, std::vector<TLuaArgTypes>>> mStateFunctionQueue; std::vector<QueuedFunction> mStateFunctionQueue;
std::mutex mStateFunctionQueueMutex; std::mutex mStateFunctionQueueMutex;
std::condition_variable mStateFunctionQueueCond; std::condition_variable mStateFunctionQueueCond;
TLuaEngine* mEngine; TLuaEngine* mEngine;
sol::state_view mStateView { mState }; sol::state_view mStateView { mState };
std::queue<fs::path> mPaths; std::queue<fs::path> mPaths;
std::recursive_mutex mPathsMutex; std::recursive_mutex mPathsMutex;
std::mt19937 mMersenneTwister;
std::uniform_real_distribution<double> mUniformRealDistribution01;
}; };
struct TimedEvent { struct TimedEvent {
@@ -199,15 +239,14 @@ private:
std::chrono::high_resolution_clock::time_point LastCompletion {}; std::chrono::high_resolution_clock::time_point LastCompletion {};
std::string EventName; std::string EventName;
TLuaStateId StateId; TLuaStateId StateId;
CallStrategy Strategy;
bool Expired(); bool Expired();
void Reset(); void Reset();
}; };
TNetwork* mNetwork; TNetwork* mNetwork;
TServer* mServer; TServer* mServer;
TPluginMonitor mPluginMonitor; const fs::path mResourceServerPath;
std::atomic_bool mShutdown { false };
fs::path mResourceServerPath;
std::vector<std::shared_ptr<TLuaPlugin>> mLuaPlugins; std::vector<std::shared_ptr<TLuaPlugin>> mLuaPlugins;
std::unordered_map<TLuaStateId, std::unique_ptr<StateThreadData>> mLuaStates; std::unordered_map<TLuaStateId, std::unique_ptr<StateThreadData>> mLuaStates;
std::recursive_mutex mLuaStatesMutex; std::recursive_mutex mLuaStatesMutex;
@@ -215,8 +254,9 @@ private:
std::recursive_mutex mLuaEventsMutex; std::recursive_mutex mLuaEventsMutex;
std::vector<TimedEvent> mTimedEvents; std::vector<TimedEvent> mTimedEvents;
std::recursive_mutex mTimedEventsMutex; std::recursive_mutex mTimedEventsMutex;
std::queue<std::shared_ptr<TLuaResult>> mResultsToCheck; std::list<std::shared_ptr<TLuaResult>> mResultsToCheck;
std::recursive_mutex mResultsToCheckMutex; 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); // std::any TriggerLuaEvent(const std::string& Event, bool local, TLuaPlugin* Caller, std::shared_ptr<TLuaArg> arg, bool Wait);
+1 -1
View File
@@ -32,7 +32,6 @@ private:
TServer& mServer; TServer& mServer;
TPPSMonitor& mPPSMonitor; TPPSMonitor& mPPSMonitor;
SOCKET mUDPSock {}; SOCKET mUDPSock {};
bool mShutdown { false };
TResourceManager& mResourceManager; TResourceManager& mResourceManager;
std::thread mUDPThread; std::thread mUDPThread;
std::thread mTCPThread; std::thread mTCPThread;
@@ -48,4 +47,5 @@ private:
void SendFile(TClient& c, const std::string& Name); void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size); static bool TCPSendRaw(TClient& C, SOCKET socket, char* Data, int32_t Size);
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name); static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
static uint8_t* SendSplit(TClient& c, SOCKET Socket, uint8_t* DataPtr, size_t Size);
}; };
+1 -2
View File
@@ -22,6 +22,5 @@ private:
TServer& mServer; TServer& mServer;
std::optional<std::reference_wrapper<TNetwork>> mNetwork { std::nullopt }; std::optional<std::reference_wrapper<TNetwork>> mNetwork { std::nullopt };
bool mShutdown { false };
int mInternalPPS { 0 }; int mInternalPPS { 0 };
}; };
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Common.h"
#include "IThreaded.h"
#include <atomic>
#include <memory>
#include <unordered_map>
class TLuaEngine;
class TPluginMonitor : IThreaded, public std::enable_shared_from_this<TPluginMonitor> {
public:
TPluginMonitor(const fs::path& Path, std::shared_ptr<TLuaEngine> Engine);
void operator()();
private:
std::shared_ptr<TLuaEngine> mEngine;
fs::path mPath;
std::unordered_map<std::string, fs::file_time_type> mFileTimes;
};
+78 -2
View File
@@ -1,6 +1,7 @@
#include "ArgsParser.h" #include "ArgsParser.h"
#include "Common.h" #include "Common.h"
#include <algorithm> #include <algorithm>
#include <doctest/doctest.h>
void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) { void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
for (const auto& Arg : ArgList) { for (const auto& Arg : ArgList) {
@@ -12,7 +13,7 @@ void ArgsParser::Parse(const std::vector<std::string_view>& ArgList) {
ConsumeLongFlag(std::string(Arg)); ConsumeLongFlag(std::string(Arg));
} }
} else { } else {
beammp_error("Error parsing commandline arguments: Supplied argument '" + std::string(Arg) + "' is not a valid argument and was ignored."); beammp_errorf("Error parsing commandline arguments: Supplied argument '{}' is not a valid argument and was ignored.", Arg);
} }
} }
} }
@@ -21,7 +22,7 @@ bool ArgsParser::Verify() {
bool Ok = true; bool Ok = true;
for (const auto& RegisteredArg : mRegisteredArguments) { for (const auto& RegisteredArg : mRegisteredArguments) {
if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) { if (RegisteredArg.Flags & Flags::REQUIRED && !FoundArgument(RegisteredArg.Names)) {
beammp_error("Error in commandline arguments: Argument '" + std::string(RegisteredArg.Names.at(0)) + "' is required but wasn't found."); beammp_errorf("Error in commandline arguments: Argument '{}' is required but wasn't found.", RegisteredArg.Names.at(0));
Ok = false; Ok = false;
continue; continue;
} else if (FoundArgument(RegisteredArg.Names)) { } else if (FoundArgument(RegisteredArg.Names)) {
@@ -92,3 +93,78 @@ void ArgsParser::ConsumeLongFlag(const std::string& Arg) {
beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored."); beammp_warn("Argument '" + Name + "' was supplied but isn't a known argument, so it is likely being ignored.");
} }
} }
TEST_CASE("ArgsParser") {
ArgsParser parser;
SUBCASE("Simple args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({ "--a", "--hello" });
CHECK(parser.Verify());
CHECK(parser.FoundArgument({ "a" }));
CHECK(parser.FoundArgument({ "hello" }));
CHECK(parser.FoundArgument({ "a", "hello" }));
CHECK(!parser.FoundArgument({ "b" }));
CHECK(!parser.FoundArgument({ "goodbye" }));
}
SUBCASE("No args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::NONE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({});
CHECK(parser.Verify());
CHECK(!parser.FoundArgument({ "a" }));
CHECK(!parser.FoundArgument({ "hello" }));
CHECK(!parser.FoundArgument({ "a", "hello" }));
CHECK(!parser.FoundArgument({ "b" }));
CHECK(!parser.FoundArgument({ "goodbye" }));
CHECK(!parser.FoundArgument({ "" }));
}
SUBCASE("Value args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::HAS_VALUE);
parser.Parse({ "--a=5", "--hello=world" });
CHECK(parser.Verify());
REQUIRE(parser.FoundArgument({ "a" }));
REQUIRE(parser.FoundArgument({ "hello" }));
CHECK(parser.GetValueOfArgument({ "a" }).has_value());
CHECK(parser.GetValueOfArgument({ "a" }).value() == "5");
CHECK(parser.GetValueOfArgument({ "hello" }).has_value());
CHECK(parser.GetValueOfArgument({ "hello" }).value() == "world");
}
SUBCASE("Mixed value & no-value args") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::HAS_VALUE);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::NONE);
parser.Parse({ "--a=5", "--hello" });
CHECK(parser.Verify());
REQUIRE(parser.FoundArgument({ "a" }));
REQUIRE(parser.FoundArgument({ "hello" }));
CHECK(parser.GetValueOfArgument({ "a" }).has_value());
CHECK(parser.GetValueOfArgument({ "a" }).value() == "5");
CHECK(!parser.GetValueOfArgument({ "hello" }).has_value());
}
SUBCASE("Required args") {
SUBCASE("Two required, two present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--a", "--hello" });
CHECK(parser.Verify());
}
SUBCASE("Two required, one present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--a" });
CHECK(!parser.Verify());
}
SUBCASE("Two required, none present") {
parser.RegisterArgument({ "a" }, ArgsParser::Flags::REQUIRED);
parser.RegisterArgument({ "hello" }, ArgsParser::Flags::REQUIRED);
parser.Parse({ "--b" });
CHECK(!parser.Verify());
}
}
}
+1 -2
View File
@@ -5,8 +5,6 @@
#include <memory> #include <memory>
#include <optional> #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);
auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) { auto iter = std::find_if(mVehicleData.begin(), mVehicleData.end(), [&](auto& elem) {
@@ -27,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) {
+171 -31
View File
@@ -12,6 +12,9 @@
#include "CustomAssert.h" #include "CustomAssert.h"
#include "Http.h" #include "Http.h"
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
Application::TSettings Application::Settings = {}; Application::TSettings Application::Settings = {};
void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
@@ -22,6 +25,7 @@ void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) {
} }
void Application::GracefullyShutdown() { void Application::GracefullyShutdown() {
SetShutdown(true);
static bool AlreadyShuttingDown = false; static bool AlreadyShuttingDown = false;
static uint8_t ShutdownAttempts = 0; static uint8_t ShutdownAttempts = 0;
if (AlreadyShuttingDown) { if (AlreadyShuttingDown) {
@@ -43,6 +47,7 @@ void Application::GracefullyShutdown() {
beammp_info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down"); beammp_info("Subsystem " + std::to_string(i + 1) + "/" + std::to_string(mShutdownHandlers.size()) + " shutting down");
mShutdownHandlers[i](); mShutdownHandlers[i]();
} }
// std::exit(-1);
} }
std::string Application::ServerVersionString() { std::string Application::ServerVersionString() {
@@ -60,7 +65,23 @@ std::array<uint8_t, 3> Application::VersionStrToInts(const std::string& str) {
return Version; return Version;
} }
// FIXME: This should be used by operator< on Version TEST_CASE("Application::VersionStrToInts") {
auto v = Application::VersionStrToInts("1.2.3");
CHECK(v[0] == 1);
CHECK(v[1] == 2);
CHECK(v[2] == 3);
v = Application::VersionStrToInts("10.20.30");
CHECK(v[0] == 10);
CHECK(v[1] == 20);
CHECK(v[2] == 30);
v = Application::VersionStrToInts("100.200.255");
CHECK(v[0] == 100);
CHECK(v[1] == 200);
CHECK(v[2] == 255);
}
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;
@@ -73,6 +94,65 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) {
} }
} }
bool Application::IsShuttingDown() {
std::shared_lock Lock(mShutdownMtx);
return mShutdown;
}
void Application::SleepSafeSeconds(size_t Seconds) {
// Sleeps for 500 ms, checks if a shutdown occurred, and so forth
for (size_t i = 0; i < Seconds * 2; ++i) {
if (Application::IsShuttingDown()) {
return;
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
}
TEST_CASE("Application::IsOutdated (version check)") {
SUBCASE("Same version") {
CHECK(!Application::IsOutdated({ 1, 2, 3 }, { 1, 2, 3 }));
}
// we need to use over 1-2 digits to test against lexical comparisons
SUBCASE("Patch outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor), uint8_t(Patch + 1) }));
}
}
}
}
SUBCASE("Minor outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major), uint8_t(Minor + 1), uint8_t(Patch) }));
}
}
}
}
SUBCASE("Major outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor), uint8_t(Patch) }));
}
}
}
}
SUBCASE("All outdated") {
for (uint8_t Patch = 0; Patch < 10; ++Patch) {
for (uint8_t Minor = 0; Minor < 10; ++Minor) {
for (uint8_t Major = 0; Major < 10; ++Major) {
CHECK(Application::IsOutdated({ uint8_t(Major), uint8_t(Minor), uint8_t(Patch) }, { uint8_t(Major + 1), uint8_t(Minor + 1), uint8_t(Patch + 1) }));
}
}
}
}
}
void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) { void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) {
switch (status) { switch (status) {
case Status::Good: case Status::Good:
@@ -95,30 +175,58 @@ void Application::SetSubsystemStatus(const std::string& Subsystem, Status status
mSystemStatusMap[Subsystem] = status; mSystemStatusMap[Subsystem] = status;
} }
void Application::SetShutdown(bool Val) {
std::unique_lock Lock(mShutdownMtx);
mShutdown = Val;
}
TEST_CASE("Application::SetSubsystemStatus") {
Application::SetSubsystemStatus("Test", Application::Status::Good);
auto Map = Application::GetSubsystemStatuses();
CHECK(Map.at("Test") == Application::Status::Good);
Application::SetSubsystemStatus("Test", Application::Status::Bad);
Map = Application::GetSubsystemStatuses();
CHECK(Map.at("Test") == Application::Status::Bad);
}
void Application::CheckForUpdates() { void Application::CheckForUpdates() {
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); 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)) {
beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info 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 {
beammp_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);
}
} }
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good);
} else {
beammp_warn("Unable to fetch version from backend.");
beammp_trace("got " + Response);
auto Lock = Sentry.CreateExclusiveContext();
Sentry.SetContext("get-response", { { "response", Response } });
Sentry.LogError("failed to get server version", _file_basename, _line);
Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad);
} }
if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) {
if (FirstTime) {
beammp_warn("Unable to fetch version info from backend.");
}
}
FirstTime = false;
} }
// thread name stuff // thread name stuff
@@ -138,6 +246,25 @@ std::string ThreadName(bool DebugModeOverride) {
return ""; return "";
} }
TEST_CASE("ThreadName") {
RegisterThread("MyThread");
auto OrigDebug = Application::Settings.DebugModeEnabled;
// ThreadName adds a space at the end, legacy but we need it still
SUBCASE("Debug mode enabled") {
Application::Settings.DebugModeEnabled = true;
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "MyThread ");
}
SUBCASE("Debug mode disabled") {
Application::Settings.DebugModeEnabled = false;
CHECK(ThreadName(true) == "MyThread ");
CHECK(ThreadName(false) == "");
}
// cleanup
Application::Settings.DebugModeEnabled = OrigDebug;
}
void RegisterThread(const std::string& str) { void RegisterThread(const std::string& str) {
std::string ThreadId; std::string ThreadId;
#ifdef BEAMMP_WINDOWS #ifdef BEAMMP_WINDOWS
@@ -148,13 +275,18 @@ void RegisterThread(const std::string& str) {
ThreadId = std::to_string(gettid()); ThreadId = std::to_string(gettid());
#endif #endif
if (Application::Settings.DebugModeEnabled) { if (Application::Settings.DebugModeEnabled) {
std::ofstream ThreadFile("Threads.log", std::ios::app); std::ofstream ThreadFile(".Threads.log", std::ios::app);
ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl; ThreadFile << ("Thread \"" + str + "\" is TID " + ThreadId) << std::endl;
} }
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;
} }
TEST_CASE("RegisterThread") {
RegisterThread("MyThread");
CHECK(threadNameMap.at(std::this_thread::get_id()) == "MyThread");
}
Version::Version(uint8_t major, uint8_t minor, uint8_t patch) Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
: major(major) : major(major)
, minor(minor) , minor(minor)
@@ -165,22 +297,30 @@ 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();
TEST_CASE("Version::AsString") {
CHECK(Version { 0, 0, 0 }.AsString() == "0.0.0");
CHECK(Version { 1, 2, 3 }.AsString() == "1.2.3");
CHECK(Version { 255, 255, 255 }.AsString() == "255.255.255");
} }
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 << ThreadName(); std::stringstream ss;
ss << "[CHAT] "; ss << ThreadName();
if (id != -1) { ss << "[CHAT] ";
ss << "(" << id << ") <" << name << "> "; if (id != -1) {
} else { ss << "(" << id << ") <" << name << "> ";
ss << name << ""; } else {
ss << name << "";
}
ss << msg;
#ifdef DOCTEST_CONFIG_DISABLE
Application::Console().Write(ss.str());
#endif
} }
ss << msg;
Application::Console().Write(ss.str());
} }
std::string GetPlatformAgnosticErrorString() { std::string GetPlatformAgnosticErrorString() {
+20
View File
@@ -1,5 +1,8 @@
#include "Compat.h" #include "Compat.h"
#include <cstring>
#include <doctest/doctest.h>
#ifndef WIN32 #ifndef WIN32
static struct termios old, current; static struct termios old, current;
@@ -20,6 +23,23 @@ void resetTermios(void) {
tcsetattr(0, TCSANOW, &old); tcsetattr(0, TCSANOW, &old);
} }
TEST_CASE("init and reset termios") {
if (isatty(STDIN_FILENO)) {
struct termios original;
tcgetattr(0, &original);
SUBCASE("no echo") {
initTermios(false);
}
SUBCASE("yes echo") {
initTermios(true);
}
resetTermios();
struct termios current;
tcgetattr(0, &current);
CHECK(std::memcmp(&original, &current, sizeof(struct termios)) == 0);
}
}
char getch_(int echo) { char getch_(int echo) {
char ch; char ch;
initTermios(echo); initTermios(echo);
+30 -197
View File
@@ -4,19 +4,16 @@
#include "Common.h" #include "Common.h"
#include "CustomAssert.h" #include "CustomAssert.h"
#include "LuaAPI.h" #include "LuaAPI.h"
#include "httplib.h"
#include <map> #include <map>
#include <nlohmann/json.hpp>
#include <random> #include <random>
#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <stdexcept> #include <stdexcept>
fs::path Http::Server::THttpServerInstance::KeyFilePath;
fs::path Http::Server::THttpServerInstance::CertFilePath;
// TODO: Add sentry error handling back // TODO: Add sentry error handling back
namespace json = rapidjson; using json = nlohmann::json;
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) { std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
httplib::SSLClient client(host, port); httplib::SSLClient client(host, port);
@@ -33,17 +30,20 @@ std::string Http::GET(const std::string& host, int port, const std::string& targ
} }
} }
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) { std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) {
httplib::SSLClient client(host, port); httplib::SSLClient client(host, port);
client.set_read_timeout(std::chrono::seconds(10));
beammp_assert(client.is_valid());
client.enable_server_certificate_verification(false); client.enable_server_certificate_verification(false);
client.set_address_family(AF_INET); client.set_address_family(AF_INET);
auto res = client.Post(target.c_str(), body.c_str(), body.size(), ContentType.c_str()); auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str());
if (res) { if (res) {
if (status) { if (status) {
*status = res->status; *status = res->status;
} }
return res->body; return res->body;
} else { } else {
beammp_debug("POST failed: " + httplib::to_string(res.error()));
return Http::ErrorString; return Http::ErrorString;
} }
} }
@@ -142,145 +142,10 @@ std::string Http::Status::ToString(int Code) {
} }
} }
long Http::Server::Tx509KeypairGenerator::GenerateRandomId() { TEST_CASE("Http::Status::ToString") {
std::random_device R; CHECK(Http::Status::ToString(200) == "OK");
std::default_random_engine E1(R()); CHECK(Http::Status::ToString(696969) == "696969");
std::uniform_int_distribution<long> UniformDist(0, ULONG_MAX); CHECK(Http::Status::ToString(-1) == "Invalid Response Code");
return UniformDist(E1);
}
// Http::Server::THttpServerInstance::THttpServerInstance() { }
EVP_PKEY* Http::Server::Tx509KeypairGenerator::GenerateKey() {
/**
* Allocate memory for the pkey
*/
EVP_PKEY* PKey = EVP_PKEY_new();
if (PKey == nullptr) {
beammp_error("Could not allocate memory for X.509 private key (PKEY) generation.");
throw std::runtime_error { std::string { "X.509 PKEY allocation error" } };
}
BIGNUM* E = BN_new();
beammp_assert(E); // TODO: replace all these asserts with beammp_errors
unsigned char three = 3;
BIGNUM* EErr = BN_bin2bn(&three, sizeof(three), E);
beammp_assert(EErr);
RSA* Rsa = RSA_new();
beammp_assert(Rsa);
int Ret = RSA_generate_key_ex(Rsa, Crypto::RSA_DEFAULT_KEYLENGTH, E, nullptr);
beammp_assert(Ret == 1);
BN_free(E);
if (!EVP_PKEY_assign_RSA(PKey, Rsa)) {
EVP_PKEY_free(PKey);
beammp_error(std::string("Could not generate " + std::to_string(Crypto::RSA_DEFAULT_KEYLENGTH) + "-bit RSA key."));
throw std::runtime_error { std::string("X.509 RSA key generation error") };
}
// todo: figure out if returning by reference instead of passing pointers is a security breach
return PKey;
}
X509* Http::Server::Tx509KeypairGenerator::GenerateCertificate(EVP_PKEY& PKey) {
X509* X509 = X509_new();
if (X509 == nullptr) {
X509_free(X509);
beammp_error("Could not allocate memory for X.509 certificate generation.");
throw std::runtime_error { std::string("X.509 certificate generation error") };
}
/**Set the metadata of the certificate*/
ASN1_INTEGER_set(X509_get_serialNumber(X509), GenerateRandomId());
/**Set the cert validity to a year*/
X509_gmtime_adj(X509_get_notBefore(X509), 0);
X509_gmtime_adj(X509_get_notAfter(X509), 31536000L);
/**Set the public key of the cert*/
X509_set_pubkey(X509, &PKey);
X509_NAME* Name = X509_get_subject_name(X509);
/**Set cert metadata*/
X509_NAME_add_entry_by_txt(Name, "C", MBSTRING_ASC, (unsigned char*)"GB", -1, -1, 0);
X509_NAME_add_entry_by_txt(Name, "O", MBSTRING_ASC, (unsigned char*)"BeamMP Ltd.", -1, -1, 0);
X509_NAME_add_entry_by_txt(Name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0);
X509_set_issuer_name(X509, Name);
// TODO: Hashing with sha256 might cause problems, check later
if (!X509_sign(X509, &PKey, EVP_sha1())) {
X509_free(X509);
beammp_error("Could not sign X.509 certificate.");
throw std::runtime_error { std::string("X.509 certificate signing error") };
}
return X509;
}
void Http::Server::Tx509KeypairGenerator::GenerateAndWriteToDisk(const fs::path& KeyFilePath, const fs::path& CertFilePath) {
// todo: generate directories for ssl keys
FILE* KeyFile = std::fopen(reinterpret_cast<const char*>(KeyFilePath.c_str()), "wb");
if (!KeyFile) {
beammp_error("Could not create file 'key.pem', check your permissions");
throw std::runtime_error("Could not create file 'key.pem'");
}
EVP_PKEY* PKey = Http::Server::Tx509KeypairGenerator::GenerateKey();
bool WriteOpResult = PEM_write_PrivateKey(KeyFile, PKey, nullptr, nullptr, 0, nullptr, nullptr);
fclose(KeyFile);
if (!WriteOpResult) {
beammp_error("Could not write to file 'key.pem', check your permissions");
throw std::runtime_error("Could not write to file 'key.pem'");
}
FILE* CertFile = std::fopen(reinterpret_cast<const char*>(CertFilePath.c_str()), "wb"); // x509 file
if (!CertFile) {
beammp_error("Could not create file 'cert.pem', check your permissions");
throw std::runtime_error("Could not create file 'cert.pem'");
}
X509* x509 = Http::Server::Tx509KeypairGenerator::GenerateCertificate(*PKey);
WriteOpResult = PEM_write_X509(CertFile, x509);
fclose(CertFile);
if (!WriteOpResult) {
beammp_error("Could not write to file 'cert.pem', check your permissions");
throw std::runtime_error("Could not write to file 'cert.pem'");
}
EVP_PKEY_free(PKey);
X509_free(x509);
return;
}
bool Http::Server::Tx509KeypairGenerator::EnsureTLSConfigExists() {
if (fs::is_regular_file(Application::Settings.SSLKeyPath)
&& fs::is_regular_file(Application::Settings.SSLCertPath)) {
return true;
} else {
return false;
}
}
void Http::Server::SetupEnvironment() {
if (!Application::Settings.HTTPServerUseSSL) {
return;
}
auto parent = fs::path(Application::Settings.SSLKeyPath).parent_path();
if (!fs::exists(parent))
fs::create_directories(parent);
Application::TSettings defaultSettings {};
if (!Tx509KeypairGenerator::EnsureTLSConfigExists()) {
beammp_warn(std::string("No default TLS Key / Cert found. "
"IF YOU HAVE NOT MODIFIED THE SSLKeyPath OR SSLCertPath VALUES "
"THIS IS NORMAL ON FIRST STARTUP! BeamMP will generate it's own certs in the default directory "
"(Check for permissions or corrupted key-/certfile)"));
Tx509KeypairGenerator::GenerateAndWriteToDisk(defaultSettings.SSLKeyPath, defaultSettings.SSLCertPath);
Http::Server::THttpServerInstance::KeyFilePath = defaultSettings.SSLKeyPath;
Http::Server::THttpServerInstance::CertFilePath = defaultSettings.SSLCertPath;
} else {
Http::Server::THttpServerInstance::KeyFilePath = Application::Settings.SSLKeyPath;
Http::Server::THttpServerInstance::CertFilePath = Application::Settings.SSLCertPath;
}
} }
Http::Server::THttpServerInstance::THttpServerInstance() { Http::Server::THttpServerInstance::THttpServerInstance() {
@@ -289,16 +154,10 @@ Http::Server::THttpServerInstance::THttpServerInstance() {
mThread.detach(); mThread.detach();
} }
void Http::Server::THttpServerInstance::operator()() { void Http::Server::THttpServerInstance::operator()() try {
beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort)); beammp_info("HTTP(S) Server started on port " + std::to_string(Application::Settings.HTTPServerPort));
std::unique_ptr<httplib::Server> HttpLibServerInstance; std::unique_ptr<httplib::Server> HttpLibServerInstance;
if (Application::Settings.HTTPServerUseSSL) { HttpLibServerInstance = std::make_unique<httplib::Server>();
HttpLibServerInstance = std::make_unique<httplib::SSLServer>(
reinterpret_cast<const char*>(Http::Server::THttpServerInstance::CertFilePath.c_str()),
reinterpret_cast<const char*>(Http::Server::THttpServerInstance::KeyFilePath.c_str()));
} else {
HttpLibServerInstance = std::make_unique<httplib::Server>();
}
// todo: make this IP agnostic so people can set their own IP // todo: make this IP agnostic so people can set their own IP
HttpLibServerInstance->Get("/", [](const httplib::Request&, httplib::Response& res) { HttpLibServerInstance->Get("/", [](const httplib::Request&, httplib::Response& res) {
res.set_content("<!DOCTYPE html><article><h1>Hello World!</h1><section><p>BeamMP Server can now serve HTTP requests!</p></section></article></html>", "text/html"); res.set_content("<!DOCTYPE html><article><h1>Hello World!</h1><section><p>BeamMP Server can now serve HTTP requests!</p></section></article></html>", "text/html");
@@ -320,52 +179,26 @@ void Http::Server::THttpServerInstance::operator()() {
break; break;
} }
} }
res.set_content(SystemsBad == 0 ? "0" : "1", "text/plain"); res.set_content(
json {
{ "ok", SystemsBad == 0 },
}
.dump(),
"application/json");
res.status = 200; res.status = 200;
}); });
/*
HttpLibServerInstance->Get("/status", [](const httplib::Request&, httplib::Response& res) {
try {
json::Document response;
response.SetObject();
rapidjson::Document::AllocatorType& Allocator = response.GetAllocator();
// add to response
auto& Server = LuaAPI::MP::Engine->Server();
size_t CarCount = 0;
size_t GuestCount = 0;
json::Value Array(rapidjson::kArrayType);
LuaAPI::MP::Engine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
if (!Client.expired()) {
auto Locked = Client.lock();
CarCount += Locked->GetCarCount();
GuestCount += Locked->IsGuest() ? 1 : 0;
json::Value Player(json::kObjectType);
Player.AddMember("name", json::StringRef(Locked->GetName().c_str()), Allocator);
Player.AddMember("id", Locked->GetID(), Allocator);
Array.PushBack(Player, Allocator);
}
return true;
});
response.AddMember("players", Array, Allocator);
response.AddMember("player_count", Server.ClientCount(), Allocator);
response.AddMember("guest_count", GuestCount, Allocator);
response.AddMember("car_count", CarCount, Allocator);
// compile & send response
json::StringBuffer sb;
json::Writer<json::StringBuffer> writer(sb);
response.Accept(writer);
res.set_content(sb.GetString(), "application/json");
} catch (const std::exception& e) {
beammp_error("Exception in /status endpoint: " + std::string(e.what()));
res.status = 500;
}
});
*/
// magic endpoint // magic endpoint
HttpLibServerInstance->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) { HttpLibServerInstance->Get({ 0x2f, 0x6b, 0x69, 0x74, 0x74, 0x79 }, [](const httplib::Request&, httplib::Response& res) {
res.set_content(std::string(Magic), "text/plain"); res.set_content(std::string(Magic), "text/plain");
}); });
HttpLibServerInstance->set_logger([](const httplib::Request& Req, const httplib::Response& Res) {
beammp_debug("Http Server: " + Req.method + " " + Req.target + " -> " + std::to_string(Res.status));
});
Application::SetSubsystemStatus("HTTPServer", Application::Status::Good); Application::SetSubsystemStatus("HTTPServer", Application::Status::Good);
HttpLibServerInstance->listen("0.0.0.0", Application::Settings.HTTPServerPort); auto ret = HttpLibServerInstance->listen(Application::Settings.HTTPServerIP.c_str(), Application::Settings.HTTPServerPort);
if (!ret) {
beammp_error("Failed to start http server (failed to listen). Please ensure the http server is configured properly in the ServerConfig.toml, or turn it off if you don't need it.");
}
} catch (const std::exception& e) {
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()));
} }
+313 -16
View File
@@ -3,6 +3,8 @@
#include "Common.h" #include "Common.h"
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include <nlohmann/json.hpp>
#define SOL_ALL_SAFETIES_ON 1 #define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp> #include <sol/sol.hpp>
@@ -100,25 +102,39 @@ void LuaAPI::Print(sol::variadic_args Args) {
luaprint(ToPrint); luaprint(ToPrint);
} }
bool LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) { TEST_CASE("LuaAPI::MP::GetServerVersion") {
const auto [ma, mi, pa] = LuaAPI::MP::GetServerVersion();
const auto real = Application::ServerVersion();
CHECK(ma == real.major);
CHECK(mi == real.minor);
CHECK(pa == real.patch);
}
static inline bool InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) {
std::string Packet = "E:" + EventName + ":" + Data; std::string Packet = "E:" + EventName + ":" + Data;
if (PlayerID == -1) if (PlayerID == -1)
Engine->Network().SendToAll(nullptr, Packet, true, true); LuaAPI::MP::Engine->Network().SendToAll(nullptr, Packet, true, true);
else { else {
auto MaybeClient = GetClient(Engine->Server(), PlayerID); auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID);
if (!MaybeClient || MaybeClient.value().expired()) { if (!MaybeClient || MaybeClient.value().expired()) {
beammp_lua_error("TriggerClientEvent invalid Player ID"); beammp_lua_error("TriggerClientEvent invalid Player ID");
return false; return false;
} }
auto c = MaybeClient.value().lock(); auto c = MaybeClient.value().lock();
if (!Engine->Network().Respond(*c, Packet, true)) { if (!LuaAPI::MP::Engine->Network().Respond(*c, Packet, true)) {
beammp_lua_error("Respond failed"); 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 false;
} }
} }
return true; 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) { void LuaAPI::MP::DropPlayer(int ID, std::optional<std::string> MaybeReason) {
auto MaybeClient = GetClient(Engine->Server(), ID); auto MaybeClient = GetClient(Engine->Server(), ID);
if (!MaybeClient || MaybeClient.value().expired()) { if (!MaybeClient || MaybeClient.value().expired()) {
@@ -168,50 +184,57 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
if (NewValue.is<bool>()) { if (NewValue.is<bool>()) {
Application::Settings.DebugModeEnabled = NewValue.as<bool>(); Application::Settings.DebugModeEnabled = NewValue.as<bool>();
beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false")); beammp_info(std::string("Set `Debug` to ") + (Application::Settings.DebugModeEnabled ? "true" : "false"));
} else } else {
beammp_lua_error("set invalid argument [2] expected boolean"); beammp_lua_error("set invalid argument [2] expected boolean");
}
break; break;
case 1: // private case 1: // private
if (NewValue.is<bool>()) { if (NewValue.is<bool>()) {
Application::Settings.Private = NewValue.as<bool>(); Application::Settings.Private = NewValue.as<bool>();
beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false")); beammp_info(std::string("Set `Private` to ") + (Application::Settings.Private ? "true" : "false"));
} else } else {
beammp_lua_error("set invalid argument [2] expected boolean"); beammp_lua_error("set invalid argument [2] expected boolean");
}
break; break;
case 2: // max cars case 2: // max cars
if (NewValue.is<int>()) { if (NewValue.is<int>()) {
Application::Settings.MaxCars = NewValue.as<int>(); Application::Settings.MaxCars = NewValue.as<int>();
beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars)); beammp_info(std::string("Set `MaxCars` to ") + std::to_string(Application::Settings.MaxCars));
} else } else {
beammp_lua_error("set invalid argument [2] expected integer"); beammp_lua_error("set invalid argument [2] expected integer");
}
break; break;
case 3: // max players case 3: // max players
if (NewValue.is<int>()) { if (NewValue.is<int>()) {
Application::Settings.MaxPlayers = NewValue.as<int>(); Application::Settings.MaxPlayers = NewValue.as<int>();
beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers)); beammp_info(std::string("Set `MaxPlayers` to ") + std::to_string(Application::Settings.MaxPlayers));
} else } else {
beammp_lua_error("set invalid argument [2] expected integer"); beammp_lua_error("set invalid argument [2] expected integer");
}
break; break;
case 4: // Map case 4: // Map
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.MapName = NewValue.as<std::string>(); Application::Settings.MapName = NewValue.as<std::string>();
beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName); beammp_info(std::string("Set `Map` to ") + Application::Settings.MapName);
} else } else {
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
case 5: // Name case 5: // Name
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.ServerName = NewValue.as<std::string>(); Application::Settings.ServerName = NewValue.as<std::string>();
beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName); beammp_info(std::string("Set `Name` to ") + Application::Settings.ServerName);
} else } else {
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
case 6: // Desc case 6: // Desc
if (NewValue.is<std::string>()) { if (NewValue.is<std::string>()) {
Application::Settings.ServerDesc = NewValue.as<std::string>(); Application::Settings.ServerDesc = NewValue.as<std::string>();
beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc); beammp_info(std::string("Set `Description` to ") + Application::Settings.ServerDesc);
} else } else {
beammp_lua_error("set invalid argument [2] expected string"); beammp_lua_error("set invalid argument [2] expected string");
}
break; break;
default: default:
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this."); beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
@@ -247,7 +270,9 @@ void LuaAPI::MP::PrintRaw(sol::variadic_args Args) {
ToPrint += LuaToString(static_cast<const sol::object>(Arg)); ToPrint += LuaToString(static_cast<const sol::object>(Arg));
ToPrint += "\t"; ToPrint += "\t";
} }
#ifdef DOCTEST_CONFIG_DISABLE
Application::Console().WriteRaw(ToPrint); Application::Console().WriteRaw(ToPrint);
#endif
} }
int LuaAPI::PanicHandler(lua_State* State) { int LuaAPI::PanicHandler(lua_State* State) {
@@ -270,7 +295,7 @@ static std::pair<bool, std::string> FSWrapper(FnT Fn, ArgsT&&... Args) {
std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path) { std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::create_directories(fs::relative(Path), errc); fs::create_directories(Path, errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -278,6 +303,33 @@ std::pair<bool, std::string> LuaAPI::FS::CreateDirectory(const std::string& Path
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::CreateDirectory") {
std::string TestDir = "beammp_test_dir";
fs::remove_all(TestDir);
SUBCASE("Single level dir") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir));
}
SUBCASE("Multi level dir") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir + "/a/b/c");
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir + "/a/b/c"));
}
SUBCASE("Already exists") {
const auto [Ok, Err] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok);
CHECK(Err == "");
CHECK(fs::exists(TestDir));
const auto [Ok2, Err2] = LuaAPI::FS::CreateDirectory(TestDir);
CHECK(Ok2);
CHECK(Err2 == "");
}
fs::remove_all(TestDir);
}
std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) { std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
@@ -289,10 +341,30 @@ std::pair<bool, std::string> LuaAPI::FS::Remove(const std::string& Path) {
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Remove") {
const std::string TestFileOrDir = "beammp_test_thing";
SUBCASE("Remove existing directory") {
fs::create_directory(TestFileOrDir);
const auto [Ok, Err] = LuaAPI::FS::Remove(TestFileOrDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestFileOrDir));
}
SUBCASE("Remove non-existing directory") {
fs::remove_all(TestFileOrDir);
const auto [Ok, Err] = LuaAPI::FS::Remove(TestFileOrDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestFileOrDir));
}
// TODO: add tests for files
// TODO: add tests for files and folders without access permissions (failure)
}
std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const std::string& NewPath) { std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const std::string& NewPath) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::rename(fs::relative(Path), fs::relative(NewPath), errc); fs::rename(Path, NewPath, errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -300,10 +372,25 @@ std::pair<bool, std::string> LuaAPI::FS::Rename(const std::string& Path, const s
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Rename") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
const auto [Ok, Err] = LuaAPI::FS::Rename(TestDir, OtherTestDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(!fs::exists(TestDir));
CHECK(fs::exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
}
std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std::string& NewPath) { std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std::string& NewPath) {
std::error_code errc; std::error_code errc;
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
fs::copy(fs::relative(Path), fs::relative(NewPath), fs::copy_options::recursive, errc); fs::copy(Path, NewPath, fs::copy_options::recursive, errc);
Result.first = errc == std::error_code {}; Result.first = errc == std::error_code {};
if (!Result.first) { if (!Result.first) {
Result.second = errc.message(); Result.second = errc.message();
@@ -311,30 +398,86 @@ std::pair<bool, std::string> LuaAPI::FS::Copy(const std::string& Path, const std
return Result; return Result;
} }
TEST_CASE("LuaAPI::FS::Copy") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
const auto [Ok, Err] = LuaAPI::FS::Copy(TestDir, OtherTestDir);
CHECK(Ok);
CHECK_EQ(Err, "");
CHECK(fs::exists(TestDir));
CHECK(fs::exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
}
bool LuaAPI::FS::Exists(const std::string& Path) { bool LuaAPI::FS::Exists(const std::string& Path) {
return fs::exists(fs::relative(Path)); return fs::exists(Path);
}
TEST_CASE("LuaAPI::FS::Exists") {
const auto TestDir = "beammp_test_dir";
const auto OtherTestDir = "beammp_test_dir_2";
fs::remove_all(OtherTestDir);
fs::create_directory(TestDir);
CHECK(LuaAPI::FS::Exists(TestDir));
CHECK(!LuaAPI::FS::Exists(OtherTestDir));
fs::remove_all(OtherTestDir);
fs::remove_all(TestDir);
} }
std::string LuaAPI::FS::GetFilename(const std::string& Path) { std::string LuaAPI::FS::GetFilename(const std::string& Path) {
return fs::path(Path).filename().string(); return fs::path(Path).filename().string();
} }
TEST_CASE("LuaAPI::FS::GetFilename") {
CHECK(LuaAPI::FS::GetFilename("test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("/test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("place/test.txt") == "test.txt");
CHECK(LuaAPI::FS::GetFilename("/some/../place/test.txt") == "test.txt");
}
std::string LuaAPI::FS::GetExtension(const std::string& Path) { std::string LuaAPI::FS::GetExtension(const std::string& Path) {
return fs::path(Path).extension().string(); return fs::path(Path).extension().string();
} }
TEST_CASE("LuaAPI::FS::GetExtension") {
CHECK(LuaAPI::FS::GetExtension("test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("place/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.txt") == ".txt");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test") == "");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.a.b.c") == ".c");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.") == ".");
CHECK(LuaAPI::FS::GetExtension("/some/../place/test.a.b.") == ".");
}
std::string LuaAPI::FS::GetParentFolder(const std::string& Path) { std::string LuaAPI::FS::GetParentFolder(const std::string& Path) {
return fs::path(Path).parent_path().string(); return fs::path(Path).parent_path().string();
} }
TEST_CASE("LuaAPI::FS::GetParentFolder") {
CHECK(LuaAPI::FS::GetParentFolder("test.txt") == "");
CHECK(LuaAPI::FS::GetParentFolder("/test.txt") == "/");
CHECK(LuaAPI::FS::GetParentFolder("place/test.txt") == "place");
CHECK(LuaAPI::FS::GetParentFolder("/some/../place/test.txt") == "/some/../place");
}
// TODO: add tests
bool LuaAPI::FS::IsDirectory(const std::string& Path) { bool LuaAPI::FS::IsDirectory(const std::string& Path) {
return fs::is_directory(Path); return fs::is_directory(Path);
} }
// TODO: add tests
bool LuaAPI::FS::IsFile(const std::string& Path) { bool LuaAPI::FS::IsFile(const std::string& Path) {
return fs::is_regular_file(Path); return fs::is_regular_file(Path);
} }
// TODO: add tests
std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) { std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
fs::path Path; fs::path Path;
for (size_t i = 0; i < Args.size(); ++i) { for (size_t i = 0; i < Args.size(); ++i) {
@@ -351,3 +494,157 @@ std::string LuaAPI::FS::ConcatPaths(sol::variadic_args Args) {
auto Result = Path.lexically_normal().string(); auto Result = Path.lexically_normal().string();
return Result; 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));
}
+94 -53
View File
@@ -17,22 +17,53 @@ 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";
static constexpr std::string_view StrDownloadThreads = "DownloadThreads";
// Misc
static constexpr std::string_view StrSendErrors = "SendErrors"; static constexpr std::string_view StrSendErrors = "SendErrors";
static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage"; static constexpr std::string_view StrSendErrorsMessageEnabled = "SendErrorsShowMessage";
static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled"; static constexpr std::string_view StrHideUpdateMessages = "ImScaredOfUpdates";
static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL";
// HTTP // HTTP
static constexpr std::string_view StrHTTPServerEnabled = "HTTPServerEnabled";
static constexpr std::string_view StrHTTPServerUseSSL = "UseSSL";
static constexpr std::string_view StrSSLKeyPath = "SSLKeyPath"; static constexpr std::string_view StrSSLKeyPath = "SSLKeyPath";
static constexpr std::string_view StrSSLCertPath = "SSLCertPath"; static constexpr std::string_view StrSSLCertPath = "SSLCertPath";
static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort";
static constexpr std::string_view StrHTTPServerIP = "HTTPServerIP";
TEST_CASE("TConfig::TConfig") {
const std::string CfgFile = "beammp_server_testconfig.toml";
fs::remove(CfgFile);
TConfig Cfg(CfgFile);
CHECK(fs::file_size(CfgFile) != 0);
std::string buf;
{
buf.resize(fs::file_size(CfgFile));
auto fp = std::fopen(CfgFile.c_str(), "r");
std::fread(buf.data(), 1, buf.size(), fp);
std::fclose(fp);
}
INFO("file contents are:", buf);
const auto table = toml::parse(CfgFile);
CHECK(table.at("General").is_table());
CHECK(table.at("Misc").is_table());
CHECK(table.at("HTTP").is_table());
fs::remove(CfgFile);
}
TConfig::TConfig(const std::string& ConfigFileName) TConfig::TConfig(const std::string& ConfigFileName)
: mConfigFileName(ConfigFileName) { : mConfigFileName(ConfigFileName) {
Application::SetSubsystemStatus("Config", Application::Status::Starting); Application::SetSubsystemStatus("Config", Application::Status::Starting);
if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) { if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) {
beammp_info("No config file found! Generating one..."); beammp_info("No config file found! Generating one...");
CreateConfigFile(mConfigFileName); CreateConfigFile();
} }
if (!mFailed) { if (!mFailed) {
if (fs::exists("Server.cfg")) { if (fs::exists("Server.cfg")) {
@@ -58,10 +89,12 @@ void SetComment(CommentsT& Comments, const std::string& Comment) {
* whether it is in TConfig.cpp or the configuration file. * whether it is in TConfig.cpp or the configuration file.
*/ */
void TConfig::FlushToFile() { void TConfig::FlushToFile() {
auto data = toml::parse<toml::preserve_comments>(mConfigFileName); // auto data = toml::parse<toml::preserve_comments>(mConfigFileName);
data["General"] = toml::table(); auto data = toml::value {};
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"); 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;
@@ -71,22 +104,44 @@ 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;
data["General"][StrSendErrors.data()] = Application::Settings.SendErrors; data["General"][StrDownloadThreads.data()] = Application::Settings.DownloadThreads;
SetComment(data["General"][StrSendErrors.data()].comments(), " You can turn on/off the SendErrors message you get on startup here"); SetComment(data["General"][StrDownloadThreads.data()].comments(), " How many simultaneous downloads may run at the same time");
data["General"][StrSendErrorsMessageEnabled.data()] = Application::Settings.SendErrorsMessageEnabled; // Misc
SetComment(data["General"][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`"); 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"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath; data["HTTP"][StrSSLKeyPath.data()] = Application::Settings.SSLKeyPath;
data["HTTP"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath; data["HTTP"][StrSSLCertPath.data()] = Application::Settings.SSLCertPath;
data["HTTP"][StrHTTPServerPort.data()] = Application::Settings.HTTPServerPort; 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"][StrHTTPServerUseSSL.data()] = Application::Settings.HTTPServerUseSSL; data["HTTP"][StrHTTPServerUseSSL.data()] = Application::Settings.HTTPServerUseSSL;
SetComment(data["HTTP"][StrHTTPServerUseSSL.data()].comments(), " Recommended to keep enabled. With SSL the server will serve https and requires valid key and cert files"); SetComment(data["HTTP"][StrHTTPServerUseSSL.data()].comments(), " Recommended to have enabled for servers which face the internet. With SSL the server will serve https and requires valid key and cert files");
data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled; data["HTTP"][StrHTTPServerEnabled.data()] = Application::Settings.HTTPServerEnabled;
SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server"); SetComment(data["HTTP"][StrHTTPServerEnabled.data()].comments(), " Enables the internal HTTP server");
std::ofstream Stream(mConfigFileName); std::stringstream Ss;
Stream << data << std::flush; Ss << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< data;
auto File = std::fopen(mConfigFileName.c_str(), "w+");
if (!File) {
beammp_error("Failed to create/write to config file: " + GetPlatformAgnosticErrorString());
throw std::runtime_error("Failed to create/write to config file");
}
auto Str = Ss.str();
auto N = std::fwrite(Str.data(), sizeof(char), Str.size(), File);
if (N != Str.size()) {
beammp_error("Failed to write to config file properly, config file might be misshapen");
}
std::fclose(File);
} }
void TConfig::CreateConfigFile(std::string_view name) { void TConfig::CreateConfigFile() {
// build from old config Server.cfg // build from old config Server.cfg
try { try {
@@ -98,32 +153,7 @@ void TConfig::CreateConfigFile(std::string_view name) {
beammp_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
std::ofstream ofs(name.data());
}
FlushToFile(); FlushToFile();
size_t FileSize = fs::file_size(name);
std::fstream ofs { std::string(name), std::ios::in | std::ios::out };
if (ofs.good()) {
std::string Contents {};
Contents.resize(FileSize);
ofs.readsome(Contents.data(), FileSize);
ofs.seekp(0);
ofs << "# This is the BeamMP-Server config file.\n"
"# Help & Documentation: `https://wiki.beammp.com/en/home/server-maintenance`\n"
"# IMPORTANT: Fill in the AuthKey with the key you got from `https://beammp.com/k/dashboard` on the left under \"Keys\"\n"
<< '\n'
<< Contents;
beammp_error("There was no \"" + std::string(mConfigFileName) + "\" file (this is normal for the first time running the server), so one was generated for you. It was automatically filled with the settings from your Server.cfg, if you have one. Please open ServerConfig.toml and ensure your AuthKey and other settings are filled in and correct, then restart the server. The old Server.cfg file will no longer be used and causes a warning if it exists from now on.");
mFailed = true;
ofs.close();
} else {
beammp_error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work.");
Application::SetSubsystemStatus("Config", Application::Status::Bad);
mFailed = true;
}
} }
void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue) { void TConfig::TryReadValue(toml::value& Table, const std::string& Category, const std::string_view& Key, std::string& OutValue) {
@@ -158,12 +188,17 @@ void TConfig::ParseFromFile(std::string_view name) {
TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc); TryReadValue(data, "General", StrDescription, Application::Settings.ServerDesc);
TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource); TryReadValue(data, "General", StrResourceFolder, Application::Settings.Resource);
TryReadValue(data, "General", StrAuthKey, Application::Settings.Key); TryReadValue(data, "General", StrAuthKey, Application::Settings.Key);
TryReadValue(data, "General", StrSendErrors, Application::Settings.SendErrors); TryReadValue(data, "General", StrLogChat, Application::Settings.LogChat);
TryReadValue(data, "General", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled); TryReadValue(data, "General", StrDownloadThreads, Application::Settings.DownloadThreads);
// Misc
TryReadValue(data, "Misc", StrSendErrors, Application::Settings.SendErrors);
TryReadValue(data, "Misc", StrHideUpdateMessages, Application::Settings.HideUpdateMessages);
TryReadValue(data, "Misc", StrSendErrorsMessageEnabled, Application::Settings.SendErrorsMessageEnabled);
// HTTP // HTTP
TryReadValue(data, "HTTP", StrSSLKeyPath, Application::Settings.SSLKeyPath); TryReadValue(data, "HTTP", StrSSLKeyPath, Application::Settings.SSLKeyPath);
TryReadValue(data, "HTTP", StrSSLCertPath, Application::Settings.SSLCertPath); TryReadValue(data, "HTTP", StrSSLCertPath, Application::Settings.SSLCertPath);
TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort); TryReadValue(data, "HTTP", StrHTTPServerPort, Application::Settings.HTTPServerPort);
TryReadValue(data, "HTTP", StrHTTPServerIP, Application::Settings.HTTPServerIP);
TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled); TryReadValue(data, "HTTP", StrHTTPServerEnabled, Application::Settings.HTTPServerEnabled);
TryReadValue(data, "HTTP", StrHTTPServerUseSSL, Application::Settings.HTTPServerUseSSL); TryReadValue(data, "HTTP", StrHTTPServerUseSSL, Application::Settings.HTTPServerUseSSL);
} catch (const std::exception& err) { } catch (const std::exception& err) {
@@ -190,20 +225,26 @@ void TConfig::ParseFromFile(std::string_view name) {
} }
void TConfig::PrintDebug() { void TConfig::PrintDebug() {
beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false")); beammp_debugf("{}: {}", StrDebug, Application::Settings.DebugModeEnabled ? "true" : "false");
beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.Private ? "true" : "false")); beammp_debugf("{}: {}", StrPrivate, Application::Settings.Private ? "true" : "false");
beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.Port)); beammp_debugf("{}: {}", StrPort, Application::Settings.Port);
beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.MaxCars)); beammp_debugf("{}: {}", StrMaxCars, Application::Settings.MaxCars);
beammp_debug(std::string(StrMaxPlayers) + ": " + std::to_string(Application::Settings.MaxPlayers)); beammp_debugf("{}: {}", StrMaxPlayers, Application::Settings.MaxPlayers);
beammp_debug(std::string(StrMap) + ": \"" + Application::Settings.MapName + "\""); beammp_debugf("{}: '{}'", StrMap, Application::Settings.MapName);
beammp_debug(std::string(StrName) + ": \"" + Application::Settings.ServerName + "\""); beammp_debugf("{}: '{}'", StrName, Application::Settings.ServerName);
beammp_debug(std::string(StrDescription) + ": \"" + Application::Settings.ServerDesc + "\""); beammp_debugf("{}: '{}'", StrDescription, Application::Settings.ServerDesc);
beammp_debug(std::string(StrResourceFolder) + ": \"" + Application::Settings.Resource + "\""); beammp_debugf("{}: {}", StrLogChat, Application::Settings.LogChat ? "true" : "false");
beammp_debug(std::string(StrSSLKeyPath) + ": \"" + Application::Settings.SSLKeyPath + "\""); beammp_debugf("{}: '{}'", StrResourceFolder, Application::Settings.Resource);
beammp_debug(std::string(StrSSLCertPath) + ": \"" + Application::Settings.SSLCertPath + "\""); beammp_debugf("{}: '{}'", StrSSLKeyPath, Application::Settings.SSLKeyPath);
beammp_debug(std::string(StrHTTPServerPort) + ": \"" + std::to_string(Application::Settings.HTTPServerPort) + "\""); beammp_debugf("{}: '{}'", StrSSLCertPath, Application::Settings.SSLCertPath);
beammp_debugf("{}: {}", StrHTTPServerPort, Application::Settings.HTTPServerPort);
beammp_debugf("{}: '{}'", StrHTTPServerIP, Application::Settings.HTTPServerIP);
beammp_debugf("{}: {}", StrDownloadThreads, Application::Settings.DownloadThreads);
if (Application::Settings.DownloadThreads < 1) {
beammp_warnf("'{}' of <1 is not allowed, will use 1", StrDownloadThreads);
}
// special! // special!
beammp_debug("Key Length: " + std::to_string(Application::Settings.Key.length()) + ""); beammp_debugf("Key Length: {}", Application::Settings.Key.length());
} }
void TConfig::ParseOldFormat() { void TConfig::ParseOldFormat() {
+315 -79
View File
@@ -14,6 +14,17 @@ static inline bool StringStartsWith(const std::string& What, const std::string&
return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith; return What.size() >= StartsWith.size() && What.substr(0, StartsWith.size()) == StartsWith;
} }
TEST_CASE("StringStartsWith") {
CHECK(StringStartsWith("Hello, World", "Hello"));
CHECK(StringStartsWith("Hello, World", "H"));
CHECK(StringStartsWith("Hello, World", ""));
CHECK(!StringStartsWith("Hello, World", "ello"));
CHECK(!StringStartsWith("Hello, World", "World"));
CHECK(StringStartsWith("", ""));
CHECK(!StringStartsWith("", "hello"));
}
// Trims leading and trailing spaces, newlines, tabs, etc.
static inline std::string TrimString(std::string S) { static inline std::string TrimString(std::string S) {
S.erase(S.begin(), std::find_if(S.begin(), S.end(), [](unsigned char ch) { S.erase(S.begin(), std::find_if(S.begin(), S.end(), [](unsigned char ch) {
return !std::isspace(ch); return !std::isspace(ch);
@@ -25,6 +36,21 @@ static inline std::string TrimString(std::string S) {
return S; return S;
} }
TEST_CASE("TrimString") {
CHECK(TrimString("hel lo") == "hel lo");
CHECK(TrimString(" hel lo") == "hel lo");
CHECK(TrimString(" hel lo ") == "hel lo");
CHECK(TrimString("hel lo ") == "hel lo");
CHECK(TrimString(" hel lo") == "hel lo");
CHECK(TrimString("hel lo ") == "hel lo");
CHECK(TrimString(" hel lo ") == "hel lo");
CHECK(TrimString("\t\thel\nlo\n\n") == "hel\nlo");
CHECK(TrimString("\n\thel\tlo\n\t") == "hel\tlo");
CHECK(TrimString(" ") == "");
CHECK(TrimString(" \t\n\r ") == "");
CHECK(TrimString("") == "");
}
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);
@@ -97,6 +123,17 @@ void TConsole::BackupOldLog() {
} }
} }
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) { void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
if (!mIsLuaConsole) { if (!mIsLuaConsole) {
if (!mLuaEngine) { if (!mLuaEngine) {
@@ -107,10 +144,10 @@ void TConsole::ChangeToLuaConsole(const std::string& LuaStateId) {
mStateId = LuaStateId; mStateId = LuaStateId;
mIsLuaConsole = true; mIsLuaConsole = true;
if (mStateId != mDefaultStateId) { if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Entered Lua console for state '" + mStateId + "'. To exit, type `exit()`"); Application::Console().WriteRaw("Attached to Lua state '" + mStateId + "'. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua @" + LuaStateId + "> "); mCommandline.set_prompt("lua @" + LuaStateId + "> ");
} else { } else {
Application::Console().WriteRaw("Entered Lua console. To exit, type `exit()`"); Application::Console().WriteRaw("Attached to Lua. For help, type `:help`. To detach, type `:detach`");
mCommandline.set_prompt("lua> "); mCommandline.set_prompt("lua> ");
} }
mCachedRegularHistory = mCommandline.history(); mCachedRegularHistory = mCommandline.history();
@@ -122,9 +159,9 @@ void TConsole::ChangeToRegularConsole() {
if (mIsLuaConsole) { if (mIsLuaConsole) {
mIsLuaConsole = false; mIsLuaConsole = false;
if (mStateId != mDefaultStateId) { if (mStateId != mDefaultStateId) {
Application::Console().WriteRaw("Left Lua console for state '" + mStateId + "'."); Application::Console().WriteRaw("Detached from Lua state '" + mStateId + "'.");
} else { } else {
Application::Console().WriteRaw("Left Lua console."); Application::Console().WriteRaw("Detached from Lua.");
} }
mCachedLuaHistory = mCommandline.history(); mCachedLuaHistory = mCommandline.history();
mCommandline.set_history(mCachedRegularHistory); mCommandline.set_history(mCachedRegularHistory);
@@ -133,21 +170,54 @@ void TConsole::ChangeToRegularConsole() {
} }
} }
void TConsole::Command_Lua(const std::string& cmd) { bool TConsole::EnsureArgsCount(const std::vector<std::string>& args, size_t n) {
if (cmd.size() > 3) { if (n == 0 && args.size() != 0) {
auto NewStateId = cmd.substr(4); 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()); beammp_assert(!NewStateId.empty());
if (mLuaEngine->HasState(NewStateId)) { if (mLuaEngine->HasState(NewStateId)) {
ChangeToLuaConsole(NewStateId); ChangeToLuaConsole(NewStateId);
} else { } else {
Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua."); Application::Console().WriteRaw("Lua state '" + NewStateId + "' is not a known state. Didn't switch to Lua.");
} }
} else { } else if (args.size() == 0) {
ChangeToLuaConsole(mDefaultStateId); ChangeToLuaConsole(mDefaultStateId);
} }
} }
void TConsole::Command_Help(const std::string&) { void TConsole::Command_Help(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
static constexpr const char* sHelpString = R"( static constexpr const char* sHelpString = R"(
Commands: Commands:
help displays this help help displays this help
@@ -156,53 +226,135 @@ void TConsole::Command_Help(const std::string&) {
list lists all players and info about them list lists all players and info about them
say <message> sends the message to all players in chat say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua lua [state id] switches to lua, optionally into a specific state id's lua
status how the server is doing and what it's up to)"; 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
clear clears the console window)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString)); Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
} }
void TConsole::Command_Kick(const std::string& cmd) { std::string TConsole::ConcatArgs(const std::vector<std::string>& args, char space) {
if (cmd.size() > 4) { std::string Result;
auto Name = cmd.substr(5); for (const auto& arg : args) {
std::string Reason = "Kicked by server console"; Result += arg + space;
auto SpacePos = Name.find(' '); }
if (SpacePos != Name.npos) { Result = Result.substr(0, Result.size() - 1); // strip trailing space
Reason = Name.substr(SpacePos + 1); return Result;
Name = cmd.substr(5, cmd.size() - Reason.size() - 5 - 1); }
void TConsole::Command_Clear(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0, size_t(-1))) {
return;
}
mCommandline.write("\x1b[;H\x1b[2J");
}
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;
}
} }
beammp_trace("attempt to kick '" + Name + "' for '" + Reason + "'"); return true;
bool Kicked = false; });
auto NameCompare = [](std::string Name1, std::string Name2) -> bool { if (!Kicked) {
std::for_each(Name1.begin(), Name1.end(), [](char& c) { c = tolower(c); }); Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found.");
std::for_each(Name2.begin(), Name2.end(), [](char& c) { c = tolower(c); }); } else {
return StringStartsWith(Name1, Name2) || StringStartsWith(Name2, Name1); Application::Console().WriteRaw("Kicked player '" + Name + "' for reason: '" + Reason + "'.");
}; }
mLuaEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { }
if (!Client.expired()) {
auto locked = Client.lock(); std::tuple<std::string, std::vector<std::string>> TConsole::ParseCommand(const std::string& CommandWithArgs) {
if (NameCompare(locked->GetName(), Name)) { // Algorithm designed and implemented by Lion Kortlepel (c) 2022
mLuaEngine->Network().ClientKick(*locked, Reason); // It correctly splits arguments, including respecting single and double quotes, as well as backticks
Kicked = true; auto End_i = CommandWithArgs.find_first_of(' ');
return false; 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
} }
} }
return true; ++Ptr;
}); }
if (!Kicked) { Arg = std::string(PrevPtr, Ptr - PrevPtr);
Application::Console().WriteRaw("Error: No player with name matching '" + Name + "' was found."); // remove quotes if enclosed in quotes
} else { for (char Quote : { '"', '\'', '`' }) {
Application::Console().WriteRaw("Kicked player '" + Name + "' for reason: '" + Reason + "'."); 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_Say(const std::string& cmd) { void TConsole::Command_List(const std::string&, const std::vector<std::string>& args) {
if (cmd.size() > 3) { if (!EnsureArgsCount(args, 0)) {
auto Message = cmd.substr(4); return;
LuaAPI::MP::SendChatMessage(-1, Message);
} }
}
void TConsole::Command_List(const std::string&) {
if (mLuaEngine->Server().ClientCount() == 0) { if (mLuaEngine->Server().ClientCount() == 0) {
Application::Console().WriteRaw("No players online."); Application::Console().WriteRaw("No players online.");
} else { } else {
@@ -222,7 +374,10 @@ void TConsole::Command_List(const std::string&) {
} }
} }
void TConsole::Command_Status(const std::string&) { void TConsole::Command_Status(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}
std::stringstream Status; std::stringstream Status;
size_t CarCount = 0; size_t CarCount = 0;
@@ -307,12 +462,12 @@ void TConsole::Command_Status(const std::string&) {
<< "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n" << "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n"
<< "\tSubsystems:\n" << "\tSubsystems:\n"
<< "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n" << "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n"
<< "\t\tShutting down/Shutdown: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n" << "\t\tShutting down/Shut down: " << SystemsShuttingDown << "/" << SystemsShutdown << "\n"
<< "\t\tGood: [ " << SystemsGoodList << " ]\n" << "\t\tGood: [ " << SystemsGoodList << " ]\n"
<< "\t\tStarting: [ " << SystemsStartingList << " ]\n" << "\t\tStarting: [ " << SystemsStartingList << " ]\n"
<< "\t\tBad: [ " << SystemsBadList << " ]\n" << "\t\tBad: [ " << SystemsBadList << " ]\n"
<< "\t\tShutting down: [ " << SystemsShuttingDownList << " ]\n" << "\t\tShutting down: [ " << SystemsShuttingDownList << " ]\n"
<< "\t\tShutdown: [ " << SystemsShutdownList << " ]\n" << "\t\tShut down: [ " << SystemsShutdownList << " ]\n"
<< ""; << "";
Application::Console().WriteRaw(Status.str()); Application::Console().WriteRaw(Status.str());
@@ -364,32 +519,81 @@ void TConsole::RunAsCommand(const std::string& cmd, bool IgnoreNotACommand) {
} }
} }
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("> ");
BackupOldLog(); BackupOldLog();
bool success = mCommandline.enable_write_to_file("Server.log");
if (!success) {
beammp_error("unable to open file for writing: \"Server.log\"");
}
mCommandline.on_command = [this](Commandline& c) { mCommandline.on_command = [this](Commandline& c) {
try { try {
auto cmd = c.get_command(); auto TrimmedCmd = c.get_command();
cmd = TrimString(cmd); TrimmedCmd = TrimString(TrimmedCmd);
mCommandline.write(mCommandline.prompt() + cmd); auto [cmd, args] = ParseCommand(TrimmedCmd);
mCommandline.write(mCommandline.prompt() + TrimmedCmd);
if (mIsLuaConsole) { if (mIsLuaConsole) {
if (!mLuaEngine) { if (!mLuaEngine) {
beammp_info("Lua not started yet, please try again in a second"); beammp_info("Lua not started yet, please try again in a second");
} else if (cmd == "exit()") { } else if (!cmd.empty() && cmd.at(0) == ':') {
ChangeToRegularConsole(); HandleLuaInternalCommand(cmd.substr(1));
} else { } else {
auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(cmd), "", "" }); auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared<std::string>(TrimmedCmd), "", "" });
while (!Future->Ready) { while (!Future->Ready) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); // TODO: Add a timeout std::this_thread::yield(); // TODO: Add a timeout
} }
if (Future->Error) { if (Future->Error) {
beammp_lua_error(Future->ErrorMessage); beammp_lua_error("error in " + mStateId + ": " + Future->ErrorMessage);
} }
} }
} else { } else {
@@ -398,31 +602,63 @@ TConsole::TConsole() {
} else if (cmd == "exit") { } else if (cmd == "exit") {
beammp_info("gracefully shutting down"); beammp_info("gracefully shutting down");
Application::GracefullyShutdown(); Application::GracefullyShutdown();
} else if (StringStartsWith(cmd, "lua")) { } else if (cmd == "say") {
Command_Lua(cmd); RunAsCommand(TrimmedCmd, true);
} else if (StringStartsWith(cmd, "help")) { Command_Say(TrimmedCmd);
RunAsCommand(cmd, true); } else {
Command_Help(cmd); if (mCommandMap.find(cmd) != mCommandMap.end()) {
} else if (StringStartsWith(cmd, "kick")) { mCommandMap.at(cmd)(cmd, args);
RunAsCommand(cmd, true); RunAsCommand(TrimmedCmd, true);
Command_Kick(cmd); } else {
} else if (StringStartsWith(cmd, "say")) { RunAsCommand(TrimmedCmd);
RunAsCommand(cmd, true); }
Command_Say(cmd);
} else if (StringStartsWith(cmd, "list")) {
RunAsCommand(cmd, true);
Command_List(cmd);
} else if (StringStartsWith(cmd, "status")) {
RunAsCommand(cmd, true);
Command_Status(cmd);
} else if (!cmd.empty()) {
RunAsCommand(cmd);
} }
} }
} catch (const std::exception& e) { } 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."); 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) {
+71 -28
View File
@@ -19,7 +19,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;
while (!mShutdown) { size_t UpdateReminderCounter = 0;
while (!Application::IsShuttingDown()) {
++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();
@@ -54,43 +56,85 @@ void THeartbeatThread::operator()() {
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) || ResponseCode != 200) { json::Document Doc;
beammp_trace("got " + T + " from backend"); bool Ok = false;
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); for (const auto& Url : Application::GetBackendUrlsInOrder()) {
SentryReportError(Application::GetBackendHostname() + Target, ResponseCode); T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } });
std::this_thread::sleep_for(std::chrono::milliseconds(500)); beammp_trace(T);
T = Http::POST(Application::GetBackup1Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); Doc.Parse(T.data(), T.size());
if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { if (Doc.HasParseError() || !Doc.IsObject()) {
SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode); if (!Application::Settings.Private) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); beammp_error("Backend response failed to parse as valid json");
std::this_thread::sleep_for(std::chrono::milliseconds(500)); beammp_trace("Response was: `" + T + "`");
T = Http::POST(Application::GetBackup2Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode);
if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) {
beammp_warn("Backend system refused server! Server will not show in the public server list.");
Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad);
isAuth = false;
SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode);
} else {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
} }
Sentry.SetContext("JSON Response", { { "reponse", T } });
SentryReportError(Url + Target, ResponseCode);
} else if (ResponseCode != 200) {
SentryReportError(Url + Target, ResponseCode);
} else { } else {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); // 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 { } else {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); 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") {
beammp_info(("Authenticated!")); beammp_info(("Authenticated! " + Message));
isAuth = true; isAuth = true;
} else if (T == "200") { } else if (Status == "200") {
beammp_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) {
Application::SetSubsystemStatus("Heartbeat", Application::Status::Good);
}
if (!Application::Settings.HideUpdateMessages && UpdateReminderCounter % 5) {
Application::CheckForUpdates();
}
} }
} }
@@ -120,7 +164,6 @@ THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& S
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown); Application::SetSubsystemStatus("Heartbeat", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
mShutdown = true;
mThread.join(); mThread.join();
} }
Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown); Application::SetSubsystemStatus("Heartbeat", Application::Status::Shutdown);
+327 -122
View File
@@ -8,6 +8,7 @@
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>
#include <httplib.h> #include <httplib.h>
#include <nlohmann/json.hpp>
#include <random> #include <random>
#include <thread> #include <thread>
#include <tuple> #include <tuple>
@@ -15,26 +16,29 @@
TLuaEngine* LuaAPI::MP::Engine; TLuaEngine* LuaAPI::MP::Engine;
TLuaEngine::TLuaEngine() TLuaEngine::TLuaEngine()
: mPluginMonitor(fs::path(Application::Settings.Resource) / "Server", *this, mShutdown) { : mResourceServerPath(fs::path(Application::Settings.Resource) / "Server") {
Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting); Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting);
LuaAPI::MP::Engine = this; LuaAPI::MP::Engine = this;
if (!fs::exists(Application::Settings.Resource)) { if (!fs::exists(Application::Settings.Resource)) {
fs::create_directory(Application::Settings.Resource); fs::create_directory(Application::Settings.Resource);
} }
fs::path Path = fs::path(Application::Settings.Resource) / "Server"; if (!fs::exists(mResourceServerPath)) {
if (!fs::exists(Path)) { fs::create_directory(mResourceServerPath);
fs::create_directory(Path);
} }
mResourceServerPath = Path;
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("LuaEngine", Application::Status::ShuttingDown); Application::SetSubsystemStatus("LuaEngine", Application::Status::ShuttingDown);
mShutdown = true;
if (mThread.joinable()) { if (mThread.joinable()) {
mThread.join(); mThread.join();
} }
Application::SetSubsystemStatus("LuaEngine", Application::Status::Shutdown); Application::SetSubsystemStatus("LuaEngine", Application::Status::Shutdown);
}); });
Start(); IThreaded::Start();
}
TEST_CASE("TLuaEngine ctor & dtor") {
Application::Settings.Resource = "beammp_server_test_resources";
TLuaEngine engine;
Application::GracefullyShutdown();
} }
void TLuaEngine::operator()() { void TLuaEngine::operator()() {
@@ -44,7 +48,7 @@ void TLuaEngine::operator()() {
CollectAndInitPlugins(); CollectAndInitPlugins();
// now call all onInit's // now call all onInit's
auto Futures = TriggerEvent("onInit", ""); auto Futures = TriggerEvent("onInit", "");
WaitForAll(Futures); WaitForAll(Futures, std::chrono::seconds(5));
for (const auto& Future : Futures) { for (const auto& Future : Futures) {
if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) { if (Future->Error && Future->ErrorMessage != BeamMPFnNotFoundError) {
beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage); beammp_lua_error("Calling \"onInit\" on \"" + Future->StateId + "\" failed: " + Future->ErrorMessage);
@@ -53,62 +57,65 @@ void TLuaEngine::operator()() {
auto ResultCheckThread = std::thread([&] { auto ResultCheckThread = std::thread([&] {
RegisterThread("ResultCheckThread"); RegisterThread("ResultCheckThread");
while (!mShutdown) { while (!Application::IsShuttingDown()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock Lock(mResultsToCheckMutex); std::unique_lock Lock(mResultsToCheckMutex);
if (!mResultsToCheck.empty()) { if (!mResultsToCheck.empty()) {
auto Res = mResultsToCheck.front(); mResultsToCheck.remove_if([](const std::shared_ptr<TLuaResult>& Ptr) -> bool {
mResultsToCheck.pop(); if (Ptr->Ready) {
Lock.unlock(); if (Ptr->Error) {
if (Ptr->ErrorMessage != BeamMPFnNotFoundError) {
size_t Waited = 0; beammp_lua_error(Ptr->Function + ": " + Ptr->ErrorMessage);
while (!Res->Ready) { }
std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
Waited++; return true;
if (Waited > 250) {
// FIXME: This should *eventually* timeout.
// beammp_lua_error(Res->Function + " in " + Res->StateId + " took >1s to respond, not printing possible errors");
Lock.lock();
mResultsToCheck.push(Res);
Lock.unlock();
break;
} }
} return false;
if (Res->Error) { });
if (Res->ErrorMessage != BeamMPFnNotFoundError) { } else {
beammp_lua_error(Res->Function + ": " + Res->ErrorMessage); mResultsToCheckCond.wait_for(Lock, std::chrono::milliseconds(20));
}
}
} }
} }
}); });
// event loop // event loop
auto Before = std::chrono::high_resolution_clock::now(); auto Before = std::chrono::high_resolution_clock::now();
while (!mShutdown) { while (!Application::IsShuttingDown()) {
if (mLuaStates.size() == 0) {
std::this_thread::sleep_for(std::chrono::seconds(500));
}
{ // Timed Events Scope { // Timed Events Scope
std::unique_lock Lock(mTimedEventsMutex); std::unique_lock Lock(mTimedEventsMutex);
for (auto& Timer : mTimedEvents) { for (auto& Timer : mTimedEvents) {
if (Timer.Expired()) { if (Timer.Expired()) {
auto LastCompletionBeforeReset = Timer.LastCompletion;
Timer.Reset(); Timer.Reset();
auto Handlers = GetEventHandlersForState(Timer.EventName, Timer.StateId); auto Handlers = GetEventHandlersForState(Timer.EventName, Timer.StateId);
std::unique_lock StateLock(mLuaStatesMutex); std::unique_lock StateLock(mLuaStatesMutex);
std::unique_lock Lock2(mResultsToCheckMutex); std::unique_lock Lock2(mResultsToCheckMutex);
for (auto& Handler : Handlers) { for (auto& Handler : Handlers) {
auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCall(Handler, {}); auto Res = mLuaStates[Timer.StateId]->EnqueueFunctionCallFromCustomEvent(Handler, {}, Timer.EventName, Timer.Strategy);
mResultsToCheck.push(Res); if (Res) {
mResultsToCheck.push_back(Res);
mResultsToCheckCond.notify_one();
} else {
// "revert" reset
Timer.LastCompletion = LastCompletionBeforeReset;
// beammp_trace("Reverted reset of \"" + Timer.EventName + "\" timer");
// no need to try to enqueue more handlers for this event (they will all fail)
break;
}
} }
} }
} }
} }
std::chrono::high_resolution_clock::duration Diff; if (mLuaStates.size() == 0) {
if ((Diff = std::chrono::high_resolution_clock::now() - Before) beammp_trace("No Lua states, event loop running extremely sparsely");
< std::chrono::milliseconds(10)) { Application::SleepSafeSeconds(10);
std::this_thread::sleep_for(Diff);
} else { } else {
beammp_trace("Event loop cannot keep up! Running " + std::to_string(Diff.count()) + "s behind"); constexpr double NsFactor = 1000000.0;
constexpr double Expected = 10.0; // ms
const auto Diff = (std::chrono::high_resolution_clock::now() - Before).count() / NsFactor;
if (Diff < Expected) {
std::this_thread::sleep_for(std::chrono::nanoseconds(size_t((Expected - Diff) * NsFactor)));
} else {
beammp_tracef("Event loop cannot keep up! Running {}ms behind", Diff);
}
} }
Before = std::chrono::high_resolution_clock::now(); Before = std::chrono::high_resolution_clock::now();
} }
@@ -149,16 +156,88 @@ TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) {
return ""; return "";
} }
void TLuaEngine::AddResultToCheck(const std::shared_ptr<TLuaResult>& Result) {
std::unique_lock Lock(mResultsToCheckMutex);
mResultsToCheck.push_back(Result);
mResultsToCheckCond.notify_one();
}
std::unordered_map<std::string /* event name */, std::vector<std::string> /* handlers */> TLuaEngine::Debug_GetEventsForState(TLuaStateId StateId) {
std::unordered_map<std::string, std::vector<std::string>> Result;
std::unique_lock Lock(mLuaEventsMutex);
for (const auto& EventNameToEventMap : mLuaEvents) {
for (const auto& IdSetOfHandlersPair : EventNameToEventMap.second) {
if (IdSetOfHandlersPair.first == StateId) {
for (const auto& Handler : IdSetOfHandlersPair.second) {
Result[EventNameToEventMap.first].push_back(Handler);
}
}
}
}
return Result;
}
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::Debug_GetStateExecuteQueueForState(TLuaStateId StateId) {
std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateExecuteQueue();
return Result;
}
std::vector<TLuaEngine::QueuedFunction> TLuaEngine::Debug_GetStateFunctionQueueForState(TLuaStateId StateId) {
std::vector<TLuaEngine::QueuedFunction> Result;
std::unique_lock Lock(mLuaStatesMutex);
Result = mLuaStates.at(StateId)->Debug_GetStateFunctionQueue();
return Result;
}
std::vector<TLuaResult> TLuaEngine::Debug_GetResultsToCheckForState(TLuaStateId StateId) {
std::unique_lock Lock(mResultsToCheckMutex);
auto ResultsToCheckCopy = mResultsToCheck;
Lock.unlock();
std::vector<TLuaResult> Result;
while (!ResultsToCheckCopy.empty()) {
auto ResultToCheck = std::move(ResultsToCheckCopy.front());
ResultsToCheckCopy.pop_front();
if (ResultToCheck->StateId == StateId) {
Result.push_back(*ResultToCheck);
}
}
return Result;
}
std::vector<std::string> TLuaEngine::GetStateGlobalKeysForState(TLuaStateId StateId) {
std::unique_lock Lock(mLuaStatesMutex);
auto Result = mLuaStates.at(StateId)->GetStateGlobalKeys();
return Result;
}
std::vector<std::string> TLuaEngine::StateThreadData::GetStateGlobalKeys() {
auto globals = mStateView.globals();
std::vector<std::string> Result;
for (const auto& [key, value] : globals) {
Result.push_back(key.as<std::string>());
}
return Result;
}
void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<std::chrono::high_resolution_clock::duration>& Max) { void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, const std::optional<std::chrono::high_resolution_clock::duration>& Max) {
for (const auto& Result : Results) { for (const auto& Result : Results) {
bool Cancelled = false; bool Cancelled = false;
size_t ms = 0; size_t ms = 0;
std::set<std::string> WarnedResults;
while (!Result->Ready && !Cancelled) { while (!Result->Ready && !Cancelled) {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
ms += 10; ms += 10;
if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) { if (Max.has_value() && std::chrono::milliseconds(ms) > Max.value()) {
beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms)"); beammp_trace("'" + Result->Function + "' in '" + Result->StateId + "' did not finish executing in time (took: " + std::to_string(ms) + "ms).");
Cancelled = true; Cancelled = true;
} else if (ms > 1000 * 60) {
auto ResultId = Result->StateId + "_" + Result->Function;
if (WarnedResults.count(ResultId) == 0) {
WarnedResults.insert(ResultId);
beammp_lua_warn("'" + Result->Function + "' in '" + Result->StateId + "' is taking very long. The event it's handling is too important to discard the result of this handler, but may block this event and possibly the whole lua state.");
}
} }
} }
if (Cancelled) { if (Cancelled) {
@@ -176,7 +255,8 @@ void TLuaEngine::WaitForAll(std::vector<std::shared_ptr<TLuaResult>>& Results, c
void TLuaEngine::ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results) { void TLuaEngine::ReportErrors(const std::vector<std::shared_ptr<TLuaResult>>& Results) {
std::unique_lock Lock2(mResultsToCheckMutex); std::unique_lock Lock2(mResultsToCheckMutex);
for (const auto& Result : Results) { for (const auto& Result : Results) {
mResultsToCheck.push(Result); mResultsToCheck.push_back(Result);
mResultsToCheckCond.notify_one();
} }
} }
@@ -196,6 +276,9 @@ std::shared_ptr<TLuaResult> TLuaEngine::EnqueueFunctionCall(TLuaStateId StateID,
} }
void TLuaEngine::CollectAndInitPlugins() { void TLuaEngine::CollectAndInitPlugins() {
if (!fs::exists(mResourceServerPath)) {
fs::create_directories(mResourceServerPath);
}
for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) { for (const auto& Dir : fs::directory_iterator(mResourceServerPath)) {
auto Path = Dir.path(); auto Path = Dir.path();
Path = fs::relative(Path); Path = fs::relative(Path);
@@ -245,7 +328,7 @@ void TLuaEngine::EnsureStateExists(TLuaStateId StateId, const std::string& Name,
std::unique_lock Lock(mLuaStatesMutex); std::unique_lock Lock(mLuaStatesMutex);
if (mLuaStates.find(StateId) == mLuaStates.end()) { if (mLuaStates.find(StateId) == mLuaStates.end()) {
beammp_debug("Creating lua state for state id \"" + StateId + "\""); beammp_debug("Creating lua state for state id \"" + StateId + "\"");
auto DataPtr = std::make_unique<StateThreadData>(Name, mShutdown, StateId, *this); auto DataPtr = std::make_unique<StateThreadData>(Name, StateId, *this);
mLuaStates[StateId] = std::move(DataPtr); mLuaStates[StateId] = std::move(DataPtr);
RegisterEvent("onInit", StateId, "onInit"); RegisterEvent("onInit", StateId, "onInit");
if (!DontCallOnInit) { if (!DontCallOnInit) {
@@ -269,7 +352,6 @@ std::set<std::string> TLuaEngine::GetEventHandlersForState(const std::string& Ev
sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) { sol::table TLuaEngine::StateThreadData::Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs) {
auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs); auto Return = mEngine->TriggerEvent(EventName, mStateId, EventArgs);
// TODO Synchronous call to the event handlers
auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId); auto MyHandlers = mEngine->GetEventHandlersForState(EventName, mStateId);
for (const auto& Handler : MyHandlers) { for (const auto& Handler : MyHandlers) {
auto Fn = mStateView[Handler]; auto Fn = mStateView[Handler];
@@ -327,7 +409,7 @@ sol::table TLuaEngine::StateThreadData::Lua_TriggerLocalEvent(const std::string&
Result.add(FnRet); Result.add(FnRet);
} else { } else {
sol::error Err = FnRet; sol::error Err = FnRet;
beammp_lua_error(Err.what()); beammp_lua_error(std::string("TriggerLocalEvent: ") + Err.what());
} }
} }
} }
@@ -378,6 +460,32 @@ int TLuaEngine::StateThreadData::Lua_GetPlayerIDByName(const std::string& Name)
return Id; return Id;
} }
sol::table TLuaEngine::StateThreadData::Lua_FS_ListFiles(const std::string& Path) {
if (!std::filesystem::exists(Path)) {
return sol::lua_nil;
}
auto table = mStateView.create_table();
for (const auto& entry : std::filesystem::directory_iterator(Path)) {
if (entry.is_regular_file() || entry.is_symlink()) {
table[table.size() + 1] = entry.path().lexically_relative(Path).string();
}
}
return table;
}
sol::table TLuaEngine::StateThreadData::Lua_FS_ListDirectories(const std::string& Path) {
if (!std::filesystem::exists(Path)) {
return sol::lua_nil;
}
auto table = mStateView.create_table();
for (const auto& entry : std::filesystem::directory_iterator(Path)) {
if (entry.is_directory()) {
table[table.size() + 1] = entry.path().lexically_relative(Path).string();
}
}
return table;
}
std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) { std::string TLuaEngine::StateThreadData::Lua_GetPlayerName(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID); auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient && !MaybeClient.value().expired()) { if (MaybeClient && !MaybeClient.value().expired()) {
@@ -431,9 +539,85 @@ sol::table TLuaEngine::StateThreadData::Lua_HttpCreateConnection(const std::stri
return table; return table;
} }
TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomic_bool& Shutdown, TLuaStateId StateId, TLuaEngine& Engine) template <typename T>
static void AddToTable(sol::table& table, const std::string& left, const T& value) {
if (left.empty()) {
table[table.size() + 1] = value;
} else {
table[left] = value;
}
}
static void JsonDecodeRecursive(sol::state_view& StateView, sol::table& table, const std::string& left, const nlohmann::json& right) {
switch (right.type()) {
case nlohmann::detail::value_t::null:
return;
case nlohmann::detail::value_t::object: {
auto value = table.create();
value.clear();
for (const auto& entry : right.items()) {
JsonDecodeRecursive(StateView, value, entry.key(), entry.value());
}
AddToTable(table, left, value);
break;
}
case nlohmann::detail::value_t::array: {
auto value = table.create();
value.clear();
for (const auto& entry : right.items()) {
JsonDecodeRecursive(StateView, value, "", entry.value());
}
AddToTable(table, left, value);
break;
}
case nlohmann::detail::value_t::string:
AddToTable(table, left, right.get<std::string>());
break;
case nlohmann::detail::value_t::boolean:
AddToTable(table, left, right.get<bool>());
break;
case nlohmann::detail::value_t::number_integer:
AddToTable(table, left, right.get<int64_t>());
break;
case nlohmann::detail::value_t::number_unsigned:
AddToTable(table, left, right.get<uint64_t>());
break;
case nlohmann::detail::value_t::number_float:
AddToTable(table, left, right.get<double>());
break;
case nlohmann::detail::value_t::binary:
beammp_lua_error("JsonDecode can't handle binary blob in json, ignoring");
return;
case nlohmann::detail::value_t::discarded:
return;
}
}
sol::table TLuaEngine::StateThreadData::Lua_JsonDecode(const std::string& str) {
sol::state_view StateView(mState);
auto table = StateView.create_table();
if (!nlohmann::json::accept(str)) {
beammp_lua_error("string given to JsonDecode is not valid json: `" + str + "`");
return sol::lua_nil;
}
nlohmann::json json = nlohmann::json::parse(str);
if (json.is_object()) {
for (const auto& entry : json.items()) {
JsonDecodeRecursive(StateView, table, entry.key(), entry.value());
}
} else if (json.is_array()) {
for (const auto& entry : json) {
JsonDecodeRecursive(StateView, table, "", entry);
}
} else {
beammp_lua_error("JsonDecode expected array or object json, instead got " + std::string(json.type_name()));
return sol::lua_nil;
}
return table;
}
TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateId StateId, TLuaEngine& Engine)
: mName(Name) : mName(Name)
, mShutdown(Shutdown)
, mStateId(StateId) , mStateId(StateId)
, mState(luaL_newstate()) , mState(luaL_newstate())
, mEngine(&Engine) { , mEngine(&Engine) {
@@ -476,6 +660,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
return Lua_TriggerLocalEvent(EventName, EventArgs); return Lua_TriggerLocalEvent(EventName, EventArgs);
}); });
MPTable.set_function("TriggerClientEvent", &LuaAPI::MP::TriggerClientEvent); MPTable.set_function("TriggerClientEvent", &LuaAPI::MP::TriggerClientEvent);
MPTable.set_function("TriggerClientEventJson", &LuaAPI::MP::TriggerClientEventJson);
MPTable.set_function("GetPlayerCount", &LuaAPI::MP::GetPlayerCount); MPTable.set_function("GetPlayerCount", &LuaAPI::MP::GetPlayerCount);
MPTable.set_function("IsPlayerConnected", &LuaAPI::MP::IsPlayerConnected); MPTable.set_function("IsPlayerConnected", &LuaAPI::MP::IsPlayerConnected);
MPTable.set_function("GetPlayerIDByName", [&](const std::string& Name) -> int { MPTable.set_function("GetPlayerIDByName", [&](const std::string& Name) -> int {
@@ -504,16 +689,53 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
return Lua_GetPlayerIdentifiers(ID); return Lua_GetPlayerIdentifiers(ID);
}); });
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep); MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
MPTable.set_function("CreateEventTimer", [&](const std::string& EventName, size_t IntervalMS) { // const std::string& EventName, size_t IntervalMS, int strategy
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
if (Args.size() < 2 || Args.size() > 3) {
beammp_lua_error("CreateEventTimer expects 2 or 3 arguments.");
}
if (Args.get_type(0) != sol::type::string) {
beammp_lua_error("CreateEventTimer expects 1st argument to be a string");
}
if (Args.get_type(1) != sol::type::number) {
beammp_lua_error("CreateEventTimer expects 2nd argument to be a number");
}
if (Args.size() == 3 && Args.get_type(2) != sol::type::number) {
beammp_lua_error("CreateEventTimer expects 3rd argument to be a number (MP.CallStrategy)");
}
auto EventName = Args.get<std::string>(0);
auto IntervalMS = Args.get<size_t>(1);
CallStrategy Strategy = Args.size() > 2 ? Args.get<CallStrategy>(2) : CallStrategy::BestEffort;
if (IntervalMS < 25) { if (IntervalMS < 25) {
beammp_warn("Timer for \"" + EventName + "\" on \"" + mStateId + "\" is set to trigger at <25ms, which is likely too fast and won't cancel properly."); beammp_warn("Timer for \"" + EventName + "\" on \"" + mStateId + "\" is set to trigger at <25ms, which is likely too fast and won't cancel properly.");
} }
mEngine->CreateEventTimer(EventName, mStateId, IntervalMS); mEngine->CreateEventTimer(EventName, mStateId, IntervalMS, Strategy);
}); });
MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) { MPTable.set_function("CancelEventTimer", [&](const std::string& EventName) {
mEngine->CancelEventTimers(EventName, mStateId); mEngine->CancelEventTimers(EventName, mStateId);
}); });
MPTable.set_function("Set", &LuaAPI::MP::Set); MPTable.set_function("Set", &LuaAPI::MP::Set);
auto UtilTable = StateView.create_named_table("Util");
UtilTable.set_function("JsonEncode", &LuaAPI::MP::JsonEncode);
UtilTable.set_function("JsonDecode", [this](const std::string& str) {
return Lua_JsonDecode(str);
});
UtilTable.set_function("JsonDiff", &LuaAPI::MP::JsonDiff);
UtilTable.set_function("JsonFlatten", &LuaAPI::MP::JsonFlatten);
UtilTable.set_function("JsonUnflatten", &LuaAPI::MP::JsonUnflatten);
UtilTable.set_function("JsonPrettify", &LuaAPI::MP::JsonPrettify);
UtilTable.set_function("JsonMinify", &LuaAPI::MP::JsonMinify);
UtilTable.set_function("Random", [this] {
return mUniformRealDistribution01(mMersenneTwister);
});
UtilTable.set_function("RandomRange", [this](double min, double max) -> double {
return std::uniform_real_distribution(min, max)(mMersenneTwister);
});
UtilTable.set_function("RandomIntRange", [this](int64_t min, int64_t max) -> int64_t {
return std::uniform_int_distribution(min, max)(mMersenneTwister);
});
auto HttpTable = StateView.create_named_table("Http"); auto HttpTable = StateView.create_named_table("Http");
HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) { HttpTable.set_function("CreateConnection", [this](const std::string& host, uint16_t port) {
return Lua_HttpCreateConnection(host, port); return Lua_HttpCreateConnection(host, port);
@@ -528,6 +750,10 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
"Name", 5, "Name", 5,
"Description", 6); "Description", 6);
MPTable.create_named("CallStrategy",
"BestEffort", CallStrategy::BestEffort,
"Precise", CallStrategy::Precise);
auto FSTable = StateView.create_named_table("FS"); auto FSTable = StateView.create_named_table("FS");
FSTable.set_function("CreateDirectory", &LuaAPI::FS::CreateDirectory); FSTable.set_function("CreateDirectory", &LuaAPI::FS::CreateDirectory);
FSTable.set_function("Exists", &LuaAPI::FS::Exists); FSTable.set_function("Exists", &LuaAPI::FS::Exists);
@@ -540,6 +766,12 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, std::atomi
FSTable.set_function("IsDirectory", &LuaAPI::FS::IsDirectory); FSTable.set_function("IsDirectory", &LuaAPI::FS::IsDirectory);
FSTable.set_function("IsFile", &LuaAPI::FS::IsFile); FSTable.set_function("IsFile", &LuaAPI::FS::IsFile);
FSTable.set_function("ConcatPaths", &LuaAPI::FS::ConcatPaths); FSTable.set_function("ConcatPaths", &LuaAPI::FS::ConcatPaths);
FSTable.set_function("ListFiles", [this](const std::string& Path) {
return Lua_FS_ListFiles(Path);
});
FSTable.set_function("ListDirectories", [this](const std::string& Path) {
return Lua_FS_ListDirectories(Path);
});
Start(); Start();
} }
@@ -550,12 +782,34 @@ std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueScript(const TLu
return Result; return Result;
} }
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCallFromCustomEvent(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args, const std::string& EventName, CallStrategy Strategy) {
// TODO: Document all this
decltype(mStateFunctionQueue)::iterator Iter = mStateFunctionQueue.end();
if (Strategy == CallStrategy::BestEffort) {
Iter = std::find_if(mStateFunctionQueue.begin(), mStateFunctionQueue.end(),
[&EventName](const QueuedFunction& Element) {
return Element.EventName == EventName;
});
}
if (Iter == mStateFunctionQueue.end()) {
auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId;
Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push_back({ FunctionName, Result, Args, EventName });
mStateFunctionQueueCond.notify_all();
return Result;
} else {
return nullptr;
}
}
std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) { std::shared_ptr<TLuaResult> TLuaEngine::StateThreadData::EnqueueFunctionCall(const std::string& FunctionName, const std::vector<TLuaArgTypes>& Args) {
auto Result = std::make_shared<TLuaResult>(); auto Result = std::make_shared<TLuaResult>();
Result->StateId = mStateId; Result->StateId = mStateId;
Result->Function = FunctionName; Result->Function = FunctionName;
std::unique_lock Lock(mStateFunctionQueueMutex); std::unique_lock Lock(mStateFunctionQueueMutex);
mStateFunctionQueue.push({ FunctionName, Result, Args }); mStateFunctionQueue.push_back({ FunctionName, Result, Args, "" });
mStateFunctionQueueCond.notify_all(); mStateFunctionQueueCond.notify_all();
return Result; return Result;
} }
@@ -566,7 +820,7 @@ void TLuaEngine::StateThreadData::RegisterEvent(const std::string& EventName, co
void TLuaEngine::StateThreadData::operator()() { void TLuaEngine::StateThreadData::operator()() {
RegisterThread("Lua:" + mStateId); RegisterThread("Lua:" + mStateId);
while (!mShutdown) { while (!Application::IsShuttingDown()) {
{ // StateExecuteQueue Scope { // StateExecuteQueue Scope
std::unique_lock Lock(mStateExecuteQueueMutex); std::unique_lock Lock(mStateExecuteQueueMutex);
if (!mStateExecuteQueue.empty()) { if (!mStateExecuteQueue.empty()) {
@@ -618,12 +872,13 @@ void TLuaEngine::StateThreadData::operator()() {
std::chrono::milliseconds(500), std::chrono::milliseconds(500),
[&]() -> bool { return !mStateFunctionQueue.empty(); }); [&]() -> bool { return !mStateFunctionQueue.empty(); });
if (NotExpired) { if (NotExpired) {
auto FnNameResultPair = std::move(mStateFunctionQueue.front()); auto TheQueuedFunction = std::move(mStateFunctionQueue.front());
mStateFunctionQueue.pop(); mStateFunctionQueue.erase(mStateFunctionQueue.begin());
Lock.unlock(); Lock.unlock();
auto& FnName = std::get<0>(FnNameResultPair); auto& FnName = TheQueuedFunction.FunctionName;
auto& Result = std::get<1>(FnNameResultPair); auto& Result = TheQueuedFunction.Result;
auto Args = std::get<2>(FnNameResultPair); auto Args = TheQueuedFunction.Args;
// TODO: Use TheQueuedFunction.EventName for errors, warnings, etc
Result->StateId = mStateId; Result->StateId = mStateId;
sol::state_view StateView(mState); sol::state_view StateView(mState);
auto Fn = StateView[FnName]; auto Fn = StateView[FnName];
@@ -671,13 +926,24 @@ void TLuaEngine::StateThreadData::operator()() {
} }
} }
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS) { std::queue<std::pair<TLuaChunk, std::shared_ptr<TLuaResult>>> TLuaEngine::StateThreadData::Debug_GetStateExecuteQueue() {
std::unique_lock Lock(mStateExecuteQueueMutex);
return mStateExecuteQueue;
}
std::vector<TLuaEngine::QueuedFunction> TLuaEngine::StateThreadData::Debug_GetStateFunctionQueue() {
std::unique_lock Lock(mStateFunctionQueueMutex);
return mStateFunctionQueue;
}
void TLuaEngine::CreateEventTimer(const std::string& EventName, TLuaStateId StateId, size_t IntervalMS, CallStrategy Strategy) {
std::unique_lock Lock(mTimedEventsMutex); std::unique_lock Lock(mTimedEventsMutex);
TimedEvent Event { TimedEvent Event {
std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) }, std::chrono::high_resolution_clock::duration { std::chrono::milliseconds(IntervalMS) },
std::chrono::high_resolution_clock::now(), std::chrono::high_resolution_clock::now(),
EventName, EventName,
StateId StateId,
Strategy
}; };
mTimedEvents.push_back(std::move(Event)); mTimedEvents.push_back(std::move(Event));
beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval"); beammp_trace("created event timer for \"" + EventName + "\" on \"" + StateId + "\" with " + std::to_string(IntervalMS) + "ms interval");
@@ -705,6 +971,7 @@ void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) {
void TLuaResult::WaitUntilReady() { void TLuaResult::WaitUntilReady() {
while (!Ready) { while (!Ready) {
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
} }
@@ -723,65 +990,3 @@ bool TLuaEngine::TimedEvent::Expired() {
void TLuaEngine::TimedEvent::Reset() { void TLuaEngine::TimedEvent::Reset() {
LastCompletion = std::chrono::high_resolution_clock::now(); LastCompletion = std::chrono::high_resolution_clock::now();
} }
TPluginMonitor::TPluginMonitor(const fs::path& Path, TLuaEngine& Engine, std::atomic_bool& Shutdown)
: mEngine(Engine)
, mPath(Path)
, mShutdown(Shutdown) {
if (!fs::exists(mPath)) {
fs::create_directories(mPath);
}
for (const auto& Entry : fs::recursive_directory_iterator(mPath)) {
// TODO: trigger an event when a subfolder file changes
if (Entry.is_regular_file()) {
mFileTimes[Entry.path().string()] = fs::last_write_time(Entry.path());
}
}
Start();
}
void TPluginMonitor::operator()() {
RegisterThread("PluginMonitor");
beammp_info("PluginMonitor started");
while (!mShutdown) {
std::this_thread::sleep_for(std::chrono::seconds(3));
for (const auto& Pair : mFileTimes) {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime != Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_info("File \"" + Pair.first + "\" changed, reloading");
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine.EnqueueScript(StateID, Chunk);
// TODO: call onInit
while (!Res->Ready) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
if (Res->Error) {
beammp_lua_error(Res->ErrorMessage);
}
} else {
// TODO: trigger onFileChanged event
beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet");
/*
// is in subfolder, dont reload, just trigger an event
auto Results = mEngine.TriggerEvent("onFileChanged", "", Pair.first);
mEngine.WaitForAll(Results);
for (const auto& Result : Results) {
if (Result->Error) {
beammp_lua_error(Result->ErrorMessage);
}
}*/
}
}
}
}
}
+146 -42
View File
@@ -6,6 +6,7 @@
#include <Http.h> #include <Http.h>
#include <array> #include <array>
#include <cstring> #include <cstring>
#include <dlhttp/dlhttp.h>
TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager) TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& ResourceManager)
: mServer(Server) : mServer(Server)
@@ -25,7 +26,6 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown); Application::SetSubsystemStatus("UDPNetwork", Application::Status::ShuttingDown);
if (mUDPThread.joinable()) { if (mUDPThread.joinable()) {
mShutdown = true;
mUDPThread.detach(); mUDPThread.detach();
} }
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Shutdown);
@@ -33,7 +33,6 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R
Application::RegisterShutdownHandler([&] { Application::RegisterShutdownHandler([&] {
Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown); Application::SetSubsystemStatus("TCPNetwork", Application::Status::ShuttingDown);
if (mTCPThread.joinable()) { if (mTCPThread.joinable()) {
mShutdown = true;
mTCPThread.detach(); mTCPThread.detach();
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown); Application::SetSubsystemStatus("TCPNetwork", Application::Status::Shutdown);
@@ -68,7 +67,7 @@ void TNetwork::UDPServerMain() {
Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good);
beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.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 (!Application::IsShuttingDown()) {
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
@@ -108,43 +107,57 @@ void TNetwork::TCPServerMain() {
#if defined(BEAMMP_WINDOWS) #if defined(BEAMMP_WINDOWS)
WSADATA wsaData; WSADATA wsaData;
if (WSAStartup(514, &wsaData)) { if (WSAStartup(514, &wsaData)) {
beammp_error("Can't start Winsock!"); beammp_error("Can't start Winsock! Shutting down");
return; Application::GracefullyShutdown();
} }
#endif // WINDOWS #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) {
beammp_error("Failed to create socket: " + GetPlatformAgnosticErrorString()
+ ". This is a fatal error, as a socket is needed for the server to operate. Shutting down.");
Application::GracefullyShutdown();
}
#if defined(BEAMMP_WINDOWS) #if defined(BEAMMP_WINDOWS)
const char* optval_ptr = reinterpret_cast<const char*>(&optval); const char optval = 0;
int ret = ::setsockopt(Listener, SOL_SOCKET, SO_DONTLINGER, &optval, sizeof(optval));
#elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE) #elif defined(BEAMMP_LINUX) || defined(BEAMMP_APPLE)
void* optval_ptr = reinterpret_cast<void*>(&optval); 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) {
beammp_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); // TODO: Wtf. + GetPlatformAgnosticErrorString());
Application::GracefullyShutdown();
} }
if (Listener == -1) { if (listen(Listener, SOMAXCONN) < 0) {
beammp_error("Invalid listening socket"); beammp_error("listen() failed, which is needed for the server to operate. "
return; "Shutting down. Error: "
} + GetPlatformAgnosticErrorString());
if (listen(Listener, SOMAXCONN)) { Application::GracefullyShutdown();
beammp_error("listen() failed: " + GetPlatformAgnosticErrorString());
// FIXME leak Listener
return;
} }
Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good); Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good);
beammp_info(("Vehicle event network online")); beammp_info("Vehicle event network online");
const auto ThreadCount = std::max(1, Application::Settings.DownloadThreads);
beammp_debugf("Using {} download threads", ThreadCount);
dlhttp::AsyncContext Ctx(ThreadCount);
dlhttp::EndpointHandlerMap Map {
{ "/modlist", [this]() -> dlhttp::Response { return { mResourceManager.FileList() }; } },
};
do { do {
try { try {
if (mShutdown) { if (Application::IsShuttingDown()) {
beammp_debug("shutdown during TCP wait for accept loop"); beammp_debug("shutdown during TCP wait for accept loop");
break; break;
} }
@@ -154,12 +167,34 @@ void TNetwork::TCPServerMain() {
beammp_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());
}
// check for http
if (dlhttp::is_http(client.Socket)) {
beammp_info("IS HTTP!");
dlhttp::handle_http(client.Socket, Ctx, Map);
} else {
beammp_info("IS NOT HTTP!");
}
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) {
beammp_error(("fatal: ") + std::string(e.what())); beammp_error("fatal: " + std::string(e.what()));
} }
} while (client.Socket); } while (client.Socket != BEAMMP_INVALID_SOCKET);
beammp_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__));
@@ -187,7 +222,11 @@ void TNetwork::Identify(const TConnection& client) {
} else if (Code == 'D') { } else if (Code == 'D') {
HandleDownload(client.Socket); HandleDownload(client.Socket);
} else if (Code == 'P') { } 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); send(client.Socket, "P", 1, 0);
#endif
CloseSocketProper(client.Socket); CloseSocketProper(client.Socket);
return; return;
} else { } else {
@@ -214,13 +253,26 @@ void TNetwork::HandleDownload(SOCKET TCPSock) {
}); });
} }
static int get_ip_str(const struct sockaddr* sa, char* strBuf, size_t strBufSize) {
switch (sa->sa_family) {
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), strBuf, strBufSize);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), strBuf, strBufSize);
break;
default:
return 1;
}
return 0;
}
void TNetwork::Authentication(const TConnection& ClientConnection) { void TNetwork::Authentication(const TConnection& ClientConnection) {
auto Client = CreateClient(ClientConnection.Socket); auto Client = CreateClient(ClientConnection.Socket);
char AddrBuf[64]; char AddrBuf[INET6_ADDRSTRLEN];
// TODO: IPv6 would need this to be changed get_ip_str(&ClientConnection.SockAddr, AddrBuf, sizeof(AddrBuf));
auto str = inet_ntop(AF_INET, reinterpret_cast<const void*>(&ClientConnection.SockAddr), AddrBuf, sizeof(ClientConnection.SockAddr)); beammp_trace("This thread is ip " + std::string(AddrBuf));
beammp_trace("This thread is ip " + std::string(str)); Client->SetIdentifier("ip", AddrBuf);
Client->SetIdentifier("ip", str);
std::string Rc; // TODO: figure out why this is not default constructed std::string Rc; // TODO: figure out why this is not default constructed
beammp_info("Identifying new ClientConnection..."); beammp_info("Identifying new ClientConnection...");
@@ -460,7 +512,7 @@ std::string TNetwork::TCPRcv(TClient& c) {
void TNetwork::ClientKick(TClient& c, const std::string& R) { void TNetwork::ClientKick(TClient& c, const std::string& R) {
beammp_info("Client kicked: " + R); beammp_info("Client kicked: " + R);
if (!TCPSend(c, "K" + 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);
@@ -510,6 +562,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) {
@@ -735,14 +788,63 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
} }
} }
static std::pair<size_t /* count */, size_t /* last chunk */> SplitIntoChunks(size_t FullSize, size_t ChunkSize) {
if (FullSize < ChunkSize) {
return { 0, FullSize };
}
size_t Count = FullSize / (FullSize / ChunkSize);
size_t LastChunkSize = FullSize - (Count * ChunkSize);
return { Count, LastChunkSize };
}
TEST_CASE("SplitIntoChunks") {
size_t FullSize;
size_t ChunkSize;
SUBCASE("Normal case") {
FullSize = 1234567;
ChunkSize = 1234;
}
SUBCASE("Zero original size") {
FullSize = 0;
ChunkSize = 100;
}
SUBCASE("Equal full size and chunk size") {
FullSize = 125;
ChunkSize = 125;
}
SUBCASE("Even split") {
FullSize = 10000;
ChunkSize = 100;
}
SUBCASE("Odd split") {
FullSize = 13;
ChunkSize = 2;
}
SUBCASE("Large sizes") {
FullSize = 10 * GB;
ChunkSize = 125 * MB;
}
auto [Count, LastSize] = SplitIntoChunks(FullSize, ChunkSize);
CHECK((Count * ChunkSize) + LastSize == FullSize);
}
uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, SOCKET Socket, uint8_t* DataPtr, size_t Size) {
if (TCPSendRaw(c, Socket, reinterpret_cast<char*>(DataPtr), Size)) {
return DataPtr + Size;
} else {
return nullptr;
}
}
void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name) { 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 auto Buf = f.rdbuf();
char* Data; uint32_t Split = 125 * MB;
std::vector<uint8_t> Data;
if (Size > Split) if (Size > Split)
Data = new char[Split]; Data.resize(Split);
else else
Data = new char[Size]; Data.resize(Size);
SOCKET TCPSock; SOCKET TCPSock;
if (D) if (D)
TCPSock = c.GetDownSock(); TCPSock = c.GetDownSock();
@@ -753,8 +855,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
size_t Diff = Size - Sent; size_t Diff = Size - Sent;
if (Diff > Split) { if (Diff > Split) {
f.seekg(Sent, std::ios_base::beg); f.seekg(Sent, std::ios_base::beg);
f.read(Data, Split); f.read(reinterpret_cast<char*>(Data.data()), Split);
if (!TCPSendRaw(c, TCPSock, Data, Split)) { if (!TCPSendRaw(c, TCPSock, reinterpret_cast<char*>(Data.data()), Split)) {
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
break; break;
@@ -762,8 +864,8 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Split; Sent += Split;
} else { } else {
f.seekg(Sent, std::ios_base::beg); f.seekg(Sent, std::ios_base::beg);
f.read(Data, Diff); f.read(reinterpret_cast<char*>(Data.data()), Diff);
if (!TCPSendRaw(c, TCPSock, Data, int32_t(Diff))) { if (!TCPSendRaw(c, TCPSock, reinterpret_cast<char*>(Data.data()), int32_t(Diff))) {
if (c.GetStatus() > -1) if (c.GetStatus() > -1)
c.SetStatus(-1); c.SetStatus(-1);
break; break;
@@ -771,14 +873,16 @@ void TNetwork::SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std
Sent += Diff; Sent += Diff;
} }
} }
delete[] Data;
f.close();
} }
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) {
beammp_info("Socket Closed! " + std::to_string(socket)); beammp_info("Socket Closed! " + std::to_string(socket));
CloseSocketProper(socket); CloseSocketProper(socket);
+3 -4
View File
@@ -10,7 +10,6 @@ TPPSMonitor::TPPSMonitor(TServer& Server)
Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown); Application::SetSubsystemStatus("PPSMonitor", Application::Status::ShuttingDown);
if (mThread.joinable()) { if (mThread.joinable()) {
beammp_debug("shutting down PPSMonitor"); beammp_debug("shutting down PPSMonitor");
mShutdown = true;
mThread.join(); mThread.join();
beammp_debug("shut down PPSMonitor"); beammp_debug("shut down PPSMonitor");
} }
@@ -21,13 +20,13 @@ TPPSMonitor::TPPSMonitor(TServer& Server)
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();
} }
beammp_debug("PPSMonitor starting"); beammp_debug("PPSMonitor starting");
Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good); Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good);
std::vector<std::shared_ptr<TClient>> TimedOutClients; std::vector<std::shared_ptr<TClient>> TimedOutClients;
while (!mShutdown) { while (!Application::IsShuttingDown()) {
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
int C = 0, V = 0; int C = 0, V = 0;
if (mServer.ClientCount() == 0) { if (mServer.ClientCount() == 0) {
+75
View File
@@ -0,0 +1,75 @@
#include "TPluginMonitor.h"
#include "TLuaEngine.h"
TPluginMonitor::TPluginMonitor(const fs::path& Path, std::shared_ptr<TLuaEngine> Engine)
: mEngine(Engine)
, mPath(Path) {
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Starting);
if (!fs::exists(mPath)) {
fs::create_directories(mPath);
}
for (const auto& Entry : fs::recursive_directory_iterator(mPath)) {
// TODO: trigger an event when a subfolder file changes
if (Entry.is_regular_file()) {
mFileTimes[Entry.path().string()] = fs::last_write_time(Entry.path());
}
}
Application::RegisterShutdownHandler([this] {
if (mThread.joinable()) {
mThread.join();
}
});
Start();
}
void TPluginMonitor::operator()() {
RegisterThread("PluginMonitor");
beammp_info("PluginMonitor started");
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Good);
while (!Application::IsShuttingDown()) {
std::vector<std::string> ToRemove;
for (const auto& Pair : mFileTimes) {
try {
auto CurrentTime = fs::last_write_time(Pair.first);
if (CurrentTime > Pair.second) {
mFileTimes[Pair.first] = CurrentTime;
// grandparent of the path should be Resources/Server
if (fs::equivalent(fs::path(Pair.first).parent_path().parent_path(), mPath)) {
beammp_infof("File \"{}\" changed, reloading", Pair.first);
// is in root folder, so reload
std::ifstream FileStream(Pair.first, std::ios::in | std::ios::binary);
auto Size = std::filesystem::file_size(Pair.first);
auto Contents = std::make_shared<std::string>();
Contents->resize(Size);
FileStream.read(Contents->data(), Contents->size());
TLuaChunk Chunk(Contents, Pair.first, fs::path(Pair.first).parent_path().string());
auto StateID = mEngine->GetStateIDForPlugin(fs::path(Pair.first).parent_path());
auto Res = mEngine->EnqueueScript(StateID, Chunk);
Res->WaitUntilReady();
if (Res->Error) {
beammp_lua_errorf("Error while hot-reloading \"{}\": {}", Pair.first, Res->ErrorMessage);
} else {
mEngine->ReportErrors(mEngine->TriggerLocalEvent(StateID, "onInit"));
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
}
} else {
// is in subfolder, dont reload, just trigger an event
beammp_debugf("File \"{}\" changed, not reloading because it's in a subdirectory. Triggering 'onFileChanged' event instead", Pair.first);
mEngine->ReportErrors(mEngine->TriggerEvent("onFileChanged", "", Pair.first));
}
}
} catch (const std::exception& e) {
ToRemove.push_back(Pair.first);
}
}
Application::SleepSafeSeconds(3);
for (const auto& File : ToRemove) {
mFileTimes.erase(File);
beammp_warnf("File \"{}\" couldn't be accessed, so it was removed from plugin hot reload monitor (probably got deleted)", File);
}
}
Application::SetSubsystemStatus("PluginMonitor", Application::Status::Shutdown);
}
+2 -1
View File
@@ -28,8 +28,9 @@ TResourceManager::TResourceManager() {
} }
} }
if (mModsLoaded) if (mModsLoaded) {
beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods"); beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods");
}
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
} }
+1 -1
View File
@@ -72,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) {
+25 -35
View File
@@ -7,14 +7,14 @@
#include <any> #include <any>
#include <sstream> #include <sstream>
#include <nlohmann/json.hpp>
#include "LuaAPI.h" #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) { TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString()); beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting); Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -125,7 +125,7 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::string Pac
break; break;
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 2)); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onChatMessage", "", LockedClient->GetID(), LockedClient->GetName(), Packet.substr(Packet.find(':', 3) + 2));
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1)); // FIXME: this needs to be adjusted once lua is merged LogChatMessage(LockedClient->GetName(), LockedClient->GetID(), Packet.substr(Packet.find(':', 3) + 1));
if (std::any_of(Futures.begin(), Futures.end(), if (std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Elem) { [](const std::shared_ptr<TLuaResult>& Elem) {
return !Elem->Error return !Elem->Error
@@ -150,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:
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent(Name, "", c.GetID(), t));
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";
beammp_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) {
+25 -17
View File
@@ -11,13 +11,14 @@
#include "TLuaEngine.h" #include "TLuaEngine.h"
#include "TNetwork.h" #include "TNetwork.h"
#include "TPPSMonitor.h" #include "TPPSMonitor.h"
#include "TPluginMonitor.h"
#include "TResourceManager.h" #include "TResourceManager.h"
#include "TScopedTimer.h" #include "TScopedTimer.h"
#include "TServer.h" #include "TServer.h"
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
static const std::string sCommandlineArguments = R"( static const std::string sCommandlineArguments = R"(
USAGE: USAGE:
BeamMP-Server [arguments] BeamMP-Server [arguments]
@@ -44,10 +45,6 @@ EXAMPLES:
'MyWestCoastServerConfig.toml'. 'MyWestCoastServerConfig.toml'.
)"; )";
// this is provided by the build system, leave empty for source builds
// global, yes, this is ugly, no, it cant be done another way
TSentry Sentry {};
struct MainArguments { struct MainArguments {
int argc {}; int argc {};
char** argv {}; char** argv {};
@@ -72,16 +69,12 @@ int main(int argc, char** argv) {
Sentry.LogException(e, _file_basename, _line); Sentry.LogException(e, _file_basename, _line);
MainRet = -1; MainRet = -1;
} }
return MainRet; std::exit(MainRet);
} }
int BeamMPServerMain(MainArguments Arguments) { int BeamMPServerMain(MainArguments Arguments) {
setlocale(LC_ALL, "C"); setlocale(LC_ALL, "C");
Application::InitializeConsole(); Application::InitializeConsole();
Application::SetSubsystemStatus("Main", Application::Status::Starting);
SetupSignalHandlers();
ArgsParser Parser; ArgsParser Parser;
Parser.RegisterArgument({ "help" }, ArgsParser::NONE); Parser.RegisterArgument({ "help" }, ArgsParser::NONE);
Parser.RegisterArgument({ "version" }, ArgsParser::NONE); Parser.RegisterArgument({ "version" }, ArgsParser::NONE);
@@ -117,26 +110,33 @@ int BeamMPServerMain(MainArguments Arguments) {
try { try {
fs::current_path(fs::path(MaybeWorkingDirectory.value())); fs::current_path(fs::path(MaybeWorkingDirectory.value()));
} catch (const std::exception& e) { } catch (const std::exception& e) {
beammp_error("Could not set working directory to '" + MaybeWorkingDirectory.value() + "': " + e.what()); 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] { Application::RegisterShutdownHandler([&Shutdown] {
beammp_info("If this takes too long, you can press Ctrl+C repeatedly to force a shutdown.");
Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown); Application::SetSubsystemStatus("Main", Application::Status::ShuttingDown);
Shutdown = true; Shutdown = true;
}); });
Application::RegisterShutdownHandler([] { Application::RegisterShutdownHandler([] {
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", ""); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onShutdown", "");
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures, std::chrono::seconds(5));
}); });
TServer Server(Arguments.List); TServer Server(Arguments.List);
TConfig Config(ConfigPath); TConfig Config(ConfigPath);
TLuaEngine LuaEngine; auto LuaEngine = std::make_shared<TLuaEngine>();
LuaEngine.SetServer(&Server); LuaEngine->SetServer(&Server);
Application::Console().InitializeLuaConsole(LuaEngine); Application::Console().InitializeLuaConsole(*LuaEngine);
if (Config.Failed()) { if (Config.Failed()) {
beammp_info("Closing in 10 seconds"); beammp_info("Closing in 10 seconds");
@@ -156,18 +156,23 @@ int BeamMPServerMain(MainArguments Arguments) {
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);
LuaEngine.SetNetwork(&Network); LuaEngine->SetNetwork(&Network);
PPSMonitor.SetNetwork(Network); PPSMonitor.SetNetwork(Network);
Application::CheckForUpdates(); Application::CheckForUpdates();
TPluginMonitor PluginMonitor(fs::path(Application::Settings.Resource) / "Server", LuaEngine);
if (Application::Settings.HTTPServerEnabled) { if (Application::Settings.HTTPServerEnabled) {
Http::Server::SetupEnvironment();
Http::Server::THttpServerInstance HttpServerInstance {}; Http::Server::THttpServerInstance HttpServerInstance {};
} }
Application::SetSubsystemStatus("Main", Application::Status::Good); Application::SetSubsystemStatus("Main", Application::Status::Good);
RegisterThread("Main(Waiting)"); RegisterThread("Main(Waiting)");
std::set<std::string> IgnoreSubsystems {
"UpdateCheck" // Ignore as not to confuse users (non-vital system)
};
bool FullyStarted = false; bool FullyStarted = false;
while (!Shutdown) { while (!Shutdown) {
if (!FullyStarted) { if (!FullyStarted) {
@@ -176,6 +181,9 @@ int BeamMPServerMain(MainArguments Arguments) {
std::string SystemsBadList {}; std::string SystemsBadList {};
auto Statuses = Application::GetSubsystemStatuses(); auto Statuses = Application::GetSubsystemStatuses();
for (const auto& NameStatusPair : Statuses) { for (const auto& NameStatusPair : Statuses) {
if (IgnoreSubsystems.count(NameStatusPair.first) > 0) {
continue; // ignore
}
if (NameStatusPair.second == Application::Status::Starting) { if (NameStatusPair.second == Application::Status::Starting) {
FullyStarted = false; FullyStarted = false;
} else if (NameStatusPair.second == Application::Status::Bad) { } else if (NameStatusPair.second == Application::Status::Bad) {
+22
View File
@@ -0,0 +1,22 @@
#define DOCTEST_CONFIG_IMPLEMENT
#include <doctest/doctest.h>
#include <Common.h>
int main(int argc, char** argv) {
doctest::Context context;
// Application::InitializeConsole();
context.applyCommandLine(argc, argv);
int res = context.run(); // run
if (context.shouldExit()) // important - query flags (and --exit) rely on the user doing this
return res; // propagate the result of the tests
int client_stuff_return_code = 0;
// your program - if the testing framework is integrated in your production code
return res + client_stuff_return_code; // the result from doctest is propagated here as well
}