From 459814a6ec3cc2f1c3ac68ba040e155f18be7692 Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Mon, 15 Feb 2021 02:35:15 +0100 Subject: [PATCH] add lua engine, lua file, server, client, vehicle data, other stuff --- CMakeLists.txt | 7 + include/Client.h | 53 +++ include/Common.h | 66 ++++ include/Compat.h | 27 +- include/CustomAssert.h | 68 ++++ include/RWMutex.h | 19 ++ include/TConfig.h | 12 + include/TConsole.h | 6 +- include/TLuaEngine.h | 32 ++ include/TLuaFile.h | 58 ++++ include/TServer.h | 26 ++ include/VehicleData.h | 18 + src/Client.cpp | 64 ++++ src/Common.cpp | 18 + src/TConfig.cpp | 126 +++++++ src/TConsole.cpp | 52 ++- src/TLuaEngine.cpp | 67 ++++ src/TLuaFile.cpp | 722 +++++++++++++++++++++++++++++++++++++++++ src/TServer.cpp | 49 +++ src/VehicleData.cpp | 6 + src/main.cpp | 57 +++- 21 files changed, 1538 insertions(+), 15 deletions(-) create mode 100644 include/Client.h create mode 100644 include/Common.h create mode 100644 include/CustomAssert.h create mode 100644 include/RWMutex.h create mode 100644 include/TConfig.h create mode 100644 include/TLuaEngine.h create mode 100644 include/TLuaFile.h create mode 100644 include/TServer.h create mode 100644 include/VehicleData.h create mode 100644 src/Client.cpp create mode 100644 src/Common.cpp create mode 100644 src/TConfig.cpp create mode 100644 src/TLuaEngine.cpp create mode 100644 src/TLuaFile.cpp create mode 100644 src/TServer.cpp create mode 100644 src/VehicleData.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d50fd..60ee713 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,14 @@ find_package(Boost REQUIRED COMPONENTS system thread) add_executable(BeamMP-Server src/main.cpp src/TConsole.cpp + src/TServer.cpp src/Compat.cpp + src/Common.cpp + src/Client.cpp + src/VehicleData.cpp + src/TConfig.cpp + src/TLuaEngine.cpp + src/TLuaFile.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 new file mode 100644 index 0000000..c9863de --- /dev/null +++ b/include/Client.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include "Common.h" +#include "Compat.h" +#include "VehicleData.h" + +class TClient final { +public: + using TSetOfVehicleData = std::unordered_set>; + + 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; } + 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 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(); + 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; } + +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; +}; diff --git a/include/Common.h b/include/Common.h new file mode 100644 index 0000000..7f14636 --- /dev/null +++ b/include/Common.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "TConsole.h" + +// static class handling application start, shutdown, etc. +// yes, static classes, singletons, globals are all pretty +// bad idioms. In this case we need a central way to access +// stuff like graceful shutdown, global settings (its in the name), +// etc. +class Application final { +public: + // types + struct TSettings { + TSettings() + : DebugModeEnabled(true) { } + std::string ServerName; + std::string ServerDesc; + std::string Resource; + std::string MapName; + std::string Key; + int MaxPlayers; + bool Private; + int MaxCars; + bool DebugModeEnabled; + int Port; + std::string CustomIP; + bool HasCustomIP() const { return !CustomIP.empty(); } + + // new settings + std::string ResourceFolder; + }; + using TShutdownHandler = std::function; + + // methods + Application() = delete; + + // 'Handler' is called when GracefullyShutdown is called + static void RegisterShutdownHandler(const TShutdownHandler& Handler); + // Causes all threads to finish up and exit gracefull gracefully + static void GracefullyShutdown(); + static TConsole& Console() { return *_Console; } + static std::string ServerVersion() { return "v1.20"; } + + static inline TSettings Settings {}; + +private: + static std::unique_ptr _Console; + static inline std::mutex _ShutdownHandlersMutex {}; + static inline std::vector _ShutdownHandlers {}; +}; + +#define warn(x) Application::Console().Write(std::string("[WARN] ") + (x)) +#define error(x) Application::Console().Write(std::string("[ERROR] ") + (x)) +#define info(x) Application::Console().Write(std::string("[INFO] ") + (x)) +#define debug(x) \ + do { \ + if (Application::Settings.DebugModeEnabled) { \ + Application::Console().Write(std::string("[DEBUG] ") + (x)); \ + } \ + } while (false) diff --git a/include/Compat.h b/include/Compat.h index 31fa8fa..f56cc8e 100644 --- a/include/Compat.h +++ b/include/Compat.h @@ -1,17 +1,28 @@ #pragma once -// Unix - Win32 compatibility stuff +// ======================= UNIX ======================== + +#ifdef __unix +#include +#include +#include +using SOCKET = int; +using DWORD = unsigned long; +using PDWORD = unsigned long*; +using LPDWORD = unsigned long*; +char _getch(void); +#endif // unix + +// ======================= WIN32 ======================= + #ifdef WIN32 #include #include -#else // *nix -typedef unsigned long DWORD, *PDWORD, *LPDWORD; -#include -#include #endif // WIN32 -#ifndef WIN32 +// ======================= OTHER ======================= -char _getch(void); +#if !defined(WIN32) && !defined(__unix) +#error "OS not supported" +#endif -#endif // !WIN32 diff --git a/include/CustomAssert.h b/include/CustomAssert.h new file mode 100644 index 0000000..7bf3622 --- /dev/null +++ b/include/CustomAssert.h @@ -0,0 +1,68 @@ +// Author: lionkor + +/* + * Asserts are to be used anywhere where assumptions about state are made + * implicitly. AssertNotReachable is used where code should never go, like in + * default switch cases which shouldn't trigger. They make it explicit + * that a place cannot normally be reached and make it an error if they do. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common.h" + +static const char* const ANSI_RESET = "\u001b[0m"; + +static const char* const ANSI_BLACK = "\u001b[30m"; +static const char* const ANSI_RED = "\u001b[31m"; +static const char* const ANSI_GREEN = "\u001b[32m"; +static const char* const ANSI_YELLOW = "\u001b[33m"; +static const char* const ANSI_BLUE = "\u001b[34m"; +static const char* const ANSI_MAGENTA = "\u001b[35m"; +static const char* const ANSI_CYAN = "\u001b[36m"; +static const char* const ANSI_WHITE = "\u001b[37m"; + +static const char* const ANSI_BLACK_BOLD = "\u001b[30;1m"; +static const char* const ANSI_RED_BOLD = "\u001b[31;1m"; +static const char* const ANSI_GREEN_BOLD = "\u001b[32;1m"; +static const char* const ANSI_YELLOW_BOLD = "\u001b[33;1m"; +static const char* const ANSI_BLUE_BOLD = "\u001b[34;1m"; +static const char* const ANSI_MAGENTA_BOLD = "\u001b[35;1m"; +static const char* const ANSI_CYAN_BOLD = "\u001b[36;1m"; +static const char* const ANSI_WHITE_BOLD = "\u001b[37;1m"; + +static const char* const ANSI_BOLD = "\u001b[1m"; +static const char* const ANSI_UNDERLINE = "\u001b[4m"; + +#if DEBUG +#include +inline void _assert([[maybe_unused]] const char* file, [[maybe_unused]] const char* function, [[maybe_unused]] unsigned line, + [[maybe_unused]] const char* condition_string, [[maybe_unused]] bool result) { + if (!result) { + std::cout << std::flush << "(debug build) TID " + << std::this_thread::get_id() << ": ASSERTION FAILED: at " + << file << ":" << line << " \n\t-> in " + << function << ", Line " << line << ": \n\t\t-> " + << "Failed Condition: " << condition_string << std::endl; + std::cout << "... terminating ..." << std::endl; + abort(); + } +} + +#define Assert(cond) _assert(__FILE__, __func__, __LINE__, #cond, (cond)) +#define AssertNotReachable() _assert(__FILE__, __func__, __LINE__, "reached unreachable code", false) +#else +// In release build, these macros turn into NOPs. The compiler will optimize these out. +#define Assert(x) \ + do { \ + } while (false) +#define AssertNotReachable() \ + do { \ + } while (false) +#endif // DEBUG diff --git a/include/RWMutex.h b/include/RWMutex.h new file mode 100644 index 0000000..5db780e --- /dev/null +++ b/include/RWMutex.h @@ -0,0 +1,19 @@ +// Author: lionkor +#pragma once + +/* + * An RWMutex allows multiple simultaneous readlocks but only one writelock at a time, + * and write locks and read locks are mutually exclusive. + */ + +#include + +// Use ReadLock(m) and WriteLock(m) to lock it. +using RWMutex = std::shared_mutex; +// Construct with an RWMutex as a non-const reference. +// locks the mutex in lock_shared mode (for reading). Locking in a thread that already owns a lock +// i.e. locking multiple times successively is UB. Construction may be blocking. Destruction is guaranteed to release the lock. +using ReadLock = std::shared_lock; +// Construct with an RWMutex as a non-const reference. +// locks the mutex for writing. Construction may be blocking. Destruction is guaranteed to release the lock. +using WriteLock = std::unique_lock; diff --git a/include/TConfig.h b/include/TConfig.h new file mode 100644 index 0000000..df86764 --- /dev/null +++ b/include/TConfig.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Common.h" + +class TConfig { +public: + TConfig(const std::string& ConfigFile); + +private: + std::string RemoveComments(const std::string& Line); + void SetValues(const std::string& Line, int Index); +}; diff --git a/include/TConsole.h b/include/TConsole.h index 133cb6b..da79b72 100644 --- a/include/TConsole.h +++ b/include/TConsole.h @@ -1,12 +1,16 @@ #pragma once +#include "commandline/commandline.h" #include -#include +#include class TConsole { public: TConsole(); + void Write(const std::string& str); + private: Commandline _Commandline; }; + diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h new file mode 100644 index 0000000..ffe5a67 --- /dev/null +++ b/include/TLuaEngine.h @@ -0,0 +1,32 @@ +#ifndef TLUAENGINE_H +#define TLUAENGINE_H + +#include "Common.h" +#include "IThreaded.h" +#include +#include +#include + +class TLuaFile; + +class TLuaEngine : public IThreaded { +public: + using TSetOfLuaFile = std::set>; + + TLuaEngine(); + + virtual void operator()() override; + + const TSetOfLuaFile& LuaFiles() const { return _LuaFiles; } + + std::optional> GetScript(lua_State* L); + +private: + void FolderList(const std::string& Path, bool HotSwap); + void RegisterFiles(const std::string& Path, bool HotSwap); + bool NewFile(const std::string& Path); + + TSetOfLuaFile _LuaFiles; +}; + +#endif // TLUAENGINE_H diff --git a/include/TLuaFile.h b/include/TLuaFile.h new file mode 100644 index 0000000..1bd2629 --- /dev/null +++ b/include/TLuaFile.h @@ -0,0 +1,58 @@ +#ifndef TLUAFILE_H +#define TLUAFILE_H + +#include "TLuaEngine.h" +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +struct TLuaArg { + std::vector args; + void PushArgs(lua_State* State); +}; + +class TLuaFile { +public: + void Init(); + void RegisterEvent(const std::string& Event, const std::string& FunctionName); + std::string GetRegistered(const std::string& Event) const; + void UnRegisterEvent(const std::string& Event); + void SetLastWrite(fs::file_time_type time); + bool IsRegistered(const std::string& Event); + void SetPluginName(const std::string& Name); + void Execute(const std::string& Command); + void SetFileName(const std::string& Name); + fs::file_time_type GetLastWrite(); + std::string GetPluginName() const; + std::string GetFileName() const; + lua_State* GetState(); + const lua_State* GetState() const; + std::string GetOrigin(); + std::mutex Lock; + void Reload(); + TLuaFile(TLuaEngine& Engine, const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote, bool Console = false); + TLuaFile(TLuaEngine& Engine, bool Console = false); + ~TLuaFile(); + void SetStopThread(bool StopThread) { _StopThread = StopThread; } + bool GetStopThread() const { return _StopThread; } + TLuaEngine& Engine() { return _Engine; } + const TLuaEngine& Engine() const { return _Engine; } + +private: + TLuaEngine& _Engine; + std::set> _RegisteredEvents; + lua_State* luaState { nullptr }; + fs::file_time_type _LastWrote; + std::string _PluginName {}; + std::string _FileName {}; + bool _StopThread = false; + bool _Console = false; +}; + +#endif // TLUAFILE_H diff --git a/include/TServer.h b/include/TServer.h new file mode 100644 index 0000000..a07cd0f --- /dev/null +++ b/include/TServer.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +#include "RWMutex.h" + +class TClient; + +class TServer final { +public: + using TClientSet = std::unordered_set>; + + TServer(int argc, char** argv); + + std::weak_ptr InsertNewClient(); + void RemoveClient(std::weak_ptr); + void ForEachClient(std::function)>); + size_t ClientCount() const; + +private: + TClientSet _Clients; + mutable RWMutex _ClientsMutex; +}; diff --git a/include/VehicleData.h b/include/VehicleData.h new file mode 100644 index 0000000..a6193e3 --- /dev/null +++ b/include/VehicleData.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class TVehicleData final { +public: + TVehicleData(int ID, const std::string& Data); + + bool IsInvalid() const { return _ID == -1; } + int ID() const { return _ID; } + + std::string Data() const { return _Data; } + void SetData(const std::string& Data) { _Data = Data; } + +private: + int _ID { -1 }; + std::string _Data; +}; diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..327617f --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,64 @@ +#include "Client.h" + +#include + +// FIXME: add debug prints + +void TClient::DeleteCar(int Ident) { + for (auto& v : _VehicleData) { + if (v != nullptr && v->ID() == Ident) { + _VehicleData.erase(v); + break; + } + } +} + +void TClient::ClearCars() { + _VehicleData.clear(); +} + +int TClient::GetOpenCarID() { + int OpenID = 0; + bool found; + do { + found = true; + for (auto& v : _VehicleData) { + if (v != nullptr && v->ID() == OpenID) { + OpenID++; + found = false; + } + } + } while (!found); + return OpenID; +} + +void TClient::AddNewCar(int Ident, const std::string& Data) { + _VehicleData.insert(std::make_unique(TVehicleData { Ident, Data })); +} + +TClient::TSetOfVehicleData& TClient::GetAllCars() { + return _VehicleData; +} + +std::string TClient::GetCarData(int Ident) { + for (auto& v : _VehicleData) { + if (v != nullptr && v->ID() == Ident) { + return v->Data(); + } + } + DeleteCar(Ident); + return ""; +} + +void TClient::SetCarData(int Ident, const std::string& Data) { + for (auto& v : _VehicleData) { + if (v != nullptr && v->ID() == Ident) { + v->Data() = Data; + return; + } + } + DeleteCar(Ident); +} +int TClient::GetCarCount() { + return int(_VehicleData.size()); +} diff --git a/src/Common.cpp b/src/Common.cpp new file mode 100644 index 0000000..873bb63 --- /dev/null +++ b/src/Common.cpp @@ -0,0 +1,18 @@ +#include "Common.h" +#include "TConsole.h" + +std::unique_ptr Application::_Console = std::make_unique(); + +void Application::RegisterShutdownHandler(const TShutdownHandler& Handler) { + std::unique_lock Lock(_ShutdownHandlersMutex); + if (Handler) { + _ShutdownHandlers.push_back(Handler); + } +} + +void Application::GracefullyShutdown() { + std::unique_lock Lock(_ShutdownHandlersMutex); + for (auto& Handler : _ShutdownHandlers) { + Handler(); + } +} diff --git a/src/TConfig.cpp b/src/TConfig.cpp new file mode 100644 index 0000000..ece98c1 --- /dev/null +++ b/src/TConfig.cpp @@ -0,0 +1,126 @@ +#include "../include/TConfig.h" +#include + +TConfig::TConfig(const std::string& ConfigFile) { + std::ifstream File(ConfigFile); + if (File.good()) { + std::string line; + int index = 1; + while (getline(File, line)) { + index++; + } + if (index - 1 < 11) { + error(("Outdated/Incorrect config please remove it server will close in 5 secs")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + _Exit(0); + } + File.close(); + File.open(("Server.cfg")); + info(("Config found updating values")); + index = 1; + while (std::getline(File, line)) { + if (line.rfind('#', 0) != 0 && line.rfind(' ', 0) != 0) { //Checks if it starts as Comment + std::string CleanLine = RemoveComments(line); //Cleans it from the Comments + SetValues(CleanLine, index); //sets the values + index++; + } + } + } else { + info(("Config not found generating default")); + std::ofstream FileStream; + FileStream.open(ConfigFile); + // TODO REPLACE THIS SHIT OMG + FileStream << ("# This is the BeamMP Server Configuration File v0.60\n" + "Debug = false # true or false to enable debug console output\n" + "Private = true # Private?\n" + "Port = 30814 # Port to run the server on UDP and TCP\n" + "Cars = 1 # Max cars for every player\n" + "MaxPlayers = 10 # Maximum Amount of Clients\n" + "Map = \"/levels/gridmap/info.json\" # Default Map\n" + "Name = \"BeamMP New Server\" # Server Name\n" + "Desc = \"BeamMP Default Description\" # Server Description\n" + "use = \"Resources\" # Resource file name\n" + "AuthKey = \"\" # Auth Key"); + FileStream.close(); + error(("You are required to input the AuthKey")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + _Exit(0); + } + debug("Debug : " + std::string(Application::Settings.DebugModeEnabled ? "true" : "false")); + debug("Private : " + std::string(Application::Settings.Private ? "true" : "false")); + debug("Port : " + std::to_string(Application::Settings.Port)); + debug("Max Cars : " + std::to_string(Application::Settings.MaxCars)); + debug("MaxPlayers : " + std::to_string(Application::Settings.MaxPlayers)); + debug("MapName : \"" + Application::Settings.MapName + "\""); + debug("ServerName : \"" + Application::Settings.ServerName + "\""); + debug("ServerDesc : \"" + Application::Settings.ServerDesc + "\""); + debug("File : \"" + Application::Settings.Resource + "\""); + debug("Key length : " + std::to_string(Application::Settings.Key.length()) + ""); +} + +std::string TConfig::RemoveComments(const std::string& Line) { + std::string Return; + for (char c : Line) { + if (c == '#') + break; + Return += c; + } + return Return; +} + +void TConfig::SetValues(const std::string& Line, int Index) { + int state = 0; + std::string Data; + bool Switch = false; + if (Index > 5) + Switch = true; + for (char c : Line) { + if (Switch) { + if (c == '\"') + state++; + if (state > 0 && state < 2) + Data += c; + } else { + if (c == ' ') + state++; + if (state > 1) + Data += c; + } + } + Data = Data.substr(1); + std::string::size_type sz; + bool FoundTrue = std::string(Data).find("true") != std::string::npos; //searches for "true" + switch (Index) { + case 1: + Application::Settings.DebugModeEnabled = FoundTrue; //checks and sets the Debug Value + break; + case 2: + Application::Settings.Private = FoundTrue; //checks and sets the Private Value + break; + case 3: + Application::Settings.Port = std::stoi(Data, &sz); //sets the Port + break; + case 4: + Application::Settings.MaxCars = std::stoi(Data, &sz); //sets the Max Car amount + break; + case 5: + Application::Settings.MaxPlayers = std::stoi(Data, &sz); //sets the Max Amount of player + break; + case 6: + Application::Settings.MapName = Data; //Map + break; + case 7: + Application::Settings.ServerName = Data; //Name + break; + case 8: + Application::Settings.ServerDesc = Data; //desc + break; + case 9: + Application::Settings.Resource = Data; //File name + break; + case 10: + Application::Settings.Key = Data; //File name + default: + break; + } +} diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 18270de..8109245 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -1,13 +1,54 @@ #include "TConsole.h" +#include "Common.h" #include "Compat.h" +#include +#include + +std::string GetDate() { + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + time_t tt = std::chrono::system_clock::to_time_t(now); + tm local_tm {}; +#ifdef WIN32 + localtime_s(&local_tm, &tt); +#else // unix + localtime_r(&tt, &local_tm); +#endif // WIN32 + std::stringstream date; + int S = local_tm.tm_sec; + int M = local_tm.tm_min; + int H = local_tm.tm_hour; + std::string Secs = (S > 9 ? std::to_string(S) : "0" + std::to_string(S)); + std::string Min = (M > 9 ? std::to_string(M) : "0" + std::to_string(M)); + std::string Hour = (H > 9 ? std::to_string(H) : "0" + std::to_string(H)); + date + << "[" + << local_tm.tm_mday << "/" + << local_tm.tm_mon + 1 << "/" + << local_tm.tm_year + 1900 << " " + << Hour << ":" + << Min << ":" + << Secs + << "] "; + /* TODO + if (Debug) { + date << ThreadName() + << " "; + } + */ + return date.str(); +} + TConsole::TConsole() { _Commandline.enable_history(); _Commandline.set_history_limit(20); - _Commandline.on_command = [](Commandline& c) { + _Commandline.set_prompt("> "); + _Commandline.on_command = [this](Commandline& c) { auto cmd = c.get_command(); + _Commandline.write("> " + cmd); if (cmd == "exit") { - _Exit(0); + info("gracefully shutting down"); + Application::GracefullyShutdown(); } else if (cmd == "clear" || cmd == "cls") { // TODO: clear screen } else { @@ -15,3 +56,10 @@ TConsole::TConsole() { } }; } + +void TConsole::Write(const std::string& str) { + auto ToWrite = GetDate() + str; + _Commandline.write(ToWrite); + // TODO write to logfile, too +} + diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp new file mode 100644 index 0000000..e4399aa --- /dev/null +++ b/src/TLuaEngine.cpp @@ -0,0 +1,67 @@ +#include "TLuaEngine.h" +#include "TLuaFile.h" + +#include + +namespace fs = std::filesystem; + +TLuaEngine::TLuaEngine() { + if (!fs::exists(Application::Settings.ResourceFolder)) { + fs::create_directory(Application::Settings.ResourceFolder); + } + std::string Path = Application::Settings.ResourceFolder + ("/Server"); + if (!fs::exists(Path)) { + fs::create_directory(Path); + } + FolderList(Path, false); +} + +void TLuaEngine::operator()() { + info("Lua system online"); + // thread main +} + +std::optional> TLuaEngine::GetScript(lua_State* L) { + for (auto& Script : _LuaFiles) { + if (Script->GetState() == L) + return *Script; + } + return std::nullopt; +} + +void TLuaEngine::FolderList(const std::string& Path, bool HotSwap) { + for (const auto& entry : fs::directory_iterator(Path)) { + auto pos = entry.path().filename().string().find('.'); + if (pos == std::string::npos) { + RegisterFiles(entry.path().string(), HotSwap); + } + } +} + +void TLuaEngine::RegisterFiles(const std::string& Path, bool HotSwap) { + std::string Name = Path.substr(Path.find_last_of('\\') + 1); + if (!HotSwap) + info(("Loading plugin : ") + Name); + for (const auto& entry : fs::directory_iterator(Path)) { + auto pos = entry.path().string().find((".lua")); + if (pos != std::string::npos && entry.path().string().length() - pos == 4) { + if (!HotSwap || NewFile(entry.path().string())) { + auto FileName = entry.path().string(); + std::unique_ptr ScriptToInsert(new TLuaFile(*this, Name, FileName, fs::last_write_time(FileName))); + auto& Script = *ScriptToInsert; + _LuaFiles.insert(std::move(ScriptToInsert)); + Script.Init(); + if (HotSwap) + info(("[HOTSWAP] Added : ") + Script.GetFileName().substr(Script.GetFileName().find('\\'))); + } + } + } +} + +bool TLuaEngine::NewFile(const std::string& Path) { + for (auto& Script : _LuaFiles) { + if (Path == Script->GetFileName()) + return false; + } + return true; +} diff --git a/src/TLuaFile.cpp b/src/TLuaFile.cpp new file mode 100644 index 0000000..a40abd3 --- /dev/null +++ b/src/TLuaFile.cpp @@ -0,0 +1,722 @@ +#include "TLuaFile.h" +#include "Client.h" +#include "CustomAssert.h" +#include "TServer.h" + +#include +#include + +// TODO: REWRITE >:( + +void SendError(TLuaEngine& Engine, lua_State* L, const std::string& msg); +std::any CallFunction(TLuaFile* lua, const std::string& FuncName, std::shared_ptr Arg); +std::any TriggerLuaEvent(TLuaEngine& Engine, const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr arg, bool Wait); + +std::shared_ptr CreateArg(lua_State* L, int T, int S) { + if (S > T) + return nullptr; + std::shared_ptr temp(new TLuaArg); + for (int C = S; C <= T; C++) { + if (lua_isstring(L, C)) { + temp->args.emplace_back(std::string(lua_tostring(L, C))); + } else if (lua_isinteger(L, C)) { + temp->args.emplace_back(int(lua_tointeger(L, C))); + } else if (lua_isboolean(L, C)) { + temp->args.emplace_back(bool(lua_toboolean(L, C))); + } else if (lua_isnumber(L, C)) { + temp->args.emplace_back(float(lua_tonumber(L, C))); + } + } + return temp; +} +void ClearStack(lua_State* L) { + lua_settop(L, 0); +} + +std::any Trigger(TLuaFile* lua, const std::string& R, std::shared_ptr arg) { + std::lock_guard lockGuard(lua->Lock); + std::packaged_task)> task([lua, R](std::shared_ptr arg) { return CallFunction(lua, R, arg); }); + std::future f1 = task.get_future(); + std::thread t(std::move(task), arg); + t.detach(); + auto status = f1.wait_for(std::chrono::seconds(5)); + if (status != std::future_status::timeout) + return f1.get(); + SendError(lua->Engine(), lua->GetState(), R + " took too long to respond"); + return 0; +} +std::any FutureWait(TLuaFile* lua, const std::string& R, std::shared_ptr arg, bool Wait) { + Assert(lua); + std::packaged_task)> task([lua, R](std::shared_ptr arg) { return Trigger(lua, R, arg); }); + std::future f1 = task.get_future(); + std::thread t(std::move(task), arg); + t.detach(); + int T = 0; + if (Wait) + T = 6; + auto status = f1.wait_for(std::chrono::seconds(T)); + if (status != std::future_status::timeout) + return f1.get(); + return 0; +} +std::any TriggerLuaEvent(TLuaEngine& Engine, const std::string& Event, bool local, TLuaFile* Caller, std::shared_ptr arg, bool Wait) { + std::any R; + std::string Type; + int Ret = 0; + for (auto& Script : Engine.LuaFiles()) { + if (Script->IsRegistered(Event)) { + if (local) { + if (Script->GetPluginName() == Caller->GetPluginName()) { + R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait); + Type = R.type().name(); + if (Type.find("int") != std::string::npos) { + if (std::any_cast(R)) + Ret++; + } else if (Event == "onPlayerAuth") + return R; + } + } else { + R = FutureWait(Script.get(), Script->GetRegistered(Event), arg, Wait); + Type = R.type().name(); + if (Type.find("int") != std::string::npos) { + if (std::any_cast(R)) + Ret++; + } else if (Event == "onPlayerAuth") + return R; + } + } + } + return Ret; +} +bool ConsoleCheck(lua_State* L, int r) { + if (r != LUA_OK) { + std::string msg = lua_tostring(L, -1); + warn(("_Console | ") + msg); + return false; + } + return true; +} +bool CheckLua(TLuaEngine& Engine, lua_State* L, int r) { + if (r != LUA_OK) { + std::string msg = lua_tostring(L, -1); + auto MaybeS = Engine.GetScript(L); + if (MaybeS.has_value()) { + TLuaFile& S = MaybeS.value(); + std::string a = fs::path(S.GetFileName()).filename().string(); + warn(a + " | " + msg); + return false; + } + // What the fuck, what do we do?! + AssertNotReachable(); + } + return true; +} + +int lua_RegisterEvent(TLuaEngine& Engine, lua_State* L) { + int Args = lua_gettop(L); + auto MaybeScript = Engine.GetScript(L); + Assert(MaybeScript.has_value()); + TLuaFile& Script = MaybeScript.value(); + if (Args == 2 && lua_isstring(L, 1) && lua_isstring(L, 2)) { + Script.RegisterEvent(lua_tostring(L, 1), lua_tostring(L, 2)); + } else + SendError(Engine, L, ("RegisterEvent invalid argument count expected 2 got ") + std::to_string(Args)); + return 0; +} +int lua_TriggerEventL(TLuaEngine& Engine, lua_State* L) { + int Args = lua_gettop(L); + auto MaybeScript = Engine.GetScript(L); + Assert(MaybeScript.has_value()); + TLuaFile& Script = MaybeScript.value(); + if (Args > 0) { + if (lua_isstring(L, 1)) { + TriggerLuaEvent(Engine, lua_tostring(L, 1), true, &Script, CreateArg(L, Args, 2), false); + } else + SendError(Engine, L, ("TriggerLocalEvent wrong argument [1] need string")); + } else { + SendError(Engine, L, ("TriggerLocalEvent not enough arguments expected 1 got 0")); + } + return 0; +} + +int lua_TriggerEventG(TLuaEngine& Engine, lua_State* L) { + int Args = lua_gettop(L); + auto MaybeScript = Engine.GetScript(L); + Assert(MaybeScript.has_value()); + TLuaFile& Script = MaybeScript.value(); + if (Args > 0) { + if (lua_isstring(L, 1)) { + TriggerLuaEvent(Engine, lua_tostring(L, 1), false, &Script, CreateArg(L, Args, 2), false); + } else + SendError(Engine, L, ("TriggerGlobalEvent wrong argument [1] need string")); + } else + SendError(Engine, L, ("TriggerGlobalEvent not enough arguments")); + return 0; +} + +char* ThreadOrigin(TLuaFile* lua) { + std::string T = "Thread in " + fs::path(lua->GetFileName()).filename().string(); + char* Data = new char[T.size() + 1]; + std::fill_n(Data, T.size() + 1, 0); + memcpy(Data, T.c_str(), T.size()); + return Data; +} +void SafeExecution(TLuaEngine& Engine, TLuaFile* lua, const std::string& FuncName) { + lua_State* luaState = lua->GetState(); + lua_getglobal(luaState, FuncName.c_str()); + if (lua_isfunction(luaState, -1)) { + char* Origin = ThreadOrigin(lua); +#ifdef WIN32 + __try { + int R = lua_pcall(luaState, 0, 0, 0); + CheckLua(luaState, R); + } __except (Handle(GetExceptionInformation(), Origin)) { + } +#else // unix + int R = lua_pcall(luaState, 0, 0, 0); + CheckLua(Engine, luaState, R); +#endif // WIN32 + delete[] Origin; + } + ClearStack(luaState); +} + +void ExecuteAsync(TLuaEngine& Engine, TLuaFile* lua, const std::string& FuncName) { + std::lock_guard lockGuard(lua->Lock); + SafeExecution(Engine, lua, FuncName); +} +void CallAsync(TLuaEngine& Engine, TLuaFile* lua, const std::string& Func, int U) { + //DebugPrintTID(); + lua->SetStopThread(false); + int D = 1000 / U; + while (!lua->GetStopThread()) { + ExecuteAsync(Engine, lua, Func); + std::this_thread::sleep_for(std::chrono::milliseconds(D)); + } +} +int lua_StopThread(TLuaEngine& Engine, lua_State* L) { + auto MaybeScript = Engine.GetScript(L); + Assert(MaybeScript.has_value()); + // ugly, but whatever, this is safe as fuck + MaybeScript.value().get().SetStopThread(true); + return 0; +} +int lua_CreateThread(TLuaEngine& Engine, lua_State* L) { + int Args = lua_gettop(L); + if (Args > 1) { + if (lua_isstring(L, 1)) { + std::string STR = lua_tostring(L, 1); + if (lua_isinteger(L, 2) || lua_isnumber(L, 2)) { + int U = int(lua_tointeger(L, 2)); + if (U > 0 && U < 501) { + auto MaybeScript = Engine.GetScript(L); + Assert(MaybeScript.has_value()); + TLuaFile& Script = MaybeScript.value(); + std::thread t1(CallAsync, &Script, STR, U); + t1.detach(); + } else + SendError(Engine, L, ("CreateThread wrong argument [2] number must be between 1 and 500")); + } else + SendError(Engine, L, ("CreateThread wrong argument [2] need number")); + } else + SendError(Engine, L, ("CreateThread wrong argument [1] need string")); + } else + SendError(Engine, L, ("CreateThread not enough arguments")); + return 0; +} +int lua_Sleep(TLuaEngine& Engine, lua_State* L) { + if (lua_isnumber(L, 1)) { + int t = int(lua_tonumber(L, 1)); + std::this_thread::sleep_for(std::chrono::milliseconds(t)); + } else { + SendError(Engine, L, ("Sleep not enough arguments")); + return 0; + } + return 1; +} +std::weak_ptr GetClient(TServer& Server, int ID) { + std::weak_ptr Client; + Server.ForEachClient([&](std::weak_ptr CPtr) { + if (!CPtr.expired()) { + auto C = CPtr.lock(); + if (C != nullptr && C->GetID() == ID) { + return false; + } + } + return true; + }); + return std::weak_ptr(); +} +// CONTINUE +int lua_isConnected(lua_State* L) { + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c != nullptr) + lua_pushboolean(L, c->isConnected); + else + return 0; + } else { + SendError(L, ("isConnected not enough arguments")); + return 0; + } + return 1; +} +int lua_GetPlayerName(lua_State* L) { + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c != nullptr) + lua_pushstring(L, c->GetName().c_str()); + else + return 0; + } else { + SendError(L, ("GetPlayerName not enough arguments")); + return 0; + } + return 1; +} +int lua_GetPlayerCount(lua_State* L) { + lua_pushinteger(L, CI->Size()); + return 1; +} +int lua_GetGuest(lua_State* L) { + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c != nullptr) + lua_pushboolean(L, c->isGuest); + else + return 0; + } else { + SendError(L, "GetGuest not enough arguments"); + return 0; + } + return 1; +} +int lua_GetAllPlayers(lua_State* L) { + lua_newtable(L); + int i = 1; + for (auto& c : CI->Clients) { + if (c == nullptr) + continue; + lua_pushinteger(L, c->GetID()); + lua_pushstring(L, c->GetName().c_str()); + lua_settable(L, -3); + i++; + } + if (CI->Clients.empty()) + return 0; + return 1; +} +int lua_GetCars(lua_State* L) { + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c != nullptr) { + int i = 1; + for (auto& v : c->GetAllCars()) { + if (v != nullptr) { + lua_pushinteger(L, v->ID); + lua_pushstring(L, v->Data.substr(3).c_str()); + lua_settable(L, -3); + i++; + } + } + if (c->GetAllCars().empty()) + return 0; + } else + return 0; + } else { + SendError(L, ("GetPlayerVehicles not enough arguments")); + return 0; + } + return 1; +} +int lua_dropPlayer(lua_State* L) { + int Args = lua_gettop(L); + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c == nullptr) + return 0; + std::string Reason; + if (Args > 1 && lua_isstring(L, 2)) { + Reason = std::string((" Reason : ")) + lua_tostring(L, 2); + } + Respond(c, "C:Server:You have been Kicked from the server! " + Reason, true); + c->SetStatus(-2); + info(("Closing socket due to kick")); + CloseSocketProper(c->GetTCPSock()); + } else + SendError(L, ("DropPlayer not enough arguments")); + return 0; +} +int lua_sendChat(lua_State* L) { + if (lua_isinteger(L, 1) || lua_isnumber(L, 1)) { + if (lua_isstring(L, 2)) { + int ID = int(lua_tointeger(L, 1)); + if (ID == -1) { + std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2)); + SendToAll(nullptr, Packet, true, true); + } else { + Client* c = GetClient(ID); + if (c != nullptr) { + if (!c->isSynced) + return 0; + std::string Packet = "C:Server: " + std::string(lua_tostring(L, 2)); + Respond(c, Packet, true); + } else + SendError(L, ("SendChatMessage invalid argument [1] invalid ID")); + } + } else + SendError(L, ("SendChatMessage invalid argument [2] expected string")); + } else + SendError(L, ("SendChatMessage invalid argument [1] expected number")); + return 0; +} +int lua_RemoveVehicle(lua_State* L) { + int Args = lua_gettop(L); + if (Args != 2) { + SendError(L, ("RemoveVehicle invalid argument count expected 2 got ") + std::to_string(Args)); + return 0; + } + if ((lua_isinteger(L, 1) || lua_isnumber(L, 1)) && (lua_isinteger(L, 2) || lua_isnumber(L, 2))) { + int PID = int(lua_tointeger(L, 1)); + int VID = int(lua_tointeger(L, 2)); + Client* c = GetClient(PID); + if (c == nullptr) { + SendError(L, ("RemoveVehicle invalid Player ID")); + return 0; + } + if (!c->GetCarData(VID).empty()) { + std::string Destroy = "Od:" + std::to_string(PID) + "-" + std::to_string(VID); + SendToAll(nullptr, Destroy, true, true); + c->DeleteCar(VID); + } + } else + SendError(L, ("RemoveVehicle invalid argument expected number")); + return 0; +} +int lua_HWID(lua_State* L) { + lua_pushinteger(L, -1); + return 1; +} +int lua_RemoteEvent(lua_State* L) { + int Args = lua_gettop(L); + if (Args != 3) { + SendError(L, ("TriggerClientEvent invalid argument count expected 3 got ") + std::to_string(Args)); + return 0; + } + if (!lua_isnumber(L, 1)) { + SendError(L, ("TriggerClientEvent invalid argument [1] expected number")); + return 0; + } + if (!lua_isstring(L, 2)) { + SendError(L, ("TriggerClientEvent invalid argument [2] expected string")); + return 0; + } + if (!lua_isstring(L, 3)) { + SendError(L, ("TriggerClientEvent invalid argument [3] expected string")); + return 0; + } + int ID = int(lua_tointeger(L, 1)); + std::string Packet = "E:" + std::string(lua_tostring(L, 2)) + ":" + std::string(lua_tostring(L, 3)); + if (ID == -1) + SendToAll(nullptr, Packet, true, true); + else { + Client* c = GetClient(ID); + if (c == nullptr) { + SendError(L, ("TriggerClientEvent invalid Player ID")); + return 0; + } + Respond(c, Packet, true); + } + return 0; +} +int lua_ServerExit(lua_State* L) { + if (lua_gettop(L) > 0) { + if (lua_isnumber(L, 1)) { + _Exit(int(lua_tointeger(L, 1))); + } + } + _Exit(0); +} +int lua_Set(lua_State* L) { + int Args = lua_gettop(L); + if (Args != 2) { + SendError(L, ("set invalid argument count expected 2 got ") + std::to_string(Args)); + return 0; + } + if (!lua_isnumber(L, 1)) { + SendError(L, ("set invalid argument [1] expected number")); + return 0; + } + auto MaybeSrc = GetScript(L); + std::string Name; + if (!MaybeSrc.has_value()) { + Name = ("_Console"); + } else { + Name = MaybeSrc.value().get().GetPluginName(); + } + int C = int(lua_tointeger(L, 1)); + switch (C) { + case 0: //debug + if (lua_isboolean(L, 2)) { + Debug = lua_toboolean(L, 2); + info(Name + (" | Debug -> ") + (Debug ? "true" : "false")); + } else + SendError(L, ("set invalid argument [2] expected boolean for ID : 0")); + break; + case 1: //private + if (lua_isboolean(L, 2)) { + Private = lua_toboolean(L, 2); + info(Name + (" | Private -> ") + (Private ? "true" : "false")); + } else + SendError(L, ("set invalid argument [2] expected boolean for ID : 1")); + break; + case 2: //max cars + if (lua_isnumber(L, 2)) { + MaxCars = int(lua_tointeger(L, 2)); + info(Name + (" | MaxCars -> ") + std::to_string(MaxCars)); + } else + SendError(L, ("set invalid argument [2] expected number for ID : 2")); + break; + case 3: //max players + if (lua_isnumber(L, 2)) { + MaxPlayers = int(lua_tointeger(L, 2)); + info(Name + (" | MaxPlayers -> ") + std::to_string(MaxPlayers)); + } else + SendError(L, ("set invalid argument [2] expected number for ID : 3")); + break; + case 4: //Map + if (lua_isstring(L, 2)) { + MapName = lua_tostring(L, 2); + info(Name + (" | MapName -> ") + MapName); + } else + SendError(L, ("set invalid argument [2] expected string for ID : 4")); + break; + case 5: //Name + if (lua_isstring(L, 2)) { + ServerName = lua_tostring(L, 2); + info(Name + (" | ServerName -> ") + ServerName); + } else + SendError(L, ("set invalid argument [2] expected string for ID : 5")); + break; + case 6: //Desc + if (lua_isstring(L, 2)) { + ServerDesc = lua_tostring(L, 2); + info(Name + (" | ServerDesc -> ") + ServerDesc); + } else + SendError(L, ("set invalid argument [2] expected string for ID : 6")); + break; + default: + warn(("Invalid config ID : ") + std::to_string(C)); + break; + } + + return 0; +} +extern "C" { +int lua_Print(lua_State* L) { + int Arg = lua_gettop(L); + for (int i = 1; i <= Arg; i++) { + auto str = lua_tostring(L, i); + if (str != nullptr) { + ConsoleOut(str + std::string(("\n"))); + } else { + ConsoleOut(("nil\n")); + } + } + return 0; +} +} + +TLuaFile::TLuaFile(TLuaEngine& Engine, const std::string& PluginName, const std::string& FileName, fs::file_time_type LastWrote, bool Console) + : _Engine(Engine) + , luaState(luaL_newstate()) { + Assert(luaState); + if (!PluginName.empty()) { + SetPluginName(PluginName); + } + if (!FileName.empty()) { + SetFileName(FileName); + } + SetLastWrite(LastWrote); + _Console = Console; +} + +TLuaFile::TLuaFile(TLuaEngine& Engine, bool Console) + : _Engine(Engine) + , luaState(luaL_newstate()) { + _Console = Console; +} + +void TLuaFile::Execute(const std::string& Command) { + if (ConsoleCheck(luaState, luaL_dostring(luaState, Command.c_str()))) { + lua_settop(luaState, 0); + } +} +void TLuaFile::Reload() { + if (CheckLua(luaState, luaL_dofile(luaState, _FileName.c_str()))) { + CallFunction(this, ("onInit"), nullptr); + } +} +std::string TLuaFile::GetOrigin() { + return fs::path(GetFileName()).filename().string(); +} + +std::any CallFunction(TLuaFile* lua, const std::string& FuncName, std::shared_ptr Arg) { + lua_State* luaState = lua->GetState(); + lua_getglobal(luaState, FuncName.c_str()); + if (lua_isfunction(luaState, -1)) { + int Size = 0; + if (Arg != nullptr) { + Size = int(Arg->args.size()); + Arg->PushArgs(luaState); + } + int R = lua_pcall(luaState, Size, 1, 0); + if (CheckLua(luaState, R)) { + if (lua_isnumber(luaState, -1)) { + return int(lua_tointeger(luaState, -1)); + } else if (lua_isstring(luaState, -1)) { + return std::string(lua_tostring(luaState, -1)); + } + } + } + ClearStack(luaState); + return 0; +} +void TLuaFile::SetPluginName(const std::string& Name) { + _PluginName = Name; +} +void TLuaFile::SetFileName(const std::string& Name) { + _FileName = Name; +} +int lua_TempFix(TLuaEngine& Engine, lua_State* L) { + if (lua_isnumber(L, 1)) { + int ID = int(lua_tonumber(L, 1)); + Client* c = GetClient(ID); + if (c == nullptr) + return 0; + std::string Ret; + if (c->isGuest) { + Ret = "Guest-" + c->GetName(); + } else + Ret = c->GetName(); + lua_pushstring(L, Ret.c_str()); + } else + SendError(Engine, L, "GetDID not enough arguments"); + return 1; +} +void TLuaFile::Init() { + Assert(luaState); + luaL_openlibs(luaState); + lua_register(luaState, "TriggerGlobalEvent", lua_TriggerEventG); + lua_register(luaState, "TriggerLocalEvent", lua_TriggerEventL); + lua_register(luaState, "TriggerClientEvent", lua_RemoteEvent); + lua_register(luaState, "GetPlayerCount", lua_GetPlayerCount); + lua_register(luaState, "isPlayerConnected", lua_isConnected); + lua_register(luaState, "RegisterEvent", lua_RegisterEvent); + lua_register(luaState, "GetPlayerName", lua_GetPlayerName); + lua_register(luaState, "RemoveVehicle", lua_RemoveVehicle); + lua_register(luaState, "GetPlayerDiscordID", lua_TempFix); + lua_register(luaState, "CreateThread", lua_CreateThread); + lua_register(luaState, "GetPlayerVehicles", lua_GetCars); + lua_register(luaState, "SendChatMessage", lua_sendChat); + lua_register(luaState, "GetPlayers", lua_GetAllPlayers); + lua_register(luaState, "GetPlayerGuest", lua_GetGuest); + lua_register(luaState, "StopThread", lua_StopThread); + lua_register(luaState, "DropPlayer", lua_dropPlayer); + lua_register(luaState, "GetPlayerHWID", lua_HWID); + lua_register(luaState, "exit", lua_ServerExit); + lua_register(luaState, "Sleep", lua_Sleep); + lua_register(luaState, "print", lua_Print); + lua_register(luaState, "Set", lua_Set); + if (!_Console) + Reload(); +} + +void TLuaFile::RegisterEvent(const std::string& Event, const std::string& FunctionName) { + _RegisteredEvents.insert(std::make_pair(Event, FunctionName)); +} +void TLuaFile::UnRegisterEvent(const std::string& Event) { + for (const std::pair& a : _RegisteredEvents) { + if (a.first == Event) { + _RegisteredEvents.erase(a); + break; + } + } +} +bool TLuaFile::IsRegistered(const std::string& Event) { + for (const std::pair& a : _RegisteredEvents) { + if (a.first == Event) + return true; + } + return false; +} +std::string TLuaFile::GetRegistered(const std::string& Event) const { + for (const std::pair& a : _RegisteredEvents) { + if (a.first == Event) + return a.second; + } + return ""; +} +std::string TLuaFile::GetFileName() const { + return _FileName; +} +std::string TLuaFile::GetPluginName() const { + return _PluginName; +} +lua_State* TLuaFile::GetState() { + return luaState; +} + +const lua_State* TLuaFile::GetState() const { + return luaState; +} + +void TLuaFile::SetLastWrite(fs::file_time_type time) { + _LastWrote = time; +} +fs::file_time_type TLuaFile::GetLastWrite() { + return _LastWrote; +} + +TLuaFile::~TLuaFile() { + info("closing lua state"); + lua_close(luaState); +} + +void SendError(TLuaEngine& Engine, lua_State* L, const std::string& msg) { + Assert(L); + auto MaybeS = Engine.GetScript(L); + std::string a; + if (!MaybeS.has_value()) { + a = ("_Console"); + } else { + TLuaFile& S = MaybeS.value(); + a = fs::path(S.GetFileName()).filename().string(); + } + warn(a + (" | Incorrect Call of ") + msg); +} + +void TLuaArg::PushArgs(lua_State* State) { + for (std::any arg : args) { + if (!arg.has_value()) + return; + std::string Type = arg.type().name(); + if (Type.find("bool") != std::string::npos) { + lua_pushboolean(State, std::any_cast(arg)); + } + if (Type.find("basic_string") != std::string::npos || Type.find("char") != std::string::npos) { + lua_pushstring(State, std::any_cast(arg).c_str()); + } + if (Type.find("int") != std::string::npos) { + lua_pushinteger(State, std::any_cast(arg)); + } + if (Type.find("float") != std::string::npos) { + lua_pushnumber(State, std::any_cast(arg)); + } + } +} diff --git a/src/TServer.cpp b/src/TServer.cpp new file mode 100644 index 0000000..824efd2 --- /dev/null +++ b/src/TServer.cpp @@ -0,0 +1,49 @@ +#include "TServer.h" +#include "Client.h" +#include "Common.h" + +TServer::TServer(int argc, char** argv) { + info("BeamMP Server running version " + Application::ServerVersion()); + if (argc > 1) { + Application::Settings.CustomIP = argv[1]; + size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.'); + auto p = Application::Settings.CustomIP.find_first_not_of((".0123456789")); + if (p != std::string::npos || n != 3 || Application::Settings.CustomIP.substr(0, 3) == ("127")) { + Application::Settings.CustomIP.clear(); + warn("IP Specified is invalid! Ignoring"); + } else { + info("server started with custom IP"); + } + } +} + +void TServer::RemoveClient(std::weak_ptr WeakClientPtr) { + if (!WeakClientPtr.expired()) { + TClient& Client = *WeakClientPtr.lock(); + debug("removing client " + Client.GetName() + " (" + std::to_string(ClientCount()) + ")"); + Client.ClearCars(); + WriteLock Lock(_ClientsMutex); + _Clients.erase(WeakClientPtr.lock()); + } +} + +std::weak_ptr TServer::InsertNewClient() { + debug("inserting new client (" + std::to_string(ClientCount()) + ")"); + WriteLock Lock(_ClientsMutex); + auto [Iter, Replaced] = _Clients.insert(std::make_shared()); + return *Iter; +} + +void TServer::ForEachClient(std::function)> Fn) { + ReadLock Lock(_ClientsMutex); + for (auto& Client : _Clients) { + if (!Fn(Client)) { + break; + } + } +} + +size_t TServer::ClientCount() const { + ReadLock Lock(_ClientsMutex); + return _Clients.size(); +} diff --git a/src/VehicleData.cpp b/src/VehicleData.cpp new file mode 100644 index 0000000..3e52c6c --- /dev/null +++ b/src/VehicleData.cpp @@ -0,0 +1,6 @@ +#include "VehicleData.h" + +TVehicleData::TVehicleData(int ID, const std::string& Data) + : _ID(ID) + , _Data(Data) { +} diff --git a/src/main.cpp b/src/main.cpp index 2f06ab7..41e337d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,61 @@ +#include "Client.h" +#include "Common.h" +#include "IThreaded.h" +#include "TConfig.h" +#include "TConsole.h" +#include "TServer.h" #include #include #include #include -#include "TConsole.h" +#ifdef __unix +#include -int main(int argc, char** argv) { - TConsole Console; - while (true) { +void UnixSignalHandler(int sig) { + switch (sig) { + case SIGPIPE: + warn("ignoring SIGPIPE"); + break; + case SIGTERM: + info("gracefully shutting down via SIGTERM"); + Application::GracefullyShutdown(); + break; + case SIGINT: + info("gracefully shutting down via SIGINT"); + Application::GracefullyShutdown(); + break; + } +} +#endif // __unix + +int main(int argc, char** argv) { +#ifdef __unix + info("registering SIGPIPE and SIGTERM handlers"); + signal(SIGPIPE, UnixSignalHandler); + signal(SIGTERM, UnixSignalHandler); + signal(SIGINT, UnixSignalHandler); +#endif // __unix + + TServer Server(argc, argv); + TConfig Config("Server.cfg"); + + auto Client = Server.InsertNewClient(); + if (!Client.expired()) { + Client.lock()->SetName("Lion"); + } else { + error("fuckj"); + _Exit(-1); + } + + Server.ForEachClient([](auto client) -> bool { debug(client.lock()->GetName()); return true; }); + + Server.RemoveClient(Client); + + // TODO: replace with blocking heartbeat + bool Shutdown = false; + Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; }); + while (!Shutdown) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }