diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index d23a24ba..16367371 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -7,6 +7,7 @@ namespace Components void Loader::Initialize() { Loader::Register(new Dedicated()); + Loader::Register(new Singleton()); Loader::Register(new Dvar()); Loader::Register(new Maps()); @@ -18,6 +19,7 @@ namespace Components Loader::Register(new Window()); Loader::Register(new Command()); Loader::Register(new Console()); + Loader::Register(new IPCPipe()); Loader::Register(new Network()); Loader::Register(new RawFiles()); Loader::Register(new Renderer()); @@ -25,7 +27,6 @@ namespace Components Loader::Register(new UIScript()); Loader::Register(new FastFiles()); Loader::Register(new Materials()); - Loader::Register(new Singleton()); Loader::Register(new FileSystem()); Loader::Register(new QuickPatch()); Loader::Register(new ServerList()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index aacc9992..711cadea 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -29,6 +29,7 @@ namespace Components #include "Modules\Window.hpp" #include "Modules\Command.hpp" #include "Modules\Console.hpp" +#include "Modules\IPCPipe.hpp" #include "Modules\Network.hpp" #include "Modules\Party.hpp" // Destroys the order, but requires network classes :D #include "Modules\RawFiles.hpp" diff --git a/src/Components/Modules/ConnectProtocol.hpp b/src/Components/Modules/ConnectProtocol.hpp index b22dc00c..3fe21896 100644 --- a/src/Components/Modules/ConnectProtocol.hpp +++ b/src/Components/Modules/ConnectProtocol.hpp @@ -4,6 +4,8 @@ namespace Components { public: ConnectProtocol(); + const char* GetName() { return "ConnectProtocol"; }; + void EvaluateProtocol(); static BOOL InvokeConnect(); diff --git a/src/Components/Modules/IPCPipe.cpp b/src/Components/Modules/IPCPipe.cpp new file mode 100644 index 00000000..92465873 --- /dev/null +++ b/src/Components/Modules/IPCPipe.cpp @@ -0,0 +1,251 @@ +#include "..\..\STDInclude.hpp" + +namespace Components +{ + Pipe* IPCPipe::ServerPipe = 0; + Pipe* IPCPipe::ClientPipe = 0; + +#pragma region Pipe + + Pipe::Pipe() : mType(IPCTYPE_NONE), ReconnectAttempt(0), hPipe(INVALID_HANDLE_VALUE), mThread(0), ConnectCallback(0), mThreadAttached(false) + { + this->Destroy(); + } + + Pipe::~Pipe() + { + this->Destroy(); + } + + bool Pipe::Connect(std::string name) + { + this->Destroy(); + + this->mType = IPCTYPE_CLIENT; + this->SetName(name); + + this->hPipe = CreateFile(this->PipeFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (INVALID_HANDLE_VALUE == this->hPipe) + { + Logger::Print("Failed to connect to the pipe\n"); + + if (this->ReconnectAttempt < IPC_MAX_RECONNECTS) + { + Logger::Print("Attempting to reconnect to the pipe.\n"); + this->ReconnectAttempt++; + Sleep(500); + + return this->Connect(name); + } + else + { + this->Destroy(); + return false; + } + } + + this->ReconnectAttempt = 0; + Logger::Print("Successfully connected to the pipe\n"); + + return true; + } + + bool Pipe::Create(std::string name) + { + this->Destroy(); + + this->mType = IPCTYPE_SERVER; + this->SetName(name); + + this->hPipe = CreateNamedPipe(this->PipeFile, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, sizeof(this->mPacket), sizeof(this->mPacket), NMPWAIT_USE_DEFAULT_WAIT, NULL); + + if (INVALID_HANDLE_VALUE != this->hPipe) + { + this->mThreadAttached = true; + this->mThread = new std::thread(this->ReceiveThread, this); + Logger::Print("Pipe successfully created\n"); + return true; + } + + Logger::Print("Failed to create the pipe\n"); + this->Destroy(); + return false; + } + + void Pipe::OnConnect(Pipe::Callback callback) + { + this->ConnectCallback = callback; + } + + void Pipe::SetCallback(std::string command, Pipe::PacketCallback callback) + { + this->PacketCallbacks[command] = callback; + } + + bool Pipe::Write(std::string command, std::string data) + { + if (this->mType != IPCTYPE_CLIENT || this->hPipe == INVALID_HANDLE_VALUE) return false; + + Pipe::Packet packet; + strcpy_s(packet.Command, command.data()); + strcpy_s(packet.Buffer, data.data()); + + DWORD cbBytes; + return (WriteFile(this->hPipe, &packet, sizeof(packet), &cbBytes, NULL) || GetLastError() == ERROR_IO_PENDING); + } + + void Pipe::Destroy() + { + //this->Type = IPCTYPE_NONE; + + //*this->PipeFile = 0; + //*this->PipeName = 0; + + if (INVALID_HANDLE_VALUE != this->hPipe) + { + if (this->mType == IPCTYPE_SERVER) DisconnectNamedPipe(this->hPipe); + + CloseHandle(this->hPipe); + Logger::Print("Disconnected from the pipe.\n"); + } + + this->mThreadAttached = false; + + if (this->mThread) + { + Logger::Print("Terminating pipe thread...\n"); + + this->mThread->join(); + + Logger::Print("Pipe thread terminated.\n"); + + delete this->mThread; + + this->mThread = 0; + } + } + + void Pipe::SetName(std::string name) + { + memset(this->PipeName, 0, sizeof(this->PipeName)); + memset(this->PipeFile, 0, sizeof(this->PipeFile)); + + strncpy_s(this->PipeName, name.c_str(), sizeof(this->PipeName)); + sprintf_s(this->PipeFile, sizeof(this->PipeFile), "\\\\.\\Pipe\\%s", this->PipeName); + } + + void Pipe::ReceiveThread(Pipe* pipe) + { + if (!pipe || pipe->mType != IPCTYPE_SERVER || pipe->hPipe == INVALID_HANDLE_VALUE) return; + + if (ConnectNamedPipe(pipe->hPipe, NULL) == FALSE) + { + Logger::Print("Failed to initialize pipe reading.\n"); + return; + } + + Logger::Print("Client connected to the pipe\n"); + if (pipe->ConnectCallback) pipe->ConnectCallback(); + + DWORD cbBytes; + + while (pipe->mThreadAttached && pipe->hPipe != INVALID_HANDLE_VALUE) + { + BOOL bResult = ReadFile(pipe->hPipe, &pipe->mPacket, sizeof(pipe->mPacket), &cbBytes, NULL); + + if (bResult && cbBytes) + { + if (pipe->PacketCallbacks.find(pipe->mPacket.Command) != pipe->PacketCallbacks.end()) + { + pipe->PacketCallbacks[pipe->mPacket.Command](pipe->mPacket.Buffer); + } + } + else if (pipe->mThreadAttached && pipe->hPipe != INVALID_HANDLE_VALUE) + { + Logger::Print("Failed to read from client through pipe\n"); + + DisconnectNamedPipe(pipe->hPipe); + ConnectNamedPipe(pipe->hPipe, NULL); + if (pipe->ConnectCallback) pipe->ConnectCallback(); + } + + ZeroMemory(&pipe->mPacket, sizeof(pipe->mPacket)); + } + } + +#pragma endregion + + // Callback to connect first instance's client pipe to the second instance's server pipe + void IPCPipe::ConnectClient() + { + if (Singleton::IsFirstInstance() && IPCPipe::ClientPipe) + { + IPCPipe::ClientPipe->Connect(IPC_PIPE_NAME_CLIENT); + } + } + + // Writes to the process on the other end of the pipe + bool IPCPipe::Write(std::string command, std::string data) + { + if (IPCPipe::ClientPipe) + { + return IPCPipe::ClientPipe->Write(command, data); + } + + return false; + } + + // Installs a callback for receiving commands from the process on the other end of the pipe + void IPCPipe::On(std::string command, Pipe::PacketCallback callback) + { + if (IPCPipe::ServerPipe) + { + IPCPipe::ServerPipe->SetCallback(command, callback); + } + } + + IPCPipe::IPCPipe() + { + // Server pipe + IPCPipe::ServerPipe = new Pipe(); + IPCPipe::ServerPipe->OnConnect(IPCPipe::ConnectClient); + IPCPipe::ServerPipe->Create((Singleton::IsFirstInstance() ? IPC_PIPE_NAME_SERVER : IPC_PIPE_NAME_CLIENT)); + + // Client pipe + IPCPipe::ClientPipe = new Pipe(); + + // Connect second instance's client pipe to first instance's server pipe + if (!Singleton::IsFirstInstance()) + { + IPCPipe::ClientPipe->Connect(IPC_PIPE_NAME_SERVER); + } + + IPCPipe::On("ping", [] (std::string data) + { + Logger::Print("Received ping form pipe, sending pong!\n"); + IPCPipe::Write("pong", data); + }); + + IPCPipe::On("pong", [] (std::string data) + { + Logger::Print("Received pong form pipe!\n"); + }); + + // Test pipe functionality by sending pings + Command::Add("ipcping", [] (Command::Params params) + { + Logger::Print("Sending ping to pipe!\n"); + IPCPipe::Write("ping", ""); + }); + } + + IPCPipe::~IPCPipe() + { + if (IPCPipe::ServerPipe) delete IPCPipe::ServerPipe; + if (IPCPipe::ClientPipe) delete IPCPipe::ClientPipe; + + IPCPipe::ServerPipe = 0; + IPCPipe::ClientPipe = 0; + } +} diff --git a/src/Components/Modules/IPCPipe.hpp b/src/Components/Modules/IPCPipe.hpp new file mode 100644 index 00000000..73ce18ab --- /dev/null +++ b/src/Components/Modules/IPCPipe.hpp @@ -0,0 +1,76 @@ +#define IPC_MAX_RECONNECTS 3 +#define IPC_COMMAND_SIZE 100 +#define IPC_BUFFER_SIZE 0x2000 + +#define IPC_PIPE_NAME_SERVER "IW4x-Server" +#define IPC_PIPE_NAME_CLIENT "IW4x-Client" + +namespace Components +{ + class Pipe + { + public: + struct Packet + { + char Command[IPC_COMMAND_SIZE]; + char Buffer[IPC_BUFFER_SIZE]; + }; + + enum Type + { + IPCTYPE_NONE, + IPCTYPE_SERVER, + IPCTYPE_CLIENT + }; + + typedef void(__cdecl* PacketCallback)(std::string data); + typedef void(__cdecl* Callback)(); + + Pipe(); + ~Pipe(); + + bool Connect(std::string name); + bool Create(std::string name); + + bool Write(std::string command, std::string data); + void SetCallback(std::string command, PacketCallback callback); + void OnConnect(Callback callback); + + private: + Callback ConnectCallback; + std::map PacketCallbacks; + + HANDLE hPipe; + std::thread* mThread; + bool mThreadAttached; + + Type mType; + Packet mPacket; + + char PipeName[MAX_PATH]; + char PipeFile[MAX_PATH]; + unsigned int ReconnectAttempt; + + void Destroy(); + void SetName(std::string name); + + static void ReceiveThread(Pipe* pipe); + }; + + class IPCPipe : public Component + { + public: + IPCPipe(); + ~IPCPipe(); + const char* GetName() { return "IPCPipe"; }; + + static bool Write(std::string command, std::string data); + static void On(std::string command, Pipe::PacketCallback callback); + + private: + static Pipe* ServerPipe; + static Pipe* ClientPipe; + + static void ConnectClient(); + }; +} diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index 17a125d8..d074084f 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -2,6 +2,9 @@ namespace Components { + std::mutex Logger::MessageMutex; + std::vector Logger::MessageQueue; + bool Logger::IsConsoleReady() { return (IsWindow(*(HWND*)0x64A3288) != FALSE); @@ -18,7 +21,14 @@ namespace Components if (Logger::IsConsoleReady()) { - Game::Com_Printf(0, "%s", buffer); + if (!Game::Sys_IsMainThread()) + { + Logger::EnqueueMessage(buffer); + } + else + { + Game::Com_PrintMessage(0, buffer, 0); + } } else { @@ -50,8 +60,43 @@ namespace Components Game::Com_Error(2, "%s", buffer); } + void Logger::Frame() + { + Logger::MessageMutex.lock(); + + for (unsigned int i = 0; i < Logger::MessageQueue.size(); i++) + { + if (Logger::IsConsoleReady()) + { + Game::Com_PrintMessage(0, Logger::MessageQueue[i].data(), 0); + } + else + { + OutputDebugStringA(Logger::MessageQueue[i].data()); + } + } + + Logger::MessageQueue.clear(); + Logger::MessageMutex.unlock(); + } + + void Logger::EnqueueMessage(std::string message) + { + Logger::MessageMutex.lock(); + Logger::MessageQueue.push_back(message); + Logger::MessageMutex.unlock(); + } + Logger::Logger() { + Renderer::OnFrame(Logger::Frame); // Client + Dedicated::OnFrame(Logger::Frame); // Dedi + } + Logger::~Logger() + { + Logger::MessageMutex.lock(); + Logger::MessageQueue.clear(); + Logger::MessageMutex.unlock(); } } diff --git a/src/Components/Modules/Logger.hpp b/src/Components/Modules/Logger.hpp index 81204dab..5aa02909 100644 --- a/src/Components/Modules/Logger.hpp +++ b/src/Components/Modules/Logger.hpp @@ -4,11 +4,19 @@ namespace Components { public: Logger(); + ~Logger(); const char* GetName() { return "Logger"; }; static void Print(const char* message, ...); static void Error(const char* message, ...); static void SoftError(const char* message, ...); static bool IsConsoleReady(); + + private: + static std::mutex MessageMutex; + static std::vector MessageQueue; + + static void Frame(); + static void EnqueueMessage(std::string message); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index a423530c..e11253e9 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -12,6 +12,7 @@ namespace Game Com_Error_t Com_Error = (Com_Error_t)0x4B22D0; Com_Printf_t Com_Printf = (Com_Printf_t)0x402500; + Com_PrintMessage_t Com_PrintMessage = (Com_PrintMessage_t)0x4AA830; Com_Milliseconds_t Com_Milliseconds = (Com_Milliseconds_t)0x42A660; Com_ParseExt_t Com_ParseExt = (Com_ParseExt_t)0x474D60; @@ -86,6 +87,8 @@ namespace Game Steam_JoinLobby_t Steam_JoinLobby = (Steam_JoinLobby_t)0x49CF70; + Sys_IsMainThread_t Sys_IsMainThread = (Sys_IsMainThread_t)0x4C37D0; + UI_AddMenuList_t UI_AddMenuList = (UI_AddMenuList_t)0x4533C0; UI_DrawHandlePic_t UI_DrawHandlePic = (UI_DrawHandlePic_t)0x4D0EA0; diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index a2a4aca0..4e0d952e 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -21,6 +21,9 @@ namespace Game typedef void(__cdecl * Com_Printf_t)(int, const char*, ...); extern Com_Printf_t Com_Printf; + typedef void(__cdecl * Com_PrintMessage_t)(int, const char*, char); + extern Com_PrintMessage_t Com_PrintMessage; + typedef int(__cdecl * Com_Milliseconds_t)(void); extern Com_Milliseconds_t Com_Milliseconds; @@ -187,6 +190,9 @@ namespace Game typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); extern Steam_JoinLobby_t Steam_JoinLobby; + typedef bool(__cdecl * Sys_IsMainThread_t)(); + extern Sys_IsMainThread_t Sys_IsMainThread; + typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 82285cb9..aa79ea94 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include