223 Commits

Author SHA1 Message Date
Tixx
ce3abf7e6e Use a JSON body for requests instead of query params 2025-01-02 23:44:44 +01:00
Tixx
a8a4dfb77c Fixed GameDir location issues. (#152)
I couldn't launch the game without this.

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2024-12-23 23:00:47 +01:00
Winos
f2b86cd5a0 Improved error handling as requested. 2024-12-16 09:10:54 -03:00
Tixx
3e7d16a8e8 Update Check for Game Files on Native Linux (#92)
Beforehand, the launcher would check only if the game directory existed.
This would lead to an error if the game was moved through Steam since
Steam leaves the BeamNG.Drive directory behind. Now it checks for to see
if integrity.json is inside. I also removed some commented out code I
seem to have left behind when I was porting it.

I agree to this code (and the whole port to native Linux since the #63
was locked and I couldn't agree there) being licensed under AGPL 3.
2024-12-14 20:47:58 +01:00
Tixx
03748d096f Switch to AGPL-3.0 (#86)
LOCKED until:
- [ ] Review each individual file for third party licenses
- [ ] Implement AGPL headers into each file
- [ ] Get confirmation from all contributors/copyright owners to make
the license change
- [ ] Eventually figure out bits of code of contributors that either
dont agree to the license change or that are unresponsive
2024-12-14 20:44:20 +01:00
Tixx
096d07fe9b Update build instructions (#156)
Update build instructions

Adresses both #155 and #153 

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2024-12-14 18:19:06 +01:00
O1LER
e7a0325e70 Add vcpkg to einstructions 2024-12-11 14:08:36 +01:00
PoorPockets McNewHold
7f627aaf92 Add missing vcpkg package for Fedora
```
BeamMP-Launcher on  master via △ v3.30.5 
 ❯ sudo dnf group info "Development Tools" 
Dernière vérification de l’expiration des métadonnées effectuée il y a 0:48:01 le mer. 11 déc. 2024 08:39:18.
Groupe : Outils de développement
 Description : Ces outils comprennent des outils de développement principaux comme git et cvs.
 Paquets obligatoires :
   gettext
 Paquets par défaut :
   diffstat
   doxygen
   git
   patch
   patchutils
   subversion
   systemtap
 Paquets optionnels :
   buildbot
   colordiff
   cvs
   cvs2cl
   cvsps
   darcs
   dejagnu
   expect
   gambas3-ide
   git-annex
   git-cola
   git2cl
   gitg
   gtranslator
   highlight
   lcov
   manedit
   meld
   monotone
   myrepos
   nemiver
   qgit
   quilt
   rapidsvn
   rcs
   robodoc
   scanmem
   subunit
   svn2cl
   tig
   tortoisehg
   translate-toolkit
   utrac
```
vcpkg isn't part of the Development Tools group on Fedora.
2024-12-11 09:28:56 +01:00
Winos
63aee03969 Update BeamNG.cpp
As requested:
- Changed 'string' variables that referred to paths to 'filesystem'.
- Added error log for when a valid 'GameDir' isn't found.

Also readded error log for when 'libraryfolders.vdf' is missing for better contextualization for the 'basic_string::_M_replace_aux' error (the code doesn't reach the GameDir segment when that file is not found).
2024-12-10 20:10:07 -03:00
Tixx
46e6fda26e Update license header 2024-12-07 11:26:09 +01:00
Lion Kortlepel
87c7edf404 fixup readme 2024-12-07 11:12:18 +01:00
Lion
f4fcbd63f5 Update README.md 2024-12-07 11:11:28 +01:00
Lion
7f1072b7c2 Create LICENSE 2024-12-07 11:10:51 +01:00
Winos
89327b8e20 Update src/Security/BeamNG.cpp
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-12-06 15:03:25 -03:00
Winos
ebf1579ce4 Update src/Security/BeamNG.cpp
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-12-06 15:03:19 -03:00
Winos
a06470cb70 Update src/Security/BeamNG.cpp
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-12-06 15:02:07 -03:00
Winos
80a3feb349 Update src/Security/BeamNG.cpp
Co-authored-by: Tixx <83774803+WiserTixx@users.noreply.github.com>
2024-12-06 15:02:00 -03:00
Tixx
f2166ff8c6 Add linux building instructions (#149)
- Do we need to mention that vcpkg should be installed in the first
place?
- Do we need to specify what the difference between release and debug
builds is?
- Are there any other prerequisites for building on windows?

---

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion.
I declare that I fully understand all code I pushed into this PR, and
wrote all this code myself and own the rights to this code.
2024-12-06 17:34:05 +01:00
Winos
ec1a09bbcb Fixed GameDir localization issues.
I couldn't launch the game without this.
2024-12-05 03:56:13 -03:00
Tixx
811fe41afb Update CMakeLists.txt to fix linux compilation (#151)
Fixed the Cmake lists file so it properly detects linux-based systems.
thats all. :3

By creating this pull request, I understand that code that is AI
generated or otherwise automatically generated may be rejected without
further discussion. I declare that I fully understand all code I pushed
into this PR, and wrote all this code myself and own the rights to this
code.
2024-12-03 13:30:15 +01:00
FirewallDaProtogen
c518a036ed Update CMakeLists.txt 2024-12-03 03:51:32 -05:00
O1LER
d8c1af4ac2 Add linux building instructions 2024-11-14 21:38:33 +01:00
Lion
00bd5be4d0 add PR template 2024-11-13 16:20:54 +01:00
Tixx
dff2f2712b Bump version 2024-11-07 22:12:20 +01:00
Lion
3effe0d4de log zlib error message and regex fix (#146) 2024-11-07 22:05:40 +01:00
Tixx
d58ff960ec Fix github regex 2024-11-07 21:39:18 +01:00
Tixx
f67f8573e0 Log zlib error messages 2024-11-07 21:36:38 +01:00
Lion
8a8e0be1a1 Print message from auth (#141) 2024-11-05 10:32:39 +01:00
Tixx
e0041666ca Clarify and change auth message log 2024-11-05 10:26:10 +01:00
Tixx
ed686333ec Print message from auth 2024-11-05 10:15:10 +01:00
Lion
8938fd84ea Add beammp.gg to the list of allowed links (#143) 2024-11-02 23:29:40 +01:00
Tixx
bd4c9c34a9 Add BeamMP github to the list of allowed links 2024-11-02 22:33:26 +01:00
Tixx
8519e279a7 Add beammp.gg to the list of allowed links 2024-11-02 22:18:46 +01:00
Lion Kortlepel
54895eb1b0 bump version 2024-11-01 12:53:55 +01:00
Lion
1423c1193b Speed up response times by waiting for http requests on another thread. (#137)
If, for example, the client requests the serverlist multiple times and
then tries to login the launcher will first wait for those requests to
finish. Thereby putting the other core communication (such as login) on
hold.
2024-11-01 12:13:21 +01:00
Lion
288e76594d Fix port cli argument (#142)
Fixes --port and -p by proccessing the config file before the cli
arguments, before it would first set --port because it was passed and
then overwrite it with the value from the config. Also removed some
useless code related to cli args.
2024-11-01 12:11:26 +01:00
Tixx
4fdc3c4031 Fix --port 2024-10-20 16:59:00 +02:00
Tixx
708da44fec Remove unused code 2024-10-20 16:57:47 +02:00
Tixx
6b6e304cfd Switch to std::async 2024-10-18 19:23:53 +02:00
Tixx
06cb366bb5 Add mutex to CoreSend 2024-10-16 23:12:02 +02:00
Tixx
0b35f0484f put blocking http requests on another thread 2024-10-16 23:12:02 +02:00
Lion
9dbbd8298d Switch to only timeout on connection (#140) 2024-10-15 19:32:55 +02:00
Tixx
ca9dd1ae75 Switch to only timeout on connection 2024-10-14 20:29:19 +02:00
Lion
9ebd218856 Fix empty modlist (#136)
This PR fixes the launcher getting confused when the server sends an
empty mod list using the new downloading system.
Related server PR: https://github.com/BeamMP/BeamMP-Server/pull/377
2024-10-12 22:10:47 +02:00
Pranay Sanghai
43b02f0118 Update Check For Game Files
Beforehand, it would check only if the game directory existed. This would lead to an error if the game is moved through Steam since it leaves the BeamNG.Drive directory. Now it checks for integrity.json. I also removed some commented out code I left behind.
2024-10-12 21:15:10 +02:00
Tixx
d9874ce70e Make return from parsemodinfo look better 2024-10-12 21:12:12 +02:00
Tixx
423519f31e Only listen on localhost ipv4 (#134)
This avoids the firewall popup on windows.
2024-10-12 20:58:29 +02:00
Tixx
3f12bb757a Mod info logs and check for old format 2024-10-10 21:35:27 +02:00
Lion Kortlepel
7d52e44434 only listen on localhost ipv4 2024-10-10 16:14:16 +02:00
Tixx
4fbd25b551 Handle new modlist being empty but still valid 2024-10-09 19:41:38 +02:00
Tixx
3cf1a2e51b Add mod info debug log 2024-10-09 19:39:27 +02:00
Lion Kortlepel
49874fd633 Revert "remove 'D' socket initialization code"
This reverts commit 6a23518eff.
2024-10-09 18:00:43 +02:00
Lion Kortlepel
6a23518eff remove 'D' socket initialization code 2024-10-09 17:36:54 +02:00
Lion Kortlepel
3297b3e62e fix not recognizing empty mod lists on new mod list 2024-10-09 17:35:50 +02:00
Lion Kortlepel
76cfc47a2f log invocation 2024-10-07 00:43:25 +02:00
Lion Kortlepel
7b59cb6f87 fix various commandline argument related things 2024-10-07 00:33:43 +02:00
Lion Kortlepel
0eba745d4c remove silly license 2024-10-07 00:33:29 +02:00
Lion
259b21502e Add command-line options (#90)
This PR adds command-line options as outlined in #74 

Closes #74
2024-10-06 23:49:47 +02:00
Tixx
afac729505 Ixmplement game arguments for linux 2024-10-06 15:56:48 +02:00
Lion
f57ebb7a92 follow HTTP redirects (#133)
Follow HTTP redirects on HTTP Get and Post functions
2024-10-06 15:20:49 +02:00
snepsnepsnep
ace96b7e33 follow HTTP redirects 2024-10-05 23:45:50 +02:00
Lion Kortlepel
fcb51adcb8 bump version 2024-10-05 21:05:02 +02:00
Tixx
0c53ff4cd4 Pass game arguments to beamng on windows 2024-10-05 18:34:39 +02:00
Tixx
68a4d64387 Fix linux relauch 2024-10-05 18:08:25 +02:00
Tixx
5bdd8c11da Fix relaunch 2024-10-05 18:01:47 +02:00
Tixx
47681cda50 Let user know about update even if --no-update was specified 2024-10-05 18:01:36 +02:00
Tixx
c99fecfa1c Fix debug log 2024-10-05 17:26:14 +02:00
Tixx
d26e0320e4 Add back support for old dev argument with warning 2024-10-05 17:26:14 +02:00
Tixx
57422a6105 Add optional dev config value 2024-10-05 17:20:40 +02:00
Tixx
467c8dc584 Add no-update flag 2024-10-05 17:20:40 +02:00
Tixx
2ddb576e72 Log core port 2024-10-05 17:20:40 +02:00
Tixx
e242057583 Improve port cli flag 2024-10-05 17:20:40 +02:00
Tixx
06686688fc Move console clear to main to avoid clearing logs 2024-10-05 17:20:40 +02:00
Tixx
aca61886d0 add command-line options 2024-10-05 17:20:40 +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
82 changed files with 8880 additions and 9512 deletions

5
.clang-format Normal file
View File

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

6
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,6 @@
Please replace this text <-> with your PR description and leave the below declarations intact.
---
By creating this pull request, I understand that code that is AI generated or otherwise automatically generated may be rejected without further discussion.
I declare that I fully understand all code I pushed into this PR, and wrote all this code myself and own the rights to this code.

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
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
env:
BUILD_TYPE: Release
@@ -18,9 +18,9 @@ jobs:
uses: lukka/run-vcpkg@v7
id: runvcpkg
with:
vcpkgArguments: 'discord-rpc zlib rapidjson openssl'
vcpkgArguments: 'discord-rpc zlib nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '86ff75c6d8232b54d3ebd0e71525b4634dcd9523'
vcpkgGitCommitId: '40616a5e954f7be1077ef37db3fbddbd5dcd1ca6'
vcpkgTriplet: 'x64-windows-static'
- name: Create Build Environment
@@ -37,7 +37,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: 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: '86ff75c6d8232b54d3ebd0e71525b4634dcd9523'
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

@@ -4,4 +4,12 @@ cmake-build-release
*.log
/*.sh
/*.obj
/*.exe
/*.exe
.cache/
.https_debug/
Launcher.cfg
Resources/
bin/
compile_commands.json
key
out/

View File

@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(Launcher)
if (WIN32)
@@ -7,11 +8,16 @@ if (WIN32)
STRING(REPLACE "/MDd" "/MTd" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})
endif(WIN32)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
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})
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)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
#-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET})
include_directories(${VcpkgRoot}/include)
link_directories(${VcpkgRoot}/lib)
target_link_libraries(${PROJECT_NAME} PRIVATE ${VcpkgRoot}/lib/discord-rpc.lib
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32)
target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json CURL::libcurl)
elseif (UNIX)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl)
else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600")
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)
target_include_directories(${PROJECT_NAME} PRIVATE "include")
target_include_directories(${PROJECT_NAME} PRIVATE "include")

661
LICENSE Normal file
View File

@@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

73
README.md Executable file → Normal file
View File

@@ -2,9 +2,72 @@
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`
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,
the only permission that has been granted is to use the software in its compiled form as distributed from the BeamMP.com website.
Anything else is prohibited. Modified works may not be published and have be upstreamed to the official repository.
## How to build for Windows
Make sure you have the necessary development tools installed:
[vcpkg](https://vcpkg.io/en/)
### 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.
### 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.
## How to build for Linux
Make sure you have `vcpkg` installed, as well as basic development tools, often found in packages, for example:
- Debian: `sudo apt install build-essential`
- Fedora: `sudo dnf groupinstall "Development Tools"`
- Arch: `sudo pacman -S base-devel`
- openSUSE: `zypper in -t pattern devel-basis`
### Release
In the root directory of the project,
1. `cmake -DCMAKE_BUILD_TYPE=Release . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
2. `cmake --build bin --parallel --config Release`
### Debug
In the root directory of the project,
1. `cmake . -B bin -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux`
2. `cmake --build bin --parallel`
## Running out of RAM while building
Should you run out of RAM while building, you can ommit the `--parallel` intruction, it will then use less RAM due to building only on one CPU thread.
You can also specify a number of threads to use, for example `--parallel 4` will use four CPU threads, but due to the small project size, you may be faster just omitting `--parallel` instead of trying to find the highest possible multithread number
## License
BeamMP Launcher, a launcher for the BeamMP mod for BeamNG.drive
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

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

23
include/Http.h Executable file → Normal file
View File

@@ -1,20 +1,19 @@
// 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/18/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
#include "Logger.h"
#include <string>
class HTTP {
public:
static bool Download(const std::string &IP, const std::string &Path);
static bool Download(const std::string& IP, const std::string& Fields, const std::string& Path);
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, const std::string& Fields = "");
static bool ProgressBar(size_t c, size_t t);
static void StartProxy();
public:
static bool isDownload;
static std::string Codes_[];
};
};

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;

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

@@ -1,13 +1,12 @@
// 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 4/2/2020.
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
#include <iostream>
#include <string>
void InitLog();
void except(const std::string& toPrint);
void fatal(const std::string& toPrint);
@@ -15,3 +14,4 @@ void debug(const std::string& toPrint);
void error(const std::string& toPrint);
void info(const std::string& toPrint);
void warn(const std::string& toPrint);
std::string getDate();

View File

@@ -1,46 +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/18/2020
///
#pragma once
#include <string>
void NetReset();
extern bool Dev;
extern int ping;
[[noreturn]] void CoreNetwork();
extern int ClientID;
extern int LastPort;
extern bool ModLoaded;
extern bool Terminate;
extern int DEFAULT_PORT;
extern uint64_t UDPSock;
extern uint64_t TCPSock;
extern std::string Branch;
extern bool TCPTerminate;
extern std::string LastIP;
extern std::string MStatus;
extern std::string UlStatus;
extern std::string PublicKey;
extern std::string ListOfMods;
int KillSocket(uint64_t Dead);
void UUl(const std::string& R);
void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes);
void GameSend(std::string Data);
void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string&IP);
void ServerParser(const std::string& Data);
std::string Login(const std::string& fields);
void TCPSend(const std::string&Data,uint64_t Sock);
void TCPClientMain(const std::string& IP,int Port);
void UDPClientMain(const std::string& IP,int Port);
void TCPGameServer(const std::string& IP, int Port);

View File

@@ -0,0 +1,54 @@
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
#ifdef __linux__
#include "linuxfixes.h"
#include <bits/types/siginfo_t.h>
#include <cstdint>
#include <sys/ucontext.h>
#endif
void NetReset();
extern bool Dev;
extern int ping;
[[noreturn]] void CoreNetwork();
extern int ProxyPort;
extern int ClientID;
extern int LastPort;
extern bool ModLoaded;
extern bool Terminate;
extern uint64_t UDPSock;
extern uint64_t TCPSock;
extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate;
extern std::string LastIP;
extern std::string MStatus;
extern std::string UlStatus;
extern std::string PublicKey;
extern std::string PrivateKey;
int KillSocket(uint64_t Dead);
void UUl(const std::string& R);
void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes);
void GameSend(std::string_view Data);
void SendLarge(std::string Data);
std::string TCPRcv(uint64_t Sock);
void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string& IP);
void ServerParser(std::string_view Data);
std::string Login(const std::string& fields);
void TCPSend(const std::string& Data, uint64_t Sock);
void TCPClientMain(const std::string& IP, int Port);
void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);

30
include/Options.h Normal file
View File

@@ -0,0 +1,30 @@
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
struct Options {
#if defined(_WIN32)
std::string executable_name = "BeamMP-Launcher.exe";
#elif defined(__linux__)
std::string executable_name = "BeamMP-Launcher";
#endif
unsigned int port = 4444;
bool verbose = false;
bool no_download = false;
bool no_update = false;
bool no_launch = false;
const char **game_arguments = nullptr;
int game_arguments_length = 0;
const char** argv = nullptr;
int argc = 0;
};
void InitOptions(int argc, const char *argv[], Options &options);
extern Options options;

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

@@ -1,9 +1,8 @@
// 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/19/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
extern unsigned long GamePID;

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

@@ -1,14 +1,13 @@
// 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/18/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
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);
std::string GetGameDir();
void LegitimacyCheck();

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

@@ -1,16 +1,18 @@
// 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/18/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <compare>
#include <string>
void InitLauncher(int argc, char* argv[]);
std::string GetEP(char*P = nullptr);
#include <vector>
void InitLauncher();
std::string GetEP(const char* P = nullptr);
std::string GetGamePath();
std::string GetVer();
std::string GetPatch();
std::string GetEN();
void ConfigInit();
extern bool Dev;

26
include/Utils.h Normal file
View File

@@ -0,0 +1,26 @@
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#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;
};
};

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

@@ -1,11 +1,12 @@
// 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/24/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <string>
std::string Comp(std::string Data);
std::string DeComp(std::string Compressed);
#include <span>
#include <vector>
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

25
include/linuxfixes.h Normal file
View File

@@ -0,0 +1,25 @@
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#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__

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

@@ -1,54 +1,61 @@
// 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/15/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <iostream>
#include "Logger.h"
#include <span>
#include <vector>
#include <zconf.h>
#include <zlib.h>
#ifdef __linux__
#include <cstring>
#endif
#define Biggest 30000
std::string Comp(std::string Data){
char*C = new char[Biggest];
memset(C, 0, Biggest);
z_stream defstream;
defstream.zalloc = Z_NULL;
defstream.zfree = Z_NULL;
defstream.opaque = Z_NULL;
defstream.avail_in = (uInt)Data.length();
defstream.next_in = (Bytef *)&Data[0];
defstream.avail_out = Biggest;
defstream.next_out = reinterpret_cast<Bytef *>(C);
deflateInit(&defstream, Z_BEST_COMPRESSION);
deflate(&defstream, Z_SYNC_FLUSH);
deflate(&defstream, Z_FINISH);
deflateEnd(&defstream);
int TO = defstream.total_out;
std::string Ret(TO,0);
memcpy_s(&Ret[0],TO,C,TO);
delete [] C;
return Ret;
std::vector<char> Comp(std::span<const char> input) {
auto max_size = compressBound(input.size());
std::vector<char> output(max_size);
uLongf output_size = output.size();
int res = compress(
reinterpret_cast<Bytef*>(output.data()),
&output_size,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size()));
if (res != Z_OK) {
error("zlib compress() failed (code: " + std::to_string(res) + ", message: " + zError(res) + ")");
throw std::runtime_error("zlib compress() failed");
}
debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B");
output.resize(output_size);
return output;
}
std::vector<char> DeComp(std::span<const char> input) {
std::vector<char> output_buffer(std::min<size_t>(input.size() * 5, 15 * 1024 * 1024));
uLongf output_size = output_buffer.size();
while (true) {
int res = uncompress(
reinterpret_cast<Bytef*>(output_buffer.data()),
&output_size,
reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size()));
if (res == Z_BUF_ERROR) {
if (output_buffer.size() > 30 * 1024 * 1024) {
throw std::runtime_error("decompressed packet size of 30 MB exceeded");
}
debug("zlib uncompress() failed, trying with 2x buffer size of " + std::to_string(output_buffer.size() * 2));
output_buffer.resize(output_buffer.size() * 2);
output_size = output_buffer.size();
} else if (res != Z_OK) {
error("zlib uncompress() failed (code: " + std::to_string(res) + ", message: " + zError(res) + ")");
throw std::runtime_error("zlib uncompress() failed");
} else if (res == Z_OK) {
break;
}
} output_buffer.resize(output_size);
return output_buffer;
}
std::string DeComp(std::string Compressed){
char*C = new char[Biggest];
memset(C, 0, Biggest);
z_stream infstream;
infstream.zalloc = Z_NULL;
infstream.zfree = Z_NULL;
infstream.opaque = Z_NULL;
infstream.avail_in = Biggest;
infstream.next_in = (Bytef *)(&Compressed[0]);
infstream.avail_out = Biggest;
infstream.next_out = (Bytef *)(C);
inflateInit(&infstream);
inflate(&infstream, Z_SYNC_FLUSH);
inflate(&infstream, Z_FINISH);
inflateEnd(&infstream);
int TO = infstream.total_out;
std::string Ret(TO,0);
memcpy_s(&Ret[0],TO,C,TO);
delete [] C;
return Ret;
}

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

@@ -1,59 +1,76 @@
///
/// Created by Anonymous275 on 2/23/2021
///
#include "Network/network.h"
#include <filesystem>
#include "Logger.h"
#include <fstream>
#include "Json.h"
#include <cstdint>
namespace fs = std::filesystem;
std::string Branch;
void ParseConfig(const json::Document& d){
if(d["Port"].IsInt()){
DEFAULT_PORT = d["Port"].GetInt();
}
//Default -1
//Release 1
//EA 2
//Dev 3
//Custom 3
if(d["Build"].IsString()){
Branch = d["Build"].GetString();
for(char& c : Branch)c = char(tolower(c));
}
}
void ConfigInit(){
if(fs::exists("Launcher.cfg")){
std::ifstream cfg("Launcher.cfg");
if(cfg.is_open()){
auto Size = fs::file_size("Launcher.cfg");
std::string Buffer(Size, 0);
cfg.read(&Buffer[0], Size);
cfg.close();
json::Document d;
d.Parse(Buffer.c_str());
if(d.HasParseError()){
fatal("Config failed to parse make sure it's valid JSON! Code : " + std::to_string(d.GetParseError()));
}
ParseConfig(d);
}else fatal("Failed to open Launcher.cfg!");
}else{
std::ofstream cfg("Launcher.cfg");
if(cfg.is_open()){
cfg <<
R"({
"Port": 4444,
"Build": "Default"
})";
cfg.close();
}else{
fatal("Failed to write config on disk!");
}
}
}
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Logger.h"
#include "Network/network.hpp"
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>
#include "Options.h"
namespace fs = std::filesystem;
std::string Branch;
std::string CachingDirectory = "./Resources";
void ParseConfig(const nlohmann::json& d) {
if (d["Port"].is_number()) {
options.port = d["Port"].get<int>();
}
// Default -1
// Release 1
// EA 2
// Dev 3
// Custom 3
if (d["Build"].is_string()) {
Branch = d["Build"].get<std::string>();
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);
}
if (d.contains("Dev") && d["Dev"].is_boolean()) {
bool dev = d["Dev"].get<bool>();
options.verbose = dev;
options.no_download = dev;
options.no_launch = dev;
options.no_update = dev;
}
}
void ConfigInit() {
if (fs::exists("Launcher.cfg")) {
std::ifstream cfg("Launcher.cfg");
if (cfg.is_open()) {
auto Size = fs::file_size("Launcher.cfg");
std::string Buffer(Size, 0);
cfg.read(&Buffer[0], Size);
cfg.close();
nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
if (d.is_discarded()) {
fatal("Config failed to parse make sure it's valid JSON!");
}
ParseConfig(d);
} else
fatal("Failed to open Launcher.cfg!");
} else {
std::ofstream cfg("Launcher.cfg");
if (cfg.is_open()) {
cfg <<
R"({
"Port": 4444,
"Build": "Default",
"CachingDirectory": "./Resources"
})";
cfg.close();
} else {
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();*/
}

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

@@ -1,68 +1,140 @@
// 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/19/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <Security/Init.h>
#if defined(_WIN32)
#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 "Startup.h"
#include <Security/Init.h>
#include <filesystem>
#include <thread>
#include "Options.h"
unsigned long GamePID = 0;
std::string QueryKey(HKEY hKey,int ID);
std::string GetGamePath(){
#if defined(_WIN32)
std::string QueryKey(HKEY hKey, int ID);
std::string GetGamePath() {
static std::string Path;
if(!Path.empty())return Path;
if (!Path.empty())
return Path;
HKEY hKey;
LPCTSTR sk = "Software\\BeamNG\\BeamNG.drive";
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!");
}
Path = QueryKey(hKey,4);
Path = QueryKey(hKey, 4);
if(Path.empty()){
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)";
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey);
if (openRes != ERROR_SUCCESS){
fatal("Cannot get Local Appdata directory!");
if (Path.empty()) {
Path = "";
char appDataPath[MAX_PATH];
HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
if (SUCCEEDED(result)) {
Path = appDataPath;
}
Path = QueryKey(hKey,5);
if (Path.empty()) {
fatal("Cannot get Local Appdata directory");
}
Path += "\\BeamNG.drive\\";
}
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 + "\\";
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;
PROCESS_INFORMATION pi;
STARTUPINFO si = {0};
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
std::string BaseDir = Dir; //+"\\Bin64";
//Dir += R"(\Bin64\BeamNG.drive.x64.exe)";
// Dir += R"(\Bin64\BeamNG.drive.x64.exe)";
Dir += "\\BeamNG.drive.exe";
bSuccess = CreateProcessA(Dir.c_str(), nullptr, nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
if (bSuccess){
std::string gameArgs = "";
for (int i = 0; i < options.game_arguments_length; i++) {
gameArgs += " ";
gameArgs += options.game_arguments[i];
}
bSuccess = CreateProcessA(nullptr, (LPSTR)(Dir + gameArgs).c_str(), nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi);
if (bSuccess) {
info("Game Launched!");
GamePID = pi.dwProcessId;
WaitForSingleObject(pi.hProcess, INFINITE);
error("Game Closed! launcher closing soon");
}else{
} else {
error("Failed to Launch the game! launcher closing soon");
}
std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2);
}
void InitGame(const std::string& Dir){
if(!Dev){
#elif defined(__linux__)
void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
std::vector<const char*> argv;
argv.push_back(filename.data());
for (int i = 0; i < options.game_arguments_length; i++) {
argv.push_back(options.game_arguments[i]);
}
argv.push_back(nullptr);
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, const_cast<char**>(argv.data()), 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 (!options.no_launch) {
std::thread Game(StartGame, Dir);
Game.detach();
}

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

@@ -1,17 +1,17 @@
// 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/17/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Startup.h"
#include "Logger.h"
#include "Startup.h"
#include <chrono>
#include <fstream>
#include <sstream>
#include <chrono>
#include <thread>
#include "Options.h"
std::string getDate() {
time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
@@ -24,24 +24,25 @@ std::string getDate() {
std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M));
std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H));
date
<< "["
<< local_tm.tm_mday << "/"
<< local_tm.tm_mon + 1 << "/"
<< local_tm.tm_year + 1900 << " "
<< Hour << ":"
<< Min << ":"
<< Secs
<< "] ";
<< "["
<< local_tm.tm_mday << "/"
<< local_tm.tm_mon + 1 << "/"
<< local_tm.tm_year + 1900 << " "
<< Hour << ":"
<< Min << ":"
<< Secs
<< "] ";
return date.str();
}
void InitLog(){
void InitLog() {
std::ofstream LFS;
LFS.open(GetEP() + "Launcher.log");
if(!LFS.is_open()){
if (!LFS.is_open()) {
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;
LFS.open(GetEP() + "Launcher.log", std::ios_base::app);
LFS << Line.c_str();
@@ -53,12 +54,13 @@ void info(const std::string& toPrint) {
addToLog(Print);
}
void debug(const std::string& toPrint) {
if(!Dev)return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
std::cout << Print;
if (options.verbose) {
std::cout << Print;
}
addToLog(Print);
}
void warn(const std::string& toPrint){
void warn(const std::string& toPrint) {
std::string Print = getDate() + "[WARN] " + toPrint + "\n";
std::cout << Print;
addToLog(Print);
@@ -73,7 +75,7 @@ void fatal(const std::string& toPrint) {
std::cout << Print;
addToLog(Print);
std::this_thread::sleep_for(std::chrono::seconds(5));
_Exit(-1);
std::exit(1);
}
void except(const std::string& toPrint) {
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";

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

@@ -1,40 +1,81 @@
// 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/20/2020
///
#include "Network/network.h"
#include "Security/Init.h"
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Http.h"
#include "Network/network.hpp"
#include "Security/Init.h"
#include <cstdlib>
#include <regex>
#if defined(_WIN32)
#include <winsock2.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 "Startup.h"
#include <charconv>
#include <thread>
#include <nlohmann/json.hpp>
#include <set>
#include <thread>
#include <mutex>
#include "Options.h"
#include <future>
extern int TraceBack;
std::set<std::string>* ConfList = nullptr;
bool TCPTerminate = false;
int DEFAULT_PORT = 4444;
bool Terminate = false;
bool LoginAuth = false;
std::string Username = "";
std::string UserRole = "";
int UserID = -1;
std::string UlStatus;
std::string MStatus;
bool ModLoaded;
int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
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)";
ListOfMods = "-";
bool SecurityWarning() {
confirmed = -1;
CoreSend("WMODS_FOUND");
while (confirmed == -1)
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;
CoreSend("L");
return;
}
CheckLocalKey();
@@ -43,120 +84,200 @@ void StartSync(const std::string &Data){
Terminate = false;
ConfList->clear();
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();
info("Connecting to server");
}
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':
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){
std::mutex sendMutex;
void CoreSend(std::string data) {
std::lock_guard lock(sendMutex);
if (CoreSocket != -1) {
int res = send(CoreSocket, (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){
int32_t Size,Temp,Rcv;
char Header[10] = {0};
do{
bool IsAllowedLink(const std::string& Link) {
std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|beammp\.gg|github\.com\/BeamMP\/|discord\.gg|patreon\.com\/BeamMP))");
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.clear();
auto future = std::async(std::launch::async, []() {
CoreSend("B" + 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 {
auto future = std::async(std::launch::async, [data = std::move(Data)]() {
CoreSend("N" + Login(data.substr(data.find(':') + 1)));
});
Data.clear();
}
break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
default:
Data.clear();
break;
}
if (!Data.empty())
CoreSend(Data);
}
void GameHandler(SOCKET Client) {
CoreSocket = Client;
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
do {
Rcv = 0;
do{
Temp = recv(Client,&Header[Rcv],1,0);
if(Temp < 1)break;
if(!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
do {
Temp = recv(Client, &Header[Rcv], 1, 0);
if (Temp < 1)
break;
if (!isdigit(Header[Rcv]) && Header[Rcv] != '>') {
error("(Core) Invalid lua communication");
KillSocket(Client);
return;
}
}while(Header[Rcv++] != '>');
if(Temp < 1)break;
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){
debug("(Core) Invalid lua Header -> " + std::string(Header,Rcv));
} while (Header[Rcv++] != '>');
if (Temp < 1)
break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Core) Invalid lua Header -> " + std::string(Header, Rcv));
break;
}
std::string Ret(Size,0);
std::string Ret(Size, 0);
Rcv = 0;
do{
Temp = recv(Client,&Ret[Rcv],Size-Rcv,0);
if(Temp < 1)break;
do {
Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
}while(Rcv < Size);
if(Temp < 1)break;
} while (Rcv < Size);
if (Temp < 1)
break;
std::thread Respond(Parse, Ret, Client);
Respond.detach();
}while(Temp > 0);
Parse(Ret, Client);
} while (Temp > 0);
if (Temp == 0) {
debug("(Core) Connection closing");
} else {
@@ -165,10 +286,10 @@ void GameHandler(SOCKET Client){
NetReset();
KillSocket(Client);
}
void localRes(){
void localRes() {
MStatus = " ";
UlStatus = "Ulstart";
if(ConfList != nullptr){
if (ConfList != nullptr) {
ConfList->clear();
delete ConfList;
ConfList = nullptr;
@@ -176,26 +297,32 @@ void localRes(){
ConfList = new std::set<std::string>;
}
void CoreMain() {
debug("Core Network on start!");
debug("Core Network on start! port: " + std::to_string(options.port));
SOCKET LSocket, CSocket;
struct addrinfo* res = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
SOCKET LSocket,CSocket;
struct addrinfo *res = nullptr;
struct addrinfo hints{};
int iRes = WSAStartup(514, &wsaData); //2.2
if (iRes)debug("WSAStartup failed with error: " + std::to_string(iRes));
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes)
debug("WSAStartup failed with error: " + std::to_string(iRes));
#endif
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res);
if (iRes){
iRes = getaddrinfo("127.0.0.1", std::to_string(options.port).c_str(), &hints, &res);
if (iRes) {
debug("(Core) addr info failed with error: " + std::to_string(iRes));
WSACleanup();
return;
}
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()));
freeaddrinfo(res);
WSACleanup();
@@ -217,7 +344,7 @@ void CoreMain() {
WSACleanup();
return;
}
do{
do {
CSocket = accept(LSocket, nullptr, nullptr);
if (CSocket == -1) {
error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
@@ -227,28 +354,38 @@ void CoreMain() {
info("Game Connected!");
GameHandler(CSocket);
warn("Game Reconnecting...");
}while(CSocket);
} while (CSocket);
KillSocket(LSocket);
WSACleanup();
}
int Handle(EXCEPTION_POINTERS *ep){
#if defined(_WIN32)
int Handle(EXCEPTION_POINTERS* ep) {
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));
delete [] hex;
delete[] hex;
return 1;
}
[[noreturn]] void CoreNetwork(){
while(true) {
#ifndef __MINGW32__
__try{
#endif
CoreMain();
#ifndef __MINGW32__
}__except(Handle(GetExceptionInformation())){}
[[noreturn]] void CoreNetwork() {
while (true) {
#if not defined(__MINGW32__)
__try {
#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));
}
}

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

@@ -1,31 +1,42 @@
// 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 9/25/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <string>
#if defined(_WIN32)
#include <winsock2.h>
#elif defined(__linux__)
#include "linuxfixes.h"
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include "Logger.h"
std::string GetAddr(const std::string&IP){
if(IP.find_first_not_of("0123456789.") == -1)return IP;
std::string GetAddr(const std::string& IP) {
if (IP.find_first_not_of("0123456789.") == -1)
return IP;
hostent* host;
#ifdef _WIN32
WSADATA wsaData;
hostent *host;
if(WSAStartup(514, &wsaData) != 0){
if (WSAStartup(514, &wsaData) != 0) {
error("WSA Startup Failed!");
WSACleanup();
return "";
}
#endif
host = gethostbyname(IP.c_str());
if(!host){
if (!host) {
error("DNS lookup failed! on " + IP);
WSACleanup();
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();
return Ret;
}

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

@@ -1,134 +1,167 @@
// 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/25/2020
///
#include "Network/network.h"
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Network/network.hpp"
#include <memory>
#include <zlib.h>
#if defined(_WIN32)
#include <winsock2.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 <charconv>
#include <mutex>
#include <string>
#include <thread>
#include <mutex>
#include "Options.h"
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 CServer = true;
SOCKET CSocket = -1;
SOCKET GSocket = -1;
int KillSocket(uint64_t Dead){
if(Dead == (SOCKET)-1){
int KillSocket(uint64_t Dead) {
if (Dead == (SOCKET)-1) {
debug("Kill socket got -1 returning...");
return 0;
}
shutdown(Dead,SD_BOTH);
shutdown(Dead, SD_BOTH);
int a = closesocket(Dead);
if(a != 0){
if (a != 0) {
warn("Failed to close socket!");
}
return a;
}
bool CheckBytes(uint32_t Bytes){
if(Bytes == 0){
bool CheckBytes(uint32_t Bytes) {
if (Bytes == 0) {
debug("(Proxy) Connection closing");
return false;
}else if(Bytes < 0){
} else if (Bytes < 0) {
debug("(Proxy) send failed with error: " + std::to_string(WSAGetLastError()));
return false;
}
return true;
}
void GameSend(std::string Data){
void GameSend(std::string_view Data) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
if(TCPTerminate || !GConnected || CSocket == -1)return;
int32_t Size,Temp,Sent;
Data += '\n';
if (TCPTerminate || !GConnected || CSocket == -1)
return;
int32_t Size, Temp, Sent;
Size = int32_t(Data.size());
Sent = 0;
#ifdef DEBUG
if(Size > 1000){
debug("Launcher -> game (" +std::to_string(Size)+")");
}
#endif
do{
if(Sent > -1){
#ifdef DEBUG
if (Size > 1000) {
debug("Launcher -> game (" + std::to_string(Size) + ")");
}
#endif
do {
if (Sent > -1) {
Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
}
if(!CheckBytes(Temp))return;
}
if (!CheckBytes(Temp))
return;
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){
if(Terminate || Data.empty())return;
if(Data.find("Zp") != std::string::npos && Data.size() > 500){
void ServerSend(std::string Data, bool Rel) {
if (Terminate || Data.empty())
return;
if (Data.find("Zp") != std::string::npos && Data.size() > 500) {
abort();
}
char C = 0;
bool Ack = false;
int DLen = int(Data.length());
if(DLen > 3)C = Data.at(0);
if (C == 'O' || C == 'T')Ack = true;
if(C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')Rel = true;
if(Ack || Rel){
if(Ack || DLen > 1000)SendLarge(Data);
else TCPSend(Data,TCPSock);
}else UDPSend(Data);
if (DLen > 3)
C = Data.at(0);
if (C == 'O' || C == 'T')
Ack = true;
if (C == 'N' || C == 'W' || C == 'Y' || C == 'V' || C == 'E' || C == 'C')
Rel = true;
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) {
debug("(Launcher->Server) Bytes sent: " + std::to_string(Data.length()) + " : "
+ Data.substr(0, 10)
+ Data.substr(Data.length() - 10));
}else if(C == 'Z'){
//debug("(Game->Launcher) : " + Data);
+ Data.substr(0, 10)
+ Data.substr(Data.length() - 10));
} else if (C == 'Z') {
// debug("(Game->Launcher) : " + Data);
}
}
void NetReset(){
void NetReset() {
TCPTerminate = false;
GConnected = false;
Terminate = false;
UlStatus = "Ulstart";
MStatus = " ";
if(UDPSock != (SOCKET)(-1)){
debug("Terminating UDP Socket : " + std::to_string(TCPSock));
if (UDPSock != (SOCKET)(-1)) {
debug("Terminating UDP Socket: " + std::to_string(TCPSock));
KillSocket(UDPSock);
}
UDPSock = -1;
if(TCPSock != (SOCKET)(-1)){
debug("Terminating TCP Socket : " + std::to_string(TCPSock));
if (TCPSock != (SOCKET)(-1)) {
debug("Terminating TCP Socket: " + std::to_string(TCPSock));
KillSocket(TCPSock);
}
TCPSock = -1;
if(GSocket != (SOCKET)(-1)){
debug("Terminating GTCP Socket : " + std::to_string(GSocket));
if (GSocket != (SOCKET)(-1)) {
debug("Terminating GTCP Socket: " + std::to_string(GSocket));
KillSocket(GSocket);
}
GSocket = -1;
}
SOCKET SetupListener(){
if(GSocket != -1)return GSocket;
struct addrinfo *result = nullptr;
struct addrinfo hints{};
SOCKET SetupListener() {
if (GSocket != -1)
return GSocket;
struct addrinfo* result = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
int iRes = WSAStartup(514, &wsaData); //2.2
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes != 0) {
error("(Proxy) WSAStartup failed with error: " + std::to_string(iRes));
return -1;
}
#endif
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT+1).c_str(), &hints, &result);
iRes = getaddrinfo(nullptr, std::to_string(options.port + 1).c_str(), &hints, &result);
if (iRes != 0) {
error("(Proxy) info failed with error: " + std::to_string(iRes));
WSACleanup();
@@ -140,7 +173,7 @@ SOCKET SetupListener(){
WSACleanup();
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) {
error("(Proxy) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(result);
@@ -158,59 +191,64 @@ SOCKET SetupListener(){
}
return GSocket;
}
void AutoPing(){
while(!Terminate){
ServerSend("p",false);
void AutoPing() {
while (!Terminate) {
ServerSend("p", false);
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;
void ParserAsync(const std::string& Data){
if(Data.empty())return;
char Code = Data.at(0),SubCode = 0;
if(Data.length() > 1)SubCode = Data.at(1);
void ParserAsync(std::string_view Data) {
if (Data.empty())
return;
char Code = Data.at(0), SubCode = 0;
if (Data.length() > 1)
SubCode = Data.at(1);
switch (Code) {
case 'p':
PingEnd = std::chrono::high_resolution_clock::now();
if(PingStart > PingEnd)ping = 0;
else ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd-PingStart).count());
return;
case 'M':
MStatus = Data;
UlStatus = "Uldone";
return;
default:
break;
case 'p':
PingEnd = std::chrono::high_resolution_clock::now();
if (PingStart > PingEnd)
ping = 0;
else
ping = int(std::chrono::duration_cast<std::chrono::milliseconds>(PingEnd - PingStart).count());
return;
case 'M':
MStatus = Data;
UlStatus = "Uldone";
return;
default:
break;
}
GameSend(Data);
}
void ServerParser(const std::string& Data){
void ServerParser(std::string_view Data) {
ParserAsync(Data);
}
void NetMain(const std::string& IP, int Port){
void NetMain(const std::string& IP, int Port) {
std::thread Ping(AutoPing);
Ping.detach();
UDPClientMain(IP,Port);
UDPClientMain(IP, Port);
CServer = true;
Terminate = true;
info("Connection Terminated!");
}
void TCPGameServer(const std::string& IP, int Port){
void TCPGameServer(const std::string& IP, int Port) {
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");
GConnected = false;
if(!CServer){
if (!CServer) {
warn("Connection still alive terminating");
NetReset();
TCPTerminate = true;
Terminate = true;
break;
}
if(CServer) {
std::thread Client(TCPClientMain, IP, Port);
Client.detach();
if (CServer) {
ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port);
}
CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) {
@@ -219,45 +257,61 @@ void TCPGameServer(const std::string& IP, int Port){
}
debug("(Proxy) Game Connected!");
GConnected = true;
if(CServer){
std::thread t1(NetMain, IP, Port);
t1.detach();
if (CServer) {
NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
CServer = false;
}
int32_t Size,Temp,Rcv;
char Header[10] = {0};
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
//Read byte by byte until '>' is rcved then get the size and read based on it
do{
// Read byte by byte until '>' is rcved then get the size and read based on it
do {
Rcv = 0;
do{
Temp = recv(CSocket,&Header[Rcv],1,0);
if(Temp < 1 || TCPTerminate)break;
}while(Header[Rcv++] != '>');
if(Temp < 1 || TCPTerminate)break;
if(std::from_chars(Header,&Header[Rcv],Size).ptr[0] != '>'){
debug("(Game) Invalid lua Header -> " + std::string(Header,Rcv));
do {
Temp = recv(CSocket, &Header[Rcv], 1, 0);
if (Temp < 1 || TCPTerminate)
break;
} while (Header[Rcv++] != '>');
if (Temp < 1 || TCPTerminate)
break;
if (std::from_chars(Header, &Header[Rcv], Size).ptr[0] != '>') {
debug("(Game) Invalid lua Header -> " + std::string(Header, Rcv));
break;
}
std::string Ret(Size,0);
std::string Ret(Size, 0);
Rcv = 0;
do{
Temp = recv(CSocket,&Ret[Rcv],Size-Rcv,0);
if(Temp < 1)break;
do {
Temp = recv(CSocket, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
}while(Rcv < Size && !TCPTerminate);
if(Temp < 1 || TCPTerminate)break;
} while (Rcv < Size && !TCPTerminate);
if (Temp < 1 || TCPTerminate)
break;
ServerSend(Ret,false);
ServerSend(Ret, false);
}while(Temp > 0 && !TCPTerminate);
if(Temp == 0)debug("(Proxy) Connection closing");
else debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
} while (Temp > 0 && !TCPTerminate);
if (Temp == 0)
debug("(Proxy) Connection closing");
else
debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
}
TCPTerminate = true;
GConnected = false;
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");
}
}

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

@@ -1,129 +1,273 @@
// 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/18/2020
///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <iostream>
#include <Logger.h>
#include <fstream>
#include "Http.h"
#include <mutex>
#include <cmath>
#include <httplib.h>
std::string HTTP::Codes_[] =
{
"Success","Unknown","Connection","BindIPAddress",
"Read","Write","ExceedRedirectCount","Canceled",
"SSLConnection","SSLLoadingCerts","SSLServerVerification",
"UnsupportedMultipartBoundaryChars","Compression"
};
bool HTTP::isDownload = false;
std::string HTTP::Get(const std::string &IP) {
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));
cli.set_follow_location(true);
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()]);
}
return Ret;
}
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;
if(!Fields.empty()) {
httplib::Result res = cli.Post(IP.substr(pos).c_str(), Fields, "application/json");
if(res.error() == 0) {
if (res->status != 200) {
error(res->reason);
}
Ret = res->body;
}else{
error("HTTP Post failed on " + Codes_[res.error()]);
}
}else{
httplib::Result res = cli.Post(IP.substr(pos).c_str());
if(res.error() == 0) {
if (res->status != 200) {
error(res->reason);
}
Ret = res->body;
}else{
error("HTTP Post failed on " + Codes_[res.error()]);
}
}
if(Ret.empty())return "-1";
else return Ret;
}
bool HTTP::ProgressBar(size_t c, size_t t){
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;
std::scoped_lock Guard(Lock);
isDownload = true;
std::string Ret = Get(IP);
isDownload = false;
if(Ret.empty())return false;
std::ofstream File(Path, std::ios::binary);
if(File.is_open()) {
File << Ret;
File.close();
std::cout << "\n";
info("Download Complete!");
}else{
error("Failed to open file directory: " + Path);
return false;
}
return true;
}
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Http.h"
#include <Logger.h>
#include <Network/network.hpp>
#include <Startup.h>
#include <Utils.h>
#include <cmath>
#include <curl/curl.h>
#include <curl/easy.h>
#include <filesystem>
#include <fstream>
#include <httplib.h>
#include <iostream>
#include <mutex>
#include <nlohmann/json.hpp>
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";
std::filesystem::create_directories(folder);
if (!std::filesystem::exists(folder / "WHAT IS THIS FOLDER.txt")) {
std::ofstream ignore { folder / "WHAT IS THIS FOLDER.txt" };
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;
}
const auto file = folder / (method + ".json");
// 1 MB limit
if (std::filesystem::exists(file) && std::filesystem::file_size(file) > 1'000'000) {
std::filesystem::rename(file, file.generic_string() + ".bak");
}
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, const std::string& Fields) {
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_CONNECTTIMEOUT, 10); // seconds
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
if (!Fields.empty()) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
}
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;
}
std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
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_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
struct curl_slist* list = nullptr;
list = curl_slist_append(list, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
res = curl_easy_perform(curl);
curl_slist_free_all(list);
if (res != CURLE_OK) {
error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
return "";
}
} else {
error("Curl easy init failed");
return "";
}
return Ret;
}
bool HTTP::Download(const std::string& IP, const std::string& Fields, const std::string& Path) {
static std::mutex Lock;
std::scoped_lock Guard(Lock);
info("Downloading an update (this may take a while)");
std::string Ret = Get(IP, Fields);
if (Ret.empty()) {
error("Download failed");
return false;
}
std::ofstream File(Path, std::ios::binary);
if (File.is_open()) {
File << Ret;
File.close();
info("Download Complete!");
} else {
error("Failed to open file directory: " + Path);
return false;
}
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("127.0.0.1");
debug("HTTP Proxy listening on port " + std::to_string(ProxyPort));
HTTPProxy.listen_after_bind();
});
proxy.detach();
}

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

@@ -1,155 +1,202 @@
// 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 4/11/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#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 <filesystem>
#include "Startup.h"
#include "Logger.h"
#include <iostream>
#elif defined(__linux__)
#include <arpa/inet.h>
#include <cstring>
#include <fstream>
#include <string>
#include <thread>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#endif
#include "Logger.h"
#include "Startup.h"
#include <Utils.h>
#include <atomic>
#include <vector>
#include <future>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <future>
#include <iostream>
#include <thread>
#include "hashpp.h"
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(){
if(!fs::exists("Resources")){
_wmkdir(L"Resources");
void CheckForDir() {
if (!fs::exists(CachingDirectory)) {
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(){
while(!Terminate && !ModLoaded){
void WaitForConfirm() {
while (!Terminate && !ModLoaded) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
ModLoaded = false;
}
void Abord(){
void Abord() {
Terminate = true;
TCPTerminate = true;
info("Terminated!");
}
std::string Auth(SOCKET Sock){
TCPSend("VC" + GetVer(),Sock);
std::string Auth(SOCKET Sock) {
TCPSend("VC" + GetVer(), Sock);
auto Res = TCPRcv(Sock);
if(Res.empty() || Res[0] == 'E' || Res[0] == 'K'){
if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
Abord();
CoreSend("L");
return "";
}
TCPSend(PublicKey,Sock);
if(Terminate)return "";
TCPSend(PublicKey, Sock);
if (Terminate) {
CoreSend("L");
return "";
}
Res = TCPRcv(Sock);
if(Res.empty() || Res[0] != 'P'){
if (Res.empty() || Res[0] != 'P') {
Abord();
CoreSend("L");
return "";
}
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);
}else{
} else {
Abord();
CoreSend("L");
UUl("Authentication failed!");
return "";
}
TCPSend("SR",Sock);
if(Terminate)return "";
Res = TCPRcv(Sock);
if(Res[0] == 'E' || Res[0] == 'K'){
Abord();
TCPSend("SR", Sock);
if (Terminate) {
CoreSend("L");
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...");
ListOfMods = "-";
TCPSend("Done",Sock);
CoreSend("L");
TCPSend("Done", Sock);
info("Done!");
return "";
}
return Res;
}
void UpdateUl(bool D,const std::string&msg){
if(D)UlStatus = "UlDownloading Resource " + msg;
else UlStatus = "UlLoading Resource " + msg;
void UpdateUl(bool D, const std::string& msg) {
if (D)
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 {
double pr = double(Rcv) / double(Size) * 100;
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));
}while(!Terminate && Rcv < Size);
} while (!Terminate && Rcv < Size);
}
char* TCPRcvRaw(SOCKET Sock,uint64_t& GRcv, uint64_t Size){
if(Sock == -1){
// MICROSOFT, I DONT CARE, WRITE BETTER CODE
#undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return nullptr;
return {};
}
char* File = new char[Size];
std::vector<char> File(Size);
uint64_t Rcv = 0;
do{
int Len = int(Size-Rcv);
if(Len > 1000000)Len = 1000000;
auto start = std::chrono::high_resolution_clock::now();
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);
if(Temp < 1){
if (Temp < 1) {
info(std::to_string(Temp));
UUl("Socket Closed Code 1");
KillSocket(Sock);
Terminate = true;
delete[] File;
return nullptr;
return {};
}
Rcv += 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;
}
void MultiKill(SOCKET Sock,SOCKET Sock1){
void MultiKill(SOCKET Sock, SOCKET Sock1) {
KillSocket(Sock1);
KillSocket(Sock);
Terminate = true;
}
SOCKET InitDSock(){
SOCKET InitDSock() {
SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN ServerAddr;
if(DSock < 1){
if (DSock < 1) {
KillSocket(DSock);
Terminate = true;
return 0;
@@ -157,13 +204,13 @@ SOCKET InitDSock(){
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(LastPort);
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);
Terminate = true;
return 0;
}
char Code[2] = {'D',char(ClientID)};
if(send(DSock,Code,2,0) != 2){
char Code[2] = { 'D', char(ClientID) };
if (send(DSock, Code, 2, 0) != 2) {
KillSocket(DSock);
Terminate = true;
return 0;
@@ -171,104 +218,394 @@ SOCKET InitDSock(){
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); });
std::packaged_task<char*()> task([&] { return TCPRcvRaw(MSock,GRcv,MSize); });
std::future<char*> f1 = task.get_future();
std::thread Dt(std::move(task));
Dt.detach();
const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, Size);
char* DData = TCPRcvRaw(DSock,GRcv,DSize);
if(!DData){
MultiKill(MSock,DSock);
return "";
if (MData.empty()) {
KillSocket(MSock);
Terminate = true;
Au.join();
return {};
}
f1.wait();
char* MData = f1.get();
if(!MData){
MultiKill(MSock,DSock);
return "";
// ensure that GRcv is good before joining the async update thread
GRcv = MData.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 {};
}
if(Au.joinable())Au.join();
///omg yes very ugly my god but i was in a rush will revisit
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;
Au.join();
return MData;
}
void InvalidResource(const std::string& File){
std::vector<char> MultiDownload(SOCKET MSock, SOCKET DSock, uint64_t Size, const std::string& Name) {
DownloadSpeed = 0;
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;
}
void SyncResources(SOCKET Sock){
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::pair<bool, std::vector<ModInfo>> ParseModInfosFromPacket(const std::string& packet) {
bool success = false;
std::vector<ModInfo> modInfos;
try {
auto json = nlohmann::json::parse(packet);
if (json.empty()) {
return std::make_pair(true, modInfos);
}
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);
success = true;
}
} 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 std::make_pair(success, 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 (ModInfos.empty()) {
CoreSend("L");
TCPSend("Done", Sock);
info("Done!");
return;
}
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);
if(Ret.empty())return;
debug("Mod info: " + Ret);
if (Ret.starts_with("R")) {
debug("This server is likely outdated, not trying to parse new mod info format");
} else {
auto [success, modInfo] = ModInfo::ParseModInfosFromPacket(Ret);
if (success) {
NewSyncResources(Sock, Ret, modInfo);
return;
}
}
if (Ret.empty())
return;
if (!SecurityWarning())
return;
info("Checking Resources...");
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> FSizes(list.begin() + (list.size() / 2), list.end());
list.clear();
Ret.clear();
int Amount = 0,Pos = 0;
std::string a,t;
for(const std::string&name : FNames){
if(!name.empty()){
int Amount = 0, Pos = 0;
std::string PathToSaveTo, t;
for (const std::string& name : FNames) {
if (!name.empty()) {
t += name.substr(name.find_last_of('/') + 1) + ";";
}
}
if(t.empty())ListOfMods = "-";
else ListOfMods = t;
if (t.empty())
CoreSend("L");
else
CoreSend("L" + t);
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 ZIP = FN->find(".zip");
if (ZIP == std::string::npos || FN->length() - ZIP != 4) {
InvalidResource(*FN);
return;
}
if (pos == std::string::npos)continue;
if (pos == std::string::npos)
continue;
Amount++;
}
if(!FNames.empty())info("Syncing...");
if (!FNames.empty())
info("Syncing...");
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('/');
if (pos != std::string::npos) {
a = "Resources" + FN->substr(pos);
} else continue;
PathToSaveTo = CachingDirectory + FN->substr(pos);
} else {
continue;
}
Pos++;
if (fs::exists(a)) {
if (FS->find_first_not_of("0123456789") != std::string::npos)continue;
if (fs::file_size(a) == std::stoull(*FS)){
UpdateUl(false,std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/')));
auto FileSize = std::stoull(*FS);
if (fs::exists(PathToSaveTo)) {
if (FS->find_first_not_of("0123456789") != std::string::npos)
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));
try {
if(!fs::exists(GetGamePath() + "mods/multiplayer")){
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
fs::copy_file(a, GetGamePath() + "mods/multiplayer" + a.substr(a.find_last_of('/')),
fs::copy_options::overwrite_existing);
auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
#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) {
error("Failed copy to the mods folder! " + std::string(e.what()));
Terminate = true;
@@ -276,15 +613,17 @@ void SyncResources(SOCKET Sock){
}
WaitForConfirm();
continue;
}else remove(a.c_str());
} else
remove(PathToSaveTo.c_str());
}
CheckForDir();
std::string FName = a.substr(a.find_last_of('/'));
std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
do {
TCPSend("f" + *FN,Sock);
debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + *FN, Sock);
std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate){
if (Data == "CO" || Terminate) {
Terminate = true;
UUl("Server cannot find " + FName);
break;
@@ -292,32 +631,46 @@ void SyncResources(SOCKET Sock){
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;
UpdateUl(false,std::to_string(Pos)+"/"+std::to_string(Amount)+": "+FName);
std::ofstream LFS;
LFS.open(a.c_str(), std::ios_base::app | std::ios::binary);
if (LFS.is_open()) {
LFS.write(&Data[0], Data.size());
LFS.close();
if (Terminate)
break;
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName);
// 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
}
}while(fs::file_size(a) != std::stoull(*FS) && !Terminate);
if(!Terminate){
if(!fs::exists(GetGamePath() + "mods/multiplayer")){
// 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) != std::stoull(*FS) && !Terminate);
if (!Terminate) {
if (!fs::exists(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();
}
KillSocket(DSock);
if(!Terminate){
TCPSend("Done",Sock);
if (!Terminate) {
TCPSend("Done", Sock);
info("Done!");
}else{
} else {
UlStatus = "Ulstart";
info("Connection Terminated!");
}
}
}

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

@@ -1,73 +1,106 @@
// 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 5/8/2020
///
#include "Zlib/Compressor.h"
#include "Network/network.h"
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Network/network.hpp"
#include "Zlib/Compressor.h"
#include <stdexcept>
#if defined(_WIN32)
#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 <array>
#include <string>
#include <set>
SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr;
void UDPSend(std::string Data){
if(ClientID == -1 || UDPSock == -1)return;
if(Data.length() > 400){
std::string CMP(Comp(Data));
Data = "ABG:" + CMP;
void UDPSend(std::string Data) {
if (ClientID == -1 || UDPSock == -1)
return;
if (Data.length() > 400) {
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));
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){
if(Data.length() > 400){
std::string CMP(Comp(Data));
Data = "ABG:" + CMP;
void SendLarge(std::string Data) {
if (Data.length() > 400) {
auto res = Comp(std::span<char>(Data.data(), Data.size()));
Data = "ABG:" + std::string(res.data(), res.size());
}
TCPSend(Data,TCPSock);
TCPSend(Data, TCPSock);
}
void UDPParser(std::string Packet){
if(Packet.substr(0,4) == "ABG:"){
Packet = DeComp(Packet.substr(4));
void UDPParser(std::string_view Packet) {
if (Packet.substr(0, 4) == "ABG:") {
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(){
sockaddr_in FromServer{};
void UDPRcv() {
sockaddr_in FromServer {};
#if defined(_WIN32)
int clientLength = sizeof(FromServer);
#elif defined(__linux__)
socklen_t clientLength = sizeof(FromServer);
#endif
ZeroMemory(&FromServer, clientLength);
std::string Ret(10240,0);
if(UDPSock == -1)return;
int32_t Rcv = recvfrom(UDPSock, &Ret[0], 10240, 0, (sockaddr*)&FromServer, &clientLength);
if (Rcv == SOCKET_ERROR)return;
UDPParser(Ret.substr(0,Rcv));
static thread_local std::array<char, 10240> Ret {};
if (UDPSock == -1)
return;
int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
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;
if (WSAStartup(514, &data)){
if (WSAStartup(514, &data)) {
error("Can't start Winsock!");
return;
}
#endif
delete ToServer;
ToServer = new sockaddr_in;
ToServer->sin_family = AF_INET;
ToServer->sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr);
UDPSock = socket(AF_INET, SOCK_DGRAM, 0);
GameSend("P"+std::to_string(ClientID));
TCPSend("H",TCPSock);
GameSend("P" + std::to_string(ClientID));
TCPSend("H", TCPSock);
UDPSend("p");
while(!Terminate)UDPRcv();
debug("Starting UDP receive loop");
while (!Terminate) {
UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock);
WSACleanup();
}
}

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

@@ -1,31 +1,39 @@
// 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 5/8/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <chrono>
#include <vector>
#include "Logger.h"
#include <iostream>
#include <ws2tcpip.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;
std::string LastIP;
SOCKET TCPSock = -1;
bool CheckBytes(int32_t Bytes){
if (Bytes == 0){
bool CheckBytes(int32_t Bytes) {
if (Bytes == 0) {
debug("(TCP) Connection closing... CheckBytes(16)");
Terminate = true;
return false;
}else if (Bytes < 0) {
} else if (Bytes < 0) {
debug("(TCP CB) recv failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock);
Terminate = true;
@@ -33,110 +41,117 @@ bool CheckBytes(int32_t Bytes){
}
return true;
}
void UUl(const std::string& R){
void UUl(const std::string& R) {
UlStatus = "UlDisconnected: " + R;
}
void TCPSend(const std::string&Data,uint64_t Sock){
if(Sock == -1){
Terminate = true;
UUl("Invalid Socket");
return;
}
void TCPSend(const std::string& Data, uint64_t Sock) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return;
}
int32_t Size,Sent,Temp;
std::string Send(4,0);
Size = int32_t(Data.size());
memcpy(&Send[0],&Size,sizeof(Size));
Send += Data;
// Do not use Size before this point for anything but the header
Sent = 0;
Size += 4;
do{
if (size_t(Sent) >= Send.size()) {
error("string OOB in " + std::string(__func__));
UUl("TCP Send OOB");
return;
}
Temp = send(Sock, &Send[Sent], Size - Sent, 0);
if(!CheckBytes(Temp)){
UUl("Socket Closed Code 2");
return;
}
Sent += Temp;
}while(Sent < Size);
int32_t Size, Sent, Temp;
std::string Send(4, 0);
Size = int32_t(Data.size());
memcpy(&Send[0], &Size, sizeof(Size));
Send += Data;
// Do not use Size before this point for anything but the header
Sent = 0;
Size += 4;
do {
if (size_t(Sent) >= Send.size()) {
error("string OOB in " + std::string(__func__));
UUl("TCP Send OOB");
return;
}
Temp = send(Sock, &Send[Sent], Size - Sent, 0);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 2");
return;
}
Sent += Temp;
} while (Sent < Size);
}
std::string TCPRcv(SOCKET Sock){
if(Sock == -1){
std::string TCPRcv(SOCKET Sock) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return "";
}
int32_t Header,BytesRcv = 0,Temp;
int32_t Header, Temp;
std::vector<char> Data(sizeof(Header));
do{
Temp = recv(Sock,&Data[BytesRcv],4-BytesRcv,0);
if(!CheckBytes(Temp)){
UUl("Socket Closed Code 3");
return "";
}
BytesRcv += Temp;
}while(BytesRcv < 4);
memcpy(&Header,&Data[0],sizeof(Header));
Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 3");
return "";
}
memcpy(&Header, Data.data(), sizeof(Header));
if(!CheckBytes(BytesRcv)){
if (!CheckBytes(Temp)) {
UUl("Socket Closed Code 4");
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:") {
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
//debug("Parsing from server -> " + std::to_string(Ret.size()));
// debug("Parsing from server -> " + std::to_string(Ret.size()));
#endif
if(Ret[0] == 'E' || Ret[0] == 'K')UUl(Ret.substr(1));
if (Ret[0] == 'E' || Ret[0] == 'K')
UUl(Ret.substr(1));
return Ret;
}
void TCPClientMain(const std::string& IP,int Port){
void TCPClientMain(const std::string& IP, int Port) {
LastIP = IP;
LastPort = Port;
WSADATA wsaData;
SOCKADDR_IN ServerAddr;
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);
if(TCPSock == -1){
if (TCPSock == -1) {
printf("Client: socket failed! Error code: %d\n", WSAGetLastError());
WSACleanup();
return;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(Port);
inet_pton(AF_INET, IP.c_str(), &ServerAddr.sin_addr);
RetCode = connect(TCPSock, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr));
if(RetCode != 0){
RetCode = connect(TCPSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
if (RetCode != 0) {
UlStatus = "UlConnection Failed!";
error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock);
WSACleanup();
Terminate = true;
CoreSend("L");
return;
}
info("Connected!");
@@ -144,14 +159,16 @@ void TCPClientMain(const std::string& IP,int Port){
char Code = 'C';
send(TCPSock, &Code, 1, 0);
SyncResources(TCPSock);
while(!Terminate){
while (!Terminate) {
ServerParser(TCPRcv(TCPSock));
}
GameSend("T");
////Game Send Terminate
if(KillSocket(TCPSock) != 0)
if (KillSocket(TCPSock) != 0)
debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError()));
if(WSACleanup() != 0)
#ifdef _WIN32
if (WSACleanup() != 0)
debug("(TCP) Client: WSACleanup() failed!...");
#endif
}

113
src/Options.cpp Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Options.h"
#include "Logger.h"
#include <cstdlib>
#include <filesystem>
void InitOptions(int argc, const char *argv[], Options &options) {
int i = 1;
options.argc = argc;
options.argv = argv;
std::string AllOptions;
for (int i = 0; i < argc; ++i) {
AllOptions += std::string(argv[i]);
if (i + 1 < argc) {
AllOptions += " ";
}
}
debug("Launcher was invoked as: '" + AllOptions + "'");
if (argc > 2) {
if (std::string(argv[1]) == "0" && std::string(argv[2]) == "0") {
options.verbose = true;
options.no_download = true;
options.no_launch = true;
options.no_update = true;
warn("You are using deprecated commandline arguments, please use --dev instead");
return;
}
}
options.executable_name = std::string(argv[0]);
while (i < argc) {
std::string argument(argv[i]);
if (argument == "-p" || argument == "--port") {
if (i + 1 >= argc) {
std::string error_message =
"No port specified, resorting to default (";
error_message += std::to_string(options.port);
error_message += ")";
error(error_message);
i++;
continue;
}
int port = options.port;
try {
port = std::stoi(argv[i + 1]);
} catch (std::exception& e) {
error("Invalid port specified: " + std::string(argv[i + 1]) + " " + std::string(e.what()));
}
if (port <= 0) {
std::string error_message =
"Port invalid, must be a non-zero positive "
"integer, resorting to default (";
error_message += options.port;
error_message += ")";
error(error_message);
i++;
continue;
}
options.port = port;
i++;
} else if (argument == "-v" || argument == "--verbose") {
options.verbose = true;
} else if (argument == "--no-download") {
options.no_download = true;
} else if (argument == "--no-update") {
options.no_update = true;
} else if (argument == "--no-launch") {
options.no_launch = true;
} else if (argument == "--dev") {
options.verbose = true;
options.no_download = true;
options.no_launch = true;
options.no_update = true;
} else if (argument == "--" || argument == "--game") {
options.game_arguments = &argv[i + 1];
options.game_arguments_length = argc - i - 1;
break;
} else if (argument == "--help" || argument == "-h" || argument == "/?") {
std::cout << "USAGE:\n"
"\t" + std::filesystem::path(options.executable_name).filename().string() + " [OPTIONS] [-- <GAME ARGS>...]\n"
"\n"
"OPTIONS:\n"
"\t--port <port> -p Change the default listen port to <port>. This must be configured ingame, too\n"
"\t--verbose -v Verbose mode, prints debug messages\n"
"\t--no-download Skip downloading and installing the BeamMP Lua mod\n"
"\t--no-update Skip applying launcher updates (you must update manually)\n"
"\t--no-launch Skip launching the game (you must launch the game manually)\n"
"\t--dev Developer mode, same as --verbose --no-download --no-launch --no-update\n"
"\t--game <args...> Passes ALL following arguments to the game, see also `--`\n"
<< std::flush;
exit(0);
} else {
warn("Unknown option: " + argument);
}
i++;
}
}

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

@@ -1,16 +1,21 @@
// 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/18/2020
///
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <filesystem>
#if defined(_WIN32)
#include <windows.h>
#elif defined(__linux__)
#include "vdf_parser.hpp"
#include <pwd.h>
#include <unistd.h>
#include <vector>
#endif
#include "Logger.h"
#include <fstream>
#include <sstream>
#include <string>
#include <thread>
@@ -20,299 +25,226 @@
int TraceBack = 0;
std::string GameDir;
void lowExit(int code){
void lowExit(int code) {
TraceBack = 0;
std::string msg =
"Failed to find the game please launch it. Report this if the issue persists code ";
error(msg+std::to_string(code));
std::string msg = "Failed to find the game please launch it. Report this if the issue persists code ";
error(msg + std::to_string(code));
std::this_thread::sleep_for(std::chrono::seconds(10));
exit(2);
}
/*void Exit(int code){
TraceBack = 0;
std::string msg =
"Sorry. We do not support cracked copies 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(3);
std::string GetGameDir() {
#if defined(_WIN32)
return GameDir.substr(0, GameDir.find_last_of('\\'));
#elif defined(__linux__)
return GameDir.substr(0, GameDir.find_last_of('/'));
#endif
}
void SteamExit(int code){
TraceBack = 0;
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){
#ifdef _WIN32
LONG OpenKey(HKEY root, const char* path, PHKEY hKey) {
return RegOpenKeyEx(root, reinterpret_cast<LPCSTR>(path), 0, KEY_READ, hKey);
}
std::string QueryKey(HKEY hKey,int ID){
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
DWORD cbName; // size of name string
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys=0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cValues; // number of values for key
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
std::string QueryKey(HKEY hKey, int ID) {
TCHAR achKey[MAX_KEY_LENGTH]; // buffer for subkey name
DWORD cbName; // size of name string
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys = 0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cValues; // number of values for key
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
DWORD i, retCode;
TCHAR achValue[MAX_VALUE_NAME];
TCHAR achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
retCode = RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
nullptr, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
nullptr, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
BYTE* buffer = new BYTE[cbMaxValueData];
ZeroMemory(buffer, cbMaxValueData);
if (cSubKeys){
for (i=0; i<cSubKeys; i++){
if (cSubKeys) {
for (i = 0; i < cSubKeys; i++) {
cbName = MAX_KEY_LENGTH;
retCode = RegEnumKeyEx(hKey, i,achKey,&cbName,nullptr,nullptr,nullptr,&ftLastWriteTime);
if (retCode == ERROR_SUCCESS){
if(strcmp(achKey,"Steam App 284160") == 0){
retCode = RegEnumKeyEx(hKey, i, achKey, &cbName, nullptr, nullptr, nullptr, &ftLastWriteTime);
if (retCode == ERROR_SUCCESS) {
if (strcmp(achKey, "Steam App 284160") == 0) {
return achKey;
}
}
}
}
if (cValues){
for (i=0, retCode = ERROR_SUCCESS; i<cValues; i++){
if (cValues) {
for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++) {
cchValue = MAX_VALUE_NAME;
achValue[0] = '\0';
retCode = RegEnumValue(hKey, i,achValue,&cchValue,nullptr,nullptr,nullptr,nullptr);
if (retCode == ERROR_SUCCESS ){
retCode = RegEnumValue(hKey, i, achValue, &cchValue, nullptr, nullptr, nullptr, nullptr);
if (retCode == ERROR_SUCCESS) {
DWORD lpData = cbMaxValueData;
buffer[0] = '\0';
LONG dwRes = RegQueryValueEx(hKey, achValue, nullptr, nullptr, buffer, &lpData);
std::string data = (char *)(buffer);
std::string data = (char*)(buffer);
std::string key = achValue;
switch (ID){
case 1: if(key == "SteamExe"){
auto p = data.find_last_of("/\\");
if(p != std::string::npos){
return data.substr(0,p);
}
}
break;
case 2: if(key == "Name" && data == "BeamNG.drive")return data;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;
switch (ID) {
case 1:
if (key == "SteamExe") {
auto p = data.find_last_of("/\\");
if (p != std::string::npos) {
return data.substr(0, p);
}
}
break;
case 2:
if (key == "Name" && data == "BeamNG.drive")
return data;
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 "";
}
#endif
namespace fs = std::filesystem;
bool NameValid(const std::string& N){
if(N == "config" || N == "librarycache"){
bool NameValid(const std::string& N) {
if (N == "config" || N == "librarycache") {
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 false;
}
void FileList(std::vector<std::string>&a,const std::string& Path){
for (const auto &entry : fs::directory_iterator(Path)) {
void FileList(std::vector<std::string>& a, const std::string& Path) {
for (const auto& entry : fs::directory_iterator(Path)) {
const auto& DPath = entry.path();
if (!entry.is_directory()) {
a.emplace_back(DPath.u8string());
}else if(NameValid(DPath.filename().u8string())){
FileList(a, DPath.u8string());
a.emplace_back(DPath.string());
} else if (NameValid(DPath.filename().string())) {
FileList(a, DPath.string());
}
}
}
bool Find(const std::string& FName,const std::string& Path){
std::vector<std::string> FS;
FileList(FS,Path+"\\userdata");
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 K1 = R"(Software\Valve\Steam)";
//std::string K2 = R"(Software\Valve\Steam\Apps\284160)";
/*LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K1.c_str(), &hKey);
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);*/
void LegitimacyCheck() {
#if defined(_WIN32)
std::string Result;
std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
HKEY hKey;
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
if(dwRegOPenKey == ERROR_SUCCESS) {
if (dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 3);
if(Result.empty())lowExit(3);
//if(IDCheck(Result,T))lowExit(5);
if (Result.empty()) {
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(3);
}
GameDir = Result;
//TraceBack++;
}else lowExit(4);
} else {
debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(4);
}
K3.clear();
Result.clear();
RegCloseKey(hKey);
//if(TraceBack < 3)exit(-1);
#elif defined(__linux__)
struct passwd* pw = getpwuid(getuid());
std::filesystem::path homeDir = pw->pw_dir;
// Right now only steam is supported
std::vector<std::filesystem::path> steamappsCommonPaths = {
".steam/root/steamapps", // default
".var/app/com.valvesoftware.Steam/.steam/root/steamapps", // flatpak
"snap/steam/common/.local/share/Steam/steamapps" // snap
};
std::filesystem::path steamappsPath;
std::filesystem::path libraryFoldersPath;
bool steamappsFolderFound = false;
bool libraryFoldersFound = false;
for (const auto& path : steamappsCommonPaths) {
steamappsPath = homeDir / path;
if (std::filesystem::exists(steamappsPath)) {
steamappsFolderFound = true;
libraryFoldersPath = steamappsPath / "libraryfolders.vdf";
if (std::filesystem::exists(libraryFoldersPath)) {
libraryFoldersPath = libraryFoldersPath;
libraryFoldersFound = true;
break;
}
}
}
if (!steamappsFolderFound) {
error("Unsupported Steam installation.");
return;
}
if (!libraryFoldersFound) {
error("libraryfolders.vdf is missing.");
return;
}
std::ifstream libraryFolders(libraryFoldersPath);
auto root = tyti::vdf::read(libraryFolders);
for (auto folderInfo : root.childs) {
if (std::filesystem::exists(folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/integrity.json")){
GameDir = folderInfo.second->attribs["path"] + "/steamapps/common/BeamNG.drive/";
break;
}
}
if (GameDir.empty()) {
error("The game directory was not found.");
return;
}
#endif
}
std::string CheckVer(const std::string &dir){
std::string temp,Path = dir + "\\integrity.json";
std::string CheckVer(const std::string& dir) {
#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);
int Size = int(std::filesystem::file_size(Path));
std::string vec(Size,0);
std::string vec(Size, 0);
f.read(&vec[0], Size);
f.close();
vec = vec.substr(vec.find_last_of("version"),vec.find_last_of('"'));
for(const char &a : vec){
if(isdigit(a) || a == '.')temp+=a;
vec = vec.substr(vec.find_last_of("version"), vec.find_last_of('"'));
for (const char& a : vec) {
if (isdigit(a) || a == '.')
temp += a;
}
return temp;
}
}

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

@@ -1,122 +1,150 @@
// 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/26/2020
///
#include "Http.h"
#include <filesystem>
#include "Logger.h"
#include <fstream>
#include "Json.h"
namespace fs = std::filesystem;
std::string PublicKey;
extern bool LoginAuth;
std::string Role;
void UpdateKey(const char* newKey){
if(newKey && std::isalnum(newKey[0])){
std::ofstream Key("key");
if(Key.is_open()){
Key << newKey;
Key.close();
}else fatal("Cannot write to disk!");
}else if(fs::exists("key")){
remove("key");
}
}
/// "username":"value","password":"value"
/// "Guest":"Name"
/// "pk":"private_key"
std::string GetFail(const std::string& R){
std::string DRet = R"({"success":false,"message":)";
DRet += "\""+R+"\"}";
error(R);
return DRet;
}
std::string Login(const std::string& fields){
if(fields == "LO"){
LoginAuth = false;
UpdateKey(nullptr);
return "";
}
info("Attempting to authenticate...");
std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields);
json::Document d;
d.Parse(Buffer.c_str());
if(Buffer == "-1"){
return GetFail("Failed to communicate with the auth system!");
}
if (Buffer.at(0) != '{' || d.HasParseError()) {
error(Buffer);
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();
}
info("Authentication successful!");
}else info("Authentication failed!");
if(!d["message"].IsNull()){
d.RemoveMember("private_key");
d.RemoveMember("public_key");
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
d.Accept(writer);
return buffer.GetString();
}
return GetFail("Invalid message parsing!");
}
void CheckLocalKey(){
if(fs::exists("key") && fs::file_size("key") < 100){
std::ifstream Key("key");
if(Key.is_open()) {
auto Size = fs::file_size("key");
std::string Buffer(Size, 0);
Key.read(&Buffer[0], Size);
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 + "\"}");
json::Document d;
d.Parse(Buffer.c_str());
if (Buffer == "-1" || Buffer.at(0) != '{' || d.HasParseError()) {
error(Buffer);
info("Invalid answer from authentication servers.");
UpdateKey(nullptr);
}
if(d["success"].GetBool()){
LoginAuth = true;
UpdateKey(d["private_key"].GetString());
PublicKey = d["public_key"].GetString();
Role = d["role"].GetString();
//info(Role);
}else{
info("Auto-Authentication unsuccessful please re-login!");
UpdateKey(nullptr);
}
}else{
warn("Could not open saved key!");
UpdateKey(nullptr);
}
}else UpdateKey(nullptr);
}
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Http.h"
#include "Logger.h"
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>
namespace fs = std::filesystem;
std::string PublicKey;
std::string PrivateKey;
extern bool LoginAuth;
extern std::string Username;
extern std::string UserRole;
extern int UserID;
void UpdateKey(const char* newKey) {
if (newKey && std::isalnum(newKey[0])) {
PrivateKey = newKey;
std::ofstream Key("key");
if (Key.is_open()) {
Key << newKey;
Key.close();
} else
fatal("Cannot write to disk!");
} else if (fs::exists("key")) {
remove("key");
}
}
/// "username":"value","password":"value"
/// "Guest":"Name"
/// "pk":"private_key"
std::string GetFail(const std::string& R) {
std::string DRet = R"({"success":false,"message":)";
DRet += "\"" + R + "\"}";
error(R);
return DRet;
}
std::string Login(const std::string& fields) {
if (fields == "LO") {
Username = "";
UserRole = "";
UserID = -1;
LoginAuth = false;
UpdateKey(nullptr);
return "";
}
info("Attempting to authenticate...");
try {
std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields);
if (Buffer.empty()) {
return GetFail("Failed to communicate with the auth system!");
}
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!");
}
if (d.contains("success") && d["success"].get<bool>()) {
LoginAuth = true;
if (d.contains("username")) {
Username = d["username"].get<std::string>();
}
if (d.contains("role")) {
UserRole = d["role"].get<std::string>();
}
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");
debug("Authentication result: " + d["message"].get<std::string>());
return d.dump();
}
return GetFail("Invalid message parsing!");
} catch (const std::exception& e) {
return GetFail(e.what());
}
}
void CheckLocalKey() {
if (fs::exists("key") && fs::file_size("key") < 100) {
std::ifstream Key("key");
if (Key.is_open()) {
auto Size = fs::file_size("key");
std::string Buffer(Size, 0);
Key.read(&Buffer[0], Size);
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 + "\"}");
nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
if (Buffer.empty() || Buffer.at(0) != '{' || d.is_discarded()) {
error(Buffer);
info("Invalid answer from authentication servers.");
UpdateKey(nullptr);
}
if (d["success"].get<bool>()) {
LoginAuth = true;
UpdateKey(d["private_key"].get<std::string>().c_str());
PublicKey = d["public_key"].get<std::string>();
if (d.contains("username")) {
Username = d["username"].get<std::string>();
}
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!");
UpdateKey(nullptr);
}
} else {
warn("Could not open saved key!");
UpdateKey(nullptr);
}
} else
UpdateKey(nullptr);
}

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

@@ -1,280 +1,341 @@
// 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 "zip_file.h"
#include <windows.h>
#include "Discord/discord_info.h"
#include "Network/network.h"
#include "Security/Init.h"
#include <filesystem>
#include "Startup.h"
#include "Logger.h"
#include <fstream>
#include <thread>
#include "Http.h"
#include "Json.h"
extern int TraceBack;
bool Dev = false;
namespace fs = std::filesystem;
std::string GetEN(){
return "BeamMP-Launcher.exe";
}
std::string GetVer(){
return "2.0";
}
std::string GetPatch(){
return ".80";
}
std::string GetEP(char*P){
static std::string Ret = [&](){
std::string path(P);
return path.substr(0, path.find_last_of("\\/") + 1);
} ();
return Ret;
}
void ReLaunch(int argc,char*args[]){
std::string Arg;
for(int c = 2; c <= argc; c++){
Arg += " ";
Arg += args[c-1];
}
system("cls");
ShellExecute(nullptr,"runas",(GetEP() + GetEN()).c_str(),Arg.c_str(),nullptr,SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(),0);
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];
}
ShellExecute(nullptr,"open",(GetEP() + GetEN()).c_str(),Arg.c_str(),nullptr,SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(),0);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
void CheckName(int argc,char* args[]){
std::string DN = GetEN(),CDir = args[0],FN = CDir.substr(CDir.find_last_of('\\')+1);
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());
URelaunch(argc,args);
}
}
void CheckForUpdates(int argc,char*args[],const std::string& CV){
std::string link;
std::string HTTP = HTTP::Get("https://beammp.com/builds/launcher?version=true");
bool fallback = false;
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";
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
if(fs::exists(Back))remove(Back.c_str());
if(HTTP > CV){
system("cls");
info("Update found!");
info("Updating...");
if(std::rename(EP.c_str(), Back.c_str()))error("failed creating a backup!");
if(!HTTP::Download(link, EP)){
error("Launcher Update failed! trying again...");
std::this_thread::sleep_for(std::chrono::seconds(2));
if(!HTTP::Download(link, EP)){
error("Launcher Update failed!");
std::this_thread::sleep_for(std::chrono::seconds(5));
ReLaunch(argc,args);
}
}
URelaunch(argc,args);
}else info("Launcher version is up to date");
TraceBack++;
}
void CustomPort(int argc, char* argv[]){
if(argc > 1){
std::string Port = argv[1];
if(Port.find_first_not_of("0123456789") == std::string::npos){
if(std::stoi(Port) > 1000){
DEFAULT_PORT = std::stoi(Port);
warn("Running on custom port : " + std::to_string(DEFAULT_PORT));
}
}
if(argc > 2)Dev = true;
}
}
void LinuxPatch(){
HKEY hKey = nullptr;
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS || getenv("USER") == nullptr)return;
RegCloseKey(hKey);
info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit");
info("Applying patches...");
result = RegCreateKey(HKEY_CURRENT_USER, R"(Software\Valve\Steam\Apps\284160)", &hKey);
if (result != ERROR_SUCCESS){
fatal(R"(failed to create HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return;
}
result = RegSetValueEx(hKey, "Name", 0, REG_SZ, (BYTE*)"BeamNG.drive", 12);
if (result != ERROR_SUCCESS){
fatal(R"(failed to create the value "Name" under HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return;
}
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!");
}
void InitLauncher(int argc, char* argv[]) {
system("cls");
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog();
CheckName(argc, argv);
LinuxPatch();
CheckLocalKey();
ConfigInit();
CustomPort(argc, argv);
Discord_Main();
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
size_t DirCount(const std::filesystem::path& path){
return (size_t)std::distance(std::filesystem::directory_iterator{path}, std::filesystem::directory_iterator{});
}
void CheckMP(const std::string& Path) {
if (!fs::exists(Path))return;
size_t c = DirCount(fs::path(Path));
try {
for (auto& p : fs::directory_iterator(Path)){
if(p.exists() && !p.is_directory()){
std::string Name = p.path().filename().u8string();
for(char&Ch : Name)Ch = char(tolower(Ch));
if(Name != "beammp.zip")fs::remove(p.path());
}
}
} catch (...) {
fatal("Please close the game, and try again!");
}
}
void EnableMP(){
std::string File(GetGamePath() + "mods/db.json");
if(!fs::exists(File))return;
auto Size = fs::file_size(File);
if(Size < 2)return;
std::ifstream db(File);
if(db.is_open()) {
std::string Data(Size, 0);
db.read(&Data[0], Size);
db.close();
json::Document d;
d.Parse(Data.c_str());
if(Data.at(0) != '{' || d.HasParseError()){
//error("Failed to parse " + File); //TODO illegal formatting
return;
}
if(!d["mods"].IsNull() && !d["mods"]["multiplayerbeammp"].IsNull()){
d["mods"]["multiplayerbeammp"]["active"] = true;
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
d.Accept(writer);
std::ofstream ofs(File);
if(ofs.is_open()){
ofs << buffer.GetString();
ofs.close();
}else{
error("Failed to write " + File);
}
}
}
}
void PreGame(const std::string& GamePath){
const std::string CurrVer("0.26.1.0");
std::string GameVer = CheckVer(GamePath);
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) {
info("Downloading mod please wait...");
try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
EnableMP();
}catch(std::exception&e){
fatal(e.what());
}
std::string ZipPath(GetGamePath() + R"(mods\multiplayer\BeamMP.zip)");
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);
}
//HTTP::Download("beammp.com/builds/client", GetGamePath() + R"(mods\multiplayer\BeamMP.zip)");
}
}
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "zip_file.h"
#include <charconv>
#include <cstring>
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <string>
#if defined(_WIN32)
#include <windows.h>
#elif defined(__linux__)
#include <unistd.h>
#endif
#include "Http.h"
#include "Logger.h"
#include "Network/network.hpp"
#include "Security/Init.h"
#include "Startup.h"
#include "hashpp.h"
#include <filesystem>
#include <fstream>
#include <thread>
#include "Options.h"
extern int TraceBack;
int ProxyPort = 0;
namespace fs = std::filesystem;
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";
#elif defined(__linux__)
return "BeamMP-Launcher";
#endif
}
std::string GetVer() {
return "2.3";
}
std::string GetPatch() {
return ".2";
}
std::string GetEP(const char* P) {
static std::string Ret = [&]() {
std::string path(P);
return path.substr(0, path.find_last_of("\\/") + 1);
}();
return Ret;
}
#if defined(_WIN32)
void ReLaunch() {
std::string Arg;
for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " ";
}
info("Relaunch!");
system("cls");
ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
void URelaunch() {
std::string Arg;
for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " ";
}
ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
#elif defined(__linux__)
void ReLaunch() {
std::string Arg;
for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " ";
}
info("Relaunch!");
system("clear");
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
if (ret < 0) {
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
exit(1);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
void URelaunch() {
int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
if (ret < 0) {
error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
exit(1);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1);
}
#endif
void CheckName() {
#if defined(_WIN32)
std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('\\') + 1);
#elif defined(__linux__)
std::string DN = GetEN(), CDir = options.executable_name, 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();
std::rename(FN.c_str(), DN.c_str());
URelaunch();
}
}
void CheckForUpdates(const std::string& CV) {
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher", requestBody);
std::string LatestVersion = HTTP::Get("https://backend.beammp.com/version/launcher", requestBody);
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
if (!options.no_update) {
info("Launcher update found!");
#if defined(__linux__)
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
#else
fs::remove(Back);
fs::rename(EP, Back);
info("Downloading Launcher update " + LatestHash);
HTTP::Download("https://backend.beammp.com/builds/launcher?download=true", requestBody, EP);
URelaunch();
#endif
} else {
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
}
} else
info("Launcher version is up to date");
TraceBack++;
}
#ifdef _WIN32
void LinuxPatch() {
HKEY hKey = nullptr;
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\Wine)", 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS || getenv("USER") == nullptr)
return;
RegCloseKey(hKey);
info("Wine/Proton Detected! If you are on windows delete HKEY_CURRENT_USER\\Software\\Wine in regedit");
info("Applying patches...");
result = RegCreateKey(HKEY_CURRENT_USER, R"(Software\Valve\Steam\Apps\284160)", &hKey);
if (result != ERROR_SUCCESS) {
fatal(R"(failed to create HKEY_CURRENT_USER\Software\Valve\Steam\Apps\284160)");
return;
}
result = RegSetValueEx(hKey, "Name", 0, REG_SZ, (BYTE*)"BeamNG.drive", 12);
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!");
}
#endif
#if defined(_WIN32)
void InitLauncher() {
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
CheckName();
LinuxPatch();
CheckLocalKey();
CheckForUpdates(std::string(GetVer()) + GetPatch());
}
#elif defined(__linux__)
void InitLauncher() {
info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName();
CheckLocalKey();
CheckForUpdates(std::string(GetVer()) + GetPatch());
}
#endif
size_t DirCount(const std::filesystem::path& path) {
return (size_t)std::distance(std::filesystem::directory_iterator { path }, std::filesystem::directory_iterator {});
}
void CheckMP(const std::string& Path) {
if (!fs::exists(Path))
return;
size_t c = DirCount(fs::path(Path));
try {
for (auto& p : fs::directory_iterator(Path)) {
if (p.exists() && !p.is_directory()) {
std::string Name = p.path().filename().string();
for (char& Ch : Name)
Ch = char(tolower(Ch));
if (Name != "beammp.zip")
fs::remove(p.path());
}
}
} catch (...) {
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() {
std::string File(GetGamePath() + "mods/db.json");
if (!fs::exists(File))
return;
auto Size = fs::file_size(File);
if (Size < 2)
return;
std::ifstream db(File);
if (db.is_open()) {
std::string Data(Size, 0);
db.read(&Data[0], Size);
db.close();
nlohmann::json d = nlohmann::json::parse(Data, nullptr, false);
if (Data.at(0) != '{' || d.is_discarded()) {
// error("Failed to parse " + File); //TODO illegal formatting
return;
}
if (d.contains("mods") && d["mods"].contains("multiplayerbeammp")) {
d["mods"]["multiplayerbeammp"]["active"] = true;
std::ofstream ofs(File);
if (ofs.is_open()) {
ofs << d.dump();
ofs.close();
} else {
error("Failed to write " + File);
}
}
}
}
void PreGame(const std::string& GamePath) {
std::string GameVer = CheckVer(GamePath);
info("Game Version : " + GameVer);
CheckMP(GetGamePath() + "mods/multiplayer");
info("Game user path: " + GetGamePath());
if (!options.no_download) {
std::string requestBody = R"({"branch": ")" + Branch + R"(","pk":")" + PublicKey + R"("})";
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod", requestBody);
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 {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer");
}
EnableMP();
} catch (std::exception& e) {
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
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, ZipPath);
if (FileHash != LatestHash) {
info("Downloading BeamMP Update " + LatestHash);
HTTP::Download("https://backend.beammp.com/builds/client?download=true", requestBody, ZipPath);
}
std::string Target(GetGamePath() + "mods/unpacked/beammp");
if (fs::is_directory(Target)) {
fs::remove_all(Target);
}
}
}

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

@@ -1,43 +1,69 @@
// 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 "Network/network.h"
/*
Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors.
Licensed under AGPL-3.0 (or later), see <https://www.gnu.org/licenses/>.
SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "Http.h"
#include "Logger.h"
#include "Network/network.hpp"
#include "Security/Init.h"
#include "Startup.h"
#include <curl/curl.h>
#include <iostream>
#include "Logger.h"
#include <thread>
#include "Http.h"
#include "Options.h"
[[noreturn]] void flush(){
while(true){
Options options;
[[noreturn]] void flush() {
while (true) {
std::cout.flush();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main(int argc, char* argv[]) {
#ifdef DEBUG
std::thread th(flush);
th.detach();
#endif
int main(int argc, const char** argv) try {
#if defined(_WIN32)
system("cls");
#elif defined(__linux__)
system("clear");
#endif
#ifdef DEBUG
std::thread th(flush);
th.detach();
#endif
curl_global_init(CURL_GLOBAL_ALL);
GetEP(argv[0]);
InitLauncher(argc,argv);
InitLog();
ConfigInit();
InitOptions(argc, argv, options);
InitLauncher();
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
try {
LegitimacyCheck();
}catch (std::exception& e){
fatal("Main 1 : " + std::string(e.what()));
} catch (std::exception& e) {
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());
InitGame(GetGameDir());
CoreNetwork();
///TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...)
} catch (const std::exception& e) {
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"
]
}