diff --git a/include/Network/network.hpp b/include/Network/network.hpp index f58bdad4..8b6ff06c 100644 --- a/include/Network/network.hpp +++ b/include/Network/network.hpp @@ -8,6 +8,8 @@ #pragma once #include #include +#include +#include #ifdef __linux__ #include "linuxfixes.h" @@ -56,3 +58,9 @@ void UDPClientMain(const std::string& IP, int Port); void TCPGameServer(const std::string& IP, int Port); bool SecurityWarning(); void CoreSend(std::string data); +void ServerSend(std::string Data, bool Rel); +extern uint64_t DVSock; +extern std::unordered_set activeVehicles; // set of active serverVehicleIDs for checking if direct vehicle sockets are valid +extern std::unordered_map vehiclePortMap; // maps a serverVehicleID to the port of its vehicle socket +void DVSend(std::string_view Data, int Port); +void DVClientMain(const std::string& IP, int Port); diff --git a/src/Network/Core.cpp b/src/Network/Core.cpp index bcb80509..6b2078df 100644 --- a/src/Network/Core.cpp +++ b/src/Network/Core.cpp @@ -346,6 +346,27 @@ void Parse(std::string Data, SOCKET CSocket) { }); break; } + case 'V': //register active vehicles for direct vehicle sockets + if (Data.length() < 3) { + debug("(Core) Failed to parse serverVehicleID from data: " + Data); + Data.clear(); + break; + } + + if (SubCode == 'a') { + std::string serverVehicleID = Data.substr(3); + debug("(Core) Registering vehicle: " + serverVehicleID); + activeVehicles.insert(serverVehicleID); + } else if (SubCode == 'd') { + std::string serverVehicleID = Data.substr(3); + debug("(Core) Unregistering vehicle: " + serverVehicleID); + activeVehicles.erase(serverVehicleID); + vehiclePortMap.erase(serverVehicleID); + } else { + debug("(Core) Invalid V packet SubCode: " + SubCode); + } + Data.clear(); + break; default: Data.clear(); break; diff --git a/src/Network/DirectVehicleConnection.cpp b/src/Network/DirectVehicleConnection.cpp new file mode 100644 index 00000000..78a3940f --- /dev/null +++ b/src/Network/DirectVehicleConnection.cpp @@ -0,0 +1,125 @@ +/* + Copyright (C) 2026 BeamMP Ltd., BeamMP team and contributors. + Licensed under AGPL-3.0 (or later), see . + SPDX-License-Identifier: AGPL-3.0-or-later +*/ + +#include "Network/network.hpp" +#include + +#if defined(_WIN32) +#include +#elif defined(__linux__) +#include "linuxfixes.h" +#include +#include +#include +#include +#include +#endif + +#include "Logger.h" +#include +#include + +SOCKET DVSock = -1; +sockaddr_in ToVehicle; +std::unordered_set activeVehicles; +std::unordered_map vehiclePortMap; + +void DVSend(std::string_view Data, int Port) { + if (DVSock == -1) + return; + ToVehicle.sin_port = htons(Port); + int sendOk = sendto(DVSock, Data.data(), int(Data.size()), 0, (sockaddr*)&ToVehicle, sizeof(ToVehicle)); + if (sendOk == SOCKET_ERROR) + error("(Direct VE) Failed to send data. Error Code : " + std::to_string(WSAGetLastError())); +} + +void DVRcv() { + sockaddr_in FromVehicle; + socklen_t size = sizeof(FromVehicle); + ZeroMemory(&FromVehicle, size); + static thread_local std::array Ret {}; + if (DVSock == -1) + return; + int32_t Rcv = recvfrom(DVSock, Ret.data(), Ret.size() - 1, 0, (sockaddr*)&FromVehicle, &size); + if (Rcv == SOCKET_ERROR) + return; + Ret[Rcv] = 0; + + std::string Data = std::string(Ret.data(), Rcv); + size_t first = Data.find(':'); + if (first == std::string::npos) { + debug("(Direct VE) Failed to parse serverVehicleID from data: " + Data); + return; + } + first += 1; + size_t len = Data.find(':', first); + if (len != std::string::npos) { + len -= first; + } + std::string serverVehicleID = Data.substr(first, len); + if (activeVehicles.contains(serverVehicleID)) { + int port = ntohs(FromVehicle.sin_port); + auto portIter = vehiclePortMap.find(serverVehicleID); + if (portIter != vehiclePortMap.end()) { + if (portIter->second == port) { + ServerSend(Data, false); + } else { + debug("(Direct VE) Received data for vehicle " + serverVehicleID + " from wrong port: " + std::to_string(port) + " != " + std::to_string(portIter->second)); + } + } else { + debug("(Direct VE) Registering port for vehicle " + serverVehicleID + ": " + std::to_string(port)); + vehiclePortMap.insert({ serverVehicleID, port }); + + ServerSend(Data, false); + } + } else { + debug("(Direct VE) Received data from unregistered vehicle: " + serverVehicleID); + } +} + +void DVClientMain(const std::string& IP, int Port) { + debug("(Direct VE) Starting direct vehicle client on adress " + IP + ":" + std::to_string(Port)); + +#ifdef _WIN32 + WSADATA data; + if (WSAStartup(514, &data)) { + error("(Direct VE) Can't start Winsock!"); + return; + } +#endif + sockaddr_in DVListenAddr; + ZeroMemory(&DVListenAddr, sizeof(DVListenAddr)); + DVListenAddr.sin_family = AF_INET; + DVListenAddr.sin_port = htons(Port); + inet_pton(AF_INET, IP.c_str(), &DVListenAddr.sin_addr); + + ZeroMemory(&ToVehicle, sizeof(ToVehicle)); + ToVehicle.sin_family = AF_INET; + ToVehicle.sin_addr = DVListenAddr.sin_addr; + + DVSock = socket(AF_INET, SOCK_DGRAM, 0); + if (DVSock == INVALID_SOCKET) { + error("(Direct VE) Socket creation failed with error: " + std::to_string(WSAGetLastError())); + KillSocket(DVSock); + WSACleanup(); + return; + } + if (bind(DVSock, (const sockaddr*)&DVListenAddr, sizeof(DVListenAddr)) == SOCKET_ERROR) { + error("(Direct VE) Socket bind failed with error: " + std::to_string(WSAGetLastError())); + KillSocket(DVSock); + WSACleanup(); + return; + } + debug("(Direct VE) Starting direct vehicle receive loop"); + while (!TCPTerminate) { + DVRcv(); + } + debug("(Direct VE) Direct vehicle receive loop done"); + KillSocket(DVSock); + WSACleanup(); + activeVehicles.clear(); + vehiclePortMap.clear(); +} diff --git a/src/Network/GlobalHandler.cpp b/src/Network/GlobalHandler.cpp index da4b6f28..0a1b6762 100644 --- a/src/Network/GlobalHandler.cpp +++ b/src/Network/GlobalHandler.cpp @@ -126,6 +126,11 @@ void NetReset() { KillSocket(GSocket); } GSocket = -1; + if (DVSock != (SOCKET)(-1)) { + debug("Terminating direct vehicle Socket: " + std::to_string(DVSock)); + KillSocket(DVSock); + } + DVSock = -1; } SOCKET SetupListener() { @@ -189,6 +194,7 @@ int ClientID = -1; void ParserAsync(std::string_view Data) { if (Data.empty()) return; + bool tryDirectVehicleSocket = false; char Code = Data.at(0), SubCode = 0; if (Data.length() > 1) SubCode = Data.at(1); @@ -206,10 +212,41 @@ void ParserAsync(std::string_view Data) { return; case 'U': magic = Data.substr(1); + break; + case 'R': //controller sync + case 'W': //electrics + case 'V': //inputs + case 'Y': //powertrain + case 'X': //nodes + case 'Z': //position + tryDirectVehicleSocket = true; + break; default: break; } - GameSend(Data); + if (tryDirectVehicleSocket) { + size_t first = Data.find(':'); + if (first == std::string::npos) { + GameSend(Data); + return; + } + first += 1; + size_t len = Data.find(':', first); + if (len != std::string::npos) { + len -= first; + } + std::string serverVehicleID = std::string(Data.substr(first, len)); + auto portIter = vehiclePortMap.find(serverVehicleID); + if (portIter != vehiclePortMap.end()) { + DVSend(Data, portIter->second); + } + else { + GameSend(Data); + } + } + else { + GameSend(Data); + } } void ServerParser(std::string_view Data) { ParserAsync(Data); @@ -226,6 +263,7 @@ void TCPGameServer(const std::string& IP, int Port) { GSocket = SetupListener(); std::unique_ptr ClientThread {}; std::unique_ptr NetMainThread {}; + std::unique_ptr DirectVehicleThread {}; while (!TCPTerminate && GSocket != -1) { debug("MAIN LOOP OF GAME SERVER"); GConnected = false; @@ -248,6 +286,7 @@ void TCPGameServer(const std::string& IP, int Port) { GConnected = true; if (CServer) { NetMainThread = std::make_unique(NetMain, IP, Port); + DirectVehicleThread = std::make_unique(DVClientMain, "127.0.0.1", options.port + 2); CServer = false; } int32_t Size, Rcv; @@ -280,6 +319,11 @@ void TCPGameServer(const std::string& IP, int Port) { NetMainThread->join(); debug("Net main thread done"); } + if (DirectVehicleThread) { + debug("Waiting for direct vehicle thread"); + DirectVehicleThread->join(); + debug("Direct vehicle thread done"); + } if (CSocket != SOCKET_ERROR) KillSocket(CSocket); debug("END OF GAME SERVER");