summaryrefslogtreecommitdiff
path: root/engine/ccs.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/ccs.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/ccs.cpp')
-rw-r--r--engine/ccs.cpp1620
1 files changed, 1620 insertions, 0 deletions
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 <algorithm>
+#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<frame_buf_window*>(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<LONG>(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<BITMAPINFO*>(&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<uint32*>(m_pBitmap_hdr->bmiColors)[0] = 0x00FF0000;
+ reinterpret_cast<uint32*>(m_pBitmap_hdr->bmiColors)[1] = 0x0000FF00;
+ reinterpret_cast<uint32*>(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<DumpedConVar_t> 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<uint8> m_RecvBuf;
+
+ CUtlVector<uint8> 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<const PacketHeader_t *>( &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<char, 4096> 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<const ScreenshotPacket_t *>( &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<const ScreenshotPacket_t *>( &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<char, 4096> 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<const SetCameraPosPacket_t &>( 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<uint8> 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<const uint8 *>( pName ) );
+ conVarData.AddMultipleToTail( V_strlen( pVal ) + 1, reinterpret_cast<const uint8 *>( 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<const char *>( pMsg );
+ const char *pEnd = reinterpret_cast<const char *>( 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<PacketHeader_t &>(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<bool> 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
+}