200 Commits

Author SHA1 Message Date
Lion Kortlepel
dd3622170e add debug print and dont try new format if the packet looks invalid 2024-10-06 15:26:02 +02:00
Lion Kortlepel
885061f73d add more info to new mod list receive error 2024-10-06 15:22:45 +02:00
Lion Kortlepel
fcb51adcb8 bump version 2024-10-05 21:05:02 +02:00
Lion
768f11f6ec Add mod hashing, improve download protocol (#129) 2024-10-04 23:36:57 +02:00
Lion
7944e9dbe8 Switch to curl for Get and Post (#132)
Because we can. I got a segfault while testing but then it didn't happen
again, so I'm tempted to call it done.
2024-10-04 23:23:49 +02:00
Lion Kortlepel
0c68f91fb2 remove debug print 2024-10-04 23:22:50 +02:00
Lion
b8fdbc4ed9 Fix GetGamePath (#130)
Previously, the registry was used to get the local appdata folder for
the user folder. I've switched this over to a windows api function which
fixes some cases where the launcher wouldn't be able to find the appdata
folder in the registry.
2024-10-04 23:12:56 +02:00
Lion Kortlepel
85908e42d5 fix download code, error checking 2024-10-04 23:12:23 +02:00
Lion Kortlepel
5c77e60f29 remove mis-merged code 2024-10-04 23:04:30 +02:00
Lion Kortlepel
c74455e0fe switch to curl for Get and Post 2024-10-04 22:59:29 +02:00
Lion
dc13e4a03c remove extra return 2024-10-04 14:13:04 +02:00
Tixx
1d7eb64fe0 Get localappdata via winapi instead of registry 2024-10-03 22:42:49 +02:00
Lion Kortlepel
1676d4174e make mods not keep the hash when copying them 2024-09-29 02:36:41 +02:00
Lion Kortlepel
ad468a8971 remove debug prints 2024-09-29 02:04:39 +02:00
Lion Kortlepel
d3805f2cfd fix mod deleting misnamed mods 2024-09-29 01:57:15 +02:00
Lion Kortlepel
9f1cc15b15 fix bugs with new download 2024-09-29 01:15:57 +02:00
Lion Kortlepel
c0fb4e4ad6 implement support for new mod hashing and download 2024-09-29 00:33:15 +02:00
Lion
7600372ca1 Fix linux executable name after BNG0.33.2 (#126) 2024-09-28 16:51:02 +02:00
Lion
54cd5b5e0e Add additional SSL Verify logging (#127) 2024-09-28 16:50:30 +02:00
Mackenzie
ede6fcd7dd log SSL errors 2024-09-27 20:33:14 +01:00
Mackenzie
eaeacbd8de log non-200 status codes 2024-09-27 20:23:28 +01:00
O1LER
0ffed00bcb rename linux executable for bng0.33.2 2024-09-27 17:48:46 +02:00
Lion
c0c3d6b30e Add download speed to UI (#125) 2024-09-24 21:59:01 +02:00
Lion
9c59a83f04 turn off stdout, stderr of the game on linux (#124) 2024-09-24 21:58:37 +02:00
Lion Kortlepel
95436cb073 turn off stdout, stderr of the game on linux 2024-09-24 21:56:55 +02:00
Lion Kortlepel
cbb5502a40 send download speed to game UI, bump version to 2.1.4 2024-09-24 21:50:09 +02:00
Lion Kortlepel
d6dfe85f69 add download speed to ingame ui 2024-09-24 21:10:10 +02:00
Tixx
ae9af1470c Removal invalid comma causing the default config to be broken (#123) 2024-09-24 12:47:20 +02:00
Tixx
9255c70b0b Removal invalid comma 2024-09-24 12:38:24 +02:00
Lion Kortlepel
53c514ecc6 bump to 2.1.3 2024-09-23 23:13:51 +02:00
Lion Kortlepel
e348d59a7e fix linux executable name 2024-09-23 23:13:34 +02:00
Lion
244d27341f Fix release actions (#122) 2024-09-23 22:49:33 +02:00
Lion Kortlepel
3a55b62907 remove release action 2024-09-23 22:49:03 +02:00
Lion
0c3ae43910 Add CachingDirectory config setting to cache mods elsewhere (#121)
also moved cls/clear to the beginning, idk wtf it was doing in there.
2024-09-23 22:45:21 +02:00
Lion Kortlepel
8436586566 print version on startup
🚀
2024-09-23 22:43:32 +02:00
Lion Kortlepel
19d1245379 catch errors when the custom caching directory is not accessible
🧯
2024-09-23 22:39:44 +02:00
Lion
470eeac821 Add better error handling (#119) 2024-09-23 22:34:19 +02:00
Lion
9c6aa86e68 Add print to inform the user that they must keep the window open (#120) 2024-09-23 22:33:54 +02:00
Lion Kortlepel
1362471657 add CachingDirectory config setting to cache mods elsewhere
also moved cls/clear to the beginning, idk wtf it was doing in there.
2024-09-23 22:31:58 +02:00
Lion Kortlepel
aa46b454e2 add print to inform the user that they must keep the window open 2024-09-23 22:12:00 +02:00
Lion Kortlepel
02465c529d add more logging to exit 2024-09-23 22:08:45 +02:00
Lion Kortlepel
c68cbf8946 remove unused """security""" code 2024-09-23 22:04:34 +02:00
Lion Kortlepel
46542c1dce always log debug to Launcher.log 2024-09-23 22:00:41 +02:00
Lion Kortlepel
97f58dd413 add better error handling to main() 2024-09-23 21:58:27 +02:00
Lion
4bedfc8e96 Little Itsy Bitsy TCP fixes (#118) 2024-09-23 21:46:11 +02:00
Lion Kortlepel
cd17df5cc2 add more debug statements, wait for threads before shutting down 2024-09-22 21:37:52 +02:00
Lion Kortlepel
0b589a74c9 refactor tcp receive to be less weird 2024-09-22 20:31:25 +02:00
Lion Kortlepel
1260515a40 fix crash when cancelling download 2024-09-22 20:20:31 +02:00
Lion
007cd6573e Refactor downloading (#116)
The way it was done was so horrid, it was not only impossible to debug,
with TODO comments saying it sucks, and other shit like that, but it was
also just full of data races. You can rest easy however - I left most of
the data races in there <3 For nostalgia (totally not because it's a
massive pain to fix that).

We now do single-threaded download, which can not only saturate my 100
Mbit/s line without any hickups, it can also go up to ~600000 Mbit/s for
localhost transfers :) So I think it's fine.
2024-09-22 20:04:45 +02:00
Lion
7b022f9907 Add --skip-ssl-verify cli option (#117)
This is a temporary fix for if anyone has issues with SSL certificate
validation. The use of this must come with the disclaimer that,
obviously, this bypasses the security that SSL gives entirely. Anyone
could MITM you at that point. Don't use, basically.
2024-09-22 19:56:43 +02:00
Lion Kortlepel
96c9c89238 add extra layer of checks for data races in download
yeah
2024-09-22 19:52:52 +02:00
Lion
b4949af1d7 Check 'User Shell Folders' (#111)
this PR is a continuation of #69
2024-09-22 19:47:50 +02:00
Lion
85086909a6 Merge pull request #108 from WiserTixx/implement-mods-warning
Implement mods warning
2024-09-22 19:46:34 +02:00
Lion Kortlepel
79209219dd remove extraneous game user path print 2024-09-22 19:42:55 +02:00
Lion Kortlepel
18e1b7a2bb add --skip-ssl-verify cli option 2024-09-22 19:42:00 +02:00
Lion Kortlepel
a5766639d6 add back user path print
Thanks @WiserTixx for finding a good place for it
2024-09-22 19:29:39 +02:00
Lion Kortlepel
191fbf083d fix stupid microsoft macro <3 2024-09-22 19:06:46 +02:00
Lion Kortlepel
8c4342853a refactor downloading
The way it was done was so horrid, it was not only impossible to debug,
with TODO comments saying it sucks, and other shit like that, but it was
also just full of data races. You can rest easy however - I left most of
the data races in there <3 For nostalgia (totally not because it's a
massive pain to fix that).

We now do single-threaded download, which can not only saturate my 100
Mbit/s line without any hickups, it can also go up to ~600000 Mbit/s for
localhost transfers :) So I think it's fine.
2024-09-22 18:52:50 +02:00
Tixx
3937ac1ae7 Fix joining 2024-09-14 22:17:21 +02:00
Tixx
a128099619 Patch up removal of while loop in Core 2024-09-14 22:17:21 +02:00
Tixx
deed24f6e8 Fix client lua error 2024-09-14 22:17:21 +02:00
Tixx
ac2db7c73f Remove now unused variable 2024-09-14 22:17:21 +02:00
Tixx
06db6d0341 Implement mod warning 2024-09-14 22:17:21 +02:00
Deer McDurr
2d43e11e96 Merge pull request #114 from WiserTixx/action-fix
Fix github actions
2024-09-14 22:14:53 +02:00
Tixx
8911158f81 Fix actions 2024-09-14 22:03:52 +02:00
20dka
a714dc3188 fix windows build and implement suggestion from lionkor 2024-09-08 16:42:03 +02:00
yeranya
29445f65ce check 'User Shell Folders' key in addition to 'Shell Folders' 2024-09-08 16:24:58 +02:00
Deer McDurr
48be292850 Merge pull request #103 from purifiedfr/readme-build-guide
Update the Build guide in README
2024-09-08 16:00:06 +02:00
purified
2397f45d3f Add the guide on how to clone the repository with the evpp submodule 2024-09-08 15:57:15 +02:00
purified
d1fb67f1f0 Update the Build guide in README
Add instructions for building in Release mode
Add the reminder to change the vcpkg location
Add the reminder to run the commands in the root of the project
2024-09-08 15:57:15 +02:00
Deer McDurr
eae6d11476 Merge pull request #110 from WiserTixx/improve-http-proxy
HTTP proxy improvements, avatar endpoint
2024-09-08 14:46:49 +02:00
Tixx
452fc1e484 Move HTTP Proxy and remove and relocate duplicate code 2024-09-07 22:35:27 +02:00
Tixx
de3888618a Safety improvements 2024-09-07 22:00:51 +02:00
Tixx
4678701f42 HTTP proxy improvements
Adds the avatar endpoint and adds the possibility to easily add others
2024-09-07 21:19:42 +02:00
Deer McDurr
7481ba4539 Merge pull request #109 from WiserTixx/allow-patreon-link
Add BeamMP patreon to the allowed links
2024-09-07 20:53:40 +02:00
Tixx
d791e2ac92 Add BeamMP patreon to the allowed links 2024-08-22 22:09:02 +02:00
Lion
a60ff48c08 Merge pull request #105 from WiserTixx/id-from-auth
Send id from auth to game
2024-08-17 20:34:19 +02:00
Lion
da3b49aa12 Merge pull request #106 from WiserTixx/fix-http-proxy-ub
Fix UB which was causing the http proxy to crash
2024-08-17 20:32:59 +02:00
Tixx
e505874af9 Send id from auth to game 2024-08-11 11:39:14 +02:00
Tixx
2f0a9fba99 move macro definition to cmakelist 2024-08-10 23:22:17 +02:00
Lion Kortlepel
b034072027 fix potential UB in decompression 2024-06-23 23:04:55 +02:00
Lion Kortlepel
f94b9adf7a print game's U S E R path 2024-06-22 23:26:10 +02:00
Lion Kortlepel
c95178ea59 dont auto-update in dev mode 2024-06-22 23:20:46 +02:00
Lion Kortlepel
1f7c498bd9 fix compiler error in decomp 2024-06-22 23:05:01 +02:00
Lion Kortlepel
e46d4b2f0e Merge branch 'performance-improvements' 2024-06-22 23:01:15 +02:00
Lion Kortlepel
f2b34543f9 switch to compression with limit at 30 MB 2024-06-22 22:48:00 +02:00
Lion Kortlepel
d32da036bc fix mod name bug 2024-06-21 17:30:47 +02:00
Lion Kortlepel
8b0f4f99f6 use thread_local static buffer to receive into, null term manually 2024-06-19 16:53:17 +02:00
Lion Kortlepel
17e887442c avoid a substr() which costs us ~20% of runtime performance 2024-06-19 16:18:11 +02:00
Lion Kortlepel
fc454cd11e avoid creating a thread every packet 2024-06-19 15:53:49 +02:00
Lion
e0e2607632 Merge pull request #96 from BeamMP/finalize-linux-merge
Finalize linux merge
2024-06-18 09:57:24 +02:00
Lion Kortlepel
25f28e7fee add common runtime files to gitignore 2024-06-18 09:57:05 +02:00
Lion Kortlepel
ba9719ed67 fix auth packet prefix 2024-06-18 09:46:33 +02:00
Lion Kortlepel
bb04d1bfe1 remove self update on linux for now 2024-06-18 08:51:23 +02:00
Lion Kortlepel
82e58e6513 add toolchain to linux build
oops
2024-06-17 22:36:15 +02:00
Lion Kortlepel
274a1dac7c fix workflows vcpkg version 2024-06-17 22:30:26 +02:00
Lion Kortlepel
ae7f8f44e3 re-implement the website launch feature for linux 2024-06-17 22:16:17 +02:00
Lion Kortlepel
a82b9fb36f reformat 2024-06-17 22:01:15 +02:00
Lion Kortlepel
3488136ca4 add clang format 2024-06-17 21:59:59 +02:00
Lion Kortlepel
05ffa0b638 Merge branch 'linux' 2024-06-17 21:57:41 +02:00
Lion
942cc78406 remove debug print 2024-06-15 22:24:19 +02:00
Lion
46690b5bbf Merge pull request #79 from BeamMP/hotfix-stuff
Fixup various bits
2024-06-15 21:46:07 +02:00
Lion
cc4e322065 Merge pull request #88 from saile515/fix-input-linux
Fix input not working on Linux
2024-05-29 11:13:05 +02:00
saile515
915c05c57c fix input not working on linux 2024-05-29 10:56:22 +02:00
Lion Kortlepel
adba66afb9 add http debug error logging 2024-03-22 12:19:18 +01:00
Lion Kortlepel
cc42a5e0ab add ssl verify debug logging in a few places 2024-03-22 11:34:14 +01:00
Lion Kortlepel
c8a1b77a54 remove discord 2024-03-22 11:23:49 +01:00
Lion Kortlepel
b9d252fd8a remove old stuff 2024-03-22 11:22:17 +01:00
Lion Kortlepel
2b02475fd4 add build instructions 2024-03-22 11:09:02 +01:00
Lion Kortlepel
8aa7a67100 add vcpkg.json 2024-03-22 11:07:30 +01:00
Lion Kortlepel
348090a127 fix perms on more source files
... wtf
2024-03-22 10:50:58 +01:00
Lion Kortlepel
c0df995e28 fix perms on all source files 2024-03-22 10:47:08 +01:00
Lion Kortlepel
8c9d3a5455 fix not returning error from Login, remove old code 2024-02-24 20:26:26 +01:00
Lion Kortlepel
9dcfa1dca4 set username or role to auth if they're empty (not set) 2024-02-24 20:26:26 +01:00
Lion Kortlepel
bd4cfe06b1 reset username and role on logout 2024-02-24 20:26:26 +01:00
Lion Kortlepel
9c7034e401 store username and role on key login as well 2024-02-24 20:26:26 +01:00
Lion Kortlepel
aeb167c1e8 forward user's role on login 2024-02-24 20:26:26 +01:00
Lion Kortlepel
7967ec38e8 bump version to *.85 2024-02-24 20:26:26 +01:00
Lion Kortlepel
250be2ccdc fix more breaking bug 2024-02-24 20:26:26 +01:00
Lion Kortlepel
6158069d4d fix game breaking bug 2024-02-24 20:26:26 +01:00
Lion Kortlepel
b2e5b8d2d3 print error when throwing exception in auth 2024-02-24 20:26:26 +01:00
Lion Kortlepel
5db1b48e07 Revert "debug print error in case of unexpected login error"
This reverts commit 68d64105de.
2024-02-24 20:26:26 +01:00
Lion Kortlepel
56dcfcc5ed debug print error in case of unexpected login error 2024-02-24 20:26:26 +01:00
Lion Kortlepel
7c1106a46a add username to auth packet 2024-02-24 20:26:26 +01:00
Anonymous275
9afdfd4d1b v2.0.84
- add Access-Control response headers
2023-12-16 14:30:35 +00:00
Anonymous275
c2f260a86c v2.0.84
- remove comment
2023-12-15 19:39:55 +00:00
Anonymous275
2781179b4b v2.0.84
- proxy tweaking
2023-12-15 19:38:47 +00:00
Anonymous275
3b479abf64 v2.0.84
- add hash check
- new routes for updates
- use C++ 20
2023-12-15 18:16:12 +00:00
Anonymous275
c731718f50 v2.0.84
- HTTP Proxy for backend.beammp.com
- Fix Attempt for mod loading, game detecting partial zip file
- Use nlohmann JSON
- Update vcpkg parameters and commit ID
- Add ability to open URL using default browser with filter
2023-12-15 17:38:47 +00:00
Anonymous-275
0fd0a9fe7e Merge remote-tracking branch 'origin/master' 2023-10-26 00:09:04 +01:00
Anonymous-275
302582bfe1 v2.0.83
- removed code that is no longer needed
2023-10-26 00:08:56 +01:00
gamingdoom
35ad09dd5f Remove extra newline 2023-10-17 21:01:38 -07:00
gamingdoom
8ed2921ec1 remove .vscode 2023-10-17 20:41:15 -07:00
gamingdoom
1bf7faa34d fix guest account issue and uppercase mods 2023-10-17 20:40:11 -07:00
gamingdoom
8ead91c527 Update release-build.yml 2023-10-17 19:31:40 -07:00
gamingdoom
5e602a651c Fix linux build artifact path 2023-10-17 19:18:48 -07:00
gamingdoom
0993b2a463 fix linux build name ... again 2023-10-17 19:16:17 -07:00
gamingdoom
73ada2b541 fix linux build name 2023-10-17 19:15:44 -07:00
gamingdoom
f9bd0967bc Add github actions Linux build 2023-10-17 19:14:54 -07:00
gamingdoom
16b13c074e remove debug symbols 2023-10-17 19:13:16 -07:00
gamingdoom
0a9a883ca4 fix windows build 2023-10-17 18:59:49 -07:00
gamingdoom
a91735531a linux build 2023-10-17 18:53:01 -07:00
gamingdoom
f98ff3f502 Update cmake-windows.yml 2023-10-17 18:38:59 -07:00
gamingdoom
5dfb5f3b88 works but linux build is broken and this is an old version of the source 2023-10-16 22:13:30 -07:00
gamingdoom
54e1beb548 rename network.h to network.hpp 2023-10-16 17:33:52 -07:00
Anonymous275
839bb48cd6 Merge pull request #58 from WhiteHusky/patch-1
Emit a useful message if cleaning the mods folder fails
2023-08-04 20:30:36 +01:00
Carlen White
c4ebdda1a4 Emit a useful message if cleaning the mods folder fails
The launcher complains if it can not delete a file when it's trying to clean the mods folder out. However the message it emits is unhelpful and does not offer a suggestion to what is going on.

This commit addresses this issue by telling the user what the launcher is trying to do and suggests checking if something is currently open in that directory. I figure not having to go into detail in *where* the folder is not necessary and (primarily) only happens if the user is inspecting the folder themselves and already knows where the folder is.
2023-07-02 23:02:14 -04:00
Anonymous275
d6b494c6c4 - release v2.0.82 2022-12-18 14:30:21 +00:00
Anonymous275
a80d4f5147 - quick fix for launcher crash 2022-12-18 14:29:38 +00:00
Anonymous275
811b04485c - try fix for github workflow 2022-12-18 13:20:20 +00:00
Simon Abed El Sater
a64fead653 update 2.0.81 2022-12-18 14:39:54 +02:00
Simon Abed El Sater
399461d1b1 remove version check 2022-12-18 14:39:28 +02:00
Anonymous275
ec5e8ed5b3 v2.0.80 2022-09-25 20:39:27 +03:00
Anonymous275
5655164e60 bump 2.0.80 2022-09-25 20:37:46 +03:00
Simon Abed El Sater
3d9b7c2d67 Merge pull request #49 from snepsnepsnep/master
Fix kick message for server 3.0+
2022-09-25 20:32:50 +03:00
snepsnepsnep
764e3ab5c1 Fix kick message for server 3.0+ 2022-09-25 19:26:46 +02:00
Simon Abed El Sater
3314362faf Merge pull request #48 from Mack29446/master
Decided by vote: Update BeamNG Version and remove Mod Delete warning & Delay
2022-09-25 01:18:58 +03:00
Mackenzie
e483f520db Bump launcher version
2.0.78 -> 2.0.79
2022-09-24 22:49:10 +01:00
Mackenzie
c92e32c0e1 Remove mod wipe warn and delay 2022-09-24 22:48:17 +01:00
Mackenzie
cb872f8a41 Update BeamNG Version 2022-09-24 22:46:38 +01:00
Anonymous275
0aae245054 release 2.0.78 2022-09-24 00:37:29 +03:00
Anonymous275
480a7d038f follow redirects if present 2022-09-24 00:37:04 +03:00
Anonymous275
f62f44d4c0 version 2.0.77 2022-09-20 22:27:26 +03:00
Anonymous275
e316b89fb1 bump 0.26 2022-09-20 22:26:51 +03:00
Anonymous275
3b2dbcac1b release 2.0.76 2022-09-05 03:23:15 +03:00
Anonymous275
d881c9faf6 release 2.0.75 2022-09-05 02:59:05 +03:00
Anonymous275
11d9375f36 fix invalid Key being used 2022-09-05 02:58:22 +03:00
Simon Abed El Sater
4207d7adcf Merge pull request #44 from snepsnepsnep/master
remove key if auth is unsuccessful
2022-09-05 02:40:50 +03:00
snepsnepsnep
832b1d66a0 remove key if auth is unsuccessful 2022-09-04 20:24:27 +02:00
Anonymous275
cd829f9f22 Update Startup.cpp
version 2.0.74
2022-08-12 12:14:31 +03:00
Anonymous275
f7c70eb6df Merge pull request #36 from Mack29446/master
Change serverlist request URL and Protocol
2022-08-12 12:11:22 +03:00
Mackenzie
01960f6470 Change serverlist request URL and Protocol 2022-08-11 20:27:26 +01:00
Anonymous275
f6065a1c00 Merge pull request #21 from Mack29446/master
Update version
2022-06-24 19:46:26 +03:00
Mackenzie
ba3b7f0ed0 Update version 2022-06-23 16:53:06 +01:00
Anonymous275
056eadbef2 Merge pull request #20 from Mack29446/master
Update version
2022-06-21 18:12:49 +03:00
Mackenzie
2bb2dc9040 Update version 2022-06-21 14:28:10 +01:00
Anonymous275
17553fd412 bump launcher version 2022-06-19 02:28:26 +03:00
Anonymous275
08c1c0f682 Merge pull request #19 from Mack29446/master
Update Version Number
2022-06-18 02:02:36 +03:00
Mackenzie
84959ae9c9 Update Version Number 2022-06-17 21:35:00 +01:00
Anonymous275
c90c102097 trying to fix github actions 2022-06-15 21:38:45 +03:00
Anonymous275
5b004426ce vcpkgGitCommitId update 2022-06-15 21:35:12 +03:00
Anonymous275
69c9060dd2 Update release-build.yml 2022-06-15 21:13:02 +03:00
Anonymous275
49870639ff Update cmake-windows.yml 2022-06-15 21:12:41 +03:00
Anonymous275
0843862af9 update for game version 0.25 2022-06-15 21:02:43 +03:00
Anonymous275
71a9a567bc 2.0.63 2021-12-03 18:45:36 +02:00
Anonymous275
a6e3d0fad9 Update cmake-windows.yml 2021-12-03 18:28:03 +02:00
Anonymous275
411d0786a5 0.24.0.1 game version bump 2021-12-03 18:18:57 +02:00
Anonymous-275
acb6b11e24 bump minor version to 62 2021-10-01 00:17:55 +03:00
Anonymous-275
1739393a73 launcher no longer extracts zip file 2021-10-01 00:11:46 +03:00
Anonymous-275
298ef33ab7 V2.0.61 2021-07-03 17:11:56 +03:00
Anonymous-275
d2433cceca V2.0.7 2021-07-03 17:10:55 +03:00
Anonymous-275
705e0ab9c4 Updated version to 0.23.1 2021-07-03 17:09:29 +03:00
Anonymous-275
53c40a2bc3 Removed debug prints 2021-07-01 18:54:36 +03:00
Anonymous-275
96c60ef05a 0.23.0.0 support 2021-07-01 18:54:06 +03:00
Anonymous-275
61759b8531 0.23.0.0 support 2021-07-01 18:53:50 +03:00
Anonymous-275
691be8cd08 Mod validity check 2021-06-02 00:02:50 +03:00
Anonymous-275
082445c295 updated gitignore 2021-05-23 20:04:34 +03:00
Anonymous-275
f4bda81be0 Added 'N' packet flag 2021-05-23 19:22:44 +03:00
Anonymous-275
1ad8e0b8e5 Minor edit 2021-05-11 22:32:46 +03:00
Anonymous-275
6c673e78e5 Fixed invalid string position, Bumped game version 2021-05-11 22:19:42 +03:00
78 changed files with 13521 additions and 9346 deletions

5
.clang-format Normal file
View File

@@ -0,0 +1,5 @@
---
BasedOnStyle: WebKit
BreakBeforeBraces: Attach
SpaceAfterTemplateKeyword: false
...

42
.github/workflows/cmake-linux.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: CMake Linux Build
on: [push, pull_request, workflow_dispatch]
env:
BUILD_TYPE: Release
jobs:
linux-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@v7
id: runvcpkg
with:
vcpkgArguments: 'zlib nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-linux
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-linux
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake'
- name: Build
working-directory: ${{github.workspace}}/build-linux
shell: bash
run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts
uses: actions/upload-artifact@v4
with:
name: BeamMP-Launcher
path: ${{github.workspace}}/build-linux/BeamMP-Launcher

View File

@@ -1,6 +1,6 @@
name: CMake Windows Build name: CMake Windows Build
on: [push, pull_request] on: [push, pull_request, workflow_dispatch]
env: env:
BUILD_TYPE: Release BUILD_TYPE: Release
@@ -15,12 +15,12 @@ jobs:
submodules: 'true' submodules: 'true'
- name: Restore artifacts, or run vcpkg, build and cache artifacts - name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main uses: lukka/run-vcpkg@v7
id: runvcpkg id: runvcpkg
with: with:
vcpkgArguments: 'discord-rpc zlib rapidjson openssl' vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f' vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
vcpkgTriplet: 'x64-windows-static' vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment - name: Create Build Environment
@@ -37,7 +37,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: BeamMP-Launcher.exe name: BeamMP-Launcher.exe
path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe

View File

@@ -1,72 +0,0 @@
name: Release Create & Build
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
env:
BUILD_TYPE: Release
jobs:
create-release:
runs-on: ubuntu-latest
name: Create Release
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
body: |
Files included in this release:
- `BeamMP-Launcher.exe` windows build
upload-release-files-windows:
name: Upload Windows Release Files
runs-on: windows-latest
needs: create-release
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Restore artifacts, or run vcpkg, build and cache artifacts
uses: lukka/run-vcpkg@main
id: runvcpkg
with:
vcpkgArguments: 'discord-rpc zlib rapidjson openssl'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '75522bb1f2e7d863078bcd06322348f053a9e33f'
vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build-windows
- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build-windows
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE='${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake' -DVCPKG_TARGET_TRIPLET=x64-windows-static
- name: Build
working-directory: ${{github.workspace}}/build-windows
shell: bash
run: cmake --build . --config $BUILD_TYPE
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe
asset_name: BeamMP-Launcher.exe
asset_content_type: application/vnd.microsoft.portable-executable

10
.gitignore vendored
View File

@@ -2,3 +2,13 @@ cmake-build-debug
cmake-build-release cmake-build-release
/.idea/ /.idea/
*.log *.log
/*.sh
/*.obj
/*.exe
.cache/
.https_debug/
Launcher.cfg
Resources/
bin/
compile_commands.json
key

View File

@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(Launcher) project(Launcher)
if (WIN32) if (WIN32)
@@ -7,11 +8,16 @@ if (WIN32)
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(WIN32) endif(WIN32)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
file(GLOB source_files "src/*.cpp" "src/*/*.cpp" "src/*/*.hpp" "include/*.h" "include/*/*.h" "include/*/*/*.h") add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT)
file(GLOB source_files "src/*.cpp" "src/*/*.cpp" "src/*/*.hpp" "include/*.h" "include/*/*.h" "include/*/*/*.h" "include/*.hpp" "include/*/*.hpp" "include/*/*/*.hpp")
find_package(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(CURL REQUIRED)
add_executable(${PROJECT_NAME} ${source_files}) add_executable(${PROJECT_NAME} ${source_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher") set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
@@ -19,15 +25,16 @@ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
if (WIN32) if (WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static target_link_libraries(${PROJECT_NAME} PRIVATE
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json CURL::libcurl)
include_directories(${VcpkgRoot}/include) elseif (LINUX)
link_directories(${VcpkgRoot}/lib) find_package(ZLIB REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/lib/discord-rpc.lib find_package(OpenSSL REQUIRED)
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32) target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl)
else(WIN32) #MINGW else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600") add_definitions("-D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
target_link_libraries(${PROJECT_NAME} discord-rpc ssl crypto ws2_32 ssp crypt32 z) target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z CURL::libcurl)
endif(WIN32) endif(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE "include") target_include_directories(${PROJECT_NAME} PRIVATE "include")

View File

@@ -2,6 +2,23 @@
The launcher is the way we communitcate to outside the game, it does a few automated actions such as but not limited to: downloading the mod, launching the game, and create a connection to a server. The launcher is the way we communitcate to outside the game, it does a few automated actions such as but not limited to: downloading the mod, launching the game, and create a connection to a server.
**To clone this repository**: `git clone --recurse-submodules https://github.com/BeamMP/BeamMP-Launcher.git`
## How to build - Release
In the root directory of the project,
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel --config Release`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
## How to build - Debug
In the root directory of the project,
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static`
2. `cmake --build bin --parallel`
Remember to change `C:/vcpkg` to wherever you have vcpkg installed.
Copyright (c) 2019-present Anonymous275. Copyright (c) 2019-present Anonymous275.
BeamMP Launcher code is not in the public domain and is not free software. BeamMP Launcher code is not in the public domain and is not free software.

View File

@@ -1,15 +0,0 @@
// Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
///
/// Created by Anonymous275 on 7/16/2020
///
#pragma once
#include <string>
void Discord_Main();
std::string GetDName();
std::string GetDTag();
std::string GetDID();
void DAboard();

View File

@@ -1,26 +0,0 @@
#pragma once
#if defined(DISCORD_DYNAMIC_LIB)
#if defined(_WIN32)
#if defined(DISCORD_BUILDING_SDK)
#define DISCORD_EXPORT __declspec(dllexport)
#else
#define DISCORD_EXPORT __declspec(dllimport)
#endif
#else
#define DISCORD_EXPORT __attribute__((visibility("default")))
#endif
#else
#define DISCORD_EXPORT
#endif
#ifdef __cplusplus
extern "C" {
#endif
DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command);
DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
#ifdef __cplusplus
}
#endif

View File

@@ -1,87 +0,0 @@
#pragma once
#include <cstdint>
// clang-format off
#if defined(DISCORD_DYNAMIC_LIB)
# if defined(_WIN32)
# if defined(DISCORD_BUILDING_SDK)
# define DISCORD_EXPORT __declspec(dllexport)
# else
# define DISCORD_EXPORT __declspec(dllimport)
# endif
# else
# define DISCORD_EXPORT __attribute__((visibility("default")))
# endif
#else
# define DISCORD_EXPORT
#endif
// clang-format on
#ifdef __cplusplus
extern "C" {
#endif
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef struct DiscordEventHandlers {
void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */
DISCORD_EXPORT void Discord_RunCallbacks(void);
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
#ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
DISCORD_EXPORT void Discord_ClearPresence(void);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus
} /* extern "C" */
#endif

11
include/http.h → include/Http.h Executable file → Normal file
View File

@@ -6,15 +6,16 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include <string>
#include "Logger.h" #include "Logger.h"
class HTTP{ #include <string>
class HTTP {
public: public:
static bool Download(const std::string &IP, const std::string &Path); static bool Download(const std::string& IP, const std::string& Path);
static std::string Post(const std::string& IP, const std::string& Fields); static std::string Post(const std::string& IP, const std::string& Fields);
static std::string Get(const std::string &IP); static std::string Get(const std::string& IP);
static bool ProgressBar(size_t c, size_t t); static bool ProgressBar(size_t c, size_t t);
static void StartProxy();
public: public:
static bool isDownload; static bool isDownload;
static std::string Codes_[]; static inline bool SkipSslVerify = false;
}; };

View File

@@ -1,12 +0,0 @@
// Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
///
/// Created by Anonymous275 on 11/27/2020
///
#pragma once
#include "rapidjson/stringbuffer.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
namespace json = rapidjson;

3
include/Logger.h Executable file → Normal file
View File

@@ -6,8 +6,8 @@
/// Created by Anonymous275 on 4/2/2020. /// Created by Anonymous275 on 4/2/2020.
/// ///
#pragma once #pragma once
#include <string>
#include <iostream> #include <iostream>
#include <string>
void InitLog(); void InitLog();
void except(const std::string& toPrint); void except(const std::string& toPrint);
void fatal(const std::string& toPrint); void fatal(const std::string& toPrint);
@@ -15,3 +15,4 @@ void debug(const std::string& toPrint);
void error(const std::string& toPrint); void error(const std::string& toPrint);
void info(const std::string& toPrint); void info(const std::string& toPrint);
void warn(const std::string& toPrint); void warn(const std::string& toPrint);
std::string getDate();

View File

@@ -6,13 +6,22 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include <string> #include <string>
#ifdef __linux__
#include "linuxfixes.h"
#include <bits/types/siginfo_t.h>
#include <cstdint>
#include <sys/ucontext.h>
#endif
void NetReset(); void NetReset();
extern bool Dev; extern bool Dev;
extern int ping; extern int ping;
void CoreNetwork();
[[noreturn]] void CoreNetwork();
extern int ProxyPort;
extern int ClientID; extern int ClientID;
extern int LastPort; extern int LastPort;
extern bool ModLoaded; extern bool ModLoaded;
@@ -21,25 +30,27 @@ extern int DEFAULT_PORT;
extern uint64_t UDPSock; extern uint64_t UDPSock;
extern uint64_t TCPSock; extern uint64_t TCPSock;
extern std::string Branch; extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate; extern bool TCPTerminate;
extern std::string LastIP; extern std::string LastIP;
extern std::string MStatus; extern std::string MStatus;
extern std::string UlStatus; extern std::string UlStatus;
extern std::string PublicKey; extern std::string PublicKey;
extern std::string ListOfMods; extern std::string PrivateKey;
int KillSocket(uint64_t Dead); int KillSocket(uint64_t Dead);
void UUl(const std::string& R); void UUl(const std::string& R);
void UDPSend(std::string Data); void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes); bool CheckBytes(int32_t Bytes);
void GameSend(std::string Data); void GameSend(std::string_view Data);
void SendLarge(std::string Data); void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock); std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock); void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string&IP); std::string GetAddr(const std::string& IP);
void ServerParser(const std::string& Data); void ServerParser(std::string_view Data);
std::string Login(const std::string& fields); std::string Login(const std::string& fields);
void TCPSend(const std::string&Data,uint64_t Sock); void TCPSend(const std::string& Data, uint64_t Sock);
void TCPClientMain(const std::string& IP,int Port); void TCPClientMain(const std::string& IP, int Port);
void UDPClientMain(const std::string& IP,int Port); void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);

0
include/Security/Game.h Executable file → Normal file
View File

2
include/Security/Init.h Executable file → Normal file
View File

@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <string> #include <string>
void PreGame(const std::string& GamePath); void PreGame(const std::string& GamePath);
std::string CheckVer(const std::string &path); std::string CheckVer(const std::string& path);
void InitGame(const std::string& Dir); void InitGame(const std::string& Dir);
std::string GetGameDir(); std::string GetGameDir();
void LegitimacyCheck(); void LegitimacyCheck();

7
include/Startup.h Executable file → Normal file
View File

@@ -6,11 +6,16 @@
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#pragma once #pragma once
#include <compare>
#include <string> #include <string>
#include <vector>
void InitLauncher(int argc, char* argv[]); void InitLauncher(int argc, char* argv[]);
std::string GetEP(char*P = nullptr); std::string GetEP(char* P = nullptr);
std::string GetGamePath(); std::string GetGamePath();
std::string GetVer(); std::string GetVer();
std::string GetPatch();
std::string GetEN(); std::string GetEN();
void ConfigInit(); void ConfigInit();
extern bool Dev; extern bool Dev;

20
include/Utils.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <vector>
namespace Utils {
inline std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
std::vector<std::string> Val;
size_t pos;
std::string token, s = String;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
if (!token.empty())
Val.push_back(token);
s.erase(0, pos + delimiter.length());
}
if (!s.empty())
Val.push_back(s);
return Val;
};
};

8
include/Zlib/Compressor.h Executable file → Normal file
View File

@@ -6,6 +6,8 @@
/// Created by Anonymous275 on 7/24/2020 /// Created by Anonymous275 on 7/24/2020
/// ///
#pragma once #pragma once
#include <string> #include <span>
std::string Comp(std::string Data); #include <vector>
std::string DeComp(std::string Compressed);
std::vector<char> Comp(std::span<const char> input);
std::vector<char> DeComp(std::span<const char> input);

4642
include/hashpp.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

19
include/linuxfixes.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef _LINUXFIXES_H
#define _LINUXFIXES_H
#include <stdint.h>
// Translate windows sockets stuff to linux sockets
#define SOCKET uint64_t
#define SOCKADDR sockaddr
#define SOCKADDR_IN sockaddr_in
#define WSAGetLastError() errno
#define closesocket close
#define SD_BOTH SHUT_RDWR
// We dont need wsacleanup
#define WSACleanup()
#define SOCKET_ERROR -1
#define ZeroMemory(mem, len) memset(mem, 0, len)
#endif

0
include/rapidjson/allocators.h Executable file → Normal file
View File

0
include/rapidjson/cursorstreamwrapper.h Executable file → Normal file
View File

0
include/rapidjson/document.h Executable file → Normal file
View File

0
include/rapidjson/encodedstream.h Executable file → Normal file
View File

0
include/rapidjson/encodings.h Executable file → Normal file
View File

0
include/rapidjson/error/en.h Executable file → Normal file
View File

0
include/rapidjson/error/error.h Executable file → Normal file
View File

0
include/rapidjson/filereadstream.h Executable file → Normal file
View File

0
include/rapidjson/filewritestream.h Executable file → Normal file
View File

0
include/rapidjson/fwd.h Executable file → Normal file
View File

0
include/rapidjson/internal/biginteger.h Executable file → Normal file
View File

0
include/rapidjson/internal/clzll.h Executable file → Normal file
View File

0
include/rapidjson/internal/diyfp.h Executable file → Normal file
View File

0
include/rapidjson/internal/dtoa.h Executable file → Normal file
View File

0
include/rapidjson/internal/ieee754.h Executable file → Normal file
View File

0
include/rapidjson/internal/itoa.h Executable file → Normal file
View File

0
include/rapidjson/internal/meta.h Executable file → Normal file
View File

0
include/rapidjson/internal/pow10.h Executable file → Normal file
View File

0
include/rapidjson/internal/regex.h Executable file → Normal file
View File

0
include/rapidjson/internal/stack.h Executable file → Normal file
View File

0
include/rapidjson/internal/strfunc.h Executable file → Normal file
View File

0
include/rapidjson/internal/strtod.h Executable file → Normal file
View File

0
include/rapidjson/internal/swap.h Executable file → Normal file
View File

0
include/rapidjson/istreamwrapper.h Executable file → Normal file
View File

0
include/rapidjson/memorybuffer.h Executable file → Normal file
View File

0
include/rapidjson/memorystream.h Executable file → Normal file
View File

0
include/rapidjson/msinttypes/inttypes.h Executable file → Normal file
View File

0
include/rapidjson/msinttypes/stdint.h Executable file → Normal file
View File

0
include/rapidjson/ostreamwrapper.h Executable file → Normal file
View File

0
include/rapidjson/pointer.h Executable file → Normal file
View File

0
include/rapidjson/prettywriter.h Executable file → Normal file
View File

0
include/rapidjson/rapidjson.h Executable file → Normal file
View File

0
include/rapidjson/reader.h Executable file → Normal file
View File

0
include/rapidjson/schema.h Executable file → Normal file
View File

0
include/rapidjson/stream.h Executable file → Normal file
View File

0
include/rapidjson/stringbuffer.h Executable file → Normal file
View File

0
include/rapidjson/writer.h Executable file → Normal file
View File

730
include/vdf_parser.hpp Normal file
View File

@@ -0,0 +1,730 @@
//MIT License
//
//Copyright(c) 2016 Matthias Moeller
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files(the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions :
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef __TYTI_STEAM_VDF_PARSER_H__
#define __TYTI_STEAM_VDF_PARSER_H__
#include <map>
#include <vector>
#include <unordered_map>
#include <utility>
#include <fstream>
#include <memory>
#include <unordered_set>
#include <algorithm>
#include <iterator>
#include <functional>
#include <system_error>
#include <exception>
//for wstring support
#include <locale>
#include <string>
// internal
#include <stack>
//VS < 2015 has only partial C++11 support
#if defined(_MSC_VER) && _MSC_VER < 1900
#ifndef CONSTEXPR
#define CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT
#endif
#else
#ifndef CONSTEXPR
#define CONSTEXPR constexpr
#define TYTI_UNDEF_CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT noexcept
#define TYTI_UNDEF_NOEXCEPT
#endif
#endif
namespace tyti
{
namespace vdf
{
namespace detail
{
///////////////////////////////////////////////////////////////////////////
// Helper functions selecting the right encoding (char/wchar_T)
///////////////////////////////////////////////////////////////////////////
template <typename T>
struct literal_macro_help
{
static CONSTEXPR const char* result(const char* c, const wchar_t*) NOEXCEPT
{
return c;
}
static CONSTEXPR const char result(const char c, const wchar_t) NOEXCEPT
{
return c;
}
};
template <>
struct literal_macro_help<wchar_t>
{
static CONSTEXPR const wchar_t* result(const char*, const wchar_t* wc) NOEXCEPT
{
return wc;
}
static CONSTEXPR const wchar_t result(const char, const wchar_t wc) NOEXCEPT
{
return wc;
}
};
#define TYTI_L(type, text) vdf::detail::literal_macro_help<type>::result(text, L##text)
inline std::string string_converter(const std::string& w) NOEXCEPT
{
return w;
}
// utility wrapper to adapt locale-bound facets for wstring/wbuffer convert
// from cppreference
template <class Facet>
struct deletable_facet : Facet
{
template <class... Args>
deletable_facet(Args &&... args) : Facet(std::forward<Args>(args)...) {}
~deletable_facet() {}
};
inline std::string string_converter(const std::wstring& w) //todo: use us-locale
{
std::wstring_convert<deletable_facet<std::codecvt<wchar_t, char, std::mbstate_t>>> conv1;
return conv1.to_bytes(w);
}
///////////////////////////////////////////////////////////////////////////
// Writer helper functions
///////////////////////////////////////////////////////////////////////////
template <typename charT>
class tabs
{
const size_t t;
public:
explicit CONSTEXPR tabs(size_t i) NOEXCEPT : t(i) {}
std::basic_string<charT> print() const { return std::basic_string<charT>(t, TYTI_L(charT, '\t')); }
inline CONSTEXPR tabs operator+(size_t i) const NOEXCEPT
{
return tabs(t + i);
}
};
template <typename oStreamT>
oStreamT& operator<<(oStreamT& s, const tabs<typename oStreamT::char_type> t)
{
s << t.print();
return s;
}
} // end namespace detail
///////////////////////////////////////////////////////////////////////////
// Interface
///////////////////////////////////////////////////////////////////////////
/// custom objects and their corresponding write functions
/// basic object node. Every object has a name and can contains attributes saved as key_value pairs or childrens
template <typename CharT>
struct basic_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_map<std::basic_string<char_type>, std::basic_string<char_type>> attribs;
std::unordered_map<std::basic_string<char_type>, std::shared_ptr<basic_object<char_type>>> childs;
void add_attribute(std::basic_string<char_type> key, std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_object<char_type>> child)
{
std::shared_ptr<basic_object<char_type>> obj{ child.release() };
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n)
{
name = std::move(n);
}
};
template <typename CharT>
struct basic_multikey_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_multimap<std::basic_string<char_type>, std::basic_string<char_type>> attribs;
std::unordered_multimap<std::basic_string<char_type>, std::shared_ptr<basic_multikey_object<char_type>>> childs;
void add_attribute(std::basic_string<char_type> key, std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_multikey_object<char_type>> child)
{
std::shared_ptr<basic_multikey_object<char_type>> obj{ child.release() };
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n)
{
name = std::move(n);
}
};
typedef basic_object<char> object;
typedef basic_object<wchar_t> wobject;
typedef basic_multikey_object<char> multikey_object;
typedef basic_multikey_object<wchar_t> wmultikey_object;
struct Options
{
bool strip_escape_symbols;
bool ignore_all_platform_conditionals;
bool ignore_includes;
Options() : strip_escape_symbols(true), ignore_all_platform_conditionals(false), ignore_includes(false) {}
};
//forward decls
//forward decl
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, const Options& opt = Options{});
/** \brief writes given object tree in vdf format to given stream.
Output is prettyfied, using tabs
*/
template <typename oStreamT, typename T>
void write(oStreamT& s, const T& r,
const detail::tabs<typename oStreamT::char_type> tab = detail::tabs<typename oStreamT::char_type>(0))
{
typedef typename oStreamT::char_type charT;
using namespace detail;
s << tab << TYTI_L(charT, '"') << r.name << TYTI_L(charT, "\"\n") << tab << TYTI_L(charT, "{\n");
for (const auto& i : r.attribs)
s << tab + 1 << TYTI_L(charT, '"') << i.first << TYTI_L(charT, "\"\t\t\"") << i.second << TYTI_L(charT, "\"\n");
for (const auto& i : r.childs)
if (i.second)
write(s, *i.second, tab + 1);
s << tab << TYTI_L(charT, "}\n");
}
namespace detail
{
template <typename iStreamT>
std::basic_string<typename iStreamT::char_type> read_file(iStreamT& inStream)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str;
inStream.seekg(0, std::ios::end);
str.resize(static_cast<size_t>(inStream.tellg()));
if (str.empty())
return str;
inStream.seekg(0, std::ios::beg);
inStream.read(&str[0], str.size());
return str;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param exclude_files list of files which cant be included anymore.
prevents circular includes
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
std::vector<std::unique_ptr<OutputT>> read_internal(IterT first, const IterT last,
std::unordered_set<std::basic_string<typename std::iterator_traits<IterT>::value_type>>& exclude_files,
const Options& opt)
{
static_assert(std::is_default_constructible<OutputT>::value,
"Output Type must be default constructible (provide constructor without arguments)");
static_assert(std::is_move_constructible<OutputT>::value,
"Output Type must be move constructible");
typedef typename std::iterator_traits<IterT>::value_type charT;
const std::basic_string<charT> comment_end_str = TYTI_L(charT, "*/");
const std::basic_string<charT> whitespaces = TYTI_L(charT, " \n\v\f\r\t");
#ifdef WIN32
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$WINDOWS");
};
#elif __APPLE__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || in == TYTI_L(charT, "$OSX");
};
#elif __linux__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") || in == TYTI_L(charT, "$LINUX");
};
#else
std::function<bool(const std::basic_string<charT>&)> is_platform_str = [](const std::basic_string<charT>& in) {
return false;
};
#endif
if (opt.ignore_all_platform_conditionals)
is_platform_str = [](const std::basic_string<charT>&) {
return false;
};
// function for skipping a comment block
// iter: iterator poition to the position after a '/'
auto skip_comments = [&comment_end_str](IterT iter, const IterT& last) -> IterT {
++iter;
if (iter != last)
{
if (*iter == TYTI_L(charT, '/'))
{
// line comment, skip whole line
iter = std::find(iter + 1, last, TYTI_L(charT, '\n'));
}
if (*iter == '*')
{
// block comment, skip until next occurance of "*\"
iter = std::search(iter + 1, last, std::begin(comment_end_str), std::end(comment_end_str));
iter += 2;
}
}
return iter;
};
auto end_quote = [](IterT iter, const IterT& last) -> IterT {
const auto begin = iter;
auto last_esc = iter;
do
{
++iter;
iter = std::find(iter, last, TYTI_L(charT, '\"'));
if (iter == last)
break;
last_esc = std::prev(iter);
while (last_esc != begin && *last_esc == '\\')
--last_esc;
} while (!(std::distance(last_esc, iter) % 2));
if (iter == last)
throw std::runtime_error{ "quote was opened but not closed." };
return iter;
};
auto end_word = [&whitespaces](IterT iter, const IterT& last) -> IterT {
const auto begin = iter;
auto last_esc = iter;
do
{
++iter;
iter = std::find_first_of(iter, last, std::begin(whitespaces), std::end(whitespaces));
if (iter == last)
break;
last_esc = std::prev(iter);
while (last_esc != begin && *last_esc == '\\')
--last_esc;
} while (!(std::distance(last_esc, iter) % 2));
//if (iter == last)
// throw std::runtime_error{ "word wasnt properly ended" };
return iter;
};
auto skip_whitespaces = [&whitespaces](IterT iter, const IterT& last) -> IterT {
iter = std::find_if_not(iter, last, [&whitespaces](charT c) {
// return true if whitespace
return std::any_of(std::begin(whitespaces), std::end(whitespaces), [c](charT pc) { return pc == c; });
});
return iter;
};
std::function<void(std::basic_string<charT>&)> strip_escape_symbols = [](std::basic_string<charT>& s) {
auto quote_searcher = [&s](size_t pos) { return s.find(TYTI_L(charT, "\\\""), pos); };
auto p = quote_searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\""));
p = quote_searcher(p);
}
auto searcher = [&s](size_t pos) { return s.find(TYTI_L(charT, "\\\\"), pos); };
p = searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\\"));
p = searcher(p);
}
};
if (!opt.strip_escape_symbols)
strip_escape_symbols = [](std::basic_string<charT>&) {};
auto conditional_fullfilled = [&skip_whitespaces, &is_platform_str](IterT& iter, const IterT& last) {
iter = skip_whitespaces(iter, last);
if (*iter == '[')
{
++iter;
const auto end = std::find(iter, last, ']');
const bool negate = *iter == '!';
if (negate)
++iter;
auto conditional = std::basic_string<charT>(iter, end);
const bool is_platform = is_platform_str(conditional);
iter = end + 1;
return static_cast<bool>(is_platform ^ negate);
}
return true;
};
//read header
// first, quoted name
std::unique_ptr<OutputT> curObj = nullptr;
std::vector<std::unique_ptr<OutputT>> roots;
std::stack<std::unique_ptr<OutputT>> lvls;
auto curIter = first;
while (curIter != last && *curIter != '\0')
{
//find first starting attrib/child, or ending
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '\0')
break;
if (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
}
else if (*curIter != TYTI_L(charT, '}'))
{
// get key
const auto keyEnd = (*curIter == TYTI_L(charT, '\"')) ? end_quote(curIter, last) : end_word(curIter, last);
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
std::basic_string<charT> key(curIter, keyEnd);
strip_escape_symbols(key);
curIter = keyEnd + ((*keyEnd == TYTI_L(charT, '\"')) ? 1 : 0);
curIter = skip_whitespaces(curIter, last);
auto conditional = conditional_fullfilled(curIter, last);
if (!conditional)
continue;
while (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{ "key declared, but no value" };
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{ "key declared, but no value" };
}
// get value
if (*curIter != '{')
{
const auto valueEnd = (*curIter == TYTI_L(charT, '\"')) ? end_quote(curIter, last) : end_word(curIter, last);
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
auto value = std::basic_string<charT>(curIter, valueEnd);
strip_escape_symbols(value);
curIter = valueEnd + ((*valueEnd == TYTI_L(charT, '\"')) ? 1 : 0);
auto conditional = conditional_fullfilled(curIter, last);
if (!conditional)
continue;
// process value
if (key != TYTI_L(charT, "#include") && key != TYTI_L(charT, "#base"))
{
if (curObj)
{
curObj->add_attribute(std::move(key), std::move(value));
}
else
{
throw std::runtime_error{ "unexpected key without object" };
}
}
else
{
if (!opt.ignore_includes && exclude_files.find(value) == exclude_files.end())
{
exclude_files.insert(value);
std::basic_ifstream<charT> i(detail::string_converter(value));
auto str = read_file(i);
auto file_objs = read_internal<OutputT>(str.begin(), str.end(), exclude_files, opt);
for (auto& n : file_objs)
{
if (curObj)
curObj->add_child(std::move(n));
else
roots.push_back(std::move(n));
}
exclude_files.erase(value);
}
}
}
else if (*curIter == '{')
{
if (curObj)
lvls.push(std::move(curObj));
curObj = std::make_unique<OutputT>();
curObj->set_name(std::move(key));
++curIter;
}
}
//end of new object
else if (curObj && *curIter == TYTI_L(charT, '}'))
{
if (!lvls.empty())
{
//get object before
std::unique_ptr<OutputT> prev{ std::move(lvls.top()) };
lvls.pop();
// add finished obj to obj before and release it from processing
prev->add_child(std::move(curObj));
curObj = std::move(prev);
}
else
{
roots.push_back(std::move(curObj));
curObj.reset();
}
++curIter;
}
else
{
throw std::runtime_error{ "unexpected '}'" };
}
}
if (curObj != nullptr || !lvls.empty())
{
throw std::runtime_error{ "object is not closed with '}'" };
}
return roots;
}
} // namespace detail
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, const Options& opt = Options{})
{
auto exclude_files = std::unordered_set<std::basic_string<typename std::iterator_traits<IterT>::value_type>>{};
auto roots = detail::read_internal<OutputT>(first, last, exclude_files, opt);
OutputT result;
if (roots.size() > 1)
{
for (auto& i : roots)
result.add_child(std::move(i));
}
else if (roots.size() == 1)
result = std::move(*roots[0]);
return result;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ec output bool. 0 if ok, otherwise, holds an system error code
Possible error codes:
std::errc::protocol_error: file is mailformatted
std::errc::not_enough_memory: not enough space
std::errc::invalid_argument: iterators throws e.g. out of range
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, IterT last, std::error_code& ec, const Options& opt = Options{}) NOEXCEPT
{
ec.clear();
OutputT r{};
try
{
r = read<OutputT>(first, last, opt);
}
catch (std::runtime_error&)
{
ec = std::make_error_code(std::errc::protocol_error);
}
catch (std::bad_alloc&)
{
ec = std::make_error_code(std::errc::not_enough_memory);
}
catch (...)
{
ec = std::make_error_code(std::errc::invalid_argument);
}
return r;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ok output bool. true, if parser successed, false, if parser failed
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, bool* ok, const Options& opt = Options{}) NOEXCEPT
{
std::error_code ec;
auto r = read<OutputT>(first, last, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename IterT>
inline auto read(IterT first, const IterT last, bool* ok, const Options& opt = Options{}) NOEXCEPT -> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, ok, opt);
}
template <typename IterT>
inline auto read(IterT first, IterT last, std::error_code& ec, const Options& opt = Options{}) NOEXCEPT
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, ec, opt);
}
template <typename IterT>
inline auto read(IterT first, const IterT last, const Options& opt = Options{})
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(first, last, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{})
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), ec, opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, std::error_code& ec, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ec, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
ok == false, if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, bool* ok, const Options& opt = Options{})
{
std::error_code ec;
const auto r = read<OutputT>(inStream, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, bool* ok, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ok, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf formatted data.
throws "std::bad_alloc" if file buffer could not be allocated
throws "std::runtime_error" if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT& inStream, const Options& opt)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type> read(iStreamT& inStream, const Options& opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, opt);
}
} // namespace vdf
} // namespace tyti
#ifndef TYTI_NO_L_UNDEF
#undef TYTI_L
#endif
#ifdef TYTI_UNDEF_CONSTEXPR
#undef CONSTEXPR
#undef TYTI_NO_L_UNDEF
#endif
#ifdef TYTI_UNDEF_NOTHROW
#undef NOTHROW
#undef TYTI_UNDEF_NOTHROW
#endif
#endif //__TYTI_STEAM_VDF_PARSER_H__

5710
include/zip_file.h Normal file

File diff suppressed because it is too large Load Diff

92
src/Compressor.cpp Executable file → Normal file
View File

@@ -6,49 +6,57 @@
/// Created by Anonymous275 on 7/15/2020 /// Created by Anonymous275 on 7/15/2020
/// ///
#include <iostream> #include "Logger.h"
#include <span>
#include <vector>
#include <zconf.h>
#include <zlib.h> #include <zlib.h>
#ifdef __linux__
#include <cstring>
#endif
#define Biggest 30000 std::vector<char> Comp(std::span<const char> input) {
std::string Comp(std::string Data){ auto max_size = compressBound(input.size());
char*C = new char[Biggest]; std::vector<char> output(max_size);
memset(C, 0, Biggest); uLongf output_size = output.size();
z_stream defstream; int res = compress(
defstream.zalloc = Z_NULL; reinterpret_cast<Bytef*>(output.data()),
defstream.zfree = Z_NULL; &output_size,
defstream.opaque = Z_NULL; reinterpret_cast<const Bytef*>(input.data()),
defstream.avail_in = (uInt)Data.length(); static_cast<uLongf>(input.size()));
defstream.next_in = (Bytef *)&Data[0]; if (res != Z_OK) {
defstream.avail_out = Biggest; error("zlib compress() failed: " + std::to_string(res));
defstream.next_out = reinterpret_cast<Bytef *>(C); throw std::runtime_error("zlib compress() failed");
deflateInit(&defstream, Z_BEST_COMPRESSION); }
deflate(&defstream, Z_SYNC_FLUSH); debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B");
deflate(&defstream, Z_FINISH); output.resize(output_size);
deflateEnd(&defstream); return output;
int TO = defstream.total_out;
std::string Ret(TO,0);
memcpy_s(&Ret[0],TO,C,TO);
delete [] C;
return Ret;
} }
std::string DeComp(std::string Compressed){
char*C = new char[Biggest]; std::vector<char> DeComp(std::span<const char> input) {
memset(C, 0, Biggest); std::vector<char> output_buffer(std::min<size_t>(input.size() * 5, 15 * 1024 * 1024));
z_stream infstream;
infstream.zalloc = Z_NULL; uLongf output_size = output_buffer.size();
infstream.zfree = Z_NULL;
infstream.opaque = Z_NULL; while (true) {
infstream.avail_in = Biggest; int res = uncompress(
infstream.next_in = (Bytef *)(&Compressed[0]); reinterpret_cast<Bytef*>(output_buffer.data()),
infstream.avail_out = Biggest; &output_size,
infstream.next_out = (Bytef *)(C); reinterpret_cast<const Bytef*>(input.data()),
inflateInit(&infstream); static_cast<uLongf>(input.size()));
inflate(&infstream, Z_SYNC_FLUSH); if (res == Z_BUF_ERROR) {
inflate(&infstream, Z_FINISH); if (output_buffer.size() > 30 * 1024 * 1024) {
inflateEnd(&infstream); throw std::runtime_error("decompressed packet size of 30 MB exceeded");
int TO = infstream.total_out; }
std::string Ret(TO,0); debug("zlib uncompress() failed, trying with 2x buffer size of " + std::to_string(output_buffer.size() * 2));
memcpy_s(&Ret[0],TO,C,TO); output_buffer.resize(output_buffer.size() * 2);
delete [] C; output_size = output_buffer.size();
return Ret; } else if (res != Z_OK) {
error("zlib uncompress() failed: " + std::to_string(res));
throw std::runtime_error("zlib uncompress() failed");
} else if (res == Z_OK) {
break;
}
} output_buffer.resize(output_size);
return output_buffer;
} }

69
src/Config.cpp Executable file → Normal file
View File

@@ -2,59 +2,64 @@
/// Created by Anonymous275 on 2/23/2021 /// Created by Anonymous275 on 2/23/2021
/// ///
#include "Network/network.h"
#include <filesystem>
#include "Logger.h" #include "Logger.h"
#include <fstream> #include "Network/network.hpp"
#include "Json.h"
#include <cstdint> #include <cstdint>
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string Branch; std::string Branch;
void ParseConfig(const json::Document& d){ std::string CachingDirectory = "./Resources";
if(d["Port"].IsInt()){
DEFAULT_PORT = d["Port"].GetInt(); void ParseConfig(const nlohmann::json& d) {
if (d["Port"].is_number()) {
DEFAULT_PORT = d["Port"].get<int>();
} }
// Default -1
//Default -1 // Release 1
//Release 1 // EA 2
//EA 2 // Dev 3
//Dev 3 // Custom 3
//Custom 3 if (d["Build"].is_string()) {
Branch = d["Build"].get<std::string>();
if(d["Build"].IsString()){ for (char& c : Branch)
Branch = d["Build"].GetString(); c = char(tolower(c));
for(char& c : Branch)c = char(tolower(c)); }
if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) {
CachingDirectory = d["CachingDirectory"].get<std::string>();
info("Mod caching directory: " + CachingDirectory);
} }
} }
void ConfigInit(){ void ConfigInit() {
if(fs::exists("Launcher.cfg")){ if (fs::exists("Launcher.cfg")) {
std::ifstream cfg("Launcher.cfg"); std::ifstream cfg("Launcher.cfg");
if(cfg.is_open()){ if (cfg.is_open()) {
auto Size = fs::file_size("Launcher.cfg"); auto Size = fs::file_size("Launcher.cfg");
std::string Buffer(Size, 0); std::string Buffer(Size, 0);
cfg.read(&Buffer[0], Size); cfg.read(&Buffer[0], Size);
cfg.close(); cfg.close();
json::Document d; nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
d.Parse(Buffer.c_str()); if (d.is_discarded()) {
if(d.HasParseError()){ fatal("Config failed to parse make sure it's valid JSON!");
fatal("Config failed to parse make sure it's valid JSON! Code : " + std::to_string(d.GetParseError()));
} }
ParseConfig(d); ParseConfig(d);
}else fatal("Failed to open Launcher.cfg!"); } else
}else{ fatal("Failed to open Launcher.cfg!");
} else {
std::ofstream cfg("Launcher.cfg"); std::ofstream cfg("Launcher.cfg");
if(cfg.is_open()){ if (cfg.is_open()) {
cfg << cfg <<
R"({ R"({
"Port": 4444, "Port": 4444,
"Build": "Default" "Build": "Default",
"CachingDirectory": "./Resources"
})"; })";
cfg.close(); cfg.close();
}else{ } else {
fatal("Failed to write config on disk!"); fatal("Failed to write config on disk!");
} }
} }
} }

View File

@@ -1,111 +0,0 @@
// Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries.
// Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
///
/// Created by Anonymous275 on 7/16/2020
///
#include "Discord/discord_rpc.h"
#include "Logger.h"
#include <cstring>
#include <thread>
#include <ctime>
struct DInfo{
std::string Name;
std::string Tag;
std::string DID;
};
DInfo* DiscordInfo = nullptr;
int64_t StartTime;
void updateDiscordPresence(){
//if (SendPresence) {
//char buffer[256];
DiscordRichPresence discordPresence;
memset(&discordPresence, 0, sizeof(discordPresence));
std::string P = "Playing with friends!"; ///to be revisited
discordPresence.state = P.c_str();
//sprintf(buffer, "Frustration level: %d", FrustrationLevel);
//discordPresence.details = buffer;
discordPresence.startTimestamp = StartTime;
//discordPresence.endTimestamp = time(0) + 5 * 60;
discordPresence.largeImageKey = "mainlogo";
//discordPresence.smallImageKey = "logo";
//discordPresence.partyId = "party1234";
//discordPresence.partySize = 1;
//discordPresence.partyMax = 6;
//discordPresence.matchSecret = "xyzzy";
//discordPresence.joinSecret = "join";
//discordPresence.spectateSecret = "look";
//discordPresence.instance = 0;
Discord_UpdatePresence(&discordPresence);
//}
//else {
// Discord_ClearPresence();
//}
}
void handleDiscordReady(const DiscordUser* User){
DiscordInfo = new DInfo{
User->username,
User->discriminator,
User->userId
};
}
void discordInit(){
DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers));
handlers.ready = handleDiscordReady;
/*handlers.disconnected = handleDiscordDisconnected;
handlers.errored = handleDiscordError;
handlers.joinGame = handleDiscordJoin;
handlers.spectateGame = handleDiscordSpectate;
handlers.joinRequest = handleDiscordJoinRequest;*/
Discord_Initialize("629743237988352010", &handlers, 1,nullptr);
}
[[noreturn]] void Loop(){
StartTime = time(nullptr);
while (true) {
updateDiscordPresence();
#ifdef DISCORD_DISABLE_IO_THREAD
Discord_UpdateConnection();
#endif
Discord_RunCallbacks();
if(DiscordInfo == nullptr){
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}else std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
void DMain(){
discordInit();
Loop();
}
std::string GetDName(){
return DiscordInfo->Name;
}
std::string GetDTag(){
return DiscordInfo->Tag;
}
std::string GetDID(){
return DiscordInfo->DID;
}
void DAboard(){
DiscordInfo = nullptr;
}
void ErrorAboard(){
error("Discord timeout! please start the discord app and try again after 30 secs");
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(6);
}
void Discord_Main(){
std::thread t1(DMain);
t1.detach();
/*info("Connecting to discord client...");
int C = 0;
while(DiscordInfo == nullptr && C < 80){
std::this_thread::sleep_for(std::chrono::milliseconds(300));
C++;
}
if(DiscordInfo == nullptr)ErrorAboard();*/
}

101
src/GameStart.cpp Executable file → Normal file
View File

@@ -6,63 +6,122 @@
/// Created by Anonymous275 on 7/19/2020 /// Created by Anonymous275 on 7/19/2020
/// ///
#include <Security/Init.h> #if defined(_WIN32)
#include <windows.h> #include <windows.h>
#include "Startup.h" #include <shlobj.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <pwd.h>
#include <spawn.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include "Logger.h" #include "Logger.h"
#include "Startup.h"
#include <Security/Init.h>
#include <filesystem>
#include <thread> #include <thread>
unsigned long GamePID = 0; unsigned long GamePID = 0;
std::string QueryKey(HKEY hKey,int ID); #if defined(_WIN32)
std::string GetGamePath(){ std::string QueryKey(HKEY hKey, int ID);
std::string GetGamePath() {
static std::string Path; static std::string Path;
if(!Path.empty())return Path; if (!Path.empty())
return Path;
HKEY hKey; HKEY hKey;
LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive"; LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive";
LONG openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey); LONG openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
if (openRes != ERROR_SUCCESS){ if (openRes != ERROR_SUCCESS) {
fatal("Please launch the game at least once!"); fatal("Please launch the game at least once!");
} }
Path = QueryKey(hKey,4); Path = QueryKey(hKey, 4);
if(Path.empty()){ if (Path.empty()) {
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)"; Path = "";
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey); char appDataPath[MAX_PATH];
if (openRes != ERROR_SUCCESS){ HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
fatal("Cannot get Local Appdata directory!"); if (SUCCEEDED(result)) {
Path = appDataPath;
} }
Path = QueryKey(hKey,5);
if (Path.empty()) {
fatal("Cannot get Local Appdata directory");
}
Path += "\\BeamNG.drive\\"; Path += "\\BeamNG.drive\\";
} }
std::string Ver = CheckVer(GetGameDir()); std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0,Ver.find('.',Ver.find('.')+1)); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "\\"; Path += Ver + "\\";
return Path; return Path;
} }
#elif defined(__linux__)
std::string GetGamePath() {
// Right now only steam is supported
struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir;
void StartGame(std::string Dir){ std::string Path = homeDir + "/.local/share/BeamNG.drive/";
std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "/";
return Path;
}
#endif
#if defined(_WIN32)
void StartGame(std::string Dir) {
BOOL bSuccess = FALSE; BOOL bSuccess = FALSE;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
STARTUPINFO si = {0}; STARTUPINFO si = { 0 };
si.cb = sizeof(si); si.cb = sizeof(si);
std::string BaseDir = Dir; //+"\\Bin64"; std::string BaseDir = Dir; //+"\\Bin64";
//Dir += R"(\Bin64\BeamNG.drive.x64.exe)"; // Dir += R"(\Bin64\BeamNG.drive.x64.exe)";
Dir += "\\BeamNG.drive.exe"; Dir += "\\BeamNG.drive.exe";
bSuccess = CreateProcessA(Dir.c_str(), nullptr, nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi); bSuccess = CreateProcessA(Dir.c_str(), nullptr, nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
if (bSuccess){ if (bSuccess) {
info("Game Launched!"); info("Game Launched!");
GamePID = pi.dwProcessId; GamePID = pi.dwProcessId;
WaitForSingleObject(pi.hProcess, INFINITE); WaitForSingleObject(pi.hProcess, INFINITE);
error("Game Closed! launcher closing soon"); error("Game Closed! launcher closing soon");
}else{ } else {
error("Failed to Launch the game! launcher closing soon"); error("Failed to Launch the game! launcher closing soon");
} }
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2); exit(2);
} }
void InitGame(const std::string& Dir){ #elif defined(__linux__)
if(!Dev){ void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
char* argv[] = { filename.data(), NULL };
pid_t pid;
posix_spawn_file_actions_t spawn_actions;
posix_spawn_file_actions_init(&spawn_actions);
posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO);
posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO);
int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, argv, environ);
if (result != 0) {
error("Failed to Launch the game! launcher closing soon");
return;
} else {
waitpid(pid, &status, 0);
error("Game Closed! launcher closing soon");
}
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2);
}
#endif
void InitGame(const std::string& Dir) {
if (!Dev) {
std::thread Game(StartGame, Dir); std::thread Game(StartGame, Dir);
Game.detach(); Game.detach();
} }

50
src/Logger.cpp Executable file → Normal file
View File

@@ -6,24 +6,16 @@
/// Created by Anonymous275 on 7/17/2020 /// Created by Anonymous275 on 7/17/2020
/// ///
#include "Startup.h"
#include "Logger.h" #include "Logger.h"
#include "Startup.h"
#include <chrono>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <chrono>
#include <thread> #include <thread>
std::string getDate() { std::string getDate() {
typedef std::chrono::duration<int, std::ratio_multiply<std::chrono::hours::period, std::ratio<24>>::type> days; time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); tm local_tm = *localtime(&tt);
std::chrono::system_clock::duration tp = now.time_since_epoch();
days d = std::chrono::duration_cast<days>(tp);tp -= d;
auto h = std::chrono::duration_cast<std::chrono::hours>(tp);tp -= h;
auto m = std::chrono::duration_cast<std::chrono::minutes>(tp);tp -= m;
auto s = std::chrono::duration_cast<std::chrono::seconds>(tp);tp -= s;
time_t tt = std::chrono::system_clock::to_time_t(now);
tm local_tm{};
localtime_s(&local_tm,&tt);
std::stringstream date; std::stringstream date;
int S = local_tm.tm_sec; int S = local_tm.tm_sec;
int M = local_tm.tm_min; int M = local_tm.tm_min;
@@ -32,24 +24,25 @@ std::string getDate() {
std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M)); std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M));
std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H)); std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H));
date date
<< "[" << "["
<< local_tm.tm_mday << "/" << local_tm.tm_mday << "/"
<< local_tm.tm_mon + 1 << "/" << local_tm.tm_mon + 1 << "/"
<< local_tm.tm_year + 1900 << " " << local_tm.tm_year + 1900 << " "
<< Hour << ":" << Hour << ":"
<< Min << ":" << Min << ":"
<< Secs << Secs
<< "] "; << "] ";
return date.str(); return date.str();
} }
void InitLog(){ void InitLog() {
std::ofstream LFS; std::ofstream LFS;
LFS.open(GetEP() + "Launcher.log"); LFS.open(GetEP() + "Launcher.log");
if(!LFS.is_open()){ if (!LFS.is_open()) {
error("logger file init failed!"); error("logger file init failed!");
}else LFS.close(); } else
LFS.close();
} }
void addToLog(const std::string& Line){ void addToLog(const std::string& Line) {
std::ofstream LFS; std::ofstream LFS;
LFS.open(GetEP() + "Launcher.log", std::ios_base::app); LFS.open(GetEP() + "Launcher.log", std::ios_base::app);
LFS << Line.c_str(); LFS << Line.c_str();
@@ -61,12 +54,13 @@ void info(const std::string& toPrint) {
addToLog(Print); addToLog(Print);
} }
void debug(const std::string& toPrint) { void debug(const std::string& toPrint) {
if(!Dev)return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n"; std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
std::cout << Print; if (Dev) {
std::cout << Print;
}
addToLog(Print); addToLog(Print);
} }
void warn(const std::string& toPrint){ void warn(const std::string& toPrint) {
std::string Print = getDate() + "[WARN] " + toPrint + "\n"; std::string Print = getDate() + "[WARN] " + toPrint + "\n";
std::cout << Print; std::cout << Print;
addToLog(Print); addToLog(Print);
@@ -81,7 +75,7 @@ void fatal(const std::string& toPrint) {
std::cout << Print; std::cout << Print;
addToLog(Print); addToLog(Print);
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
_Exit(-1); std::exit(1);
} }
void except(const std::string& toPrint) { void except(const std::string& toPrint) {
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n"; std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";

386
src/Network/Core.cpp Executable file → Normal file
View File

@@ -5,17 +5,31 @@
/// ///
/// Created by Anonymous275 on 7/20/2020 /// Created by Anonymous275 on 7/20/2020
/// ///
#include "Network/network.h" #include "Http.h"
#include "Network/network.hpp"
#include "Security/Init.h" #include "Security/Init.h"
#include <cstdlib>
#include "http.h" #include <regex>
#if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include "Startup.h" #elif defined(__linux__)
#include <cstring>
#include <errno.h>
#include <netdb.h>
#include <spawn.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include "Logger.h" #include "Logger.h"
#include "Startup.h"
#include <charconv> #include <charconv>
#include <thread> #include <nlohmann/json.hpp>
#include <set> #include <set>
#include <thread>
extern int TraceBack; extern int TraceBack;
std::set<std::string>* ConfList = nullptr; std::set<std::string>* ConfList = nullptr;
@@ -23,18 +37,43 @@ bool TCPTerminate = false;
int DEFAULT_PORT = 4444; int DEFAULT_PORT = 4444;
bool Terminate = false; bool Terminate = false;
bool LoginAuth = false; bool LoginAuth = false;
std::string Username = "";
std::string UserRole = "";
int UserID = -1;
std::string UlStatus; std::string UlStatus;
std::string MStatus; std::string MStatus;
bool ModLoaded; bool ModLoaded;
int ping = -1; int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
void StartSync(const std::string &Data){ bool SecurityWarning() {
std::string IP = GetAddr(Data.substr(1,Data.find(':')-1)); confirmed = -1;
if(IP.find('.') == -1){ CoreSend("WMODS_FOUND");
if(IP == "DNS")UlStatus ="UlConnection Failed! (DNS Lookup Failed)";
else UlStatus = "UlConnection Failed! (WSA failed to start)"; while (confirmed == -1)
ListOfMods = "-"; std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (confirmed == 1)
return true;
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
return false;
}
void StartSync(const std::string& Data) {
std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
if (IP.find('.') == -1) {
if (IP == "DNS")
UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
else
UlStatus = "UlConnection Failed! (WSA failed to start)";
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
CheckLocalKey(); CheckLocalKey();
@@ -43,120 +82,193 @@ void StartSync(const std::string &Data){
Terminate = false; Terminate = false;
ConfList->clear(); ConfList->clear();
ping = -1; ping = -1;
std::thread GS(TCPGameServer,IP,std::stoi(Data.substr(Data.find(':')+1))); std::thread GS(TCPGameServer, IP, std::stoi(Data.substr(Data.find(':') + 1)));
GS.detach(); GS.detach();
info("Connecting to server"); info("Connecting to server");
} }
void Parse(std::string Data,SOCKET CSocket){
char Code = Data.at(0), SubCode = 0; void CoreSend(std::string data) {
if(Data.length() > 1)SubCode = Data.at(1); if (CoreSocket != -1) {
switch (Code){ int res = send(CoreSocket, (data + "\n").c_str(), int(data.size()) + 1, 0);
case 'A': if (res < 0) {
Data = Data.substr(0,1);
break;
case 'B':
NetReset();
Terminate = true;
TCPTerminate = true;
Data = Code + HTTP::Post("https://backend.beammp.com/servers","");
break;
case 'C':
ListOfMods.clear();
StartSync(Data);
while(ListOfMods.empty() && !Terminate){
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if(ListOfMods == "-")Data = "L";
else Data = "L"+ListOfMods;
break;
case 'U':
if(SubCode == 'l')Data = UlStatus;
if(SubCode == 'p'){
if(ping > 800){
Data = "Up-2";
}else Data = "Up" + std::to_string(ping);
}
if(!SubCode){
std::string Ping;
if(ping > 800)Ping = "-2";
else Ping = std::to_string(ping);
Data = std::string(UlStatus) + "\n" + "Up" + Ping;
}
break;
case 'M':
Data = MStatus;
break;
case 'Q':
if(SubCode == 'S'){
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
}
if(SubCode == 'G')exit(2);
Data.clear();
break;
case 'R': //will send mod name
if(ConfList->find(Data) == ConfList->end()){
ConfList->insert(Data);
ModLoaded = true;
}
Data.clear();
break;
case 'Z':
Data = "Z" + GetVer();
break;
case 'N':
if (SubCode == 'c'){
Data = "N{\"Auth\":"+std::to_string(LoginAuth)+"}";
}else{
Data = "N" + Login(Data.substr(Data.find(':') + 1));
}
break;
default:
Data.clear();
break;
}
if(!Data.empty() && CSocket != -1){
int res = send(CSocket, (Data+"\n").c_str(), int(Data.size())+1, 0);
if(res < 0){
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError())); debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
} }
} }
} }
void GameHandler(SOCKET Client){
int32_t Size,Temp,Rcv; bool IsAllowedLink(const std::string& Link) {
char Header[10] = {0}; std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|discord\.gg|patreon\.com\/BeamMP))");
do{ std::smatch link_match;
return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
}
void Parse(std::string Data, SOCKET CSocket) {
char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
switch (Code) {
case 'A':
Data = Data.substr(0, 1);
break;
case 'B':
NetReset();
Terminate = true;
TCPTerminate = true;
Data = Code + HTTP::Get("https://backend.beammp.com/servers-info");
break;
case 'C':
StartSync(Data);
Data.clear();
break;
case 'O': // open default browser with URL
if (IsAllowedLink(Data.substr(1))) {
#if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid;
auto arg = Data.substr(1);
char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) {
debug("Browser PID: " + std::to_string(pid));
// we don't wait for it to exit, because we just don't care.
// typically, you'd waitpid() here.
} else {
error("Failed to open the following link in the browser (error follows below): " + arg);
error(std::string("posix_spawn: ") + strerror(status));
}
} else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1));
}
#elif defined(WIN32)
ShellExecuteA(nullptr, "open", Data.substr(1).c_str(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
#endif
info("Opening Link \"" + Data.substr(1) + "\"");
}
Data.clear();
break;
case 'P':
Data = Code + std::to_string(ProxyPort);
break;
case 'U':
if (SubCode == 'l')
Data = UlStatus;
if (SubCode == 'p') {
if (ping > 800) {
Data = "Up-2";
} else
Data = "Up" + std::to_string(ping);
}
if (!SubCode) {
std::string Ping;
if (ping > 800)
Ping = "-2";
else
Ping = std::to_string(ping);
Data = std::string(UlStatus) + "\n" + "Up" + Ping;
}
break;
case 'M':
Data = MStatus;
break;
case 'Q':
if (SubCode == 'S') {
NetReset();
Terminate = true;
TCPTerminate = true;
ping = -1;
}
if (SubCode == 'G') {
debug("Closing via 'G' packet");
exit(2);
}
Data.clear();
break;
case 'R': // will send mod name
if (ConfList->find(Data) == ConfList->end()) {
ConfList->insert(Data);
ModLoaded = true;
}
Data.clear();
break;
case 'Z':
Data = "Z" + GetVer();
break;
case 'N':
if (SubCode == 'c') {
nlohmann::json Auth = {
{ "Auth", LoginAuth ? 1 : 0 },
};
if (!Username.empty()) {
Auth["username"] = Username;
}
if (!UserRole.empty()) {
Auth["role"] = UserRole;
}
if (UserID != -1) {
Auth["id"] = UserID;
}
Data = "N" + Auth.dump();
} else {
Data = "N" + Login(Data.substr(Data.find(':') + 1));
}
break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
default:
Data.clear();
break;
}
if (!Data.empty() && CSocket != -1) {
int res = send(CSocket, (Data + "\n").c_str(), int(Data.size()) + 1, 0);
if (res < 0) {
debug("(Core) send failed with error: " + std::to_string(WSAGetLastError()));
}
}
}
void GameHandler(SOCKET Client) {
CoreSocket = Client;
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
do {
Rcv = 0; Rcv = 0;
do{ do {
Temp = recv(Client,&Header[Rcv],1,0); Temp = recv(Client, &Header[Rcv], 1, 0);
if(Temp < 1)break; if (Temp < 1)
if(!isdigit(Header[Rcv]) && Header[Rcv] != '>') { break;
if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
error("(Core) Invalid lua communication"); error("(Core) Invalid lua communication");
KillSocket(Client); KillSocket(Client);
return; return;
} }
}while(Header[Rcv++] != '>'); } while (Header[Rcv++] != '>');
if(Temp < 1)break; if (Temp < 1)
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){ break;
debug("(Core) Invalid lua Header -> " + std::string(Header,Rcv)); if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Core) Invalid lua Header -> " + std::string(Header, Rcv));
break; break;
} }
std::string Ret(Size,0); std::string Ret(Size, 0);
Rcv = 0; Rcv = 0;
do{ do {
Temp = recv(Client,&Ret[Rcv],Size-Rcv,0); Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
if(Temp < 1)break; if (Temp < 1)
break;
Rcv += Temp; Rcv += Temp;
}while(Rcv < Size); } while (Rcv < Size);
if(Temp < 1)break; if (Temp < 1)
break;
std::thread Respond(Parse, Ret, Client); Parse(Ret, Client);
Respond.detach(); } while (Temp > 0);
}while(Temp > 0);
if (Temp == 0) { if (Temp == 0) {
debug("(Core) Connection closing"); debug("(Core) Connection closing");
} else { } else {
@@ -165,10 +277,10 @@ void GameHandler(SOCKET Client){
NetReset(); NetReset();
KillSocket(Client); KillSocket(Client);
} }
void localRes(){ void localRes() {
MStatus = " "; MStatus = " ";
UlStatus = "Ulstart"; UlStatus = "Ulstart";
if(ConfList != nullptr){ if (ConfList != nullptr) {
ConfList->clear(); ConfList->clear();
delete ConfList; delete ConfList;
ConfList = nullptr; ConfList = nullptr;
@@ -177,25 +289,31 @@ void localRes(){
} }
void CoreMain() { void CoreMain() {
debug("Core Network on start!"); debug("Core Network on start!");
SOCKET LSocket, CSocket;
struct addrinfo* res = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
SOCKET LSocket,CSocket; iRes = WSAStartup(514, &wsaData); // 2.2
struct addrinfo *res = nullptr; if (iRes)
struct addrinfo hints{}; debug("WSAStartup failed with error: " + std::to_string(iRes));
int iRes = WSAStartup(514, &wsaData); //2.2 #endif
if (iRes)debug("WSAStartup failed with error: " + std::to_string(iRes));
ZeroMemory(&hints, sizeof(hints)); ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP; hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE; hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res); iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res);
if (iRes){ if (iRes) {
debug("(Core) addr info failed with error: " + std::to_string(iRes)); debug("(Core) addr info failed with error: " + std::to_string(iRes));
WSACleanup(); WSACleanup();
return; return;
} }
LSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); LSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (LSocket == -1){ if (LSocket == -1) {
debug("(Core) socket failed with error: " + std::to_string(WSAGetLastError())); debug("(Core) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res); freeaddrinfo(res);
WSACleanup(); WSACleanup();
@@ -217,7 +335,7 @@ void CoreMain() {
WSACleanup(); WSACleanup();
return; return;
} }
do{ do {
CSocket = accept(LSocket, nullptr, nullptr); CSocket = accept(LSocket, nullptr, nullptr);
if (CSocket == -1) { if (CSocket == -1) {
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError())); error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
@@ -227,28 +345,38 @@ void CoreMain() {
info("Game Connected!"); info("Game Connected!");
GameHandler(CSocket); GameHandler(CSocket);
warn("Game Reconnecting..."); warn("Game Reconnecting...");
}while(CSocket); } while (CSocket);
KillSocket(LSocket); KillSocket(LSocket);
WSACleanup(); WSACleanup();
} }
int Handle(EXCEPTION_POINTERS *ep){
#if defined(_WIN32)
int Handle(EXCEPTION_POINTERS* ep) {
char* hex = new char[100]; char* hex = new char[100];
sprintf_s(hex,100, "%lX", ep->ExceptionRecord->ExceptionCode); sprintf_s(hex, 100, "%lX", ep->ExceptionRecord->ExceptionCode);
except("(Core) Code : " + std::string(hex)); except("(Core) Code : " + std::string(hex));
delete [] hex; delete[] hex;
return 1; return 1;
} }
void CoreNetwork(){
while(TraceBack >= 4){
#ifndef __MINGW32__
__try{
#endif #endif
CoreMain();
#ifndef __MINGW32__ [[noreturn]] void CoreNetwork() {
}__except(Handle(GetExceptionInformation())){} while (true) {
#if not defined(__MINGW32__)
__try {
#endif #endif
CoreMain();
#if not defined(__MINGW32__) and not defined(__linux__)
} __except (Handle(GetExceptionInformation())) { }
#elif not defined(__MINGW32__) and defined(__linux__)
}
catch (...) {
except("(Core) Code : " + std::string(strerror(errno)));
}
#endif
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
} }

24
src/Network/DNS.cpp Executable file → Normal file
View File

@@ -7,25 +7,37 @@
/// ///
#include <string> #include <string>
#if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include "Logger.h" #include "Logger.h"
std::string GetAddr(const std::string&IP){ std::string GetAddr(const std::string& IP) {
if(IP.find_first_not_of("0123456789.") == -1)return IP; if (IP.find_first_not_of("0123456789.") == -1)
return IP;
hostent* host;
#ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
hostent *host; if (WSAStartup(514, &wsaData) != 0) {
if(WSAStartup(514, &wsaData) != 0){
error("WSA Startup Failed!"); error("WSA Startup Failed!");
WSACleanup(); WSACleanup();
return ""; return "";
} }
#endif
host = gethostbyname(IP.c_str()); host = gethostbyname(IP.c_str());
if(!host){ if (!host) {
error("DNS lookup failed! on " + IP); error("DNS lookup failed! on " + IP);
WSACleanup(); WSACleanup();
return "DNS"; return "DNS";
} }
std::string Ret = inet_ntoa(*((struct in_addr *)host->h_addr)); std::string Ret = inet_ntoa(*((struct in_addr*)host->h_addr));
WSACleanup(); WSACleanup();
return Ret; return Ret;
} }

264
src/Network/GlobalHandler.cpp Executable file → Normal file
View File

@@ -5,130 +5,163 @@
/// ///
/// Created by Anonymous275 on 7/25/2020 /// Created by Anonymous275 on 7/25/2020
/// ///
#include "Network/network.h" #include "Network/network.hpp"
#include <memory>
#include <zlib.h>
#if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include "Logger.h" #include "Logger.h"
#include <charconv> #include <charconv>
#include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#include <mutex>
std::chrono::time_point<std::chrono::high_resolution_clock> PingStart,PingEnd; std::chrono::time_point<std::chrono::high_resolution_clock> PingStart, PingEnd;
bool GConnected = false; bool GConnected = false;
bool CServer = true; bool CServer = true;
SOCKET CSocket = -1; SOCKET CSocket = -1;
SOCKET GSocket = -1; SOCKET GSocket = -1;
int KillSocket(uint64_t Dead){ int KillSocket(uint64_t Dead) {
if(Dead == (SOCKET)-1){ if (Dead == (SOCKET)-1) {
debug("Kill socket got -1 returning..."); debug("Kill socket got -1 returning...");
return 0; return 0;
} }
shutdown(Dead,SD_BOTH); shutdown(Dead, SD_BOTH);
int a = closesocket(Dead); int a = closesocket(Dead);
if(a != 0){ if (a != 0) {
warn("Failed to close socket!"); warn("Failed to close socket!");
} }
return a; return a;
} }
bool CheckBytes(uint32_t Bytes){ bool CheckBytes(uint32_t Bytes) {
if(Bytes == 0){ if (Bytes == 0) {
debug("(Proxy) Connection closing"); debug("(Proxy) Connection closing");
return false; return false;
}else if(Bytes < 0){ } else if (Bytes < 0) {
debug("(Proxy) send failed with error: " + std::to_string(WSAGetLastError())); debug("(Proxy) send failed with error: " + std::to_string(WSAGetLastError()));
return false; return false;
} }
return true; return true;
} }
void GameSend(std::string Data){ void GameSend(std::string_view Data) {
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
if(TCPTerminate || !GConnected || CSocket == -1)return; if (TCPTerminate || !GConnected || CSocket == -1)
int32_t Size,Temp,Sent; return;
Data += '\n'; int32_t Size, Temp, Sent;
Size = int32_t(Data.size()); Size = int32_t(Data.size());
Sent = 0; Sent = 0;
#ifdef DEBUG #ifdef DEBUG
if(Size > 1000){ if (Size > 1000) {
debug("Launcher -> game (" +std::to_string(Size)+")"); debug("Launcher -> game (" + std::to_string(Size) + ")");
} }
#endif #endif
do{ do {
if(Sent > -1){ if (Sent > -1) {
Temp = send(CSocket, &Data[Sent], Size - Sent, 0); Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
} }
if(!CheckBytes(Temp))return; if (!CheckBytes(Temp))
return;
Sent += Temp; Sent += Temp;
}while(Sent < Size); } while (Sent < Size);
// send separately to avoid an allocation for += "\n"
Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) {
return;
}
} }
void ServerSend(std::string Data, bool Rel){ void ServerSend(std::string Data, bool Rel) {
if(Terminate || Data.empty())return; if (Terminate || Data.empty())
if(Data.find("Zp") != std::string::npos && Data.size() > 500){ return;
if (Data.find("Zp") != std::string::npos && Data.size() > 500) {
abort(); abort();
} }
char C = 0; char C = 0;
bool Ack = false; bool Ack = false;
int DLen = int(Data.length()); int DLen = int(Data.length());
if(DLen > 3)C = Data.at(0); if (DLen > 3)
if (C == 'O' || C == 'T')Ack = true; C = Data.at(0);
if(C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')Rel = true; if (C == 'O' || C == 'T')
if(Ack || Rel){ Ack = true;
if(Ack || DLen > 1000)SendLarge(Data); if (C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')
else TCPSend(Data,TCPSock); Rel = true;
}else UDPSend(Data); if (compressBound(Data.size()) > 1024)
Rel = true;
if (Ack || Rel) {
if (Ack || DLen > 1000)
SendLarge(Data);
else
TCPSend(Data, TCPSock);
} else
UDPSend(Data);
if (DLen > 1000) { if (DLen > 1000) {
debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : " debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : "
+ Data.substr(0, 10) + Data.substr(0, 10)
+ Data.substr(Data.length() - 10)); + Data.substr(Data.length() - 10));
}else if(C == 'Z'){ } else if (C == 'Z') {
//debug("(Game->Launcher) : " + Data); // debug("(Game->Launcher) : " + Data);
} }
} }
void NetReset(){ void NetReset() {
TCPTerminate = false; TCPTerminate = false;
GConnected = false; GConnected = false;
Terminate = false; Terminate = false;
UlStatus = "Ulstart"; UlStatus = "Ulstart";
MStatus = " "; MStatus = " ";
if(UDPSock != (SOCKET)(-1)){ if (UDPSock != (SOCKET)(-1)) {
debug("Terminating UDP Socket : " + std::to_string(TCPSock)); debug("Terminating UDP Socket: " + std::to_string(TCPSock));
KillSocket(UDPSock); KillSocket(UDPSock);
} }
UDPSock = -1; UDPSock = -1;
if(TCPSock != (SOCKET)(-1)){ if (TCPSock != (SOCKET)(-1)) {
debug("Terminating TCP Socket : " + std::to_string(TCPSock)); debug("Terminating TCP Socket: " + std::to_string(TCPSock));
KillSocket(TCPSock); KillSocket(TCPSock);
} }
TCPSock = -1; TCPSock = -1;
if(GSocket != (SOCKET)(-1)){ if (GSocket != (SOCKET)(-1)) {
debug("Terminating GTCP Socket : " + std::to_string(GSocket)); debug("Terminating GTCP Socket: " + std::to_string(GSocket));
KillSocket(GSocket); KillSocket(GSocket);
} }
GSocket = -1; GSocket = -1;
} }
SOCKET SetupListener(){ SOCKET SetupListener() {
if(GSocket != -1)return GSocket; if (GSocket != -1)
struct addrinfo *result = nullptr; return GSocket;
struct addrinfo hints{}; struct addrinfo* result = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
int iRes = WSAStartup(514, &wsaData); //2.2 iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes != 0) { if (iRes != 0) {
error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes)); error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes));
return -1; return -1;
} }
#endif
ZeroMemory(&hints, sizeof(hints)); ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP; hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE; hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT+1).c_str(), &hints, &result); iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT + 1).c_str(), &hints, &result);
if (iRes != 0) { if (iRes != 0) {
error("(Proxy) info failed with error: " + std::to_string(iRes)); error("(Proxy) info failed with error: " + std::to_string(iRes));
WSACleanup(); WSACleanup();
@@ -140,7 +173,7 @@ SOCKET SetupListener(){
WSACleanup(); WSACleanup();
return -1; return -1;
} }
iRes = bind(GSocket, result->ai_addr, (int) result->ai_addrlen); iRes = bind(GSocket, result->ai_addr, (int)result->ai_addrlen);
if (iRes == SOCKET_ERROR) { if (iRes == SOCKET_ERROR) {
error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError())); error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(result); freeaddrinfo(result);
@@ -158,59 +191,64 @@ SOCKET SetupListener(){
} }
return GSocket; return GSocket;
} }
void AutoPing(){ void AutoPing() {
while(!Terminate){ while (!Terminate) {
ServerSend("p",false); ServerSend("p", false);
PingStart = std::chrono::high_resolution_clock::now(); PingStart = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds (1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
} }
int ClientID = -1; int ClientID = -1;
void ParserAsync(const std::string& Data){ void ParserAsync(std::string_view Data) {
if(Data.empty())return; if (Data.empty())
char Code = Data.at(0),SubCode = 0; return;
if(Data.length() > 1)SubCode = Data.at(1); char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
switch (Code) { switch (Code) {
case 'p': case 'p':
PingEnd = std::chrono::high_resolution_clock::now(); PingEnd = std::chrono::high_resolution_clock::now();
if(PingStart > PingEnd)ping = 0; if (PingStart > PingEnd)
else ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd-PingStart).count()); ping = 0;
return; else
case 'M': ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd - PingStart).count());
MStatus = Data; return;
UlStatus = "Uldone"; case 'M':
return; MStatus = Data;
default: UlStatus = "Uldone";
break; return;
default:
break;
} }
GameSend(Data); GameSend(Data);
} }
void ServerParser(const std::string& Data){ void ServerParser(std::string_view Data) {
ParserAsync(Data); ParserAsync(Data);
} }
void NetMain(const std::string& IP, int Port){ void NetMain(const std::string& IP, int Port) {
std::thread Ping(AutoPing); std::thread Ping(AutoPing);
Ping.detach(); Ping.detach();
UDPClientMain(IP,Port); UDPClientMain(IP, Port);
CServer = true; CServer = true;
Terminate = true; Terminate = true;
info("Connection Terminated!"); info("Connection Terminated!");
} }
void TCPGameServer(const std::string& IP, int Port){ void TCPGameServer(const std::string& IP, int Port) {
GSocket = SetupListener(); GSocket = SetupListener();
while (!TCPTerminate && GSocket != -1){ std::unique_ptr<std::thread> ClientThread {};
std::unique_ptr<std::thread> NetMainThread {};
while (!TCPTerminate && GSocket != -1) {
debug("MAIN LOOP OF GAME SERVER"); debug("MAIN LOOP OF GAME SERVER");
GConnected = false; GConnected = false;
if(!CServer){ if (!CServer) {
warn("Connection still alive terminating"); warn("Connection still alive terminating");
NetReset(); NetReset();
TCPTerminate = true; TCPTerminate = true;
Terminate = true; Terminate = true;
break; break;
} }
if(CServer) { if (CServer) {
std::thread Client(TCPClientMain, IP, Port); ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port);
Client.detach();
} }
CSocket = accept(GSocket, nullptr, nullptr); CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) { if (CSocket == -1) {
@@ -219,45 +257,61 @@ void TCPGameServer(const std::string& IP, int Port){
} }
debug("(Proxy) Game Connected!"); debug("(Proxy) Game Connected!");
GConnected = true; GConnected = true;
if(CServer){ if (CServer) {
std::thread t1(NetMain, IP, Port); NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
t1.detach();
CServer = false; CServer = false;
} }
int32_t Size,Temp,Rcv; int32_t Size, Temp, Rcv;
char Header[10] = {0}; char Header[10] = { 0 };
//Read byte by byte until '>' is rcved then get the size and read based on it // Read byte by byte until '>' is rcved then get the size and read based on it
do{ do {
Rcv = 0; Rcv = 0;
do{ do {
Temp = recv(CSocket,&Header[Rcv],1,0); Temp = recv(CSocket, &Header[Rcv], 1, 0);
if(Temp < 1 || TCPTerminate)break; if (Temp < 1 || TCPTerminate)
}while(Header[Rcv++] != '>'); break;
if(Temp < 1 || TCPTerminate)break; } while (Header[Rcv++] != '>');
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){ if (Temp < 1 || TCPTerminate)
debug("(Game) Invalid lua Header -> " + std::string(Header,Rcv)); break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Game) Invalid lua Header -> " + std::string(Header, Rcv));
break; break;
} }
std::string Ret(Size,0); std::string Ret(Size, 0);
Rcv = 0; Rcv = 0;
do{ do {
Temp = recv(CSocket,&Ret[Rcv],Size-Rcv,0); Temp = recv(CSocket, &Ret[Rcv], Size - Rcv, 0);
if(Temp < 1)break; if (Temp < 1)
break;
Rcv += Temp; Rcv += Temp;
}while(Rcv < Size && !TCPTerminate); } while (Rcv < Size && !TCPTerminate);
if(Temp < 1 || TCPTerminate)break; if (Temp < 1 || TCPTerminate)
break;
ServerSend(Ret,false); ServerSend(Ret, false);
}while(Temp > 0 && !TCPTerminate); } while (Temp > 0 && !TCPTerminate);
if(Temp == 0)debug("(Proxy) Connection closing"); if (Temp == 0)
else debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError())); debug("(Proxy) Connection closing");
else
debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
} }
TCPTerminate = true; TCPTerminate = true;
GConnected = false; GConnected = false;
Terminate = true; Terminate = true;
if(CSocket != SOCKET_ERROR)KillSocket(CSocket); if (ClientThread) {
debug("Waiting for client thread");
ClientThread->join();
debug("Client thread done");
}
if (NetMainThread) {
debug("Waiting for net main thread");
NetMainThread->join();
debug("Net main thread done");
}
if (CSocket != SOCKET_ERROR)
KillSocket(CSocket);
debug("END OF GAME SERVER"); debug("END OF GAME SERVER");
} }

316
src/Network/Http.cpp Executable file → Normal file
View File

@@ -5,124 +5,264 @@
/// ///
/// Created by Anonymous275 on 7/18/2020 /// Created by Anonymous275 on 7/18/2020
/// ///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <iostream> #include "Http.h"
#include <Logger.h> #include <Logger.h>
#include <fstream> #include <Network/network.hpp>
#include "http.h" #include <Startup.h>
#include <mutex> #include <Utils.h>
#include <cmath> #include <cmath>
#include <curl/curl.h>
#include <curl/easy.h>
#include <filesystem>
#include <fstream>
#include <httplib.h> #include <httplib.h>
#include <iostream>
#include <mutex>
#include <nlohmann/json.hpp>
std::string HTTP::Codes_[] = void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try {
{ const std::filesystem::path folder = ".https_debug";
"Success","Unknown","Connection","BindIPAddress", std::filesystem::create_directories(folder);
"Read","Write","ExceedRedirectCount","Canceled", if (!std::filesystem::exists(folder / "WHAT IS THIS FOLDER.txt")) {
"SSLConnection","SSLLoadingCerts","SSLServerVerification", std::ofstream ignore { folder / "WHAT IS THIS FOLDER.txt" };
"UnsupportedMultipartBoundaryChars","Compression" ignore << "This folder exists to help debug current issues with the backend. Do not share this folder with anyone but BeamMP staff. It contains detailed logs of any failed http requests." << std::endl;
}; }
bool HTTP::isDownload = false; const auto file = folder / (method + ".json");
std::string HTTP::Get(const std::string &IP) { // 1 MB limit
static std::mutex Lock; if (std::filesystem::exists(file) && std::filesystem::file_size(file) > 1'000'000) {
std::scoped_lock Guard(Lock); std::filesystem::rename(file, file.generic_string() + ".bak");
auto pos = IP.find('/',10);
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
auto res = cli.Get(IP.substr(pos).c_str(), ProgressBar);
std::string Ret;
if(res.error() == 0){
if(res->status == 200){
Ret = res->body;
}else error(res->reason);
}else{
if(isDownload) {
std::cout << "\n";
}
error("HTTP Get failed on " + Codes_[res.error()]);
} }
std::ofstream of { file, std::ios::app };
nlohmann::json js {
{ "utc", std::chrono::system_clock::now().time_since_epoch().count() },
{ "target", target },
{ "client_info", {
{ "openssl_verify_result", client.get_openssl_verify_result() },
{ "host", client.host() },
{ "port", client.port() },
{ "socket_open", client.is_socket_open() },
{ "valid", client.is_valid() },
} },
};
if (result) {
auto value = result.value();
js["result"] = {};
js["result"]["body"] = value.body;
js["result"]["status"] = value.status;
js["result"]["headers"] = value.headers;
js["result"]["version"] = value.version;
js["result"]["location"] = value.location;
js["result"]["reason"] = value.reason;
}
of << js.dump();
} catch (const std::exception& e) {
error(e.what());
}
static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
std::string* Result = reinterpret_cast<std::string*>(userp);
std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
*Result += NewContents;
return size * nmemb;
}
bool HTTP::isDownload = false;
std::string HTTP::Get(const std::string& IP) {
std::string Ret;
static thread_local CURL* curl = curl_easy_init();
if (curl) {
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); // seconds
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
return "";
}
} else {
error("Curl easy init failed");
return "";
}
return Ret; return Ret;
} }
std::string HTTP::Post(const std::string& IP, const std::string& Fields) { std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
auto pos = IP.find('/',10);
httplib::Client cli(IP.substr(0, pos).c_str());
cli.set_connection_timeout(std::chrono::seconds(10));
std::string Ret; std::string Ret;
static thread_local CURL* curl = curl_easy_init();
if(!Fields.empty()) { if (curl) {
httplib::Result res = cli.Post(IP.substr(pos).c_str(), Fields, "application/json"); CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
if(res.error() == 0) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
if (res->status != 200) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
error(res->reason); curl_easy_setopt(curl, CURLOPT_POST, 1);
} curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
Ret = res->body; curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
}else{ struct curl_slist* list = nullptr;
error("HTTP Post failed on " + Codes_[res.error()]); list = curl_slist_append(list, "Content-Type: application/json");
} curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
}else{ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10); // seconds
httplib::Result res = cli.Post(IP.substr(pos).c_str()); res = curl_easy_perform(curl);
if(res.error() == 0) { curl_slist_free_all(list);
if (res->status != 200) { if (res != CURLE_OK) {
error(res->reason); error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
} return "";
Ret = res->body;
}else{
error("HTTP Post failed on " + Codes_[res.error()]);
} }
} else {
error("Curl easy init failed");
return "";
} }
return Ret;
if(Ret.empty())return "-1";
else return Ret;
} }
bool HTTP::ProgressBar(size_t c, size_t t){ bool HTTP::Download(const std::string& IP, const std::string& Path) {
if(isDownload) {
static double last_progress, progress_bar_adv;
progress_bar_adv = round(c / double(t) * 25);
std::cout << "\r";
std::cout << "Progress : [ ";
std::cout << round(c / double(t) * 100);
std::cout << "% ] [";
int i;
for (i = 0; i <= progress_bar_adv; i++)std::cout << "#";
for (i = 0; i < 25 - progress_bar_adv; i++)std::cout << ".";
std::cout << "]";
last_progress = round(c / double(t) * 100);
}
return true;
}
bool HTTP::Download(const std::string &IP, const std::string &Path) {
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
isDownload = true; info("Downloading an update (this may take a while)");
std::string Ret = Get(IP); std::string Ret = Get(IP);
isDownload = false;
if(Ret.empty())return false; if (Ret.empty()) {
error("Download failed");
return false;
}
std::ofstream File(Path, std::ios::binary); std::ofstream File(Path, std::ios::binary);
if(File.is_open()) { if (File.is_open()) {
File << Ret; File << Ret;
File.close(); File.close();
std::cout << "\n"; std::cout << "\n";
info("Download Complete!"); info("Download Complete!");
}else{ } else {
error("Failed to open file directory: " + Path); error("Failed to open file directory: " + Path);
return false; return false;
} }
return true; return true;
} }
void set_headers(httplib::Response& res) {
res.set_header("Access-Control-Allow-Origin", "*");
res.set_header("Access-Control-Request-Method", "POST, OPTIONS, GET");
res.set_header("Access-Control-Request-Headers", "X-API-Version");
}
void HTTP::StartProxy() {
std::thread proxy([&]() {
httplib::Server HTTPProxy;
httplib::Headers headers = {
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
{ "Accept", "*/*" }
};
httplib::Client backend("https://backend.beammp.com");
httplib::Client forum("https://forum.beammp.com");
const std::string pattern = ".*";
auto handle_request = [&](const httplib::Request& req, httplib::Response& res) {
set_headers(res);
if (req.has_header("X-BMP-Authentication")) {
headers.emplace("X-BMP-Authentication", PrivateKey);
}
if (req.has_header("X-API-Version")) {
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
}
const std::vector<std::string> path = Utils::Split(req.path, "/");
httplib::Result cli_res;
const std::string method = req.method;
std::string host = "";
if (!path.empty())
host = path[0];
if (host == "backend") {
std::string remaining_path = req.path.substr(std::strlen("/backend"));
if (method == "GET")
cli_res = backend.Get(remaining_path, headers);
else if (method == "POST")
cli_res = backend.Post(remaining_path, headers);
} else if (host == "avatar") {
bool error = false;
std::string username;
std::string avatar_size = "100";
if (path.size() > 1) {
username = path[1];
} else {
error = true;
}
if (path.size() > 2) {
try {
if (std::stoi(path[2]) > 0)
avatar_size = path[2];
} catch (std::exception&) { }
}
httplib::Result summary_res;
if (!error) {
summary_res = forum.Get("/u/" + username + ".json", headers);
if (!summary_res || summary_res->status != 200) {
error = true;
}
}
if (!error) {
try {
nlohmann::json d = nlohmann::json::parse(summary_res->body, nullptr, false); // can fail with parse_error
auto user = d.at("user"); // can fail with out_of_range
auto avatar_link_json = user.at("avatar_template"); // can fail with out_of_range
auto avatar_link = avatar_link_json.get<std::string>();
size_t start_pos = avatar_link.find("{size}");
if (start_pos != std::string::npos)
avatar_link.replace(start_pos, std::strlen("{size}"), avatar_size);
cli_res = forum.Get(avatar_link, headers);
} catch (std::exception&) {
error = true;
}
}
if (error) {
cli_res = forum.Get("/user_avatar/forum.beammp.com/user/0/0.png", headers);
}
} else {
res.set_content("Host not found", "text/plain");
return;
}
if (cli_res) {
res.set_content(cli_res->body, cli_res->get_header_value("Content-Type"));
} else {
res.set_content(to_string(cli_res.error()), "text/plain");
}
};
HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) {
handle_request(req, res);
});
HTTPProxy.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) {
handle_request(req, res);
});
ProxyPort = HTTPProxy.bind_to_any_port("0.0.0.0");
debug("HTTP Proxy listening on port " + std::to_string(ProxyPort));
HTTPProxy.listen_after_bind();
});
proxy.detach();
}

639
src/Network/Resources.cpp Executable file → Normal file
View File

@@ -6,151 +6,198 @@
/// Created by Anonymous275 on 4/11/2020 /// Created by Anonymous275 on 4/11/2020
/// ///
#include "Network/network.h" #include "Network/network.hpp"
#include <chrono>
#include <iomanip>
#include <ios>
#include <mutex>
#include <nlohmann/json.hpp>
#include <openssl/err.h>
#include <openssl/evp.h>
#if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <filesystem> #elif defined(__linux__)
#include "Startup.h" #include <arpa/inet.h>
#include "Logger.h"
#include <iostream>
#include <cstring> #include <cstring>
#include <fstream> #include <errno.h>
#include <string> #include <netdb.h>
#include <thread> #include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Logger.h"
#include "Startup.h"
#include <Utils.h>
#include <atomic> #include <atomic>
#include <vector>
#include <future>
#include <cmath> #include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <future>
#include <iostream>
#include <thread>
#include "hashpp.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string ListOfMods;
std::vector<std::string> Split(const std::string& String,const std::string& delimiter){
std::vector<std::string> Val;
size_t pos;
std::string token,s = String;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
if(!token.empty())Val.push_back(token);
s.erase(0, pos + delimiter.length());
}
if(!s.empty())Val.push_back(s);
return Val;
}
void CheckForDir(){ void CheckForDir() {
if(!fs::exists("Resources")){ if (!fs::exists(CachingDirectory)) {
_wmkdir(L"Resources"); try {
fs::create_directories(CachingDirectory);
} catch (const std::exception& e) {
error(std::string("Failed to create caching directory: ") + e.what() + ". This is a fatal error. Please make sure to configure a directory which you have permission to create, read and write from/to.");
std::this_thread::sleep_for(std::chrono::seconds(3));
std::exit(1);
}
} }
} }
void WaitForConfirm(){ void WaitForConfirm() {
while(!Terminate && !ModLoaded){ while (!Terminate && !ModLoaded) {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
ModLoaded = false; ModLoaded = false;
} }
void Abord() {
void Abord(){
Terminate = true; Terminate = true;
TCPTerminate = true; TCPTerminate = true;
info("Terminated!"); info("Terminated!");
} }
std::string Auth(SOCKET Sock){ std::string Auth(SOCKET Sock) {
TCPSend("VC" + GetVer(),Sock); TCPSend("VC" + GetVer(), Sock);
auto Res = TCPRcv(Sock); auto Res = TCPRcv(Sock);
if(Res.empty() || Res[0] == 'E'){ if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
TCPSend(PublicKey,Sock); TCPSend(PublicKey, Sock);
if(Terminate)return ""; if (Terminate) {
CoreSend("L");
return "";
}
Res = TCPRcv(Sock); Res = TCPRcv(Sock);
if(Res.empty() || Res[0] != 'P'){ if (Res.empty() || Res[0] != 'P') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
Res = Res.substr(1); Res = Res.substr(1);
if(Res.find_first_not_of("0123456789") == std::string::npos){ if (Res.find_first_not_of("0123456789") == std::string::npos) {
ClientID = std::stoi(Res); ClientID = std::stoi(Res);
}else{ } else {
Abord(); Abord();
CoreSend("L");
UUl("Authentication failed!"); UUl("Authentication failed!");
return ""; return "";
} }
TCPSend("SR",Sock); TCPSend("SR", Sock);
if(Terminate)return ""; if (Terminate) {
CoreSend("L");
Res = TCPRcv(Sock);
if(Res[0] == 'E'){
Abord();
return ""; return "";
} }
if(Res.empty() || Res == "-"){ Res = TCPRcv(Sock);
if (Res[0] == 'E' || Res[0] == 'K') {
Abord();
CoreSend("L");
return "";
}
if (Res.empty() || Res == "-") {
info("Didn't Receive any mods..."); info("Didn't Receive any mods...");
ListOfMods = "-"; CoreSend("L");
TCPSend("Done",Sock); TCPSend("Done", Sock);
info("Done!"); info("Done!");
return ""; return "";
} }
return Res; return Res;
} }
void UpdateUl(bool D,const std::string&msg){ void UpdateUl(bool D, const std::string& msg) {
if(D)UlStatus = "UlDownloading Resource " + msg; if (D)
else UlStatus = "UlLoading Resource " + msg; UlStatus = "UlDownloading Resource " + msg;
else
UlStatus = "UlLoading Resource " + msg;
} }
void AsyncUpdate(uint64_t& Rcv,uint64_t Size,const std::string& Name){ float DownloadSpeed = 0;
void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) {
do { do {
double pr = Rcv / double(Size) * 100; double pr = double(Rcv) / double(Size) * 100;
std::string Per = std::to_string(trunc(pr * 10) / 10); std::string Per = std::to_string(trunc(pr * 10) / 10);
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)"); std::string SpeedString = "";
if (DownloadSpeed > 0.01) {
std::stringstream ss;
ss << " at " << std::setprecision(1) << std::fixed << DownloadSpeed << " Mbit/s";
SpeedString = ss.str();
}
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)" + SpeedString);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while (!Terminate && Rcv < Size);
}while(!Terminate && Rcv < Size);
} }
char* TCPRcvRaw(SOCKET Sock,uint64_t& GRcv, uint64_t Size){ // MICROSOFT, I DONT CARE, WRITE BETTER CODE
if(Sock == -1){ #undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return nullptr; return {};
} }
char* File = new char[Size]; std::vector<char> File(Size);
uint64_t Rcv = 0; uint64_t Rcv = 0;
do{
int Len = int(Size-Rcv); auto start = std::chrono::high_resolution_clock::now();
if(Len > 1000000)Len = 1000000;
int i = 0;
do {
// receive at most some MB at a time
int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL); int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
if(Temp < 1){ if (Temp < 1) {
info(std::to_string(Temp)); info(std::to_string(Temp));
UUl("Socket Closed Code 1"); UUl("Socket Closed Code 1");
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
delete[] File; return {};
return nullptr;
} }
Rcv += Temp; Rcv += Temp;
GRcv += Temp; GRcv += Temp;
}while(Rcv < Size && !Terminate);
auto end = std::chrono::high_resolution_clock::now();
auto difference = end - start;
float bits_per_s = float(Rcv * 8) / float(std::chrono::duration_cast<std::chrono::milliseconds>(difference).count());
float megabits_per_s = bits_per_s / 1000;
DownloadSpeed = megabits_per_s;
// every 8th iteration print the speed
if (i % 8 == 0) {
debug("Download speed: " + std::to_string(uint32_t(megabits_per_s)) + "Mbit/s");
}
++i;
} while (Rcv < Size && !Terminate);
return File; return File;
} }
void MultiKill(SOCKET Sock,SOCKET Sock1){ void MultiKill(SOCKET Sock, SOCKET Sock1) {
KillSocket(Sock1); KillSocket(Sock1);
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
} }
SOCKET InitDSock(){ SOCKET InitDSock() {
SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr; SOCKADDR_IN ServerAddr;
if(DSock < 1){ if (DSock < 1) {
KillSocket(DSock); KillSocket(DSock);
Terminate = true; Terminate = true;
return 0; return 0;
@@ -158,13 +205,13 @@ SOCKET InitDSock(){
ServerAddr.sin_family = AF_INET; ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(LastPort); ServerAddr.sin_port = htons(LastPort);
inet_pton(AF_INET, LastIP.c_str(), &ServerAddr.sin_addr); inet_pton(AF_INET, LastIP.c_str(), &ServerAddr.sin_addr);
if(connect(DSock, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)) != 0){ if (connect(DSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)) != 0) {
KillSocket(DSock); KillSocket(DSock);
Terminate = true; Terminate = true;
return 0; return 0;
} }
char Code[2] = {'D',char(ClientID)}; char Code[2] = { 'D', char(ClientID) };
if(send(DSock,Code,2,0) != 2){ if (send(DSock, Code, 2, 0) != 2) {
KillSocket(DSock); KillSocket(DSock);
Terminate = true; Terminate = true;
return 0; return 0;
@@ -172,94 +219,380 @@ SOCKET InitDSock(){
return DSock; return DSock;
} }
std::string MultiDownload(SOCKET MSock,SOCKET DSock, uint64_t Size, const std::string& Name){ std::vector<char> SingleNormalDownload(SOCKET MSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
uint64_t GRcv = 0, MSize = Size/2, DSize = Size - MSize; uint64_t GRcv = 0;
std::thread Au(AsyncUpdate,std::ref(GRcv),Size,Name); std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
Au.detach();
std::packaged_task<char*()> task([&] { return TCPRcvRaw(MSock,GRcv,MSize); }); const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, Size);
std::future<char*> f1 = task.get_future();
std::thread Dt(std::move(task));
Dt.detach();
char* DData = TCPRcvRaw(DSock,GRcv,DSize); if (MData.empty()) {
KillSocket(MSock);
if(!DData){ Terminate = true;
MultiKill(MSock,DSock); Au.join();
return ""; return {};
} }
f1.wait(); // ensure that GRcv is good before joining the async update thread
char* MData = f1.get(); GRcv = MData.size();
if (GRcv != Size) {
if(!MData){ error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
MultiKill(MSock,DSock); Terminate = true;
return ""; Au.join();
return {};
} }
if(Au.joinable())Au.join();
Au.join();
///omg yes very ugly my god but i was in a rush will revisit return MData;
std::string Ret(Size,0);
memcpy_s(&Ret[0],MSize,MData,MSize);
delete[]MData;
memcpy_s(&Ret[MSize],DSize,DData,DSize);
delete[]DData;
return Ret;
} }
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
void SyncResources(SOCKET Sock){ uint64_t GRcv = 0;
uint64_t MSize = Size / 2;
uint64_t DSize = Size - MSize;
std::thread Au([&] { AsyncUpdate(GRcv, Size, Name); });
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, MSize);
if (MData.empty()) {
MultiKill(MSock, DSock);
Terminate = true;
Au.join();
return {};
}
const std::vector<char> DData = TCPRcvRaw(DSock, GRcv, DSize);
if (DData.empty()) {
MultiKill(MSock, DSock);
Terminate = true;
Au.join();
return {};
}
// ensure that GRcv is good before joining the async update thread
GRcv = MData.size() + DData.size();
if (GRcv != Size) {
error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
Terminate = true;
Au.join();
return {};
}
Au.join();
std::vector<char> Result {};
Result.insert(Result.begin(), MData.begin(), MData.end());
Result.insert(Result.end(), DData.begin(), DData.end());
return Result;
}
void InvalidResource(const std::string& File) {
UUl("Invalid mod \"" + File + "\"");
warn("The server tried to sync \"" + File + "\" that is not a .zip file!");
Terminate = true;
}
std::string GetSha256HashReallyFast(const std::string& filename) {
try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
uint8_t sha256_value[EVP_MAX_MD_SIZE];
md = EVP_sha256();
if (md == nullptr) {
throw std::runtime_error("EVP_sha256() failed");
}
mdctx = EVP_MD_CTX_new();
if (mdctx == nullptr) {
throw std::runtime_error("EVP_MD_CTX_new() failed");
}
if (!EVP_DigestInit_ex2(mdctx, md, NULL)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestInit_ex2() failed");
}
std::ifstream stream(filename, std::ios::binary);
const size_t FileSize = std::filesystem::file_size(filename);
size_t Read = 0;
std::vector<char> Data;
while (Read < FileSize) {
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
size_t RealDataSize = Data.size();
stream.read(Data.data(), std::streamsize(Data.size()));
if (stream.eof() || stream.fail()) {
RealDataSize = size_t(stream.gcount());
}
Data.resize(RealDataSize);
if (RealDataSize == 0) {
break;
}
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestUpdate() failed");
}
Read += RealDataSize;
}
unsigned int sha256_len = 0;
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestFinal_ex() failed");
}
EVP_MD_CTX_free(mdctx);
std::string result;
for (size_t i = 0; i < sha256_len; i++) {
char buf[3];
sprintf(buf, "%02x", sha256_value[i]);
buf[2] = 0;
result += buf;
}
return result;
} catch (const std::exception& e) {
error("Sha256 hashing of '" + filename + "' failed: " + e.what());
return "";
}
}
struct ModInfo {
static std::vector<ModInfo> ParseModInfosFromPacket(const std::string& packet) {
std::vector<ModInfo> modInfos;
try {
auto json = nlohmann::json::parse(packet);
for (const auto& entry : json) {
ModInfo modInfo {
.FileName = entry["file_name"],
.FileSize = entry["file_size"],
.Hash = entry["hash"],
.HashAlgorithm = entry["hash_algorithm"],
};
modInfos.push_back(modInfo);
}
} catch (const std::exception& e) {
debug(std::string("Failed to receive mod list: ") + e.what());
warn("Failed to receive new mod list format! This server may be outdated, but everything should still work as expected.");
return {};
}
return modInfos;
}
std::string FileName;
size_t FileSize;
std::string Hash;
std::string HashAlgorithm;
};
void NewSyncResources(SOCKET Sock, const std::string& Mods, const std::vector<ModInfo> ModInfos) {
if (!SecurityWarning())
return;
info("Checking Resources...");
CheckForDir();
std::string t;
for (const auto& mod : ModInfos) {
t += mod.FileName + ";";
}
if (t.empty())
CoreSend("L");
else
CoreSend("L" + t);
t.clear();
info("Syncing...");
int ModNo = 1;
int TotalMods = ModInfos.size();
for (auto ModInfoIter = ModInfos.begin(), AlsoModInfoIter = ModInfos.begin(); ModInfoIter != ModInfos.end() && !Terminate; ++ModInfoIter, ++AlsoModInfoIter) {
if (ModInfoIter->Hash.length() < 8 || ModInfoIter->HashAlgorithm != "sha256") {
error("Unsupported hash algorithm or invalid hash for '" + ModInfoIter->FileName + "'");
Terminate = true;
return;
}
auto FileName = std::filesystem::path(ModInfoIter->FileName).stem().string() + "-" + ModInfoIter->Hash.substr(0, 8) + std::filesystem::path(ModInfoIter->FileName).extension().string();
auto PathToSaveTo = (fs::path(CachingDirectory) / FileName).string();
if (fs::exists(PathToSaveTo) && GetSha256HashReallyFast(PathToSaveTo) == ModInfoIter->Hash) {
debug("Mod '" + FileName + "' found in cache");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
auto modname = ModInfoIter->FileName;
#if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
debug("Mod name: " + modname);
auto name = std::filesystem::path(GetGamePath()) / "mods/multiplayer" / modname;
std::string tmp_name = name.string();
tmp_name += ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
} catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
continue;
}
WaitForConfirm();
continue;
}
CheckForDir();
std::string FName = ModInfoIter->FileName;
do {
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + ModInfoIter->FileName, Sock);
std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) {
Terminate = true;
UUl("Server cannot find " + FName);
break;
}
std::string Name = std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName;
std::vector<char> DownloadedFile = SingleNormalDownload(Sock, ModInfoIter->FileSize, Name);
if (Terminate)
break;
UpdateUl(false, std::to_string(ModNo) + "/" + std::to_string(TotalMods) + ": " + FName);
// 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
OutFile.flush();
}
// 2. verify size
if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
Terminate = true;
}
} while (fs::file_size(PathToSaveTo) != ModInfoIter->FileSize && !Terminate);
if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
// Linux version of the game doesnt support uppercase letters in mod names
#if defined(__linux__)
for (char& c : FName) {
c = ::tolower(c);
}
#endif
fs::copy_file(PathToSaveTo, std::filesystem::path(GetGamePath()) / "mods/multiplayer" / FName, fs::copy_options::overwrite_existing);
}
WaitForConfirm();
++ModNo;
}
if (!Terminate) {
TCPSend("Done", Sock);
info("Done!");
} else {
UlStatus = "Ulstart";
info("Connection Terminated!");
}
}
void SyncResources(SOCKET Sock) {
std::string Ret = Auth(Sock); std::string Ret = Auth(Sock);
if(Ret.empty())return;
if (Ret.starts_with("R")) {
debug("This server is likely outdated, not trying to parse new mod info format");
} else {
auto ModInfos = ModInfo::ParseModInfosFromPacket(Ret);
if (!ModInfos.empty()) {
NewSyncResources(Sock, Ret, ModInfos);
return;
}
}
if (Ret.empty())
return;
if (!SecurityWarning())
return;
info("Checking Resources..."); info("Checking Resources...");
CheckForDir(); CheckForDir();
std::vector<std::string> list = Split(Ret, ";"); std::vector<std::string> list = Utils::Split(Ret, ";");
std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2)); std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2));
std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end()); std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end());
list.clear(); list.clear();
Ret.clear(); Ret.clear();
int Amount = 0,Pos = 0; int Amount = 0, Pos = 0;
std::string a,t; std::string PathToSaveTo, t;
for(const std::string&name : FNames){ for (const std::string& name : FNames) {
if(!name.empty()){ if (!name.empty()) {
t += name.substr(name.find_last_of('/') + 1) + ";"; t += name.substr(name.find_last_of('/') + 1) + ";";
} }
} }
if(t.empty())ListOfMods = "-"; if (t.empty())
else ListOfMods = t; CoreSend("L");
else
CoreSend("L" + t);
t.clear(); t.clear();
for(auto FN = FNames.begin(),FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN,++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
if (pos == std::string::npos)continue; auto ZIP = FN->find(".zip");
if (ZIP == std::string::npos || FN->length() - ZIP != 4) {
InvalidResource(*FN);
return;
}
if (pos == std::string::npos)
continue;
Amount++; Amount++;
} }
if(!FNames.empty())info("Syncing..."); if (!FNames.empty())
info("Syncing...");
SOCKET DSock = InitDSock(); SOCKET DSock = InitDSock();
for(auto FN = FNames.begin(),FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN,++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
if (pos != std::string::npos) { if (pos != std::string::npos) {
a = "Resources" + FN->substr(pos); PathToSaveTo = CachingDirectory + FN->substr(pos);
} else continue; } else {
continue;
}
Pos++; Pos++;
if (fs::exists(a)) { auto FileSize = std::stoull(*FS);
if (FS->find_first_not_of("0123456789") != std::string::npos)continue; if (fs::exists(PathToSaveTo)) {
if (fs::file_size(a) == std::stoull(*FS)){ if (FS->find_first_not_of("0123456789") != std::string::npos)
UpdateUl(false,std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/'))); continue;
if (fs::file_size(PathToSaveTo) == FileSize) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
try { try {
if(!fs::exists(GetGamePath() + "mods/multiplayer")){ if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
} }
fs::copy_file(a, GetGamePath() + "mods/multiplayer" + a.substr(a.find_last_of('/')), auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
fs::copy_options::overwrite_existing); #if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) {
c = ::tolower(c);
}
#endif
auto name = GetGamePath() + "mods/multiplayer" + modname;
auto tmp_name = name + ".tmp";
fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name);
} catch (std::exception& e) { } catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what())); error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true; Terminate = true;
@@ -267,15 +600,17 @@ void SyncResources(SOCKET Sock){
} }
WaitForConfirm(); WaitForConfirm();
continue; continue;
}else remove(a.c_str()); } else
remove(PathToSaveTo.c_str());
} }
CheckForDir(); CheckForDir();
std::string FName = a.substr(a.find_last_of('/')); std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
do { do {
TCPSend("f" + *FN,Sock); debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + *FN, Sock);
std::string Data = TCPRcv(Sock); std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate){ if (Data == "CO" || Terminate) {
Terminate = true; Terminate = true;
UUl("Server cannot find " + FName); UUl("Server cannot find " + FName);
break; break;
@@ -283,31 +618,45 @@ void SyncResources(SOCKET Sock){
std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName; std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName;
Data = MultiDownload(Sock,DSock,std::stoull(*FS), Name); std::vector<char> DownloadedFile = MultiDownload(Sock, DSock, FileSize, Name);
if(Terminate)break; if (Terminate)
UpdateUl(false,std::to_string(Pos)+"/"+std::to_string(Amount)+": "+FName); break;
std::ofstream LFS; UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName);
LFS.open(a.c_str(), std::ios_base::app | std::ios::binary);
if (LFS.is_open()) { // 1. write downloaded file to disk
LFS.write(&Data[0], Data.size()); {
LFS.close(); std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
} }
// 2. verify size
}while(fs::file_size(a) != std::stoull(*FS) && !Terminate); if (std::filesystem::file_size(PathToSaveTo) != DownloadedFile.size()) {
if(!Terminate){ error("Failed to write the entire file '" + PathToSaveTo + "' correctly (file size mismatch)");
if(!fs::exists(GetGamePath() + "mods/multiplayer")){ Terminate = true;
}
} while (fs::file_size(PathToSaveTo) != std::stoull(*FS) && !Terminate);
if (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
} }
fs::copy_file(a,GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
// Linux version of the game doesnt support uppercase letters in mod names
#if defined(__linux__)
for (char& c : FName) {
c = ::tolower(c);
}
#endif
fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
} }
WaitForConfirm(); WaitForConfirm();
} }
KillSocket(DSock); KillSocket(DSock);
if(!Terminate){ if (!Terminate) {
TCPSend("Done",Sock); TCPSend("Done", Sock);
info("Done!"); info("Done!");
}else{ } else {
UlStatus = "Ulstart"; UlStatus = "Ulstart";
info("Connection Terminated!"); info("Connection Terminated!");
} }

96
src/Network/VehicleData.cpp Executable file → Normal file
View File

@@ -5,69 +5,103 @@
/// ///
/// Created by Anonymous275 on 5/8/2020 /// Created by Anonymous275 on 5/8/2020
/// ///
#include "Network/network.hpp"
#include "Zlib/Compressor.h" #include "Zlib/Compressor.h"
#include "Network/network.h" #include <stdexcept>
#if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Logger.h" #include "Logger.h"
#include <array>
#include <string> #include <string>
#include <set>
SOCKET UDPSock = -1; SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr; sockaddr_in* ToServer = nullptr;
void UDPSend(std::string Data){ void UDPSend(std::string Data) {
if(ClientID == -1 || UDPSock == -1)return; if (ClientID == -1 || UDPSock == -1)
if(Data.length() > 400){ return;
std::string CMP(Comp(Data)); if (Data.length() > 400) {
Data = "ABG:" + CMP; auto res = Comp(std::span<char>(Data.data(), Data.size()));
Data = "ABG:" + std::string(res.data(), res.size());
} }
std::string Packet = char(ClientID+1) + std::string(":") + Data; std::string Packet = char(ClientID + 1) + std::string(":") + Data;
int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer)); int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer));
if (sendOk == SOCKET_ERROR)error("Error Code : " + std::to_string(WSAGetLastError())); if (sendOk == SOCKET_ERROR)
error("Error Code : " + std::to_string(WSAGetLastError()));
} }
void SendLarge(std::string Data) {
void SendLarge(std::string Data){ if (Data.length() > 400) {
if(Data.length() > 400){ auto res = Comp(std::span<char>(Data.data(), Data.size()));
std::string CMP(Comp(Data)); Data = "ABG:" + std::string(res.data(), res.size());
Data = "ABG:" + CMP;
} }
TCPSend(Data,TCPSock); TCPSend(Data, TCPSock);
} }
void UDPParser(std::string Packet){ void UDPParser(std::string_view Packet) {
if(Packet.substr(0,4) == "ABG:"){ if (Packet.substr(0, 4) == "ABG:") {
Packet = DeComp(Packet.substr(4)); auto substr = Packet.substr(4);
try {
auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
std::string DeCompPacket = std::string(res.data(), res.size());
ServerParser(DeCompPacket);
} catch (const std::runtime_error& err) {
error("Error in decompression of UDP, ignoring");
}
} else {
ServerParser(Packet);
} }
ServerParser(Packet);
} }
void UDPRcv(){ void UDPRcv() {
sockaddr_in FromServer{}; sockaddr_in FromServer {};
#if defined(_WIN32)
int clientLength = sizeof(FromServer); int clientLength = sizeof(FromServer);
#elif defined(__linux__)
socklen_t clientLength = sizeof(FromServer);
#endif
ZeroMemory(&FromServer, clientLength); ZeroMemory(&FromServer, clientLength);
std::string Ret(10240,0); static thread_local std::array<char, 10240> Ret {};
if(UDPSock == -1)return; if (UDPSock == -1)
int32_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&FromServer, &clientLength); return;
if (Rcv == SOCKET_ERROR)return; int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
UDPParser(Ret.substr(0,Rcv)); if (Rcv == SOCKET_ERROR)
return;
Ret[Rcv] = 0;
UDPParser(std::string_view(Ret.data(), Rcv));
} }
void UDPClientMain(const std::string& IP,int Port){ void UDPClientMain(const std::string& IP, int Port) {
#ifdef _WIN32
WSADATA data; WSADATA data;
if (WSAStartup(514, &data)){ if (WSAStartup(514, &data)) {
error("Can't start Winsock!"); error("Can't start Winsock!");
return; return;
} }
#endif
delete ToServer; delete ToServer;
ToServer = new sockaddr_in; ToServer = new sockaddr_in;
ToServer->sin_family = AF_INET; ToServer->sin_family = AF_INET;
ToServer->sin_port = htons(Port); ToServer->sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr); inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
UDPSock = socket(AF_INET, SOCK_DGRAM, 0); UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
GameSend("P"+std::to_string(ClientID)); GameSend("P" + std::to_string(ClientID));
TCPSend("H",TCPSock); TCPSend("H", TCPSock);
UDPSend("p"); UDPSend("p");
while(!Terminate)UDPRcv(); debug("Starting UDP receive loop");
while (!Terminate) {
UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock); KillSocket(UDPSock);
WSACleanup(); WSACleanup();
} }

162
src/Network/VehicleEvent.cpp Executable file → Normal file
View File

@@ -6,26 +6,35 @@
/// Created by Anonymous275 on 5/8/2020 /// Created by Anonymous275 on 5/8/2020
/// ///
#include <chrono>
#include <vector>
#include "Logger.h" #include "Logger.h"
#include <iostream>
#include <ws2tcpip.h>
#include <Zlib/Compressor.h> #include <Zlib/Compressor.h>
#include <chrono>
#include <iostream>
#include <vector>
#include "Network/network.h" #if defined(_WIN32)
#include <ws2tcpip.h>
#elif defined(__linux__)
#include <arpa/inet.h>
#include <cstring>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Network/network.hpp"
int LastPort; int LastPort;
std::string LastIP; std::string LastIP;
SOCKET TCPSock = -1; SOCKET TCPSock = -1;
bool CheckBytes(int32_t Bytes){ bool CheckBytes(int32_t Bytes) {
if (Bytes == 0){ if (Bytes == 0) {
debug("(TCP) Connection closing... CheckBytes(16)"); debug("(TCP) Connection closing... CheckBytes(16)");
Terminate = true; Terminate = true;
return false; return false;
}else if (Bytes < 0) { } else if (Bytes < 0) {
debug("(TCP CB) recv failed with error: " + std::to_string(WSAGetLastError())); debug("(TCP CB) recv failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock); KillSocket(TCPSock);
Terminate = true; Terminate = true;
@@ -33,110 +42,117 @@ bool CheckBytes(int32_t Bytes){
} }
return true; return true;
} }
void UUl(const std::string& R){ void UUl(const std::string& R) {
UlStatus = "UlDisconnected: " + R; UlStatus = "UlDisconnected: " + R;
} }
void TCPSend(const std::string&Data,uint64_t Sock){ void TCPSend(const std::string& Data, uint64_t Sock) {
if(Sock == -1){ if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return; return;
} }
int32_t Size,Sent,Temp; int32_t Size, Sent, Temp;
std::string Send(4,0); std::string Send(4, 0);
Size = int32_t(Data.size()); Size = int32_t(Data.size());
memcpy(&Send[0],&Size,sizeof(Size)); memcpy(&Send[0], &Size, sizeof(Size));
Send += Data; Send += Data;
// Do not use Size before this point for anything but the header // Do not use Size before this point for anything but the header
Sent = 0; Sent = 0;
Size += 4; Size += 4;
do{ do {
if (size_t(Sent) >= Send.size()) { if (size_t(Sent) >= Send.size()) {
error("string OOB in " + std::string(__func__)); error("string OOB in " + std::string(__func__));
UUl("TCP Send OOB"); UUl("TCP Send OOB");
return; return;
} }
Temp = send(Sock, &Send[Sent], Size - Sent, 0); Temp = send(Sock, &Send[Sent], Size - Sent, 0);
if(!CheckBytes(Temp)){ if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 2"); UUl("Socket Closed Code 2");
return; return;
} }
Sent += Temp; Sent += Temp;
}while(Sent < Size); } while (Sent < Size);
} }
std::string TCPRcv(SOCKET Sock){ std::string TCPRcv(SOCKET Sock) {
if(Sock == -1){ if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return ""; return "";
} }
int32_t Header,BytesRcv = 0,Temp; int32_t Header, Temp;
std::vector<char> Data(sizeof(Header)); std::vector<char> Data(sizeof(Header));
do{ Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
Temp = recv(Sock,&Data[BytesRcv],4-BytesRcv,0); if (!CheckBytes(Temp)) {
if(!CheckBytes(Temp)){ UUl("Socket Closed Code 3");
UUl("Socket Closed Code 3"); return "";
return ""; }
} memcpy(&Header, Data.data(), sizeof(Header));
BytesRcv += Temp;
}while(BytesRcv < 4);
memcpy(&Header,&Data[0],sizeof(Header));
if(!CheckBytes(BytesRcv)){ if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 4"); UUl("Socket Closed Code 4");
return ""; return "";
} }
Data.resize(Header);
BytesRcv = 0;
do{
Temp = recv(Sock,&Data[BytesRcv],Header-BytesRcv,0);
if(!CheckBytes(Temp)){
UUl("Socket Closed Code 5");
return "";
}
BytesRcv += Temp;
}while(BytesRcv < Header);
std::string Ret(Data.data(),Header); Data.resize(Header, 0);
Temp = recv(Sock, Data.data(), Header, MSG_WAITALL);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 5");
return "";
}
std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") { if (Ret.substr(0, 4) == "ABG:") {
Ret = DeComp(Ret.substr(4)); auto substr = Ret.substr(4);
try {
auto res = DeComp(std::span<char>(substr.data(), substr.size()));
Ret = std::string(res.data(), res.size());
} catch (const std::runtime_error& err) {
// this happens e.g. when we're out of memory, or when we get incomplete data
error("Decompression failed");
return "";
}
} }
#ifdef DEBUG #ifdef DEBUG
//debug("Parsing from server -> " + std::to_string(Ret.size())); // debug("Parsing from server -> " + std::to_string(Ret.size()));
#endif #endif
if(Ret[0] == 'E')UUl(Ret.substr(1)); if (Ret[0] == 'E' || Ret[0] == 'K')
UUl(Ret.substr(1));
return Ret; return Ret;
} }
void TCPClientMain(const std::string& IP,int Port){ void TCPClientMain(const std::string& IP, int Port) {
LastIP = IP; LastIP = IP;
LastPort = Port; LastPort = Port;
WSADATA wsaData;
SOCKADDR_IN ServerAddr; SOCKADDR_IN ServerAddr;
int RetCode; int RetCode;
WSAStartup(514, &wsaData); //2.2 #ifdef _WIN32
WSADATA wsaData;
WSAStartup(514, &wsaData); // 2.2
#endif
TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(TCPSock == -1){ if (TCPSock == -1) {
printf("Client: socket failed! Error code: %d\n", WSAGetLastError()); printf("Client: socket failed! Error code: %d\n", WSAGetLastError());
WSACleanup(); WSACleanup();
return; return;
} }
ServerAddr.sin_family = AF_INET; ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port); ServerAddr.sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr); inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
RetCode = connect(TCPSock, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)); RetCode = connect(TCPSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
if(RetCode != 0){ if (RetCode != 0) {
UlStatus = "UlConnection Failed!"; UlStatus = "UlConnection Failed!";
error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError())); error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock); KillSocket(TCPSock);
WSACleanup(); WSACleanup();
Terminate = true; Terminate = true;
CoreSend("L");
return; return;
} }
info("Connected!"); info("Connected!");
@@ -144,14 +160,16 @@ void TCPClientMain(const std::string& IP,int Port){
char Code = 'C'; char Code = 'C';
send(TCPSock, &Code, 1, 0); send(TCPSock, &Code, 1, 0);
SyncResources(TCPSock); SyncResources(TCPSock);
while(!Terminate){ while (!Terminate) {
ServerParser(TCPRcv(TCPSock)); ServerParser(TCPRcv(TCPSock));
} }
GameSend("T"); GameSend("T");
////Game Send Terminate ////Game Send Terminate
if(KillSocket(TCPSock) != 0) if (KillSocket(TCPSock) != 0)
debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError())); debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError()));
if(WSACleanup() != 0) #ifdef _WIN32
if (WSACleanup() != 0)
debug("(TCP) Client: WSACleanup() failed!..."); debug("(TCP) Client: WSACleanup() failed!...");
#endif
} }

361
src/Security/BeamNG.cpp Executable file → Normal file
View File

@@ -7,10 +7,16 @@
/// ///
#include <filesystem> #include <filesystem>
#if defined(_WIN32)
#include <windows.h> #include <windows.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <pwd.h>
#include <unistd.h>
#include <vector>
#endif
#include "Logger.h" #include "Logger.h"
#include <fstream> #include <fstream>
#include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -20,298 +26,189 @@
int TraceBack = 0; int TraceBack = 0;
std::string GameDir; std::string GameDir;
void lowExit(int code){ void lowExit(int code) {
TraceBack = 0; TraceBack = 0;
std::string msg = std::string msg = "Failed to find the game please launch it. Report this if the issue persists code ";
"Failed to find the game please launch it. Report this if the issue persists code "; error(msg + std::to_string(code));
error(msg+std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::seconds(10));
exit(2); exit(2);
} }
void Exit(int code){
TraceBack = 0; std::string GetGameDir() {
std::string msg = #if defined(_WIN32)
"Sorry. We do not support cracked copies report this if you believe this is a mistake code "; return GameDir.substr(0, GameDir.find_last_of('\\'));
error(msg+std::to_string(code)); #elif defined(__linux__)
std::this_thread::sleep_for(std::chrono::seconds(10)); return GameDir.substr(0, GameDir.find_last_of('/'));
exit(3); #endif
} }
void SteamExit(int code){ #ifdef _WIN32
TraceBack = 0; LONG OpenKey(HKEY root, const char* path, PHKEY hKey) {
std::string msg =
"Illegal steam modifications detected report this if you believe this is a mistake code ";
error(msg+std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(4);
}
std::string GetGameDir(){
if(TraceBack != 4)Exit(0);
return GameDir.substr(0,GameDir.find_last_of('\\'));
}
LONG OpenKey(HKEY root,const char* path,PHKEY hKey){
return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey); return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey);
} }
std::string QueryKey(HKEY hKey,int ID){ std::string QueryKey(HKEY hKey, int ID) {
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
DWORD cbName; // size of name string DWORD cbName; // size of name string
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys=0; // number of subkeys DWORD cSubKeys = 0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string DWORD cchMaxClass; // longest class string
DWORD cValues; // number of values for key DWORD cValues; // number of values for key
DWORD cchMaxValue; // longest value name DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time FILETIME ftLastWriteTime; // last write time
DWORD i, retCode; DWORD i, retCode;
TCHAR achValue[MAX_VALUE_NAME]; TCHAR achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME; DWORD cchValue = MAX_VALUE_NAME;
retCode = RegQueryInfoKey( retCode = RegQueryInfoKey(
hKey, // key handle hKey, // key handle
achClass, // buffer for class name achClass, // buffer for class name
&cchClassName, // size of class string &cchClassName, // size of class string
nullptr, // reserved nullptr, // reserved
&cSubKeys, // number of subkeys &cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size &cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string &cchMaxClass, // longest class string
&cValues, // number of values for this key &cValues, // number of values for this key
&cchMaxValue, // longest value name &cchMaxValue, // longest value name
&cbMaxValueData, // longest value data &cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor &cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time &ftLastWriteTime); // last write time
BYTE* buffer = new BYTE[cbMaxValueData]; BYTE* buffer = new BYTE[cbMaxValueData];
ZeroMemory(buffer, cbMaxValueData); ZeroMemory(buffer, cbMaxValueData);
if (cSubKeys){ if (cSubKeys) {
for (i=0; i<cSubKeys; i++){ for (i = 0; i < cSubKeys; i++) {
cbName = MAX_KEY_LENGTH; cbName = MAX_KEY_LENGTH;
retCode = RegEnumKeyEx(hKey, i,achKey,&cbName,nullptr,nullptr,nullptr,&ftLastWriteTime); retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime);
if (retCode == ERROR_SUCCESS){ if (retCode == ERROR_SUCCESS) {
if(strcmp(achKey,"Steam App 284160") == 0){ if (strcmp(achKey, "Steam App 284160") == 0) {
return achKey; return achKey;
} }
} }
} }
} }
if (cValues){ if (cValues) {
for (i=0, retCode = ERROR_SUCCESS; i<cValues; i++){ for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++) {
cchValue = MAX_VALUE_NAME; cchValue = MAX_VALUE_NAME;
achValue[0] = '\0'; achValue[0] = '\0';
retCode = RegEnumValue(hKey, i,achValue,&cchValue,nullptr,nullptr,nullptr,nullptr); retCode = RegEnumValue(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr);
if (retCode == ERROR_SUCCESS ){ if (retCode == ERROR_SUCCESS) {
DWORD lpData = cbMaxValueData; DWORD lpData = cbMaxValueData;
buffer[0] = '\0'; buffer[0] = '\0';
LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData); LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData);
std::string data = (char *)(buffer); std::string data = (char*)(buffer);
std::string key = achValue; std::string key = achValue;
switch (ID){ switch (ID) {
case 1: if(key == "SteamExe"){ case 1:
auto p = data.find_last_of("/\\"); if (key == "SteamExe") {
if(p != std::string::npos){ auto p = data.find_last_of("/\\");
return data.substr(0,p); if (p != std::string::npos) {
} return data.substr(0, p);
} }
break; }
case 2: if(key == "Name" && data == "BeamNG.drive")return data;break; break;
case 3: if(key == "rootpath")return data;break; case 2:
case 4: if(key == "userpath_override")return data; if (key == "Name" && data == "BeamNG.drive")
case 5: if(key == "Local AppData")return data; return data;
default: break; break;
case 3:
if (key == "rootpath")
return data;
break;
case 4:
if (key == "userpath_override")
return data;
case 5:
if (key == "Local AppData")
return data;
default:
break;
} }
} }
} }
} }
delete [] buffer; delete[] buffer;
return ""; return "";
} }
#endif
namespace fs = std::filesystem; namespace fs = std::filesystem;
bool NameValid(const std::string& N){ bool NameValid(const std::string& N) {
if(N == "config" || N == "librarycache"){ if (N == "config" || N == "librarycache") {
return true; return true;
} }
if(N.find_first_not_of("0123456789") == std::string::npos){ if (N.find_first_not_of("0123456789") == std::string::npos) {
return true; return true;
} }
return false; return false;
} }
void FileList(std::vector<std::string>&a,const std::string& Path){ void FileList(std::vector<std::string>& a, const std::string& Path) {
for (const auto &entry : fs::directory_iterator(Path)) { for (const auto& entry : fs::directory_iterator(Path)) {
const auto& DPath = entry.path(); const auto& DPath = entry.path();
if (!entry.is_directory()) { if (!entry.is_directory()) {
a.emplace_back(DPath.u8string()); a.emplace_back(DPath.string());
}else if(NameValid(DPath.filename().u8string())){ } else if (NameValid(DPath.filename().string())) {
FileList(a, DPath.u8string()); FileList(a, DPath.string());
} }
} }
} }
bool Find(const std::string& FName,const std::string& Path){ void LegitimacyCheck() {
std::vector<std::string> FS; #if defined(_WIN32)
FileList(FS,Path+"\\userdata"); std::string Result;
for(std::string&a : FS){
if(a.find(FName) != std::string::npos){
FS.clear();
return true;
}
}
FS.clear();
return false;
}
bool FindHack(const std::string& Path){
bool s = true;
for (const auto &entry : fs::directory_iterator(Path)) {
std::string Name = entry.path().filename().u8string();
for(char&c : Name)c = char(tolower(c));
if(Name == "steam.exe")s = false;
if(Name.find("greenluma") != -1){
error("Found malicious file/folder \"" + Name+"\"");
return true;
}
Name.clear();
}
return s;
}
std::vector<std::string> GetID(const std::string& log){
std::string vec,t,r;
std::vector<std::string> Ret;
std::ifstream f(log.c_str(), std::ios::binary);
f.seekg(0, std::ios_base::end);
std::streampos fileSize = f.tellg();
vec.resize(size_t(fileSize) + 1);
f.seekg(0, std::ios_base::beg);
f.read(&vec[0], fileSize);
f.close();
std::stringstream ss(vec);
bool S = false;
while (std::getline(ss, t, '{')) {
if(!S)S = true;
else{
for(char& c : t){
if(isdigit(c))r += c;
}
break;
}
}
Ret.emplace_back(r);
r.clear();
S = false;
bool L = true;
while (std::getline(ss, t, '}')) {
if(L){
L = false;
continue;
}
for(char& c : t){
if(c == '"'){
if(!S)S = true;
else{
if(r.length() > 10) {
Ret.emplace_back(r);
}
r.clear();
S = false;
continue;
}
}
if(isdigit(c))r += c;
}
}
vec.clear();
return Ret;
}
std::string GetManifest(const std::string& Man){
std::string vec;
std::ifstream f(Man.c_str(), std::ios::binary);
f.seekg(0, std::ios_base::end);
std::streampos fileSize = f.tellg();
vec.resize(size_t(fileSize) + 1);
f.seekg(0, std::ios_base::beg);
f.read(&vec[0], fileSize);
f.close();
std::string ToFind = "\"LastOwner\"\t\t\"";
int pos = int(vec.find(ToFind));
if(pos != -1){
pos += int(ToFind.length());
vec = vec.substr(pos);
return vec.substr(0,vec.find('\"'));
}else return "";
}
bool IDCheck(std::string Man, std::string steam){
bool a = false,b = true;
int pos = int(Man.rfind("steamapps"));
if(pos == -1)Exit(5);
Man = Man.substr(0,pos+9) + "\\appmanifest_284160.acf";
steam += "\\config\\loginusers.vdf";
if(fs::exists(Man) && fs::exists(steam)){
for(const std::string&ID : GetID(steam)){
if(ID == GetManifest(Man))b = false;
}
if(b)Exit(6);
}else a = true;
return a;
}
void LegitimacyCheck(){
std::string Result,T;
std::string K1 = R"(Software\Valve\Steam)";
std::string K2 = R"(Software\Valve\Steam\Apps\284160)";
std::string K3 = R"(Software\BeamNG\BeamNG.drive)"; std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
HKEY hKey; HKEY hKey;
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K1.c_str(), &hKey); if (dwRegOPenKey == ERROR_SUCCESS) {
if(dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 1);
if(Result.empty())Exit(1);
if(fs::exists(Result)){
if(!Find("284160.json",Result))Exit(2);
if(FindHack(Result))SteamExit(1);
}else Exit(3);
T = Result;
Result.clear();
TraceBack++;
}else Exit(4);
K1.clear();
RegCloseKey(hKey);
dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K2.c_str(), &hKey);
if(dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 2);
if(Result.empty())lowExit(1);
TraceBack++;
}else lowExit(2);
K2.clear();
RegCloseKey(hKey);
dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
if(dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 3); Result = QueryKey(hKey, 3);
if(Result.empty())lowExit(3); if (Result.empty()) {
if(IDCheck(Result,T))lowExit(5); debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(3);
}
GameDir = Result; GameDir = Result;
TraceBack++; } else {
}else lowExit(4); debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(4);
}
K3.clear(); K3.clear();
Result.clear(); Result.clear();
RegCloseKey(hKey); RegCloseKey(hKey);
if(TraceBack < 3)exit(-1); #elif defined(__linux__)
struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir;
// Right now only steam is supported
std::ifstream libraryFolders(homeDir + "/.steam/root/steamapps/libraryfolders.vdf");
auto root = tyti::vdf::read(libraryFolders);
for (auto folderInfo : root.childs) {
if (std::filesystem::exists(folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/")) {
GameDir = folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/";
break;
}
}
#endif
} }
std::string CheckVer(const std::string &dir){ std::string CheckVer(const std::string& dir) {
std::string temp,Path = dir + "\\integrity.json"; #if defined(_WIN32)
std::string temp, Path = dir + "\\integrity.json";
#elif defined(__linux__)
std::string temp, Path = dir + "/integrity.json";
#endif
std::ifstream f(Path.c_str(), std::ios::binary); std::ifstream f(Path.c_str(), std::ios::binary);
int Size = int(std::filesystem::file_size(Path)); int Size = int(std::filesystem::file_size(Path));
std::string vec(Size,0); std::string vec(Size, 0);
f.read(&vec[0], Size); f.read(&vec[0], Size);
f.close(); f.close();
vec = vec.substr(vec.find_last_of("version"),vec.find_last_of('"')); vec = vec.substr(vec.find_last_of("version"), vec.find_last_of('"'));
for(const char &a : vec){ for (const char& a : vec) {
if(isdigit(a) || a == '.')temp+=a; if (isdigit(a) || a == '.')
temp += a;
} }
return temp; return temp;
} }

144
src/Security/Login.cpp Executable file → Normal file
View File

@@ -6,25 +6,30 @@
/// Created by Anonymous275 on 11/26/2020 /// Created by Anonymous275 on 11/26/2020
/// ///
#include "http.h" #include "Http.h"
#include <filesystem>
#include "Logger.h" #include "Logger.h"
#include <filesystem>
#include <fstream> #include <fstream>
#include "Json.h" #include <nlohmann/json.hpp>
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string PublicKey; std::string PublicKey;
std::string PrivateKey;
extern bool LoginAuth; extern bool LoginAuth;
std::string Role; extern std::string Username;
extern std::string UserRole;
extern int UserID;
void UpdateKey(const char* newKey){ void UpdateKey(const char* newKey) {
if(newKey){ if (newKey && std::isalnum(newKey[0])) {
PrivateKey = newKey;
std::ofstream Key("key"); std::ofstream Key("key");
if(Key.is_open()){ if (Key.is_open()) {
Key << newKey; Key << newKey;
Key.close(); Key.close();
}else fatal("Cannot write to disk!"); } else
}else if(fs::exists("key")){ fatal("Cannot write to disk!");
} else if (fs::exists("key")) {
remove("key"); remove("key");
} }
} }
@@ -33,82 +38,113 @@ void UpdateKey(const char* newKey){
/// "Guest":"Name" /// "Guest":"Name"
/// "pk":"private_key" /// "pk":"private_key"
std::string GetFail(const std::string& R){ std::string GetFail(const std::string& R) {
std::string DRet = R"({"success":false,"message":)"; std::string DRet = R"({"success":false,"message":)";
DRet += "\""+R+"\"}"; DRet += "\"" + R + "\"}";
error(R); error(R);
return DRet; return DRet;
} }
std::string Login(const std::string& fields){ std::string Login(const std::string& fields) {
if(fields == "LO"){ if (fields == "LO") {
Username = "";
UserRole = "";
UserID = -1;
LoginAuth = false; LoginAuth = false;
UpdateKey(nullptr); UpdateKey(nullptr);
return ""; return "";
} }
info("Attempting to authenticate..."); info("Attempting to authenticate...");
std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields); try {
json::Document d; std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields);
d.Parse(Buffer.c_str());
if(Buffer == "-1"){
return GetFail("Failed to communicate with the auth system!");
}
if (Buffer.at(0) != '{' || d.HasParseError()) { if (Buffer.empty()) {
error(Buffer); return GetFail("Failed to communicate with the auth system!");
return GetFail("Invalid answer from authentication servers, please try again later!");
}
if(!d["success"].IsNull() && d["success"].GetBool()){
LoginAuth = true;
if(!d["private_key"].IsNull()){
UpdateKey(d["private_key"].GetString());
} }
if(!d["public_key"].IsNull()){
PublicKey = d["public_key"].GetString(); nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
if (Buffer.at(0) != '{' || d.is_discarded()) {
error(Buffer);
return GetFail("Invalid answer from authentication servers, please try again later!");
} }
info("Authentication successful!"); if (d.contains("success") && d["success"].get<bool>()) {
}else info("Authentication failed!"); LoginAuth = true;
if(!d["message"].IsNull()){ if (d.contains("username")) {
d.RemoveMember("private_key"); Username = d["username"].get<std::string>();
d.RemoveMember("public_key"); }
rapidjson::StringBuffer buffer; if (d.contains("role")) {
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); UserRole = d["role"].get<std::string>();
d.Accept(writer); }
return buffer.GetString(); if (d.contains("id")) {
UserID = d["id"].get<int>();
}
if (d.contains("private_key")) {
UpdateKey(d["private_key"].get<std::string>().c_str());
}
if (d.contains("public_key")) {
PublicKey = d["public_key"].get<std::string>();
}
info("Authentication successful!");
} else
info("Authentication failed!");
if (d.contains("message")) {
d.erase("private_key");
d.erase("public_key");
return d.dump();
}
return GetFail("Invalid message parsing!");
} catch (const std::exception& e) {
return GetFail(e.what());
} }
return GetFail("Invalid message parsing!");
} }
void CheckLocalKey(){ void CheckLocalKey() {
if(fs::exists("key") && fs::file_size("key") < 100){ if (fs::exists("key") && fs::file_size("key") < 100) {
std::ifstream Key("key"); std::ifstream Key("key");
if(Key.is_open()) { if (Key.is_open()) {
auto Size = fs::file_size("key"); auto Size = fs::file_size("key");
std::string Buffer(Size, 0); std::string Buffer(Size, 0);
Key.read(&Buffer[0], Size); Key.read(&Buffer[0], Size);
Key.close(); Key.close();
for (char& c : Buffer) {
if (!std::isalnum(c) && c != '-') {
UpdateKey(nullptr);
return;
}
}
Buffer = HTTP::Post("https://auth.beammp.com/userlogin", R"({"pk":")" + Buffer + "\"}"); Buffer = HTTP::Post("https://auth.beammp.com/userlogin", R"({"pk":")" + Buffer + "\"}");
json::Document d; nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
d.Parse(Buffer.c_str());
if (Buffer == "-1" || Buffer.at(0) != '{' || d.HasParseError()) { if (Buffer.empty() || Buffer.at(0) != '{' || d.is_discarded()) {
error(Buffer); error(Buffer);
fatal("Invalid answer from authentication servers, please try again later!"); info("Invalid answer from authentication servers.");
UpdateKey(nullptr);
} }
if(d["success"].GetBool()){ if (d["success"].get<bool>()) {
LoginAuth = true; LoginAuth = true;
UpdateKey(d["private_key"].GetString()); UpdateKey(d["private_key"].get<std::string>().c_str());
PublicKey = d["public_key"].GetString(); PublicKey = d["public_key"].get<std::string>();
Role = d["role"].GetString(); if (d.contains("username")) {
//info(Role); Username = d["username"].get<std::string>();
}else{ }
if (d.contains("role")) {
UserRole = d["role"].get<std::string>();
}
if (d.contains("id")) {
UserID = d["id"].get<int>();
}
} else {
info("Auto-Authentication unsuccessful please re-login!"); info("Auto-Authentication unsuccessful please re-login!");
UpdateKey(nullptr); UpdateKey(nullptr);
} }
}else{ } else {
warn("Could not open saved key!"); warn("Could not open saved key!");
UpdateKey(nullptr); UpdateKey(nullptr);
} }
}else UpdateKey(nullptr); } else
UpdateKey(nullptr);
} }

372
src/Startup.cpp Executable file → Normal file
View File

@@ -5,177 +5,236 @@
/// ///
/// Created by Anonymous275 on 7/16/2020 /// Created by Anonymous275 on 7/16/2020
/// ///
#include "zip_file.h"
#include <charconv>
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <string>
#if defined(_WIN32)
#include <windows.h> #include <windows.h>
#include "Discord/discord_info.h" #elif defined(__linux__)
#include "Network/network.h" #include <unistd.h>
#include "Security/Init.h" #endif
#include <filesystem> #include "Http.h"
#include "Startup.h"
#include "Logger.h" #include "Logger.h"
#include "Network/network.hpp"
#include "Security/Init.h"
#include "Startup.h"
#include "hashpp.h"
#include <filesystem>
#include <fstream> #include <fstream>
#include <thread> #include <thread>
#include "http.h"
#include "Json.h"
extern int TraceBack; extern int TraceBack;
bool Dev = false; bool Dev = false;
int ProxyPort = 0;
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string GetEN(){ struct Version {
uint8_t major;
uint8_t minor;
uint8_t patch;
Version(uint8_t major, uint8_t minor, uint8_t patch);
Version(const std::array<uint8_t, 3>& v);
};
std::array<uint8_t, 3> VersionStrToInts(const std::string& str) {
std::array<uint8_t, 3> Version;
std::stringstream ss(str);
for (uint8_t& i : Version) {
std::string Part;
std::getline(ss, Part, '.');
std::from_chars(&*Part.begin(), &*Part.begin() + Part.size(), i);
}
return Version;
}
bool IsOutdated(const Version& Current, const Version& Newest) {
if (Newest.major > Current.major) {
return true;
} else if (Newest.major == Current.major && Newest.minor > Current.minor) {
return true;
} else if (Newest.major == Current.major && Newest.minor == Current.minor && Newest.patch > Current.patch) {
return true;
} else {
return false;
}
}
Version::Version(uint8_t major, uint8_t minor, uint8_t patch)
: major(major)
, minor(minor)
, patch(patch) { }
Version::Version(const std::array<uint8_t, 3>& v)
: Version(v[0], v[1], v[2]) {
}
std::string GetEN() {
#if defined(_WIN32)
return "BeamMP-Launcher.exe"; return "BeamMP-Launcher.exe";
#elif defined(__linux__)
return "BeamMP-Launcher";
#endif
} }
std::string GetVer(){
return "2.0"; std::string GetVer() {
return "2.2";
} }
std::string GetPatch(){ std::string GetPatch() {
return ".4"; return ".0";
} }
std::string GetEP(char*P){
static std::string Ret = [&](){ std::string GetEP(char* P) {
static std::string Ret = [&]() {
std::string path(P); std::string path(P);
return path.substr(0, path.find_last_of("\\/") + 1); return path.substr(0, path.find_last_of("\\/") + 1);
} (); }();
return Ret; return Ret;
} }
void ReLaunch(int argc,char*args[]){ #if defined(_WIN32)
void ReLaunch(int argc, char* args[]) {
std::string Arg; std::string Arg;
for(int c = 2; c <= argc; c++){ for (int c = 2; c <= argc; c++) {
Arg += " "; Arg += " ";
Arg += args[c-1]; Arg += args[c - 1];
} }
info("Relaunch!");
system("cls"); system("cls");
ShellExecute(nullptr,"runas",(GetEP() + GetEN()).c_str(),Arg.c_str(),nullptr,SW_SHOWNORMAL); ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(),0); ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
void URelaunch(int argc,char* args[]){ void URelaunch(int argc, char* args[]) {
std::string Arg; std::string Arg;
for(int c = 2; c <= argc; c++){ for (int c = 2; c <= argc; c++) {
Arg += " "; Arg += " ";
Arg += args[c-1]; Arg += args[c - 1];
} }
ShellExecute(nullptr,"open",(GetEP() + GetEN()).c_str(),Arg.c_str(),nullptr,SW_SHOWNORMAL); ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(),0); ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
void CheckName(int argc,char* args[]){ #elif defined(__linux__)
std::string DN = GetEN(),CDir = args[0],FN = CDir.substr(CDir.find_last_of('\\')+1); void ReLaunch(int argc, char* args[]) {
if(FN != DN){ std::string Arg;
if(fs::exists(DN))remove(DN.c_str()); for (int c = 2; c <= argc; c++) {
if(fs::exists(DN))ReLaunch(argc,args); Arg += " ";
Arg += args[c - 1];
}
info("Relaunch!");
system("clear");
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
void URelaunch(int argc, char* args[]) {
std::string Arg;
for (int c = 2; c <= argc; c++) {
Arg += " ";
Arg += args[c - 1];
}
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
#endif
void CheckName(int argc, char* args[]) {
#if defined(_WIN32)
std::string DN = GetEN(), CDir = args[0], FN = CDir.substr(CDir.find_last_of('\\') + 1);
#elif defined(__linux__)
std::string DN = GetEN(), CDir = args[0], FN = CDir.substr(CDir.find_last_of('/') + 1);
#endif
if (FN != DN) {
if (fs::exists(DN))
remove(DN.c_str());
if (fs::exists(DN))
ReLaunch(argc, args);
std::rename(FN.c_str(), DN.c_str()); std::rename(FN.c_str(), DN.c_str());
URelaunch(argc,args); URelaunch(argc, args);
} }
} }
void CheckForUpdates(int argc,char*args[],const std::string& CV){ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
std::string link; std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey);
std::string HTTP = HTTP::Get("https://beammp.com/builds/launcher?version=true"); std::string LatestVersion = HTTP::Get(
bool fallback = false; "https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey);
if(HTTP.find_first_of("0123456789") == std::string::npos){
HTTP = HTTP::Get("https://backup1.beammp.com/builds/launcher?version=true");
fallback = true;
if(HTTP.find_first_of("0123456789") == std::string::npos) {
fatal("Primary Servers Offline! sorry for the inconvenience!");
}
}
if(fallback){
link = "https://backup1.beammp.com/builds/launcher?download=true";
}else link = "https://beammp.com/builds/launcher?download=true";
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back"); std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
if(fs::exists(Back))remove(Back.c_str()); std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
if(HTTP > CV){ if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion))) && !Dev) {
system("cls"); info("Launcher update found!");
info("Update found!"); #if defined(__linux__)
info("Updating..."); error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
if(std::rename(EP.c_str(), Back.c_str()))error("failed creating a backup!"); #else
fs::remove(Back);
if(!HTTP::Download(link, EP)){ fs::rename(EP, Back);
error("Launcher Update failed! trying again..."); info("Downloading Launcher update " + LatestHash);
std::this_thread::sleep_for(std::chrono::seconds(2)); HTTP::Download(
"https://backend.beammp.com/builds/launcher?download=true"
if(!HTTP::Download(link, EP)){ "&pk="
error("Launcher Update failed!"); + PublicKey + "&branch=" + Branch,
std::this_thread::sleep_for(std::chrono::seconds(5)); EP);
ReLaunch(argc,args); URelaunch(argc, args);
} #endif
} } else
URelaunch(argc,args); info("Launcher version is up to date");
}else info("Launcher version is up to date");
TraceBack++; TraceBack++;
} }
void CustomPort(int argc, char* argv[]){ void CustomPort(int argc, char* argv[]) {
if(argc > 1){ if (argc > 1) {
std::string Port = argv[1]; std::string Port = argv[1];
if(Port.find_first_not_of("0123456789") == std::string::npos){ if (Port.find_first_not_of("0123456789") == std::string::npos) {
if(std::stoi(Port) > 1000){ if (std::stoi(Port) > 1000) {
DEFAULT_PORT = std::stoi(Port); DEFAULT_PORT = std::stoi(Port);
warn("Running on custom port : " + std::to_string(DEFAULT_PORT)); warn("Running on custom port : " + std::to_string(DEFAULT_PORT));
} }
} }
if(argc > 2)Dev = true; if (argc > 2)
Dev = true;
} }
} }
void LinuxPatch(){ #ifdef _WIN32
void LinuxPatch() {
HKEY hKey = nullptr; HKEY hKey = nullptr;
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey); LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS || getenv("USER") == nullptr)return; if (result != ERROR_SUCCESS || getenv("USER") == nullptr)
return;
RegCloseKey(hKey); RegCloseKey(hKey);
info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit"); info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit");
info("Applying patches..."); info("Applying patches...");
result = RegCreateKey(HKEY_CURRENT_USER, R"(Software\Valve\Steam\Apps\284160)", &hKey); result = RegCreateKey(HKEY_CURRENT_USER, R"(Software\Valve\Steam\Apps\284160)", &hKey);
if (result != ERROR_SUCCESS){ if (result != ERROR_SUCCESS) {
fatal(R"(failed to create HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)"); fatal(R"(failed to create HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return; return;
} }
result = RegSetValueEx(hKey, "Name", 0, REG_SZ, (BYTE*)"BeamNG.drive", 12); result = RegSetValueEx(hKey, "Name", 0, REG_SZ, (BYTE*)"BeamNG.drive", 12);
if (result != ERROR_SUCCESS){ if (result != ERROR_SUCCESS) {
fatal(R"(failed to create the value "Name" under HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)"); fatal(R"(failed to create the value "Name" under HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return; return;
} }
RegCloseKey(hKey); RegCloseKey(hKey);
std::string Path = R"(Z:\home\)" + std::string(getenv("USER")) + R"(\.steam\steam\Steam.exe)";
if(!fs::exists(Path)) {
std::ofstream ofs(Path);
if (!ofs.is_open()) {
fatal("Failed to create file \"" + Path + "\"");
return;
} else ofs.close();
}
result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Valve\Steam)", 0, KEY_ALL_ACCESS, &hKey);
if (result != ERROR_SUCCESS){
fatal(R"(failed to open HKEY_CURRENT_USER\Software\Valve\Steam)");
return;
}
result = RegSetValueEx(hKey, "SteamExe", 0, REG_SZ, (BYTE*)Path.c_str(), Path.size());
if (result != ERROR_SUCCESS){
fatal(R"(failed to create the value "Name" under HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return;
}
RegCloseKey(hKey);
info("Patched!"); info("Patched!");
} }
#endif
#if defined(_WIN32)
void InitLauncher(int argc, char* argv[]) { void InitLauncher(int argc, char* argv[]) {
system("cls");
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str()); SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog(); InitLog();
CheckName(argc, argv); CheckName(argc, argv);
@@ -183,93 +242,116 @@ void InitLauncher(int argc, char* argv[]) {
CheckLocalKey(); CheckLocalKey();
ConfigInit(); ConfigInit();
CustomPort(argc, argv); CustomPort(argc, argv);
Discord_Main();
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch()); CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
} }
#elif defined(__linux__)
void InitLauncher(int argc, char* argv[]) {
InitLog();
info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName(argc, argv);
CheckLocalKey();
ConfigInit();
CustomPort(argc, argv);
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
#endif
size_t DirCount(const std::filesystem::path& path){ size_t DirCount(const std::filesystem::path& path) {
return (size_t)std::distance(std::filesystem::directory_iterator{path}, std::filesystem::directory_iterator{}); return (size_t)std::distance(std::filesystem::directory_iterator { path }, std::filesystem::directory_iterator {});
} }
void CheckMP(const std::string& Path) { void CheckMP(const std::string& Path) {
if (!fs::exists(Path))return; if (!fs::exists(Path))
return;
size_t c = DirCount(fs::path(Path)); size_t c = DirCount(fs::path(Path));
if (c > 3) {
warn(std::to_string(c - 1) + " multiplayer mods will be wiped from mods/multiplayer! Close this if you don't want that!");
std::this_thread::sleep_for(std::chrono::seconds(15));
}
try { try {
for (auto& p : fs::directory_iterator(Path)){ for (auto& p : fs::directory_iterator(Path)) {
if(p.exists() && !p.is_directory()){ if (p.exists() && !p.is_directory()) {
std::string Name = p.path().filename().u8string(); std::string Name = p.path().filename().string();
for(char&Ch : Name)Ch = char(tolower(Ch)); for (char& Ch : Name)
if(Name != "beammp.zip")fs::remove(p.path()); Ch = char(tolower(Ch));
if (Name != "beammp.zip")
fs::remove(p.path());
} }
} }
} catch (...) { } catch (...) {
fatal("Please close the game, and try again!"); fatal("We were unable to clean the multiplayer mods folder! Is the game still running or do you have something open in that folder?");
} }
} }
void EnableMP(){ void EnableMP() {
std::string File(GetGamePath() + "mods/db.json"); std::string File(GetGamePath() + "mods/db.json");
if(!fs::exists(File))return; if (!fs::exists(File))
return;
auto Size = fs::file_size(File);
if (Size < 2)
return;
std::ifstream db(File); std::ifstream db(File);
if(db.is_open()) { if (db.is_open()) {
auto Size = fs::file_size(File);
std::string Data(Size, 0); std::string Data(Size, 0);
db.read(&Data[0], Size); db.read(&Data[0], Size);
db.close(); db.close();
json::Document d; nlohmann::json d = nlohmann::json::parse(Data, nullptr, false);
d.Parse(Data.c_str()); if (Data.at(0) != '{' || d.is_discarded()) {
if(Data.at(0) != '{' || d.HasParseError()){ // error("Failed to parse " + File); //TODO illegal formatting
//error("Failed to parse " + File); //TODO illegal formatting
return; return;
} }
if(!d["mods"].IsNull() && !d["mods"]["multiplayerbeammp"].IsNull()){ if (d.contains("mods") && d["mods"].contains("multiplayerbeammp")) {
d["mods"]["multiplayerbeammp"]["active"] = true; d["mods"]["multiplayerbeammp"]["active"] = true;
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
d.Accept(writer);
std::ofstream ofs(File); std::ofstream ofs(File);
if(ofs.is_open()){ if (ofs.is_open()) {
ofs << buffer.GetString(); ofs << d.dump();
ofs.close(); ofs.close();
}else{ } else {
error("Failed to write " + File); error("Failed to write " + File);
} }
} }
} }
} }
void PreGame(const std::string& GamePath){ void PreGame(const std::string& GamePath) {
const std::string CurrVer("0.22.2.0");
std::string GameVer = CheckVer(GamePath); std::string GameVer = CheckVer(GamePath);
info("Game Version : " + GameVer); info("Game Version : " + GameVer);
if(GameVer < CurrVer){
fatal("Game version is old! Please update.");
}else if(GameVer > CurrVer){
warn("Game is newer than recommended, multiplayer may not work as intended!");
}
CheckMP(GetGamePath() + "mods/multiplayer");
if(!Dev) { CheckMP(GetGamePath() + "mods/multiplayer");
info("Downloading mod please wait..."); info("Game user path: " + GetGamePath());
if (!Dev) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
[](auto const& c) -> bool { return !std::isalnum(c); }),
LatestHash.end());
try { try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
} }
EnableMP(); EnableMP();
}catch(std::exception&e){ } catch (std::exception& e) {
fatal(e.what()); fatal(e.what());
} }
#if defined(_WIN32)
std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)");
#elif defined(__linux__)
// Linux version of the game cant handle mods with uppercase names
std::string ZipPath(GetGamePath() + R"(mods/multiplayer/beammp.zip)");
#endif
HTTP::Download("https://backend.beammp.com/builds/client?download=true" std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath);
"&pk=" + PublicKey +
"&branch=" + Branch, GetGamePath() + R"(mods\multiplayer\BeamMP.zip)");
//HTTP::Download("beammp.com/builds/client", GetGamePath() + R"(mods\multiplayer\BeamMP.zip)"); if (FileHash != LatestHash) {
info("Downloading BeamMP Update " + LatestHash);
HTTP::Download("https://backend.beammp.com/builds/client?download=true"
"&pk="
+ PublicKey + "&branch=" + Branch,
ZipPath);
}
std::string Target(GetGamePath() + "mods/unpacked/beammp");
if (fs::is_directory(Target)) {
fs::remove_all(Target);
}
} }
} }

59
src/main.cpp Executable file → Normal file
View File

@@ -5,39 +5,68 @@
/// ///
/// Created by Anonymous275 on 7/16/2020 /// Created by Anonymous275 on 7/16/2020
/// ///
#include "Network/network.h" #include "Http.h"
#include "Logger.h"
#include "Network/network.hpp"
#include "Security/Init.h" #include "Security/Init.h"
#include "Startup.h" #include "Startup.h"
#include <curl/curl.h>
#include <iostream> #include <iostream>
#include "Logger.h"
#include <thread> #include <thread>
#include "http.h"
[[noreturn]] void flush(){ [[noreturn]] void flush() {
while(true){ while (true) {
std::cout.flush(); std::cout.flush();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
} }
int main(int argc, char* argv[]) { int main(int argc, char** argv) try {
#ifdef DEBUG #ifdef DEBUG
std::thread th(flush); std::thread th(flush);
th.detach(); th.detach();
#endif #endif
curl_global_init(CURL_GLOBAL_ALL);
#if defined(_WIN32)
system("cls");
#elif defined(__linux__)
system("clear");
#endif
GetEP(argv[0]); GetEP(argv[0]);
InitLauncher(argc,argv); for (int i = 0; i < argc; ++i) {
if (std::string_view(argv[i]) == "--skip-ssl-verify") {
info("SSL verification skip enabled");
HTTP::SkipSslVerify = true;
}
}
InitLauncher(argc, argv);
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
try { try {
LegitimacyCheck(); LegitimacyCheck();
}catch (std::exception& e){ } catch (std::exception& e) {
fatal("Main 1 : " + std::string(e.what())); error("Failure in LegitimacyCheck: " + std::string(e.what()));
throw;
} }
try {
HTTP::StartProxy();
} catch (const std::exception& e) {
error(std::string("Failed to start HTTP proxy: Some in-game functions may not work. Error: ") + e.what());
}
PreGame(GetGameDir()); PreGame(GetGameDir());
InitGame(GetGameDir()); InitGame(GetGameDir());
CoreNetwork(); CoreNetwork();
} catch (const std::exception& e) {
///TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...) error(std::string("Exception in main(): ") + e.what());
info("Closing in 5 seconds");
info("If this keeps happening, contact us on either: Forum: https://forum.beammp.com, Discord: https://discord.gg/beammp");
std::this_thread::sleep_for(std::chrono::seconds(5));
} }

9
vcpkg.json Normal file
View File

@@ -0,0 +1,9 @@
{
"dependencies": [
"cpp-httplib",
"nlohmann-json",
"zlib",
"openssl",
"curl"
]
}