You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1620 lines
40 KiB
1620 lines
40 KiB
//========= 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 |
|
}
|
|
|