diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b08f4363..bf387d5d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -161,6 +161,11 @@ target_compile_definitions( ${PROJECT_NAME} PRIVATE endif() +target_include_directories( ${PROJECT_NAME} PRIVATE + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" +) + target_link_options( ${PROJECT_NAME} PRIVATE "/STACK:8000000" # Match game executable stack reserve size diff --git a/src/core/dllmain.cpp b/src/core/dllmain.cpp index f9efc6e2..45a71a15 100644 --- a/src/core/dllmain.cpp +++ b/src/core/dllmain.cpp @@ -95,7 +95,7 @@ void SDK_Init() SpdLog_Init(bAnsiColor); Show_Emblem(); - Winsock_Init(); // Initialize Winsock. + Winsock_Startup(); // Initialize Winsock. Systems_Init(); WinSys_Init(); @@ -103,6 +103,8 @@ void SDK_Init() Input_Init(); #endif // !DEDICATED + DirtySDK_Startup(); + GOOGLE_PROTOBUF_VERIFY_VERSION; curl_global_init(CURL_GLOBAL_ALL); lzham_enable_fail_exceptions(true); @@ -128,6 +130,7 @@ void SDK_Shutdown() Msg(eDLL_T::NONE, "GameSDK shutdown initiated\n"); curl_global_cleanup(); + DirtySDK_Shutdown(); #ifndef DEDICATED Input_Shutdown(); diff --git a/src/core/init.cpp b/src/core/init.cpp index 31b7a171..7f1bc28f 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -59,6 +59,9 @@ #include "engine/server/datablock_sender.h" #endif // !CLIENT_DLL #include "studiorender/studiorendercontext.h" +#ifndef CLIENT_DLL +#include "rtech/liveapi/liveapi.h" +#endif // !CLIENT_DLL #include "rtech/rstdlib.h" #include "rtech/rson.h" #include "rtech/async/asyncio.h" @@ -148,6 +151,11 @@ #include "windows/id3dx.h" #endif // !DEDICATED +#include "DirtySDK/dirtysock.h" +#include "DirtySDK/dirtysock/netconn.h" +#include "DirtySDK/proto/protossl.h" +#include "DirtySDK/proto/protowebsocket.h" + ///////////////////////////////////////////////////////////////////////////////////////////////// // @@ -275,6 +283,10 @@ void Systems_Shutdown() RCONClient()->Shutdown(); #endif // !SERVER_DLL +#ifndef CLIENT_DLL + LiveAPISystem()->Shutdown(); +#endif// !CLIENT_DLL + CFastTimer shutdownTimer; shutdownTimer.Start(); @@ -309,25 +321,51 @@ void Systems_Shutdown() // ///////////////////////////////////////////////////// -void Winsock_Init() +void Winsock_Startup() { WSAData wsaData{}; - int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + const int nError = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + if (nError != 0) { - Error(eDLL_T::COMMON, NO_ERROR, "%s: Failed to start Winsock: (%s)\n", + Error(eDLL_T::COMMON, 0, "%s: Windows Sockets API startup failure: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError())); } } + void Winsock_Shutdown() { - int nError = ::WSACleanup(); + const int nError = ::WSACleanup(); + if (nError != 0) { - Error(eDLL_T::COMMON, NO_ERROR, "%s: Failed to stop Winsock: (%s)\n", + Error(eDLL_T::COMMON, 0, "%s: Windows Sockets API shutdown failure: (%s)\n", __FUNCTION__, NET_ErrorString(WSAGetLastError())); } } + +void DirtySDK_Startup() +{ + const int32_t netConStartupRet = NetConnStartup("-servicename=sourcesdk"); + + if (netConStartupRet < 0) + { + Error(eDLL_T::COMMON, 0, "%s: Network connection module startup failure: (%i)\n", + __FUNCTION__, netConStartupRet); + } +} + +void DirtySDK_Shutdown() +{ + const int32_t netConShutdownRet = NetConnShutdown(0); + + if (netConShutdownRet < 0) + { + Error(eDLL_T::COMMON, 0, "%s: Network connection module shutdown failure: (%i)\n", + __FUNCTION__, netConShutdownRet); + } +} + void QuerySystemInfo() { #ifndef DEDICATED diff --git a/src/core/init.h b/src/core/init.h index 0647794e..bc2276fe 100644 --- a/src/core/init.h +++ b/src/core/init.h @@ -6,8 +6,10 @@ void SDK_Shutdown(); void Systems_Init(); void Systems_Shutdown(); -void Winsock_Init(); +void Winsock_Startup(); void Winsock_Shutdown(); +void DirtySDK_Startup(); +void DirtySDK_Shutdown(); void QuerySystemInfo(); void CheckCPU(); diff --git a/src/engine/host_state.cpp b/src/engine/host_state.cpp index 6908a929..09213da5 100644 --- a/src/engine/host_state.cpp +++ b/src/engine/host_state.cpp @@ -157,10 +157,6 @@ void CHostState::FrameUpdate(CHostState* pHostState, double flCurrentTime, float RCONClient()->RunFrame(); #endif // !DEDICATED -#ifndef CLIENT_DLL - LiveAPISystem()->RunFrame(); // TODO[ AMOS ]: move to server frame !!! -#endif // !CLIENT_DLL - // Disable "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable" #pragma warning(push) #pragma warning(disable : 4611) @@ -311,7 +307,7 @@ void CHostState::Setup(void) #endif // !DEDICATED #ifndef CLIENT_DLL - LiveAPISystem()->Init(); // TODO[ AMOS ]: move to server frame !!! + LiveAPISystem()->Init(); #endif // !CLIENT_DLL if (net_useRandomKey.GetBool()) diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 23866ba4..b5efc531 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -19,6 +19,7 @@ #include "ebisusdk/EbisuSDK.h" #include "public/edict.h" #include "pluginsystem/pluginsystem.h" +#include "rtech/liveapi/liveapi.h" //--------------------------------------------------------------------------------- // Console variables @@ -215,6 +216,7 @@ void CServer::BroadcastMessage(CNetMessage* const msg, const bool onlyActive, co void CServer::FrameJob(double flFrameTime, bool bRunOverlays, bool bUpdateFrame) { CServer__FrameJob(flFrameTime, bRunOverlays, bUpdateFrame); + LiveAPISystem()->RunFrame(); } //--------------------------------------------------------------------------------- diff --git a/src/rtech/CMakeLists.txt b/src/rtech/CMakeLists.txt index 45022df4..8be05513 100644 --- a/src/rtech/CMakeLists.txt +++ b/src/rtech/CMakeLists.txt @@ -47,8 +47,8 @@ add_sources( SOURCE_GROUP "Public" end_sources() target_include_directories( ${PROJECT_NAME} PRIVATE - "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" - "${THIRDPARTY_SOURCE_DIR}/ea/" + "${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/" + "${THIRDPARTY_SOURCE_DIR}/ea/" ) add_module( "lib" "rson" "vpc" ${FOLDER_CONTEXT} TRUE TRUE ) diff --git a/src/rtech/liveapi/liveapi.cpp b/src/rtech/liveapi/liveapi.cpp index 1d4eed05..515b5556 100644 --- a/src/rtech/liveapi/liveapi.cpp +++ b/src/rtech/liveapi/liveapi.cpp @@ -10,25 +10,48 @@ #include "DirtySDK/proto/protossl.h" #include "DirtySDK/proto/protowebsocket.h" +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void LiveAPI_ParamsChangedCallback(IConVar* var, const char* pOldValue) +{ + // TODO[ AMOS ]: latch this off to the server frame thread! + LiveAPISystem()->UpdateParams(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void LiveAPI_AddressChangedCallback(IConVar* var, const char* pOldValue) +{ + // TODO[ AMOS ]: latch this off to the server frame thread! + LiveAPISystem()->InstallAddressList(); +} + //----------------------------------------------------------------------------- // console variables //----------------------------------------------------------------------------- -static ConVar liveapi_enabled("liveapi_enabled", "1", FCVAR_RELEASE); -static ConVar liveapi_servers("liveapi_servers", "ws://127.0.0.1:7777" , FCVAR_RELEASE, "Comma separated list of addresses to connect to", "'ws://domain.suffix:port'"); +ConVar liveapi_enabled("liveapi_enabled", "1", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Enable LiveAPI functionality"); +ConVar liveapi_session_name("liveapi_session_name", "liveapi_session", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "LiveAPI session name to identify this connection"); -static ConVar liveapi_timeout("liveapi_timeout", "300", FCVAR_RELEASE, "WebSocket connection timeout in seconds"); -static ConVar liveapi_keepalive("liveapi_keepalive", "30", FCVAR_RELEASE, "Interval of time to send Pong to any connected server"); -static ConVar liveapi_lax_ssl("liveapi_lax_ssl", "1", FCVAR_RELEASE, "Skip SSL certificate validation for all WSS connections (allows the use of self-signed certificates)"); +// WebSocket core +static ConVar liveapi_use_websocket("liveapi_use_websocket", "1", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Use WebSocket to transmit LiveAPI events"); +static ConVar liveapi_servers("liveapi_servers", "ws://127.0.0.1:7777", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Comma separated list of addresses to connect to", &LiveAPI_AddressChangedCallback, "ws://domain.suffix:port"); -static ConVar liveapi_retry_count("liveapi_retry_count", "5", FCVAR_RELEASE, "Amount of times to retry connecting before marking the connection as unavailable"); -static ConVar liveapi_retry_time("liveapi_retry_time", "30", FCVAR_RELEASE, "Amount of time between each retry"); +// WebSocket connection base parameters +static ConVar liveapi_retry_count("liveapi_retry_count", "5", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Amount of times to retry connecting before marking the connection as unavailable", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_retry_time("liveapi_retry_time", "30", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Amount of time between each retry", &LiveAPI_ParamsChangedCallback); + +// WebSocket connection context parameters +static ConVar liveapi_timeout("liveapi_timeout", "300", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "WebSocket connection timeout in seconds", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_keepalive("liveapi_keepalive", "30", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Interval of time to send Pong to any connected server", &LiveAPI_ParamsChangedCallback); +static ConVar liveapi_lax_ssl("liveapi_lax_ssl", "1", FCVAR_RELEASE | FCVAR_SERVER_FRAME_THREAD, "Skip SSL certificate validation for all WSS connections (allows the use of self-signed certificates)", &LiveAPI_ParamsChangedCallback); //----------------------------------------------------------------------------- // constructors/destructors //----------------------------------------------------------------------------- LiveAPI::LiveAPI() { - initialized = false; } //----------------------------------------------------------------------------- @@ -39,26 +62,16 @@ void LiveAPI::Init() if (!liveapi_enabled.GetBool()) return; - NetConnStatus('open', 0, NULL, 0); - - const int32_t startupRet = NetConnStartup("-servicename=liveapi"); - - if (startupRet < 0) + if (liveapi_use_websocket.GetBool()) { - Error(eDLL_T::RTECH, 0, "LiveAPI: initialization failed! [%x]\n", startupRet); - return; + const char* initError = nullptr; + + if (!InitWebSocket(initError)) + { + Error(eDLL_T::RTECH, 0, "LiveAPI: WebSocket initialization failed! [%s]\n", initError); + return; + } } - - ProtoSSLStartup(); - const vector addresses = StringSplit(liveapi_servers.GetString(), ','); - - for (const string& addres : addresses) - { - const ConnContext_s conn(addres); - servers.push_back(conn); - } - - initialized = true; } //----------------------------------------------------------------------------- @@ -66,14 +79,53 @@ void LiveAPI::Init() //----------------------------------------------------------------------------- void LiveAPI::Shutdown() { - initialized = false; + webSocketSystem.Shutdown(); +} - for (ConnContext_s& conn : servers) - { - conn.Destroy(); - } +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void LiveAPI::CreateParams(CWebSocket::ConnParams_s& params) +{ + params.bufSize = LIVE_API_MAX_FRAME_BUFFER_SIZE; - servers.clear(); + params.retryTime = liveapi_retry_time.GetFloat(); + params.maxRetries = liveapi_retry_count.GetInt(); + + params.timeOut = liveapi_timeout.GetInt(); + params.keepAlive = liveapi_keepalive.GetInt(); + params.laxSSL = liveapi_lax_ssl.GetInt(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void LiveAPI::UpdateParams() +{ + CWebSocket::ConnParams_s connParams; + CreateParams(connParams); + + webSocketSystem.UpdateParams(connParams); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool LiveAPI::InitWebSocket(const char*& initError) +{ + CWebSocket::ConnParams_s connParams; + CreateParams(connParams); + + return webSocketSystem.Init(liveapi_servers.GetString(), connParams, initError); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void LiveAPI::InstallAddressList() +{ + webSocketSystem.ClearAll(); + webSocketSystem.UpdateAddressList(liveapi_servers.GetString()); } //----------------------------------------------------------------------------- @@ -84,70 +136,20 @@ void LiveAPI::RunFrame() if (!IsEnabled()) return; - const double queryTime = Plat_FloatTime(); - - for (ConnContext_s& conn : servers) - { - if (conn.webSocket) - ProtoWebSocketUpdate(conn.webSocket); - - if (conn.state == CS_CREATE || conn.state == CS_RETRY) - { - conn.Connect(queryTime); - continue; - } - - if (conn.state == CS_CONNECTED || conn.state == CS_LISTENING) - { - conn.Status(queryTime); - continue; - } - - if (conn.state == CS_DESTROYED) - { - if (conn.retryCount > liveapi_retry_count.GetInt()) - { - // All retry attempts have been used; mark unavailable for deletion - conn.state = CS_UNAVAIL; - } - else - { - // Mark as retry, this will recreate the socket and reattempt - // the connection - conn.state = CS_RETRY; - } - } - } - - DeleteUnavailable(); -} - -//----------------------------------------------------------------------------- -// Delete all server connections marked unavailable -//----------------------------------------------------------------------------- -void LiveAPI::DeleteUnavailable() -{ - servers.erase(std::remove_if(servers.begin(), servers.end(), - [](const ConnContext_s& conn) - { - return conn.state == CS_UNAVAIL; - } - ), servers.end()); + if (liveapi_use_websocket.GetBool()) + webSocketSystem.Update(); } //----------------------------------------------------------------------------- // Send an event to all sockets //----------------------------------------------------------------------------- -void LiveAPI::SendEvent(const char* const dataBuf, const int32_t dataSize) +void LiveAPI::LogEvent(const char* const dataBuf, const int32_t dataSize) { - for (ConnContext_s& conn : servers) - { - if (conn.state != CS_LISTENING) - continue; + if (!IsEnabled()) + return; - if (ProtoWebSocketSend(conn.webSocket, dataBuf, dataSize) < 0) - conn.Destroy(); // Reattempt the connection for this socket - } + if (liveapi_use_websocket.GetBool()) + webSocketSystem.SendData(dataBuf, dataSize); } //----------------------------------------------------------------------------- @@ -155,96 +157,7 @@ void LiveAPI::SendEvent(const char* const dataBuf, const int32_t dataSize) //----------------------------------------------------------------------------- bool LiveAPI::IsEnabled() const { - return initialized && liveapi_enabled.GetBool(); -} - -//----------------------------------------------------------------------------- -// Connect to a socket -//----------------------------------------------------------------------------- -bool LiveAPI::ConnContext_s::Connect(const double queryTime) -{ - const double retryTimeTotal = retryTime + liveapi_retry_time.GetFloat(); - const double currTime = Plat_FloatTime(); - - if (retryTimeTotal > currTime) - return false; // Still within retry period - - retryCount++; - webSocket = ProtoWebSocketCreate(LIVE_API_MAX_FRAME_BUFFER_SIZE); - - if (!webSocket) - { - state = CS_UNAVAIL; - return false; - } - - const int32_t timeOut = liveapi_timeout.GetInt(); - - if (timeOut > 0) - { - ProtoWebSocketControl(webSocket, 'time', timeOut, 0, NULL); - } - - const int32_t keepAlive = liveapi_keepalive.GetInt(); - - if (keepAlive > 0) - { - ProtoWebSocketControl(webSocket, 'keep', keepAlive, 0, NULL); - } - - ProtoWebSocketControl(webSocket, 'ncrt', liveapi_lax_ssl.GetInt(), 0, NULL); - ProtoWebSocketUpdate(webSocket); - - if (ProtoWebSocketConnect(webSocket, address.c_str()) != NULL) - { - // Failure - Destroy(); - return false; - } - - state = CS_CONNECTED; - retryTime = queryTime; - - return true; -} - -//----------------------------------------------------------------------------- -// Check the connection status and destroy if not connected (-1) -//----------------------------------------------------------------------------- -bool LiveAPI::ConnContext_s::Status(const double queryTime) -{ - const int32_t status = ProtoWebSocketStatus(webSocket, 'stat', NULL, 0); - - if (status == -1) - { - Destroy(); - retryTime = queryTime; - - return false; - } - else if (!status) - { - retryTime = queryTime; - return false; - } - - retryCount = 0; - state = CS_LISTENING; - - return true; -} - -//----------------------------------------------------------------------------- -// Destroy the connection -//----------------------------------------------------------------------------- -void LiveAPI::ConnContext_s::Destroy() -{ - ProtoWebSocketDisconnect(webSocket); - ProtoWebSocketUpdate(webSocket); - ProtoWebSocketDestroy(webSocket); - - webSocket = nullptr; - state = CS_DESTROYED; + return liveapi_enabled.GetBool(); } static LiveAPI s_liveApi; diff --git a/src/rtech/liveapi/liveapi.h b/src/rtech/liveapi/liveapi.h index ef16d20b..17ffc5e8 100644 --- a/src/rtech/liveapi/liveapi.h +++ b/src/rtech/liveapi/liveapi.h @@ -1,68 +1,38 @@ #ifndef RTECH_LIVEAPI_H #define RTECH_LIVEAPI_H +#include "tier2/websocket.h" #define LIVE_API_MAX_FRAME_BUFFER_SIZE 0x8000 +extern ConVar liveapi_enabled; +extern ConVar liveapi_session_name; + struct ProtoWebSocketRefT; typedef void (*LiveAPISendCallback_t)(ProtoWebSocketRefT* webSocket); class LiveAPI { public: - enum ConnState_e - { - CS_CREATE = 0, - - CS_CONNECTED, - CS_LISTENING, - - CS_DESTROYED, - - CS_RETRY, - CS_UNAVAIL - }; - - struct ConnContext_s - { - ConnContext_s(const string& addr) - { - webSocket = nullptr; - address = addr; - - state = CS_CREATE; - - retryCount = 0; - retryTime = 0; - } - - bool Connect(const double queryTime); - bool Process(const double queryTime); - - void Destroy(); - - ProtoWebSocketRefT* webSocket; - ConnState_e state; - - int retryCount; - double retryTime; - - string address; - }; LiveAPI(); void Init(); void Shutdown(); - void RunFrame(); - void DeleteUnavailable(); + void CreateParams(CWebSocket::ConnParams_s& params); + void UpdateParams(); + + bool InitWebSocket(const char*& initError); + void InstallAddressList(); + + void RunFrame(); + void LogEvent(const char* const dataBuf, const int32_t dataSize); - void SendEvent(const char* const dataBuf, const int32_t dataSize); bool IsEnabled() const; + inline bool WebSocketInitialized() const { return webSocketSystem.IsInitialized(); } private: - bool initialized; - vector servers; + CWebSocket webSocketSystem; }; LiveAPI* LiveAPISystem();