diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index e7e9d11..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Default ignored files -/workspace.xml diff --git a/.idea/.name b/.idea/.name index f083ce8..53e4c14 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -Server \ No newline at end of file +TUDPServer.cpp \ No newline at end of file diff --git a/.idea/BeamNG-MP-Server.iml b/.idea/BeamNG-MP-Server.iml deleted file mode 100644 index f08604b..0000000 --- a/.idea/BeamNG-MP-Server.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 8822db8..79b3c94 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,4 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index dd314cc..e87924b 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,9 @@ + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f5a2e70..7115ef9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ add_executable(BeamMP-Server include/THeartbeatThread.h src/THeartbeatThread.cpp include/Http.h src/Http.cpp include/SocketIO.h src/SocketIO.cpp - include/TPPSMonitor.h src/TPPSMonitor.cpp) + include/TPPSMonitor.h src/TPPSMonitor.cpp include/TUDPServer.h src/TUDPServer.cpp include/TTCPServer.h src/TTCPServer.cpp) target_include_directories(BeamMP-Server PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/commandline") diff --git a/include/Client.h b/include/Client.h index c9863de..23332ec 100644 --- a/include/Client.h +++ b/include/Client.h @@ -8,46 +8,52 @@ #include "Compat.h" #include "VehicleData.h" +class TServer; + class TClient final { public: using TSetOfVehicleData = std::unordered_set>; + explicit TClient(TServer& Server); + void AddNewCar(int Ident, const std::string& Data); void SetCarData(int Ident, const std::string& Data); TSetOfVehicleData& GetAllCars(); - void SetName(const std::string& Name) { _Name = Name; } - void SetRoles(const std::string& Role) { _Role = Role; } + void SetName(const std::string& Name) { mName = Name; } + void SetRoles(const std::string& Role) { mRole = Role; } std::string GetCarData(int Ident); - void SetUDPAddr(sockaddr_in Addr) { _UDPAddress = Addr; } - void SetDownSock(SOCKET CSock) { _Socket[1] = CSock; } - void SetTCPSock(SOCKET CSock) { _Socket[0] = CSock; } - void SetStatus(int Status) { _Status = Status; } + void SetUDPAddr(sockaddr_in Addr) { mUDPAddress = Addr; } + void SetDownSock(SOCKET CSock) { mSocket[1] = CSock; } + void SetTCPSock(SOCKET CSock) { mSocket[0] = CSock; } + void SetStatus(int Status) { mStatus = Status; } void DeleteCar(int Ident); - sockaddr_in GetUDPAddr() { return _UDPAddress; } - std::string GetRoles() { return _Role; } - std::string GetName() { return _Name; } - SOCKET GetDownSock() { return _Socket[1]; } - SOCKET GetTCPSock() { return _Socket[0]; } - void SetID(int ID) { _ID = ID; } - int GetOpenCarID(); - int GetCarCount(); + sockaddr_in GetUDPAddr() const { return mUDPAddress; } + std::string GetRoles() const { return mRole; } + std::string GetName() const { return mName; } + SOCKET GetDownSock() const { return mSocket[1]; } + SOCKET GetTCPSock() const { return mSocket[0]; } + void SetID(int ID) { mID = ID; } + int GetOpenCarID() const; + int GetCarCount() const; void ClearCars(); - int GetStatus() { return _Status; } - int GetID() { return _ID; } - bool IsConnected() const { return _IsConnected; } - bool IsSynced() const { return _IsSynced; } - bool IsGuest() const { return _IsGuest; } + int GetStatus() const { return mStatus; } + int GetID() const { return mID; } + bool IsConnected() const { return mIsConnected; } + bool IsSynced() const { return mIsSynced; } + bool IsGuest() const { return mIsGuest; } + TServer& Server() const; private: - bool _IsConnected = false; - bool _IsSynced = false; - bool _IsGuest = false; - TSetOfVehicleData _VehicleData; - std::string _Name = "Unknown Client"; - SOCKET _Socket[2] { SOCKET(-1) }; - sockaddr_in _UDPAddress; - std::string _Role; - std::string _DID; - int _Status = 0; - int _ID = -1; + TServer& mServer; + bool mIsConnected = false; + bool mIsSynced = false; + bool mIsGuest = false; + TSetOfVehicleData mVehicleData; + std::string mName = "Unknown Client"; + SOCKET mSocket[2] { SOCKET(-1) }; + sockaddr_in mUDPAddress {}; // is this initialization OK? + std::string mRole; + std::string mDID; + int mStatus = 0; + int mID = -1; }; diff --git a/include/Common.h b/include/Common.h index 969ece2..1e9f5a9 100644 --- a/include/Common.h +++ b/include/Common.h @@ -76,3 +76,8 @@ static inline void debug(const std::string& str) { static inline void luaprint(const std::string& str) { Application::Console().Write(std::string("[LUA] ") + str); } + +#define Biggest 30000 +std::string Comp(std::string Data); +std::string DeComp(std::string Compressed); + diff --git a/include/Compat.h b/include/Compat.h index f56cc8e..9fd3a1e 100644 --- a/include/Compat.h +++ b/include/Compat.h @@ -4,13 +4,18 @@ #ifdef __unix #include +#include #include #include using SOCKET = int; using DWORD = unsigned long; using PDWORD = unsigned long*; using LPDWORD = unsigned long*; -char _getch(void); +char _getch(); +inline void CloseSocketProper(int socket) { + shutdown(socket, SHUT_RDWR); + close(socket); +} #endif // unix // ======================= WIN32 ======================= @@ -18,6 +23,10 @@ char _getch(void); #ifdef WIN32 #include #include +inline void CloseSocketProper(int socket) { + shutdown(socket, SHUT_RDWR); + close(socket); +} #endif // WIN32 // ======================= OTHER ======================= @@ -25,4 +34,3 @@ char _getch(void); #if !defined(WIN32) && !defined(__unix) #error "OS not supported" #endif - diff --git a/include/TPPSMonitor.h b/include/TPPSMonitor.h index 6f5e8f2..5ce0406 100644 --- a/include/TPPSMonitor.h +++ b/include/TPPSMonitor.h @@ -10,8 +10,11 @@ public: void operator()() override; -private: + void SetInternalPPS(int NewPPS) { mInternalPPS = NewPPS; } + void IncrementInternalPPS() { ++mInternalPPS; } + [[nodiscard]] int InternalPPS() const { return mInternalPPS; } +private: TServer& mServer; bool mShutdown { false }; int mInternalPPS { 0 }; diff --git a/include/TTCPServer.h b/include/TTCPServer.h new file mode 100644 index 0000000..946741e --- /dev/null +++ b/include/TTCPServer.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Common.h" +#include "Compat.h" +#include "IThreaded.h" +#include "TServer.h" + +class TTCPServer : public IThreaded { +public: + explicit TTCPServer(TServer& Server); + +private: + TServer& mServer; +}; \ No newline at end of file diff --git a/include/TUDPServer.h b/include/TUDPServer.h new file mode 100644 index 0000000..481b553 --- /dev/null +++ b/include/TUDPServer.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Client.h" +#include "Common.h" +#include "Compat.h" +#include "IThreaded.h" +#include "TPPSMonitor.h" +#include "TServer.h" + +class TUDPServer : public IThreaded { +public: + explicit TUDPServer(TServer& Server, TPPSMonitor& PPSMonitor); + + void operator()() override; + + void UDPSend(TClient& Client, std::string Data) const; + void SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel); + +private: + void UDPParser(TClient& Client, std::string Packet); + + TServer& mServer; + TPPSMonitor& mPPSMonitor; + SOCKET mUDPSock; + std::string UDPRcvFromClient(sockaddr_in& client) const; +}; \ No newline at end of file diff --git a/src/Client.cpp b/src/Client.cpp index 327617f..b730131 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1,28 +1,29 @@ #include "Client.h" +#include "CustomAssert.h" #include // FIXME: add debug prints void TClient::DeleteCar(int Ident) { - for (auto& v : _VehicleData) { + for (auto& v : mVehicleData) { if (v != nullptr && v->ID() == Ident) { - _VehicleData.erase(v); + mVehicleData.erase(v); break; } } } void TClient::ClearCars() { - _VehicleData.clear(); + mVehicleData.clear(); } -int TClient::GetOpenCarID() { +int TClient::GetOpenCarID() const { int OpenID = 0; bool found; do { found = true; - for (auto& v : _VehicleData) { + for (auto& v : mVehicleData) { if (v != nullptr && v->ID() == OpenID) { OpenID++; found = false; @@ -33,15 +34,15 @@ int TClient::GetOpenCarID() { } void TClient::AddNewCar(int Ident, const std::string& Data) { - _VehicleData.insert(std::make_unique(TVehicleData { Ident, Data })); + mVehicleData.insert(std::make_unique(TVehicleData { Ident, Data })); } TClient::TSetOfVehicleData& TClient::GetAllCars() { - return _VehicleData; + return mVehicleData; } std::string TClient::GetCarData(int Ident) { - for (auto& v : _VehicleData) { + for (auto& v : mVehicleData) { if (v != nullptr && v->ID() == Ident) { return v->Data(); } @@ -51,7 +52,7 @@ std::string TClient::GetCarData(int Ident) { } void TClient::SetCarData(int Ident, const std::string& Data) { - for (auto& v : _VehicleData) { + for (auto& v : mVehicleData) { if (v != nullptr && v->ID() == Ident) { v->Data() = Data; return; @@ -59,6 +60,13 @@ void TClient::SetCarData(int Ident, const std::string& Data) { } DeleteCar(Ident); } -int TClient::GetCarCount() { - return int(_VehicleData.size()); +int TClient::GetCarCount() const { + return int(mVehicleData.size()); +} +TServer& TClient::Server() const { + return mServer; +} + +TClient::TClient(TServer& Server) + : mServer(Server) { } diff --git a/src/Common.cpp b/src/Common.cpp index e8a57fb..81bf7b8 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -1,5 +1,10 @@ #include "Common.h" + #include "TConsole.h" +#include +#include +#include +#include std::unique_ptr Application::mConsole = std::make_unique(); @@ -16,3 +21,46 @@ void Application::GracefullyShutdown() { Handler(); } } +std::string Comp(std::string Data) { + std::array C {}; + // obsolete + C.fill(0); + z_stream defstream; + defstream.zalloc = Z_NULL; + defstream.zfree = Z_NULL; + defstream.opaque = Z_NULL; + defstream.avail_in = (uInt)Data.length(); + defstream.next_in = (Bytef*)&Data[0]; + defstream.avail_out = Biggest; + defstream.next_out = reinterpret_cast(C.data()); + deflateInit(&defstream, Z_BEST_COMPRESSION); + deflate(&defstream, Z_SYNC_FLUSH); + deflate(&defstream, Z_FINISH); + deflateEnd(&defstream); + size_t TO = defstream.total_out; + std::string Ret(TO, 0); + std::copy_n(C.begin(), TO, Ret.begin()); + return Ret; +} + +std::string DeComp(std::string Compressed) { + std::array C {}; + // not needed + C.fill(0); + z_stream infstream; + infstream.zalloc = Z_NULL; + infstream.zfree = Z_NULL; + infstream.opaque = Z_NULL; + infstream.avail_in = Biggest; + infstream.next_in = (Bytef*)(&Compressed[0]); + infstream.avail_out = Biggest; + infstream.next_out = (Bytef*)(C.data()); + inflateInit(&infstream); + inflate(&infstream, Z_SYNC_FLUSH); + inflate(&infstream, Z_FINISH); + inflateEnd(&infstream); + size_t TO = infstream.total_out; + std::string Ret(TO, 0); + std::copy_n(C.begin(), TO, Ret.begin()); + return Ret; +} diff --git a/src/TServer.cpp b/src/TServer.cpp index 175c964..e6ba42c 100644 --- a/src/TServer.cpp +++ b/src/TServer.cpp @@ -30,7 +30,7 @@ void TServer::RemoveClient(std::weak_ptr WeakClientPtr) { std::weak_ptr TServer::InsertNewClient() { debug("inserting new client (" + std::to_string(ClientCount()) + ")"); WriteLock Lock(mClientsMutex); - auto [Iter, Replaced] = mClients.insert(std::make_shared()); + auto [Iter, Replaced] = mClients.insert(std::make_shared(*this)); return *Iter; } diff --git a/src/TTCPServer.cpp b/src/TTCPServer.cpp new file mode 100644 index 0000000..de3b281 --- /dev/null +++ b/src/TTCPServer.cpp @@ -0,0 +1,5 @@ +#include "TTCPServer.h" + +TTCPServer::TTCPServer(TServer& Server) + : mServer(Server) { +} diff --git a/src/TUDPServer.cpp b/src/TUDPServer.cpp new file mode 100644 index 0000000..a99bdc3 --- /dev/null +++ b/src/TUDPServer.cpp @@ -0,0 +1,224 @@ +#include "TUDPServer.h" +#include "CustomAssert.h" +#include +#include + +TUDPServer::TUDPServer(TServer& Server, TPPSMonitor& PPSMonitor) + : mServer(Server) + , mPPSMonitor(PPSMonitor) { +} + +void TUDPServer::operator()() { +#ifdef WIN32 + WSADATA data; + if (WSAStartup(514, &data)) { + error(("Can't start Winsock!")); + //return; + } + + mUDPSock = socket(AF_INET, SOCK_DGRAM, 0); + // Create a server hint structure for the server + sockaddr_in serverAddr {}; + serverAddr.sin_addr.S_un.S_addr = ADDR_ANY; //Any Local + serverAddr.sin_family = AF_INET; // Address format is IPv4 + serverAddr.sin_port = htons(Application::Settings.Port); // Convert from little to big endian + + // Try and bind the socket to the IP and port + if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { + error(("Can't bind socket!") + std::to_string(WSAGetLastError())); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(-1); + //return; + } +#else // unix + mUDPSock = socket(AF_INET, SOCK_DGRAM, 0); + // Create a server hint structure for the server + sockaddr_in serverAddr {}; + serverAddr.sin_addr.s_addr = INADDR_ANY; //Any Local + serverAddr.sin_family = AF_INET; // Address format is IPv4 + serverAddr.sin_port = htons(uint16_t(Application::Settings.Port)); // Convert from little to big endian + + // Try and bind the socket to the IP and port + if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { + error(("Can't bind socket!") + std::string(strerror(errno))); + std::this_thread::sleep_for(std::chrono::seconds(5)); + exit(-1); + //return; + } +#endif + + info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ") + + std::to_string(Application::Settings.MaxPlayers) + (" Clients")); + while (true) { + try { + sockaddr_in client {}; + std::string Data = UDPRcvFromClient(client); //Receives any data from Socket + size_t Pos = Data.find(':'); + if (Data.empty() || Pos > 2) + continue; + /*char clientIp[256]; + ZeroMemory(clientIp, 256); ///Code to get IP we don't need that yet + inet_ntop(AF_INET, &client.sin_addr, clientIp, 256);*/ + uint8_t ID = uint8_t(Data.at(0)) - 1; + mServer.ForEachClient([&](std::weak_ptr ClientPtr) -> bool { + if (!ClientPtr.expired()) { + auto Client = ClientPtr.lock(); + if (Client->GetID() == ID) { + Client->SetUDPAddr(client); + Client->SetConnected(true); + UDPParser(*Client, Data.substr(2)); + } + } + return true; + }); + } catch (const std::exception& e) { + error(("fatal: ") + std::string(e.what())); + } + } +} +void TUDPServer::UDPParser(TClient& Client, std::string Packet) { + if (Packet.find("Zp") != std::string::npos && Packet.size() > 500) { + abort(); + } + if (Packet.substr(0, 4) == "ABG:") { + Packet = DeComp(Packet.substr(4)); + } + if (Packet.empty()) { + return; + } + std::any Res; + char Code = Packet.at(0); + + //V to Z + if (Code <= 90 && Code >= 86) { + mPPSMonitor.IncrementInternalPPS(); + SendToAll(&Client, Packet, false, false); + return; + } + switch (Code) { + case 'H': // initial connection +#ifdef DEBUG + debug(std::string("got 'H' packet: '") + Packet + "' (" + std::to_string(Packet.size()) + ")"); +#endif + SyncClient(Client); + return; + case 'p': + Respond(Client, ("p"), false); + UpdatePlayers(); + return; + case 'O': + if (Packet.length() > 1000) { + debug(("Received data from: ") + Client->GetName() + (" Size: ") + std::to_string(Packet.length())); + } + ParseVeh(Client, Packet); + return; + case 'J': +#ifdef DEBUG + debug(std::string(("got 'J' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); +#endif + SendToAll(Client, Packet, false, true); + return; + case 'C': +#ifdef DEBUG + debug(std::string(("got 'C' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); +#endif + if (Packet.length() < 4 || Packet.find(':', 3) == std::string::npos) + break; + Res = TriggerLuaEvent("onChatMessage", false, nullptr, std::make_unique(LuaArg { { Client->GetID(), Client->GetName(), Packet.substr(Packet.find(':', 3) + 1) } }), true); + if (std::any_cast(Res)) + break; + SendToAll(nullptr, Packet, true, true); + return; + case 'E': +#ifdef DEBUG + debug(std::string(("got 'E' packet: '")) + Packet + ("' (") + std::to_string(Packet.size()) + (")")); +#endif + HandleEvent(Client, Packet); + return; + default: + return; + } +} + +void TUDPServer::SendToAll(TClient* c, const std::string& Data, bool Self, bool Rel) { + if (!Self) + Assert(c); + char C = Data.at(0); + mServer.ForEachClient([&](std::weak_ptr ClientPtr) -> bool { + if (!ClientPtr.expired()) { + auto Client = ClientPtr.lock(); + if (Self || Client.get() != c) { + if (Client->IsSynced()) { + if (Rel || C == 'W' || C == 'Y' || C == 'V' || C == 'E') { + if (C == 'O' || C == 'T' || Data.length() > 1000) + SendLarge(*Client, Data); + else + TCPSend(*Client, Data); + } else + UDPSend(*Client, Data); + } + } + } + return true; + }); +} + +void TUDPServer::UDPSend(TClient& Client, std::string Data) const { + if (!Client.IsConnected() || Client.GetStatus() < 0) { +#ifdef DEBUG + debug(Client.GetName() + ": !IsConnected() or GetStatus() < 0"); +#endif // DEBUG + return; + } + sockaddr_in Addr = Client.GetUDPAddr(); + socklen_t AddrSize = sizeof(Client.GetUDPAddr()); + if (Data.length() > 400) { + std::string CMP(Comp(Data)); + Data = "ABG:" + CMP; + } +#ifdef WIN32 + int sendOk; + int len = static_cast(Data.size()); +#else + int64_t sendOk; + size_t len = Data.size(); +#endif // WIN32 + + sendOk = sendto(mUDPSock, Data.c_str(), len, 0, (sockaddr*)&Addr, AddrSize); +#ifdef WIN32 + if (sendOk == -1) { + debug(("(UDP) Send Failed Code : ") + std::to_string(WSAGetLastError())); + if (Client.GetStatus() > -1) + Client.SetStatus(-1); + } else if (sendOk == 0) { + debug(("(UDP) sendto returned 0")); + if (Client.GetStatus() > -1) + Client.SetStatus(-1); + } +#else // unix + if (sendOk == -1) { + debug(("(UDP) Send Failed Code : ") + std::string(strerror(errno))); + if (Client.GetStatus() > -1) + Client.SetStatus(-1); + } else if (sendOk == 0) { + debug(("(UDP) sendto returned 0")); + if (Client.GetStatus() > -1) + Client.SetStatus(-1); + } +#endif // WIN32 +} + +std::string TUDPServer::UDPRcvFromClient(sockaddr_in& client) const { + size_t clientLength = sizeof(client); + std::array Ret {}; + int64_t Rcv = recvfrom(mUDPSock, Ret.data(), Ret.size(), 0, (sockaddr*)&client, (socklen_t*)&clientLength); + if (Rcv == -1) { +#ifdef WIN32 + error(("(UDP) Error receiving from Client! Code : ") + std::to_string(WSAGetLastError())); +#else // unix + error(("(UDP) Error receiving from Client! Code : ") + std::string(strerror(errno))); +#endif // WIN32 + return ""; + } + return std::string(Ret.begin(), Ret.begin() + Rcv); +} diff --git a/src/main.cpp b/src/main.cpp index a4d45a6..e64b4a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "TResourceManager.h" #include "TServer.h" #include "TPPSMonitor.h" +#include "TUDPServer.h" #include #include #include @@ -46,10 +47,12 @@ int main(int argc, char** argv) { TServer Server(argc, argv); [[maybe_unused]] TConfig Config("Server.cfg"); - TLuaEngine LuaEngine(Server); TResourceManager ResourceManager; [[maybe_unused]] TPPSMonitor PPSMonitor(Server); THeartbeatThread Heartbeat(ResourceManager, Server); + TTCPServer TCPServer(Server); + TUDPServer UDPServer(Server, PPSMonitor); + TLuaEngine LuaEngine(Server); // TODO: replace bool Shutdown = false;