129 Commits

Author SHA1 Message Date
Tixx
4f03d21dea Move mod caching directory log 2024-11-14 11:12:19 +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
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
32 changed files with 1572 additions and 1231 deletions

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.

View File

@@ -36,7 +36,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: BeamMP-Launcher name: BeamMP-Launcher
path: ${{github.workspace}}/build-linux/BeamMP-Launcher path: ${{github.workspace}}/build-linux/BeamMP-Launcher

View File

@@ -37,7 +37,7 @@ jobs:
run: cmake --build . --config $BUILD_TYPE run: cmake --build . --config $BUILD_TYPE
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: BeamMP-Launcher.exe name: BeamMP-Launcher.exe
path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe path: ${{github.workspace}}/build-windows/Release/BeamMP-Launcher.exe

View File

@@ -1,104 +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
- `BeamMP-Launcher` linux 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 nlohmann-json openssl cpp-httplib[openssl]'
vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg'
vcpkgGitCommitId: '16ee2ecb31788c336ace8bb14c21801efb6836e4'
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
upload-release-files-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- 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
- name: Build
working-directory: ${{github.workspace}}/build-linux
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-linux/BeamMP-Launcher
asset_name: BeamMP-Launcher
asset_content_type: application/octet-stream

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ Resources/
bin/ bin/
compile_commands.json compile_commands.json
key key
out/

View File

@@ -12,11 +12,12 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
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") 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(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED)
find_package(asio CONFIG REQUIRED) find_package(CURL REQUIRED)
find_package(fmt CONFIG REQUIRED)
add_executable(${PROJECT_NAME} ${source_files}) add_executable(${PROJECT_NAME} ${source_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher") set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "BeamMP-Launcher")
@@ -25,15 +26,15 @@ if (WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json asio::asio fmt::fmt) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto ws2_32 httplib::httplib nlohmann_json::nlohmann_json CURL::libcurl)
elseif (LINUX) elseif (LINUX)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto asio::asio fmt::fmt) ZLIB::ZLIB OpenSSL::SSL OpenSSL::Crypto CURL::libcurl)
elseif (WIN32) #MINGW else(WIN32) #MINGW
add_definitions("-D_WIN32_WINNT=0x0600") add_definitions("-D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s --static")
target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z asio::asio fmt::fmt) target_link_libraries(${PROJECT_NAME} ssl crypto ws2_32 ssp crypt32 z CURL::libcurl)
endif(WIN32) endif(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE "include") target_include_directories(${PROJECT_NAME} PRIVATE "include")

View File

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

View File

@@ -1,11 +0,0 @@
#include <span>
#include <string>
#include <vector>
#pragma once
using ByteSpan = std::span<const char>;
std::string bytespan_to_string(ByteSpan span);
std::vector<char> strtovec(std::string_view str);

View File

@@ -14,7 +14,7 @@ public:
static std::string Post(const std::string& IP, const std::string& Fields); static std::string Post(const std::string& IP, const std::string& Fields);
static std::string Get(const std::string& IP); static std::string Get(const std::string& IP);
static bool ProgressBar(size_t c, size_t t); static bool ProgressBar(size_t c, size_t t);
static void StartProxy();
public: public:
static bool isDownload; static bool isDownload;
}; };

View File

@@ -7,10 +7,6 @@
/// ///
#pragma once #pragma once
#include "Helpers.h"
#include "asio/io_context.hpp"
#include "asio/ip/address.hpp"
#include <span>
#include <string> #include <string>
#ifdef __linux__ #ifdef __linux__
@@ -18,13 +14,8 @@
#include <bits/types/siginfo_t.h> #include <bits/types/siginfo_t.h>
#include <cstdint> #include <cstdint>
#include <sys/ucontext.h> #include <sys/ucontext.h>
#include <vector>
#endif #endif
#include <asio.hpp>
extern asio::io_context io;
void NetReset(); void NetReset();
extern bool Dev; extern bool Dev;
extern int ping; extern int ping;
@@ -35,32 +26,30 @@ extern int ClientID;
extern int LastPort; extern int LastPort;
extern bool ModLoaded; extern bool ModLoaded;
extern bool Terminate; extern bool Terminate;
extern int DEFAULT_PORT; extern uint64_t UDPSock;
extern std::shared_ptr<asio::ip::udp::socket> UDPSock; extern uint64_t TCPSock;
extern std::shared_ptr<asio::ip::tcp::socket> TCPSock;
extern std::string Branch; extern std::string Branch;
extern std::string CachingDirectory;
extern bool TCPTerminate; extern bool TCPTerminate;
extern std::string LastIP; extern std::string LastIP;
extern std::string MStatus; extern std::string MStatus;
extern std::string UlStatus; extern std::string UlStatus;
extern std::string PublicKey; extern std::string PublicKey;
extern std::string PrivateKey; extern std::string PrivateKey;
extern std::string ListOfMods; int KillSocket(uint64_t Dead);
void KillSocket(std::shared_ptr<asio::ip::tcp::socket>& Dead);
void KillSocket(std::shared_ptr<asio::ip::udp::socket>& Dead);
void KillSocket(asio::ip::tcp::socket& Dead);
void KillSocket(asio::ip::udp::socket& Dead);
void UUl(const std::string& R); void UUl(const std::string& R);
void UDPSend(const std::vector<char>& Data); void UDPSend(std::string Data);
bool CheckBytes(int32_t Bytes); bool CheckBytes(int32_t Bytes);
void GameSend(std::string_view Data); void GameSend(std::string_view Data);
void SendLarge(const std::vector<char>& Data); void SendLarge(std::string Data);
std::string TCPRcv(asio::ip::tcp::socket& Sock); std::string TCPRcv(uint64_t Sock);
void SyncResources(asio::ip::tcp::socket& TCPSock); void SyncResources(uint64_t TCPSock);
std::string GetAddr(const std::string& IP); std::string GetAddr(const std::string& IP);
void ServerParser(std::string_view Data); void ServerParser(std::string_view Data);
std::string Login(const std::string& fields); std::string Login(const std::string& fields);
void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock); void TCPSend(const std::string& Data, uint64_t Sock);
void TCPClientMain(asio::ip::tcp::socket&& Socket); void TCPClientMain(const std::string& IP, int Port);
void UDPClientMain(asio::ip::address addr, uint16_t port); void UDPClientMain(const std::string& IP, int Port);
void TCPGameServer(asio::ip::tcp::socket&& Socket); void TCPGameServer(const std::string& IP, int Port);
bool SecurityWarning();
void CoreSend(std::string data);

View File

@@ -1,6 +0,0 @@
#pragma once
#include <asio.hpp>
#include <vector>
void ReceiveFromGame(asio::ip::tcp::socket& socket, std::vector<char>& out_data);

24
include/Options.h Normal file
View File

@@ -0,0 +1,24 @@
#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;

View File

@@ -10,12 +10,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
void InitLauncher(int argc, char* argv[]); void InitLauncher();
std::string GetEP(char* P = nullptr); std::string GetEP(const char* P = nullptr);
std::string GetGamePath(); std::string GetGamePath();
std::string GetVer(); std::string GetVer();
std::string GetPatch();
std::string GetEN(); std::string GetEN();
void StartProxy();
void ConfigInit(); void ConfigInit();
extern bool Dev;

20
include/Utils.h Normal file
View File

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

View File

@@ -19,14 +19,13 @@ std::vector<char> Comp(std::span<const char> input) {
auto max_size = compressBound(input.size()); auto max_size = compressBound(input.size());
std::vector<char> output(max_size); std::vector<char> output(max_size);
uLongf output_size = output.size(); uLongf output_size = output.size();
int res = compress2( int res = compress(
reinterpret_cast<Bytef*>(output.data()), reinterpret_cast<Bytef*>(output.data()),
&output_size, &output_size,
reinterpret_cast<const Bytef*>(input.data()), reinterpret_cast<const Bytef*>(input.data()),
static_cast<uLongf>(input.size()), static_cast<uLongf>(input.size()));
3);
if (res != Z_OK) { if (res != Z_OK) {
error("zlib compress() failed: " + std::to_string(res)); error("zlib compress() failed (code: " + std::to_string(res) + ", message: " + zError(res) + ")");
throw std::runtime_error("zlib compress() failed"); throw std::runtime_error("zlib compress() failed");
} }
debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B"); debug("zlib compressed " + std::to_string(input.size()) + " B to " + std::to_string(output_size) + " B");
@@ -53,7 +52,7 @@ std::vector<char> DeComp(std::span<const char> input) {
output_buffer.resize(output_buffer.size() * 2); output_buffer.resize(output_buffer.size() * 2);
output_size = output_buffer.size(); output_size = output_buffer.size();
} else if (res != Z_OK) { } else if (res != Z_OK) {
error("zlib uncompress() failed: " + std::to_string(res)); error("zlib uncompress() failed (code: " + std::to_string(res) + ", message: " + zError(res) + ")");
throw std::runtime_error("zlib uncompress() failed"); throw std::runtime_error("zlib uncompress() failed");
} else if (res == Z_OK) { } else if (res == Z_OK) {
break; break;

View File

@@ -8,24 +8,37 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "Options.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string Branch; std::string Branch;
std::string CachingDirectory = "./Resources";
void ParseConfig(const nlohmann::json& d) { void ParseConfig(const nlohmann::json& d) {
if (d["Port"].is_number()) { if (d["Port"].is_number()) {
DEFAULT_PORT = d["Port"].get<int>(); options.port = d["Port"].get<int>();
} }
// Default -1 // Default -1
// Release 1 // Release 1
// EA 2 // EA 2
// Dev 3 // Dev 3
// Custom 3 // Custom 3
if (d["Build"].is_string()) { if (d["Build"].is_string()) {
Branch = d["Build"].get<std::string>(); Branch = d["Build"].get<std::string>();
for (char& c : Branch) for (char& c : Branch)
c = char(tolower(c)); c = char(tolower(c));
} }
if (d.contains("CachingDirectory") && d["CachingDirectory"].is_string()) {
CachingDirectory = d["CachingDirectory"].get<std::string>();
}
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() { void ConfigInit() {
@@ -49,7 +62,8 @@ void ConfigInit() {
cfg << cfg <<
R"({ R"({
"Port": 4444, "Port": 4444,
"Build": "Default" "Build": "Default",
"CachingDirectory": "./Resources"
})"; })";
cfg.close(); cfg.close();
} else { } else {

View File

@@ -8,10 +8,9 @@
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <windows.h>
#include <shlobj.h>
#elif defined(__linux__) #elif defined(__linux__)
#include "vdf_parser.hpp" #include "vdf_parser.hpp"
#include <cerrno>
#include <cstring>
#include <pwd.h> #include <pwd.h>
#include <spawn.h> #include <spawn.h>
#include <sys/types.h> #include <sys/types.h>
@@ -24,6 +23,7 @@
#include <Security/Init.h> #include <Security/Init.h>
#include <filesystem> #include <filesystem>
#include <thread> #include <thread>
#include "Options.h"
unsigned long GamePID = 0; unsigned long GamePID = 0;
#if defined(_WIN32) #if defined(_WIN32)
@@ -42,14 +42,20 @@ std::string GetGamePath() {
Path = QueryKey(hKey, 4); Path = QueryKey(hKey, 4);
if (Path.empty()) { if (Path.empty()) {
sk = R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders)"; Path = "";
openRes = RegOpenKeyEx(HKEY_CURRENT_USER, sk, 0, KEY_ALL_ACCESS, &hKey); char appDataPath[MAX_PATH];
if (openRes != ERROR_SUCCESS) { HRESULT result = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appDataPath);
fatal("Cannot get Local Appdata directory!"); if (SUCCEEDED(result)) {
Path = appDataPath;
} }
Path = QueryKey(hKey, 5);
if (Path.empty()) {
fatal("Cannot get Local Appdata directory");
}
Path += "\\BeamNG.drive\\"; Path += "\\BeamNG.drive\\";
} }
std::string Ver = CheckVer(GetGameDir()); std::string Ver = CheckVer(GetGameDir());
Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1)); Ver = Ver.substr(0, Ver.find('.', Ver.find('.') + 1));
Path += Ver + "\\"; Path += Ver + "\\";
@@ -78,7 +84,14 @@ void StartGame(std::string Dir) {
std::string BaseDir = Dir; //+"\\Bin64"; std::string BaseDir = Dir; //+"\\Bin64";
// Dir += R"(\Bin64\BeamNG.drive.x64.exe)"; // Dir += R"(\Bin64\BeamNG.drive.x64.exe)";
Dir += "\\BeamNG.drive.exe"; Dir += "\\BeamNG.drive.exe";
bSuccess = CreateProcessA(Dir.c_str(), nullptr, nullptr, nullptr, TRUE, 0, nullptr, BaseDir.c_str(), &si, &pi); 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) { if (bSuccess) {
info("Game Launched!"); info("Game Launched!");
GamePID = pi.dwProcessId; GamePID = pi.dwProcessId;
@@ -92,27 +105,21 @@ void StartGame(std::string Dir) {
} }
#elif defined(__linux__) #elif defined(__linux__)
void StartGame(std::string Dir) { void StartGame(std::string Dir) {
int status;
std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64"); std::string filename = (Dir + "/BinLinux/BeamNG.drive.x64");
char* argv[] = { filename.data(), NULL }; 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; pid_t pid;
posix_spawn_file_actions_t spawn_actions;
posix_spawn_file_actions_t file_actions; posix_spawn_file_actions_init(&spawn_actions);
auto status = posix_spawn_file_actions_init(&file_actions); posix_spawn_file_actions_addclose(&spawn_actions, STDOUT_FILENO);
// disable stdout posix_spawn_file_actions_addclose(&spawn_actions, STDERR_FILENO);
if (status != 0) { int result = posix_spawn(&pid, filename.c_str(), &spawn_actions, nullptr, const_cast<char**>(argv.data()), environ);
error(std::string("posix_spawn_file_actions_init failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDOUT_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDOUT failed: ") + std::strerror(errno));
}
status = posix_spawn_file_actions_addclose(&file_actions, STDERR_FILENO);
if (status != 0) {
error(std::string("posix_spawn_file_actions_addclose for STDERR failed: ") + std::strerror(errno));
}
// launch the game
int result = posix_spawn(&pid, filename.c_str(), &file_actions, NULL, argv, environ);
if (result != 0) { if (result != 0) {
error("Failed to Launch the game! launcher closing soon"); error("Failed to Launch the game! launcher closing soon");
@@ -122,18 +129,13 @@ void StartGame(std::string Dir) {
error("Game Closed! launcher closing soon"); error("Game Closed! launcher closing soon");
} }
status = posix_spawn_file_actions_destroy(&file_actions);
if (status != 0) {
warn(std::string("posix_spawn_file_actions_destroy failed: ") + std::strerror(errno));
}
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
exit(2); exit(2);
} }
#endif #endif
void InitGame(const std::string& Dir) { void InitGame(const std::string& Dir) {
if (!Dev) { if (!options.no_launch) {
std::thread Game(StartGame, Dir); std::thread Game(StartGame, Dir);
Game.detach(); Game.detach();
} }

View File

@@ -1,9 +0,0 @@
#include "Helpers.h"
std::string bytespan_to_string(ByteSpan span) {
return std::string(span.data(), span.size());
}
std::vector<char> strtovec(std::string_view str) {
return std::vector<char>(str.begin(), str.end());
}

View File

@@ -12,6 +12,7 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include "Options.h"
std::string getDate() { std::string getDate() {
time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
@@ -50,35 +51,35 @@ void addToLog(const std::string& Line) {
} }
void info(const std::string& toPrint) { void info(const std::string& toPrint) {
std::string Print = getDate() + "[INFO] " + toPrint + "\n"; std::string Print = getDate() + "[INFO] " + toPrint + "\n";
std::cout << Print << std::flush; std::cout << Print;
addToLog(Print); addToLog(Print);
} }
void debug(const std::string& toPrint) { void debug(const std::string& toPrint) {
if (!Dev)
return;
std::string Print = getDate() + "[DEBUG] " + toPrint + "\n"; std::string Print = getDate() + "[DEBUG] " + toPrint + "\n";
std::cout << Print << std::flush; if (options.verbose) {
std::cout << Print;
}
addToLog(Print); addToLog(Print);
} }
void warn(const std::string& toPrint) { void warn(const std::string& toPrint) {
std::string Print = getDate() + "[WARN] " + toPrint + "\n"; std::string Print = getDate() + "[WARN] " + toPrint + "\n";
std::cout << Print << std::flush; std::cout << Print;
addToLog(Print); addToLog(Print);
} }
void error(const std::string& toPrint) { void error(const std::string& toPrint) {
std::string Print = getDate() + "[ERROR] " + toPrint + "\n"; std::string Print = getDate() + "[ERROR] " + toPrint + "\n";
std::cout << Print << std::flush; std::cout << Print;
addToLog(Print); addToLog(Print);
} }
void fatal(const std::string& toPrint) { void fatal(const std::string& toPrint) {
std::string Print = getDate() + "[FATAL] " + toPrint + "\n"; std::string Print = getDate() + "[FATAL] " + toPrint + "\n";
std::cout << Print << std::flush; std::cout << Print;
addToLog(Print); addToLog(Print);
std::this_thread::sleep_for(std::chrono::seconds(5)); std::this_thread::sleep_for(std::chrono::seconds(5));
_Exit(-1); std::exit(1);
} }
void except(const std::string& toPrint) { void except(const std::string& toPrint) {
std::string Print = getDate() + "[EXCEP] " + toPrint + "\n"; std::string Print = getDate() + "[EXCEP] " + toPrint + "\n";
std::cout << Print << std::flush; std::cout << Print;
addToLog(Print); addToLog(Print);
} }

View File

@@ -7,17 +7,18 @@
/// ///
#include "Http.h" #include "Http.h"
#include "Network/network.hpp" #include "Network/network.hpp"
#include "NetworkHelpers.h"
#include "Security/Init.h" #include "Security/Init.h"
#include <asio/io_context.hpp>
#include <cstdlib> #include <cstdlib>
#include <optional>
#include <regex> #include <regex>
#if defined(__linux__) #if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#elif defined(__linux__)
#include <cstring> #include <cstring>
#include <errno.h> #include <errno.h>
#include <netdb.h> #include <netdb.h>
#include <spawn.h> #include <spawn.h>
#include <sys/socket.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
@@ -26,135 +27,116 @@
#include "Logger.h" #include "Logger.h"
#include "Startup.h" #include "Startup.h"
#include <charconv> #include <charconv>
#include <fmt/format.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <set> #include <set>
#include <thread> #include <thread>
#include <mutex>
#include "Options.h"
#include <future>
extern int TraceBack; extern int TraceBack;
std::set<std::string>* ConfList = nullptr; std::set<std::string>* ConfList = nullptr;
bool TCPTerminate = false; bool TCPTerminate = false;
int DEFAULT_PORT = 4444;
bool Terminate = false; bool Terminate = false;
bool LoginAuth = false; bool LoginAuth = false;
std::string Username = ""; std::string Username = "";
std::string UserRole = ""; std::string UserRole = "";
int UserID = -1;
std::string UlStatus; std::string UlStatus;
std::string MStatus; std::string MStatus;
bool ModLoaded; bool ModLoaded;
int ping = -1; int ping = -1;
SOCKET CoreSocket = -1;
signed char confirmed = -1;
asio::io_context io {}; bool SecurityWarning() {
confirmed = -1;
CoreSend("WMODS_FOUND");
static asio::ip::tcp::socket ResolveAndConnect(const std::string& host_port_string) { while (confirmed == -1)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
using namespace asio; if (confirmed == 1)
ip::tcp::resolver resolver(io); return true;
asio::error_code ec;
auto port = host_port_string.substr(host_port_string.find_last_of(':') + 1);
auto host = host_port_string.substr(0, host_port_string.find_last_of(':'));
auto resolved = resolver.resolve(host, port, ec);
if (ec) {
::error(fmt::format("Failed to resolve '[{}]:{}': {}", host, port, ec.message()));
throw std::runtime_error(fmt::format("Failed to resolve '{}': {}", host_port_string, ec.message()));
}
bool connected = false;
UlStatus = "UlLoading..."; NetReset();
Terminate = true;
for (const auto& addr : resolved) { TCPTerminate = true;
try { ping = -1;
info(fmt::format("Resolved and connected to '[{}]:{}'",
addr.endpoint().address().to_string(), return false;
addr.endpoint().port()));
ip::tcp::socket socket(io);
socket.connect(addr);
// done, connected fine
return socket;
} catch (...) {
// ignore
}
}
throw std::runtime_error(fmt::format("Failed to connect to '{}'; connection refused", host_port_string));
} }
void StartSync(const std::string& Data) { void StartSync(const std::string& Data) {
try { std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1));
auto Socket = ResolveAndConnect(Data.substr(1)); if (IP.find('.') == -1) {
ListOfMods = "-"; if (IP == "DNS")
CheckLocalKey(); UlStatus = "UlConnection Failed! (DNS Lookup Failed)";
TCPTerminate = false; else
Terminate = false; UlStatus = "UlConnection Failed! (WSA failed to start)";
ConfList->clear();
ping = -1;
std::thread GS(TCPGameServer, std::move(Socket));
GS.detach();
info("Connecting to server");
} catch (const std::exception& e) {
UlStatus = "UlConnection Failed!";
error(fmt::format("Client: connect failed! Error: {}", e.what()));
WSACleanup();
Terminate = true; Terminate = true;
CoreSend("L");
return;
}
CheckLocalKey();
UlStatus = "UlLoading...";
TCPTerminate = false;
Terminate = false;
ConfList->clear();
ping = -1;
std::thread GS(TCPGameServer, IP, std::stoi(Data.substr(Data.find(':') + 1)));
GS.detach();
info("Connecting to server");
}
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()));
}
} }
} }
bool IsAllowedLink(const std::string& Link) { bool IsAllowedLink(const std::string& Link) {
std::vector<std::string> allowed_links = { std::regex link_pattern(R"(https:\/\/(?:\w+)?(?:\.)?(?:beammp\.com|beammp\.gg|github\.com\/BeamMP\/|discord\.gg|patreon\.com\/BeamMP))");
R"(patreon\.com\/beammp$)", std::smatch link_match;
R"(discord\.gg\/beammp$)", return std::regex_search(Link, link_match, link_pattern) && link_match.position() == 0;
R"(forum\.beammp\.com$)",
R"(beammp\.com$)",
R"(patreon\.com\/beammp\/$)",
R"(discord\.gg\/beammp\/$)",
R"(forum\.beammp\.com\/$)",
R"(beammp\.com\/$)",
R"(docs\.beammp\.com$)",
R"(wiki\.beammp\.com$)",
R"(docs\.beammp\.com\/$)",
R"(wiki\.beammp\.com\/$)",
R"(docs\.beammp\.com\/.*$)",
R"(wiki\.beammp\.com\/.*$)",
};
for (const auto& allowed_link : allowed_links) {
if (std::regex_match(Link, std::regex(std::string(R"(^http(s)?:\/\/)") + allowed_link))) {
return true;
}
}
return false;
} }
void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) { void Parse(std::string Data, SOCKET CSocket) {
std::string OutData; char Code = Data.at(0), SubCode = 0;
char Code = InData[0], SubCode = 0; if (Data.length() > 1)
if (InData.size() > 1) SubCode = Data.at(1);
SubCode = InData[1];
switch (Code) { switch (Code) {
case 'A': case 'A':
OutData = "A"; Data = Data.substr(0, 1);
break; break;
case 'B': case 'B': {
NetReset(); NetReset();
Terminate = true; Terminate = true;
TCPTerminate = true; TCPTerminate = true;
OutData = Code + HTTP::Get("https://backend.beammp.com/servers-info"); Data.clear();
auto future = std::async(std::launch::async, []() {
CoreSend("B" + HTTP::Get("https://backend.beammp.com/servers-info"));
});
}
break; break;
case 'C': case 'C':
ListOfMods.clear(); StartSync(Data);
StartSync(std::string(InData.data(), InData.size())); Data.clear();
while (ListOfMods.empty() && !Terminate) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (ListOfMods == "-")
OutData = "L";
else
OutData = "L" + ListOfMods;
break; break;
case 'O': // open default browser with URL case 'O': // open default browser with URL
if (IsAllowedLink(bytespan_to_string(InData.subspan(1)))) { if (IsAllowedLink(Data.substr(1))) {
#if defined(__linux) #if defined(__linux)
if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) { if (char* browser = getenv("BROWSER"); browser != nullptr && !std::string_view(browser).empty()) {
pid_t pid; pid_t pid;
auto arg = bytespan_to_string(InData.subspan(1)); auto arg = Data.substr(1);
char* argv[] = { browser, arg.data() }; char* argv[] = { browser, arg.data() };
auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ); auto status = posix_spawn(&pid, browser, nullptr, nullptr, argv, environ);
if (status == 0) { if (status == 0) {
@@ -166,27 +148,27 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
error(std::string("posix_spawn: ") + strerror(status)); error(std::string("posix_spawn: ") + strerror(status));
} }
} else { } else {
error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + bytespan_to_string(InData.subspan(1))); error("Failed to open the following link in the browser because the $BROWSER environment variable is not set: " + Data.substr(1));
} }
#elif defined(WIN32) #elif defined(WIN32)
ShellExecuteA(nullptr, "open", InData.subspan(1).data(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port ShellExecuteA(nullptr, "open", Data.substr(1).c_str(), nullptr, nullptr, SW_SHOW); /// TODO: Look at when working on linux port
#endif #endif
info("Opening Link \"" + bytespan_to_string(InData.subspan(1)) + "\""); info("Opening Link \"" + Data.substr(1) + "\"");
} }
OutData.clear(); Data.clear();
break; break;
case 'P': case 'P':
OutData = Code + std::to_string(ProxyPort); Data = Code + std::to_string(ProxyPort);
break; break;
case 'U': case 'U':
if (SubCode == 'l') if (SubCode == 'l')
OutData = UlStatus; Data = UlStatus;
if (SubCode == 'p') { if (SubCode == 'p') {
if (ping > 800) { if (ping > 800) {
OutData = "Up-2"; Data = "Up-2";
} else } else
OutData = "Up" + std::to_string(ping); Data = "Up" + std::to_string(ping);
} }
if (!SubCode) { if (!SubCode) {
std::string Ping; std::string Ping;
@@ -194,11 +176,11 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
Ping = "-2"; Ping = "-2";
else else
Ping = std::to_string(ping); Ping = std::to_string(ping);
OutData = std::string(UlStatus) + "\n" + "Up" + Ping; Data = std::string(UlStatus) + "\n" + "Up" + Ping;
} }
break; break;
case 'M': case 'M':
OutData = MStatus; Data = MStatus;
break; break;
case 'Q': case 'Q':
if (SubCode == 'S') { if (SubCode == 'S') {
@@ -207,21 +189,21 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
TCPTerminate = true; TCPTerminate = true;
ping = -1; ping = -1;
} }
if (SubCode == 'G') if (SubCode == 'G') {
debug("Closing via 'G' packet");
exit(2); exit(2);
OutData.clear(); }
Data.clear();
break; break;
case 'R': // will send mod name case 'R': // will send mod name
{ if (ConfList->find(Data) == ConfList->end()) {
auto str = bytespan_to_string(InData); ConfList->insert(Data);
if (ConfList->find(str) == ConfList->end()) {
ConfList->insert(str);
ModLoaded = true; ModLoaded = true;
} }
OutData.clear(); Data.clear();
} break; break;
case 'Z': case 'Z':
OutData = "Z" + GetVer(); Data = "Z" + GetVer();
break; break;
case 'N': case 'N':
if (SubCode == 'c') { if (SubCode == 'c') {
@@ -234,39 +216,74 @@ void Parse(std::span<char> InData, asio::ip::tcp::socket& CSocket) {
if (!UserRole.empty()) { if (!UserRole.empty()) {
Auth["role"] = UserRole; Auth["role"] = UserRole;
} }
OutData = "N" + Auth.dump(); if (UserID != -1) {
Auth["id"] = UserID;
}
Data = "N" + Auth.dump();
} else { } else {
auto indata_str = bytespan_to_string(InData); auto future = std::async(std::launch::async, [data = std::move(Data)]() {
OutData = "N" + Login(indata_str.substr(indata_str.find(':') + 1)); CoreSend("N" + Login(data.substr(data.find(':') + 1)));
});
Data.clear();
} }
break; break;
case 'W':
if (SubCode == 'Y') {
confirmed = 1;
} else if (SubCode == 'N') {
confirmed = 0;
}
Data.clear();
break;
default: default:
OutData.clear(); Data.clear();
break; break;
} }
if (!OutData.empty() && CSocket.is_open()) { if (!Data.empty())
uint32_t DataSize = OutData.size(); CoreSend(Data);
std::vector<char> ToSend(sizeof(DataSize) + OutData.size());
std::copy_n(reinterpret_cast<char*>(&DataSize), sizeof(DataSize), ToSend.begin());
std::copy_n(OutData.data(), OutData.size(), ToSend.begin() + sizeof(DataSize));
asio::error_code ec;
asio::write(CSocket, asio::buffer(ToSend), ec);
if (ec) {
debug(fmt::format("(Core) send failed with error: {}", ec.message()));
}
}
} }
void GameHandler(asio::ip::tcp::socket& Client) { void GameHandler(SOCKET Client) {
std::vector<char> data {}; CoreSocket = Client;
int32_t Size, Temp, Rcv;
char Header[10] = { 0 };
do { do {
try { Rcv = 0;
ReceiveFromGame(Client, data); do {
Parse(data, Client); Temp = recv(Client, &Header[Rcv], 1, 0);
} catch (const std::exception& e) { if (Temp < 1)
error(std::string("Error while receiving from game: ") + e.what()); 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));
break; break;
} }
} while (true); std::string Ret(Size, 0);
Rcv = 0;
do {
Temp = recv(Client, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
} while (Rcv < Size);
if (Temp < 1)
break;
Parse(Ret, Client);
} while (Temp > 0);
if (Temp == 0) {
debug("(Core) Connection closing");
} else {
debug("(Core) recv failed with error: " + std::to_string(WSAGetLastError()));
}
NetReset(); NetReset();
KillSocket(Client); KillSocket(Client);
} }
@@ -281,53 +298,64 @@ void localRes() {
ConfList = new std::set<std::string>; ConfList = new std::set<std::string>;
} }
void CoreMain() { 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;
iRes = WSAStartup(514, &wsaData); // 2.2
if (iRes)
debug("WSAStartup failed with error: " + std::to_string(iRes));
#endif
asio::ip::tcp::endpoint listen_ep(asio::ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(DEFAULT_PORT)); ZeroMemory(&hints, sizeof(hints));
asio::ip::tcp::socket LSocket(io);
asio::error_code ec;
LSocket.open(listen_ep.protocol(), ec);
if (ec) {
::error(fmt::format("Failed to open core socket: {}", ec.message()));
return;
}
asio::ip::tcp::socket::linger linger_opt {};
linger_opt.enabled(false);
LSocket.set_option(linger_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
asio::ip::tcp::socket::reuse_address reuse_opt { true };
LSocket.set_option(reuse_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
auto acceptor = asio::ip::tcp::acceptor(io, listen_ep); hints.ai_family = AF_INET;
acceptor.listen(asio::ip::tcp::socket::max_listen_connections, ec); hints.ai_socktype = SOCK_STREAM;
if (ec) { hints.ai_protocol = IPPROTO_TCP;
::error(fmt::format("listen() failed, which is needed for the launcher to operate. Error: {}", hints.ai_flags = AI_PASSIVE;
ec.message())); 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) {
debug("(Core) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
WSACleanup();
return;
}
iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen));
if (iRes == SOCKET_ERROR) {
error("(Core) bind failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return;
}
iRes = listen(LSocket, SOMAXCONN);
if (iRes == SOCKET_ERROR) {
debug("(Core) listen failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(res);
KillSocket(LSocket);
WSACleanup();
return; return;
} }
do { do {
auto CSocket = acceptor.accept(ec); CSocket = accept(LSocket, nullptr, nullptr);
if (ec) { if (CSocket == -1) {
error(fmt::format("(Core) accept failed with error: {}", ec.message())); error("(Core) accept failed with error: " + std::to_string(WSAGetLastError()));
continue; continue;
} }
localRes(); localRes();
info("Game Connected!"); info("Game Connected!");
GameHandler(CSocket); GameHandler(CSocket);
warn("Game Reconnecting..."); warn("Game Reconnecting...");
} while (LSocket.is_open()); } while (CSocket);
KillSocket(LSocket); KillSocket(LSocket);
WSACleanup(); WSACleanup();
} }

View File

@@ -5,13 +5,8 @@
/// ///
/// Created by Anonymous275 on 7/25/2020 /// Created by Anonymous275 on 7/25/2020
/// ///
#include "Helpers.h"
#include "Network/network.hpp" #include "Network/network.hpp"
#include "NetworkHelpers.h" #include <memory>
#include "asio/socket_base.hpp"
#include <algorithm>
#include <span>
#include <vector>
#include <zlib.h> #include <zlib.h>
#if defined(_WIN32) #if defined(_WIN32)
#include <winsock2.h> #include <winsock2.h>
@@ -28,39 +23,28 @@
#include "Logger.h" #include "Logger.h"
#include <charconv> #include <charconv>
#include <fmt/format.h>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread> #include <thread>
#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 GConnected = false;
bool CServer = true; bool CServer = true;
std::shared_ptr<asio::ip::tcp::socket> CSocket = nullptr; SOCKET CSocket = -1;
std::shared_ptr<asio::ip::tcp::socket> GSocket = nullptr; SOCKET GSocket = -1;
void KillSocket(std::shared_ptr<asio::ip::tcp::socket>& Dead) { int KillSocket(uint64_t Dead) {
if (!Dead) if (Dead == (SOCKET)-1) {
return; debug("Kill socket got -1 returning...");
asio::error_code ec; return 0;
Dead->shutdown(asio::socket_base::shutdown_both, ec); }
} shutdown(Dead, SD_BOTH);
int a = closesocket(Dead);
void KillSocket(std::shared_ptr<asio::ip::udp::socket>& Dead) { if (a != 0) {
if (!Dead) warn("Failed to close socket!");
return; }
asio::error_code ec; return a;
Dead->shutdown(asio::socket_base::shutdown_both, ec);
}
void KillSocket(asio::ip::tcp::socket& Dead) {
asio::error_code ec;
Dead.shutdown(asio::socket_base::shutdown_both, ec);
}
void KillSocket(asio::ip::udp::socket& Dead) {
asio::error_code ec;
Dead.shutdown(asio::socket_base::shutdown_both, ec);
} }
bool CheckBytes(uint32_t Bytes) { bool CheckBytes(uint32_t Bytes) {
@@ -74,34 +58,42 @@ bool CheckBytes(uint32_t Bytes) {
return true; return true;
} }
void GameSend(std::string_view RawData) { void GameSend(std::string_view Data) {
static std::mutex Lock; static std::mutex Lock;
std::scoped_lock Guard(Lock); std::scoped_lock Guard(Lock);
if (TCPTerminate || !GConnected || CSocket == nullptr) if (TCPTerminate || !GConnected || CSocket == -1)
return; return;
int32_t Size, Temp, Sent; int32_t Size, Temp, Sent;
uint32_t DataSize = RawData.size(); Size = int32_t(Data.size());
std::vector<char> Data(sizeof(DataSize) + RawData.size());
std::copy_n(reinterpret_cast<char*>(&DataSize), sizeof(DataSize), Data.begin());
std::copy_n(RawData.data(), RawData.size(), Data.begin() + sizeof(DataSize));
Size = Data.size();
Sent = 0; Sent = 0;
#ifdef DEBUG
asio::error_code ec; if (Size > 1000) {
asio::write(*CSocket, asio::buffer(Data), ec); debug("Launcher -> game (" + std::to_string(Size) + ")");
if (ec) { }
debug(fmt::format("(TCP CB) recv failed with error: {}", ec.message())); #endif
KillSocket(TCPSock); do {
Terminate = true; if (Sent > -1) {
Temp = send(CSocket, &Data[Sent], Size - Sent, 0);
}
if (!CheckBytes(Temp))
return;
Sent += Temp;
} while (Sent < Size);
// send separately to avoid an allocation for += "\n"
Temp = send(CSocket, "\n", 1, 0);
if (!CheckBytes(Temp)) {
return;
} }
} }
void ServerSend(std::string Data, bool Rel) {
void ServerSend(const std::vector<char>& Data, bool Rel) {
if (Terminate || Data.empty()) if (Terminate || Data.empty())
return; return;
if (Data.find("Zp") != std::string::npos && Data.size() > 500) {
abort();
}
char C = 0; char C = 0;
bool Ack = false; bool Ack = false;
int DLen = int(Data.size()); int DLen = int(Data.length());
if (DLen > 3) if (DLen > 3)
C = Data.at(0); C = Data.at(0);
if (C == 'O' || C == 'T') if (C == 'O' || C == 'T')
@@ -113,10 +105,18 @@ void ServerSend(const std::vector<char>& Data, bool Rel) {
if (Ack || Rel) { if (Ack || Rel) {
if (Ack || DLen > 1000) if (Ack || DLen > 1000)
SendLarge(Data); SendLarge(Data);
else if (TCPSock) else
TCPSend(Data, *TCPSock); TCPSend(Data, TCPSock);
} else } else
UDPSend(Data); 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);
}
} }
void NetReset() { void NetReset() {
@@ -125,23 +125,76 @@ void NetReset() {
Terminate = false; Terminate = false;
UlStatus = "Ulstart"; UlStatus = "Ulstart";
MStatus = " "; MStatus = " ";
if (UDPSock != nullptr) { if (UDPSock != (SOCKET)(-1)) {
KillSocket(*UDPSock); debug("Terminating UDP Socket: " + std::to_string(TCPSock));
KillSocket(UDPSock);
} }
UDPSock = nullptr; UDPSock = -1;
if (TCPSock != nullptr) { if (TCPSock != (SOCKET)(-1)) {
KillSocket(*TCPSock); debug("Terminating TCP Socket: " + std::to_string(TCPSock));
KillSocket(TCPSock);
} }
TCPSock = nullptr; TCPSock = -1;
if (GSocket != nullptr) { if (GSocket != (SOCKET)(-1)) {
KillSocket(*GSocket); debug("Terminating GTCP Socket: " + std::to_string(GSocket));
KillSocket(GSocket);
} }
GSocket = nullptr; GSocket = -1;
} }
SOCKET SetupListener() {
if (GSocket != -1)
return GSocket;
struct addrinfo* result = nullptr;
struct addrinfo hints { };
int iRes;
#ifdef _WIN32
WSADATA wsaData;
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(options.port + 1).c_str(), &hints, &result);
if (iRes != 0) {
error("(Proxy) info failed with error: " + std::to_string(iRes));
WSACleanup();
}
GSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (GSocket == -1) {
error("(Proxy) socket failed with error: " + std::to_string(WSAGetLastError()));
freeaddrinfo(result);
WSACleanup();
return -1;
}
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);
KillSocket(GSocket);
WSACleanup();
return -1;
}
freeaddrinfo(result);
iRes = listen(GSocket, SOMAXCONN);
if (iRes == SOCKET_ERROR) {
error("(Proxy) listen failed with error: " + std::to_string(WSAGetLastError()));
KillSocket(GSocket);
WSACleanup();
return -1;
}
return GSocket;
}
void AutoPing() { void AutoPing() {
while (!Terminate) { while (!Terminate) {
ServerSend(strtovec("p"), false); ServerSend("p", false);
PingStart = std::chrono::high_resolution_clock::now(); PingStart = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }
@@ -173,47 +226,19 @@ void ParserAsync(std::string_view Data) {
void ServerParser(std::string_view Data) { void ServerParser(std::string_view Data) {
ParserAsync(Data); ParserAsync(Data);
} }
void NetMain(asio::ip::address addr, uint16_t port) { void NetMain(const std::string& IP, int Port) {
std::thread Ping(AutoPing); std::thread Ping(AutoPing);
Ping.detach(); Ping.detach();
UDPClientMain(addr, port); UDPClientMain(IP, Port);
CServer = true; CServer = true;
Terminate = true; Terminate = true;
info("Connection Terminated!"); info("Connection Terminated!");
} }
void TCPGameServer(asio::ip::tcp::socket&& Socket) { void TCPGameServer(const std::string& IP, int Port) {
asio::ip::tcp::endpoint listen_ep(asio::ip::address::from_string("0.0.0.0"), static_cast<uint16_t>(DEFAULT_PORT + 1)); GSocket = SetupListener();
asio::ip::tcp::socket listener(io); std::unique_ptr<std::thread> ClientThread {};
asio::error_code ec; std::unique_ptr<std::thread> NetMainThread {};
listener.open(listen_ep.protocol(), ec); while (!TCPTerminate && GSocket != -1) {
if (ec) {
::error(fmt::format("Failed to open game socket: {}", ec.message()));
return;
}
asio::ip::tcp::socket::linger linger_opt {};
linger_opt.enabled(false);
listener.set_option(linger_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening game socket to not linger / reuse address. "
"This may cause the game socket to refuse to bind(). Error: {}",
ec.message()));
}
asio::ip::tcp::socket::reuse_address reuse_opt { true };
listener.set_option(reuse_opt, ec);
if (ec) {
::error(fmt::format("Failed to set up listening core socket to not linger / reuse address. "
"This may cause the core socket to refuse to bind(). Error: {}",
ec.message()));
return;
}
auto acceptor = asio::ip::tcp::acceptor(io, listen_ep);
acceptor.listen(asio::ip::tcp::socket::max_listen_connections, ec);
if (ec) {
debug(fmt::format("Proxy accept failed: {}", ec.message()));
TCPTerminate = true; // skip the loop
}
debug(fmt::format("Game server listening on {}:{}", acceptor.local_endpoint().address().to_string(), acceptor.local_endpoint().port()));
while (!TCPTerminate && acceptor.is_open()) {
debug("MAIN LOOP OF GAME SERVER"); debug("MAIN LOOP OF GAME SERVER");
GConnected = false; GConnected = false;
if (!CServer) { if (!CServer) {
@@ -224,35 +249,70 @@ void TCPGameServer(asio::ip::tcp::socket&& Socket) {
break; break;
} }
if (CServer) { if (CServer) {
std::thread Client(TCPClientMain, std::move(Socket)); ClientThread = std::make_unique<std::thread>(TCPClientMain, IP, Port);
Client.detach(); }
CSocket = accept(GSocket, nullptr, nullptr);
if (CSocket == -1) {
debug("(Proxy) accept failed with error: " + std::to_string(WSAGetLastError()));
break;
} }
CSocket = std::make_shared<asio::ip::tcp::socket>(acceptor.accept());
debug("(Proxy) Game Connected!"); debug("(Proxy) Game Connected!");
GConnected = true; GConnected = true;
if (CServer) { if (CServer) {
std::thread t1(NetMain, CSocket->remote_endpoint().address(), CSocket->remote_endpoint().port()); NetMainThread = std::make_unique<std::thread>(NetMain, IP, Port);
t1.detach();
CServer = false; CServer = false;
} }
std::vector<char> data {}; 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 // Read byte by byte until '>' is rcved then get the size and read based on it
while (!TCPTerminate && !CSocket) { do {
try { Rcv = 0;
ReceiveFromGame(*CSocket, data);
ServerSend(data, false); do {
} catch (const std::exception& e) { Temp = recv(CSocket, &Header[Rcv], 1, 0);
error(std::string("Error while receiving from game: ") + e.what()); 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; break;
} }
} std::string Ret(Size, 0);
Rcv = 0;
do {
Temp = recv(CSocket, &Ret[Rcv], Size - Rcv, 0);
if (Temp < 1)
break;
Rcv += Temp;
} while (Rcv < Size && !TCPTerminate);
if (Temp < 1 || TCPTerminate)
break;
ServerSend(Ret, false);
} while (Temp > 0 && !TCPTerminate);
if (Temp == 0)
debug("(Proxy) Connection closing");
else
debug("(Proxy) recv failed error : " + std::to_string(WSAGetLastError()));
} }
TCPTerminate = true; TCPTerminate = true;
GConnected = false; GConnected = false;
Terminate = true; Terminate = true;
if (CSocket != nullptr) 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); KillSocket(CSocket);
debug("END OF GAME SERVER"); debug("END OF GAME SERVER");
} }

View File

@@ -1,174 +1,269 @@
// Copyright (c) 2019-present Anonymous275. // Copyright (c) 2019-present Anonymous275.
// BeamMP Launcher code is not in the public domain and is not free software. // BeamMP Launcher code is not in the public domain and is not free software.
// One must be granted explicit permission by the copyright holder in order to modify or distribute any part of the source or binaries. // 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. // 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 /// Created by Anonymous275 on 7/18/2020
/// ///
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "Http.h"
#include "Http.h" #include <Logger.h>
#include <Logger.h> #include <Network/network.hpp>
#include <cmath> #include <Startup.h>
#include <filesystem> #include <Utils.h>
#include <fstream> #include <cmath>
#include <httplib.h> #include <curl/curl.h>
#include <iostream> #include <curl/easy.h>
#include <mutex> #include <filesystem>
#include <nlohmann/json.hpp> #include <fstream>
#include <httplib.h>
void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try { #include <iostream>
const std::filesystem::path folder = ".https_debug"; #include <mutex>
std::filesystem::create_directories(folder); #include <nlohmann/json.hpp>
if (!std::filesystem::exists(folder / "WHAT IS THIS FOLDER.txt")) {
std::ofstream ignore { folder / "WHAT IS THIS FOLDER.txt" }; void WriteHttpDebug(const httplib::Client& client, const std::string& method, const std::string& target, const httplib::Result& result) try {
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 std::filesystem::path folder = ".https_debug";
} std::filesystem::create_directories(folder);
const auto file = folder / (method + ".json"); if (!std::filesystem::exists(folder / "WHAT IS THIS FOLDER.txt")) {
// 1 MB limit std::ofstream ignore { folder / "WHAT IS THIS FOLDER.txt" };
if (std::filesystem::exists(file) && std::filesystem::file_size(file) > 1'000'000) { 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;
std::filesystem::rename(file, file.generic_string() + ".bak"); }
} const auto file = folder / (method + ".json");
// 1 MB limit
std::ofstream of { file, std::ios::app }; if (std::filesystem::exists(file) && std::filesystem::file_size(file) > 1'000'000) {
nlohmann::json js { std::filesystem::rename(file, file.generic_string() + ".bak");
{ "utc", std::chrono::system_clock::now().time_since_epoch().count() }, }
{ "target", target },
{ "client_info", { std::ofstream of { file, std::ios::app };
{ "openssl_verify_result", client.get_openssl_verify_result() }, nlohmann::json js {
{ "host", client.host() }, { "utc", std::chrono::system_clock::now().time_since_epoch().count() },
{ "port", client.port() }, { "target", target },
{ "socket_open", client.is_socket_open() }, { "client_info", {
{ "valid", client.is_valid() }, { "openssl_verify_result", client.get_openssl_verify_result() },
} }, { "host", client.host() },
}; { "port", client.port() },
if (result) { { "socket_open", client.is_socket_open() },
auto value = result.value(); { "valid", client.is_valid() },
js["result"] = {}; } },
js["result"]["body"] = value.body; };
js["result"]["status"] = value.status; if (result) {
js["result"]["headers"] = value.headers; auto value = result.value();
js["result"]["version"] = value.version; js["result"] = {};
js["result"]["location"] = value.location; js["result"]["body"] = value.body;
js["result"]["reason"] = value.reason; js["result"]["status"] = value.status;
} js["result"]["headers"] = value.headers;
of << js.dump(); js["result"]["version"] = value.version;
} catch (const std::exception& e) { js["result"]["location"] = value.location;
error(e.what()); js["result"]["reason"] = value.reason;
} }
of << js.dump();
bool HTTP::isDownload = false; } catch (const std::exception& e) {
std::string HTTP::Get(const std::string& IP) { error(e.what());
static std::mutex Lock; }
std::scoped_lock Guard(Lock);
static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
auto pos = IP.find('/', 10); std::string* Result = reinterpret_cast<std::string*>(userp);
std::string NewContents(reinterpret_cast<char*>(contents), size * nmemb);
httplib::Client cli(IP.substr(0, pos).c_str()); *Result += NewContents;
cli.set_connection_timeout(std::chrono::seconds(10)); return size * nmemb;
cli.set_follow_location(true); }
auto res = cli.Get(IP.substr(pos).c_str(), ProgressBar);
std::string Ret; bool HTTP::isDownload = false;
std::string HTTP::Get(const std::string& IP) {
if (res) { std::string Ret;
if (res->status == 200) { static thread_local CURL* curl = curl_easy_init();
Ret = res->body; if (curl) {
} else { CURLcode res;
WriteHttpDebug(cli, "GET", IP, res); curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
error("Failed to GET '" + IP + "': " + res->reason + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result())); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
} curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
} else { curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
if (isDownload) { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
std::cout << "\n"; res = curl_easy_perform(curl);
} if (res != CURLE_OK) {
WriteHttpDebug(cli, "GET", IP, res); error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
error("HTTP Get failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result())); return "";
} }
} else {
return Ret; error("Curl easy init failed");
} return "";
}
std::string HTTP::Post(const std::string& IP, const std::string& Fields) { return Ret;
static std::mutex Lock; }
std::scoped_lock Guard(Lock);
std::string HTTP::Post(const std::string& IP, const std::string& Fields) {
auto pos = IP.find('/', 10); std::string Ret;
static thread_local CURL* curl = curl_easy_init();
httplib::Client cli(IP.substr(0, pos).c_str()); if (curl) {
cli.set_connection_timeout(std::chrono::seconds(10)); CURLcode res;
std::string Ret; curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
if (!Fields.empty()) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret);
httplib::Result res = cli.Post(IP.substr(pos).c_str(), Fields, "application/json"); curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str());
if (res) { curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size());
if (res->status != 200) { struct curl_slist* list = nullptr;
error(res->reason); list = curl_slist_append(list, "Content-Type: application/json");
} curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
Ret = res->body; curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); // seconds
} else { curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
WriteHttpDebug(cli, "POST", IP, res); res = curl_easy_perform(curl);
error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result())); curl_slist_free_all(list);
} if (res != CURLE_OK) {
} else { error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res)));
httplib::Result res = cli.Post(IP.substr(pos).c_str()); return "";
if (res) { }
if (res->status != 200) { } else {
error(res->reason); error("Curl easy init failed");
} return "";
Ret = res->body; }
} else { return Ret;
WriteHttpDebug(cli, "POST", IP, res); }
error("HTTP Post failed on " + to_string(res.error()) + ", ssl verify = " + std::to_string(cli.get_openssl_verify_result()));
} bool HTTP::Download(const std::string& IP, const std::string& Path) {
} static std::mutex Lock;
std::scoped_lock Guard(Lock);
if (Ret.empty())
return "-1"; info("Downloading an update (this may take a while)");
else std::string Ret = Get(IP);
return Ret;
} if (Ret.empty()) {
error("Download failed");
bool HTTP::ProgressBar(size_t c, size_t t) { return false;
if (isDownload) { }
static double last_progress, progress_bar_adv;
progress_bar_adv = round(c / double(t) * 25); std::ofstream File(Path, std::ios::binary);
std::cout << "\r"; if (File.is_open()) {
std::cout << "Progress : [ "; File << Ret;
std::cout << round(c / double(t) * 100); File.close();
std::cout << "% ] ["; info("Download Complete!");
int i; } else {
for (i = 0; i <= progress_bar_adv; i++) error("Failed to open file directory: " + Path);
std::cout << "#"; return false;
for (i = 0; i < 25 - progress_bar_adv; i++) }
std::cout << ".";
std::cout << "]"; return true;
last_progress = round(c / double(t) * 100); }
}
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");
bool HTTP::Download(const std::string& IP, const std::string& Path) { res.set_header("Access-Control-Request-Headers", "X-API-Version");
static std::mutex Lock; }
std::scoped_lock Guard(Lock);
void HTTP::StartProxy() {
isDownload = true; std::thread proxy([&]() {
std::string Ret = Get(IP); httplib::Server HTTPProxy;
isDownload = false; httplib::Headers headers = {
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
if (Ret.empty()) { "Accept", "*/*" }
return false; };
httplib::Client backend("https://backend.beammp.com");
std::ofstream File(Path, std::ios::binary); httplib::Client forum("https://forum.beammp.com");
if (File.is_open()) {
File << Ret; const std::string pattern = ".*";
File.close();
std::cout << "\n"; auto handle_request = [&](const httplib::Request& req, httplib::Response& res) {
info("Download Complete!"); set_headers(res);
} else { if (req.has_header("X-BMP-Authentication")) {
error("Failed to open file directory: " + Path); headers.emplace("X-BMP-Authentication", PrivateKey);
return false; }
} if (req.has_header("X-API-Version")) {
headers.emplace("X-API-Version", req.get_header_value("X-API-Version"));
return true; }
}
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();
}

View File

@@ -7,7 +7,13 @@
/// ///
#include "Network/network.hpp" #include "Network/network.hpp"
#include "fmt/core.h" #include <chrono>
#include <iomanip>
#include <ios>
#include <mutex>
#include <nlohmann/json.hpp>
#include <openssl/err.h>
#include <openssl/evp.h>
#if defined(_WIN32) #if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -22,43 +28,29 @@
#include "Logger.h" #include "Logger.h"
#include "Startup.h" #include "Startup.h"
#include <Utils.h>
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <future> #include <future>
#include <asio.hpp>
#include <iostream> #include <iostream>
#include <string>
#include <thread> #include <thread>
#include <vector>
#include "hashpp.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::string ListOfMods;
std::vector<std::string> Split(const std::string& String, const std::string& delimiter) {
std::vector<std::string> Val;
size_t pos;
std::string token, s = String;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
if (!token.empty())
Val.push_back(token);
s.erase(0, pos + delimiter.length());
}
if (!s.empty())
Val.push_back(s);
return Val;
}
void CheckForDir() { void CheckForDir() {
if (!fs::exists("Resources")) { if (!fs::exists(CachingDirectory)) {
// Could we just use fs::create_directory instead? try {
#if defined(_WIN32) fs::create_directories(CachingDirectory);
_wmkdir(L"Resources"); } catch (const std::exception& e) {
#elif defined(__linux__) 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.");
fs::create_directory(L"Resources"); std::this_thread::sleep_for(std::chrono::seconds(3));
#endif std::exit(1);
}
} }
} }
void WaitForConfirm() { void WaitForConfirm() {
@@ -74,23 +66,27 @@ void Abord() {
info("Terminated!"); info("Terminated!");
} }
std::string Auth(asio::ip::tcp::socket& Sock) { std::string Auth(SOCKET Sock) {
TCPSend(strtovec("VC" + GetVer()), Sock); TCPSend("VC" + GetVer(), Sock);
auto Res = TCPRcv(Sock); auto Res = TCPRcv(Sock);
if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') { if (Res.empty() || Res[0] == 'E' || Res[0] == 'K') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
TCPSend(strtovec(PublicKey), Sock); TCPSend(PublicKey, Sock);
if (Terminate) if (Terminate) {
CoreSend("L");
return ""; return "";
}
Res = TCPRcv(Sock); Res = TCPRcv(Sock);
if (Res.empty() || Res[0] != 'P') { if (Res.empty() || Res[0] != 'P') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
@@ -99,24 +95,28 @@ std::string Auth(asio::ip::tcp::socket& Sock) {
ClientID = std::stoi(Res); ClientID = std::stoi(Res);
} else { } else {
Abord(); Abord();
CoreSend("L");
UUl("Authentication failed!"); UUl("Authentication failed!");
return ""; return "";
} }
TCPSend(strtovec("SR"), Sock); TCPSend("SR", Sock);
if (Terminate) if (Terminate) {
CoreSend("L");
return ""; return "";
}
Res = TCPRcv(Sock); Res = TCPRcv(Sock);
if (Res[0] == 'E' || Res[0] == 'K') { if (Res[0] == 'E' || Res[0] == 'K') {
Abord(); Abord();
CoreSend("L");
return ""; return "";
} }
if (Res.empty() || Res == "-") { if (Res.empty() || Res == "-") {
info("Didn't Receive any mods..."); info("Didn't Receive any mods...");
ListOfMods = "-"; CoreSend("L");
TCPSend(strtovec("Done"), Sock); TCPSend("Done", Sock);
info("Done!"); info("Done!");
return ""; return "";
} }
@@ -130,54 +130,88 @@ void UpdateUl(bool D, const std::string& msg) {
UlStatus = "UlLoading Resource " + msg; UlStatus = "UlLoading Resource " + msg;
} }
float DownloadSpeed = 0;
void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) { void AsyncUpdate(uint64_t& Rcv, uint64_t Size, const std::string& Name) {
do { do {
double pr = double(Rcv) / double(Size) * 100; double pr = double(Rcv) / double(Size) * 100;
std::string Per = std::to_string(trunc(pr * 10) / 10); std::string Per = std::to_string(trunc(pr * 10) / 10);
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)"); std::string SpeedString = "";
if (DownloadSpeed > 0.01) {
std::stringstream ss;
ss << " at " << std::setprecision(1) << std::fixed << DownloadSpeed << " Mbit/s";
SpeedString = ss.str();
}
UpdateUl(true, Name + " (" + Per.substr(0, Per.find('.') + 2) + "%)" + SpeedString);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while (!Terminate && Rcv < Size); } while (!Terminate && Rcv < Size);
} }
char* TCPRcvRaw(asio::ip::tcp::socket& Sock, uint64_t& GRcv, uint64_t Size) { // MICROSOFT, I DONT CARE, WRITE BETTER CODE
char* File = new char[Size]; #undef min
std::vector<char> TCPRcvRaw(SOCKET Sock, uint64_t& GRcv, uint64_t Size) {
if (Sock == -1) {
Terminate = true;
UUl("Invalid Socket");
return {};
}
std::vector<char> File(Size);
uint64_t Rcv = 0; uint64_t Rcv = 0;
asio::error_code ec;
auto start = std::chrono::high_resolution_clock::now();
int i = 0;
do { do {
int Len = int(Size - Rcv); // receive at most some MB at a time
if (Len > 1000000) int Len = std::min(int(Size - Rcv), 1 * 1024 * 1024);
Len = 1000000; int32_t Temp = recv(Sock, &File[Rcv], Len, MSG_WAITALL);
int32_t Temp = asio::read(Sock, asio::buffer(&File[Rcv], Len), ec); if (Temp < 1) {
if (ec) { info(std::to_string(Temp));
::error(fmt::format("Failed to receive data from server: {}", ec.message())); UUl("Socket Closed Code 1");
UUl("Failed to receive data from server, connection closed (Code 1)");
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
delete[] File; return {};
return nullptr;
} }
Rcv += Temp; Rcv += Temp;
GRcv += Temp; GRcv += Temp;
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); } while (Rcv < Size && !Terminate);
return File; return File;
} }
void MultiKill(asio::ip::tcp::socket& Sock, asio::ip::tcp::socket& Sock1) { void MultiKill(SOCKET Sock, SOCKET Sock1) {
KillSocket(Sock1); KillSocket(Sock1);
KillSocket(Sock); KillSocket(Sock);
Terminate = true; Terminate = true;
} }
std::shared_ptr<asio::ip::tcp::socket> InitDSock(asio::ip::tcp::endpoint ep) { SOCKET InitDSock() {
auto DSock = std::make_shared<asio::ip::tcp::socket>(io); SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
asio::error_code ec; SOCKADDR_IN ServerAddr;
DSock->connect(ep, ec); if (DSock < 1) {
if (ec) {
KillSocket(DSock); KillSocket(DSock);
Terminate = true; Terminate = true;
return nullptr; return 0;
}
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) {
KillSocket(DSock);
Terminate = true;
return 0;
} }
char Code[2] = { 'D', char(ClientID) }; char Code[2] = { 'D', char(ClientID) };
asio::write(*DSock, asio::buffer(Code, 2), ec); if (send(DSock, Code, 2, 0) != 2) {
if (ec) {
KillSocket(DSock); KillSocket(DSock);
Terminate = true; Terminate = true;
return 0; return 0;
@@ -185,44 +219,78 @@ std::shared_ptr<asio::ip::tcp::socket> InitDSock(asio::ip::tcp::endpoint ep) {
return DSock; return DSock;
} }
std::string MultiDownload(asio::ip::tcp::socket& MSock, asio::ip::tcp::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); }); const std::vector<char> MData = TCPRcvRaw(MSock, GRcv, Size);
std::future<char*> f1 = task.get_future();
std::thread Dt(std::move(task));
Dt.detach();
char* DData = TCPRcvRaw(DSock, GRcv, DSize); if (MData.empty()) {
KillSocket(MSock);
if (!DData) { Terminate = true;
MultiKill(MSock, DSock);
return "";
}
f1.wait();
char* MData = f1.get();
if (!MData) {
MultiKill(MSock, DSock);
return "";
}
if (Au.joinable())
Au.join(); Au.join();
return {};
}
/// omg yes very ugly my god but i was in a rush will revisit // ensure that GRcv is good before joining the async update thread
std::string Ret(Size, 0); GRcv = MData.size();
memcpy(&Ret[0], MData, MSize); if (GRcv != Size) {
delete[] MData; error("Something went wrong during download; didn't get enough data. Expected " + std::to_string(Size) + " bytes, got " + std::to_string(GRcv) + " bytes instead");
Terminate = true;
Au.join();
return {};
}
memcpy(&Ret[MSize], DData, DSize); Au.join();
delete[] DData; return MData;
}
return Ret; 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) { void InvalidResource(const std::string& File) {
@@ -231,31 +299,269 @@ void InvalidResource(const std::string& File) {
Terminate = true; Terminate = true;
} }
void SyncResources(asio::ip::tcp::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); std::string Ret = Auth(Sock);
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()) if (Ret.empty())
return; return;
if (!SecurityWarning())
return;
info("Checking Resources..."); info("Checking Resources...");
CheckForDir(); CheckForDir();
std::vector<std::string> list = Split(Ret, ";"); std::vector<std::string> list = Utils::Split(Ret, ";");
std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2)); std::vector<std::string> FNames(list.begin(), list.begin() + (list.size() / 2));
std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end()); std::vector<std::string> FSizes(list.begin() + (list.size() / 2), list.end());
list.clear(); list.clear();
Ret.clear(); Ret.clear();
int Amount = 0, Pos = 0; int Amount = 0, Pos = 0;
std::string a, t; std::string PathToSaveTo, t;
for (const std::string& name : FNames) { for (const std::string& name : FNames) {
if (!name.empty()) { if (!name.empty()) {
t += name.substr(name.find_last_of('/') + 1) + ";"; t += name.substr(name.find_last_of('/') + 1) + ";";
} }
} }
if (t.empty()) if (t.empty())
ListOfMods = "-"; CoreSend("L");
else else
ListOfMods = t; CoreSend("L" + t);
t.clear(); t.clear();
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
@@ -270,25 +576,27 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
} }
if (!FNames.empty()) if (!FNames.empty())
info("Syncing..."); info("Syncing...");
auto DSock = InitDSock(Sock.remote_endpoint()); SOCKET DSock = InitDSock();
for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) { for (auto FN = FNames.begin(), FS = FSizes.begin(); FN != FNames.end() && !Terminate; ++FN, ++FS) {
auto pos = FN->find_last_of('/'); auto pos = FN->find_last_of('/');
if (pos != std::string::npos) { if (pos != std::string::npos) {
a = "Resources" + FN->substr(pos); PathToSaveTo = CachingDirectory + FN->substr(pos);
} else } else {
continue; continue;
}
Pos++; Pos++;
if (fs::exists(a)) { auto FileSize = std::stoull(*FS);
if (fs::exists(PathToSaveTo)) {
if (FS->find_first_not_of("0123456789") != std::string::npos) if (FS->find_first_not_of("0123456789") != std::string::npos)
continue; continue;
if (fs::file_size(a) == std::stoull(*FS)) { if (fs::file_size(PathToSaveTo) == FileSize) {
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + a.substr(a.find_last_of('/'))); UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + PathToSaveTo.substr(PathToSaveTo.find_last_of('/')));
std::this_thread::sleep_for(std::chrono::milliseconds(50)); std::this_thread::sleep_for(std::chrono::milliseconds(50));
try { try {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
} }
auto modname = a.substr(a.find_last_of('/')); auto modname = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
#if defined(__linux__) #if defined(__linux__)
// Linux version of the game doesnt support uppercase letters in mod names // Linux version of the game doesnt support uppercase letters in mod names
for (char& c : modname) { for (char& c : modname) {
@@ -297,7 +605,7 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
#endif #endif
auto name = GetGamePath() + "mods/multiplayer" + modname; auto name = GetGamePath() + "mods/multiplayer" + modname;
auto tmp_name = name + ".tmp"; auto tmp_name = name + ".tmp";
fs::copy_file(a, tmp_name, fs::copy_options::overwrite_existing); fs::copy_file(PathToSaveTo, tmp_name, fs::copy_options::overwrite_existing);
fs::rename(tmp_name, name); fs::rename(tmp_name, name);
} catch (std::exception& e) { } catch (std::exception& e) {
error("Failed copy to the mods folder! " + std::string(e.what())); error("Failed copy to the mods folder! " + std::string(e.what()));
@@ -307,12 +615,13 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
WaitForConfirm(); WaitForConfirm();
continue; continue;
} else } else
remove(a.c_str()); remove(PathToSaveTo.c_str());
} }
CheckForDir(); CheckForDir();
std::string FName = a.substr(a.find_last_of('/')); std::string FName = PathToSaveTo.substr(PathToSaveTo.find_last_of('/'));
do { do {
TCPSend(strtovec("f" + *FN), Sock); debug("Loading file '" + FName + "' to '" + PathToSaveTo + "'");
TCPSend("f" + *FN, Sock);
std::string Data = TCPRcv(Sock); std::string Data = TCPRcv(Sock);
if (Data == "CO" || Terminate) { if (Data == "CO" || Terminate) {
@@ -323,19 +632,23 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName; std::string Name = std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName;
Data = MultiDownload(Sock, *DSock, std::stoull(*FS), Name); std::vector<char> DownloadedFile = MultiDownload(Sock, DSock, FileSize, Name);
if (Terminate) if (Terminate)
break; break;
UpdateUl(false, std::to_string(Pos) + "/" + std::to_string(Amount) + ": " + FName); 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();
}
} while (fs::file_size(a) != std::stoull(*FS) && !Terminate); // 1. write downloaded file to disk
{
std::ofstream OutFile(PathToSaveTo, std::ios::binary | std::ios::trunc);
OutFile.write(DownloadedFile.data(), DownloadedFile.size());
}
// 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 (!Terminate) {
if (!fs::exists(GetGamePath() + "mods/multiplayer")) { if (!fs::exists(GetGamePath() + "mods/multiplayer")) {
fs::create_directories(GetGamePath() + "mods/multiplayer"); fs::create_directories(GetGamePath() + "mods/multiplayer");
@@ -348,13 +661,14 @@ void SyncResources(asio::ip::tcp::socket& Sock) {
} }
#endif #endif
fs::copy_file(a, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing); fs::copy_file(PathToSaveTo, GetGamePath() + "mods/multiplayer" + FName, fs::copy_options::overwrite_existing);
} }
WaitForConfirm(); WaitForConfirm();
} }
KillSocket(DSock); KillSocket(DSock);
if (!Terminate) { if (!Terminate) {
TCPSend(strtovec("Done"), Sock); TCPSend("Done", Sock);
info("Done!"); info("Done!");
} else { } else {
UlStatus = "Ulstart"; UlStatus = "Ulstart";

View File

@@ -7,8 +7,7 @@
/// ///
#include "Network/network.hpp" #include "Network/network.hpp"
#include "Zlib/Compressor.h" #include "Zlib/Compressor.h"
#include "asio/ip/address.hpp" #include <stdexcept>
#include "fmt/format.h"
#if defined(_WIN32) #if defined(_WIN32)
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -25,78 +24,84 @@
#include <array> #include <array>
#include <string> #include <string>
std::shared_ptr<asio::ip::udp::socket> UDPSock = nullptr; SOCKET UDPSock = -1;
sockaddr_in* ToServer = nullptr;
void UDPSend(const std::vector<char>& RawData) { void UDPSend(std::string Data) {
if (ClientID == -1 || UDPSock == nullptr) if (ClientID == -1 || UDPSock == -1)
return; return;
std::string Data; if (Data.length() > 400) {
if (Data.size() > 400) { auto res = Comp(std::span<char>(Data.data(), Data.size()));
auto res = Comp(RawData);
Data = "ABG:" + std::string(res.data(), res.size()); Data = "ABG:" + std::string(res.data(), res.size());
} else {
Data = std::string(RawData.data(), RawData.size());
} }
std::string Packet = char(ClientID + 1) + std::string(":") + Data; std::string Packet = char(ClientID + 1) + std::string(":") + Data;
int sendOk = UDPSock->send(asio::buffer(Packet)); int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer));
if (sendOk == SOCKET_ERROR) if (sendOk == SOCKET_ERROR)
error("Error Code : " + std::to_string(WSAGetLastError())); error("Error Code : " + std::to_string(WSAGetLastError()));
} }
void SendLarge(const std::vector<char>& Data) { void SendLarge(std::string Data) {
if (Data.size() > 400) { if (Data.length() > 400) {
auto res = Comp(Data); auto res = Comp(std::span<char>(Data.data(), Data.size()));
res.insert(res.begin(), { 'A', 'B', 'G', ':' }); Data = "ABG:" + std::string(res.data(), res.size());
if (!TCPSock) {
::debug("TCPSock is null");
return;
}
TCPSend(res, *TCPSock);
} else {
if (!TCPSock) {
::debug("TCPSock is null");
return;
}
TCPSend(Data, *TCPSock);
} }
TCPSend(Data, TCPSock);
} }
void UDPParser(std::string_view Packet) { void UDPParser(std::string_view Packet) {
if (Packet.substr(0, 4) == "ABG:") { if (Packet.substr(0, 4) == "ABG:") {
auto substr = Packet.substr(4); auto substr = Packet.substr(4);
auto res = DeComp(std::span<const char>(substr.data(), substr.size())); try {
std::string DeCompPacket = std::string(res.data(), res.size()); auto res = DeComp(std::span<const char>(substr.data(), substr.size()));
ServerParser(DeCompPacket); 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 { } else {
ServerParser(Packet); ServerParser(Packet);
} }
} }
void UDPRcv() { void UDPRcv() {
sockaddr_in FromServer {};
#if defined(_WIN32)
int clientLength = sizeof(FromServer);
#elif defined(__linux__)
socklen_t clientLength = sizeof(FromServer);
#endif
ZeroMemory(&FromServer, clientLength);
static thread_local std::array<char, 10240> Ret {}; static thread_local std::array<char, 10240> Ret {};
if (UDPSock == nullptr) { if (UDPSock == -1)
::debug("UDPSock is null");
return; return;
} int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength);
asio::error_code ec; if (Rcv == SOCKET_ERROR)
int32_t Rcv = UDPSock->receive(asio::buffer(Ret.data(), Ret.size() - 1), 0, ec);
if (ec)
return; return;
Ret[Rcv] = 0; Ret[Rcv] = 0;
UDPParser(std::string_view(Ret.data(), Rcv)); UDPParser(std::string_view(Ret.data(), Rcv));
} }
void UDPClientMain(asio::ip::address addr, uint16_t port) { void UDPClientMain(const std::string& IP, int Port) {
UDPSock = std::make_shared<asio::ip::udp::socket>(io); #ifdef _WIN32
asio::error_code ec; WSADATA data;
UDPSock->connect(asio::ip::udp::endpoint(addr, port), ec); if (WSAStartup(514, &data)) {
if (ec) { error("Can't start Winsock!");
::error(fmt::format("Failed to connect UDP to server: {}", ec.message())); return;
Terminate = true;
} }
#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)); GameSend("P" + std::to_string(ClientID));
TCPSend(strtovec("H"), *TCPSock); TCPSend("H", TCPSock);
UDPSend(strtovec("p")); UDPSend("p");
while (!Terminate) debug("Starting UDP receive loop");
while (!Terminate) {
UDPRcv(); UDPRcv();
}
debug("UDP receive loop done");
KillSocket(UDPSock); KillSocket(UDPSock);
WSACleanup(); WSACleanup();
} }

View File

@@ -7,7 +7,6 @@
/// ///
#include "Logger.h" #include "Logger.h"
#include "fmt/format.h"
#include <Zlib/Compressor.h> #include <Zlib/Compressor.h>
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
@@ -28,7 +27,7 @@
int LastPort; int LastPort;
std::string LastIP; std::string LastIP;
std::shared_ptr<asio::ip::tcp::socket> TCPSock = nullptr; SOCKET TCPSock = -1;
bool CheckBytes(int32_t Bytes) { bool CheckBytes(int32_t Bytes) {
if (Bytes == 0) { if (Bytes == 0) {
@@ -47,8 +46,8 @@ void UUl(const std::string& R) {
UlStatus = "UlDisconnected: " + R; UlStatus = "UlDisconnected: " + R;
} }
void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock) { void TCPSend(const std::string& Data, uint64_t Sock) {
if (!Sock.is_open()) { if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return; return;
@@ -58,44 +57,64 @@ void TCPSend(const std::vector<char>& Data, asio::ip::tcp::socket& Sock) {
std::string Send(4, 0); std::string Send(4, 0);
Size = int32_t(Data.size()); Size = int32_t(Data.size());
memcpy(&Send[0], &Size, sizeof(Size)); memcpy(&Send[0], &Size, sizeof(Size));
Send += std::string(Data.data(), Data.size()); Send += Data;
// Do not use Size before this point for anything but the header // Do not use Size before this point for anything but the header
Sent = 0; Sent = 0;
Size += 4; Size += 4;
asio::error_code ec; do {
asio::write(Sock, asio::buffer(Send), ec); if (size_t(Sent) >= Send.size()) {
if (ec) { error("string OOB in " + std::string(__func__));
UUl(fmt::format("Failed to send data: {}", ec.message())); 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(asio::ip::tcp::socket& Sock) { std::string TCPRcv(SOCKET Sock) {
if (!Sock.is_open()) { if (Sock == -1) {
Terminate = true; Terminate = true;
UUl("Invalid Socket"); UUl("Invalid Socket");
return ""; return "";
} }
int32_t Header, BytesRcv = 0, Temp; int32_t Header, Temp;
std::vector<char> Data(sizeof(Header)); std::vector<char> Data(sizeof(Header));
asio::error_code ec; Temp = recv(Sock, Data.data(), sizeof(Header), MSG_WAITALL);
asio::read(Sock, asio::buffer(Data), ec); if (!CheckBytes(Temp)) {
if (ec) { UUl("Socket Closed Code 3");
UUl(fmt::format("Failed to receive header: {}", ec.message())); return "";
} }
memcpy(&Header, &Data[0], sizeof(Header)); memcpy(&Header, Data.data(), sizeof(Header));
Data.resize(Header); if (!CheckBytes(Temp)) {
asio::read(Sock, asio::buffer(Data), ec); UUl("Socket Closed Code 4");
if (ec) { return "";
UUl(fmt::format("Failed to receive data: {}", ec.message())); }
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); std::string Ret(Data.data(), Header);
if (Ret.substr(0, 4) == "ABG:") { if (Ret.substr(0, 4) == "ABG:") {
auto substr = Ret.substr(4); auto substr = Ret.substr(4);
auto res = DeComp(strtovec(substr)); try {
Ret = std::string(res.data(), res.size()); auto res = DeComp(std::span<char>(substr.data(), substr.size()));
Ret = std::string(res.data(), res.size());
} catch (const std::runtime_error& err) {
// this happens e.g. when we're out of memory, or when we get incomplete data
error("Decompression failed");
return "";
}
} }
#ifdef DEBUG #ifdef DEBUG
@@ -106,24 +125,51 @@ std::string TCPRcv(asio::ip::tcp::socket& Sock) {
return Ret; return Ret;
} }
void TCPClientMain(asio::ip::tcp::socket&& socket) { void TCPClientMain(const std::string& IP, int Port) {
if (!TCPSock) { LastIP = IP;
return; LastPort = Port;
}
LastIP = socket.remote_endpoint().address().to_string();
LastPort = socket.remote_endpoint().port();
SOCKADDR_IN ServerAddr; SOCKADDR_IN ServerAddr;
int RetCode; int RetCode;
TCPSock = std::make_shared<asio::ip::tcp::socket>(std::move(socket)); #ifdef _WIN32
WSADATA wsaData;
WSAStartup(514, &wsaData); // 2.2
#endif
TCPSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
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) {
UlStatus = "UlConnection Failed!";
error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError()));
KillSocket(TCPSock);
WSACleanup();
Terminate = true;
CoreSend("L");
return;
}
info("Connected!"); info("Connected!");
char Code = 'C'; char Code = 'C';
asio::write(*TCPSock, asio::buffer(&Code, 1)); send(TCPSock, &Code, 1, 0);
SyncResources(*TCPSock); SyncResources(TCPSock);
while (!Terminate && TCPSock) { while (!Terminate) {
ServerParser(TCPRcv(*TCPSock)); ServerParser(TCPRcv(TCPSock));
} }
GameSend("T"); GameSend("T");
KillSocket(TCPSock); ////Game Send Terminate
if (KillSocket(TCPSock) != 0)
debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError()));
#ifdef _WIN32
if (WSACleanup() != 0)
debug("(TCP) Client: WSACleanup() failed!...");
#endif
} }

View File

@@ -1,35 +0,0 @@
#include "NetworkHelpers.h"
#include <array>
#include <cerrno>
#include <cstring>
#include <stdexcept>
using asio::ip::tcp;
static uint32_t RecvHeader(tcp::socket& socket) {
std::array<uint8_t, sizeof(uint32_t)> header_buffer {};
asio::error_code ec;
auto n = asio::read(socket, asio::buffer(header_buffer), ec);
if (ec) {
throw std::runtime_error(std::string("recv() of header failed: ") + ec.message());
}
if (n == 0) {
throw std::runtime_error("Game disconnected");
}
return *reinterpret_cast<uint32_t*>(header_buffer.data());
}
/// Throws!!!
void ReceiveFromGame(tcp::socket& socket, std::vector<char>& out_data) {
auto header = RecvHeader(socket);
out_data.resize(header);
asio::error_code ec;
auto n = asio::read(socket, asio::buffer(out_data), ec);
if (ec) {
throw std::runtime_error(std::string("recv() of data failed: ") + ec.message());
}
if (n == 0) {
throw std::runtime_error("Game disconnected");
}
}

107
src/Options.cpp Normal file
View File

@@ -0,0 +1,107 @@
#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++;
}
}

View File

@@ -17,7 +17,6 @@
#endif #endif
#include "Logger.h" #include "Logger.h"
#include <fstream> #include <fstream>
#include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -34,24 +33,8 @@ void lowExit(int code) {
std::this_thread::sleep_for(std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::seconds(10));
exit(2); exit(2);
} }
/*void Exit(int code){
TraceBack = 0;
std::string 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);
}
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() { std::string GetGameDir() {
// if(TraceBack != 4)Exit(0);
#if defined(_WIN32) #if defined(_WIN32)
return GameDir.substr(0, GameDir.find_last_of('\\')); return GameDir.substr(0, GameDir.find_last_of('\\'));
#elif defined(__linux__) #elif defined(__linux__)
@@ -175,151 +158,7 @@ void FileList(std::vector<std::string>& a, const std::string& Path) {
} }
} }
} }
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().string();
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() { 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);*/
#if defined(_WIN32) #if defined(_WIN32)
std::string Result; std::string Result;
std::string K3 = R"(Software\BeamNG\BeamNG.drive)"; std::string K3 = R"(Software\BeamNG\BeamNG.drive)";
@@ -327,17 +166,18 @@ RegCloseKey(hKey);*/
LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey); LONG dwRegOPenKey = OpenKey(HKEY_CURRENT_USER, K3.c_str(), &hKey);
if (dwRegOPenKey == ERROR_SUCCESS) { if (dwRegOPenKey == ERROR_SUCCESS) {
Result = QueryKey(hKey, 3); Result = QueryKey(hKey, 3);
if (Result.empty()) if (Result.empty()) {
debug("Failed to QUERY key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(3); lowExit(3);
// if(IDCheck(Result,T))lowExit(5); }
GameDir = Result; GameDir = Result;
// TraceBack++; } else {
} else debug("Failed to OPEN key HKEY_CURRENT_USER\\Software\\BeamNG\\BeamNG.drive");
lowExit(4); lowExit(4);
}
K3.clear(); K3.clear();
Result.clear(); Result.clear();
RegCloseKey(hKey); RegCloseKey(hKey);
// if(TraceBack < 3)exit(-1);
#elif defined(__linux__) #elif defined(__linux__)
struct passwd* pw = getpwuid(getuid()); struct passwd* pw = getpwuid(getuid());
std::string homeDir = pw->pw_dir; std::string homeDir = pw->pw_dir;

View File

@@ -18,6 +18,7 @@ std::string PrivateKey;
extern bool LoginAuth; extern bool LoginAuth;
extern std::string Username; extern std::string Username;
extern std::string UserRole; extern std::string UserRole;
extern int UserID;
void UpdateKey(const char* newKey) { void UpdateKey(const char* newKey) {
if (newKey && std::isalnum(newKey[0])) { if (newKey && std::isalnum(newKey[0])) {
@@ -48,6 +49,7 @@ std::string Login(const std::string& fields) {
if (fields == "LO") { if (fields == "LO") {
Username = ""; Username = "";
UserRole = ""; UserRole = "";
UserID = -1;
LoginAuth = false; LoginAuth = false;
UpdateKey(nullptr); UpdateKey(nullptr);
return ""; return "";
@@ -56,7 +58,7 @@ std::string Login(const std::string& fields) {
try { try {
std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields); std::string Buffer = HTTP::Post("https://auth.beammp.com/userlogin", fields);
if (Buffer == "-1") { if (Buffer.empty()) {
return GetFail("Failed to communicate with the auth system!"); return GetFail("Failed to communicate with the auth system!");
} }
@@ -74,6 +76,9 @@ std::string Login(const std::string& fields) {
if (d.contains("role")) { if (d.contains("role")) {
UserRole = d["role"].get<std::string>(); UserRole = d["role"].get<std::string>();
} }
if (d.contains("id")) {
UserID = d["id"].get<int>();
}
if (d.contains("private_key")) { if (d.contains("private_key")) {
UpdateKey(d["private_key"].get<std::string>().c_str()); UpdateKey(d["private_key"].get<std::string>().c_str());
} }
@@ -86,6 +91,7 @@ std::string Login(const std::string& fields) {
if (d.contains("message")) { if (d.contains("message")) {
d.erase("private_key"); d.erase("private_key");
d.erase("public_key"); d.erase("public_key");
debug("Authentication result: " + d["message"].get<std::string>());
return d.dump(); return d.dump();
} }
return GetFail("Invalid message parsing!"); return GetFail("Invalid message parsing!");
@@ -114,7 +120,7 @@ void CheckLocalKey() {
nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false); nlohmann::json d = nlohmann::json::parse(Buffer, nullptr, false);
if (Buffer == "-1" || Buffer.at(0) != '{' || d.is_discarded()) { if (Buffer.empty() || Buffer.at(0) != '{' || d.is_discarded()) {
error(Buffer); error(Buffer);
info("Invalid answer from authentication servers."); info("Invalid answer from authentication servers.");
UpdateKey(nullptr); UpdateKey(nullptr);
@@ -129,7 +135,9 @@ void CheckLocalKey() {
if (d.contains("role")) { if (d.contains("role")) {
UserRole = d["role"].get<std::string>(); UserRole = d["role"].get<std::string>();
} }
// info(Role); if (d.contains("id")) {
UserID = d["id"].get<int>();
}
} else { } else {
info("Auto-Authentication unsuccessful please re-login!"); info("Auto-Authentication unsuccessful please re-login!");
UpdateKey(nullptr); UpdateKey(nullptr);

View File

@@ -8,6 +8,7 @@
#include "zip_file.h" #include "zip_file.h"
#include <charconv> #include <charconv>
#include <cstring>
#include <httplib.h> #include <httplib.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string> #include <string>
@@ -25,9 +26,9 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <thread> #include <thread>
#include "Options.h"
extern int TraceBack; extern int TraceBack;
bool Dev = false;
int ProxyPort = 0; int ProxyPort = 0;
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -81,13 +82,13 @@ std::string GetEN() {
} }
std::string GetVer() { std::string GetVer() {
return "2.1"; return "2.3";
} }
std::string GetPatch() { std::string GetPatch() {
return ".0"; return ".2";
} }
std::string GetEP(char* P) { std::string GetEP(const char* P) {
static std::string Ret = [&]() { static std::string Ret = [&]() {
std::string path(P); std::string path(P);
return path.substr(0, path.find_last_of("\\/") + 1); return path.substr(0, path.find_last_of("\\/") + 1);
@@ -95,23 +96,24 @@ std::string GetEP(char* P) {
return Ret; return Ret;
} }
#if defined(_WIN32) #if defined(_WIN32)
void ReLaunch(int argc, char* args[]) { void ReLaunch() {
std::string Arg; std::string Arg;
for (int c = 2; c <= argc; c++) { for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " "; Arg += " ";
Arg += args[c - 1];
} }
info("Relaunch!");
system("cls"); system("cls");
ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL); ShellExecute(nullptr, "runas", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0); ShowWindow(GetConsoleWindow(), 0);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
void URelaunch(int argc, char* args[]) { void URelaunch() {
std::string Arg; std::string Arg;
for (int c = 2; c <= argc; c++) { for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " "; Arg += " ";
Arg += args[c - 1];
} }
ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL); ShellExecute(nullptr, "open", (GetEP() + GetEN()).c_str(), Arg.c_str(), nullptr, SW_SHOWNORMAL);
ShowWindow(GetConsoleWindow(), 0); ShowWindow(GetConsoleWindow(), 0);
@@ -119,46 +121,50 @@ void URelaunch(int argc, char* args[]) {
exit(1); exit(1);
} }
#elif defined(__linux__) #elif defined(__linux__)
void ReLaunch(int argc, char* args[]) { void ReLaunch() {
std::string Arg; std::string Arg;
for (int c = 2; c <= argc; c++) { for (int c = 2; c <= options.argc; c++) {
Arg += options.argv[c - 1];
Arg += " "; Arg += " ";
Arg += args[c - 1];
} }
info("Relaunch!");
system("clear"); system("clear");
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL); 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)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
void URelaunch(int argc, char* args[]) { void URelaunch() {
std::string Arg; int ret = execv(options.executable_name.c_str(), const_cast<char**>(options.argv));
for (int c = 2; c <= argc; c++) { if (ret < 0) {
Arg += " "; error(std::string("execv() failed with: ") + strerror(errno) + ". Failed to relaunch");
Arg += args[c - 1]; exit(1);
} }
execl((GetEP() + GetEN()).c_str(), Arg.c_str(), NULL);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
exit(1); exit(1);
} }
#endif #endif
void CheckName(int argc, char* args[]) { void CheckName() {
#if defined(_WIN32) #if defined(_WIN32)
std::string DN = GetEN(), CDir = args[0], FN = CDir.substr(CDir.find_last_of('\\') + 1); std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('\\') + 1);
#elif defined(__linux__) #elif defined(__linux__)
std::string DN = GetEN(), CDir = args[0], FN = CDir.substr(CDir.find_last_of('/') + 1); std::string DN = GetEN(), CDir = options.executable_name, FN = CDir.substr(CDir.find_last_of('/') + 1);
#endif #endif
if (FN != DN) { if (FN != DN) {
if (fs::exists(DN)) if (fs::exists(DN))
remove(DN.c_str()); remove(DN.c_str());
if (fs::exists(DN)) if (fs::exists(DN))
ReLaunch(argc, args); ReLaunch();
std::rename(FN.c_str(), DN.c_str()); std::rename(FN.c_str(), DN.c_str());
URelaunch(argc, args); URelaunch();
} }
} }
void CheckForUpdates(int argc, char* args[], const std::string& CV) { void CheckForUpdates(const std::string& CV) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey); std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/launcher?branch=" + Branch + "&pk=" + PublicKey);
std::string LatestVersion = HTTP::Get( std::string LatestVersion = HTTP::Get(
"https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey); "https://backend.beammp.com/version/launcher?branch=" + Branch + "&pk=" + PublicKey);
@@ -167,51 +173,31 @@ void CheckForUpdates(int argc, char* args[], const std::string& CV) {
std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back"); std::string EP(GetEP() + GetEN()), Back(GetEP() + "BeamMP-Launcher.back");
std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP); std::string FileHash = hashpp::get::getFileHash(hashpp::ALGORITHMS::SHA2_256, EP);
#if defined(_WIN32)
#elif defined(__linux__)
system("clear");
#endif
if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) { if (FileHash != LatestHash && IsOutdated(Version(VersionStrToInts(GetVer() + GetPatch())), Version(VersionStrToInts(LatestVersion)))) {
info("Launcher update found!"); if (!options.no_update) {
info("Launcher update found!");
#if defined(__linux__) #if defined(__linux__)
error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches."); error("Auto update is NOT implemented for the Linux version. Please update manually ASAP as updates contain security patches.");
#else #else
fs::remove(Back); fs::remove(Back);
fs::rename(EP, Back); fs::rename(EP, Back);
info("Downloading Launcher update " + LatestHash); info("Downloading Launcher update " + LatestHash);
HTTP::Download( HTTP::Download(
"https://backend.beammp.com/builds/launcher?download=true" "https://backend.beammp.com/builds/launcher?download=true"
"&pk=" "&pk="
+ PublicKey + "&branch=" + Branch, + PublicKey + "&branch=" + Branch,
EP); EP);
URelaunch(argc, args); URelaunch();
#endif #endif
} else {
warn("Launcher update was found, but not updating because --no-update or --dev was specified.");
}
} else } else
info("Launcher version is up to date"); info("Launcher version is up to date");
TraceBack++; 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;
}
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "--dev") {
Dev = true;
} else if (std::string_view(argv[i]) == "--no-dev") {
Dev = false;
}
}
}
#ifdef _WIN32 #ifdef _WIN32
void LinuxPatch() { void LinuxPatch() {
@@ -243,34 +229,21 @@ void LinuxPatch() {
#endif #endif
#if defined(_WIN32) #if defined(_WIN32)
void InitLauncher(int argc, char* argv[]) {
system("cls"); void InitLauncher() {
SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str()); SetConsoleTitleA(("BeamMP Launcher v" + std::string(GetVer()) + GetPatch()).c_str());
InitLog(); CheckName();
CheckName(argc, argv);
LinuxPatch(); LinuxPatch();
CheckLocalKey(); CheckLocalKey();
ConfigInit(); CheckForUpdates(std::string(GetVer()) + GetPatch());
CustomPort(argc, argv);
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
} }
#elif defined(__linux__) #elif defined(__linux__)
void InitLauncher(int argc, char* argv[]) {
system("clear"); void InitLauncher() {
InitLog(); info("BeamMP Launcher v" + GetVer() + GetPatch());
CheckName(argc, argv); CheckName();
CheckLocalKey(); CheckLocalKey();
ConfigInit(); CheckForUpdates(std::string(GetVer()) + GetPatch());
CustomPort(argc, argv);
bool update = true;
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "--no-update") {
update = false;
}
}
if (update) {
CheckForUpdates(argc, argv, std::string(GetVer()) + GetPatch());
}
} }
#endif #endif
@@ -332,10 +305,9 @@ void PreGame(const std::string& GamePath) {
info("Game Version : " + GameVer); info("Game Version : " + GameVer);
CheckMP(GetGamePath() + "mods/multiplayer"); CheckMP(GetGamePath() + "mods/multiplayer");
info("Game user path: " + GetGamePath());
info("Game user path: '" + GetGamePath() + "'"); if (!options.no_download) {
if (!Dev) {
std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey); std::string LatestHash = HTTP::Get("https://backend.beammp.com/sha/mod?branch=" + Branch + "&pk=" + PublicKey);
transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower); transform(LatestHash.begin(), LatestHash.end(), LatestHash.begin(), ::tolower);
LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(), LatestHash.erase(std::remove_if(LatestHash.begin(), LatestHash.end(),
@@ -374,59 +346,3 @@ void PreGame(const std::string& GamePath) {
} }
} }
} }
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 StartProxy() {
std::thread proxy([&]() {
httplib::Server HTTPProxy;
httplib::Headers headers = {
{ "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() },
{ "Accept", "*/*" }
};
std::string pattern = "/:any1";
for (int i = 2; i <= 4; i++) {
HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) {
httplib::Client cli("https://backend.beammp.com");
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"));
}
if (auto cli_res = cli.Get(req.path, headers); 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.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) {
httplib::Client cli("https://backend.beammp.com");
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"));
}
if (auto cli_res = cli.Post(req.path, headers, req.body,
req.get_header_value("Content-Type"));
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");
}
});
pattern += "/:any" + std::to_string(i);
}
ProxyPort = HTTPProxy.bind_to_any_port("0.0.0.0");
HTTPProxy.listen_after_bind();
});
proxy.detach();
}

View File

@@ -10,8 +10,12 @@
#include "Network/network.hpp" #include "Network/network.hpp"
#include "Security/Init.h" #include "Security/Init.h"
#include "Startup.h" #include "Startup.h"
#include <curl/curl.h>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include "Options.h"
Options options;
[[noreturn]] void flush() { [[noreturn]] void flush() {
while (true) { while (true) {
@@ -20,26 +24,48 @@
} }
} }
int main(int argc, char* argv[]) { int main(int argc, const char** argv) try {
#if defined(_WIN32)
system("cls");
#elif defined(__linux__)
system("clear");
#endif
#ifdef DEBUG #ifdef DEBUG
std::thread th(flush); std::thread th(flush);
th.detach(); th.detach();
#endif #endif
curl_global_init(CURL_GLOBAL_ALL);
GetEP(argv[0]); GetEP(argv[0]);
InitLauncher(argc, argv); InitLog();
ConfigInit();
InitOptions(argc, argv, options);
info("Mod caching directory: " + CachingDirectory);
InitLauncher();
info("IMPORTANT: You MUST keep this window open to play BeamMP!");
try { try {
LegitimacyCheck(); LegitimacyCheck();
} catch (std::exception& e) { } catch (std::exception& e) {
fatal("Main 1 : " + std::string(e.what())); error("Failure in LegitimacyCheck: " + std::string(e.what()));
throw;
} }
StartProxy(); try {
HTTP::StartProxy();
} catch (const std::exception& e) {
error(std::string("Failed to start HTTP proxy: Some in-game functions may not work. Error: ") + e.what());
}
PreGame(GetGameDir()); PreGame(GetGameDir());
InitGame(GetGameDir()); InitGame(GetGameDir());
CoreNetwork(); CoreNetwork();
} catch (const std::exception& e) {
/// TODO: make sure to use argv[0] for everything that should be in the same dir (mod down ect...) error(std::string("Exception in main(): ") + e.what());
info("Closing in 5 seconds");
info("If this keeps happening, contact us on either: Forum: https://forum.beammp.com, Discord: https://discord.gg/beammp");
std::this_thread::sleep_for(std::chrono::seconds(5));
} }

View File

@@ -4,7 +4,6 @@
"nlohmann-json", "nlohmann-json",
"zlib", "zlib",
"openssl", "openssl",
"asio", "curl"
"fmt"
] ]
} }