diff --git a/src/SvenBXT.cpp b/src/SvenBXT.cpp index b867ab4..ab2467d 100644 --- a/src/SvenBXT.cpp +++ b/src/SvenBXT.cpp @@ -295,6 +295,8 @@ void SvenBXT_Shutdown() SvenBXT_UnhookEngine(); SvenBXT_UnhookClient(); + + Interprocess::Shutdown(); g_bHasLoaded = false; } diff --git a/src/SvenBXT.h b/src/SvenBXT.h index 361b3dc..f594c86 100644 --- a/src/SvenBXT.h +++ b/src/SvenBXT.h @@ -14,6 +14,26 @@ #define SVENBXT_VERSION __DATE__ // maybe something else? :thinking: #define SVENBXT_GITHUB_URL "https://github.com/ScriptedSnark/SvenBXT" +// INTERPROCESS & SVENSPLIT +#define SVENSPLIT_PIPE_NAME "SvenBXT-SvenSplit" + +enum class MessageType : unsigned char +{ + TIME = 0x00, + EVENT = 0x01 +}; + +enum class EventType : unsigned char +{ + GAMEEND = 0x00, + MAPCHANGE = 0x01, + TIMER_RESET = 0x02, + TIMER_START = 0x03, + SS_ALEAPOFFAITH = 0x04 +}; + +#include "interprocess.h" + // WINDOWS #ifdef PLATFORM_WINDOWS #include diff --git a/src/cl_dll/cdll_int.cpp b/src/cl_dll/cdll_int.cpp index 2e6932a..66389e9 100644 --- a/src/cl_dll/cdll_int.cpp +++ b/src/cl_dll/cdll_int.cpp @@ -90,6 +90,9 @@ void CL_Initialize() CreateHook(Client, V_CalcRefdef); CreateHook(Client, HUD_VidInit); CreateHook(Client, HUD_Redraw); + + TRACE("Initializing interprocesses...\n"); + Interprocess::Initialize(); TRACE("Initializing HUD...\n"); gBXTHud.Init(); diff --git a/src/cl_dll/hud_timer.cpp b/src/cl_dll/hud_timer.cpp index 782e28f..fbc0322 100644 --- a/src/cl_dll/hud_timer.cpp +++ b/src/cl_dll/hud_timer.cpp @@ -50,6 +50,7 @@ int __MsgFunc_BXTTimer(const char* pszName, int iSize, void* pbuf) bool stop = READ_BYTE(); g_RTATimer.SyncTimer(time, stop); + Interprocess::WriteTime(Interprocess::GetTime()); return 0; } @@ -65,6 +66,9 @@ int CHudTimer::Init() hud_timer_anchor = CVAR_CREATE("sbxt_hud_timer_anchor", "0.0 0.5", 0); hud_timer_offset = CVAR_CREATE("sbxt_hud_timer_offset", "", 0); + svensplit_time_update_frequency = CVAR_CREATE("_sbxt_svensplit_time_update_frequency", "41", 0); + interprocess_enable = CVAR_CREATE("sbxt_interprocess_enable", "0", 0); + HOOK_COMMAND("sbxt_timer_start", TimerStart); HOOK_COMMAND("sbxt_timer_stop", TimerStop); HOOK_COMMAND("sbxt_timer_reset", TimerReset); @@ -81,6 +85,8 @@ int CHudTimer::VidInit() int CHudTimer::Draw(float time) { + Interprocess::WriteTime(Interprocess::GetTime()); + if (!hud_timer->value) return 0; @@ -91,7 +97,7 @@ int CHudTimer::Draw(float time) int minutes = g_RTATimer.GetMinutes(); int seconds = g_RTATimer.GetSeconds(); int milliseconds = g_RTATimer.GetMilliseconds(); - + if (hud_timer->value == 1) { char rta_timer[64]; @@ -135,6 +141,8 @@ void CHudTimer::TimerStart() g_RTATimer.StartTimer(); //g_IGTTimer.StartTimer(); + Interprocess::WriteTimerStart(Interprocess::GetTime()); + if (UTIL_IsHost()) { SV_SyncTimer(); @@ -157,6 +165,8 @@ void CHudTimer::TimerReset() g_RTATimer.ResetTimer(); //g_IGTTimer.ResetTimer(); + Interprocess::WriteTimerReset(Interprocess::GetTime()); + if (UTIL_IsHost()) { SV_SyncTimer(); diff --git a/src/cl_dll/hud_timer.h b/src/cl_dll/hud_timer.h index 88f1ffd..27083db 100644 --- a/src/cl_dll/hud_timer.h +++ b/src/cl_dll/hud_timer.h @@ -27,6 +27,9 @@ class CHudTimer: public CBXTHudBase bool ShouldSync() { return (hud_timer_serversync->value && !UTIL_IsHost()); } bool IsInILMode() { return hud_timer_il_mode->value; }; + cvar_t* svensplit_time_update_frequency; + cvar_t* interprocess_enable; + private: cvar_t* hud_timer_serversync; cvar_t* hud_timer_il_mode; diff --git a/src/dlls/server.cpp b/src/dlls/server.cpp index 0ac69bf..1da605b 100644 --- a/src/dlls/server.cpp +++ b/src/dlls/server.cpp @@ -11,6 +11,8 @@ void FASTCALL HOOKED_CNihilanth_DyingThink(void* thisptr) { g_lpHUDTimer->TimerStop(); + Interprocess::WriteGameEnd(Interprocess::GetTime()); + ORIG_CNihilanth_DyingThink(thisptr); } diff --git a/src/interprocess.cpp b/src/interprocess.cpp new file mode 100644 index 0000000..29ef3b9 --- /dev/null +++ b/src/interprocess.cpp @@ -0,0 +1,235 @@ +#include "interprocess.h" +#include +#include "Utils.h" +#include "SvenBXT.h" +#include "cl_dll/hud_timer.h" + +namespace Interprocess +{ + static HANDLE pipe_SvenSplit = INVALID_HANDLE_VALUE; + static OVERLAPPED overlapped; + static bool writing_to_pipe; + + static void InitSvenSplitPipe() + { + pipe_SvenSplit = CreateNamedPipe( + "\\\\.\\pipe\\" SVENSPLIT_PIPE_NAME, + PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS, + 1, + 256 * 1000, + 0, + 0, + NULL); + if (pipe_SvenSplit == INVALID_HANDLE_VALUE) + { + Sys_Printf("Error opening the SvenSplit pipe: %d\n", GetLastError()); + Sys_Printf("SvenSplit integration is not available.\n"); + return; + } + Sys_Printf("Opened the SvenSplit pipe.\n"); + + std::memset(&overlapped, 0, sizeof(overlapped)); + overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + if (overlapped.hEvent == NULL) + { + Sys_Printf("Error creating an event for overlapped: %d. Closing the SvenSplit pipe.\n", GetLastError()); + Sys_Printf("SvenSplit integration is not available.\n"); + CloseHandle(pipe_SvenSplit); + pipe_SvenSplit = INVALID_HANDLE_VALUE; + } + } + + void Initialize() + { + InitSvenSplitPipe(); + } + + static void ShutdownSvenSplitPipe() + { + if (pipe_SvenSplit != INVALID_HANDLE_VALUE) + { + CloseHandle(pipe_SvenSplit); + Sys_Printf("Closed the SvenSplit pipe.\n"); + } + pipe_SvenSplit = INVALID_HANDLE_VALUE; + + CloseHandle(overlapped.hEvent); + std::memset(&overlapped, 0, sizeof(overlapped)); + } + + void Shutdown() + { + ShutdownSvenSplitPipe(); + } + + static void WriteSvenSplit(const std::vector& data) + { + if (pipe_SvenSplit == INVALID_HANDLE_VALUE) + return; + + if (writing_to_pipe) + { + if (WaitForSingleObject(overlapped.hEvent, INFINITE) != WAIT_OBJECT_0) + { + // Some weird error? + Sys_Printf("WaitForSingleObject failed with %d.\n", GetLastError()); + DisconnectNamedPipe(pipe_SvenSplit); + return WriteSvenSplit(data); + } + writing_to_pipe = false; + } + + if (!ConnectNamedPipe(pipe_SvenSplit, &overlapped)) + { + auto err = GetLastError(); + if (err == ERROR_NO_DATA) + { + // Client has disconnected. + DisconnectNamedPipe(pipe_SvenSplit); + return WriteSvenSplit(data); + } + else if (err == ERROR_IO_PENDING) + { + // Waiting for someone to connect. + return; + } + else if (err != ERROR_PIPE_CONNECTED) + { + // Some weird error with pipe? + // Try remaking it. + Sys_Printf("ConnectNamedPipe failed with %d.\n", err); + ShutdownSvenSplitPipe(); + InitSvenSplitPipe(); + return WriteSvenSplit(data); + } + } + + if (!WriteFile(pipe_SvenSplit, data.data(), data.size(), NULL, &overlapped)) + { + auto err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + // Started writing. + writing_to_pipe = true; + return; + } + else + { + Sys_Printf("WriteFile failed with %d.\n", err); + DisconnectNamedPipe(pipe_SvenSplit); + return; + } + } + } + + static size_t AddTimeToBuffer(char* buf, const Time& time) + { + std::memcpy(buf, &time.hours, sizeof(time.hours)); + std::memcpy(buf + sizeof(time.hours), &time.minutes, sizeof(time.minutes)); + std::memcpy(buf + sizeof(time.hours) + sizeof(time.minutes), &time.seconds, sizeof(time.seconds)); + std::memcpy(buf + sizeof(time.hours) + sizeof(time.minutes) + sizeof(time.seconds), &time.milliseconds, sizeof(time.milliseconds)); + return sizeof(time.hours) + sizeof(time.minutes) + sizeof(time.seconds) + sizeof(time.milliseconds); + } + + void WriteTime(const Time& time) + { + if (!g_lpHUDTimer->interprocess_enable->value) + return; + + std::vector buf(10); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::TIME); + AddTimeToBuffer(buf.data() + 2, time); + + if (g_lpHUDTimer->svensplit_time_update_frequency->value > 0.0f) + { + static auto last_time = std::chrono::steady_clock::now() - std::chrono::milliseconds(static_cast(1000 / g_lpHUDTimer->svensplit_time_update_frequency->value) + 1); + auto now = std::chrono::steady_clock::now(); + if (now >= last_time + std::chrono::milliseconds(static_cast(1000 / g_lpHUDTimer->svensplit_time_update_frequency->value))) + { + WriteSvenSplit(buf); + last_time = now; + } + } + else + { + WriteSvenSplit(buf); + } + } + + void WriteGameEnd(const Time& time) + { + std::vector buf(11); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::EVENT); + buf[2] = static_cast(EventType::GAMEEND); + AddTimeToBuffer(buf.data() + 3, time); + + WriteSvenSplit(buf); + } + + void WriteMapChange(const Time& time, const std::string& map) + { + int32_t size = static_cast(map.size()); + + std::vector buf(15 + size); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::EVENT); + buf[2] = static_cast(EventType::MAPCHANGE); + auto time_size = AddTimeToBuffer(buf.data() + 3, time); + + std::memcpy(buf.data() + 3 + time_size, &size, sizeof(size)); + std::memcpy(buf.data() + 3 + time_size + 4, map.data(), size); + + WriteSvenSplit(buf); + } + + void WriteTimerReset(const Time& time) + { + std::vector buf(11); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::EVENT); + buf[2] = static_cast(EventType::TIMER_RESET); + AddTimeToBuffer(buf.data() + 3, time); + + WriteSvenSplit(buf); + } + + void WriteTimerStart(const Time& time) + { + std::vector buf(11); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::EVENT); + buf[2] = static_cast(EventType::TIMER_START); + AddTimeToBuffer(buf.data() + 3, time); + + WriteSvenSplit(buf); + } + + void WriteSSALeapOfFaith(const Time& time) + { + std::vector buf(11); + buf[0] = static_cast(buf.size()); + buf[1] = static_cast(MessageType::EVENT); + buf[2] = static_cast(EventType::SS_ALEAPOFFAITH); + AddTimeToBuffer(buf.data() + 3, time); + + WriteSvenSplit(buf); + } + + Interprocess::Time GetTime() + { + int hours = g_RTATimer.GetHours(); + int minutes = g_RTATimer.GetMinutes(); + int seconds = g_RTATimer.GetSeconds(); + int milliseconds = g_RTATimer.GetMilliseconds(); + + return Interprocess::Time{ + static_cast(hours), + static_cast(minutes), + static_cast(seconds), + static_cast(milliseconds) + }; + } +} \ No newline at end of file diff --git a/src/interprocess.h b/src/interprocess.h new file mode 100644 index 0000000..b9703d9 --- /dev/null +++ b/src/interprocess.h @@ -0,0 +1,30 @@ +#ifndef INTERPROCESS_H_INCLUDED +#define INTERPROCESS_H_INCLUDED + +#include +#include + +namespace Interprocess +{ + struct Time + { + uint32_t hours; + uint8_t minutes; + uint8_t seconds; + uint16_t milliseconds; + }; + + void Initialize(); + void Shutdown(); + + void WriteTime(const Time& time); + + void WriteGameEnd(const Time& time); + void WriteMapChange(const Time& time, const std::string& map); + void WriteTimerReset(const Time& time); + void WriteTimerStart(const Time& time); + void WriteSSALeapOfFaith(const Time& time); + Time GetTime(); +} + +#endif // INTERPROCESS_H_INCLUDED \ No newline at end of file