diff --git a/.gitignore b/.gitignore index 01db102f..9ddccb24 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Resources/ bin/ compile_commands.json key +out/ +.vs/ +CMakeSettings.json \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index b37131e0..00d6695d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "evpp"] path = evpp url = https://github.com/BeamMP/evpp.git +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b6b98f83..05282449 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,13 @@ cmake_minimum_required(VERSION 3.10) +include(cmake/Vcpkg.cmake) + project(Launcher) +include(cmake/StandardSettings.cmake) +include(cmake/StaticAnalyzers.cmake) +include(cmake/Git.cmake) + if (WIN32) message(STATUS "MSVC -> forcing use of statically-linked runtime.") STRING(REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 00000000..c42cb17d --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,115 @@ +# from here: +# +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md +# Courtesy of Jason Turner +# License here: https://github.com/cpp-best-practices/cppbestpractices/blob/master/LICENSE +# +# This version has been modified by the owners of the current respository. +# Modifications have mostly been marked with "modified" or similar, though this is not +# strictly required. + +function(set_project_warnings project_name) + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss + # of data + /w14254 # 'operator': conversion from 'type1:field_bits' to + # 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class + # virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not + # virtual instances of this class may not be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable + # declared in the for-loop is used outside the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing + # an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected + # operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend + # 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may + # cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined + # conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a + # parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a + # non-virtual destructor. This helps catch hard to + # track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual + # function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output + # (ie printf) + # modified; added more errors / warnings + # some have been set to be errors, but the option _WARNINGS_AS_ERRORS + # (see below) should still be used in strict pipelines. + -Werror=uninitialized + -Werror=float-equal + -Werror=write-strings + -Werror=strict-aliasing -fstrict-aliasing + -Werror=missing-declarations + -Werror=missing-field-initializers + -Werror=ctor-dtor-privacy + -Werror=switch-enum + -Wswitch-default + -Werror=unused-result + -Werror=implicit-fallthrough + -Werror=return-type + -Wmissing-include-dirs + ) + + if (${PROJECT_NAME}_WARNINGS_AS_ERRORS) + set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) + set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) + endif() + + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks + # do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were + # probably wanted + # -Wuseless-cast # warn if you perform a cast to the same type (modified: removed) + ) + + if(MSVC) + set(PRJ_WARNINGS ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PRJ_WARNINGS ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PRJ_WARNINGS ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + target_compile_options(${project_name} PUBLIC ${PRJ_WARNINGS}) + + if(NOT TARGET ${project_name}) + message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") + endif() +endfunction() diff --git a/cmake/Git.cmake b/cmake/Git.cmake new file mode 100644 index 00000000..abeffc6c --- /dev/null +++ b/cmake/Git.cmake @@ -0,0 +1,21 @@ +find_package(Git) +if(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES) + if(Git_FOUND) + message(STATUS "Git found, submodule update and init") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(SEND_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, please checkout submodules. This may result in missing dependencies.") + endif() + else() + message(SEND_ERROR "git required for checking out submodules, but not found. Submodules will not be checked out - this may result in missing dependencies.") + endif() +endif() + +if(Git_FOUND) + +else() + message(STATUS "Git not found - the version will not include a git hash.") + set(PRJ_GIT_HASH "unknown") +endif() diff --git a/cmake/StandardSettings.cmake b/cmake/StandardSettings.cmake new file mode 100644 index 00000000..0d8808ce --- /dev/null +++ b/cmake/StandardSettings.cmake @@ -0,0 +1,44 @@ +# Modified, original version from https://github.com/filipdutescu/modern-cpp-template (Unlicense) + +option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." OFF) +option(${PROJECT_NAME}_CHECKOUT_GIT_SUBMODULES "If git is found, initialize all submodules." ON) +option(${PROJECT_NAME}_ENABLE_UNIT_TESTING "Enable unit tests for the projects (from the `test` subfolder)." ON) +option(${PROJECT_NAME}_ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy." OFF) +option(${PROJECT_NAME}_ENABLE_CPPCHECK "Enable static analysis with Cppcheck." OFF) +# TODO Implement code coverage +# option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE "Enable code coverage through GCC." OFF) +option(${PROJECT_NAME}_ENABLE_DOXYGEN "Enable Doxygen documentation builds of source." OFF) + +# Generate compile_commands.json for clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Export all symbols when building a shared library +if(BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) +endif() + +option(${PROJECT_NAME}_ENABLE_LTO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." OFF) +if(${PROJECT_NAME}_ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT result OUTPUT output) + if(result) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(SEND_ERROR "IPO is not supported: ${output}.") + endif() +endif() + +option(${PROJECT_NAME}_ENABLE_CCACHE "Enable the usage of Ccache, in order to speed up rebuild times." ON) +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif() + +option(${PROJECT_NAME}_ENABLE_SANITIZER "Enable sanitizer to detect memory errors, undefined behavior, etc. (slows down the executable)." OFF) +if(${PROJECT_NAME}_ENABLE_SANITIZER) + add_compile_options(-fsanitize=address,undefined) + add_link_options(-fsanitize=address,undefined) +endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake new file mode 100644 index 00000000..ed149f80 --- /dev/null +++ b/cmake/StaticAnalyzers.cmake @@ -0,0 +1,20 @@ +if(${PROJECT_NAME}_ENABLE_CLANG_TIDY) + find_program(CLANGTIDY clang-tidy) + if(CLANGTIDY) + set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) + message("Clang-Tidy finished setting up.") + else() + message(SEND_ERROR "Clang-Tidy requested but executable not found.") + endif() +endif() + +if(${PROJECT_NAME}_ENABLE_CPPCHECK) + find_program(CPPCHECK cppcheck) + if(CPPCHECK) + set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --suppress=missingInclude --enable=all + --inline-suppr --inconclusive) + message("Cppcheck finished setting up.") + else() + message(SEND_ERROR "Cppcheck requested but executable not found.") + endif() +endif() diff --git a/cmake/Vcpkg.cmake b/cmake/Vcpkg.cmake new file mode 100644 index 00000000..9d8ecbac --- /dev/null +++ b/cmake/Vcpkg.cmake @@ -0,0 +1,17 @@ +if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) + if(NOT EXISTS ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) + find_package(Git) + if(Git_FOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive vcpkg + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(SEND_ERROR "Checking out vcpkg in source tree failed with ${GIT_SUBMOD_RESULT}.") + endif() + else() + message(FATAL_ERROR "Could not find git or vcpkg.cmake. Please either, install git and re-run cmake (or run `git submodule update --init --recursive`), or install vcpkg and add `-DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake` to your cmake invocation. Please try again after making those changes.") + endif() + endif() + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) +endif() + diff --git a/include/Logger.h b/include/Logger.h index c588789c..06a445cc 100644 --- a/include/Logger.h +++ b/include/Logger.h @@ -8,6 +8,7 @@ #pragma once #include #include + void InitLog(); void except(const std::string& toPrint); void fatal(const std::string& toPrint); @@ -15,4 +16,5 @@ void debug(const std::string& toPrint); void error(const std::string& toPrint); void info(const std::string& toPrint); void warn(const std::string& toPrint); +void neterror(const std::string& toPrint); std::string getDate(); diff --git a/include/Network/network.hpp b/include/Network/network.hpp index 5c0b2b3d..2c91da1b 100644 --- a/include/Network/network.hpp +++ b/include/Network/network.hpp @@ -8,12 +8,23 @@ #pragma once #include +#include -#ifdef __linux__ +#if defined(_WIN32) +#include +#elif defined(__linux__) +#include +#include +#include +#include +#include +#include #include "linuxfixes.h" #include #include #include + +#define INVALID_SOCKET -1 #endif void NetReset(); @@ -21,6 +32,8 @@ extern bool Dev; extern int ping; [[noreturn]] void CoreNetwork(); +extern SOCKET LSocket; +extern bool shuttingdown; extern int ProxyPort; extern int ClientID; extern int LastPort; @@ -45,10 +58,18 @@ void GameSend(std::string_view Data); void SendLarge(std::string Data); std::string TCPRcv(uint64_t Sock); void SyncResources(uint64_t TCPSock); -std::string GetAddr(const std::string& IP); +std::string resolveHost(const std::string& IP); void ServerParser(std::string_view Data); std::string Login(const std::string& fields); void TCPSend(const std::string& Data, uint64_t Sock); void TCPClientMain(const std::string& IP, int Port); void UDPClientMain(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port); +/** + * Init a socket on the IP and port provided, and fill an sockaddr_storage. + * @param ip : IP of the distant host + * @param port : Port of the distant host + * @param sockType : Type of the socket asked: SOCK_DGRAM or SOCK_STREAM + * @param pStoreAddrInfo : A **valid** pointer to handle sockaddr informations used by the socket + */ +SOCKET initSocket(std::string ip, int port, int sockType, sockaddr_storage* pStoreAddrInfo); diff --git a/src/GameStart.cpp b/src/GameStart.cpp index 6e49db92..22b456f5 100644 --- a/src/GameStart.cpp +++ b/src/GameStart.cpp @@ -6,6 +6,8 @@ /// Created by Anonymous275 on 7/19/2020 /// +#include "Network/network.hpp" + #if defined(_WIN32) #include #elif defined(__linux__) @@ -83,12 +85,14 @@ void StartGame(std::string Dir) { info("Game Launched!"); GamePID = pi.dwProcessId; WaitForSingleObject(pi.hProcess, INFINITE); - error("Game Closed! launcher closing soon"); + warn("Game Closed! launcher closing soon"); } else { error("Failed to Launch the game! launcher closing soon"); } - std::this_thread::sleep_for(std::chrono::seconds(5)); - exit(2); + + shuttingdown = true; + if (LSocket != INVALID_SOCKET) + KillSocket(LSocket); } #elif defined(__linux__) void StartGame(std::string Dir) { @@ -105,9 +109,9 @@ void StartGame(std::string Dir) { waitpid(pid, &status, 0); error("Game Closed! launcher closing soon"); } - - std::this_thread::sleep_for(std::chrono::seconds(5)); - exit(2); + shuttingdown = true; + if (LSocket != INVALID_SOCKET) + KillSocket(LSocket); } #endif diff --git a/src/Logger.cpp b/src/Logger.cpp index 557cec00..de910569 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -13,6 +13,11 @@ #include #include +#ifdef WIN32 +#include +#endif // WIN32 + + std::string getDate() { time_t tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); tm local_tm = *localtime(&tt); @@ -82,3 +87,16 @@ void except(const std::string& toPrint) { std::cout << Print; addToLog(Print); } + +void neterror(const std::string& toPrint) { + std::string Print = getDate() + "[NET_ERROR] " + toPrint; +#ifdef WIN32 + int errorCode = WSAGetLastError(); + Print += " WSA error: " + std::to_string(WSAGetLastError()) + "\n"; +#else + int errorCode = errno; + Print += " System Error Code: " + std::to_string(errorCode) + "\n"; +#endif + std::cout << Print; + addToLog(Print); +} diff --git a/src/Network/Core.cpp b/src/Network/Core.cpp index 561b9c61..138d36e6 100644 --- a/src/Network/Core.cpp +++ b/src/Network/Core.cpp @@ -5,8 +5,9 @@ /// /// Created by Anonymous275 on 7/20/2020 /// -#include "Http.h" + #include "Network/network.hpp" +#include "Http.h" #include "Security/Init.h" #include #include @@ -44,27 +45,34 @@ std::string UlStatus; std::string MStatus; bool ModLoaded; int ping = -1; +bool shuttingdown = false; +SOCKET LSocket = INVALID_SOCKET; void StartSync(const std::string& Data) { - std::string IP = GetAddr(Data.substr(1, Data.find(':') - 1)); - if (IP.find('.') == -1) { - if (IP == "DNS") - UlStatus = "UlConnection Failed! (DNS Lookup Failed)"; - else - UlStatus = "UlConnection Failed! (WSA failed to start)"; + + std::string host = Data.substr(1, Data.rfind(':') - 1); + uint16_t port = std::stoi(Data.substr(Data.rfind(':') + 1)); + + std::string IP; + + IP = resolveHost(host); + + if (IP.length() == 0) { + UlStatus = "UlConnection Failed! (DNS Lookup Failed)"; ListOfMods = "-"; Terminate = true; 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::thread GS(TCPGameServer, IP, port); + GS.detach(); } bool IsAllowedLink(const std::string& Link) { @@ -171,6 +179,7 @@ void Parse(std::string Data, SOCKET CSocket) { Data = "Z" + GetVer(); break; case 'N': + if (SubCode == 'c') { nlohmann::json Auth = { { "Auth", LoginAuth ? 1 : 0 }, @@ -184,7 +193,7 @@ void Parse(std::string Data, SOCKET CSocket) { if (UserID != -1) { Auth["id"] = UserID; } - Data = "N" + Auth.dump(); + Data = "N" + Auth.dump(); } else { Data = "N" + Login(Data.substr(Data.find(':') + 1)); } @@ -202,6 +211,7 @@ void Parse(std::string Data, SOCKET CSocket) { } void GameHandler(SOCKET Client) { + int32_t Size, Temp, Rcv; char Header[10] = { 0 }; do { @@ -256,65 +266,50 @@ void localRes() { } void CoreMain() { debug("Core Network on start!"); - 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 - ZeroMemory(&hints, sizeof(hints)); + SOCKET CSocket; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE; - iRes = getaddrinfo(nullptr, std::to_string(DEFAULT_PORT).c_str(), &hints, &res); - if (iRes) { - 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(); + struct sockaddr_storage loopBackLUA { }; + + LSocket = initSocket("0.0.0.0", DEFAULT_PORT, SOCK_STREAM, &loopBackLUA); + + if (LSocket == INVALID_SOCKET) { + neterror("(Core) Client LUA Loopback socket creation failed!"); return; } - iRes = bind(LSocket, res->ai_addr, int(res->ai_addrlen)); + + int iRes = bind(LSocket, (sockaddr*)&loopBackLUA, sizeof(sockaddr_storage)); + if (iRes == SOCKET_ERROR) { - error("(Core) bind failed with error: " + std::to_string(WSAGetLastError())); - freeaddrinfo(res); + neterror("(Core) Client LUA Loopback socket binding failed!"); KillSocket(LSocket); - WSACleanup(); + shuttingdown = true; + warn("Maybe your game is already launched!"); return; } iRes = listen(LSocket, SOMAXCONN); if (iRes == SOCKET_ERROR) { - debug("(Core) listen failed with error: " + std::to_string(WSAGetLastError())); - freeaddrinfo(res); + debug("(Core) Client LUA Loopback socket listen failed!"); KillSocket(LSocket); - WSACleanup(); return; } + //MAIN LOOP do { + //Waiting LUA Connexion CSocket = accept(LSocket, nullptr, nullptr); if (CSocket == -1) { - error("(Core) accept failed with error: " + std::to_string(WSAGetLastError())); + if (shuttingdown) + break; + neterror("(Core) Client LUA Loopback socket accept failed!"); continue; } localRes(); - info("Game Connected!"); + info("Game Connected to LUA interface!"); GameHandler(CSocket); - warn("Game Reconnecting..."); - } while (CSocket); + warn("Game reconnecting to LUA interface..."); + } while (LSocket != INVALID_SOCKET); + KillSocket(LSocket); - WSACleanup(); } #if defined(_WIN32) @@ -328,22 +323,28 @@ int Handle(EXCEPTION_POINTERS* ep) { #endif [[noreturn]] void CoreNetwork() { - while (true) { -#if not defined(__MINGW32__) - __try { -#endif - - CoreMain(); -#if not defined(__MINGW32__) and not defined(__linux__) - } __except (Handle(GetExceptionInformation())) { } -#elif not defined(__MINGW32__) and defined(__linux__) - } - catch (...) { - except("(Core) Code : " + std::string(strerror(errno))); +#ifdef WIN32 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + fatal("(CoreNetwork) Can't start Winsock!"); } #endif - std::this_thread::sleep_for(std::chrono::seconds(1)); + while (!shuttingdown) { + try { + CoreMain(); + } catch (const std::exception& e) { + except("(Core) Fatal Execption: " + std::string(e.what())); +#ifdef WIN32 + WSACleanup(); +#endif + } catch (...) { + except("(Core) Code : " + std::string(strerror(errno))); + } } + +#ifdef _WIN32 + WSACleanup(); +#endif } diff --git a/src/Network/DNS.cpp b/src/Network/DNS.cpp index d28b829f..06d2f6ae 100644 --- a/src/Network/DNS.cpp +++ b/src/Network/DNS.cpp @@ -10,34 +10,54 @@ #if defined(_WIN32) #include +#include #elif defined(__linux__) #include "linuxfixes.h" #include #include +#include #endif #include "Logger.h" -std::string GetAddr(const std::string& IP) { - if (IP.find_first_not_of("0123456789.") == -1) - return IP; - hostent* host; -#ifdef _WIN32 - WSADATA wsaData; - if (WSAStartup(514, &wsaData) != 0) { - error("WSA Startup Failed!"); - WSACleanup(); - return ""; +/** + * Resolve IPs of host, prefered IPv6, and return IPv4 otherwise. + */ +std::string resolveHost(const std::string& hostStr) { + struct addrinfo* addresses = nullptr; + struct addrinfo hints {}; + memset(&hints, 0, sizeof(hints)); + + std::string resolved = ""; + + // UNSPEC to resolve both ip stack (IPv4 & IPv6) + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(hostStr.c_str(), nullptr, &hints, &addresses) != 0) + { + neterror("(DNS) getaddrinfo failed."); + return resolved; } -#endif - host = gethostbyname(IP.c_str()); - if (!host) { - error("DNS lookup failed! on " + IP); - WSACleanup(); - return "DNS"; + //Loop all and return it by prefeence ipv6 + for (struct addrinfo* ptr = addresses; ptr != nullptr; ptr = ptr->ai_next) { + char ipstr[INET6_ADDRSTRLEN] = { 0 }; + + if (ptr->ai_family == AF_INET6) { + struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)ptr->ai_addr; + inet_ntop(AF_INET6, &(ipv6->sin6_addr), ipstr, sizeof(ipstr)); + resolved = ipstr; + break; //Break if IPv6 finded + } + else if (ptr->ai_family == AF_INET) { + struct sockaddr_in* ipv4 = (struct sockaddr_in*)ptr->ai_addr; + inet_ntop(AF_INET, &(ipv4->sin_addr), ipstr, sizeof(ipstr)); + resolved = ipstr; + } } - std::string Ret = inet_ntoa(*((struct in_addr*)host->h_addr)); - WSACleanup(); - return Ret; + + freeaddrinfo(addresses); + return resolved; } \ No newline at end of file diff --git a/src/Network/GlobalHandler.cpp b/src/Network/GlobalHandler.cpp index dc4b5801..2e07365c 100644 --- a/src/Network/GlobalHandler.cpp +++ b/src/Network/GlobalHandler.cpp @@ -29,17 +29,18 @@ std::chrono::time_point PingStart, PingEnd; bool GConnected = false; bool CServer = true; -SOCKET CSocket = -1; -SOCKET GSocket = -1; +SOCKET CSocket = INVALID_SOCKET; +SOCKET GSocket = INVALID_SOCKET; +int ClientID = -1; int KillSocket(uint64_t Dead) { - if (Dead == (SOCKET)-1) { - debug("Kill socket got -1 returning..."); + if (Dead == INVALID_SOCKET) { + debug("Kill invalid socket got, returning..."); return 0; } shutdown(Dead, SD_BOTH); int a = closesocket(Dead); - if (a != 0) { + if (a != 0 && !shuttingdown) { warn("Failed to close socket!"); } return a; @@ -118,78 +119,30 @@ void ServerSend(std::string Data, bool Rel) { } void NetReset() { + debug("Network reset called"); TCPTerminate = false; GConnected = false; Terminate = false; UlStatus = "Ulstart"; MStatus = " "; - if (UDPSock != (SOCKET)(-1)) { + + if (UDPSock != INVALID_SOCKET) { debug("Terminating UDP Socket : " + std::to_string(TCPSock)); KillSocket(UDPSock); } UDPSock = -1; - if (TCPSock != (SOCKET)(-1)) { + if (TCPSock != INVALID_SOCKET) { debug("Terminating TCP Socket : " + std::to_string(TCPSock)); KillSocket(TCPSock); } TCPSock = -1; - if (GSocket != (SOCKET)(-1)) { + if (GSocket != INVALID_SOCKET) { debug("Terminating GTCP Socket : " + std::to_string(GSocket)); KillSocket(GSocket); } GSocket = -1; } -SOCKET SetupListener() { - if (GSocket != -1) - return GSocket; - struct addrinfo* result = nullptr; - struct addrinfo hints { }; - 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(DEFAULT_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() { while (!Terminate) { ServerSend("p", false); @@ -197,8 +150,9 @@ void AutoPing() { std::this_thread::sleep_for(std::chrono::seconds(1)); } } -int ClientID = -1; + void ParserAsync(std::string_view Data) { + if (Data.empty()) return; char Code = Data.at(0), SubCode = 0; @@ -221,9 +175,11 @@ void ParserAsync(std::string_view Data) { } GameSend(Data); } + void ServerParser(std::string_view Data) { ParserAsync(Data); } + void NetMain(const std::string& IP, int Port) { std::thread Ping(AutoPing); Ping.detach(); @@ -232,9 +188,32 @@ void NetMain(const std::string& IP, int Port) { Terminate = true; info("Connection Terminated!"); } + void TCPGameServer(const std::string& IP, int Port) { - GSocket = SetupListener(); - while (!TCPTerminate && GSocket != -1) { + + struct sockaddr_storage loopBackTcp { }; + + GSocket = initSocket("127.0.0.1", DEFAULT_PORT + 1, SOCK_STREAM, &loopBackTcp); + + if (GSocket == INVALID_SOCKET) { + return; + } + + int iRes = bind(GSocket, (sockaddr*)&loopBackTcp, sizeof(sockaddr_storage)); + if (iRes == SOCKET_ERROR) { + neterror("(Proxy) LoopBack TCP bind failed!"); + KillSocket(GSocket); + return; + } + + iRes = listen(GSocket, SOMAXCONN); + if (iRes == SOCKET_ERROR) { + neterror("(Proxy) LoopBack TCP listen failed!"); + KillSocket(GSocket); + return; + } + + while (!TCPTerminate && GSocket != INVALID_SOCKET) { debug("MAIN LOOP OF GAME SERVER"); GConnected = false; if (!CServer) { @@ -248,6 +227,7 @@ void TCPGameServer(const std::string& IP, int Port) { std::thread Client(TCPClientMain, IP, Port); Client.detach(); } + CSocket = accept(GSocket, nullptr, nullptr); if (CSocket == -1) { debug("(Proxy) accept failed with error: " + std::to_string(WSAGetLastError())); diff --git a/src/Network/Resources.cpp b/src/Network/Resources.cpp index f32c2633..5d4ce85b 100644 --- a/src/Network/Resources.cpp +++ b/src/Network/Resources.cpp @@ -34,6 +34,7 @@ namespace fs = std::filesystem; std::string ListOfMods; + std::vector Split(const std::string& String, const std::string& delimiter) { std::vector Val; size_t pos; @@ -72,6 +73,9 @@ void Abord() { info("Terminated!"); } +/** + * Auth the client, and return a list of mods needed as string, or empty string otherwise + */ std::string Auth(SOCKET Sock) { TCPSend("VC" + GetVer(), Sock); @@ -116,7 +120,7 @@ std::string Auth(SOCKET Sock) { ListOfMods = "-"; TCPSend("Done", Sock); info("Done!"); - return ""; + return ""; //return empty } return Res; } @@ -168,22 +172,30 @@ void MultiKill(SOCKET Sock, SOCKET Sock1) { KillSocket(Sock); Terminate = true; } +/** + * Init the download socket. + */ SOCKET InitDSock() { - SOCKET DSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - SOCKADDR_IN ServerAddr; - if (DSock < 1) { - KillSocket(DSock); + + struct sockaddr_storage serverDownload { }; + + SOCKET DSock = initSocket(LastIP, LastPort, SOCK_STREAM, &serverDownload); + + if (DSock == INVALID_SOCKET) { + UlStatus = "UlConnection Failed!"; + neterror("Client: Download socket creation failed."); Terminate = true; - return 0; + return INVALID_SOCKET; } - 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) { + // Try to connect to the distant server, using the socket created + if (connect(DSock, (struct sockaddr*)&serverDownload, sizeof(sockaddr_storage))) { + UlStatus = "UlConnection Failed!"; + neterror("Client: Connection to download mods failed!."); KillSocket(DSock); Terminate = true; - return 0; + return INVALID_SOCKET; } + char Code[2] = { 'D', char(ClientID) }; if (send(DSock, Code, 2, 0) != 2) { KillSocket(DSock); @@ -239,7 +251,7 @@ void InvalidResource(const std::string& File) { Terminate = true; } -void SyncResources(SOCKET Sock) { +void SyncResources(uint64_t Sock) { std::string Ret = Auth(Sock); if (Ret.empty()) return; diff --git a/src/Network/VehicleData.cpp b/src/Network/VehicleData.cpp index 23314646..a483b6ef 100644 --- a/src/Network/VehicleData.cpp +++ b/src/Network/VehicleData.cpp @@ -24,7 +24,7 @@ #include SOCKET UDPSock = -1; -sockaddr_in* ToServer = nullptr; +sockaddr_storage ToServer {}; void UDPSend(std::string Data) { if (ClientID == -1 || UDPSock == -1) @@ -34,7 +34,7 @@ void UDPSend(std::string Data) { Data = "ABG:" + std::string(res.data(), res.size()); } std::string Packet = char(ClientID + 1) + std::string(":") + Data; - int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)ToServer, sizeof(*ToServer)); + int sendOk = sendto(UDPSock, Packet.c_str(), int(Packet.size()), 0, (sockaddr*)&ToServer, sizeof(sockaddr_storage)); if (sendOk == SOCKET_ERROR) error("Error Code : " + std::to_string(WSAGetLastError())); } @@ -57,43 +57,39 @@ void UDPParser(std::string_view Packet) { ServerParser(Packet); } } + void UDPRcv() { - sockaddr_in FromServer {}; -#if defined(_WIN32) - int clientLength = sizeof(FromServer); -#elif defined(__linux__) - socklen_t clientLength = sizeof(FromServer); -#endif - ZeroMemory(&FromServer, clientLength); + sockaddr_storage FromServer {}; + socklen_t addrLen = sizeof(FromServer); static thread_local std::array Ret {}; if (UDPSock == -1) return; - int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &clientLength); + int32_t Rcv = recvfrom(UDPSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromServer, &addrLen); if (Rcv == SOCKET_ERROR) return; Ret[Rcv] = 0; UDPParser(std::string_view(Ret.data(), Rcv)); } -void UDPClientMain(const std::string& IP, int Port) { -#ifdef _WIN32 - WSADATA data; - if (WSAStartup(514, &data)) { - error("Can't start Winsock!"); + +void UDPClientMain(const std::string& IP, int Port) +{ + + UDPSock = initSocket(IP, Port, SOCK_DGRAM, &ToServer); + + if (UDPSock == INVALID_SOCKET) { + UlStatus = "UlConnection Failed!"; + neterror("Client: Failed to create UDP socket."); + Terminate = true; return; } -#endif - delete ToServer; - ToServer = new sockaddr_in; - ToServer->sin_family = AF_INET; - ToServer->sin_port = htons(Port); - inet_pton(AF_INET, IP.c_str(), &ToServer->sin_addr); - UDPSock = socket(AF_INET, SOCK_DGRAM, 0); + //Send to the game client GameSend("P" + std::to_string(ClientID)); TCPSend("H", TCPSock); UDPSend("p"); + //Main loop while (!Terminate) UDPRcv(); + KillSocket(UDPSock); - WSACleanup(); } diff --git a/src/Network/VehicleEvent.cpp b/src/Network/VehicleEvent.cpp index c02a5726..8db75b13 100644 --- a/src/Network/VehicleEvent.cpp +++ b/src/Network/VehicleEvent.cpp @@ -12,17 +12,6 @@ #include #include -#if defined(_WIN32) -#include -#elif defined(__linux__) -#include -#include -#include -#include -#include -#include -#endif - #include "Network/network.hpp" int LastPort; @@ -42,6 +31,7 @@ bool CheckBytes(int32_t Bytes) { } return true; } + void UUl(const std::string& R) { UlStatus = "UlDisconnected: " + R; } @@ -128,29 +118,23 @@ std::string TCPRcv(SOCKET Sock) { void TCPClientMain(const std::string& IP, int Port) { LastIP = IP; LastPort = Port; - SOCKADDR_IN ServerAddr; - int RetCode; -#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(); + sockaddr_storage server {}; + + TCPSock = initSocket(IP, Port, SOCK_STREAM, &server); + + if (TCPSock == INVALID_SOCKET) { + UlStatus = "UlConnection Failed!"; + Terminate = true; 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) { + //Try to connect to the distant server, using the socket created + if (connect(TCPSock, (struct sockaddr*)&server, sizeof(sockaddr_storage))) + { UlStatus = "UlConnection Failed!"; - error("Client: connect failed! Error code: " + std::to_string(WSAGetLastError())); + neterror("Client: connect to server failed!"); KillSocket(TCPSock); - WSACleanup(); Terminate = true; return; } @@ -165,10 +149,42 @@ void TCPClientMain(const std::string& IP, int Port) { GameSend("T"); ////Game Send Terminate if (KillSocket(TCPSock) != 0) - debug("(TCP) Cannot close socket. Error code: " + std::to_string(WSAGetLastError())); + neterror("(TCP) Cannot close socket."); +} -#ifdef _WIN32 - if (WSACleanup() != 0) - debug("(TCP) Client: WSACleanup() failed!..."); -#endif +SOCKET initSocket(std::string ip, int port, int sockType, sockaddr_storage* storeAddrInfo) { + int AF = (ip.find(':') != std::string::npos) ? AF_INET6 : AF_INET; + + // Create the socket + SOCKET sock = socket(AF, sockType, (sockType == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP); + + if (sock == -1) { + neterror("Client: socket creation failed!"); + return INVALID_SOCKET; + } + + if (AF == AF_INET) { + // IPv4 + struct sockaddr_in* saddr = (sockaddr_in*) storeAddrInfo; + saddr->sin_family = AF_INET; + saddr->sin_port = htons(port); + if (inet_pton(AF_INET, ip.c_str(), &(saddr->sin_addr)) != 1) { + neterror("Client: inet_pton failed!"); + KillSocket(sock); + return INVALID_SOCKET; + } + + } else { + // IPv6 + struct sockaddr_in6 *saddr = (sockaddr_in6*) storeAddrInfo; + saddr->sin6_family = AF_INET6; + saddr->sin6_port = htons(port); + if(inet_pton(AF_INET6, ip.c_str(), &(saddr->sin6_addr)) != 1) { + neterror("Client: inet_pton failed!"); + KillSocket(sock); + return INVALID_SOCKET; + } + } + + return sock; } diff --git a/src/Startup.cpp b/src/Startup.cpp index 7289d88e..835c2dc9 100644 --- a/src/Startup.cpp +++ b/src/Startup.cpp @@ -27,7 +27,7 @@ #include extern int TraceBack; -bool Dev = false; +bool Dev = true; int ProxyPort = 0; namespace fs = std::filesystem; @@ -81,10 +81,10 @@ std::string GetEN() { } std::string GetVer() { - return "2.0"; + return "2.1"; } std::string GetPatch() { - return ".99"; + return ".0"; } std::string GetEP(char* P) { diff --git a/vcpkg b/vcpkg new file mode 160000 index 00000000..3f142d2f --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit 3f142d2fc6f6cf10360e89248405057fe771a9ed