From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- engine/ccs.cpp | 1620 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1620 insertions(+) create mode 100644 engine/ccs.cpp (limited to 'engine/ccs.cpp') diff --git a/engine/ccs.cpp b/engine/ccs.cpp new file mode 100644 index 0000000..890e5b0 --- /dev/null +++ b/engine/ccs.cpp @@ -0,0 +1,1620 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// ccs.cpp - "con command server" +// Not to be confused with the game's server - this is a completely different thing that allows multiple game clients to connect to a single client, +// so con commands can be sent simultaneously to multiple running game clients, screen captures can be sent in real-time to a single client for comparison purposes, +// and all convar's can be diff'd between all connected clients and the server. +// The server portion of this code needs socketlib, which hasn't been ported to Linux yet, and shouldn't be shipped because it could be a security risk +// (it's only intended for primarily graphics debugging and detailed comparisons of GL vs. D3D9). + +#include "host_state.h" + +//#define CON_COMMAND_SERVER_SUPPORT + +#include "tier2/tier2.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" + +#ifdef CON_COMMAND_SERVER_SUPPORT +#include +#include "socketlib/socketlib.h" +#define MINIZ_NO_ARCHIVE_APIS +#include "../../thirdparty/miniz/miniz.c" +#include "../../thirdparty/miniz/simple_bitmap.h" +#define STBI_NO_STDIO +#include "../../thirdparty/stb_image/stb_image.c" +#include "ivideomode.h" +#endif + +#include "cmd.h" +#include "filesystem.h" +#include "render.h" +#include "icliententitylist.h" +#include "client.h" +#include "icliententity.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +#ifdef STAGING_ONLY +CON_COMMAND_F( ccs_write_convars, "Write all convars to file.", FCVAR_CHEAT ) +{ + FILE* pFile = fopen( "convars.txt", "w" ); + if ( !pFile ) + { + ConMsg( "Unable to open convars.txt\n" ); + return; + } + + ICvar::Iterator iter( g_pCVar ); + for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() ) + { + ConCommandBase *var = iter.Get(); + + ConVar *pConVar = dynamic_cast< ConVar* >( var ); + if ( !pConVar ) + continue; + + const char *pName = pConVar->GetName(); + const char *pVal = pConVar->GetString(); + if ( ( !pName ) || ( !pVal ) ) + continue; + + fprintf( pFile, "%s=%s\n", pName, pVal ); + } + fclose( pFile ); + ConMsg( "Wrote all convars to convars.txt\n" ); +} +#endif + +//----------------------------------------------------------------------------- + +#ifdef CON_COMMAND_SERVER_SUPPORT + +class frame_buf_window +{ + friend LRESULT CALLBACK static_window_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + + static LRESULT CALLBACK static_window_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + frame_buf_window* pApp = reinterpret_cast(GetWindowLong(hWnd, 0)); + if (!pApp) + pApp = frame_buf_window::m_pCur_app; + + Assert(pApp); + + return pApp->window_proc(hWnd, message, wParam, lParam); + } + +public: + frame_buf_window(const char* pTitle = "frame_buf_window", int width = 640, int height = 480, int scaleX = 1, int scaleY = 1); + + virtual ~frame_buf_window(); + + // Return true if you handle the window message. + typedef bool (*user_window_proc_ptr_t)(LRESULT& hres, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, void *pData); + void set_window_proc_callback(user_window_proc_ptr_t windowProcPtr, void *pData) { m_pWindow_proc = windowProcPtr; m_pWindow_proc_data = pData; } + + int width(void) const { return m_width; } + int height(void) const { return m_height; } + + void update(void); + + void close(void); + + const simple_bgr_bitmap& frameBuffer(void) const { return m_frame_buffer; } + + simple_bgr_bitmap& frameBuffer(void) { return m_frame_buffer; } + +private: + int m_width, m_height; + int m_orig_width, m_orig_height; + int m_orig_x, m_orig_y; + int m_scale_x, m_scale_y; + WNDCLASS m_window_class; + HWND m_window; + char m_bitmap_info[16 + sizeof(BITMAPINFO)]; + BITMAPINFO* m_pBitmap_hdr; + HDC m_windowHDC; + simple_bgr_bitmap m_frame_buffer; + static frame_buf_window* m_pCur_app; + + user_window_proc_ptr_t m_pWindow_proc; + void *m_pWindow_proc_data; + + void create_window(const char* pTitle); + + void create_bitmap(void); + + LRESULT window_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +}; // class frame_buf_window + + +frame_buf_window* frame_buf_window::m_pCur_app; + +frame_buf_window::frame_buf_window(const char* pTitle, int width, int height, int scaleX, int scaleY) : + m_width(width), + m_height(height), + m_orig_width(0), + m_orig_height(0), + m_orig_x(0), + m_orig_y(0), + m_window(0), + m_pBitmap_hdr(NULL), + m_windowHDC(0), + m_pWindow_proc(NULL), + m_pWindow_proc_data(NULL) +{ + m_scale_x = scaleX; + m_scale_y = scaleY; + + create_window(pTitle); + + create_bitmap(); +} + +frame_buf_window::~frame_buf_window() +{ + close(); +} + +void frame_buf_window::update(void) +{ + if ( m_window ) + { + InvalidateRect(m_window, NULL, FALSE); + + SendMessage(m_window, WM_PAINT, 0, 0); + + MSG msg; + while (PeekMessage(&msg, m_window, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + //Sleep(0); + } +} + +void frame_buf_window::close(void) +{ + m_frame_buffer.clear(); + + if (m_window) + { + ReleaseDC(m_window, m_windowHDC); + DestroyWindow(m_window); + m_window = 0; + m_windowHDC = 0; + } +} + +void frame_buf_window::create_window(const char* pTitle) +{ + memset( &m_window_class, 0, sizeof( m_window_class ) ); + m_window_class.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW; + m_window_class.lpfnWndProc = static_window_proc; + m_window_class.cbWndExtra = sizeof(DWORD); + m_window_class.hCursor = LoadCursor(0, IDC_ARROW); + m_window_class.lpszClassName = pTitle; + m_window_class.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); //GetStockObject(NULL_BRUSH); + RegisterClass(&m_window_class); + + RECT rect; + rect.left = rect.top = 0; + rect.right = m_width * m_scale_x; + rect.bottom = m_height * m_scale_y; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0); + rect.right -= rect.left; + rect.bottom -= rect.top; + + m_orig_x = (GetSystemMetrics(SM_CXSCREEN) - rect.right) / 2; + m_orig_y = (GetSystemMetrics(SM_CYSCREEN) - rect.bottom) / 2; + + m_orig_width = rect.right; + m_orig_height = rect.bottom; + + m_pCur_app = this; + + m_window = CreateWindowEx( + 0, pTitle, pTitle, + WS_OVERLAPPEDWINDOW, + m_orig_x, m_orig_y, rect.right, rect.bottom, 0, 0, 0, 0); + + SetWindowLong(m_window, 0, reinterpret_cast(this)); + + m_pCur_app = NULL; + + ShowWindow(m_window, SW_NORMAL); +} + +void frame_buf_window::create_bitmap(void) +{ + memset( &m_bitmap_info, 0, sizeof( m_bitmap_info ) ); + + m_pBitmap_hdr = reinterpret_cast(&m_bitmap_info); + m_pBitmap_hdr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + m_pBitmap_hdr->bmiHeader.biWidth = m_width; + m_pBitmap_hdr->bmiHeader.biHeight = -m_height; + m_pBitmap_hdr->bmiHeader.biBitCount = 24; + m_pBitmap_hdr->bmiHeader.biPlanes = 1; + m_pBitmap_hdr->bmiHeader.biCompression = BI_RGB;//BI_BITFIELDS; + // Only really needed for 32bpp BI_BITFIELDS + reinterpret_cast(m_pBitmap_hdr->bmiColors)[0] = 0x00FF0000; + reinterpret_cast(m_pBitmap_hdr->bmiColors)[1] = 0x0000FF00; + reinterpret_cast(m_pBitmap_hdr->bmiColors)[2] = 0x000000FF; + + m_windowHDC = GetDC(m_window); + + m_frame_buffer.init( m_width, m_height ); + m_frame_buffer.cls( 30, 30, 30 ); + //m_frame_buffer.draw_text( 50, 200, 2, 255, 127, 128, "This is a test!" ); +} + +LRESULT frame_buf_window::window_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (m_pWindow_proc) + { + LRESULT lres; + bool status = m_pWindow_proc(lres, hWnd, message, wParam, lParam, m_pWindow_proc_data); + if (status) + return lres; + } + + switch (message) + { + case WM_PAINT: + { + if (m_frame_buffer.is_valid()) + { + RECT window_size; + GetClientRect(hWnd, &window_size); + + StretchDIBits(m_windowHDC, + 0, 0, window_size.right, window_size.bottom, + 0, 0, m_width, m_height, + m_frame_buffer.get_ptr(), m_pBitmap_hdr, + DIB_RGB_COLORS, SRCCOPY); + + ValidateRect(hWnd, NULL); + } + break; + } + case WM_KEYDOWN: + { + const int cEscCode = 27; + if (cEscCode != (wParam & 0xFF)) + break; + } + case WM_CLOSE: + { + close(); + + break; + } + } + + return DefWindowProc( hWnd, message, wParam, lParam ); +} + +class CConCommandServerProtocolTypes +{ +public: + enum + { + cProtocolPort = 3779, + cMaxClients = 4 + }; + + struct PacketHeader_t + { + uint16 m_nID; + uint16 m_nType; + uint32 m_nTotalSize; + }; + + struct SetCameraPosPacket_t : PacketHeader_t + { + Vector m_Pos; + QAngle m_Angle; + }; + + struct ScreenshotPacket_t : PacketHeader_t + { + uint m_nScreenshotID; + char m_szFilename[256]; + }; + + enum + { + cPacketHeaderID = 0x1234, + cPacketHeaderSize = sizeof( PacketHeader_t ), + }; + + enum PacketTypes_t + { + cPacketTypeMessage = 0, + cPacketTypeCommand = 1, + cPacketTypeScreenshotRequest = 2, + cPacketTypeScreenshotReply = 3, + cPacketTypeSetCameraPos = 4, + cPacketTypeConVarDumpRequest = 5, + cPacketTypeConVarDumpReply = 6, + + cPackerTypeTotalMessages + }; +}; + +struct DumpedConVar_t +{ + CUtlString m_Name; + CUtlString m_Value; + + bool operator< (const DumpedConVar_t & rhs ) const { return V_strcmp( m_Name.Get(), rhs.m_Name.Get() ) < 0; } +}; +typedef CUtlVector DumpedConVarVector_t; + +static void DumpConVars( DumpedConVarVector_t &conVars ) +{ + ICvar::Iterator iter( g_pCVar ); + for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() ) + { + ConCommandBase *var = iter.Get(); + + ConVar *pConVar = dynamic_cast< ConVar* >( var ); + if ( !pConVar ) + continue; + + const char *pName = pConVar->GetName(); + const char *pVal = pConVar->GetString(); + if ( ( !pName ) || ( !pVal ) ) + continue; + + int nIndex = conVars.AddToTail(); + conVars[nIndex].m_Name.Set( pName ); + conVars[nIndex].m_Value.Set( pVal ); + } +} + +class CConCommandConnection : public CConCommandServerProtocolTypes +{ +public: + CConCommandConnection() : + m_pSocket( NULL ), + m_bDeleteSocket( false ), + m_nEndpointIndex( -1 ), + m_bClientFlag( false ), + m_bReceivedNewCameraPos( false ), + m_nSendBufOfs( 0 ), + m_bHasNewScreenshot( false ), + m_nScreenshotID( 0 ) + { + memset( &m_NewCameraPos, 0, sizeof( m_NewCameraPos ) ); + } + + ~CConCommandConnection() + { + Deinit(); + } + + bool IsConnected() const { return m_nEndpointIndex >= 0; } + + bool Init( const char *pAddress ) + { + Deinit(); + + m_nEndpointIndex = 0; + m_bClientFlag = true; + + m_RecvBuf.EnsureCapacity( 4096 ); + m_SendBuf.EnsureCapacity( 4096 ); + m_RecvBuf.SetCountNonDestructively( 0 ); + m_SendBuf.SetCountNonDestructively( 0 ); + m_nSendBufOfs = 0; + + m_pSocket = new CSocketConnection; + m_bDeleteSocket = true; + m_pSocket->Init( CT_CLIENT, SP_TCP ); + + SocketErrorCode_t err = m_pSocket->ConnectToServer( pAddress, cProtocolPort ); + if ( err != SOCKET_SUCCESS ) + { + Warning( "CONCMDSRV: Failed connecting to con cmd server \"%s\"!\n", pAddress ); + + Deinit(); + + return false; + } + + uint n = 0; + while ( m_pSocket->GetEndpointSocketState( 0 ) == SSTATE_CONNECTION_IN_PROGRESS ) + { + bool bIsConnected = false; + err = m_pSocket->PollClientConnectionState( &bIsConnected ); + if ( err != SOCKET_SUCCESS ) + { + Warning( "CONCMDSRV: Failed connecting to con cmd server \"%s\"!\n", pAddress ); + + Deinit(); + + return false; + } + + if ( bIsConnected ) + break; + + ThreadSleep( 10 ); + + if ( ++n >= 500 ) + { + Warning( "CONCMDSRV: Failed connecting to con cmd server \"%s\"!\n", pAddress ); + + Deinit(); + + return false; + } + } + + if ( m_pSocket->GetEndpointSocketState( 0 ) != SSTATE_CONNECTED ) + { + Warning( "CONCMDSRV: Failed connecting to con cmd server \"%s\"!\n", pAddress ); + + Deinit(); + + return false; + } + + int nFlag = 1; + m_pSocket->SetSocketOpt( 0, IPPROTO_TCP, TCP_NODELAY, &nFlag, sizeof( int ) ); + + return TickConnection(); + } + + bool Init( CSocketConnection *pSocket, int nEndpointIndex, bool bClientFlag ) + { + Deinit(); + + int nFlag = 1; + pSocket->SetSocketOpt( nEndpointIndex, IPPROTO_TCP, TCP_NODELAY, &nFlag, sizeof( int ) ); + + m_pSocket = pSocket; + m_bDeleteSocket = false; + m_nEndpointIndex = nEndpointIndex; + m_bClientFlag = bClientFlag; + + m_RecvBuf.EnsureCapacity( 4096 ); + m_SendBuf.EnsureCapacity( 4096 ); + + m_RecvBuf.SetCountNonDestructively( 0 ); + m_SendBuf.SetCountNonDestructively( 0 ); + m_nSendBufOfs = 0; + + return TickConnection(); + } + + void Deinit() + { + if ( m_nEndpointIndex < 0 ) + return; + + ConMsg( "CONCMDSRV: Disconnecting endpoint %i\n", m_nEndpointIndex ); + + if ( m_pSocket ) + { + m_pSocket->ResetEndpoint( m_nEndpointIndex ); + + if ( m_bDeleteSocket ) + { + m_pSocket->Cleanup(); + delete m_pSocket; + } + m_pSocket = NULL; + } + + m_nEndpointIndex = -1; + m_bDeleteSocket = false; + m_bClientFlag = false; + + m_RecvBuf.SetCountNonDestructively( 0 ); + m_SendBuf.SetCountNonDestructively( 0 ); + m_nSendBufOfs = 0; + + m_bHasNewScreenshot = false; + m_nScreenshotID = 0; + m_screenshot.clear(); + + m_bReceivedNewCameraPos = false; + memset( &m_NewCameraPos, 0, sizeof( m_NewCameraPos ) ); + } + + bool SendData( const void * pPacket, uint nSize ) + { + if ( m_nEndpointIndex < 0 ) + return false; + + m_SendBuf.AddMultipleToTail( nSize, static_cast< const uint8 * >( pPacket ) ); + return TickConnectionSend(); + } + + bool TickConnectionRecv( ) + { + for ( ; ; ) + { + uint8 buf[4096]; + int nBytesRead = 0; + SocketErrorCode_t nReadErr = m_pSocket->ReadFromEndpoint( m_nEndpointIndex, buf, sizeof( buf ), &nBytesRead ); + if ( nReadErr == SOCKET_ERR_READ_OPERATION_WOULD_BLOCK ) + break; + else if ( nReadErr != SOCKET_SUCCESS ) + { + Warning( "CONCMDSRV: Failed receiving data from client %i\n", m_nEndpointIndex ); + Deinit(); + return false; + } + + m_RecvBuf.AddMultipleToTail( nBytesRead, buf ); + if ( m_RecvBuf.Count() >= cPacketHeaderSize ) + { + if ( !ProcessMessages() ) + { + Warning( "CONCMDSRV: Failed processing messages from client %i\n", m_nEndpointIndex ); + Deinit(); + return false; + } + } + } + + return true; + } + + bool TickConnectionSend( ) + { + while ( m_nSendBufOfs < m_SendBuf.Count() ) + { + int nBytesWritten = 0; + SocketErrorCode_t nWriteErr = m_pSocket->WriteToEndpoint( m_nEndpointIndex, &m_SendBuf[ m_nSendBufOfs ], m_SendBuf.Count() - m_nSendBufOfs, &nBytesWritten ); + if ( nWriteErr == SOCKET_ERR_WRITE_OPERATION_WOULD_BLOCK ) + break; + else if ( nWriteErr != SOCKET_SUCCESS ) + { + Warning( "CONCMDSRV: Failed sending data to client %i\n", m_nEndpointIndex ); + Deinit(); + return false; + } + + m_nSendBufOfs += nBytesWritten; + if ( m_nSendBufOfs == m_SendBuf.Count() ) + { + m_SendBuf.SetCountNonDestructively( 0 ); + m_nSendBufOfs = 0; + break; + } + } + + return true; + } + + bool TickConnection() + { + if ( !IsConnected() ) + return false; + + if ( !TickConnectionSend() ) + return false; + if ( !TickConnectionRecv() ) + return false; + + if ( m_bReceivedNewCameraPos ) + { + m_bReceivedNewCameraPos = false; + + char buf[256]; + //V_snprintf( buf, sizeof( buf ), "setpos_exact %f %f %f;setang_exact %f %f %f\n", m_NewCameraPos.m_Pos.x, m_NewCameraPos.m_Pos.y, m_NewCameraPos.m_Pos.z, m_NewCameraPos.m_Angle.x, m_NewCameraPos.m_Angle.y, m_NewCameraPos.m_Angle.z ); + V_snprintf( buf, sizeof( buf ), "setpos_exact 0x%X 0x%X 0x%X;setang_exact 0x%X 0x%X 0x%X\n", *(DWORD*)&m_NewCameraPos.m_Pos.x, *(DWORD*)&m_NewCameraPos.m_Pos.y, *(DWORD*)&m_NewCameraPos.m_Pos.z, *(DWORD*)&m_NewCameraPos.m_Angle.x, *(DWORD*)&m_NewCameraPos.m_Angle.y, *(DWORD*)&m_NewCameraPos.m_Angle.z ); + + Cbuf_AddText( buf ); + Cbuf_Execute(); + } + + return true; + } + + bool HasNewScreenshotFlag() const { return m_bHasNewScreenshot; } + void ClearNewScreenshotFlag() { m_bHasNewScreenshot = false; } + uint GetScreenshotID() const { return m_nScreenshotID; } + simple_bitmap &GetScreenshot() { return m_screenshot; } + + bool HasNewConVarDumpFlag() const { return m_bHasNewConVarDump; } + void ClearHasNewConVarDumpFlag() { m_bHasNewConVarDump = false; } + DumpedConVarVector_t &GetConVarDumpVector() { return m_DumpedConVars; } + const DumpedConVarVector_t &GetConVarDumpVector() const { return m_DumpedConVars; } + +private: + CSocketConnection *m_pSocket; + bool m_bDeleteSocket; + int m_nEndpointIndex; + bool m_bClientFlag; + + CUtlVector m_RecvBuf; + + CUtlVector m_SendBuf; + int m_nSendBufOfs; + + bool m_bReceivedNewCameraPos; + SetCameraPosPacket_t m_NewCameraPos; + + simple_bitmap m_screenshot; + bool m_bHasNewScreenshot; + uint m_nScreenshotID; + + bool m_bHasNewConVarDump; + DumpedConVarVector_t m_DumpedConVars; + + bool ProcessMessages() + { + while ( m_RecvBuf.Count() >= cPacketHeaderSize ) + { + const int nCurRecvBufSize = m_RecvBuf.Count(); + + const PacketHeader_t header( *reinterpret_cast( &m_RecvBuf[0] ) ); + if ( header.m_nID != cPacketHeaderID ) + return false; + + if ( header.m_nTotalSize < cPacketHeaderSize ) + return false; + + if ( header.m_nType >= cPackerTypeTotalMessages ) + return false; + + if ( (uint32)m_RecvBuf.Count() < header.m_nTotalSize ) + break; + + const uint nNumMessageBytes = header.m_nTotalSize - cPacketHeaderSize; + const uint8 *pMsg = nNumMessageBytes ? &m_RecvBuf[cPacketHeaderSize] : NULL; + + switch ( header.m_nType ) + { + case cPacketTypeMessage: + { + if ( nNumMessageBytes ) + { + ConMsg( "CONCMDSRV: Message from client %i: ", m_nEndpointIndex ); + + CUtlVectorFixedGrowable buf; + buf.SetCount( nNumMessageBytes + 1 ); + memcpy( &buf[0], pMsg, nNumMessageBytes ); + buf[nNumMessageBytes] = '\0'; + + ConMsg( "%s\n", &buf[0] ); + } + + break; + } + case cPacketTypeScreenshotRequest: + { + const ScreenshotPacket_t &requestPacket = *reinterpret_cast( &m_RecvBuf[0] ); + if ( requestPacket.m_nTotalSize != sizeof( ScreenshotPacket_t ) ) + { + Warning( "CONCMDSRV: Invalid screenshot request packet!\n" ); + return false; + } + + if ( !videomode ) + break; + + uint nWidth = videomode->GetModeWidth(); + uint nHeight = videomode->GetModeHeight(); + if ( !nWidth || !nHeight ) + break; + + static void *s_pBuf; + if ( !s_pBuf ) + { + s_pBuf = malloc( nWidth * nHeight * 3 ); + } + videomode->ReadScreenPixels( 0, 0, nWidth, nHeight, s_pBuf, IMAGE_FORMAT_RGB888 ); + + size_t nPNGSize = 0; + void *pPNGData = tdefl_write_image_to_png_file_in_memory( s_pBuf, nWidth, nHeight, 3, &nPNGSize, true ); + + if ( pPNGData ) + { + ScreenshotPacket_t replyPacket; + replyPacket.m_nID = cPacketHeaderID; + replyPacket.m_nTotalSize = sizeof( replyPacket ) + nPNGSize; + replyPacket.m_nType = cPacketTypeScreenshotReply; + V_strcpy( replyPacket.m_szFilename, requestPacket.m_szFilename ); + replyPacket.m_nScreenshotID = requestPacket.m_nScreenshotID; + + if ( !SendData( &replyPacket, sizeof( replyPacket ) ) ) + { + free( pPNGData ); + return false; + } + + if ( !SendData( pPNGData, nPNGSize ) ) + { + free( pPNGData ); + return false; + } + + free( pPNGData ); + } + + break; + } + case cPacketTypeScreenshotReply: + { + const ScreenshotPacket_t &replyPacket = *reinterpret_cast( &m_RecvBuf[0] ); + if ( replyPacket.m_nTotalSize <= sizeof( ScreenshotPacket_t ) ) + { + Warning( "CONCMDSRV: Invalid screenshot reply packet!\n" ); + return false; + } + + const void *pPNGData = &m_RecvBuf[sizeof(ScreenshotPacket_t)]; + uint nPNGDataSize = header.m_nTotalSize - sizeof(ScreenshotPacket_t); + + if ( !replyPacket.m_nScreenshotID ) + { + char szFilename[512]; + V_snprintf( szFilename, sizeof( szFilename ), "%s_%u.png", replyPacket.m_szFilename, m_nEndpointIndex ); + + FileHandle_t fh = g_pFullFileSystem->Open( szFilename, "wb" ); + if ( fh ) + { + g_pFullFileSystem->Write( pPNGData, nPNGDataSize, fh ); + g_pFullFileSystem->Close(fh); + } + } + else + { + int nWidth = 0, nHeight = 0, nComp = 3; + unsigned char *pImageData = stbi_load_from_memory( reinterpret_cast< stbi_uc const * >( pPNGData ), nPNGDataSize, &nWidth, &nHeight, &nComp, 3); + + if ( !pImageData ) + { + Warning( "CONCMDSRV: Failed unpacking PNG screenshot!\n" ); + return false; + } + + m_bHasNewScreenshot = true; + m_nScreenshotID = replyPacket.m_nScreenshotID; + + if ( ( (int)m_screenshot.width() != nWidth ) || ( (int)m_screenshot.height() != nHeight ) ) + { + m_screenshot.init( nWidth, nHeight ); + } + + memcpy( m_screenshot.get_ptr(), pImageData, nWidth * nHeight * 3 ); + + stbi_image_free( pImageData ); + } + + break; + } + case cPacketTypeCommand: + { + if ( nNumMessageBytes ) + { + //ConMsg( "CONCMDSRV: Console command from client %i: ", m_nEndpointIndex ); + + CUtlVectorFixedGrowable buf; + buf.SetCount( nNumMessageBytes + 1 ); + memcpy( &buf[0], pMsg, nNumMessageBytes ); + buf[nNumMessageBytes] = '\0'; + + //ConMsg( "\"%s\"\n", &buf[0] ); + + Cbuf_AddText( &buf[0] ); + Cbuf_AddText( "\n" ); + Cbuf_Execute(); + } + + break; + } + case cPacketTypeSetCameraPos: + { + const SetCameraPosPacket_t &setCameraPosPacket = reinterpret_cast( m_RecvBuf[0] ); + if ( setCameraPosPacket.m_nTotalSize != sizeof( SetCameraPosPacket_t ) ) + { + Warning( "CONCMDSRV: Invalid set camera pos packet!\n" ); + return false; + } + + m_bReceivedNewCameraPos = true; + m_NewCameraPos = setCameraPosPacket; + break; + } + case cPacketTypeConVarDumpRequest: + { + CUtlVector conVarData; + + DumpedConVarVector_t conVars; + DumpConVars( conVars ); + + for ( int i = 0; i < conVars.Count(); i++ ) + { + const char *pName = conVars[i].m_Name.Get(); + const char *pVal = conVars[i].m_Value.Get(); + if ( ( !pName ) || ( !pVal ) ) + continue; + + conVarData.AddMultipleToTail( V_strlen( pName ) + 1, reinterpret_cast( pName ) ); + conVarData.AddMultipleToTail( V_strlen( pVal ) + 1, reinterpret_cast( pVal ) ); + } + + PacketHeader_t replyPacket; + replyPacket.m_nID = cPacketHeaderID; + replyPacket.m_nTotalSize = sizeof( replyPacket ) + conVarData.Count(); + replyPacket.m_nType = cPacketTypeConVarDumpReply; + if ( !SendData( &replyPacket, sizeof( replyPacket ) ) ) + return false; + if ( conVarData.Count() ) + { + if ( !SendData( &conVarData[0], conVarData.Count() ) ) + return false; + } + + break; + } + case cPacketTypeConVarDumpReply: + { + m_bHasNewConVarDump = true; + m_DumpedConVars.SetCountNonDestructively( 0 ); + m_DumpedConVars.EnsureCapacity( 4096 ); + + const char *pCur = reinterpret_cast( pMsg ); + const char *pEnd = reinterpret_cast( pMsg ) + nNumMessageBytes; + + while ( pCur < pEnd ) + { + const uint nBytesLeft = pEnd - pCur; + + uint nNameLen = V_strlen( pCur ); + if ( ( nNameLen + 1 ) >= nBytesLeft ) + { + Warning( "CONCMDSRV: Failed parsing convar dump reply!\n" ); + Assert( 0 ); + return false; + } + + uint nValLen = V_strlen( pCur + nNameLen + 1 ); + + uint nTotalLenInBytes = nNameLen + 1 + nValLen + 1; + if ( nTotalLenInBytes > nBytesLeft ) + { + Warning( "CONCMDSRV: Failed parsing convar dump reply!\n" ); + Assert( 0 ); + return false; + } + + int nIndex = m_DumpedConVars.AddToTail(); + m_DumpedConVars[nIndex].m_Name.Set( pCur ); + m_DumpedConVars[nIndex].m_Value.Set( pCur + nNameLen + 1 ); + + pCur += nTotalLenInBytes; + } + + ConMsg( "Received convar dump reply from endpoint %i, %u total convars\n", m_nEndpointIndex, m_DumpedConVars.Count() ); + + break; + } + default: + { + return false; + } + } + + // In case the connection gets lost during a send to reply + if ( ( m_nEndpointIndex < 0 ) || ( nCurRecvBufSize != m_RecvBuf.Count() ) ) + return false; + + int nNumBytesRemaining = m_RecvBuf.Count() - header.m_nTotalSize; + Assert( nNumBytesRemaining >= 0 ); + if ( nNumBytesRemaining >= 0 ) + { + memmove( &m_RecvBuf[0], &m_RecvBuf[0] + header.m_nTotalSize, nNumBytesRemaining ); + m_RecvBuf.SetCountNonDestructively( nNumBytesRemaining ); + } + } + + return true; + } +}; + +ConVar ccs_broadcast_camera( "ccs_broadcast_camera", "0", FCVAR_CHEAT ); +ConVar ccs_camera_sync( "ccs_camera_sync", "0", FCVAR_CHEAT ); +ConVar ccs_remote_screenshots( "ccs_remote_screenshots", "0", FCVAR_CHEAT ); +ConVar ccs_remote_screenshots_delta( "ccs_remote_screenshots_delta", "1", FCVAR_CHEAT ); + +class CConCommandServer : public CConCommandServerProtocolTypes +{ + static bool UserWindowProc( LRESULT& hres, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, void *pData ) + { + CConCommandServer *pServer = static_cast< CConCommandServer * >( pData ); + + switch ( message ) + { + case WM_KEYDOWN: + { + uint cKey = ( wParam & 0xFF ); + if ( ( cKey >= '0' ) && ( cKey <= '9' ) ) + { + pServer->m_nViewEndpointIndex = cKey - '0'; + } + } + } + + return false; + } + +public: + CConCommandServer() : + m_bInitialized( false ), + m_pFrameBufWindow( NULL ), + m_nViewEndpointIndex( 0 ) + { + } + + ~CConCommandServer() + { + Deinit(); + } + + bool Init() + { + Deinit(); + + m_Socket.Init( CT_SERVER, SP_TCP ); + + if ( m_Socket.Listen( cProtocolPort, cMaxClients ) != SOCKET_SUCCESS ) + { + Warning( "CONCMDSRV: CConCommandServer:Init: Failed to create listen socket!\n" ); + return false; + } + + ConMsg( "CONCMDSVR: Listening for connections\n" ); + + uint nWidth = 1280, nHeight = 1024; + if ( videomode ) + { + nWidth = videomode->GetModeWidth(); + nHeight = videomode->GetModeHeight(); + } + m_pFrameBufWindow = new frame_buf_window( "CCS", nWidth, nHeight ); + m_pFrameBufWindow->set_window_proc_callback( UserWindowProc, this ); + + m_bInitialized = true; + return true; + } + + void Deinit() + { + if ( !m_bInitialized ) + return; + + delete m_pFrameBufWindow; + m_pFrameBufWindow = NULL; + + for ( int i = 0; i < cMaxClients; i++ ) + { + m_Clients[i].Deinit(); + } + + m_Socket.Cleanup(); + m_nViewEndpointIndex = 0; + + ConMsg( "CONCMDSVR: Deinitialized\n" ); + + m_bInitialized = false; + } + + bool IsInitialized() + { + return m_bInitialized; + } + + void TickFrame( float flTime ) + { + if ( !m_bInitialized ) + return; + + AcceptNewConnections(); + + if ( ccs_broadcast_camera.GetBool() ) + { + Vector vecOrigin = MainViewOrigin(); + QAngle angles( 0, 0, 0 ); + + IClientEntity *localPlayer = entitylist ? entitylist->GetClientEntity( cl.m_nPlayerSlot + 1 ) : NULL; + if ( localPlayer ) + { + vecOrigin = localPlayer->GetAbsOrigin(); + angles = localPlayer->GetAbsAngles(); + } + + SetCameraPosPacket_t setCameraPacket; + setCameraPacket.m_nID = cPacketHeaderID; + setCameraPacket.m_nType = cPacketTypeSetCameraPos; + setCameraPacket.m_nTotalSize = sizeof( setCameraPacket ); + setCameraPacket.m_Pos = vecOrigin; + setCameraPacket.m_Angle = angles; + SendDataToAllClients( &setCameraPacket, sizeof( setCameraPacket ) ); + + if ( ccs_camera_sync.GetBool() ) + { + ccs_camera_sync.SetValue( false ); + char buf[256]; + V_snprintf( buf, sizeof( buf ), "setpos_exact 0x%X 0x%X 0x%X;setang_exact 0x%X 0x%X 0x%X\n", *(DWORD*)&setCameraPacket.m_Pos.x, *(DWORD*)&setCameraPacket.m_Pos.y, *(DWORD*)&setCameraPacket.m_Pos.z, *(DWORD*)&setCameraPacket.m_Angle.x, *(DWORD*)&setCameraPacket.m_Angle.y, *(DWORD*)&setCameraPacket.m_Angle.z ); + Cbuf_AddText( buf ); + Cbuf_Execute(); + } + } + + if ( ccs_remote_screenshots.GetBool() ) + { + static double flTotalTime; + flTotalTime += flTime; + if ( flTotalTime > .5f ) + { + flTotalTime = 0.0f; + + ScreenshotPacket_t screenshotPacket; + screenshotPacket.m_nID = cPacketHeaderID; + screenshotPacket.m_nType = cPacketTypeScreenshotRequest; + screenshotPacket.m_nTotalSize = sizeof( screenshotPacket ); + screenshotPacket.m_szFilename[0] = '\0'; + screenshotPacket.m_nScreenshotID = 1; + SendDataToAllClients( &screenshotPacket, sizeof( screenshotPacket ) ); + } + } + + bool bHasUpdatedScreenshot = false; + + simple_bgr_bitmap &frameBuf = m_pFrameBufWindow->frameBuffer(); + + for ( int i = 0; i < cMaxClients; i++ ) + { + CConCommandConnection &client = m_Clients[i]; + if ( !client.IsConnected() ) + continue; + + client.TickConnection(); + + if ( client.HasNewConVarDumpFlag() ) + { + client.ClearHasNewConVarDumpFlag(); + + diffClientsDumpedConVars( i, client.GetConVarDumpVector() ); + } + + if ( m_nViewEndpointIndex == i ) + { + if ( client.HasNewScreenshotFlag() ) + { + client.ClearNewScreenshotFlag(); + + simple_bitmap &clientScreenshot = client.GetScreenshot(); + + if ( ccs_remote_screenshots_delta.GetBool() ) + { + if ( videomode && !bHasUpdatedScreenshot ) + { + bHasUpdatedScreenshot = true; + + uint nScreenWidth = videomode->GetModeWidth(); + uint nScreenHeight = videomode->GetModeHeight(); + + if ( ( m_screenshot.width() != nScreenWidth ) || ( m_screenshot.height() != nScreenHeight ) ) + { + m_screenshot.init( nScreenWidth, nScreenHeight ); + } + + videomode->ReadScreenPixels( 0, 0, nScreenWidth, nScreenHeight, m_screenshot.get_ptr(), IMAGE_FORMAT_RGB888 ); + } + + uint mx = MIN( clientScreenshot.width(), m_screenshot.width() ); + mx = MIN( mx, frameBuf.width() ); + + uint my = MIN( clientScreenshot.height(), m_screenshot.height() ); + my = MIN( my, frameBuf.height() ); + + for ( uint y = 0; y < my; ++y ) + { + const uint8 *pSrc1 = clientScreenshot.get_scanline( y ); + const uint8 *pSrc2 = m_screenshot.get_scanline( y ); + uint8 *pDst = frameBuf.get_scanline( y ); + + for ( uint x = 0; x < mx; ++x ) + { + int r = 2 * ( pSrc1[0] - pSrc2[0] ) + 128; + int g = 2 * ( pSrc1[1] - pSrc2[1] ) + 128; + int b = 2 * ( pSrc1[2] - pSrc2[2] ) + 128; + + if ( ( r | g | b ) & 0xFFFFFF00 ) + { + if ( r & 0xFFFFFF00 ) { r = (~( r >> 31 )) & 0xFF; } + if ( g & 0xFFFFFF00 ) { g = (~( g >> 31 )) & 0xFF; } + if ( b & 0xFFFFFF00 ) { b = (~( b >> 31 )) & 0xFF; } + } + + pDst[0] = (uint8)b; + pDst[1] = (uint8)g; + pDst[2] = (uint8)r; + + pSrc1 += 3; + pSrc2 += 3; + pDst += 3; + } + } + + } + else + { + frameBuf.blit( 0, 0, (simple_bgr_bitmap &)clientScreenshot, true ); + } + } + } + } + + if ( m_pFrameBufWindow ) + { + m_pFrameBufWindow->update(); + } + } + + void SendMessageToClients( const char * pCmd ) + { + if ( !m_bInitialized ) + return; + + int n = V_strlen( pCmd ); + + char buf[4096]; + if ( ( n >= 2 ) && ( pCmd[0] == '\"' ) && ( pCmd[ n - 1 ] == '\"' ) ) + { + memcpy( buf, pCmd + 1, n - 2 ); + buf[n - 2] = '\0'; + pCmd = buf; + } + + SendStringToClients( pCmd, cPacketTypeMessage ); + } + + void SendConCommandToClients( const char * pCmd, bool bExecLocally = false ) + { + int n = V_strlen( pCmd ); + + char buf[4096]; + if ( ( n >= 2 ) && ( pCmd[0] == '\"' ) && ( pCmd[ n - 1 ] == '\"' ) ) + { + memcpy( buf, pCmd + 1, n - 2 ); + buf[n - 2] = '\0'; + pCmd = buf; + } + + if ( m_bInitialized ) + { + SendStringToClients( pCmd, cPacketTypeCommand ); + } + + if ( bExecLocally ) + { + Cbuf_AddText( pCmd ); + Cbuf_AddText( "\n" ); + Cbuf_Execute(); + } + } + + void SendCameraPosAndAngleToClients( Vector &vecOrigin, QAngle &angles ) + { + if ( !m_bInitialized ) + return; + + SetCameraPosPacket_t packet; + + packet.m_nID = cPacketHeaderID; + packet.m_nType = cPacketTypeSetCameraPos; + packet.m_nTotalSize = sizeof( packet ); + packet.m_Pos = vecOrigin; + packet.m_Angle = angles; + + SendDataToAllClients( &packet, sizeof( packet ) ); + } + + void SendScreenshotRequestToClients( const char *pFilename ) + { + if ( !m_bInitialized ) + return; + + ScreenshotPacket_t packet; + + packet.m_nID = cPacketHeaderID; + packet.m_nType = cPacketTypeScreenshotRequest; + packet.m_nTotalSize = sizeof( packet ); + V_strncpy( packet.m_szFilename, pFilename, sizeof( packet.m_szFilename ) ); + packet.m_nScreenshotID = 0; + + SendDataToAllClients( &packet, sizeof( packet ) ); + } + + void DiffConVarsOfAllClients() + { + if ( !m_bInitialized ) + return; + + PacketHeader_t packet; + packet.m_nID = cPacketHeaderID; + packet.m_nTotalSize = sizeof( PacketHeader_t ); + packet.m_nType = cPacketTypeConVarDumpRequest; + SendDataToAllClients( &packet, sizeof( packet ) ); + } + +private: + bool m_bInitialized; + CSocketConnection m_Socket; + + CConCommandConnection m_Clients[cMaxClients]; + frame_buf_window *m_pFrameBufWindow; + + simple_bitmap m_screenshot; + int m_nViewEndpointIndex; + + void AcceptNewConnections() + { + for ( ; ; ) + { + int nNewEndpointIndex = -1; + SocketErrorCode_t err = m_Socket.TryAcceptIncomingConnection( &nNewEndpointIndex ); + if ( ( err != SOCKET_SUCCESS ) || ( nNewEndpointIndex < 0 ) ) + break; + + int i; + for ( i = 0; i < cMaxClients; i++ ) + { + if ( !m_Clients[i].IsConnected() ) + break; + } + if ( i == cMaxClients ) + { + m_Socket.ResetEndpoint( nNewEndpointIndex ); + Warning( "CONCMDSRV: Too many active connections!\n" ); + break; + } + + if ( !m_Clients[i].Init( &m_Socket, nNewEndpointIndex, false ) ) + { + Warning( "CONCMDSRV: Failed accepting connection from endpoint %i\n", nNewEndpointIndex ); + } + else + { + ConMsg( "CONCMDSRV: Accepting connection at endpoint %i\n", nNewEndpointIndex ); + } + } + } + + bool SendDataToAllClients( const void *p, uint nSize ) + { + if ( !nSize ) + return true; + + bool bSuccess = true; + + for ( uint i = 0; i < cMaxClients; i++ ) + { + if ( !m_Clients[i].IsConnected() ) + continue; + + if ( !m_Clients[i].SendData( p, nSize ) ) + { + bSuccess = false; + } + } + + return bSuccess; + } + + void SendStringToClients( const char *pCmd, PacketTypes_t nType ) + { + uint8 buf[8192]; + + uint nStrLen = strlen( pCmd ); + const uint nMaxStrLen = sizeof( buf ) - cPacketHeaderSize; + nStrLen = MIN( nStrLen, nMaxStrLen ); + + PacketHeader_t &hdr = reinterpret_cast(buf[0]); + hdr.m_nID = cPacketHeaderID; + hdr.m_nType = nType; + hdr.m_nTotalSize = cPacketHeaderSize + nStrLen; + memcpy( buf + cPacketHeaderSize, pCmd, nStrLen ); + + SendDataToAllClients( buf, hdr.m_nTotalSize ); + } + + static int FindConVar( const DumpedConVarVector_t &sortedConVars, const char *pName ) + { + int l = 0, h = sortedConVars.Count() - 1; + while ( l <= h ) + { + int m = ( l + h ) >> 1; + int d = V_strcmp( sortedConVars[m].m_Name.Get(), pName ); + if ( !d ) + return m; + else if ( d > 0 ) + h = m - 1; + else + l = m + 1; + } + return -1; + } + + static void DiffConVars( DumpedConVarVector_t &serverConVars, DumpedConVarVector_t &clientConVars ) + { + if ( serverConVars.Count() ) + { + std::sort( &serverConVars.Head(), &serverConVars.Tail() + 1 ); + } + + if ( clientConVars.Count() ) + { + std::sort( &clientConVars.Head(), &clientConVars.Tail() + 1 ); + } + + CUtlVector clientConVarFoundFlags; + clientConVarFoundFlags.SetCount( clientConVars.Count() ); + memset( &clientConVarFoundFlags.Head(), 0, sizeof( bool ) * clientConVars.Count() ); + + uint nTotalNotFound = 0; + uint nTotalMatches = 0; + uint nTotalMismatches = 0; + + for ( int i = 0; i < serverConVars.Count(); i++ ) + { + const DumpedConVar_t &serverConVar = serverConVars[i]; + const char *pServerConVarName = serverConVar.m_Name.Get(); + const char *pServerConVarValue = serverConVar.m_Value.Get(); + + Assert( FindConVar( serverConVars, pServerConVarName ) != -1 ); + + int nClientConVarIndex = FindConVar( clientConVars, pServerConVarName ); + if ( nClientConVarIndex < 0 ) + { + Warning( "%s: Can't find server convar on client, value: \"%s\"\n", pServerConVarName, pServerConVarValue ); + nTotalNotFound++; + } + else + { + clientConVarFoundFlags[nClientConVarIndex] = true; + + if ( V_strcmp( pServerConVarValue, clientConVars[nClientConVarIndex].m_Value.Get() ) == 0 ) + { + nTotalMatches++; + } + else + { + nTotalMismatches++; + Warning( "%s: Convar diff: server=\"%s\", client=\"%s\"\n", pServerConVarName, pServerConVarValue, clientConVars[nClientConVarIndex].m_Value ); + } + } + } + + uint nTotalUnmatched = 0; + for ( int i = 0; i < clientConVars.Count(); ++i ) + { + if ( !clientConVarFoundFlags[i] ) + { + nTotalUnmatched++; + Warning( "%s: Client convar does not exist on server, value: \"%s\"\n", clientConVars[i].m_Name.Get(), clientConVars[i].m_Value.Get() ); + } + } + + ConMsg( "--- Summary:\n"); + ConMsg( "Total server convars: %u\n", serverConVars.Count() ); + ConMsg( "Total client convars: %u\n", clientConVars.Count() ); + ConMsg( "Total server convars not found on client: %u\n", nTotalNotFound ); + ConMsg( "Total server convars that match clients: %u\n", nTotalMatches ); + ConMsg( "Total server convars that mismatch clients: %u\n", nTotalMismatches ); + ConMsg( "Total client convars that don't exist on server: %u\n", nTotalUnmatched ); + } + + void diffClientsDumpedConVars( int nClientIndex, DumpedConVarVector_t &clientConVars ) + { + DumpedConVarVector_t serverConVars; + DumpConVars( serverConVars ); + ConMsg( "Convar diff of client %i:\n", nClientIndex ); + DiffConVars( serverConVars, clientConVars ); + } +}; + +CConCommandServer g_ConCommandServer; + +CON_COMMAND_F( ccs_start, "Start the con command server.", FCVAR_CHEAT ) +{ + if ( !g_ConCommandServer.IsInitialized() ) + { + g_ConCommandServer.Init(); + } + else + { + Warning( "CONCMDSVR: Already initialized\n" ); + } +} + +CON_COMMAND_F( ccs_stop, "Stop the con command server.", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + g_ConCommandServer.Deinit(); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_msg, "Send a message to any connected con command server clients.", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + if ( args.ArgC() < 2 ) + { + Warning( "Usage: ccs_msg \"message\"\n" ); + return; + } + + g_ConCommandServer.SendMessageToClients( args.ArgS() ); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_cmd, "Send a console message to any connected con command server clients.", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + if ( args.ArgC() < 2 ) + { + ConMsg( "Usage: ccs_cmd \"command\"\n" ); + return; + } + + g_ConCommandServer.SendConCommandToClients( args.ArgS() ); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_cmd_local, "Send a console message to any connected con command server clients, and also issue the command locally.", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + if ( args.ArgC() < 2 ) + { + ConMsg( "Usage: ccs_cmd_local \"command\"\n" ); + return; + } + + g_ConCommandServer.SendConCommandToClients( args.ArgS(), true ); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_screenshot, "Request screenshots from connected clients", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + const char *pBaseFileName = "ccs_screenshot"; + if ( args.ArgC() >= 2 ) + { + pBaseFileName = args.Arg( 1 ); + } + g_ConCommandServer.SendScreenshotRequestToClients( pBaseFileName ); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_diff_convars, "Diffs server's convars vs. all connected clients", FCVAR_CHEAT ) +{ + if ( g_ConCommandServer.IsInitialized() ) + { + g_ConCommandServer.DiffConVarsOfAllClients(); + } + else + { + Warning( "CONCMDSVR: Not initialized\n" ); + } +} + +CON_COMMAND_F( ccs_dump_convars, "Dump all convars to console", FCVAR_CHEAT ) +{ + DumpedConVarVector_t conVars; + DumpConVars( conVars ); + + for ( int i = 0; i < conVars.Count(); i++ ) + { + ConMsg( "%s %s\n", conVars[i].m_Name.Get(), conVars[i].m_Value.Get() ); + } + ConMsg( "Dumped %i convars\n", conVars.Count() ); +} + +CConCommandConnection g_ConCommandConnection; + +CON_COMMAND_F( ccs_connect, "Connect to a con cmd server.", FCVAR_CHEAT ) +{ + if ( args.ArgC() < 2 ) + { + ConMsg( "Usage: ccs_connect \"address\"\n" ); + return; + } + + if ( g_ConCommandConnection.IsConnected() ) + { + g_ConCommandConnection.Deinit(); + Warning( "CONCMDSRV: Disconnected\n" ); + } + + if ( g_ConCommandConnection.Init( args.ArgS() ) ) + { + ConMsg( "CONCMDSRV: Connected\n" ); + } + else + { + Warning( "CONCMDSRV: Failed to connect!\n" ); + } +} + +CON_COMMAND_F( ccs_disconnect, "Disconnect from a con cmd server.", FCVAR_CHEAT ) +{ + if ( !g_ConCommandConnection.IsConnected() ) + { + Warning( "CONCMDSRV: Not connected!\n" ); + } + else + { + g_ConCommandConnection.Deinit(); + + Warning( "CONCMDSRV: Disconnected\n" ); + } +} + +#endif // CON_COMMAND_SERVER_SUPPORT + +void CCS_Init() +{ +} + +void CCS_Shutdown() +{ +#ifdef CON_COMMAND_SERVER_SUPPORT + g_ConCommandConnection.Deinit(); + g_ConCommandServer.Deinit(); +#endif +} + +void CCS_Tick( float flTime ) +{ + (void)flTime; + +#ifdef CON_COMMAND_SERVER_SUPPORT + if ( g_ConCommandConnection.IsConnected() ) + { + g_ConCommandConnection.TickConnection(); + } + if ( g_ConCommandServer.IsInitialized() ) + { + g_ConCommandServer.TickFrame( flTime ); + } +#endif +} -- cgit v1.2.3