diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/vmpi | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/vmpi')
183 files changed, 32136 insertions, 0 deletions
diff --git a/utils/vmpi/IThreadedTCPSocket.h b/utils/vmpi/IThreadedTCPSocket.h new file mode 100644 index 0000000..904dc7f --- /dev/null +++ b/utils/vmpi/IThreadedTCPSocket.h @@ -0,0 +1,173 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ITHREADEDTCPSOCKET_H +#define ITHREADEDTCPSOCKET_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "iphelpers.h" + + +class IThreadedTCPSocket; + + +class CTCPPacket +{ +public: + // Access the contents of the packet. + const char* GetData() const; + int GetLen() const; + + // You can attach some user data to the packet. + int GetUserData() const; + void SetUserData( int userData ); + + // Free resources associated with the packet. + void Release(); + +public: + friend class CThreadedTCPSocket; + ~CTCPPacket(); // Use Release(), not delete. + + int m_UserData; + int m_Len; + char m_Data[1]; +}; + + +inline const char* CTCPPacket::GetData() const +{ + return m_Data; +} + + +inline int CTCPPacket::GetLen() const +{ + return m_Len; +} + + +// The application implements this to handle packets that are received. +// Note that the implementation must be thread-safe because these functions can be called +// from various threads. +class ITCPSocketHandler +{ +public: + + enum + { + SocketError=0, + ConnectionTimedOut + }; + + // This is called right when the socket becomes ready to have data sent through it and + // before OnPacketReceive is ever called. + virtual void Init( IThreadedTCPSocket *pSocket ) = 0; + + // This is called when a packet arrives. NOTE: you are responsible for freeing the packet + // by calling CTCPPacket::Release() on it. + virtual void OnPacketReceived( CTCPPacket *pPacket ) = 0; + + // Handle errors inside the socket. After this is called, the socket is no longer alive. + // Note: this might be called from ANY thread (the main thread, the send thread, or the receive thread). + // + // errorCode is one of the enums above (SocketError, ConnectionTimedOut, etc). + virtual void OnError( int errorCode, const char *pErrorString ) = 0; +}; + + +// +// This is the main threaded TCP socket class. +// The way these work is that they have a thread for sending and a thread for receiving data. +// +// The send thread is continually pushing your data out the door. +// +// The receive thread is continually receiving data. When it receives data, it calls your HandlePacketFn +// to allow the user to handle it. Be very careful in your HandlePacketFn, since it is in another thread. +// Anything it accesses should be protected by mutexes and the like. +// +class IThreadedTCPSocket +{ +public: + // Cleanup everything and exit. + // Note: if the receive thread is inside your HandlePacketFn returns, this function blocks until that function returns. + virtual void Release() = 0; + + // Returns the address of whoever you are connected to. + virtual CIPAddr GetRemoteAddr() const = 0; + + // Returns true if the socket is connected and ready to go. If this returns false, then the socket won't + // send or receive data any more. It also means that your ITCPSocketHandler's OnError function has been called. + virtual bool IsValid() = 0; + + // Send data. Any thread can call these functions, and they don't block. They make a copy of the data, then + // enqueue it for sending. + virtual bool Send( const void *pData, int len ) = 0; + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; +}; + + + +// Use these to get incoming connections. +class ITCPConnectSocket +{ +public: + // Call this to stop listening for connections and delete the object. + virtual void Release() = 0; + + // Keep calling this as long as you want to wait for connections. + // + // If it returns true and pSocket is NULL, it means it hasn't connected yet. + // If it returns true and pSocket is non-NULL, then it has connected. + // If it returns false, then the connection attempt failed and all further Update() calls will return false. + virtual bool Update( IThreadedTCPSocket **pSocket, unsigned long milliseconds=0 ) = 0; +}; + + +// This class is implemented by the app and passed into CreateListener. When the listener makes +// a new connection, it calls CreateNewHandler() to have the app create something that will handle +// the received packets and errors for the new socket. +class IHandlerCreator +{ +public: + // This function must return a valid value. + virtual ITCPSocketHandler* CreateNewHandler() = 0; +}; + + +// Use this to listen for TCP connections. The ITCPConnectSocket will keep returning connections +// until you call Release(). +ITCPConnectSocket* ThreadedTCP_CreateListener( + IHandlerCreator *pHandlerCreator, // This handles messages from the socket. + const unsigned short port, // Listen on this port. + int nQueueLength = 5 // How many connections + ); + + +// Use this to connect to a remote process. After Update() returns a non-NULL value, you should +// call Release() on the ITCPConnectSocket because it won't ever return another connection. +ITCPConnectSocket* ThreadedTCP_CreateConnector( + const CIPAddr &addr, // Who to connect to. + const CIPAddr &localAddr, // Local address to bind to. Leave uninitialized (pass in CIPAddr()) and it will + // an interface and a port for you. You can also just fill in the port, and it will + // use that port and choose an interface for you. + IHandlerCreator *pHandlerCreator// If it connects, it asks this thing to make a handler for the connection. + ); + + +// Enable or disable timeouts. +void ThreadedTCP_EnableTimeouts( bool bEnable ); + +// This should be called at init time. If set to true, it'll set the send and recv threads to low priority. +// (Default is true). +void ThreadedTCP_SetTCPSocketThreadPriorities( bool bSetTCPSocketThreadPriorities ); + + +#endif // ITHREADEDTCPSOCKET_H diff --git a/utils/vmpi/ThreadedTCPSocket.cpp b/utils/vmpi/ThreadedTCPSocket.cpp new file mode 100644 index 0000000..444e22b --- /dev/null +++ b/utils/vmpi/ThreadedTCPSocket.cpp @@ -0,0 +1,1085 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ThreadedTCPSocket.cpp : Defines the entry point for the console application. +// + +#include <winsock2.h> +#include <mswsock.h> +#include "IThreadedTCPSocket.h" +#include "utllinkedlist.h" +#include "threadhelpers.h" +#include "iphelpers.h" +#include "tier1/strtools.h" + + +#define SEND_KEEPALIVE_INTERVAL 3000 +#define KEEPALIVE_TIMEOUT 25000 // The sockets timeout after this long. + +#define KEEPALIVE_SENTINEL -12345 // When first 4 bytes of a packet = this, then it's just a keepalive. + + +static int g_KeepaliveSentinel = KEEPALIVE_SENTINEL; +bool g_bHandleTimeouts = true; + +// If true, it'll set the socket thread priorities lower than normal. +bool g_bSetTCPSocketThreadPriorities = true; + +// We get crashes at runtime if they don't link in the multithreaded runtime libraries, +// so raise a ruckus if they're using singlethreaded libraries. +#ifndef _MT + #pragma message( "**** WARNING **** ThreadedTCPSocket requires multithreaded runtime libraries to be used.\n" ) + class MTChecker + { + public: + MTChecker() { Assert( false ); } + } g_MTChecker; +#endif + + + +// ------------------------------------------------------------------------------------------------ // +// Static helpers. +// ------------------------------------------------------------------------------------------------ // +static SOCKET TCPBind( const CIPAddr *pAddr ) +{ + // Create a socket to send and receive through. + SOCKET sock = WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED ); + if ( sock == INVALID_SOCKET ) + { + Assert( false ); + return INVALID_SOCKET; + } + + // bind to it! + sockaddr_in addr; + IPAddrToSockAddr( pAddr, &addr ); + + int status = bind( sock, (sockaddr*)&addr, sizeof(addr) ); + if ( status == 0 ) + { + return sock; + } + else + { + closesocket( sock ); + return INVALID_SOCKET; + } +} + + + +// ------------------------------------------------------------------------------------------------ // +// CTCPPacket. +// ------------------------------------------------------------------------------------------------ // + +int CTCPPacket::GetUserData() const +{ + return m_UserData; +} + +void CTCPPacket::SetUserData( int userData ) +{ + m_UserData = userData; +} + +void CTCPPacket::Release() +{ + free( this ); +} + + +// ------------------------------------------------------------------------------------------------ // +// CThreadedTCPSocket. +// ------------------------------------------------------------------------------------------------ // +class CThreadedTCPSocket : public IThreadedTCPSocket +{ +public: + + static IThreadedTCPSocket* Create( SOCKET iSocket, CIPAddr remoteAddr, ITCPSocketHandler *pHandler ) + { + CThreadedTCPSocket *pRet = new CThreadedTCPSocket; + if ( pRet->Init( iSocket, remoteAddr, pHandler ) ) + { + return pRet; + } + else + { + pRet->Release(); + return NULL; + } + } + + +// IThreadedTCPSocket implementation. +public: + + virtual void Release() + { + delete this; + } + + virtual CIPAddr GetRemoteAddr() const + { + return m_RemoteAddr; + } + + virtual bool IsValid() + { + return !CheckErrorSignal(); + } + + virtual bool Send( const void *pData, int len ) + { + const void *pChunks[1] = { pData }; + return SendChunks( pChunks, &len, 1 ); + } + + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) + { + if ( CheckErrorSignal() ) + return false; + + return InternalSend( pChunks, pChunkLengths, nChunks, true ); + } + + +// Initialization. +private: + + CThreadedTCPSocket() + { + m_Socket = INVALID_SOCKET; + m_pHandler = NULL; + memset( &m_SendOverlapped, 0, sizeof( m_SendOverlapped ) ); + memset( &m_RecvOverlapped, 0, sizeof( m_RecvOverlapped ) ); + m_bWaitingForSendCompletion = false; + m_nBytesToReceive = -1; + m_bWaitingForSize = false; + m_bErrorSignal = false; + m_pRecvBuffer = NULL; + } + + virtual ~CThreadedTCPSocket() + { + Term(); + } + + bool Init( SOCKET iSocket, CIPAddr remoteAddr, ITCPSocketHandler *pHandler ) + { + m_Socket = iSocket; + m_RemoteAddr = remoteAddr; + m_pHandler = pHandler; + + SetInitialSocketOptions(); + + // Create all the event objects we'll use to communicate. + m_hExitThreadsEvent.Init( true, false ); + m_hSendCompletionEvent.Init( false, false ); + m_hReadyToSendEvent.Init( false, false ); + m_hRecvEvent.Init( false, false ); + + m_SendOverlapped.hEvent = m_hSendCompletionEvent.GetEventHandle(); + m_RecvOverlapped.hEvent = m_hRecvEvent.GetEventHandle(); + + // Create our threads. + DWORD dwSendThreadID, dwRecvThreadID; + m_hSendThread = CreateThread( NULL, 0, &CThreadedTCPSocket::StaticSendThreadFn, this, CREATE_SUSPENDED, &dwSendThreadID ); + m_hRecvThread = CreateThread( NULL, 0, &CThreadedTCPSocket::StaticRecvThreadFn, this, CREATE_SUSPENDED, &dwRecvThreadID ); + if ( !m_hSendThread || !m_hRecvThread ) + { + return false; + } + + if ( g_bSetTCPSocketThreadPriorities ) + { + SetThreadPriority( m_hSendThread, THREAD_PRIORITY_LOWEST ); + SetThreadPriority( m_hRecvThread, THREAD_PRIORITY_LOWEST ); + } + + ThreadSetDebugName( (ThreadId_t)dwSendThreadID, "TCPSend" ); + ThreadSetDebugName( (ThreadId_t)dwRecvThreadID, "TCPRecv" ); + + // Make sure to init the handler before the threads actually run, so it isn't handed data before initializing. + m_pHandler->Init( this ); + + ResumeThread( m_hSendThread ); + ResumeThread( m_hRecvThread ); + + return true; + } + + void Term() + { + // Signal our threads to exit. + m_hExitThreadsEvent.SetEvent(); + if ( m_hSendThread ) + { + WaitForSingleObject( m_hSendThread, INFINITE ); + CloseHandle( m_hSendThread ); + m_hSendThread = NULL; + } + + if ( m_hRecvThread ) + { + WaitForSingleObject( m_hRecvThread, INFINITE ); + CloseHandle( m_hRecvThread ); + m_hRecvThread = NULL; + } + m_hExitThreadsEvent.ResetEvent(); + + + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + } + } + + // Set the initial socket options that we want. + void SetInitialSocketOptions() + { + // Set nodelay to improve latency. + BOOL val = TRUE; + setsockopt( m_Socket, IPPROTO_TCP, TCP_NODELAY, (const char FAR *)&val, sizeof(BOOL) ); + + // Make it linger for 3 seconds when it exits. + LINGER linger; + linger.l_onoff = 1; + linger.l_linger = 3; + setsockopt( m_Socket, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof( linger ) ); + } + + +// Send thread functionality. +private: + + // This function copies off the payload and adds a SendChunk_t to the list of chunks to be sent. + // It also fires the ReadyToSend event so the thread will pick it up. + bool InternalSend( void const * const *pChunks, const int *pChunkLengths, int nChunks, bool bPrependLength ) + { + int totalLength = 0; + for ( int i=0; i < nChunks; i++ ) + totalLength += pChunkLengths[i]; + + if ( bPrependLength ) + { + if ( totalLength == 0 ) + return true; + + totalLength += 4; + } + + // Copy all the data into a SendData_t. + SendData_t *pSendData = (SendData_t*)malloc( sizeof( SendData_t ) - 1 + totalLength ); + pSendData->m_Len = totalLength; + + char *pOut = pSendData->m_Payload; + if ( bPrependLength ) + { + *((int*)pOut) = totalLength - 4; // The length we prepend is the size of the data, not data size + integer for length. + pOut += 4; + } + for ( int i=0; i < nChunks; i++ ) + { + memcpy( pOut, pChunks[i], pChunkLengths[i] ); + pOut += pChunkLengths[i]; + } + + CCriticalSectionLock csLock( &m_SendCS ); + csLock.Lock(); + + m_SendDatas.AddToTail( pSendData ); + m_hReadyToSendEvent.SetEvent(); // Notify the thread that there is data to send. + + csLock.Unlock(); + + return true; + } + + void SendThread_HandleTimeout() + { + // Timeout.. send a keepalive. + // But only if we're not already sending something. + CCriticalSectionLock csLock( &m_SendCS ); + csLock.Lock(); + int count = m_SendDatas.Count(); + csLock.Unlock(); + + if ( count == 0 ) + { + void *pBuf[1] = { &g_KeepaliveSentinel }; + int len[1] = { sizeof( g_KeepaliveSentinel ) }; + InternalSend( pBuf, len, 1, false ); + } + } + + bool SendThread_HandleSendCompletionEvent() + { + Assert( m_bWaitingForSendCompletion ); + m_bWaitingForSendCompletion = false; + + // A send operation just completed. Now do the next one. + DWORD cbTransfer, flags; + if ( !WSAGetOverlappedResult( m_Socket, &m_SendOverlapped, &cbTransfer, TRUE, &flags ) ) + { + HandleError( WSAGetLastError() ); + return false; + } + + if ( cbTransfer != m_nBytesToTransfer ) + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Invalid # bytes transferred (%d) in send thread (should be %d)", cbTransfer, m_nBytesToTransfer ); + HandleError( ITCPSocketHandler::SocketError, str ); + return false; + } + + // Remove the block we just sent. + CCriticalSectionLock csLock( &m_SendCS ); + csLock.Lock(); + + SendData_t *pSendData = m_SendDatas[ m_SendDatas.Head() ]; + free( pSendData ); + m_SendDatas.Remove( m_SendDatas.Head() ); + + m_bWaitingForSendCompletion = false; + + // Set our send event if there's anything else to send. + if ( m_SendDatas.Count() > 0 ) + m_hReadyToSendEvent.SetEvent(); + + csLock.Unlock(); + return true; + } + + bool SendThread_HandleReadyToSendEvent() + { + // We've got at least one buffer that's ready to be sent. + // NOTE: don't send anything until our current send is completed. + CCriticalSectionLock csLock( &m_SendCS ); + csLock.Lock(); + + Assert( !m_bWaitingForSendCompletion ); + + // Send it off! + SendData_t *pSendData = m_SendDatas[ m_SendDatas.Head() ]; + WSABUF buf = { pSendData->m_Len, pSendData->m_Payload }; + + m_nBytesToTransfer = pSendData->m_Len; + m_bWaitingForSendCompletion = true; + + csLock.Unlock(); + + DWORD dwNumBytesSent = 0; + DWORD ret = WSASend( m_Socket, &buf, 1, &dwNumBytesSent, 0, &m_SendOverlapped, NULL ); + DWORD err = WSAGetLastError(); + if ( ret == 0 || ( ret == SOCKET_ERROR && err == WSA_IO_PENDING ) ) + { + // Either way, the operation completed successfully, and m_hSendCompletionEvent is now set. + return true; + } + else + { + HandleError( err ); + return false; + } + + return true; + } + + DWORD SendThreadFn() + { + while ( 1 ) + { + HANDLE handles[] = + { + m_hExitThreadsEvent.GetEventHandle(), + m_hSendCompletionEvent.GetEventHandle(), + m_hReadyToSendEvent.GetEventHandle() + }; + int nHandles = ARRAYSIZE( handles ); + + // While waiting for send completion, don't handle "ready to send" events. + if ( m_bWaitingForSendCompletion ) + --nHandles; + + DWORD waitValue = WaitForMultipleObjects( nHandles, handles, FALSE, SEND_KEEPALIVE_INTERVAL ); + switch ( waitValue ) + { + case WAIT_TIMEOUT: + { + if ( g_bHandleTimeouts ) + { + // We haven't sent anything in a bit. Send out a keepalive. + SendThread_HandleTimeout(); + } + } + break; + + case WAIT_OBJECT_0: + { + // The main thread is signaling us to exit. + return 0; + } + + case WAIT_OBJECT_0 + 1: + { + if ( !SendThread_HandleSendCompletionEvent() ) + return 1; + } + break; + + case WAIT_OBJECT_0 + 2: + { + if ( !SendThread_HandleReadyToSendEvent() ) + return 1; + } + break; + + case WAIT_FAILED: + { + // Uh oh. We're dead. Cleanup and signal an error. + HandleError( GetLastError() ); + return 1; + } + + default: + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Unknown return value (%lu) from WaitForMultipleObjects", waitValue ); + HandleError( ITCPSocketHandler::SocketError, str ); + return 0; + } + } + } + + return 0; + } + + static DWORD WINAPI StaticSendThreadFn( LPVOID pParameter ) + { + return ((CThreadedTCPSocket*)pParameter)->SendThreadFn(); + } + + +// Receive thread functionality. +private: + + bool RecvThread_WaitToReceiveSize() + { + return RecvThread_InternalRecv( &m_NextPacketLen, sizeof( m_NextPacketLen ), false, true ); + } + + + bool RecvThread_InternalHandleRecvCompletion( DWORD dwTransfer ) + { + int cbTransfer = (int)dwTransfer; + int nBytesWanted = m_nBytesToReceive - m_nBytesReceivedSoFar; + if ( cbTransfer > nBytesWanted ) + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Invalid # bytes received (%d) in recv thread (should be %d)", cbTransfer, m_nBytesToReceive ); + HandleError( ITCPSocketHandler::SocketError, str ); + return false; + } + else if ( cbTransfer < nBytesWanted ) + { + // We have to reissue the receive command because it didn't receive all the data. + m_nBytesReceivedSoFar += cbTransfer; + + char *pDest = (char*)&m_NextPacketLen; + if ( !m_bWaitingForSize ) + { + Assert( m_pRecvBuffer ); + pDest = m_pRecvBuffer->m_Data; + } + + return RecvThread_InternalRecv( &pDest[m_nBytesReceivedSoFar], m_nBytesToReceive - m_nBytesReceivedSoFar, true ); + } + + if ( m_bWaitingForSize ) + { + // If we were waiting for size, now wait for the data. + if ( m_NextPacketLen == KEEPALIVE_SENTINEL ) + { + // Ok, it was just a keepalive. Wait for size again. + return RecvThread_WaitToReceiveSize(); + } + else + { + if ( m_NextPacketLen < 1 || m_NextPacketLen > 1024*1024*75 ) + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Invalid packet size in RecvThread (size = %d)", m_NextPacketLen ); + HandleError( ITCPSocketHandler::SocketError, str ); + return false; + } + else + { + Assert( !m_pRecvBuffer ); + m_pRecvBuffer = (CTCPPacket*)malloc( sizeof( CTCPPacket ) - 1 + m_NextPacketLen ); + m_pRecvBuffer->m_UserData = 0; + m_pRecvBuffer->m_Len = m_NextPacketLen; + + return RecvThread_InternalRecv( m_pRecvBuffer->m_Data, m_pRecvBuffer->m_Len, false, false ); + } + } + } + else + { + // Got a packet! Give it to the app. + m_pHandler->OnPacketReceived( m_pRecvBuffer ); + m_pRecvBuffer = NULL; + + return RecvThread_WaitToReceiveSize(); + } + } + + bool RecvThread_HandleRecvCompletionEvent() + { + // A send operation just completed. Now do the next one. + DWORD cbTransfer, flags; + if ( !WSAGetOverlappedResult( m_Socket, &m_RecvOverlapped, &cbTransfer, TRUE, &flags ) ) + { + HandleError( WSAGetLastError() ); + return false; + } + + return RecvThread_InternalHandleRecvCompletion( cbTransfer ); + } + + bool RecvThread_InternalRecv( void *pDest, int destSize, bool bContinuation, bool bWaitingForSize = false ) + { + WSABUF buf = { destSize, (char*)pDest }; + + if ( !bContinuation ) + { + // If this is not a continuation of whatever we were receiving before, then + m_bWaitingForSize = bWaitingForSize; + m_nBytesToReceive = destSize; + m_nBytesReceivedSoFar = 0; + } + + DWORD dwFlags = 0; + DWORD nBytesReceived = 0; + DWORD ret = WSARecv( m_Socket, &buf, 1, &nBytesReceived, &dwFlags, &m_RecvOverlapped, NULL ); + DWORD dwLastError = WSAGetLastError(); + if ( ret == 0 || ( ret == SOCKET_ERROR && dwLastError == WSA_IO_PENDING ) ) + { + // Note: m_hRecvEvent is in a signaled state, so the RecvThread will pick up the results next time around. + return true; + } + else + { + HandleError( dwLastError ); + return false; + } + } + + + + DWORD RecvThreadFn() + { + // Start us off by setting up to receive the first packet size. + if ( !RecvThread_WaitToReceiveSize() ) + return 1; + + HANDLE handles[] = + { + m_hExitThreadsEvent.GetEventHandle(), + m_hRecvEvent.GetEventHandle() + }; + + while ( 1 ) + { + DWORD waitValue = WaitForMultipleObjects( ARRAYSIZE( handles ), handles, FALSE, KEEPALIVE_TIMEOUT ); + switch ( waitValue ) + { + case WAIT_TIMEOUT: + { + if ( g_bHandleTimeouts ) + { + HandleError( ITCPSocketHandler::ConnectionTimedOut, "Connection timed out" ); + return 1; + } + } + break; + + case WAIT_OBJECT_0: + { + // We're being told to exit. + return 0; + } + + case WAIT_OBJECT_0 + 1: + { + // Just finished receiving something. + if ( !RecvThread_HandleRecvCompletionEvent() ) + return 1; + } + break; + + case WAIT_FAILED: + { + // Uh oh. We're dead. Cleanup and signal an error. + HandleError( GetLastError() ); + return 1; + } + + default: + { + char str[512]; + Q_snprintf( str, sizeof( str ), "Unknown return value (%lu) from WaitForMultipleObjects", waitValue ); + HandleError( ITCPSocketHandler::SocketError, str ); + return 1; + } + } + } + + return 0; + } + + static DWORD WINAPI StaticRecvThreadFn( LPVOID pParameter ) + { + return ((CThreadedTCPSocket*)pParameter)->RecvThreadFn(); + } + + + +// Error handling. +private: + + // This checks to see if either thread has signaled an error. If so, it shuts down the socket and returns true. + bool CheckErrorSignal() + { + return m_bErrorSignal; + } + + // This is called from any of the threads and signals that something went awry. It shuts down the object + // and makes it return false from all of its functions. + void HandleError( DWORD errorValue ) + { + char *lpMsgBuf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (char*)&lpMsgBuf, + 0, + NULL + ); + + // Windows likes to stick a carriage return in there and we don't want it so get rid of it. + int len = strlen( lpMsgBuf ); + while ( len > 0 && ( lpMsgBuf[len-1] == '\n' || lpMsgBuf[len-1] == '\r' ) ) + { + --len; + lpMsgBuf[len] = 0; + } + + HandleError( ITCPSocketHandler::SocketError, lpMsgBuf ); + + LocalFree( lpMsgBuf ); + } + + + void HandleError( int errorCode, const char *pErrorString ) + { + //Assert( false ); + + // Tell the app. + m_pHandler->OnError( errorCode, pErrorString ); + + // Tell the threads to exit. + m_hExitThreadsEvent.SetEvent(); + + // Notify the main thread so it can call Term() when it gets a chance. + m_bErrorSignal = true; + } + + +private: + + // Data for the send thread. + typedef struct + { + //WSAOVERLAPPED m_Overlapped; + int m_Len; + char m_Payload[1]; + } SendData_t; + + HANDLE m_hSendThread; + WSAOVERLAPPED m_SendOverlapped; + CEvent m_hReadyToSendEvent; + CEvent m_hSendCompletionEvent; + + CCriticalSection m_SendCS; + DWORD m_nBytesToTransfer; + bool m_bWaitingForSendCompletion; + CUtlLinkedList<SendData_t*, int> m_SendDatas; // Added to the tail, popped off the head for sending. + + + // Data for the recv thread. + HANDLE m_hRecvThread; + int m_nBytesToReceive; // This stores how many bytes we want to receive for the next packet. + int m_nBytesReceivedSoFar; // This stores how many bytes we've received so far. + bool m_bWaitingForSize; // This tells if we're trying to receive the next packet length or the next packet's data. + + int m_NextPacketLen; // Data is received INTO here before it receives each packet. This + // holds the length of each incoming packet. + + WSAOVERLAPPED m_RecvOverlapped; + CEvent m_hRecvEvent; + CTCPPacket *m_pRecvBuffer; // This is allocated for each packet we're receiving and given to the + // app when the packet is done being received. + + + volatile bool m_bErrorSignal; + + + CEvent m_hExitThreadsEvent; + + ITCPSocketHandler *m_pHandler; + + SOCKET m_Socket; + CIPAddr m_RemoteAddr; +}; + + +// ------------------------------------------------------------------------------------------------ // +// CTCPConnectSocket_Listener +// ------------------------------------------------------------------------------------------------ // +class CTCPConnectSocket_Listener : public ITCPConnectSocket +{ +public: + CTCPConnectSocket_Listener() + { + m_Socket = INVALID_SOCKET; + } + + + virtual ~CTCPConnectSocket_Listener() + { + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + } + } + + + // The main function to create one of these suckers. + static ITCPConnectSocket* Create( + IHandlerCreator *pHandlerCreator, + const unsigned short port, + int nQueueLength + ) + { + CTCPConnectSocket_Listener *pRet = new CTCPConnectSocket_Listener; + if ( !pRet ) + return NULL; + + if ( nQueueLength < 0 ) + { + Error( "CTCPConnectSocket_Listener::Create - SOMAXCONN not allowed - causes some XP SP2 systems to stop receiving any network data (systemwide)." ); + } + + // Bind it to a socket and start listening. + CIPAddr addr( 0, 0, 0, 0, port ); // INADDR_ANY + pRet->m_Socket = TCPBind( &addr ); + if ( pRet->m_Socket == INVALID_SOCKET || + listen( pRet->m_Socket, nQueueLength == -1 ? SOMAXCONN : nQueueLength ) != 0 ) + { + pRet->Release(); + return false; + } + + pRet->m_pHandler = pHandlerCreator; + return pRet; + } + + +// ITCPConnectSocket implementation. +public: + + virtual void Release() + { + delete this; + } + + virtual bool Update( IThreadedTCPSocket **pSocket, unsigned long milliseconds ) + { + *pSocket = NULL; + if ( m_Socket == INVALID_SOCKET ) + return false; + + // We're still ok.. just wait until the socket becomes writable (is connected) or we timeout. + fd_set readSet; + readSet.fd_count = 1; + readSet.fd_array[0] = m_Socket; + TIMEVAL timeVal = {0, milliseconds*1000}; + + // Wait until it connects. + int status = select( 0, &readSet, NULL, NULL, &timeVal ); + if ( status > 0 ) + { + sockaddr_in addr; + int addrSize = sizeof( addr ); + + // Now accept the final connection. + SOCKET newSock = accept( m_Socket, (struct sockaddr*)&addr, &addrSize ); + if ( newSock == INVALID_SOCKET ) + { + Assert( false ); + return true; + } + else + { + CIPAddr connectedAddr; + SockAddrToIPAddr( &addr, &connectedAddr ); + + IThreadedTCPSocket *pRet = CThreadedTCPSocket::Create( newSock, connectedAddr, m_pHandler->CreateNewHandler() ); + if ( !pRet ) + { + Assert( false ); + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + return false; + } + + *pSocket = pRet; + return true; + } + } + else if ( status == SOCKET_ERROR ) + { + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + return false; + } + else + { + return true; + } + } + + +private: + SOCKET m_Socket; + + IHandlerCreator *m_pHandler; +}; + + + +ITCPConnectSocket* ThreadedTCP_CreateListener( + IHandlerCreator *pHandlerCreator, + const unsigned short port, + int nQueueLength + ) +{ + return CTCPConnectSocket_Listener::Create( pHandlerCreator, port, nQueueLength ); +} + + + +// ------------------------------------------------------------------------------------------------ // +// CTCPConnectSocket_Connector +// ------------------------------------------------------------------------------------------------ // +class CTCPConnectSocket_Connector : public ITCPConnectSocket +{ +public: + + CTCPConnectSocket_Connector() + { + m_bConnected = false; + m_Socket = INVALID_SOCKET; + m_bError = false; + } + + virtual ~CTCPConnectSocket_Connector() + { + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + } + } + + static ITCPConnectSocket* Create( + const CIPAddr &connectAddr, + const CIPAddr &localAddr, + IHandlerCreator *pHandlerCreator + ) + { + CTCPConnectSocket_Connector *pRet = new CTCPConnectSocket_Connector; + + pRet->m_Socket = TCPBind( &localAddr ); + if ( pRet->m_Socket == INVALID_SOCKET ) + { + pRet->Release(); + return NULL; + } + + sockaddr_in addr; + IPAddrToSockAddr( &connectAddr, &addr ); + + // We don't want the connect() call to block. + DWORD val = 1; + int status = ioctlsocket( pRet->m_Socket, FIONBIO, &val ); + if ( status != 0 ) + { + Assert( false ); + pRet->Release(); + return NULL; + } + + pRet->m_RemoteAddr = connectAddr; + pRet->m_pHandlerCreator = pHandlerCreator; + + int ret = connect( pRet->m_Socket, (struct sockaddr*)&addr, sizeof( addr ) ); + if ( ret == 0 ) + { + pRet->m_bConnected = true; + return pRet; + } + else if ( ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK ) + { + return pRet; + } + else + { + Assert( false ); + pRet->Release(); + return NULL; + } + } + + +// ITCPConnectSocket implementation. +public: + + virtual void Release() + { + delete this; + } + + + virtual bool Update( IThreadedTCPSocket **pSocket, unsigned long milliseconds ) + { + *pSocket = NULL; + + // If we got an error previously, keep returning false. + if ( m_bError ) + return false; + + // If this condition holds, then we already returned a valid socket and we're just waiting to be released. + if ( m_Socket == INVALID_SOCKET ) + return true; + + // Ok, see if we're connected now. + if ( !m_bConnected ) + { + TIMEVAL timeVal = { 0, milliseconds*1000 }; + + fd_set writeSet; + writeSet.fd_count = 1; + writeSet.fd_array[0] = m_Socket; + + int ret = select( 0, NULL, &writeSet, NULL, &timeVal ); + if ( ret > 0 ) + { + m_bConnected = true; + } + else if ( ret == SOCKET_ERROR ) + { + return EnterErrorMode(); + } + } + + if ( m_bConnected ) + { + // Ok, return a connected socket for them. + + // Make our socket blocking again. + DWORD val = 0; + int status = ioctlsocket( m_Socket, FIONBIO, &val ); + if ( status != 0 ) + { + Assert( false ); + m_bError = true; + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + return false; + } + + IThreadedTCPSocket *pRet = CThreadedTCPSocket::Create( m_Socket, m_RemoteAddr, m_pHandlerCreator->CreateNewHandler() ); + if ( pRet ) + { + m_Socket = INVALID_SOCKET; + *pSocket = pRet; + return true; + } + else + { + return EnterErrorMode(); + } + } + else + { + // Still waiting.. + return true; + } + } + + // Shutdown the socket and start returning false from Update(). + bool EnterErrorMode() + { + Assert( false ); + m_bError = true; + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + return false; + } + + +private: + + bool m_bError; + bool m_bConnected; + + SOCKET m_Socket; + CIPAddr m_RemoteAddr; + + IHandlerCreator *m_pHandlerCreator; +}; + + +ITCPConnectSocket* ThreadedTCP_CreateConnector( + const CIPAddr &addr, + const CIPAddr &localAddr, + IHandlerCreator *pHandlerCreator + ) +{ + return CTCPConnectSocket_Connector::Create( addr, localAddr, pHandlerCreator ); +} + + +void ThreadedTCP_EnableTimeouts( bool bEnable ) +{ + g_bHandleTimeouts = bEnable; +} + + +void ThreadedTCP_SetTCPSocketThreadPriorities( bool bSetTCPSocketThreadPriorities ) +{ + g_bSetTCPSocketThreadPriorities = bSetTCPSocketThreadPriorities; +} + diff --git a/utils/vmpi/ThreadedTCPSocketEmu.cpp b/utils/vmpi/ThreadedTCPSocketEmu.cpp new file mode 100644 index 0000000..f8257a5 --- /dev/null +++ b/utils/vmpi/ThreadedTCPSocketEmu.cpp @@ -0,0 +1,344 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <windows.h> +#include "tcpsocket.h" +#include "IThreadedTCPSocket.h" +#include "ThreadedTCPSocketEmu.h" +#include "ThreadHelpers.h" + + + +// ---------------------------------------------------------------------------------------- // +// CThreadedTCPSocketEmu. This uses IThreadedTCPSocket to emulate the polling-type interface +// in ITCPSocket. +// ---------------------------------------------------------------------------------------- // + +// This class uses the IThreadedTCPSocket interface to emulate the old ITCPSocket. +class CThreadedTCPSocketEmu : public ITCPSocket, public ITCPSocketHandler, public IHandlerCreator +{ +public: + + CThreadedTCPSocketEmu() + { + m_pSocket = NULL; + m_LocalPort = 0xFFFF; + m_pConnectSocket = NULL; + m_RecvPacketsEvent.Init( false, false ); + m_bError = false; + } + + virtual ~CThreadedTCPSocketEmu() + { + Term(); + } + + void Init( IThreadedTCPSocket *pSocket ) + { + m_pSocket = pSocket; + } + + void Term() + { + if ( m_pSocket ) + { + m_pSocket->Release(); + m_pSocket = NULL; + } + + if ( m_pConnectSocket ) + { + m_pConnectSocket->Release(); + m_pConnectSocket = NULL; + } + } + + +// ITCPSocketHandler implementation. +private: + + + virtual void OnPacketReceived( CTCPPacket *pPacket ) + { + CCriticalSectionLock csLock( &m_RecvPacketsCS ); + csLock.Lock(); + + m_RecvPackets.AddToTail( pPacket ); + m_RecvPacketsEvent.SetEvent(); + } + + + virtual void OnError( int errorCode, const char *pErrorString ) + { + CCriticalSectionLock csLock( &m_ErrorStringCS ); + csLock.Lock(); + + m_ErrorString.CopyArray( pErrorString, strlen( pErrorString ) + 1 ); + m_bError = true; + } + + +// IHandlerCreator implementation. +public: + + // This is used for connecting. + virtual ITCPSocketHandler* CreateNewHandler() + { + return this; + } + + +// ITCPSocket implementation. +public: + + virtual void Release() + { + delete this; + } + + virtual bool BindToAny( const unsigned short port ) + { + m_LocalPort = port; + return true; + } + + virtual bool BeginConnect( const CIPAddr &addr ) + { + // They should have "bound" to a port before trying to connect. + Assert( m_LocalPort != 0xFFFF ); + + if ( m_pConnectSocket ) + m_pConnectSocket->Release(); + + m_pConnectSocket = ThreadedTCP_CreateConnector( + addr, + CIPAddr( 0, 0, 0, 0, m_LocalPort ), + this ); + + return m_pConnectSocket != 0; + } + + virtual bool UpdateConnect() + { + Assert( !m_pSocket ); + if ( !m_pConnectSocket ) + return false; + + if ( m_pConnectSocket->Update( &m_pSocket ) ) + { + if ( m_pSocket ) + { + // Ok, we're connected now. + m_pConnectSocket->Release(); + m_pConnectSocket = NULL; + return true; + } + else + { + return false; + } + } + else + { + Assert( false ); + m_pConnectSocket->Release(); + m_pConnectSocket = NULL; + return false; + } + } + + virtual bool IsConnected() + { + if ( m_bError ) + { + Term(); + return false; + } + else + { + return m_pSocket != NULL; + } + } + + virtual void GetDisconnectReason( CUtlVector<char> &reason ) + { + CCriticalSectionLock csLock( &m_ErrorStringCS ); + csLock.Lock(); + + reason = m_ErrorString; + } + + virtual bool Send( const void *pData, int size ) + { + Assert( m_pSocket ); + if ( !m_pSocket ) + return false; + + return m_pSocket->Send( pData, size ); + } + + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) + { + Assert( m_pSocket ); + if ( !m_pSocket || !m_pSocket->IsValid() ) + return false; + + return m_pSocket->SendChunks( pChunks, pChunkLengths, nChunks ); + } + + virtual bool Recv( CUtlVector<unsigned char> &data, double flTimeout ) + { + // Use our m_RecvPacketsEvent event to determine if there is data to receive yet. + DWORD nMilliseconds = (DWORD)( flTimeout * 1000.0f ); + DWORD ret = WaitForSingleObject( m_RecvPacketsEvent.GetEventHandle(), nMilliseconds ); + if ( ret == WAIT_OBJECT_0 ) + { + // Ok, there's a packet. + CCriticalSectionLock csLock( &m_RecvPacketsCS ); + csLock.Lock(); + + Assert( m_RecvPackets.Count() > 0 ); + + int iHead = m_RecvPackets.Head(); + CTCPPacket *pPacket = m_RecvPackets[ iHead ]; + + data.CopyArray( (const unsigned char*)pPacket->GetData(), pPacket->GetLen() ); + + pPacket->Release(); + m_RecvPackets.Remove( iHead ); + + // Re-set the event if there are more packets left to receive. + if ( m_RecvPackets.Count() > 0 ) + { + m_RecvPacketsEvent.SetEvent(); + } + + return true; + } + else + { + return false; + } + } + + +private: + + IThreadedTCPSocket *m_pSocket; + + unsigned short m_LocalPort; // The port we bind to when we want to connect. + ITCPConnectSocket *m_pConnectSocket; + + + // All the received data is stored in here. + CEvent m_RecvPacketsEvent; + CCriticalSection m_RecvPacketsCS; + CUtlLinkedList<CTCPPacket*, int> m_RecvPackets; + + CCriticalSection m_ErrorStringCS; + CUtlVector<char> m_ErrorString; + bool m_bError; // Set to true when there's an error. Next chance we get in the main thread, we'll close the socket. +}; + + +ITCPSocket* CreateTCPSocketEmu() +{ + return new CThreadedTCPSocketEmu; +} + + +// ---------------------------------------------------------------------------------------- // +// CThreadedTCPListenSocketEmu implementation. +// ---------------------------------------------------------------------------------------- // + +class CThreadedTCPListenSocketEmu : public ITCPListenSocket, public IHandlerCreator +{ +public: + CThreadedTCPListenSocketEmu() + { + m_pListener = NULL; + m_pLastCreatedSocket = NULL; + } + + virtual ~CThreadedTCPListenSocketEmu() + { + if ( m_pListener ) + m_pListener->Release(); + } + + bool StartListening( const unsigned short port, int nQueueLength ) + { + m_pListener = ThreadedTCP_CreateListener( + this, + port, + nQueueLength ); + + return m_pListener != 0; + } + + +// ITCPListenSocket implementation. +private: + + virtual void Release() + { + delete this; + } + + virtual ITCPSocket* UpdateListen( CIPAddr *pAddr ) + { + if ( !m_pListener ) + return NULL; + + IThreadedTCPSocket *pSocket; + if ( m_pListener->Update( &pSocket ) && pSocket ) + { + *pAddr = pSocket->GetRemoteAddr(); + + // This is pretty hacky, but this stuff is just around for test code. + CThreadedTCPSocketEmu *pLast = m_pLastCreatedSocket; + pLast->Init( pSocket ); + m_pLastCreatedSocket = NULL; + return pLast; + } + else + { + return NULL; + } + } + + +// IHandlerCreator implementation. +private: + + virtual ITCPSocketHandler* CreateNewHandler() + { + m_pLastCreatedSocket = new CThreadedTCPSocketEmu; + return m_pLastCreatedSocket; + } + + +private: + + ITCPConnectSocket *m_pListener; + CThreadedTCPSocketEmu *m_pLastCreatedSocket; +}; + +ITCPListenSocket* CreateTCPListenSocketEmu( const unsigned short port, int nQueueLength ) +{ + CThreadedTCPListenSocketEmu *pSocket = new CThreadedTCPListenSocketEmu; + if ( pSocket->StartListening( port, nQueueLength ) ) + { + return pSocket; + } + else + { + delete pSocket; + return NULL; + } +} + diff --git a/utils/vmpi/ThreadedTCPSocketEmu.h b/utils/vmpi/ThreadedTCPSocketEmu.h new file mode 100644 index 0000000..1ed1380 --- /dev/null +++ b/utils/vmpi/ThreadedTCPSocketEmu.h @@ -0,0 +1,26 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADEDTCPSOCKETEMU_H +#define THREADEDTCPSOCKETEMU_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tcpsocket.h" + + +// This creates a class that's based on IThreadedTCPSocket, but emulates the old ITCPSocket interface. +// This is used for stress-testing IThreadedTCPSocket. +ITCPSocket* CreateTCPSocketEmu(); + + +ITCPListenSocket* CreateTCPListenSocketEmu( const unsigned short port, int nQueueLength = -1 ); + + +#endif // THREADEDTCPSOCKETEMU_H diff --git a/utils/vmpi/WaitAndRestart/StdAfx.cpp b/utils/vmpi/WaitAndRestart/StdAfx.cpp new file mode 100644 index 0000000..bac634d --- /dev/null +++ b/utils/vmpi/WaitAndRestart/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// WaitAndRestart.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/WaitAndRestart/StdAfx.h b/utils/vmpi/WaitAndRestart/StdAfx.h new file mode 100644 index 0000000..954e1f8 --- /dev/null +++ b/utils/vmpi/WaitAndRestart/StdAfx.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__6E874DA8_5D18_47D5_B557_3E07B6171907__INCLUDED_) +#define AFX_STDAFX_H__6E874DA8_5D18_47D5_B557_3E07B6171907__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> +#include <stdio.h> +#include <stdlib.h> +#include <conio.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__6E874DA8_5D18_47D5_B557_3E07B6171907__INCLUDED_) diff --git a/utils/vmpi/WaitAndRestart/WaitAndRestart.cpp b/utils/vmpi/WaitAndRestart/WaitAndRestart.cpp new file mode 100644 index 0000000..c9ef1e0 --- /dev/null +++ b/utils/vmpi/WaitAndRestart/WaitAndRestart.cpp @@ -0,0 +1,166 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// WaitAndRestart.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "tier1/strtools.h" +#include "vmpi_defs.h" + + +void PrintLog( const char *pMsg, ... ) +{ +#ifdef VMPI_SERVICE_LOGS + char str[4096]; + va_list marker; + + va_start( marker, pMsg ); + _vsnprintf( str, sizeof( str ), pMsg, marker ); + va_end( marker ); + + printf( "%s", str ); + + static FILE *fp = fopen( "c:\\vmpi_WaitAndRestart.log", "wt" ); + if ( fp ) + { + fprintf( fp, "%s", str ); + fflush( fp ); + } +#endif +} + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + return err; +} + + +int main( int argc, char* argv[] ) +{ +Sleep(5000); + if ( argc < 4 ) + { + PrintLog( "WaitAndRestart <seconds to wait> <working directory> command line...\n" ); + return 1; + } + + PrintLog( "WaitAndRestart <seconds to wait> <working directory> command line...\n" ); + + + const char *pTimeToWait = argv[1]; + const char *pWorkingDir = argv[2]; + + // If a * precedes the time-to-wait arg, then it's a process ID and we wait for that process to exit. + if ( pTimeToWait[0] == '*' ) + { + ++pTimeToWait; + DWORD dwProcessId; + sscanf( pTimeToWait, "%lu", &dwProcessId ); + + PrintLog( "Waiting for process %lu to exit. Press a key to cancel...\n", dwProcessId ); + + HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | SYNCHRONIZE, false, dwProcessId ); + if ( hProcess ) + { + while ( 1 ) + { + DWORD val = WaitForSingleObject( hProcess, 100 ); + if ( val == WAIT_OBJECT_0 ) + { + break; + } + else if ( val == WAIT_ABANDONED ) + { + PrintLog( "Got WAIT_ABANDONED (error). Waiting 5 seconds, then continuing.\n" ); + Sleep( 5000 ); + break; + } + + if ( kbhit() ) + return 2; + } + PrintLog( "Process %lu terminated. Continuing.\n", dwProcessId ); + } + else + { + PrintLog( "Process %lu not running. Continuing.\n", dwProcessId ); + } + + CloseHandle( hProcess ); + } + else + { + DWORD timeToWait = (DWORD)atoi( argv[1] ); + + PrintLog( "\n\nWaiting for %d seconds to launch ' ", timeToWait ); + PrintLog( "%s> ", pWorkingDir ); + for ( int i=3; i < argc; i++ ) + { + PrintLog( "%s ", argv[i] ); + } + PrintLog( "'\n\nPress a key to cancel... " ); + + DWORD startTime = GetTickCount(); + while ( GetTickCount() - startTime < (timeToWait*1000) ) + { + if ( kbhit() ) + return 2; + + Sleep( 100 ); + } + } + + // Ok, launch it! + char commandLine[1024] = {0}; + for ( int i=3; i < argc; i++ ) + { + Q_strncat( commandLine, "\"", sizeof( commandLine ), COPY_ALL_CHARACTERS ); + Q_strncat( commandLine, argv[i], sizeof( commandLine ), COPY_ALL_CHARACTERS ); + Q_strncat( commandLine, "\" ", sizeof( commandLine ), COPY_ALL_CHARACTERS ); + } + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( CreateProcess( + NULL, + commandLine, + NULL, // security + NULL, + FALSE, + 0, // flags + NULL, // environment + pWorkingDir, // current directory + &si, + &pi ) ) + { + PrintLog( "Process started.\n" ); + CloseHandle( pi.hThread ); // We don't care what the process does. + CloseHandle( pi.hProcess ); + } + else + { + PrintLog( "CreateProcess error!\n%s", GetLastErrorString() ); + } + + return 0; +} + diff --git a/utils/vmpi/WaitAndRestart/waitandrestart.vpc b/utils/vmpi/WaitAndRestart/waitandrestart.vpc new file mode 100644 index 0000000..8bf43ba --- /dev/null +++ b/utils/vmpi/WaitAndRestart/waitandrestart.vpc @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// WAITANDRESTART.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "WaitAndRestart" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_win32_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } +} + +$Project "WaitAndRestart" +{ + $Folder "Source Files" + { + $File "WaitAndRestart.cpp" + } + + $Folder "Header Files" + { + } +} diff --git a/utils/vmpi/ZLib.lib b/utils/vmpi/ZLib.lib Binary files differnew file mode 100644 index 0000000..23c3dfd --- /dev/null +++ b/utils/vmpi/ZLib.lib diff --git a/utils/vmpi/ZLib/deflate.h b/utils/vmpi/ZLib/deflate.h new file mode 100644 index 0000000..962676d --- /dev/null +++ b/utils/vmpi/ZLib/deflate.h @@ -0,0 +1,318 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-1998 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef _DEFLATE_H +#define _DEFLATE_H + +#include "zutil.h" + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define INIT_STATE 42 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + int pending; /* nb of bytes in the pending buffer */ + int noheader; /* suppress zlib header and adler32 */ + Byte data_type; /* UNKNOWN, BINARY or ASCII */ + Byte method; /* STORED (for zip only) or DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to supress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + int last_eob_len; /* bit length of EOB code for last block */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + + /* in trees.c */ +void _tr_init OF((deflate_state *s)); +int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); +void _tr_align OF((deflate_state *s)); +void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch _length_code[]; + extern uch _dist_code[]; +#else + extern const uch _length_code[]; + extern const uch _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif diff --git a/utils/vmpi/ZLib/infblock.h b/utils/vmpi/ZLib/infblock.h new file mode 100644 index 0000000..bd25c80 --- /dev/null +++ b/utils/vmpi/ZLib/infblock.h @@ -0,0 +1,39 @@ +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state FAR inflate_blocks_statef; + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLongf *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Bytef *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); diff --git a/utils/vmpi/ZLib/infcodes.h b/utils/vmpi/ZLib/infcodes.h new file mode 100644 index 0000000..6c750d8 --- /dev/null +++ b/utils/vmpi/ZLib/infcodes.h @@ -0,0 +1,27 @@ +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state FAR inflate_codes_statef; + +extern inflate_codes_statef *inflate_codes_new OF(( + uInt, uInt, + inflate_huft *, inflate_huft *, + z_streamp )); + +extern int inflate_codes OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +extern void inflate_codes_free OF(( + inflate_codes_statef *, + z_streamp )); + diff --git a/utils/vmpi/ZLib/inffast.h b/utils/vmpi/ZLib/inffast.h new file mode 100644 index 0000000..8facec5 --- /dev/null +++ b/utils/vmpi/ZLib/inffast.h @@ -0,0 +1,17 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +extern int inflate_fast OF(( + uInt, + uInt, + inflate_huft *, + inflate_huft *, + inflate_blocks_statef *, + z_streamp )); diff --git a/utils/vmpi/ZLib/inffixed.h b/utils/vmpi/ZLib/inffixed.h new file mode 100644 index 0000000..77f7e76 --- /dev/null +++ b/utils/vmpi/ZLib/inffixed.h @@ -0,0 +1,151 @@ +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +local uInt fixed_bl = 9; +local uInt fixed_bd = 5; +local inflate_huft fixed_tl[] = { + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} + }; +local inflate_huft fixed_td[] = { + {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, + {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, + {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, + {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, + {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, + {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, + {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, + {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} + }; diff --git a/utils/vmpi/ZLib/inftrees.h b/utils/vmpi/ZLib/inftrees.h new file mode 100644 index 0000000..85853e0 --- /dev/null +++ b/utils/vmpi/ZLib/inftrees.h @@ -0,0 +1,58 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s FAR inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; /* number of extra bits or operation */ + Byte Bits; /* number of bits in this code or subcode */ + } what; + uInt pad; /* pad structure to a power of 2 (4 bytes for */ + } word; /* 16-bit, 8 bytes for 32-bit int's) */ + uInt base; /* literal, length base, distance base, + or table offset */ +}; + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1004 huft structures (850 for length/literals + and 154 for distances, the latter actually the result of an + exhaustive search). The actual maximum is not known, but the + value below is more than safe. */ +#define MANY 1440 + +extern int inflate_trees_bits OF(( + uIntf *, /* 19 code lengths */ + uIntf *, /* bits tree desired/actual depth */ + inflate_huft * FAR *, /* bits tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_dynamic OF(( + uInt, /* number of literal/length codes */ + uInt, /* number of distance codes */ + uIntf *, /* that many (total) code lengths */ + uIntf *, /* literal desired/actual bit depth */ + uIntf *, /* distance desired/actual bit depth */ + inflate_huft * FAR *, /* literal/length tree result */ + inflate_huft * FAR *, /* distance tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_fixed OF(( + uIntf *, /* literal desired/actual bit depth */ + uIntf *, /* distance desired/actual bit depth */ + inflate_huft * FAR *, /* literal/length tree result */ + inflate_huft * FAR *, /* distance tree result */ + z_streamp)); /* for memory allocation */ diff --git a/utils/vmpi/ZLib/infutil.h b/utils/vmpi/ZLib/infutil.h new file mode 100644 index 0000000..99d1135 --- /dev/null +++ b/utils/vmpi/ZLib/infutil.h @@ -0,0 +1,98 @@ +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { + TYPE, /* get type bits (3, including end bit) */ + LENS, /* get lengths for stored */ + STORED, /* processing stored block */ + TABLE, /* get table lengths */ + BTREE, /* get bit lengths tree for a dynamic block */ + DTREE, /* get length, distance trees for a dynamic block */ + CODES, /* processing fixed or dynamic block */ + DRY, /* output remaining window bytes */ + DONE, /* finished last block, done */ + BAD} /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + + /* mode */ + inflate_block_mode mode; /* current inflate_block mode */ + + /* mode dependent information */ + union { + uInt left; /* if STORED, bytes left to copy */ + struct { + uInt table; /* table lengths (14 bits) */ + uInt index; /* index into blens (or border) */ + uIntf *blens; /* bit lengths of codes */ + uInt bb; /* bit length tree depth */ + inflate_huft *tb; /* bit length decoding tree */ + } trees; /* if DTREE, decoding info for trees */ + struct { + inflate_codes_statef + *codes; + } decode; /* if CODES, current state */ + } sub; /* submode */ + uInt last; /* true if this block is the last block */ + + /* mode independent information */ + uInt bitk; /* bits in bit buffer */ + uLong bitb; /* bit buffer */ + inflate_huft *hufts; /* single malloc for tree space */ + Bytef *window; /* sliding window */ + Bytef *end; /* one byte after sliding window */ + Bytef *read; /* window read pointer */ + Bytef *write; /* window write pointer */ + check_func checkfn; /* check function */ + uLong check; /* check on output */ + +}; + + +/* defines for inflate input/output */ +/* update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/* get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<<k;k+=8;}} +#define DUMPBITS(j) {b>>=(j);k-=(j);} +/* output bytes */ +#define WAVAIL (uInt)(q<s->read?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/* load local pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +extern uInt inflate_mask[17]; + +/* copy as much as possible from the sliding window to the output area */ +extern int inflate_flush OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +struct internal_state {int dummy;}; /* for buggy compilers */ + +#endif diff --git a/utils/vmpi/ZLib/trees.h b/utils/vmpi/ZLib/trees.h new file mode 100644 index 0000000..72facf9 --- /dev/null +++ b/utils/vmpi/ZLib/trees.h @@ -0,0 +1,128 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + diff --git a/utils/vmpi/ZLib/zconf.h b/utils/vmpi/ZLib/zconf.h new file mode 100644 index 0000000..6d450fc --- /dev/null +++ b/utils/vmpi/ZLib/zconf.h @@ -0,0 +1,279 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateReset z_inflateReset +# define compress z_compress +# define compress2 z_compress2 +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table + +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif +#if defined(__GNUC__) || defined(WIN32) || defined(__386__) || defined(i386) +# ifndef __32BIT__ +# define __32BIT__ +# endif +#endif +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#if defined(MSDOS) && !defined(__32BIT__) +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#if (defined(MSDOS) || defined(_WINDOWS) || defined(WIN32)) && !defined(STDC) +# define STDC +#endif +#if defined(__STDC__) || defined(__cplusplus) || defined(__OS2__) +# ifndef STDC +# define STDC +# endif +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__) || defined(applec) ||defined(THINK_C) ||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Old Borland C incorrectly complains about missing returns: */ +#if defined(__BORLANDC__) && (__BORLANDC__ < 0x500) +# define NEED_DUMMY_RETURN +#endif + + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#if (defined(M_I86SM) || defined(M_I86MM)) && !defined(__32BIT__) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +#endif +#if defined(__BORLANDC__) && (defined(__SMALL__) || defined(__MEDIUM__)) +# ifndef __32BIT__ +# define SMALL_MEDIUM +# define FAR _far +# endif +#endif + +/* Compile with -DZLIB_DLL for Windows DLL support */ +#if defined(ZLIB_DLL) +# if defined(_WINDOWS) || defined(WINDOWS) +# ifdef FAR +# undef FAR +# endif +# include <windows.h> +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR _cdecl _export +# endif +# endif +# if defined (__BORLANDC__) +# if (__BORLANDC__ >= 0x0500) && defined (WIN32) +# include <windows.h> +# define ZEXPORT __declspec(dllexport) WINAPI +# define ZEXPORTRVA __declspec(dllexport) WINAPIV +# else +# if defined (_Windows) && defined (__DLL__) +# define ZEXPORT _export +# define ZEXPORTVA _export +# endif +# endif +# endif +#endif + +#if defined (__BEOS__) +# if defined (ZLIB_DLL) +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +#endif + +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif +#ifndef ZEXTERN +# define ZEXTERN extern +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(MACOS) && !defined(TARGET_OS_MAC) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#ifdef HAVE_UNISTD_H +# include <sys/types.h> /* for off_t */ +# include <unistd.h> /* for SEEK_* and off_t */ +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(inflate_blocks,"INBL") +# pragma map(inflate_blocks_new,"INBLNE") +# pragma map(inflate_blocks_free,"INBLFR") +# pragma map(inflate_blocks_reset,"INBLRE") +# pragma map(inflate_codes_free,"INCOFR") +# pragma map(inflate_codes,"INCO") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_flush,"INFLU") +# pragma map(inflate_mask,"INMA") +# pragma map(inflate_set_dictionary,"INSEDI2") +# pragma map(inflate_copyright,"INCOPY") +# pragma map(inflate_trees_bits,"INTRBI") +# pragma map(inflate_trees_dynamic,"INTRDY") +# pragma map(inflate_trees_fixed,"INTRFI") +# pragma map(inflate_trees_free,"INTRFR") +#endif + +#endif /* _ZCONF_H */ diff --git a/utils/vmpi/ZLib/zlib.h b/utils/vmpi/ZLib/zlib.h new file mode 100644 index 0000000..49f56b4 --- /dev/null +++ b/utils/vmpi/ZLib/zlib.h @@ -0,0 +1,893 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.1.3, July 9th, 1998 + + Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef _ZLIB_H +#define _ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.1.3" + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: ascii or binary */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + the compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + 0.1% larger than avail_in plus 12 bytes. If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update data_type if it can make a good guess about + the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may some + introduce some output latency (reading input without producing any output) + except when forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much + output as possible to the output buffer. The flushing behavior of inflate is + not specified for values of the flush parameter other than Z_SYNC_FLUSH + and Z_FINISH, but the current implementation actually flushes as much output + as possible anyway. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster routine + may be used for the single inflate() call. + + If a preset dictionary is needed at this point (see inflateSetDictionary + below), inflate sets strm-adler to the adler32 checksum of the + dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise + it sets strm->adler to the adler32 checksum of all output produced + so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or + an error code as described below. At the end of the stream, inflate() + checks that its computed adler32 checksum is equal to that saved by the + compressor and returns Z_STREAM_END only if the checksum is correct. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect + adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent + (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if no progress is possible or if there was not + enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR + case, the application may then call inflateSync to look for a good + compression block. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match). Filtered data consists mostly of small values with a + somewhat random distribution. In this case, the compression algorithm is + tuned to compress them better. The effect of Z_FILTERED is to force more + Huffman coding and less string matching; it is somewhat intermediate + between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects + the compression ratio but not the correctness of the compressed output even + if it is not set appropriately. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. + + Upon return of this function, strm->adler is set to the Adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. If a compressed stream with a larger window size is given as + input, inflate() will return with the error code Z_DATA_ERROR instead of + trying to allocate a larger window. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative + memLevel). msg is set to null if there is no error message. inflateInit2 + does not perform any decompression apart from reading the zlib header if + present: this will be done by inflate(). (So next_in and avail_in may be + modified, but next_out and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate + if this call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler32 value returned by this call of + inflate. The compressor and decompressor must use exactly the same + dictionary (see deflateSetDictionary). + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); + +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running crc with the bytes buf[0..len-1] and return the updated + crc. If buf is NULL, this function returns the required initial value + for the crc. Pre- and post-conditioning (one's complement) is performed + within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(_Z_UTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int err)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZLIB_H */ diff --git a/utils/vmpi/ZLib/zutil.h b/utils/vmpi/ZLib/zutil.h new file mode 100644 index 0000000..6f2cb97 --- /dev/null +++ b/utils/vmpi/ZLib/zutil.h @@ -0,0 +1,220 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef _Z_UTIL_H +#define _Z_UTIL_H + +#include "zlib.h" + +#ifdef STDC +# include <stddef.h> +# include <string.h> +# include <stdlib.h> +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include <errno.h> +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#ifdef MSDOS +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include <alloc.h> +# endif +# else /* MSC or DJGPP */ +# include <malloc.h> +# endif +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +#endif + +#ifdef WIN32 /* Window 95 & Windows NT */ +# define OS_CODE 0x0b +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include <unix.h> /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0F +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# define fdopen(fd,type) _fdopen(fd,type) +#endif + + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#ifdef HAVE_STRERROR + extern char *strerror OF((int)); +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include <stdio.h> + extern int z_verbose; + extern void z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +typedef uLong (ZEXPORT *check_func) OF((uLong check, const Bytef *buf, + uInt len)); +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* _Z_UTIL_H */ diff --git a/utils/vmpi/ichannel.h b/utils/vmpi/ichannel.h new file mode 100644 index 0000000..a26f034 --- /dev/null +++ b/utils/vmpi/ichannel.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ICHANNEL_H +#define ICHANNEL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utlvector.h" + + +class IChannel +{ +public: + // Note: this also releases any channels contained inside. So if you make a reliable + // channel that contains an unreliable channel and release the reliable one, + // it will automatically release the unreliable one it contains. + virtual void Release() = 0; + + // Send data to the destination. + virtual bool Send( const void *pData, int len ) = 0; + + // This version puts all the chunks into one packet and ships it off. + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Check for any packets coming in from the destination. + // Returns false if no packet was received. + // + // flTimeout can be used to make it wait for data. + // + // Note: this is most efficient if you keep the buffer around between calls so it only + // reallocates it when it needs more space. + virtual bool Recv( CUtlVector<unsigned char> &data, double flTimeout=0 ) = 0; + + // Returns false if the connection has been broken. + virtual bool IsConnected() = 0; + + // If IsConnected returns false, you can call this to find out why the socket got disconnected. + virtual void GetDisconnectReason( CUtlVector<char> &reason ) = 0; +}; + + +#endif // ICHANNEL_H diff --git a/utils/vmpi/idle_dialog.cpp b/utils/vmpi/idle_dialog.cpp new file mode 100644 index 0000000..5518fff --- /dev/null +++ b/utils/vmpi/idle_dialog.cpp @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "idle_dialog.h" + + +#define WM_STARTIDLE (WM_USER + 565) + + +BEGIN_MESSAGE_MAP(CIdleDialog, CDialog) + //{{AFX_MSG_MAP(CVMPIBrowserDlg) + ON_MESSAGE(WM_STARTIDLE, OnStartIdle) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +CIdleDialog::CIdleDialog( int id, CWnd *pParent ) + : CDialog( id, pParent ) +{ +} + + +void CIdleDialog::StartIdleProcessing( DWORD msInterval ) +{ + m_cWinIdle.StartIdle( GetSafeHwnd(), WM_STARTIDLE, 0, 0, msInterval ); + m_cWinIdle.NextIdle(); +} + + +LONG CIdleDialog::OnStartIdle( UINT, LONG ) +{ + MSG msg; + + if ( !PeekMessage( &msg, GetSafeHwnd(), 0,0, PM_NOREMOVE ) ) + OnIdle(); + + m_cWinIdle.NextIdle(); + return 0; +} + + diff --git a/utils/vmpi/idle_dialog.h b/utils/vmpi/idle_dialog.h new file mode 100644 index 0000000..5dbe20c --- /dev/null +++ b/utils/vmpi/idle_dialog.h @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IDLE_DIALOG_H +#define IDLE_DIALOG_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "win_idle.h" + + +// +// This is a base class that provides in-thread idle processing. +// +// To use it: +// 1. derive from it +// 2. Change your message map to point at this class instead of CDialog +// 3. Call StartIdleProcessing to begin receiving idle calls. +// 4. Override OnIdle(). +// + +class CIdleDialog : public CDialog +{ +public: + + CIdleDialog( int id, CWnd *pParent ); + + // Call this to start the idle processing. + void StartIdleProcessing( DWORD msInterval ); + + virtual void OnIdle() = 0; + + +private: + DECLARE_MESSAGE_MAP() + afx_msg LONG OnStartIdle(UINT, LONG); + + CWinIdle m_cWinIdle; +}; + + +#endif // IDLE_DIALOG_H diff --git a/utils/vmpi/imysqlwrapper.h b/utils/vmpi/imysqlwrapper.h new file mode 100644 index 0000000..2604128 --- /dev/null +++ b/utils/vmpi/imysqlwrapper.h @@ -0,0 +1,115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQL_WRAPPER_H +#define MYSQL_WRAPPER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" +#include "interface.h" + + +class IMySQLRowSet; + + +class CColumnValue +{ +public: + + CColumnValue( IMySQLRowSet *pSQL, int iColumn ); + + const char* String(); + long Int32(); + +private: + IMySQLRowSet *m_pSQL; + int m_iColumn; +}; + + + +class IMySQLRowSet +{ +public: + virtual void Release() = 0; + + // Get the number of columns in the data returned from the last query (if it was a select statement). + virtual int NumFields() = 0; + + // Get the name of each column returned by the last query. + virtual const char* GetFieldName( int iColumn ) = 0; + + // Call this in a loop until it returns false to iterate over all rows the query returned. + virtual bool NextRow() = 0; + + // You can call this to start iterating over the result set from the start again. + // Note: after calling this, you have to call NextRow() to actually get the first row's value ready. + virtual bool SeekToFirstRow() = 0; + + virtual CColumnValue GetColumnValue( int iColumn ) = 0; + virtual CColumnValue GetColumnValue( const char *pColumnName ) = 0; + + virtual const char* GetColumnValue_String( int iColumn ) = 0; + virtual long GetColumnValue_Int( int iColumn ) = 0; + + // You can call this to get the index of a column for faster lookups with GetColumnValue( int ). + // Returns -1 if the column can't be found. + virtual int GetColumnIndex( const char *pColumnName ) = 0; +}; + + +class IMySQL : public IMySQLRowSet +{ +public: + virtual bool InitMySQL( const char *pDBName, const char *pHostName="", const char *pUserName="", const char *pPassword="" ) = 0; + virtual void Release() = 0; + + // These execute SQL commands. They return 0 if the query was successful. + virtual int Execute( const char *pString ) = 0; + + // This reads in all of the data in the last row set you queried with Execute and builds a separate + // copy. This is useful in some of the VMPI tools to have a thread repeatedly execute a slow query, then + // store off the results for the main thread to parse. + virtual IMySQLRowSet* DuplicateRowSet() = 0; + + // If you just inserted rows into a table with an AUTO_INCREMENT column, + // then this returns the (unique) value of that column. + virtual unsigned long InsertID() = 0; + + // Returns the last error message, if an error took place + virtual const char * GetLastError() = 0; +}; + + +#define MYSQL_WRAPPER_VERSION_NAME "MySQLWrapper001" + + +// ------------------------------------------------------------------------------------------------ // +// Inlines. +// ------------------------------------------------------------------------------------------------ // + +inline CColumnValue::CColumnValue( IMySQLRowSet *pSQL, int iColumn ) +{ + m_pSQL = pSQL; + m_iColumn = iColumn; +} + +inline const char* CColumnValue::String() +{ + return m_pSQL->GetColumnValue_String( m_iColumn ); +} + +inline long CColumnValue::Int32() +{ + return m_pSQL->GetColumnValue_Int( m_iColumn ); +} + + +#endif // MYSQL_WRAPPER_H diff --git a/utils/vmpi/iphelpers.cpp b/utils/vmpi/iphelpers.cpp new file mode 100644 index 0000000..119bba8 --- /dev/null +++ b/utils/vmpi/iphelpers.cpp @@ -0,0 +1,610 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#pragma warning (disable:4127) +#include <winsock2.h> +#include <ws2tcpip.h> +#pragma warning (default:4127) + +#include "iphelpers.h" +#include "basetypes.h" +#include <assert.h> +#include "utllinkedlist.h" +#include "utlvector.h" +#include "tier1/strtools.h" + + +// This automatically calls WSAStartup for the app at startup. +class CIPStarter +{ +public: + CIPStarter() + { + WSADATA wsaData; + WSAStartup( WINSOCK_VERSION, &wsaData ); + } +}; +static CIPStarter g_Starter; + + +unsigned long SampleMilliseconds() +{ + CCycleCount cnt; + cnt.Sample(); + return cnt.GetMilliseconds(); +} + + +// ------------------------------------------------------------------------------------------ // +// CChunkWalker. +// ------------------------------------------------------------------------------------------ // + +CChunkWalker::CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ) +{ + m_TotalLength = 0; + for ( int i=0; i < nChunks; i++ ) + m_TotalLength += pChunkLengths[i]; + + m_iCurChunk = 0; + m_iCurChunkPos = 0; + m_pChunks = pChunks; + m_pChunkLengths = pChunkLengths; + m_nChunks = nChunks; +} + +int CChunkWalker::GetTotalLength() const +{ + return m_TotalLength; +} + +void CChunkWalker::CopyTo( void *pOut, int nBytes ) +{ + unsigned char *pOutPos = (unsigned char*)pOut; + + int nBytesLeft = nBytes; + while ( nBytesLeft > 0 ) + { + int toCopy = nBytesLeft; + int curChunkLen = m_pChunkLengths[m_iCurChunk]; + + int amtLeft = curChunkLen - m_iCurChunkPos; + if ( nBytesLeft > amtLeft ) + { + toCopy = amtLeft; + } + + unsigned char *pCurChunkData = (unsigned char*)m_pChunks[m_iCurChunk]; + memcpy( pOutPos, &pCurChunkData[m_iCurChunkPos], toCopy ); + nBytesLeft -= toCopy; + pOutPos += toCopy; + + // Slide up to the next chunk if we're done with the one we're on. + m_iCurChunkPos += toCopy; + assert( m_iCurChunkPos <= curChunkLen ); + if ( m_iCurChunkPos == curChunkLen ) + { + ++m_iCurChunk; + m_iCurChunkPos = 0; + if ( m_iCurChunk == m_nChunks ) + { + assert( nBytesLeft == 0 ); + } + } + } +} + + +// ------------------------------------------------------------------------------------------ // +// CWaitTimer +// ------------------------------------------------------------------------------------------ // + +bool g_bForceWaitTimers = false; + +CWaitTimer::CWaitTimer( double flSeconds ) +{ + m_StartTime = SampleMilliseconds(); + m_WaitMS = (unsigned long)( flSeconds * 1000.0 ); +} + + +bool CWaitTimer::ShouldKeepWaiting() +{ + if ( m_WaitMS == 0 ) + { + return false; + } + else + { + return ( SampleMilliseconds() - m_StartTime ) <= m_WaitMS || g_bForceWaitTimers; + } +} + + + +// ------------------------------------------------------------------------------------------ // +// CIPAddr. +// ------------------------------------------------------------------------------------------ // + +CIPAddr::CIPAddr() +{ + Init( 0, 0, 0, 0, 0 ); +} + + +CIPAddr::CIPAddr( const int inputIP[4], const int inputPort ) +{ + Init( inputIP[0], inputIP[1], inputIP[2], inputIP[3], inputPort ); +} + + +CIPAddr::CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ) +{ + Init( ip0, ip1, ip2, ip3, ipPort ); +} + + +void CIPAddr::Init( int ip0, int ip1, int ip2, int ip3, int ipPort ) +{ + ip[0] = (unsigned char)ip0; + ip[1] = (unsigned char)ip1; + ip[2] = (unsigned char)ip2; + ip[3] = (unsigned char)ip3; + port = (unsigned short)ipPort; +} + +bool CIPAddr::operator==( const CIPAddr &o ) const +{ + return ip[0] == o.ip[0] && ip[1] == o.ip[1] && ip[2] == o.ip[2] && ip[3] == o.ip[3] && port == o.port; +} + + +bool CIPAddr::operator!=( const CIPAddr &o ) const +{ + return !( *this == o ); +} + + +void CIPAddr::SetupLocal( int inPort ) +{ + ip[0] = 0x7f; + ip[1] = 0; + ip[2] = 0; + ip[3] = 1; + port = inPort; +} + + +// ------------------------------------------------------------------------------------------ // +// Static helpers. +// ------------------------------------------------------------------------------------------ // + +static double IP_FloatTime() +{ + CCycleCount cnt; + cnt.Sample(); + return cnt.GetSeconds(); +} + +TIMEVAL SetupTimeVal( double flTimeout ) +{ + TIMEVAL timeVal; + timeVal.tv_sec = (long)flTimeout; + timeVal.tv_usec = (long)( (flTimeout - (long)flTimeout) * 1000.0 ); + return timeVal; +} + +// Convert a CIPAddr to a sockaddr_in. +void IPAddrToInAddr( const CIPAddr *pIn, in_addr *pOut ) +{ + u_char *p = (u_char*)pOut; + p[0] = pIn->ip[0]; + p[1] = pIn->ip[1]; + p[2] = pIn->ip[2]; + p[3] = pIn->ip[3]; +} + +// Convert a CIPAddr to a sockaddr_in. +void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ) +{ + memset( pOut, 0, sizeof(*pOut) ); + pOut->sin_family = AF_INET; + pOut->sin_port = htons( pIn->port ); + + IPAddrToInAddr( pIn, &pOut->sin_addr ); +} + +// Convert a CIPAddr to a sockaddr_in. +void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ) +{ + const u_char *p = (const u_char*)&pIn->sin_addr; + pOut->ip[0] = p[0]; + pOut->ip[1] = p[1]; + pOut->ip[2] = p[2]; + pOut->ip[3] = p[3]; + pOut->port = ntohs( pIn->sin_port ); +} + + +class CIPSocket : public ISocket +{ +public: + CIPSocket() + { + m_Socket = INVALID_SOCKET; + m_bSetupToBroadcast = false; + } + + virtual ~CIPSocket() + { + Term(); + } + + +// ISocket implementation. +public: + + virtual void Release() + { + delete this; + } + + + virtual bool CreateSocket() + { + // Clear any old socket we had around. + Term(); + + // Create a socket to send and receive through. + SOCKET sock = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP ); + if ( sock == INVALID_SOCKET ) + { + Assert( false ); + return false; + } + + // Nonblocking please.. + int status; + DWORD val = 1; + status = ioctlsocket( sock, FIONBIO, &val ); + if ( status != 0 ) + { + assert( false ); + closesocket( sock ); + return false; + } + + m_Socket = sock; + return true; + } + + + // Called after we have a socket. + virtual bool BindPart2( const CIPAddr *pAddr ) + { + Assert( m_Socket != INVALID_SOCKET ); + + // bind to it! + sockaddr_in addr; + IPAddrToSockAddr( pAddr, &addr ); + + int status = bind( m_Socket, (sockaddr*)&addr, sizeof(addr) ); + if ( status == 0 ) + { + return true; + } + else + { + Term(); + return false; + } + } + + + virtual bool Bind( const CIPAddr *pAddr ) + { + if ( !CreateSocket() ) + return false; + + return BindPart2( pAddr ); + } + + virtual bool BindToAny( const unsigned short port ) + { + // (INADDR_ANY) + CIPAddr addr; + addr.ip[0] = addr.ip[1] = addr.ip[2] = addr.ip[3] = 0; + addr.port = port; + return Bind( &addr ); + } + + virtual bool ListenToMulticastStream( const CIPAddr &addr, const CIPAddr &localInterface ) + { + ip_mreq mr; + IPAddrToInAddr( &addr, &mr.imr_multiaddr ); + IPAddrToInAddr( &localInterface, &mr.imr_interface ); + + // This helps a lot if the stream is sending really fast. + int rcvBuf = 1024*1024*2; + setsockopt( m_Socket, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, sizeof( rcvBuf ) ); + + if ( setsockopt( m_Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mr, sizeof( mr ) ) == 0 ) + { + // Remember this so we do IP_DEL_MEMBERSHIP on shutdown. + m_bMulticastGroupMembership = true; + m_MulticastGroupMREQ = mr; + return true; + } + else + { + return false; + } + } + + virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) + { + assert( m_Socket != INVALID_SOCKET ); + + // Make sure we're setup to broadcast. + if ( !m_bSetupToBroadcast ) + { + BOOL bBroadcast = true; + if ( setsockopt( m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof( bBroadcast ) ) != 0 ) + { + assert( false ); + return false; + } + + m_bSetupToBroadcast = true; + } + + CIPAddr addr; + addr.ip[0] = addr.ip[1] = addr.ip[2] = addr.ip[3] = 0xFF; + addr.port = port; + return SendTo( &addr, pData, len ); + } + + virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) + { + return SendChunksTo( pAddr, &pData, &len, 1 ); + } + + virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) + { + WSABUF bufs[32]; + if ( nChunks > 32 ) + { + Error( "CIPSocket::SendChunksTo: too many chunks (%d).", nChunks ); + } + + int nTotalBytes = 0; + for ( int i=0; i < nChunks; i++ ) + { + bufs[i].len = pChunkLengths[i]; + bufs[i].buf = (char*)pChunks[i]; + nTotalBytes += pChunkLengths[i]; + } + + assert( m_Socket != INVALID_SOCKET ); + + // Translate the address. + sockaddr_in addr; + IPAddrToSockAddr( pAddr, &addr ); + + DWORD dwNumBytesSent = 0; + DWORD ret = WSASendTo( + m_Socket, + bufs, + nChunks, + &dwNumBytesSent, + 0, + (sockaddr*)&addr, + sizeof( addr ), + NULL, + NULL + ); + + return ret == 0 && (int)dwNumBytesSent == nTotalBytes; + } + + virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) + { + assert( m_Socket != INVALID_SOCKET ); + + fd_set readSet; + readSet.fd_count = 1; + readSet.fd_array[0] = m_Socket; + + TIMEVAL timeVal = SetupTimeVal( 0 ); + + // See if it has a packet waiting. + int status = select( 0, &readSet, NULL, NULL, &timeVal ); + if ( status == 0 || status == SOCKET_ERROR ) + return -1; + + // Get the data. + sockaddr_in sender; + int fromSize = sizeof( sockaddr_in ); + status = recvfrom( m_Socket, (char*)pData, maxDataLen, 0, (struct sockaddr*)&sender, &fromSize ); + if ( status == 0 || status == SOCKET_ERROR ) + { + return -1; + } + else + { + if ( pFrom ) + { + SockAddrToIPAddr( &sender, pFrom ); + } + + m_flLastRecvTime = IP_FloatTime(); + return status; + } + } + + virtual double GetRecvTimeout() + { + return IP_FloatTime() - m_flLastRecvTime; + } + + +private: + + void Term() + { + if ( m_Socket != INVALID_SOCKET ) + { + if ( m_bMulticastGroupMembership ) + { + // Undo our multicast group membership. + setsockopt( m_Socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&m_MulticastGroupMREQ, sizeof( m_MulticastGroupMREQ ) ); + } + + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + } + + m_bSetupToBroadcast = false; + m_bMulticastGroupMembership = false; + } + + +private: + + SOCKET m_Socket; + + bool m_bMulticastGroupMembership; // Did we join a multicast group? + ip_mreq m_MulticastGroupMREQ; + + bool m_bSetupToBroadcast; + double m_flLastRecvTime; + bool m_bListenSocket; +}; + + + +ISocket* CreateIPSocket() +{ + return new CIPSocket; +} + + +ISocket* CreateMulticastListenSocket( + const CIPAddr &addr, + const CIPAddr &localInterface ) +{ + CIPSocket *pSocket = new CIPSocket; + + CIPAddr bindAddr = localInterface; + bindAddr.port = addr.port; + + if ( pSocket->Bind( &bindAddr ) && + pSocket->ListenToMulticastStream( addr, localInterface ) + ) + { + return pSocket; + } + else + { + pSocket->Release(); + return NULL; + } +} + + +bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ) +{ + char ipStr[512]; + + const char *pColon = strchr( pStr, ':' ); + if ( pColon ) + { + int toCopy = pColon - pStr; + if ( toCopy < 2 || toCopy > sizeof(ipStr)-1 ) + { + assert( false ); + return false; + } + + memcpy( ipStr, pStr, toCopy ); + ipStr[toCopy] = 0; + + pOut->port = (unsigned short)atoi( pColon+1 ); + } + else + { + strncpy( ipStr, pStr, sizeof( ipStr ) ); + ipStr[ sizeof(ipStr)-1 ] = 0; + } + + if ( ipStr[0] >= '0' && ipStr[0] <= '9' ) + { + // It's numbers. + int ip[4]; + sscanf( ipStr, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3] ); + pOut->ip[0] = (unsigned char)ip[0]; + pOut->ip[1] = (unsigned char)ip[1]; + pOut->ip[2] = (unsigned char)ip[2]; + pOut->ip[3] = (unsigned char)ip[3]; + } + else + { + // It's a text string. + struct hostent *pHost = gethostbyname( ipStr ); + if( !pHost ) + return false; + + pOut->ip[0] = pHost->h_addr_list[0][0]; + pOut->ip[1] = pHost->h_addr_list[0][1]; + pOut->ip[2] = pHost->h_addr_list[0][2]; + pOut->ip[3] = pHost->h_addr_list[0][3]; + } + + return true; +} + + +bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ) +{ + in_addr addr; + addr.S_un.S_un_b.s_b1 = pIn->ip[0]; + addr.S_un.S_un_b.s_b2 = pIn->ip[1]; + addr.S_un.S_un_b.s_b3 = pIn->ip[2]; + addr.S_un.S_un_b.s_b4 = pIn->ip[3]; + + HOSTENT *pEnt = gethostbyaddr( (char*)&addr, sizeof( addr ), AF_INET ); + if ( pEnt ) + { + Q_strncpy( pOut, pEnt->h_name, outLen ); + return true; + } + else + { + return false; + } +} + + +void IP_GetLastErrorString( char *pStr, int maxLen ) +{ + char *lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + Q_strncpy( pStr, lpMsgBuf, maxLen ); + LocalFree( lpMsgBuf ); +} + diff --git a/utils/vmpi/iphelpers.h b/utils/vmpi/iphelpers.h new file mode 100644 index 0000000..acd356a --- /dev/null +++ b/utils/vmpi/iphelpers.h @@ -0,0 +1,162 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef IPHELPERS_H +#define IPHELPERS_H + + +#include "ichannel.h" + + +// Loops that poll sockets should Sleep for this amount of time between iterations +// so they don't hog all the CPU. +#define LOOP_POLL_INTERVAL 5 + + +// Useful for putting the arguments into a printf statement. +#define EXPAND_ADDR( x ) (x).ip[0], (x).ip[1], (x).ip[2], (x).ip[3], (x).port + + +// This is a simple wrapper layer for UDP sockets. +class CIPAddr +{ +public: + CIPAddr(); + CIPAddr( const int inputIP[4], const int inputPort ); + CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ); + + void Init( int ip0, int ip1, int ip2, int ip3, int ipPort ); + bool operator==( const CIPAddr &o ) const; + bool operator!=( const CIPAddr &o ) const; + + // Setup to send to the local machine on the specified port. + void SetupLocal( int inPort ); + +public: + + unsigned char ip[4]; + unsigned short port; +}; + + + +// The chunk walker provides an easy way to copy data out of the chunks as though it were a +// single contiguous chunk of memory.s +class CChunkWalker +{ +public: + CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ); + + int GetTotalLength() const; + void CopyTo( void *pOut, int nBytes ); + +private: + + void const * const *m_pChunks; + const int *m_pChunkLengths; + int m_nChunks; + + int m_iCurChunk; + int m_iCurChunkPos; + + int m_TotalLength; +}; + + +// This class makes loop that wait on something look nicer. ALL loops using this class +// should follow this pattern, or they can wind up with unforeseen delays that add a whole +// lot of lag. +// +// CWaitTimer waitTimer( 5.0 ); +// while ( 1 ) +// { +// do your thing here like Recv() from a socket. +// +// if ( waitTimer.ShouldKeepWaiting() ) +// Sleep() for some time interval like 5ms so you don't hog the CPU +// else +// BREAK HERE +// } +class CWaitTimer +{ +public: + CWaitTimer( double flSeconds ); + + bool ShouldKeepWaiting(); + +private: + unsigned long m_StartTime; + unsigned long m_WaitMS; +}; + + +// Helper function to get time in milliseconds. +unsigned long SampleMilliseconds(); + + +class ISocket +{ +public: + + // Call this when you're done. + virtual void Release() = 0; + + + // Bind the socket so you can send and receive with it. + // If you bind to port 0, then the system will select the port for you. + virtual bool Bind( const CIPAddr *pAddr ) = 0; + virtual bool BindToAny( const unsigned short port ) = 0; + + + // Broadcast some data. + virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) = 0; + + // Send a packet. + virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) = 0; + virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Receive a packet. Returns the length received or -1 if no data came in. + // If pFrom is set, then it is filled in with the sender's IP address. + virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) = 0; + + // How long has it been since we successfully received a packet? + virtual double GetRecvTimeout() = 0; +}; + +// Create a connectionless socket that you can send packets out of. +ISocket* CreateIPSocket(); + +// This sets up the socket to receive multicast data on the specified group. +// By default, localInterface is INADDR_ANY, but if you want to specify a specific interface +// the data should come in through, you can. +ISocket* CreateMulticastListenSocket( + const CIPAddr &addr, + const CIPAddr &localInterface = CIPAddr() + ); + + +// Setup a CIPAddr from the string. The string can be a dotted IP address or +// a hostname, and it can be followed by a colon and a port number like "1.2.3.4:3443" +// or "myhostname.valvesoftware.com:2342". +// +// Note: if the string does not contain a port, then pOut->port will be left alone. +bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ); + +// Do a DNS lookup on the IP. +// You can optionally get a service name back too. +bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ); + + +void IP_GetLastErrorString( char *pStr, int maxLen ); + +void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ); +void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ); + + +#endif + diff --git a/utils/vmpi/loopback_channel.cpp b/utils/vmpi/loopback_channel.cpp new file mode 100644 index 0000000..79b2fa6 --- /dev/null +++ b/utils/vmpi/loopback_channel.cpp @@ -0,0 +1,98 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "loopback_channel.h" +#include "utllinkedlist.h" +#include "iphelpers.h" + + +// -------------------------------------------------------------------------------- // +// CLoopbackChannel. +// -------------------------------------------------------------------------------- // + +typedef struct +{ + int m_Len; + unsigned char m_Data[1]; +} LoopbackMsg_t; + + +class CLoopbackChannel : public IChannel +{ +public: + + virtual ~CLoopbackChannel() + { + FOR_EACH_LL( m_Messages, i ) + { + free( m_Messages[i] ); + } + m_Messages.Purge(); + } + + virtual void Release() + { + delete this; + } + + virtual bool Send( const void *pData, int len ) + { + const void *pChunks[1] = { pData }; + int chunkLengths[1] = { len }; + return SendChunks( pChunks, chunkLengths, 1 ); + } + + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) + { + CChunkWalker walker( pChunks, pChunkLengths, nChunks ); + + LoopbackMsg_t *pMsg = (LoopbackMsg_t*)malloc( sizeof( LoopbackMsg_t ) - 1 + walker.GetTotalLength() ); + walker.CopyTo( pMsg->m_Data, walker.GetTotalLength() ); + pMsg->m_Len = walker.GetTotalLength(); + m_Messages.AddToTail( pMsg ); + return true; + } + + virtual bool Recv( CUtlVector<unsigned char> &data, double flTimeout ) + { + int iNext = m_Messages.Head(); + if ( iNext == m_Messages.InvalidIndex() ) + { + return false; + } + else + { + LoopbackMsg_t *pMsg = m_Messages[iNext]; + + data.CopyArray( pMsg->m_Data, pMsg->m_Len ); + + free( pMsg ); + m_Messages.Remove( iNext ); + + return true; + } + } + + virtual bool IsConnected() + { + return true; + } + + virtual void GetDisconnectReason( CUtlVector<char> &reason ) + { + } + +private: + CUtlLinkedList<LoopbackMsg_t*,int> m_Messages; // FIFO for messages we've sent. +}; + + +IChannel* CreateLoopbackChannel() +{ + return new CLoopbackChannel; +} + diff --git a/utils/vmpi/loopback_channel.h b/utils/vmpi/loopback_channel.h new file mode 100644 index 0000000..137efbe --- /dev/null +++ b/utils/vmpi/loopback_channel.h @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef LOOPBACK_CHANNEL_H +#define LOOPBACK_CHANNEL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ichannel.h" + + +// Loopback sockets receive the same data they send. +IChannel* CreateLoopbackChannel(); + + +#endif // LOOPBACK_CHANNEL_H diff --git a/utils/vmpi/messagemgr.cpp b/utils/vmpi/messagemgr.cpp new file mode 100644 index 0000000..35d5e53 --- /dev/null +++ b/utils/vmpi/messagemgr.cpp @@ -0,0 +1,300 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <windows.h> +#include "messagemgr.h" +#include "tcpsocket.h" +#include "iphelpers.h" +#include "tier0/platform.h" +#include "threadhelpers.h" + + +#define MSGMGR_LISTEN_PORT_FIRST 22512 +#define MSGMGR_LISTEN_PORT_LAST 22520 + + + +#define BROADCAST_INTERVAL 2 // Broadcast our presence every N seconds. + +#define NUM_QUEUED_MESSAGES 200 + + + +class CMessageMgr : public IMessageMgr +{ +public: + CMessageMgr(); + ~CMessageMgr(); + + bool Init(); + void Term(); + + +// IMessageMgr overrides. +public: + + virtual void Print( const char *pMsg ); + + + +private: + + DWORD ThreadFn(); + static DWORD WINAPI StaticThreadFn( LPVOID pParameter ); + + +private: + + // Only our thread touches this, NOT the main thread. + CUtlLinkedList<ITCPSocket*,int> m_Sockets; + + HANDLE m_hThread; + DWORD m_dwThreadID; + + HANDLE m_hExitObj; // This is signalled when we want the thread to exit. + HANDLE m_hExitResponseObj; // The thread sets this when it exits. + + HANDLE m_hMessageObj; // This signals to the thread that there's a message to send. + HANDLE m_hMessageSentObj; // This signals back to the main thread that the message was sent. + const char *m_pMessageText; // The text to send. + + // This is only touched by the thread. + CUtlLinkedList<char*,int> m_MessageQ; // FIFO of NUM_QUEUED_MESSAGES. + + ITCPListenSocket *m_pListenSocket; + int m_iListenPort; + + ISocket *m_pBroadcastSocket; + double m_flLastBroadcast; + +}; + + + +CMessageMgr::CMessageMgr() +{ + m_pBroadcastSocket = NULL; + m_pListenSocket = NULL; + m_hThread = NULL; + m_hExitObj = m_hExitResponseObj = m_hMessageObj = m_hMessageSentObj = NULL; +} + + +CMessageMgr::~CMessageMgr() +{ + Term(); +} + + +bool CMessageMgr::Init() +{ + m_hExitObj = CreateEvent( NULL, false, false, NULL ); + m_hExitResponseObj = CreateEvent( NULL, false, false, NULL ); + m_hMessageObj = CreateEvent( NULL, false, false, NULL ); + m_hMessageSentObj = CreateEvent( NULL, false, false, NULL ); + if ( !m_hExitObj || !m_hExitResponseObj || !m_hMessageObj || !m_hMessageSentObj ) + return false; + + // Create the broadcast socket. + m_pBroadcastSocket = CreateIPSocket(); + if ( !m_pBroadcastSocket ) + return false; + + if ( !m_pBroadcastSocket->BindToAny( 0 ) ) + return false; + + + // Create the listen socket. + m_pListenSocket = NULL; + for ( m_iListenPort=MSGMGR_LISTEN_PORT_FIRST; m_iListenPort <= MSGMGR_LISTEN_PORT_LAST; m_iListenPort++ ) + { + m_pListenSocket = CreateTCPListenSocket( m_iListenPort ); + if ( m_pListenSocket ) + break; + } + if ( !m_pListenSocket ) + return false; + + + // Create our broadcast/connection thread. + m_flLastBroadcast = 0; + m_hThread = CreateThread( + NULL, + 0, + &CMessageMgr::StaticThreadFn, + this, + 0, + &m_dwThreadID ); + + if ( !m_hThread ) + return false; + + Plat_SetThreadName( m_dwThreadID, "MessageMgr" ); + return true; +} + + +void CMessageMgr::Term() +{ + // Wait for the thread to exit? + if ( m_hThread ) + { + DWORD dwExitCode = 0; + if ( GetExitCodeThread( m_hThread, &dwExitCode ) && dwExitCode == STILL_ACTIVE ) + { + SetEvent( m_hExitObj ); + WaitForSingleObject( m_hExitResponseObj, INFINITE ); + } + + CloseHandle( m_hThread ); + m_hThread = NULL; + } + + CloseHandle( m_hExitObj ); + m_hExitObj = NULL; + + CloseHandle( m_hExitResponseObj ); + m_hExitResponseObj = NULL; + + CloseHandle( m_hMessageObj ); + m_hMessageObj = NULL; + + CloseHandle( m_hMessageSentObj ); + m_hMessageSentObj = NULL; + + if ( m_pListenSocket ) + { + m_pListenSocket->Release(); + m_pListenSocket = NULL; + } + + if ( m_pBroadcastSocket ) + { + m_pBroadcastSocket->Release(); + m_pBroadcastSocket = NULL; + } +} + + +void CMessageMgr::Print( const char *pMsg ) +{ + m_pMessageText = pMsg; + SetEvent( m_hMessageObj ); + WaitForSingleObject( m_hMessageSentObj, INFINITE ); +} + + +DWORD CMessageMgr::ThreadFn() +{ + while ( 1 ) + { + // Broadcast our presence? + double flCurTime = Plat_FloatTime(); + if ( flCurTime - m_flLastBroadcast >= BROADCAST_INTERVAL ) + { + // Broadcast our presence. + char msg[9]; + msg[0] = MSGMGR_PACKETID_ANNOUNCE_PRESENCE; + *((int*)&msg[1]) = MSGMGR_VERSION; + *((int*)&msg[5]) = m_iListenPort; + m_pBroadcastSocket->Broadcast( msg, sizeof( msg ), MSGMGR_BROADCAST_PORT ); + + m_flLastBroadcast = flCurTime; + } + + + // Accept new connections. + CIPAddr addr; + ITCPSocket *pConn = m_pListenSocket->UpdateListen( &addr ); + if ( pConn ) + { + // Send what's in our queue. + FOR_EACH_LL( m_MessageQ, iQ ) + { + char *pMsg = m_MessageQ[iQ]; + int bufLen = strlen( pMsg ) + 1; + + char packetID = MSGMGR_PACKETID_MSG; + const void *data[2] = { &packetID, pMsg }; + int len[2] = { 1, bufLen }; + + // Send it out to our sockets. + pConn->SendChunks( data, len, 2 ); + } + + m_Sockets.AddToTail( pConn ); + } + + + // Should we exit? + HANDLE handles[2] = {m_hExitObj, m_hMessageObj}; + DWORD ret = WaitForMultipleObjects( 2, handles, FALSE, 200 ); + if ( ret == WAIT_OBJECT_0 ) + { + break; + } + else if ( ret == (WAIT_OBJECT_0+1) ) + { + // Add it to the queue. + int index; + if ( m_MessageQ.Count() >= NUM_QUEUED_MESSAGES ) + { + index = m_MessageQ.Tail(); + delete m_MessageQ[index]; + } + else + { + index = m_MessageQ.AddToTail(); + } + int bufLen = strlen( m_pMessageText ) + 1; + m_MessageQ[index] = new char[ bufLen ]; + strcpy( m_MessageQ[index], m_pMessageText ); + + + + // Ok, send out the message. + char packetID = MSGMGR_PACKETID_MSG; + const void *data[2] = { &packetID, m_pMessageText }; + int len[2] = { 1, bufLen }; + + // Send it out to our sockets. + FOR_EACH_LL( m_Sockets, i ) + { + m_Sockets[i]->SendChunks( data, len, 2 ); + } + + // Notify the main thread that we've sent it. + SetEvent( m_hMessageSentObj ); + } + } + + // Cleanup all our sockets (the main thread should never touch them). + FOR_EACH_LL( m_Sockets, i ) + m_Sockets[i]->Release(); + + m_Sockets.Purge(); + + m_MessageQ.PurgeAndDeleteElements(); + + SetEvent( m_hExitResponseObj ); + return 0; +} + + +DWORD CMessageMgr::StaticThreadFn( LPVOID pParameter ) +{ + return ((CMessageMgr*)pParameter)->ThreadFn(); +} + + +static CMessageMgr g_MessageMgr; + +IMessageMgr* GetMessageMgr() +{ + return &g_MessageMgr; +} + diff --git a/utils/vmpi/messagemgr.h b/utils/vmpi/messagemgr.h new file mode 100644 index 0000000..cebbe76 --- /dev/null +++ b/utils/vmpi/messagemgr.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MESSAGEMGR_H +#define MESSAGEMGR_H +#ifdef _WIN32 +#pragma once +#endif + + +#define MSGMGR_VERSION 52314 +#define MSGMGR_BROADCAST_PORT 22511 + +#define MSGMGR_PACKETID_MSG 0 +#define MSGMGR_PACKETID_ANNOUNCE_PRESENCE 1 // followed by version # and port + + +// IMessageMgr provides a simple interface apps can use to generate output. Apps +// on the network can connect to the messagemgr to get its output and display it. +class IMessageMgr +{ +public: + virtual bool Init() = 0; + virtual void Term() = 0; + + virtual void Print( const char *pMsg ) = 0; +}; + + +// Get the message manager. It's a global singleton so this will always +// return the same value (null if the manager can't initialize). +IMessageMgr* GetMessageMgr(); + + +#endif // MESSAGEMGR_H diff --git a/utils/vmpi/messbuf.cpp b/utils/vmpi/messbuf.cpp new file mode 100644 index 0000000..d0f65eb --- /dev/null +++ b/utils/vmpi/messbuf.cpp @@ -0,0 +1,279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// MessageBuffer - handy for serializing/unserializing +// structures to be sent as messages +// dal - 9/2002 +// +#include <stdlib.h> +#include <string.h> +#include "messbuf.h" +#include "tier1/strtools.h" + + +/////////////////////////// +// +// +// +MessageBuffer::MessageBuffer() +{ + size = DEFAULT_MESSAGE_BUFFER_SIZE; + data = (char *) malloc(size); + len = 0; + offset = 0; +} + +/////////////////////////// +// +// +// +MessageBuffer::MessageBuffer(int minsize) +{ + size = minsize; + data = (char *) malloc(size); + len = 0; + offset = 0; +} + +/////////////////////////// +// +// +// +MessageBuffer::~MessageBuffer() +{ + free(data); +} + + +/////////////////////////// +// +// +// +int +MessageBuffer::getSize() +{ + return size; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::getLen() +{ + return len; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::setLen(int nlen) +{ + if (nlen < 0) return -1; + if (nlen > size) { + resize(nlen); + } + + int res = len; + len = nlen; + + return res; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::getOffset() +{ + return offset; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::setOffset(int noffset) +{ + if (noffset < 0 || noffset > len) return -1; + int res = offset; + offset = noffset; + + return res; +} + + +/////////////////////////// +// +// +// +int +MessageBuffer::write(void const * p, int bytes) +{ + if (bytes + len > size) { + resize(bytes + len); + } + memcpy(data + len, p, bytes); + int res = len; + len += bytes; + + return res; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::update(int loc, void const * p, int bytes) +{ + if (loc + bytes > size) { + resize(loc + bytes); + } + memcpy(data + loc, p, bytes); + + if (len < loc + bytes) { + len = loc + bytes; + } + + return len; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::extract(int loc, void * p, int bytes) +{ + if (loc + bytes > len) return -1; + memcpy(p, data + loc, bytes); + + return loc + bytes; +} + +/////////////////////////// +// +// +// +int +MessageBuffer::read(void * p, int bytes) +{ + if (offset + bytes > len) return -1; + memcpy(p, data + offset, bytes); + + offset += bytes; + return offset; +} + +int MessageBuffer::WriteString( const char *pString ) +{ + return write( pString, V_strlen( pString ) + 1 ); +} + +int MessageBuffer::ReadString( char *pOut, int bufferLength ) +{ + int nChars = 0; + while ( 1 ) + { + char ch; + if ( read( &ch, sizeof( ch ) ) == -1 ) + { + pOut[0] = 0; + return -1; + } + + if ( ch == 0 || nChars >= (bufferLength-1) ) + break; + + pOut[nChars] = ch; + ++nChars; + } + pOut[nChars] = 0; + return nChars + 1; +} + + +/////////////////////////// +// +// +// +void +MessageBuffer::clear() +{ + memset(data, 0, size); + offset = 0; + len = 0; +} + +/////////////////////////// +// +// +// +void +MessageBuffer::clear(int minsize) +{ + if (minsize > size) { + resize(minsize); + } + memset(data, 0, size); + offset = 0; + len = 0; +} + +/////////////////////////// +// +// +// +void +MessageBuffer::reset(int minsize) +{ + if (minsize > size) { + resize(minsize); + } + len = 0; + offset = 0; +} + +/////////////////////////// +// +// +// +void +MessageBuffer::resize(int minsize) +{ + if (minsize < size) return; + + if (size * 2 > minsize) minsize = size * 2; + + char * odata = data; + data = (char *) malloc(minsize); + memcpy(data, odata, len); + size = minsize; + free(odata); +} + + +/////////////////////////// +// +// +void +MessageBuffer::print(FILE * ofile, int num) +{ + fprintf(ofile, "Len: %d Offset: %d Size: %d\n", len, offset, size); + if (num > size) num = size; + for (int i=0; i<num; ++i) { + fprintf(ofile, "%02x ", (unsigned char) data[i]); + } + fprintf(ofile, "\n"); +}
\ No newline at end of file diff --git a/utils/vmpi/messbuf.h b/utils/vmpi/messbuf.h new file mode 100644 index 0000000..976ffa2 --- /dev/null +++ b/utils/vmpi/messbuf.h @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// MessageBuffer - handy for packing and upacking +// structures to be sent as messages +// +#ifndef _MESSAGEBUFFER +#define _MESSAGEBUFFER + +#include <stdio.h> +#define DEFAULT_MESSAGE_BUFFER_SIZE 2048 + +class MessageBuffer { + public: + char * data; + + MessageBuffer(); + MessageBuffer(int size); + ~MessageBuffer(); + + int getSize(); + int getLen(); + int setLen(int len); + int getOffset(); + int setOffset(int offset); + + int write(void const * p, int bytes); + int update(int loc, void const * p, int bytes); + int extract(int loc, void * p, int bytes); + int read(void * p, int bytes); + + int WriteString( const char *pString ); + int ReadString( char *pOut, int bufferLength ); + + void clear(); + void clear(int minsize); + void reset(int minsize); + void print(FILE * ofile, int num); + + private: + void resize(int minsize); + int size; + int offset; + int len; +}; + +#endif diff --git a/utils/vmpi/mysql_async.cpp b/utils/vmpi/mysql_async.cpp new file mode 100644 index 0000000..e4721a0 --- /dev/null +++ b/utils/vmpi/mysql_async.cpp @@ -0,0 +1,275 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <windows.h> +#include "imysqlwrapper.h" +#include "mysql_async.h" +#include "utllinkedlist.h" + + +static char* CopyString( const char *pStr ) +{ + char *pRet = new char[ strlen( pStr ) + 1 ]; + strcpy( pRet, pStr ); + return pRet; +} + + +class CMySQLAsync : public IMySQLAsync +{ +public: + + CMySQLAsync() + { + m_hThread = NULL; + m_pSQL = NULL; + + m_hExitEvent = CreateEvent( NULL, true, false, NULL ); // Use manual reset because we want it to cascade out without + // resetting the event if it gets set. + m_hPendingQueryEvent = CreateEvent( NULL, false, false, NULL ); + m_hQueryResultsEvent = CreateEvent( NULL, false, false, NULL ); + + InitializeCriticalSection( &m_ExecuteQueryCS ); + InitializeCriticalSection( &m_PendingQueryCS ); + } + + ~CMySQLAsync() + { + Term(); + + CloseHandle( m_hExitEvent ); + CloseHandle( m_hPendingQueryEvent ); + CloseHandle( m_hQueryResultsEvent ); + + DeleteCriticalSection( &m_ExecuteQueryCS ); + DeleteCriticalSection( &m_PendingQueryCS ); + } + + virtual void Release() + { + delete this; + } + + virtual IMySQLRowSet* ExecuteBlocking( const char *pStr ) + { + IMySQLRowSet *pRet; + + EnterCriticalSection( &m_ExecuteQueryCS ); + m_pSQL->Execute( pStr ); + pRet = m_pSQL->DuplicateRowSet(); + LeaveCriticalSection( &m_ExecuteQueryCS ); + + return pRet; + } + + virtual void Execute( const char *pStr, void *pUserData ) + { + EnterCriticalSection( &m_PendingQueryCS ); + + CPendingQuery query; + query.m_pStr = CopyString( pStr ); + query.m_pUserData = pUserData; + query.m_Timer.Start(); + + m_PendingQueries.AddToTail( query ); + SetEvent( m_hPendingQueryEvent ); + + LeaveCriticalSection( &m_PendingQueryCS ); + } + + virtual bool GetNextResults( CQueryResults &results ) + { + results.m_pResults = NULL; + + if ( WaitForSingleObject( m_hQueryResultsEvent, 0 ) == WAIT_OBJECT_0 ) + { + EnterCriticalSection( &m_PendingQueryCS ); + + Assert( m_QueryResults.Count() > 0 ); + int iHead = m_QueryResults.Head(); + results = m_QueryResults[iHead]; + m_QueryResults.Remove( iHead ); + + if ( m_QueryResults.Count() > 0 ) + SetEvent( m_hQueryResultsEvent ); + + LeaveCriticalSection( &m_PendingQueryCS ); + return true; + } + else + { + return false; + } + } + + bool Init( IMySQL *pSQL ) + { + Term(); + + DWORD dwThreadID; + m_hThread = CreateThread( NULL, 0, &CMySQLAsync::StaticThreadFn, this, 0, &dwThreadID ); + if ( m_hThread ) + { + m_pSQL = pSQL; + return true; + } + else + { + return false; + } + } + + void Term() + { + // Stop the thread. + if ( m_hThread ) + { + // Delete all our queries. + SetEvent( m_hExitEvent ); + WaitForSingleObject( m_hThread, INFINITE ); + CloseHandle( m_hThread ); + m_hThread = NULL; + } + + // Delete leftover queries. + FOR_EACH_LL( m_PendingQueries, iPendingQuery ) + { + delete [] m_PendingQueries[iPendingQuery].m_pStr; + } + m_PendingQueries.Purge(); + + FOR_EACH_LL( m_QueryResults, i ) + { + m_QueryResults[i].m_pResults->Release(); + } + m_QueryResults.Purge(); + + if ( m_pSQL ) + { + m_pSQL->Release(); + m_pSQL = NULL; + } + } + + +private: + + DWORD ThreadFn() + { + HANDLE hEvents[2] = { m_hExitEvent, m_hPendingQueryEvent }; + + // + while ( 1 ) + { + int ret = WaitForMultipleObjects( ARRAYSIZE( hEvents ), hEvents, false, INFINITE ); + if ( ret == WAIT_OBJECT_0 ) + break; + + if ( ret == WAIT_OBJECT_0+1 ) + { + // A new string has been queued up for us to execute. + EnterCriticalSection( &m_PendingQueryCS ); + + Assert( m_PendingQueries.Count() > 0 ); + int iHead = m_PendingQueries.Head(); + + CPendingQuery pending = m_PendingQueries[iHead]; + m_PendingQueries.Remove( iHead ); + + // Set the pending query event if there are more queries waiting to run. + if ( m_PendingQueries.Count() > 0 ) + SetEvent( m_hPendingQueryEvent ); + + LeaveCriticalSection( &m_PendingQueryCS ); + + + // Run the query. + EnterCriticalSection( &m_ExecuteQueryCS ); + + CQueryResults results; + results.m_pResults = NULL; + results.m_pUserData = pending.m_pUserData; + results.m_ExecuteTime.Init(); + pending.m_Timer.End(); + results.m_QueueTime = pending.m_Timer.GetDuration(); + + CFastTimer executeTimer; + executeTimer.Start(); + + if ( m_pSQL->Execute( pending.m_pStr ) == 0 ) + { + executeTimer.End(); + results.m_ExecuteTime = executeTimer.GetDuration(); + results.m_pResults = m_pSQL->DuplicateRowSet(); + } + + delete pending.m_pStr; + + LeaveCriticalSection( &m_ExecuteQueryCS ); + + + // Store the results. + EnterCriticalSection( &m_PendingQueryCS ); + + m_QueryResults.AddToTail( results ); + SetEvent( m_hQueryResultsEvent ); + + LeaveCriticalSection( &m_PendingQueryCS ); + } + } + + return 0; + } + + static DWORD WINAPI StaticThreadFn( LPVOID lpParameter ) + { + return ((CMySQLAsync*)lpParameter)->ThreadFn(); + } + +private: + + HANDLE m_hThread; + HANDLE m_hExitEvent; + HANDLE m_hPendingQueryEvent; // Signaled when a new query is added. + HANDLE m_hQueryResultsEvent; + + IMySQL *m_pSQL; + + CRITICAL_SECTION m_PendingQueryCS; + CRITICAL_SECTION m_ExecuteQueryCS; + + + // Outgoing query results. New ones are added to the tail. + CUtlLinkedList<CQueryResults, int> m_QueryResults; + + + // New ones added to the tail. + class CPendingQuery + { + public: + char *m_pStr; + void *m_pUserData; + CFastTimer m_Timer; // Times how long this query is in the queue. + }; + + CUtlLinkedList<CPendingQuery,int> m_PendingQueries; +}; + + +IMySQLAsync* CreateMySQLAsync( IMySQL *pSQL ) +{ + CMySQLAsync *pRet = new CMySQLAsync; + if ( pRet->Init( pSQL ) ) + { + return pRet; + } + else + { + delete pRet; + return NULL; + } +} + diff --git a/utils/vmpi/mysql_async.h b/utils/vmpi/mysql_async.h new file mode 100644 index 0000000..03844a2 --- /dev/null +++ b/utils/vmpi/mysql_async.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MYSQL_ASYNC_H +#define MYSQL_ASYNC_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier0/fasttimer.h" + + +class IMySQL; + + +class CQueryResults +{ +public: + IMySQLRowSet *m_pResults; + void *m_pUserData; // This is the value passed to Execute. + CCycleCount m_ExecuteTime; // How long it took to execute this query in MySQL. + CCycleCount m_QueueTime; // How long it spent in the queue. +}; + + +// This provides a way to do asynchronous MySQL queries. They are executed in a thread, +// then you can pop the results off as they arrive. +class IMySQLAsync +{ +public: + + virtual void Release() = 0; + + // After finishing the current query, if there is one, this immediately executes + // the specified query and returns its results. + virtual IMySQLRowSet* ExecuteBlocking( const char *pStr ) = 0; + + // Queue up a query. + virtual void Execute( const char *pStr, void *pUserData ) = 0; + + // Poll this to pick up results from Execute(). + // NOTE: if this returns true, but pResults is set to NULL, then that query had an error. + virtual bool GetNextResults( CQueryResults &results ) = 0; +}; + +// Create an async mysql interface. Note: if this call returns a non-null value, +// then after this call, you do NOT own pSQL anymore and you shouldn't ever call +// a function in it again. +IMySQLAsync* CreateMySQLAsync( IMySQL *pSQL ); + + +#endif // MYSQL_ASYNC_H diff --git a/utils/vmpi/mysql_wrapper.h b/utils/vmpi/mysql_wrapper.h new file mode 100644 index 0000000..01ca9d9 --- /dev/null +++ b/utils/vmpi/mysql_wrapper.h @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQL_WRAPPER_H +#define MYSQL_WRAPPER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" + + +class IMySQL; + + +// This is a helper class to build queries like the stream IO. +class CMySQLQuery +{ +friend class CMySQL; + +public: + // This is like a sprintf, but it will grow the string as necessary. + void Format( PRINTF_FORMAT_STRING const char *pFormat, ... ); + +private: + CUtlVector<char> m_QueryText; +}; + + +class CColumnValue +{ +public: + + CColumnValue( CMySQL *pSQL, int iColumn ); + + const char* String(); + long Int32(); + unsigned long UInt32(); + +private: + CMySQL *m_pSQL; + int m_iColumn; +}; + + +class IMySQL +{ +public: + virtual void Release() = 0; + + // These execute SQL commands. They return 0 if the query was successful. + virtual int Execute( const char *pString ) = 0; + virtual int Execute( CMySQLQuery &query ) = 0; + + // If you just inserted rows into a table with an AUTO_INCREMENT column, + // then this returns the (unique) value of that column. + virtual unsigned long InsertID() = 0; + + // If you just executed a select statement, then you can use these functions to + // iterate over the result set. + + // Get the number of columns in the data returned from the last query (if it was a select statement). + virtual int NumFields() = 0; + + // Get the name of each column returned by the last query. + virtual const char* GetFieldName( int iColumn ) = 0; + + // Call this in a loop until it returns false to iterate over all rows the query returned. + virtual bool NextRow() = 0; + + // You can call this to start iterating over the result set from the start again. + // Note: after calling this, you have to call NextRow() to actually get the first row's value ready. + virtual bool SeekToFirstRow() = 0; + + virtual CColumnValue GetColumnValue( int iColumn ) = 0; + virtual CColumnValue GetColumnValue( const char *pColumnName ) = 0; + + // You can call this to get the index of a column for faster lookups with GetColumnValue( int ). + // Returns -1 if the column can't be found. + virtual int GetColumnIndex( const char *pColumnName ) = 0; +}; + + +IMySQL* InitMySQL( const char *pDBName, const char *pHostName="", const char *pUserName="", const char *pPassword="" ); + + + +// ------------------------------------------------------------------------------------------------ // +// Inlines. +// ------------------------------------------------------------------------------------------------ // + +inline CColumnValue::CColumnValue( CMySQL *pSQL, int iColumn ) +{ + m_pSQL = pSQL; + m_iColumn = iColumn; +} + + +#endif // MYSQL_WRAPPER_H diff --git a/utils/vmpi/net_view_thread.cpp b/utils/vmpi/net_view_thread.cpp new file mode 100644 index 0000000..19ab38d --- /dev/null +++ b/utils/vmpi/net_view_thread.cpp @@ -0,0 +1,208 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "stdafx.h" +#include "net_view_thread.h" + + +char* CopyAlloc( const char *pStr ) +{ + char *pRet = new char[ strlen( pStr ) + 1]; + strcpy( pRet, pStr ); + return pRet; +} + + +CNetViewThread::CNetViewThread() +{ + m_hThread = NULL; + m_hThreadExitEvent = NULL; + InitializeCriticalSection( &m_ComputerNamesCS ); +} + + +CNetViewThread::~CNetViewThread() +{ + Term(); + DeleteCriticalSection( &m_ComputerNamesCS ); +} + + +void CNetViewThread::Init() +{ + Term(); + + m_hThreadExitEvent = CreateEvent( NULL, false, false, NULL ); + + DWORD dwThreadID = 0; + m_hThread = CreateThread( + NULL, + 0, + &CNetViewThread::StaticThreadFn, + this, + 0, + &dwThreadID ); +} + + +void CNetViewThread::Term() +{ + if ( m_hThread ) + { + SetEvent( m_hThreadExitEvent ); + WaitForSingleObject( m_hThread, INFINITE ); + CloseHandle( m_hThread ); + m_hThread = NULL; + } + + if ( m_hThreadExitEvent ) + { + CloseHandle( m_hThreadExitEvent ); + m_hThreadExitEvent = NULL; + } +} + + +void CNetViewThread::GetComputerNames( CUtlVector<char*> &computerNames ) +{ + EnterCriticalSection( &m_ComputerNamesCS ); + + computerNames.Purge(); + for ( int i=0; i < m_ComputerNames.Count(); i++ ) + { + computerNames.AddToTail( CopyAlloc( m_ComputerNames[i] ) ); + } + + LeaveCriticalSection( &m_ComputerNamesCS ); +} + + +void CNetViewThread::UpdateServicesFromNetView() +{ + HANDLE hChildStdoutRd, hChildStdoutWr; + + // Set the bInheritHandle flag so pipe handles are inherited. + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if( CreatePipe( &hChildStdoutRd, &hChildStdoutWr, &saAttr, 0 ) ) + { + STARTUPINFO si; + memset(&si, 0, sizeof si); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = hChildStdoutWr; + + PROCESS_INFORMATION pi; + + if( CreateProcess( + NULL, + "net view", + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + TRUE, // bInheritHandls + DETACHED_PROCESS, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &si, // lpStartupInfo + &pi // lpProcessInformation + ) ) + { + // read from pipe.. + #define BUFFER_SIZE 8192 + char buffer[BUFFER_SIZE]; + BOOL bDone = FALSE; + CUtlVector<char> totalBuffer; + + while(1) + { + DWORD dwCount = 0; + DWORD dwRead = 0; + + // read from input handle + PeekNamedPipe(hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL); + if (dwCount) + { + dwCount = min (dwCount, (DWORD)BUFFER_SIZE - 1); + ReadFile(hChildStdoutRd, buffer, dwCount, &dwRead, NULL); + } + if(dwRead) + { + buffer[dwRead] = 0; + totalBuffer.AddMultipleToTail( dwRead, buffer ); + } + // check process termination + else if( WaitForSingleObject( pi.hProcess, 1000 ) != WAIT_TIMEOUT ) + { + if ( bDone ) + break; + + bDone = TRUE; // next time we get it + } + } + + // Now parse the output. + totalBuffer.AddToTail( 0 ); + ParseComputerNames( totalBuffer.Base() ); + } + + CloseHandle( hChildStdoutRd ); + CloseHandle( hChildStdoutWr ); + } +} + + +void CNetViewThread::ParseComputerNames( const char *pNetViewOutput ) +{ + EnterCriticalSection( &m_ComputerNamesCS ); + + m_ComputerNames.PurgeAndDeleteElements(); + + const char *pCur = pNetViewOutput; + while ( *pCur != 0 ) + { + // If we get a \\, then it's a computer name followed by whitespace. + if ( pCur[0] == '\\' && pCur[1] == '\\' ) + { + char curComputerName[512]; + char *pOutPos = curComputerName; + + pCur += 2; + while ( *pCur && !V_isspace( *pCur ) && (pOutPos-curComputerName < 510) ) + { + *pOutPos++ = *pCur++; + } + *pOutPos = 0; + + m_ComputerNames.AddToTail( CopyAlloc( curComputerName ) ); + } + ++pCur; + } + + LeaveCriticalSection( &m_ComputerNamesCS ); +} + + +DWORD CNetViewThread::ThreadFn() +{ + // Update the services list every 30 seconds. + do + { + UpdateServicesFromNetView(); + } while ( WaitForSingleObject( m_hThreadExitEvent, 30000 ) != WAIT_OBJECT_0 ); + + return 0; +} + + +DWORD CNetViewThread::StaticThreadFn( LPVOID lpParameter ) +{ + return ((CNetViewThread*)lpParameter)->ThreadFn(); +} + + diff --git a/utils/vmpi/net_view_thread.h b/utils/vmpi/net_view_thread.h new file mode 100644 index 0000000..4de4406 --- /dev/null +++ b/utils/vmpi/net_view_thread.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef NET_VIEW_THREAD_H +#define NET_VIEW_THREAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" + + +class CNetViewThread +{ +public: + CNetViewThread(); + ~CNetViewThread(); + + // This creates the thread that periodically checks "net view" to get the current list of + // machines out on the network. + void Init(); + void Term(); + + void GetComputerNames( CUtlVector<char*> &computerNames ); + +private: + + void UpdateServicesFromNetView(); + void ParseComputerNames( const char *pNetViewOutput ); + + DWORD ThreadFn(); + static DWORD WINAPI StaticThreadFn( LPVOID lpParameter ); + + CUtlVector<char*> m_ComputerNames; + HANDLE m_hThread; + HANDLE m_hThreadExitEvent; + CRITICAL_SECTION m_ComputerNamesCS; +}; + + +#endif // NET_VIEW_THREAD_H diff --git a/utils/vmpi/tcpsocket.cpp b/utils/vmpi/tcpsocket.cpp new file mode 100644 index 0000000..8ab67cf --- /dev/null +++ b/utils/vmpi/tcpsocket.cpp @@ -0,0 +1,1178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +//#define PARANOID + +#if defined( PARANOID ) + #include <stdlib.h> + #include <crtdbg.h> +#endif + +#include <winsock2.h> +#include <mswsock.h> +#include "tcpsocket.h" +#include "tier1/utllinkedlist.h" +#include <stdio.h> +#include "threadhelpers.h" +#include "tier0/dbg.h" + + + +#error "I am TCPSocket and I suck. Use IThreadedTCPSocket or ThreadedTCPSocketEmu instead." + + +extern TIMEVAL SetupTimeVal( double flTimeout ); +extern void IPAddrToSockAddr( const CIPAddr *pIn, sockaddr_in *pOut ); +extern void SockAddrToIPAddr( const sockaddr_in *pIn, CIPAddr *pOut ); + + +#define SENTINEL_DISCONNECT -1 +#define SENTINEL_KEEPALIVE -2 + + +#define KEEPALIVE_INTERVAL_MS 3000 // keepalives are sent every N MS +#define KEEPALIVE_TIMEOUT_SECONDS 15.0 // connections timeout after this long + + +static bool g_bEnableTCPTimeout = true; + + +class CRecvData +{ +public: + int m_Count; + unsigned char m_Data[1]; +}; + + + +SOCKET TCPBind( const CIPAddr *pAddr ) +{ + // Create a socket to send and receive through. + SOCKET sock = WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED ); + if ( sock == INVALID_SOCKET ) + { + Assert( false ); + return INVALID_SOCKET; + } + + // bind to it! + sockaddr_in addr; + IPAddrToSockAddr( pAddr, &addr ); + + int status = bind( sock, (sockaddr*)&addr, sizeof(addr) ); + if ( status == 0 ) + { + return sock; + } + else + { + closesocket( sock ); + return INVALID_SOCKET; + } +} + + + +// ---------------------------------------------------------------------------------------- // +// TCP sockets. +// ---------------------------------------------------------------------------------------- // + +enum +{ + OP_RECV=111, + OP_SEND +}; + +// We use this for all OVERLAPPED structures. +class COverlappedPlus : public WSAOVERLAPPED +{ +public: + COverlappedPlus() + { + memset( this, 0, sizeof( WSAOVERLAPPED ) ); + } + + int m_OPType; // One of the OP_ defines. +}; + +typedef struct SendBuf_t +{ + COverlappedPlus m_Overlapped; + int m_Index; // Index into m_SendBufs. + int m_DataLength; + char m_Data[1]; +} SendBuf_s; + + +// These manage a thread that calls SendKeepalive() on all TCPSockets. +// AddGlobalTCPSocket shouldn't be called until you're ready for SendKeepalive() to be called. +class CTCPSocket; +void AddGlobalTCPSocket( CTCPSocket *pSocket ); +void RemoveGlobalTCPSocket( CTCPSocket *pSocket ); + + + +// ------------------------------------------------------------------------------------------ // +// CTCPSocket implementation. +// ------------------------------------------------------------------------------------------ // + +class CTCPSocket : public ITCPSocket +{ +friend class CTCPListenSocket; + +public: + + CTCPSocket() + { + m_Socket = INVALID_SOCKET; + m_bConnected = false; + + m_hIOCP = NULL; + + m_bShouldExitThreads = false; + m_bConnectionLost = false; + m_nSizeBytesReceived = 0; + + m_pIncomingData = NULL; + + memset( &m_RecvOverlapped, 0, sizeof( m_RecvOverlapped ) ); + m_RecvOverlapped.m_OPType = OP_RECV; + + m_hRecvSignal = CreateEvent( NULL, FALSE, FALSE, NULL ); + m_RecvStage = -1; + + m_MainThreadID = GetCurrentThreadId(); + } + + virtual ~CTCPSocket() + { + Term(); + CloseHandle( m_hRecvSignal ); + } + + void Term() + { + Assert( GetCurrentThreadId() == m_MainThreadID ); + + RemoveGlobalTCPSocket( this ); + + if ( m_Socket != SOCKET_ERROR && !m_bConnectionLost ) + { + SendDisconnectSentinel(); + + // Give the sends a second to complete. SO_LINGER is having trouble for some reason. + WaitForSendsToComplete( 1 ); + } + + + StopThreads(); + + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + } + + if ( m_hIOCP ) + { + CloseHandle( m_hIOCP ); + m_hIOCP = NULL; + } + + m_bConnected = false; + m_bConnectionLost = true; + m_RecvStage = -1; + + FOR_EACH_LL( m_SendBufs, i ) + { + SendBuf_t *pSendBuf = m_SendBufs[i]; + ParanoidMemoryCheck( pSendBuf ); + free( pSendBuf ); + } + m_SendBufs.Purge(); + + FOR_EACH_LL( m_RecvDatas, j ) + { + CRecvData *pRecvData = m_RecvDatas[j]; + ParanoidMemoryCheck( pRecvData ); + free( pRecvData ); + } + m_RecvDatas.Purge(); + + if ( m_pIncomingData ) + { + ParanoidMemoryCheck( m_pIncomingData ); + free( m_pIncomingData ); + m_pIncomingData = 0; + } + } + + virtual void Release() + { + delete this; + } + + + void ParanoidMemoryCheck( void *ptr = NULL ) + { +#if defined( PARANOID ) + Assert( _CrtIsValidHeapPointer( this ) ); + + if ( ptr ) + { + Assert( _CrtIsValidHeapPointer( ptr ) ); + } + + Assert( _CrtCheckMemory() == TRUE ); +#endif + } + + + virtual bool BindToAny( const unsigned short port ) + { + Term(); + + CIPAddr addr( 0, 0, 0, 0, port ); // INADDR_ANY + m_Socket = TCPBind( &addr ); + if ( m_Socket == INVALID_SOCKET ) + { + return false; + } + else + { + SetInitialSocketOptions(); + return true; + } + } + + + // Set the initial socket options that we want. + void SetInitialSocketOptions() + { + // Set nodelay to improve latency. + BOOL val = TRUE; + setsockopt( m_Socket, IPPROTO_TCP, TCP_NODELAY, (const char FAR *)&val, sizeof(BOOL) ); + + // Make it linger for 3 seconds when it exits. + LINGER linger; + linger.l_onoff = 1; + linger.l_linger = 3; + setsockopt( m_Socket, SOL_SOCKET, SO_LINGER, (char*)&linger, sizeof( linger ) ); + } + + + // Called only by main thread interface functions. + // Returns true if the connection is lost. + bool CheckConnectionLost() + { + Assert( GetCurrentThreadId() == m_MainThreadID ); + + if ( m_Socket == SOCKET_ERROR ) + return true; + + // Have we timed out? + if ( g_bEnableTCPTimeout && (Plat_FloatTime() - m_LastRecvTime > KEEPALIVE_TIMEOUT_SECONDS) ) + { + SetConnectionLost( "Connection timed out." ); + } + + // Has any thread posted that the connection has been lost? + CCriticalSectionLock postLock( &m_ConnectionLostCS ); + postLock.Lock(); + if ( m_bConnectionLost ) + { + Term(); + return true; + } + else + { + return false; + } + } + + // Called by any thread. All interface functions call CheckConnectionLost() and return errors if it's lost. + void SetConnectionLost( const char *pErrorString, int err = -1 ) + { + CCriticalSectionLock postLock( &m_ConnectionLostCS ); + postLock.Lock(); + m_bConnectionLost = true; + postLock.Unlock(); + + // Handle it right away if we're in the main thread. If we're in an IO thread, + // it has to wait until the next interface function calls CheckConnectionLost(). + if ( GetCurrentThreadId() == m_MainThreadID ) + { + Term(); + } + + if ( pErrorString ) + { + m_ErrorString.CopyArray( pErrorString, strlen( pErrorString ) + 1 ); + } + else + { + char *lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + m_ErrorString.CopyArray( lpMsgBuf, strlen( lpMsgBuf ) + 1 ); + LocalFree( lpMsgBuf ); + } + } + + + // -------------------------------------------------------------------------------------------------- // + // The receive code. + // -------------------------------------------------------------------------------------------------- // + + virtual bool StartWaitingForSize( bool bFresh ) + { + Assert( m_Socket != INVALID_SOCKET ); + Assert( m_bConnected ); + + m_RecvStage = 0; + m_RecvDataSize = -1; + if ( bFresh ) + m_nSizeBytesReceived = 0; + + DWORD dwNumBytesReceived = 0; + WSABUF buf = { sizeof( &m_RecvDataSize ) - m_nSizeBytesReceived, ((char*)&m_RecvDataSize) + m_nSizeBytesReceived }; + DWORD dwFlags = 0; + + int status = WSARecv( + m_Socket, + &buf, + 1, + &dwNumBytesReceived, + &dwFlags, + &m_RecvOverlapped, + NULL ); + + int err = -1; + if ( status == SOCKET_ERROR && (err = WSAGetLastError()) != ERROR_IO_PENDING ) + { + SetConnectionLost( NULL, err ); + return false; + } + else + { + return true; + } + } + + + bool PostNextDataPart() + { + DWORD dwNumBytesReceived = 0; + WSABUF buf = { m_RecvDataSize - m_AmountReceived, (char*)m_pIncomingData->m_Data + m_AmountReceived }; + DWORD dwFlags = 0; + + int status = WSARecv( + m_Socket, + &buf, + 1, + &dwNumBytesReceived, + &dwFlags, + &m_RecvOverlapped, + NULL ); + + int err = -1; + if ( status == SOCKET_ERROR && (err = WSAGetLastError()) != ERROR_IO_PENDING ) + { + SetConnectionLost( NULL, err ); + return false; + } + else + { + return true; + } + } + + + bool StartWaitingForData() + { + Assert( m_Socket != INVALID_SOCKET ); + Assert( m_RecvStage == 0 ); + Assert( m_bConnected ); + Assert( m_RecvDataSize > 0 ); + + m_RecvStage = 1; + + // Add a CRecvData element. + ParanoidMemoryCheck(); + m_pIncomingData = (CRecvData*)malloc( sizeof( CRecvData ) - 1 + m_RecvDataSize ); + if ( !m_pIncomingData ) + { + char str[512]; + _snprintf( str, sizeof( str ), "malloc() failed. m_RecvDataSize = %d\n", m_RecvDataSize ); + SetConnectionLost( str ); + return false; + } + + m_pIncomingData->m_Count = m_RecvDataSize; + + m_AmountReceived = 0; + + return PostNextDataPart(); + } + + virtual bool Recv( CUtlVector<unsigned char> &data, double flTimeout ) + { + if ( CheckConnectionLost() ) + return false; + + // Wait in 50ms chunks, checking for disconnections along the way. + bool bGotData = false; + DWORD msToWait = (DWORD)( flTimeout * 1000.0 ); + do + { + DWORD curWaitTime = min( msToWait, 50 ); + DWORD ret = WaitForSingleObject( m_hRecvSignal, curWaitTime ); + if ( ret == WAIT_OBJECT_0 ) + { + bGotData = true; + break; + } + + // Did the connection timeout? + if ( CheckConnectionLost() ) + return false; + + msToWait -= curWaitTime; + } while ( msToWait ); + + // If we never got a WAIT_OBJECT_0, then we never received anything. + if ( !bGotData ) + return false; + + + CCriticalSectionLock csLock( &m_RecvDataCS ); + csLock.Lock(); + + // Pickup the head m_RecvDatas element. + CRecvData *pRecvData = m_RecvDatas[ m_RecvDatas.Head() ]; + data.CopyArray( pRecvData->m_Data, pRecvData->m_Count ); + + // Now free it. + m_RecvDatas.Remove( m_RecvDatas.Head() ); + ParanoidMemoryCheck( pRecvData ); + free( pRecvData ); + + // Set the event again for the next time around, if there is more data waiting. + if ( m_RecvDatas.Count() > 0 ) + SetEvent( m_hRecvSignal ); + + return true; + } + + // INSIDE IO THREAD. + void HandleRecvCompletion( COverlappedPlus *pInfo, DWORD dwNumBytes ) + { + if ( dwNumBytes == 0 ) + { + SetConnectionLost( "Got 0 bytes in HandleRecvCompletion" ); + return; + } + + m_LastRecvTime = Plat_FloatTime(); + if ( m_RecvStage == 0 ) + { + m_nSizeBytesReceived += dwNumBytes; + if ( m_nSizeBytesReceived == sizeof( m_RecvDataSize ) ) + { + // Size of -1 means the other size is breaking the connection. + if ( m_RecvDataSize == SENTINEL_DISCONNECT ) + { + SetConnectionLost( "Got a graceful disconnect message." ); + return; + } + else if ( m_RecvDataSize == SENTINEL_KEEPALIVE ) + { + // No data follows this. Just let m_LastRecvTime get updated. + StartWaitingForSize( true ); + return; + } + + StartWaitingForData(); + } + else if ( m_nSizeBytesReceived < sizeof( m_RecvDataSize ) ) + { + // Handle the case where we only got some of the data (maybe one of the clients got disconnected). + StartWaitingForSize( false ); + } + else + { + // This case should never ever happen! +#if defined( _DEBUG ) + __asm int 3; +#endif + + SetConnectionLost( "Received too much data in a packet!" ); + return; + } + } + else if ( m_RecvStage == 1 ) + { + // Got the data, make sure we got it all. + m_AmountReceived += dwNumBytes; + + // Sanity check. +#if defined( _DEBUG ) + Assert( m_RecvDataSize == m_pIncomingData->m_Count ); + Assert( m_AmountReceived <= m_RecvDataSize ); // TODO: make this threadsafe for multiple IO threads. +#endif + + if ( m_AmountReceived == m_RecvDataSize ) + { + m_RecvStage = 2; + + // Add the data to the list of packets waiting to be picked up. + CCriticalSectionLock csLock( &m_RecvDataCS ); + csLock.Lock(); + + m_RecvDatas.AddToTail( m_pIncomingData ); + m_pIncomingData = NULL; + + if ( m_RecvDatas.Count() == 1 ) + SetEvent( m_hRecvSignal ); // Notify the Recv() function. + + StartWaitingForSize( true ); + } + else + { + PostNextDataPart(); + } + } + else + { + Assert( false ); + } + } + + + // -------------------------------------------------------------------------------------------------- // + // The send code. + // -------------------------------------------------------------------------------------------------- // + + virtual void WaitForSendsToComplete( double flTimeout ) + { + CWaitTimer waitTimer( flTimeout ); + while ( 1 ) + { + CCriticalSectionLock sendBufLock( &m_SendCS ); + sendBufLock.Lock(); + if( m_SendBufs.Count() == 0 ) + return; + sendBufLock.Unlock(); + + if ( waitTimer.ShouldKeepWaiting() ) + Sleep( 10 ); + else + break; + } + } + + + // This is called in the keepalive thread. + void SendKeepalive() + { + // Send a message saying we're exiting. + ParanoidMemoryCheck(); + SendBuf_t *pBuf = (SendBuf_t*)malloc( sizeof( SendBuf_t ) - 1 + sizeof( int ) ); + if ( !pBuf ) + { + SetConnectionLost( "malloc() in SendKeepalive() failed." ); + return; + } + + pBuf->m_DataLength = sizeof( int ); + *((int*)pBuf->m_Data) = SENTINEL_KEEPALIVE; + InternalSendDataBuf( pBuf ); + } + + + void SendDisconnectSentinel() + { + // Send a message saying we're exiting. + ParanoidMemoryCheck(); + SendBuf_t *pBuf = (SendBuf_t*)malloc( sizeof( SendBuf_t ) - 1 + sizeof( int ) ); + if ( pBuf ) + { + pBuf->m_DataLength = sizeof( int ); + *((int*)pBuf->m_Data) = SENTINEL_DISCONNECT; // This signifies that we're exiting. + InternalSendDataBuf( pBuf ); + } + } + + + virtual bool Send( const void *pData, int len ) + { + const void *pChunks[1] = { pData }; + int chunkLengths[1] = { len }; + return SendChunks( pChunks, chunkLengths, 1 ); + } + + + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) + { + if ( CheckConnectionLost() ) + return false; + + CChunkWalker walker( pChunks, pChunkLengths, nChunks ); + int totalLength = walker.GetTotalLength(); + + if ( !totalLength ) + return true; + + // Create a buffer to hold the data and copy the data in. + ParanoidMemoryCheck(); + SendBuf_t *pBuf = (SendBuf_t*)malloc( sizeof( SendBuf_t ) - 1 + totalLength + sizeof( int ) ); + if ( !pBuf ) + { + char str[512]; + _snprintf( str, sizeof( str ), "malloc() in SendChunks() failed. totalLength = %d.", totalLength ); + SetConnectionLost( str ); + return false; + } + + pBuf->m_DataLength = totalLength + sizeof( int ); + + int *pByteCountPos = (int*)pBuf->m_Data; + *pByteCountPos = totalLength; + + char *pDataPos = &pBuf->m_Data[ sizeof( int ) ]; + walker.CopyTo( pDataPos, totalLength ); + + int status = InternalSendDataBuf( pBuf ); + int err = -1; + if ( status == SOCKET_ERROR && (err = WSAGetLastError()) != ERROR_IO_PENDING ) + { + SetConnectionLost( NULL, err ); + return false; + } + else + { + return true; + } + } + + + int InternalSendDataBuf( SendBuf_t *pBuf ) + { + // Protect against interference from the keepalive thread. + CCriticalSectionLock csLock( &m_SendCS ); + csLock.Lock(); + + + pBuf->m_Overlapped.m_OPType = OP_SEND; + pBuf->m_Overlapped.hEvent = NULL; + + // Add it to our list of buffers. + pBuf->m_Index = m_SendBufs.AddToTail( pBuf ); + + // Tell Winsock to send it. + WSABUF buf = { pBuf->m_DataLength, pBuf->m_Data }; + + DWORD dwNumBytesSent = 0; + return WSASend( + m_Socket, + &buf, + 1, + &dwNumBytesSent, + 0, + &pBuf->m_Overlapped, + NULL ); + } + + + // INSIDE IO THREAD. + void HandleSendCompletion( COverlappedPlus *pInfo, DWORD dwNumBytes ) + { + if ( dwNumBytes == 0 ) + { + SetConnectionLost( "0 bytes in HandleSendCompletion." ); + return; + } + + // Just free the buffer. + SendBuf_t *pBuf = (SendBuf_t*)pInfo; + Assert( dwNumBytes == (DWORD)pBuf->m_DataLength ); + + CCriticalSectionLock sendBufLock( &m_SendCS ); + sendBufLock.Lock(); + m_SendBufs.Remove( pBuf->m_Index ); + sendBufLock.Unlock(); + + ParanoidMemoryCheck( pBuf ); + free( pBuf ); + } + + + // -------------------------------------------------------------------------------------------------- // + // The connect code. + // -------------------------------------------------------------------------------------------------- // + + virtual bool BeginConnect( const CIPAddr &inputAddr ) + { + sockaddr_in addr; + IPAddrToSockAddr( &inputAddr, &addr ); + + m_bConnected = false; + int ret = connect( m_Socket, (struct sockaddr*)&addr, sizeof( addr ) ); + ret=ret; + + return true; + } + + + virtual bool UpdateConnect() + { + // We're still ok.. just wait until the socket becomes writable (is connected) or we timeout. + fd_set writeSet; + writeSet.fd_count = 1; + writeSet.fd_array[0] = m_Socket; + TIMEVAL timeVal = SetupTimeVal( 0 ); + + // See if it has a packet waiting. + int status = select( 0, NULL, &writeSet, NULL, &timeVal ); + if ( status > 0 ) + { + SetupConnected(); + return true; + } + + return false; + } + + + void SetupConnected() + { + m_bConnected = true; + m_bConnectionLost = false; + m_LastRecvTime = Plat_FloatTime(); + + CreateThreads(); + StartWaitingForSize( true ); + AddGlobalTCPSocket( this ); + } + + + virtual bool IsConnected() + { + CheckConnectionLost(); + return m_bConnected; + } + + + virtual void GetDisconnectReason( CUtlVector<char> &reason ) + { + reason = m_ErrorString; + } + + + // -------------------------------------------------------------------------------------------------- // + // Threads code. + // -------------------------------------------------------------------------------------------------- // + + // Create our IO Completion Port threads. + bool CreateThreads() + { + int nThreads = 1; + SetShouldExitThreads( false ); + + // Create our IO completion port and hook it to our socket. + m_hIOCP = CreateIoCompletionPort( + INVALID_HANDLE_VALUE, NULL, 0, 0); + + m_hIOCP = CreateIoCompletionPort( (HANDLE)m_Socket, m_hIOCP, (unsigned long)this, nThreads ); + + for ( int i=0; i < nThreads; i++ ) + { + DWORD dwThreadID = 0; + HANDLE hThread = CreateThread( + NULL, + 0, + &CTCPSocket::StaticThreadFn, + this, + 0, + &dwThreadID ); + + if ( hThread ) + { + SetThreadPriority( hThread, THREAD_PRIORITY_ABOVE_NORMAL ); + m_Threads.AddToTail( hThread ); + } + else + { + StopThreads(); + return false; + } + } + + return true; + } + + + void StopThreads() + { + // Tell the threads to exit, then wait for them to do so. + SetShouldExitThreads( true ); + WaitForMultipleObjects( m_Threads.Count(), m_Threads.Base(), TRUE, INFINITE ); + + for ( int i=0; i < m_Threads.Count(); i++ ) + { + CloseHandle( m_Threads[i] ); + } + m_Threads.Purge(); + } + + + void SetShouldExitThreads( bool bShouldExit ) + { + CCriticalSectionLock lock( &m_ThreadsCS ); + lock.Lock(); + m_bShouldExitThreads = bShouldExit; + } + + + bool ShouldExitThreads() + { + CCriticalSectionLock lock( &m_ThreadsCS ); + lock.Lock(); + + bool bRet = m_bShouldExitThreads; + return bRet; + } + + + DWORD ThreadFn() + { + while ( 1 ) + { + DWORD dwNumBytes = 0; + unsigned long pInputTCPSocket; + LPOVERLAPPED pOverlapped; + + if ( GetQueuedCompletionStatus( + m_hIOCP, // the port we're listening on + &dwNumBytes, // # bytes received on the port + &pInputTCPSocket,// "completion key" = CTCPSocket* + &pOverlapped, // the overlapped info that was passed into AcceptEx, WSARecv, or WSASend. + 100 // listen for 100ms at a time so we can exit gracefully when the socket is deleted. + ) ) + { + COverlappedPlus *pInfo = (COverlappedPlus*)pOverlapped; + ParanoidMemoryCheck( pInfo ); + + if ( pInfo->m_OPType == OP_RECV ) + { + Assert( pInfo == &m_RecvOverlapped ); + HandleRecvCompletion( pInfo, dwNumBytes ); + } + else + { + Assert( pInfo->m_OPType == OP_SEND ); + HandleSendCompletion( pInfo, dwNumBytes ); + } + } + + if ( ShouldExitThreads() ) + break; + } + + return 0; + } + + + static DWORD WINAPI StaticThreadFn( LPVOID pParameter ) + { + return ((CTCPSocket*)pParameter)->ThreadFn(); + } + + + +private: + + SOCKET m_Socket; + bool m_bConnected; + + + // m_RecvOverlapped is setup to first wait for the size, then the data. + // Then it is not posted until the app grabs the data. + HANDLE m_hRecvSignal; // Tells Recv() when we have data. + COverlappedPlus m_RecvOverlapped; + int m_RecvStage; // -1 = not initialized + // 0 = waiting for size + // 1 = waiting for data + // 2 = waiting for app to pickup the data + + CUtlLinkedList<CRecvData*,int> m_RecvDatas; // The head element is the next one to be picked up. + CRecvData *m_pIncomingData; // The packet we're currently receiving. + CCriticalSection m_RecvDataCS; // This protects adds and removes in the list. + + // These reference the element at the tail of m_RecvData. It is the current one getting + volatile int m_nSizeBytesReceived; // How much of m_RecvDataSize have we received yet? + int m_RecvDataSize; // this is received over the network + int m_AmountReceived; // How much we've received so far. + + // Last time we received anything from this connection. Used to determine if the connection is + // still active. + double m_LastRecvTime; + + + // Outgoing send buffers. + CUtlLinkedList<SendBuf_t*,int> m_SendBufs; + CCriticalSection m_SendCS; + + + // All the threads waiting for IO. + CUtlVector<HANDLE> m_Threads; + HANDLE m_hIOCP; + + // Used during shutdown. + volatile bool m_bShouldExitThreads; + CCriticalSection m_ThreadsCS; + + // For debugging. + DWORD m_MainThreadID; + + // Set by the main thread or IO threads to signal connection lost. + bool m_bConnectionLost; + CCriticalSection m_ConnectionLostCS; + + // This is set when we get disconnected. + CUtlVector<char> m_ErrorString; +}; + + +// ------------------------------------------------------------------------------------------ // +// ITCPListenSocket implementation. +// ------------------------------------------------------------------------------------------ // + +class CTCPListenSocket : public ITCPListenSocket +{ +public: + + CTCPListenSocket() + { + m_Socket = INVALID_SOCKET; + } + + + virtual ~CTCPListenSocket() + { + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + } + } + + + // The main function to create one of these suckers. + static ITCPListenSocket* Create( const unsigned short port, int nQueueLength ) + { + CTCPListenSocket *pRet = new CTCPListenSocket; + if ( !pRet ) + return NULL; + + // Bind it to a socket and start listening. + CIPAddr addr( 0, 0, 0, 0, port ); // INADDR_ANY + pRet->m_Socket = TCPBind( &addr ); + if ( pRet->m_Socket == INVALID_SOCKET || + listen( pRet->m_Socket, nQueueLength == -1 ? SOMAXCONN : nQueueLength ) != 0 ) + { + pRet->Release(); + return false; + } + + return pRet; + } + + + virtual void Release() + { + delete this; + } + + + virtual ITCPSocket* UpdateListen( CIPAddr *pAddr ) + { + // We're still ok.. just wait until the socket becomes writable (is connected) or we timeout. + fd_set readSet; + readSet.fd_count = 1; + readSet.fd_array[0] = m_Socket; + TIMEVAL timeVal = SetupTimeVal( 0 ); + + // Wait until it connects. + int status = select( 0, &readSet, NULL, NULL, &timeVal ); + if ( status > 0 ) + { + sockaddr_in addr; + int addrSize = sizeof( addr ); + + // Now accept the final connection. + SOCKET newSock = accept( m_Socket, (struct sockaddr*)&addr, &addrSize ); + if ( newSock == INVALID_SOCKET ) + { + Assert( false ); + } + else + { + CTCPSocket *pRet = new CTCPSocket; + if ( !pRet ) + { + closesocket( newSock ); + return NULL; + } + + pRet->m_Socket = newSock; + pRet->SetInitialSocketOptions(); + pRet->SetupConnected(); + + // Report the address.. + SockAddrToIPAddr( &addr, pAddr ); + + return pRet; + } + } + + return NULL; + } + + +private: + SOCKET m_Socket; +}; + + + +ITCPListenSocket* CreateTCPListenSocket( const unsigned short port, int nQueueLength ) +{ + return CTCPListenSocket::Create( port, nQueueLength ); +} + + +ITCPSocket* CreateTCPSocket() +{ + return new CTCPSocket; +} + + +void TCPSocket_EnableTimeout( bool bEnable ) +{ + g_bEnableTCPTimeout = bEnable; +} + + +// --------------------------------------------------------------------------------- // +// This thread sends keepalives on all active TCP sockets. +// --------------------------------------------------------------------------------- // + +HANDLE g_hKeepaliveThread; +HANDLE g_hKeepaliveThreadSignal; +HANDLE g_hKeepaliveThreadReply; +CUtlLinkedList<CTCPSocket*,int> g_TCPSockets; +CCriticalSection g_TCPSocketsCS; + + +DWORD WINAPI TCPKeepaliveThread( LPVOID pParameter ) +{ + while ( 1 ) + { + if ( WaitForSingleObject( g_hKeepaliveThreadSignal, KEEPALIVE_INTERVAL_MS ) == WAIT_OBJECT_0 ) + break; + + // Tell all TCP sockets to send a keepalive. + CCriticalSectionLock csLock( &g_TCPSocketsCS ); + csLock.Lock(); + + FOR_EACH_LL( g_TCPSockets, i ) + { + g_TCPSockets[i]->SendKeepalive(); + } + } + + SetEvent( g_hKeepaliveThreadReply ); + return 0; +} + + +void AddGlobalTCPSocket( CTCPSocket *pSocket ) +{ + CCriticalSectionLock csLock( &g_TCPSocketsCS ); + csLock.Lock(); + + Assert( g_TCPSockets.Find( pSocket ) == g_TCPSockets.InvalidIndex() ); + g_TCPSockets.AddToTail( pSocket ); + + // If this is the first one, create the keepalive thread. + if ( g_TCPSockets.Count() == 1 ) + { + g_hKeepaliveThreadSignal = CreateEvent( NULL, false, false, NULL ); + g_hKeepaliveThreadReply = CreateEvent( NULL, false, false, NULL ); + + DWORD dwThreadID = 0; + g_hKeepaliveThread = CreateThread( + NULL, + 0, + TCPKeepaliveThread, + NULL, + 0, + &dwThreadID + ); + } +} + + +void RemoveGlobalTCPSocket( CTCPSocket *pSocket ) +{ + bool bThreadRunning = false; + DWORD dwExitCode = 0; + if ( GetExitCodeThread( g_hKeepaliveThread, &dwExitCode ) && dwExitCode == STILL_ACTIVE ) + { + bThreadRunning = true; + } + + CCriticalSectionLock csLock( &g_TCPSocketsCS ); + csLock.Lock(); + + int index = g_TCPSockets.Find( pSocket ); + if ( index != g_TCPSockets.InvalidIndex() ) + { + g_TCPSockets.Remove( index ); + + // If this was the last one, delete the thread. + if ( g_TCPSockets.Count() == 0 ) + { + csLock.Unlock(); + + if ( bThreadRunning ) + { + SetEvent( g_hKeepaliveThreadSignal ); + WaitForSingleObject( g_hKeepaliveThreadReply, INFINITE ); + } + + CloseHandle( g_hKeepaliveThreadSignal ); + CloseHandle( g_hKeepaliveThreadReply ); + CloseHandle( g_hKeepaliveThread ); + return; + } + } + + csLock.Unlock(); +} diff --git a/utils/vmpi/tcpsocket.h b/utils/vmpi/tcpsocket.h new file mode 100644 index 0000000..7cb2998 --- /dev/null +++ b/utils/vmpi/tcpsocket.h @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ITCPSOCKET_H +#define ITCPSOCKET_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ichannel.h" +#include "iphelpers.h" + + +class ITCPSocket : public IChannel +{ +public: + virtual void Release() = 0; + + // Bind to the specified port on any address this host has. Note that the address the + // socket winds up with (and getsockname() returns) isn't decided until you send a packet. + virtual bool BindToAny( const unsigned short port ) = 0; + + + // Use these to connect to a remote listening socket without blocking. + // Call BeginConnect, then call UpdateConnect until it returns true. + virtual bool BeginConnect( const CIPAddr &addr ) = 0; + virtual bool UpdateConnect() = 0; + + + // Connection state. + virtual bool IsConnected() = 0; + + // If IsConnected returns false, you can call this to find out why the socket got disconnected. + virtual void GetDisconnectReason( CUtlVector<char> &reason ) = 0; + + + // Send data. Returns true if successful. + // + // Note: TCP likes to clump your packets together in one, so the data multiple send() calls will + // get concatenated and returned in one recv() call. ITCPSocket FIXES this behavior so your recv() + // calls match your send() calls. + // + virtual bool Send( const void *pData, int size ) = 0; + + // Receive data. Returns the number of bytes received. + // This will wait as long as flTimeout for something to come in. + // Returns false if no data was waiting. + virtual bool Recv( CUtlVector<unsigned char> &data, double flTimeout=0 ) = 0; +}; + + +// Use these to get incoming connections. +class ITCPListenSocket +{ +public: + // Call this to stop listening for connections and delete the object. + virtual void Release() = 0; + + // Keep calling this as long as you want to wait for connections. + virtual ITCPSocket* UpdateListen( CIPAddr *pAddr ) = 0; // pAddr is set to the remote process's address. +}; + + + + +// Use these to create the interfaces. +ITCPSocket* CreateTCPSocket(); + +// Create a socket to listen with. nQueueLength specifies how many connections to enqueue. +// When the queue runs out, connections can take a little longer to make. +ITCPListenSocket* CreateTCPListenSocket( const unsigned short port, int nQueueLength = -1 ); + + +// By default, timeouts are on. It's helpful to turn them off during debugging. +void TCPSocket_EnableTimeout( bool bEnable ); + + + +#endif // ITCPSOCKET_H diff --git a/utils/vmpi/tcpsocket_helpers.cpp b/utils/vmpi/tcpsocket_helpers.cpp new file mode 100644 index 0000000..c336b21 --- /dev/null +++ b/utils/vmpi/tcpsocket_helpers.cpp @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <windows.h> +#include "tcpsocket_helpers.h" + + +// This connects to an ISocket listening with Listen(). +bool TCPSocket_Connect( ITCPSocket *pSocket, const CIPAddr *pAddr, double flTimeout ) +{ + pSocket->BeginConnect( *pAddr ); + + CWaitTimer waitTimer( flTimeout ); + while ( 1 ) + { + if ( pSocket->UpdateConnect() ) + return true; + + if ( waitTimer.ShouldKeepWaiting() ) + Sleep( 10 ); + else + break; + } + + return false; +} + + +ITCPSocket* TCPSocket_ListenForOneConnection( ITCPListenSocket *pSocket, CIPAddr *pAddr, double flTimeout ) +{ + CWaitTimer waitTimer( flTimeout ); + while ( 1 ) + { + ITCPSocket *pRet = pSocket->UpdateListen( pAddr ); + if ( pRet ) + return pRet; + + if ( waitTimer.ShouldKeepWaiting() ) + Sleep( 10 ); + else + break; + } + + return NULL; +} diff --git a/utils/vmpi/tcpsocket_helpers.h b/utils/vmpi/tcpsocket_helpers.h new file mode 100644 index 0000000..870e7e9 --- /dev/null +++ b/utils/vmpi/tcpsocket_helpers.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TCPSOCKET_HELPERS_H +#define TCPSOCKET_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tcpsocket.h" + + +bool TCPSocket_Connect( ITCPSocket *pSocket, const CIPAddr *pAddr, double flTimeout ); +ITCPSocket* TCPSocket_ListenForOneConnection( ITCPListenSocket *pSocket, CIPAddr *pAddr, double flTimeout ); + + +#endif // TCPSOCKET_HELPERS_H diff --git a/utils/vmpi/testapps/MessageWatch/MessageRecvMgr.h b/utils/vmpi/testapps/MessageWatch/MessageRecvMgr.h new file mode 100644 index 0000000..f570ddc --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageRecvMgr.h @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MESSAGERECVMGR_H +#define MESSAGERECVMGR_H +#ifdef _WIN32 +#pragma once +#endif + + +class IMessageRecvMgr +{ +public: + +}; + + +#endif // MESSAGERECVMGR_H diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatch.cpp b/utils/vmpi/testapps/MessageWatch/MessageWatch.cpp new file mode 100644 index 0000000..4e8dab0 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatch.cpp @@ -0,0 +1,77 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// MessageWatch.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "MessageWatch.h" +#include "MessageWatchDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchApp + +BEGIN_MESSAGE_MAP(CMessageWatchApp, CWinApp) + //{{AFX_MSG_MAP(CMessageWatchApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchApp construction + +CMessageWatchApp::CMessageWatchApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CMessageWatchApp object + +CMessageWatchApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchApp initialization + +BOOL CMessageWatchApp::InitInstance() +{ + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif + + CMessageWatchDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + return FALSE; +} diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatch.h b/utils/vmpi/testapps/MessageWatch/MessageWatch.h new file mode 100644 index 0000000..3ad65ab --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatch.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// MessageWatch.h : main header file for the MESSAGEWATCH application +// + +#if !defined(AFX_MESSAGEWATCH_H__72A09EC9_2B19_4AC5_A281_5FAD41F6DFCA__INCLUDED_) +#define AFX_MESSAGEWATCH_H__72A09EC9_2B19_4AC5_A281_5FAD41F6DFCA__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchApp: +// See MessageWatch.cpp for the implementation of this class +// + +class CMessageWatchApp : public CWinApp +{ +public: + CMessageWatchApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMessageWatchApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CMessageWatchApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MESSAGEWATCH_H__72A09EC9_2B19_4AC5_A281_5FAD41F6DFCA__INCLUDED_) diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatch.rc b/utils/vmpi/testapps/MessageWatch/MessageWatch.rc new file mode 100644 index 0000000..bfbbbd5 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatch.rc @@ -0,0 +1,194 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\MessageWatch.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\MessageWatch.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_MESSAGEWATCH_DIALOG DIALOGEX 0, 0, 232, 147 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "MessageWatch" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Quit",IDCANCEL,175,126,50,14 + LISTBOX IDC_MACHINES,7,7,218,114,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Show All",IDSHOWALL,7,126,50,14 + PUSHBUTTON "&Hide All",IDHIDEALL,91,126,50,14 +END + +IDD_OUTPUT DIALOG DISCARDABLE 0, 0, 320, 225 +STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Output" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_DEBUG_OUTPUT,7,7,306,211,ES_MULTILINE | ES_READONLY | + WS_VSCROLL +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "MessageWatch MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "MessageWatch\0" + VALUE "LegalCopyright", "Copyright (C) 2002\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "MessageWatch.EXE\0" + VALUE "ProductName", "MessageWatch Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MESSAGEWATCH_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 225 + TOPMARGIN, 7 + BOTTOMMARGIN, 140 + END + + IDD_OUTPUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 313 + TOPMARGIN, 7 + BOTTOMMARGIN, 218 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\MessageWatch.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatch.vcproj b/utils/vmpi/testapps/MessageWatch/MessageWatch.vcproj new file mode 100644 index 0000000..1fd1804 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatch.vcproj @@ -0,0 +1,282 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="MessageWatch" + ProjectGUID="{52DE0F9C-D5D8-4C31-A6EB-6841285BF1CB}" + SccProjectName="" + SccAuxPath="" + SccLocalPath="" + SccProvider="" + Keyword="MFCProj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="1" + UseOfMFC="2" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\common,..\..\..\public,..\..\..\public\tier1,..\..\..\common,.." + PreprocessorDefinitions="_DEBUG;WIN32;_WINDOWS;PROTECTED_THINGS_DISABLE" + BasicRuntimeChecks="3" + RuntimeLibrary="3" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Debug/MessageWatch.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(TargetPath)" ..\..\..\..\game\bin +" + Outputs="..\..\..\..\game\bin\messagewatch.exe"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + OutputFile=".\Debug/MessageWatch.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/MessageWatch.pdb" + SubSystem="2" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="TRUE" + SuppressStartupBanner="TRUE" + TargetEnvironment="1" + TypeLibraryName=".\Debug/MessageWatch.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="1" + UseOfMFC="2" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="..\..\common,..\..\..\public,..\..\..\public\tier1,..\..\..\common,.." + PreprocessorDefinitions="NDEBUG;WIN32;_WINDOWS;PROTECTED_THINGS_DISABLE" + StringPooling="TRUE" + RuntimeLibrary="2" + EnableFunctionLevelLinking="TRUE" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Release/MessageWatch.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool" + CommandLine="copy "$(TargetPath)" ..\..\..\..\game\bin +" + Outputs="..\..\..\..\game\bin\messagewatch.exe"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + OutputFile=".\Release/MessageWatch.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + ProgramDatabaseFile=".\Release/MessageWatch.pdb" + SubSystem="2" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="TRUE" + SuppressStartupBanner="TRUE" + TargetEnvironment="1" + TypeLibraryName=".\Release/MessageWatch.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"> + <File + RelativePath="..\..\common\consolewnd.cpp"> + </File> + <File + RelativePath="MessageWatch.cpp"> + </File> + <File + RelativePath="MessageWatch.rc"> + </File> + <File + RelativePath="MessageWatchDlg.cpp"> + </File> + <File + RelativePath="StdAfx.cpp"> + </File> + <File + RelativePath="win_idle.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl"> + <File + RelativePath="..\..\common\consolewnd.h"> + </File> + <File + RelativePath="..\..\common\iphelpers.h"> + </File> + <File + RelativePath="..\..\common\messagemgr.h"> + </File> + <File + RelativePath="MessageWatch.h"> + </File> + <File + RelativePath="MessageWatchDlg.h"> + </File> + <File + RelativePath="Resource.h"> + </File> + <File + RelativePath="StdAfx.h"> + </File> + <File + RelativePath="..\..\common\tcpsocket.h"> + </File> + <File + RelativePath="..\..\common\threadhelpers.h"> + </File> + <File + RelativePath="win_idle.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"> + <File + RelativePath="res\MessageWatch.ico"> + </File> + <File + RelativePath="res\MessageWatch.rc2"> + </File> + </Filter> + <File + RelativePath="ReadMe.txt"> + </File> + <File + RelativePath="..\..\..\lib\public\tier0.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\vmpi.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\vstdlib.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.cpp b/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.cpp new file mode 100644 index 0000000..9098caa --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.cpp @@ -0,0 +1,324 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// MessageWatchDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "MessageWatch.h" +#include "MessageWatchDlg.h" +#include "messagemgr.h" +#include "tier1/strtools.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define WM_STARTIDLE (WM_USER + 565) + + +// --------------------------------------------------------------------------- // +// CSender. +// --------------------------------------------------------------------------- // + +CSender::CSender() +{ + m_pSocket = NULL; + m_pConsoleWnd = NULL; +} + +CSender::~CSender() +{ + if ( m_pSocket ) + m_pSocket->Release(); + + if ( m_pConsoleWnd ) + m_pConsoleWnd->Release(); +} + + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchDlg dialog + +CMessageWatchDlg::CMessageWatchDlg(CWnd* pParent /*=NULL*/) + : CDialog(CMessageWatchDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CMessageWatchDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + + m_pListenSocket = NULL; +} + +CMessageWatchDlg::~CMessageWatchDlg() +{ + // destroy the sender objects. + + if ( m_pListenSocket ) + m_pListenSocket->Release(); +} + +void CMessageWatchDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CMessageWatchDlg) + DDX_Control(pDX, IDC_MACHINES, m_Machines); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CMessageWatchDlg, CDialog) + //{{AFX_MSG_MAP(CMessageWatchDlg) + ON_MESSAGE(WM_STARTIDLE, OnStartIdle) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_LBN_DBLCLK(IDC_MACHINES, OnDblclkMachines) + ON_BN_CLICKED(IDSHOWALL, OnShowall) + ON_BN_CLICKED(IDHIDEALL, OnHideall) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchDlg message handlers + +BOOL CMessageWatchDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // Setup our listen socket and thread. + m_pListenSocket = CreateIPSocket(); + m_pListenSocket->BindToAny( MSGMGR_BROADCAST_PORT ); + + m_cWinIdle.StartIdle( GetSafeHwnd(), WM_STARTIDLE, 0, 0, 100 ); + m_cWinIdle.NextIdle(); + + return TRUE; // return TRUE unless you set the focus to a control +} + + +LONG CMessageWatchDlg::OnStartIdle( UINT, LONG ) +{ + MSG msg; + if (!PeekMessage(&msg, GetSafeHwnd(), 0,0, PM_NOREMOVE)) + OnIdle(); + m_cWinIdle.NextIdle(); + return 0; +} + + +void CMessageWatchDlg::OnIdle() +{ + // Kill dead connections. + int iNext; + for ( int iSender=m_Senders.Head(); iSender != m_Senders.InvalidIndex(); iSender = iNext ) + { + iNext = m_Senders.Next( iSender ); + + CSender *pSender = m_Senders[iSender]; + if ( pSender->m_pSocket && !pSender->m_pSocket->IsConnected() ) + { + // Just release the socket so the text stays there. + pSender->m_pSocket->Release(); + pSender->m_pSocket = NULL; + } + } + + // Look for new connections. + while ( 1 ) + { + CIPAddr ipFrom; + char data[16]; + int len = m_pListenSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + if ( len == -1 ) + break; + + if ( data[0] == MSGMGR_PACKETID_ANNOUNCE_PRESENCE && + *((int*)&data[1]) == MSGMGR_VERSION ) + { + int iPort = *((int*)&data[5]); + + // See if we have a machine with this info yet. + CIPAddr connectAddr = ipFrom; + connectAddr.port = iPort; + + // NOTE: we'll accept connections from machines we were connected to earlier but + // lost the connection to. + CSender *pSender = FindSenderByAddr( ipFrom.ip ); + if ( !pSender || !pSender->m_pSocket ) + { + // 'nitiate the connection. + ITCPSocket *pNew = CreateTCPSocket(); + if ( pNew->BindToAny( 0 ) && TCPSocket_Connect( pNew, &connectAddr, 1000 ) ) + { + char nameStr[256]; + char title[512]; + if ( !ConvertIPAddrToString( &ipFrom, nameStr, sizeof( nameStr ) ) ) + Q_snprintf( nameStr, sizeof( nameStr ), "%d.%d.%d.%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3] ); + + Q_snprintf( title, sizeof( title ), "%s:%d", nameStr, iPort ); + + // If the sender didn't exist yet, add a new one. + if ( !pSender ) + { + pSender = new CSender; + + IConsoleWnd *pWnd = CreateConsoleWnd( + AfxGetInstanceHandle(), + IDD_OUTPUT, + IDC_DEBUG_OUTPUT, + false + ); + + pSender->m_pConsoleWnd = pWnd; + pWnd->SetTitle( title ); + + Q_strncpy( pSender->m_Name, title, sizeof( pSender->m_Name ) ); + m_Senders.AddToTail( pSender ); + m_Machines.AddString( pSender->m_Name ); + } + + pSender->m_Addr = connectAddr; + pSender->m_pSocket = pNew; + } + else + { + pNew->Release(); + } + } + } + } + + + // Read input from our current connections. + FOR_EACH_LL( m_Senders, i ) + { + CSender *pSender = m_Senders[i]; + + while ( 1 ) + { + if ( !pSender->m_pSocket ) + break; + + CUtlVector<unsigned char> data; + if ( !pSender->m_pSocket->Recv( data ) ) + break; + + if ( data[0] == MSGMGR_PACKETID_MSG ) + { + char *pMsg = (char*)&data[1]; + pSender->m_pConsoleWnd->PrintToConsole( pMsg ); + OutputDebugString( pMsg ); + } + } + } +} + + +void CMessageWatchDlg::OnDestroy() +{ + // Stop the idling thread + m_cWinIdle.EndIdle(); + CDialog::OnDestroy(); +} + + +CSender* CMessageWatchDlg::FindSenderByAddr( const unsigned char ip[4] ) +{ + FOR_EACH_LL( m_Senders, i ) + { + if ( memcmp( m_Senders[i]->m_Addr.ip, ip, 4 ) == 0 ) + return m_Senders[i]; + } + return NULL; +} + + +CSender* CMessageWatchDlg::FindSenderByName( const char *pName ) +{ + FOR_EACH_LL( m_Senders, i ) + { + if ( stricmp( pName, m_Senders[i]->m_Name ) == 0 ) + return m_Senders[i]; + } + return NULL; +} + + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CMessageWatchDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CMessageWatchDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + +void CMessageWatchDlg::OnDblclkMachines() +{ + int index = m_Machines.GetCurSel(); + if ( index != LB_ERR ) + { + CString str; + m_Machines.GetText( index, str ); + + CSender *pSender = FindSenderByName( str ); + if ( pSender ) + pSender->m_pConsoleWnd->SetVisible( true ); + } +} + +void CMessageWatchDlg::OnShowall() +{ + FOR_EACH_LL( m_Senders, i ) + { + m_Senders[i]->m_pConsoleWnd->SetVisible( true ); + } +} + +void CMessageWatchDlg::OnHideall() +{ + FOR_EACH_LL( m_Senders, i ) + { + m_Senders[i]->m_pConsoleWnd->SetVisible( false ); + } +} diff --git a/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.h b/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.h new file mode 100644 index 0000000..e495a54 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/MessageWatchDlg.h @@ -0,0 +1,100 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// MessageWatchDlg.h : header file +// + +#if !defined(AFX_MESSAGEWATCHDLG_H__AB9CEAF4_0166_4CCA_9DEC_77C0918F78C4__INCLUDED_) +#define AFX_MESSAGEWATCHDLG_H__AB9CEAF4_0166_4CCA_9DEC_77C0918F78C4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +#include "iphelpers.h" +#include "tcpsocket.h" +#include "threadhelpers.h" +#include "consolewnd.h" +#include "win_idle.h" + + +///////////////////////////////////////////////////////////////////////////// +// CMessageWatchDlg dialog + +class CSender +{ +public: + CSender(); + ~CSender(); + +public: + + CIPAddr m_Addr; + ITCPSocket *m_pSocket; + IConsoleWnd *m_pConsoleWnd; + char m_Name[128]; +}; + +class CMessageWatchDlg : public CDialog +{ +// Construction +public: + CMessageWatchDlg(CWnd* pParent = NULL); // standard constructor + ~CMessageWatchDlg(); + + + // Listen for broadcasts on this socket. + ISocket *m_pListenSocket; + + // Connections we've made. + CUtlLinkedList<CSender*,int> m_Senders; + + CCriticalSection m_SocketsCS; + CWinIdle m_cWinIdle; + + + CSender* FindSenderByAddr( const unsigned char ip[4] ); + CSender* FindSenderByName( const char *pName ); + + +// Dialog Data + //{{AFX_DATA(CMessageWatchDlg) + enum { IDD = IDD_MESSAGEWATCH_DIALOG }; + CListBox m_Machines; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CMessageWatchDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + void OnIdle(); + + // Generated message map functions + //{{AFX_MSG(CMessageWatchDlg) + afx_msg void OnDestroy(); + afx_msg LONG OnStartIdle(UINT, LONG); + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnDblclkMachines(); + afx_msg void OnShowall(); + afx_msg void OnHideall(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_MESSAGEWATCHDLG_H__AB9CEAF4_0166_4CCA_9DEC_77C0918F78C4__INCLUDED_) diff --git a/utils/vmpi/testapps/MessageWatch/StdAfx.cpp b/utils/vmpi/testapps/MessageWatch/StdAfx.cpp new file mode 100644 index 0000000..43fa619 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// MessageWatch.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/utils/vmpi/testapps/MessageWatch/StdAfx.h b/utils/vmpi/testapps/MessageWatch/StdAfx.h new file mode 100644 index 0000000..adf866a --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/StdAfx.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__70653A1B_FB34_4AD9_861C_580071240D6F__INCLUDED_) +#define AFX_STDAFX_H__70653A1B_FB34_4AD9_861C_580071240D6F__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include <afxwin.h> // MFC core and standard components +#include <afxext.h> // MFC extensions +#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include <afxcmn.h> // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__70653A1B_FB34_4AD9_861C_580071240D6F__INCLUDED_) diff --git a/utils/vmpi/testapps/MessageWatch/res/MessageWatch.ico b/utils/vmpi/testapps/MessageWatch/res/MessageWatch.ico Binary files differnew file mode 100644 index 0000000..dc0d87d --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/res/MessageWatch.ico diff --git a/utils/vmpi/testapps/MessageWatch/res/MessageWatch.rc2 b/utils/vmpi/testapps/MessageWatch/res/MessageWatch.rc2 new file mode 100644 index 0000000..cf543e7 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/res/MessageWatch.rc2 @@ -0,0 +1,13 @@ +// +// MESSAGEWATCH.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/utils/vmpi/testapps/MessageWatch/resource.h b/utils/vmpi/testapps/MessageWatch/resource.h new file mode 100644 index 0000000..05f2c75 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/resource.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by MessageWatch.rc +// +#define IDSHOWALL 3 +#define IDHIDEALL 4 +#define IDD_MESSAGEWATCH_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_OUTPUT 129 +#define IDC_MACHINES 1000 +#define IDC_DEBUG_OUTPUT 1000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/testapps/MessageWatch/win_idle.cpp b/utils/vmpi/testapps/MessageWatch/win_idle.cpp new file mode 100644 index 0000000..69ac9da --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/win_idle.cpp @@ -0,0 +1,123 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// Class for sending idle messages to a window + +#include "stdafx.h" +#include "win_idle.h" + +// Stub function to get into the object's main thread loop +DWORD WINAPI CWinIdle::ThreadStub(LPVOID pIdle) +{ + return ((CWinIdle *)pIdle)->RunIdle(); +} + +CWinIdle::CWinIdle() : + m_hIdleThread(NULL), + m_hIdleEvent(NULL), + m_hStopEvent(NULL), + m_hWnd(0), + m_uMsg(0), + m_dwDelay(0) +{ +} + +CWinIdle::~CWinIdle() +{ + if (m_hIdleThread) + OutputDebugString("!!CWinIdle Warning!! Idle thread not shut down!\n"); +} + +DWORD CWinIdle::RunIdle() +{ + // Set up an event list + HANDLE aEvents[2]; + + aEvents[0] = m_hStopEvent; + aEvents[1] = m_hIdleEvent; + + // Wait for a stop or idle event + while (WaitForMultipleObjects(2, aEvents, FALSE, INFINITE) != WAIT_OBJECT_0) + { + // Send an idle message + PostMessage(m_hWnd, m_uMsg, m_wParam, m_lParam); + // Wait for a bit... + Sleep(m_dwDelay); + } + + return 0; +} + +BOOL CWinIdle::StartIdle(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam, DWORD dwDelay) +{ + // Make sure it's not already running + if (m_hIdleThread) + return FALSE; + + // Make sure they send in a valid handle.. + if (!hWnd) + return FALSE; + + // Create the events + m_hIdleEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Make sure the events got created + if ((!m_hIdleEvent) || (!m_hStopEvent)) + return FALSE; + + // Create the thread + DWORD dwThreadID; + m_hIdleThread = CreateThread(NULL, 0, CWinIdle::ThreadStub, (void *)this, 0, &dwThreadID); + + if (m_hIdleThread) + { + SetThreadPriority(m_hIdleThread, THREAD_PRIORITY_IDLE); + + m_hWnd = hWnd; + m_uMsg = uMessage; + m_wParam = wParam; + m_lParam = lParam; + + m_dwDelay = dwDelay; + } + + return m_hIdleThread != 0; +} + +BOOL CWinIdle::EndIdle() +{ + // Make sure it's running + if (!m_hIdleThread) + return FALSE; + + // Stop the idle thread + SetEvent(m_hStopEvent); + WaitForSingleObject(m_hIdleThread, INFINITE); + CloseHandle(m_hIdleThread); + + // Get rid of the event objects + CloseHandle(m_hIdleEvent); + CloseHandle(m_hStopEvent); + + // Set everything back to 0 + m_hIdleEvent = 0; + m_hStopEvent = 0; + m_hIdleThread = 0; + + return TRUE; +} + +void CWinIdle::NextIdle() +{ + // Make sure the thread's running + if (!m_hIdleThread) + return; + + // Signal an idle message + SetEvent(m_hIdleEvent); +} diff --git a/utils/vmpi/testapps/MessageWatch/win_idle.h b/utils/vmpi/testapps/MessageWatch/win_idle.h new file mode 100644 index 0000000..6e2e3f3 --- /dev/null +++ b/utils/vmpi/testapps/MessageWatch/win_idle.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// WinIdle.h - Defines a class for sending idle messages to a window from a secondary thread + +#ifndef __WINIDLE_H__ +#define __WINIDLE_H__ + + +class CWinIdle +{ +protected: + HANDLE m_hIdleEvent, m_hStopEvent; + + HWND m_hWnd; + UINT m_uMsg; + WPARAM m_wParam; + LPARAM m_lParam; + + DWORD m_dwDelay; + + HANDLE m_hIdleThread; + + // The thread calling stub + static DWORD WINAPI ThreadStub(LPVOID pIdle); + // The actual idle loop + virtual DWORD RunIdle(); + +public: + CWinIdle(); + virtual ~CWinIdle(); + + inline DWORD GetDelay() {return m_dwDelay;} + inline void SetDelay(DWORD delay) {m_dwDelay = delay;} + + // Member access + virtual HANDLE GetThreadHandle() const { return m_hIdleThread; }; + + // Start idling, and define the message and window to use + // Returns TRUE on success + virtual BOOL StartIdle(HWND hWnd, UINT uMessage, WPARAM wParam = 0, LPARAM lParam = 0, DWORD dwDelay = 0); + // Stop idling + // Returns TRUE on success + virtual BOOL EndIdle(); + // Notify the idle process that the message was received. + // Note : If this function is not called, the idle thread will not send any messages + virtual void NextIdle(); +}; + + +// Used to slow down the idle thread while dialogs are up. +class IdleChanger +{ +public: + IdleChanger(CWinIdle *pIdle, DWORD msDelay) + { + m_pIdle = pIdle; + m_OldDelay = pIdle->GetDelay(); + pIdle->SetDelay(msDelay); + } + + ~IdleChanger() + { + m_pIdle->SetDelay(m_OldDelay); + } + + CWinIdle *m_pIdle; + DWORD m_OldDelay; +}; + + + +#endif //__WINIDLE_H__ + diff --git a/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.cpp b/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.cpp new file mode 100644 index 0000000..538b0e0 --- /dev/null +++ b/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// ThreadedTCPSocketTest.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.h b/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.h new file mode 100644 index 0000000..e29801c --- /dev/null +++ b/utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__AA10D99C_786F_4324_86C6_4D7CDE546561__INCLUDED_) +#define AFX_STDAFX_H__AA10D99C_786F_4324_86C6_4D7CDE546561__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <stdio.h> +#include <windows.h> +#include <conio.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__AA10D99C_786F_4324_86C6_4D7CDE546561__INCLUDED_) diff --git a/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.cpp b/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.cpp new file mode 100644 index 0000000..0b82c56 --- /dev/null +++ b/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.cpp @@ -0,0 +1,198 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ThreadedTCPSocketTest.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "IThreadedTCPSocket.h" +#include "threadhelpers.h" +#include "vstdlib/random.h" + + +CCriticalSection g_MsgCS; + + +IThreadedTCPSocket *g_pClientSocket = NULL; +IThreadedTCPSocket *g_pServerSocket = NULL; + +CEvent g_ClientPacketEvent; +CUtlVector<char> g_ClientPacket; + + + +SpewRetval_t MySpewFunc( SpewType_t type, char const *pMsg ) +{ + CCriticalSectionLock csLock( &g_MsgCS ); + csLock.Lock(); + + printf( "%s", pMsg ); + OutputDebugString( pMsg ); + + csLock.Unlock(); + + if( type == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( type == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +class CHandler_Server : public ITCPSocketHandler +{ +public: + virtual void Init( IThreadedTCPSocket *pSocket ) + { + } + + virtual void OnPacketReceived( CTCPPacket *pPacket ) + { + // Echo the data back. + g_pServerSocket->Send( pPacket->GetData(), pPacket->GetLen() ); + pPacket->Release(); + } + + virtual void OnError( int errorCode, const char *pErrorString ) + { + Msg( "Server error: %s\n", pErrorString ); + } +}; + + + +class CHandler_Client : public ITCPSocketHandler +{ +public: + virtual void Init( IThreadedTCPSocket *pSocket ) + { + } + + virtual void OnPacketReceived( CTCPPacket *pPacket ) + { + if ( g_ClientPacket.Count() < pPacket->GetLen() ) + g_ClientPacket.SetSize( pPacket->GetLen() ); + + memcpy( g_ClientPacket.Base(), pPacket->GetData(), pPacket->GetLen() ); + g_ClientPacketEvent.SetEvent(); + pPacket->Release(); + } + + virtual void OnError( int errorCode, const char *pErrorString ) + { + Msg( "Client error: %s\n", pErrorString ); + } +}; + + + +class CHandlerCreator_Server : public IHandlerCreator +{ +public: + virtual ITCPSocketHandler* CreateNewHandler() + { + return new CHandler_Server; + } +}; + +class CHandlerCreator_Client : public IHandlerCreator +{ +public: + virtual ITCPSocketHandler* CreateNewHandler() + { + return new CHandler_Client; + } +}; + + + +int main(int argc, char* argv[]) +{ + SpewOutputFunc( MySpewFunc ); + + // Figure out a random port to use. + CCycleCount cnt; + cnt.Sample(); + CUniformRandomStream randomStream; + randomStream.SetSeed( cnt.GetMicroseconds() ); + int iPort = randomStream.RandomInt( 20000, 30000 ); + + + g_ClientPacketEvent.Init( false, false ); + + + // Setup the "server". + CHandlerCreator_Server serverHandler; + CIPAddr addr( 127, 0, 0, 1, iPort ); + + ITCPConnectSocket *pListener = ThreadedTCP_CreateListener( + &serverHandler, + (unsigned short)iPort ); + + + // Setup the "client". + CHandlerCreator_Client clientCreator; + ITCPConnectSocket *pConnector = ThreadedTCP_CreateConnector( + CIPAddr( 127, 0, 0, 1, iPort ), + CIPAddr(), + &clientCreator ); + + + // Wait for them to connect. + while ( !g_pClientSocket ) + { + if ( !pConnector->Update( &g_pClientSocket ) ) + { + Error( "Error in client connector!\n" ); + } + } + pConnector->Release(); + + + while ( !g_pServerSocket ) + { + if ( !pListener->Update( &g_pServerSocket ) ) + Error( "Error in server connector!\n" ); + } + pListener->Release(); + + + // Send some data. + __int64 totalBytes = 0; + CCycleCount startTime; + int iPacket = 1; + + startTime.Sample(); + CUtlVector<char> buf; + + while ( (GetAsyncKeyState( VK_SHIFT ) & 0x8000) == 0 ) + { + int size = randomStream.RandomInt( 1024*0, 1024*320 ); + if ( buf.Count() < size ) + buf.SetSize( size ); + + if ( g_pClientSocket->Send( buf.Base(), size ) ) + { + // Server receives the data and echoes it back. Verify that the data is good. + WaitForSingleObject( g_ClientPacketEvent.GetEventHandle(), INFINITE ); + Assert( memcmp( g_ClientPacket.Base(), buf.Base(), size ) == 0 ); + + totalBytes += size; + CCycleCount curTime, elapsed; + curTime.Sample(); + CCycleCount::Sub( curTime, startTime, elapsed ); + double flSeconds = elapsed.GetSeconds(); + Msg( "Packet %d, %d bytes, %dk/sec\n", iPacket++, size, (int)(((totalBytes+511)/1024) / flSeconds) ); + } + } + + g_pClientSocket->Release(); + g_pServerSocket->Release(); + return 0; +} + diff --git a/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.vcproj b/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.vcproj new file mode 100644 index 0000000..60c80d4 --- /dev/null +++ b/utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.vcproj @@ -0,0 +1,253 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="ThreadedTCPSocketTest" + ProjectGUID="{6973F221-D381-4569-901F-6D5311FB4CD5}" + SccProjectName="" + SccAuxPath="" + SccLocalPath="" + SccProvider=""> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..,..\..\..\public,..\..\..\public\tier1" + PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Debug/ThreadedTCPSocketTest.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Debug/ThreadedTCPSocketTest.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/ThreadedTCPSocketTest.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Debug/ThreadedTCPSocketTest.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="..,..\..\..\public,..\..\..\public\tier1" + PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + StringPooling="TRUE" + RuntimeLibrary="0" + EnableFunctionLevelLinking="TRUE" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Release/ThreadedTCPSocketTest.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="3" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Release/ThreadedTCPSocketTest.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Release/ThreadedTCPSocketTest.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Release/ThreadedTCPSocketTest.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"> + <File + RelativePath="StdAfx.cpp"> + </File> + <File + RelativePath="..\ThreadedTCPSocket.cpp"> + </File> + <File + RelativePath="..\ThreadedTCPSocketEmu.cpp"> + </File> + <File + RelativePath="ThreadedTCPSocketTest.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl"> + <File + RelativePath="..\IThreadedTCPSocket.h"> + </File> + <File + RelativePath="StdAfx.h"> + </File> + <File + RelativePath="..\ThreadedTCPSocketEmu.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"> + </Filter> + <File + RelativePath="..\..\..\lib\public\dbg.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\platform.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="ReadMe.txt"> + </File> + <File + RelativePath="..\..\..\lib\public\vmpi.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\vstdlib.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/vmpi/testapps/pingpong/StdAfx.cpp b/utils/vmpi/testapps/pingpong/StdAfx.cpp new file mode 100644 index 0000000..31353c4 --- /dev/null +++ b/utils/vmpi/testapps/pingpong/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// pingpong.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/testapps/pingpong/StdAfx.h b/utils/vmpi/testapps/pingpong/StdAfx.h new file mode 100644 index 0000000..59dfc17 --- /dev/null +++ b/utils/vmpi/testapps/pingpong/StdAfx.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__13A3CFA6_6BF8_45A8_A2CD_91444DCFF7C0__INCLUDED_) +#define AFX_STDAFX_H__13A3CFA6_6BF8_45A8_A2CD_91444DCFF7C0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <stdio.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__13A3CFA6_6BF8_45A8_A2CD_91444DCFF7C0__INCLUDED_) diff --git a/utils/vmpi/testapps/pingpong/pingpong.cpp b/utils/vmpi/testapps/pingpong/pingpong.cpp new file mode 100644 index 0000000..e589197 --- /dev/null +++ b/utils/vmpi/testapps/pingpong/pingpong.cpp @@ -0,0 +1,308 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// pingpong.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include <assert.h> +#include <stdlib.h> +#include "tcpsocket.h" +#include "tier0/fasttimer.h" +#include "vmpi.h" +#include "tcpsocket_helpers.h" + +//#define USE_MPI + + +#if defined( USE_MPI ) + #include "mpi/mpi.h" + #include "vmpi.h" + #include "tier1/bitbuf.h" + + int myProcId = -1; +#else + IChannel *g_pSocket = NULL; + int g_iPortNum = 27141; +#endif + + +int PrintUsage() +{ + printf( "pingpong <-server or -client ip>\n" ); + return 1; +} + + +void DoClientConnect( const char *pIP ) +{ +#if defined( USE_MPI ) + int argc = 1; + char *testargv[1] = { "-nounc" }; + char **argv = testargv; + if ( MPI_Init( &argc, &argv ) ) + { + assert( false ); + } + MPI_Comm_rank( MPI_COMM_WORLD, &myProcId ); + + int nProcs; + MPI_Comm_size( MPI_COMM_WORLD, &nProcs ); + if ( nProcs != 2 ) + { + assert( false ); + } +#else + // Try to connect, or listen. + ITCPSocket *pTCPSocket = CreateTCPSocket(); + if ( !pTCPSocket->BindToAny( 0 ) ) + { + assert( false ); + } + + CIPAddr addr; + if ( !ConvertStringToIPAddr( pIP, &addr ) ) + { + assert( false ); + } + + addr.port = g_iPortNum; + printf( "Client connecting to %d.%d.%d.%d:%d\n", addr.ip[0], addr.ip[1], addr.ip[2], addr.ip[3], addr.port ); + if ( !TCPSocket_Connect( pTCPSocket, &addr, 50000 ) ) + { + assert( false ); + } + + printf( "Client connected...\n "); + g_pSocket = pTCPSocket; +#endif +} + + +void DoServerConnect() +{ +#if defined( USE_MPI ) + ISocket *pSocket = CreateIPSocket(); + if ( !pSocket ) + { + printf( "Error creating a socket.\n" ); + assert( false ); + return; + } + else if ( !pSocket->BindToAny( VMPI_SERVICE_PORT ) ) + { + printf( "Error binding a socket to port %d.\n", VMPI_SERVICE_PORT ); + assert( false ); + return; + } + + printf( "Waiting for jobs...\n" ); + while ( 1 ) + { + // Any incoming packets? + char data[2048]; + CIPAddr ipFrom; + int len = pSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + if ( len > 3 ) + { + bf_read buf( data, len ); + if ( buf.ReadByte() == VMPI_PROTOCOL_VERSION ) + { + if ( buf.ReadByte() == VMPI_LOOKING_FOR_WORKERS ) + { + // Read the listen port. + int iListenPort = buf.ReadLong(); + + static char ipString[128]; + _snprintf( ipString, sizeof( ipString ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iListenPort ); + + int argc = 3; + char *testargv[3]; + testargv[0] = "<supposedly the executable name!>"; + testargv[1] = "-mpi_worker"; + testargv[2] = ipString; + + char **argv = testargv; + if ( MPI_Init( &argc, &argv ) ) + { + assert( false ); + } + MPI_Comm_rank( MPI_COMM_WORLD, &myProcId ); + + int nProcs; + MPI_Comm_size( MPI_COMM_WORLD, &nProcs ); + if ( nProcs != 2 ) + { + assert( false ); + } + break; + } + } + } + + Sleep( 100 ); + } + + pSocket->Release(); +#else + // Try to connect, or listen. + ITCPListenSocket *pListen = CreateTCPListenSocket( g_iPortNum ); + if ( !pListen ) + { + assert( false ); + } + + printf( "Server listening...\n" ); + + CIPAddr addr; + ITCPSocket *pTCPSocket = TCPSocket_ListenForOneConnection( pListen, &addr, 50000 ); + if ( !pTCPSocket ) + { + assert( false ); + } + pListen->Release(); + + printf( "Server connected...\n "); + g_pSocket = pTCPSocket; +#endif +} + + +void SendData( const void *pBuf, int size ) +{ +#if defined( USE_MPI ) + MPI_Send( (void*)pBuf, size, MPI_BYTE, !myProcId, 0, MPI_COMM_WORLD ); +#else + g_pSocket->Send( pBuf, size ); +#endif +} + + +void RecvData( CUtlVector<unsigned char> &recvBuf ) +{ +#if defined( USE_MPI ) + MPI_Status stat; + MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &stat); + + recvBuf.SetCount( stat.count ); + MPI_Recv( recvBuf.Base(), stat.count, MPI_BYTE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &stat); +#else + if ( !g_pSocket->Recv( recvBuf, 50000 ) ) + { + g_pSocket->Release(); + g_pSocket = NULL; + } +#endif +} + + +int main( int argc, char* argv[] ) +{ + if ( argc < 2 ) + { + return PrintUsage(); + } + + const char *pClientOrServer = argv[1]; + const char *pIP = NULL; + + bool bClient = false; + if ( stricmp( pClientOrServer, "-client" ) == 0 ) + { + if ( argc < 3 ) + { + return PrintUsage(); + } + + bClient = true; + pIP = argv[2]; + } + + CUtlVector<unsigned char> recvBuf; + if ( bClient ) + { + DoClientConnect( pIP ); + + // Ok, now start blasting packets of different sizes and measure how long it takes to get an ack back. + int nIterations = 30; + + for ( int size=350; size <= 350000; size += 512 ) + { + CUtlVector<unsigned char> buf; + buf.SetCount( size ); + + double flTotalRoundTripTime = 0; + + CFastTimer throughputTimer; + throughputTimer.Start(); + + for ( int i=0; i < nIterations; i++ ) + { + for ( int z=0; z < size; z++ ) + buf[z] = (char)rand(); + + SendData( buf.Base(), buf.Count() ); + + CFastTimer timer; + timer.Start(); + RecvData( recvBuf ); + timer.End(); + + + // Make sure we got the same data back. + assert( recvBuf.Count() == buf.Count() ); + for ( z=0; z < size; z++ ) + { + assert( recvBuf[z] == buf[z] ); + } + + + //if ( i % 100 == 0 ) + // printf( "%05d\n", i ); +printf( "%d\n", i ); + flTotalRoundTripTime += timer.GetDuration().GetMillisecondsF(); + } + throughputTimer.End(); + double flTotalSeconds = throughputTimer.GetDuration().GetSeconds(); + + double flAvgRoundTripTime = flTotalRoundTripTime / nIterations; + printf( "%d: %.2f ms per roundtrip (%d bytes/sec) sec: %.2f megs: %.2f\n", + size, + flAvgRoundTripTime, + (int)((size*nIterations)/flTotalSeconds), + flTotalSeconds, + (double)(size*nIterations) / (1024*1024) ); + } + + // Send an 'end' message to the server. + int val = -1; + SendData( &val, sizeof( val ) ); + } + else + { + // Wait for a connection. + DoServerConnect(); + + // Wait for packets and ack them. + while ( 1 ) + { + RecvData( recvBuf ); + if ( !g_pSocket ) + break; + + if ( recvBuf.Count() < 4 ) + { + assert( false ); + } + + SendData( recvBuf.Base(), recvBuf.Count() ); + } + } + + return 0; +} + diff --git a/utils/vmpi/testapps/pingpong/pingpong.vcproj b/utils/vmpi/testapps/pingpong/pingpong.vcproj new file mode 100644 index 0000000..e162afb --- /dev/null +++ b/utils/vmpi/testapps/pingpong/pingpong.vcproj @@ -0,0 +1,245 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="pingpong" + ProjectGUID="{5E114A75-BC7C-4E3A-895D-47C097AFF02B}" + SccProjectName="" + SccAuxPath="" + SccLocalPath="" + SccProvider=""> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="..\..\..\public,..\..\common,..\..\vmpi" + PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + StringPooling="TRUE" + RuntimeLibrary="0" + EnableFunctionLevelLinking="TRUE" + PrecompiledHeaderFile=".\Release/pingpong.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="4" + SuppressStartupBanner="TRUE" + DebugInformationFormat="3" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Release/pingpong.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + IgnoreAllDefaultLibraries="FALSE" + IgnoreDefaultLibraryNames="libcmtd.lib" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Release/pingpong.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Release/pingpong.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\..\public,..\..\common,..\..\vmpi" + PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + PrecompiledHeaderFile=".\Debug/pingpong.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="4" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Debug/pingpong.exe" + LinkIncremental="2" + SuppressStartupBanner="TRUE" + IgnoreDefaultLibraryNames="libcmt.lib" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/pingpong.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Debug/pingpong.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"> + <File + RelativePath="..\..\..\public\tier0\memoverride.cpp"> + </File> + <File + RelativePath="pingpong.cpp"> + </File> + <File + RelativePath="StdAfx.cpp"> + </File> + <File + RelativePath="..\tcpsocket.cpp"> + </File> + <File + RelativePath="..\tcpsocket_helpers.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl"> + <File + RelativePath="..\..\..\public\tier1\bitbuf.h"> + </File> + <File + RelativePath="..\..\..\public\platform\fasttimer.h"> + </File> + <File + RelativePath="..\..\common\ichannel.h"> + </File> + <File + RelativePath="..\..\common\iphelpers.h"> + </File> + <File + RelativePath="..\..\common\loopback_channel.h"> + </File> + <File + RelativePath="StdAfx.h"> + </File> + <File + RelativePath="..\..\common\tcpsocket.h"> + </File> + <File + RelativePath="..\..\..\public\tier1\utlvector.h"> + </File> + <File + RelativePath="..\..\common\vmpi.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"> + </Filter> + <File + RelativePath="ReadMe.txt"> + </File> + <File + RelativePath="..\..\..\lib\public\tier0.lib"> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\tier1.lib"> + </File> + <File + RelativePath="..\..\..\lib\public\vmpi.lib"> + </File> + <File + RelativePath="..\..\..\lib\public\vstdlib.lib"> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/vmpi/testapps/socket_stresstest/StdAfx.cpp b/utils/vmpi/testapps/socket_stresstest/StdAfx.cpp new file mode 100644 index 0000000..2b565b0 --- /dev/null +++ b/utils/vmpi/testapps/socket_stresstest/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// socket_stresstest.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/testapps/socket_stresstest/StdAfx.h b/utils/vmpi/testapps/socket_stresstest/StdAfx.h new file mode 100644 index 0000000..3c554be --- /dev/null +++ b/utils/vmpi/testapps/socket_stresstest/StdAfx.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__7E382B26_1CB4_461A_8087_762358153941__INCLUDED_) +#define AFX_STDAFX_H__7E382B26_1CB4_461A_8087_762358153941__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> +#include <stdio.h> +#include "tcpsocket.h" +#include <conio.h> +#include <stdlib.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__7E382B26_1CB4_461A_8087_762358153941__INCLUDED_) diff --git a/utils/vmpi/testapps/socket_stresstest/socket_stresstest.cpp b/utils/vmpi/testapps/socket_stresstest/socket_stresstest.cpp new file mode 100644 index 0000000..55ab97d --- /dev/null +++ b/utils/vmpi/testapps/socket_stresstest/socket_stresstest.cpp @@ -0,0 +1,274 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// socket_stresstest.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "utllinkedlist.h" + + +class CSocketInfo +{ +public: + + bool IsValid() + { + return m_pSocket != 0; + } + + void Term(); + + void ThreadFn(); + + + +public: + ITCPSocket *m_pSocket; + int m_iListenPort; + + DWORD m_CreateTime; // When this socket was created. + DWORD m_ExpireTime; +}; + + + +CSocketInfo g_Infos[132]; +CRITICAL_SECTION g_CS, g_PrintCS; +HANDLE g_hThreads[ ARRAYSIZE( g_Infos ) ]; +bool g_bShouldExit = false; +CUtlLinkedList<int,int> g_ListenPorts; + + +SpewRetval_t StressTestSpew( SpewType_t type, char const *pMsg ) +{ + EnterCriticalSection( &g_PrintCS ); + printf( "%s", pMsg ); + LeaveCriticalSection( &g_PrintCS ); + + if( type == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( type == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +void CSocketInfo::Term() +{ + if ( m_pSocket ) + { + m_pSocket->Release(); + m_pSocket = 0; + } +} + + +CSocketInfo* FindOldestSocketInfo( CSocketInfo *pInfos, int nInfos ) +{ + int iOldest = 0; + DWORD oldestTime = 0xFFFFFFFF; + for ( int i=0; i < nInfos; i++ ) + { + if ( !pInfos[i].IsValid() ) + return &pInfos[i]; + + if ( pInfos[i].m_CreateTime < oldestTime ) + { + oldestTime = pInfos[i].m_CreateTime; + iOldest = i; + } + } + return &pInfos[iOldest]; +} + + +int g_iNextExpire = -1; + +void CSocketInfo::ThreadFn() +{ + int iInfo = this - g_Infos; + + while ( !g_bShouldExit ) + { + DWORD curTime = GetTickCount(); + + // Break the connection after a certain amount of time. + if ( m_pSocket && curTime >= m_ExpireTime ) + { + Term(); + Msg( "%02d: expire.\n", iInfo, m_iListenPort ); + } + + if ( m_pSocket ) + { + EnterCriticalSection( &g_CS ); + if ( g_iNextExpire == -1 ) + { + g_iNextExpire = iInfo; + LeaveCriticalSection( &g_CS ); + + Msg( "%02d: forcing an expire.\n", iInfo, m_iListenPort ); + Sleep( 16000 ); + + EnterCriticalSection( &g_CS ); + g_iNextExpire = -1; + } + LeaveCriticalSection( &g_CS ); + + if ( m_pSocket->IsConnected() ) + { + // Receive whatever data it has waiting for it. + CUtlVector<unsigned char> data; + while ( m_pSocket->Recv( data ) ) + { + Msg( "%02d: recv %d.\n", iInfo, data.Count() ); + } + + // Send some data. + int size = rand() % 8192; + data.SetSize( size ); + m_pSocket->Send( data.Base(), data.Count() ); + //Msg( "%02d: send %d.\n", iInfo, data.Count() ); + } + else + { + Term(); + } + } + else + { + // Not initialized.. either listen or connect. + int iConnectPort = -1; + if ( rand() > VALVE_RAND_MAX/2 ) + { + if ( rand() % 100 < 50 ) + Sleep( 500 ); + + EnterCriticalSection( &g_CS ); + int iHead = g_ListenPorts.Head(); + if ( iHead != g_ListenPorts.InvalidIndex() ) + iConnectPort = g_ListenPorts[iHead]; + LeaveCriticalSection( &g_CS ); + } + + if ( iConnectPort != -1 ) + { + CIPAddr addr( 127, 0, 0, 1, iConnectPort ); + + m_pSocket = CreateTCPSocket(); + m_pSocket->BindToAny( 0 ); + m_CreateTime = curTime; + m_ExpireTime = curTime + rand() % 5000; + if ( !TCPSocket_Connect( m_pSocket, &addr, 3.0f ) ) + { + Term(); + } + } + else + { + for ( int iTry=0; iTry < 32; iTry++ ) + { + m_iListenPort = 100 + rand() % (VALVE_RAND_MAX/2); + ITCPListenSocket *pListenSocket = CreateTCPListenSocket( m_iListenPort ); + if ( pListenSocket ) + { + Msg( "%02d: listen on %d.\n", iInfo, m_iListenPort ); + + // Add us to the list of ports to connect to. + EnterCriticalSection( &g_CS ); + g_ListenPorts.AddToTail( m_iListenPort ); + LeaveCriticalSection( &g_CS ); + + // Listen for a connection. + CIPAddr connectedAddr; + m_pSocket = TCPSocket_ListenForOneConnection( pListenSocket, &connectedAddr, 4.0 ); + + // Remove us from the list of ports to connect to. + EnterCriticalSection( &g_CS ); + g_ListenPorts.Remove( g_ListenPorts.Find( m_iListenPort ) ); + LeaveCriticalSection( &g_CS ); + + pListenSocket->Release(); + + if ( m_pSocket ) + { + Msg( "%02d: listen found connection.\n", iInfo ); + m_CreateTime = curTime; + m_ExpireTime = curTime + rand() % 5000; + } + break; + } + } + } + } + + Sleep( 1 ); + } + + g_hThreads[iInfo] = 0; +} + + +DWORD WINAPI ThreadFn( LPVOID lpParameter ) +{ + CSocketInfo *pInfo = (CSocketInfo*)lpParameter; + pInfo->ThreadFn(); + return 0; +} + + +void AllocError( unsigned long size ) +{ + Assert( false ); +} + + +int main(int argc, char* argv[]) +{ + memset( g_Infos, 0, sizeof( g_Infos ) ); + memset( g_hThreads, 0, sizeof( g_hThreads ) ); + + InitializeCriticalSection( &g_CS ); + InitializeCriticalSection( &g_PrintCS ); + + SpewOutputFunc( StressTestSpew ); + Plat_SetAllocErrorFn( AllocError ); + + SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); + + for ( int i=0; i < ARRAYSIZE( g_Infos ); i++ ) + { + DWORD dwThreadID = 0; + g_hThreads[i] = CreateThread( + NULL, + 0, + ThreadFn, + &g_Infos[i], + 0, + &dwThreadID ); + } + + + while ( !kbhit() ) + { + } + + g_bShouldExit = true; + + HANDLE hZeroArray[ ARRAYSIZE( g_Infos ) ]; + memset( hZeroArray, 0, sizeof( hZeroArray ) ); + + while ( memcmp( hZeroArray, g_hThreads, sizeof( hZeroArray ) ) != 0 ) + { + Sleep( 10 ); + } + + return 0; +} + diff --git a/utils/vmpi/testapps/socket_stresstest/socket_stresstest.vcproj b/utils/vmpi/testapps/socket_stresstest/socket_stresstest.vcproj new file mode 100644 index 0000000..89b144e --- /dev/null +++ b/utils/vmpi/testapps/socket_stresstest/socket_stresstest.vcproj @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="socket_stresstest" + ProjectGUID="{46DE8944-1A71-4A43-98FE-7A96CB8E5596}" + SccProjectName="" + SccAuxPath="" + SccLocalPath="" + SccProvider=""> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..,..\..\common,..\..\..\public,..\..\..\public\tier1" + PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + PrecompiledHeaderFile=".\Debug/socket_stresstest.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Debug/socket_stresstest.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/socket_stresstest.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Debug/socket_stresstest.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="..,..\..\common,..\..\..\public,..\..\..\public\tier1" + PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + StringPooling="TRUE" + RuntimeLibrary="0" + EnableFunctionLevelLinking="TRUE" + PrecompiledHeaderFile=".\Release/socket_stresstest.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Release/socket_stresstest.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + ProgramDatabaseFile=".\Release/socket_stresstest.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Release/socket_stresstest.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"> + <File + RelativePath="..\iphelpers.cpp"> + </File> + <File + RelativePath="socket_stresstest.cpp"> + </File> + <File + RelativePath="StdAfx.cpp"> + </File> + <File + RelativePath="..\tcpsocket.cpp"> + </File> + <File + RelativePath="..\threadhelpers.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl"> + <File + RelativePath="StdAfx.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"> + </Filter> + <File + RelativePath="ReadMe.txt"> + </File> + <File + RelativePath="..\..\..\lib\public\tier0.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\vstdlib.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/vmpi/testapps/vmpi_launch/StdAfx.cpp b/utils/vmpi/testapps/vmpi_launch/StdAfx.cpp new file mode 100644 index 0000000..7d621ea --- /dev/null +++ b/utils/vmpi/testapps/vmpi_launch/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_launch.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/testapps/vmpi_launch/StdAfx.h b/utils/vmpi/testapps/vmpi_launch/StdAfx.h new file mode 100644 index 0000000..a673b65 --- /dev/null +++ b/utils/vmpi/testapps/vmpi_launch/StdAfx.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__00616BF6_B7E2_4D94_8DC8_3F85BEBD1834__INCLUDED_) +#define AFX_STDAFX_H__00616BF6_B7E2_4D94_8DC8_3F85BEBD1834__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <stdio.h> +#include <windows.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__00616BF6_B7E2_4D94_8DC8_3F85BEBD1834__INCLUDED_) diff --git a/utils/vmpi/testapps/vmpi_launch/vmpi_launch.cpp b/utils/vmpi/testapps/vmpi_launch/vmpi_launch.cpp new file mode 100644 index 0000000..7ecb47b --- /dev/null +++ b/utils/vmpi/testapps/vmpi_launch/vmpi_launch.cpp @@ -0,0 +1,256 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_launch.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "iphelpers.h" +#include "bitbuf.h" +#include "vmpi.h" + +bool g_bBroadcast = false; + +int PrintUsage() +{ + printf( "vmpi_launch -machine <remote machine> -priority <priority> [-mpi_pw <password>] -command \"command line...\"\n" ); + printf( "-command must be the last switch..\n" ); + return 1; +} + + +int GetCurMicrosecondsAndSleep( int sleepLen ) +{ + Sleep( sleepLen ); + + int retVal; + __asm + { + rdtsc + mov retVal, eax + } + return retVal; +} + + +const char* FindArg( int argc, char **argv, const char *pName, const char *pDefault ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pName ) == 0 ) + { + if ( (i+1) < argc ) + return argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + + +int ParseArgs( int argc, char **argv, CIPAddr &remoteIP, int &iPriority, int &iFirstArg ) +{ + if ( FindArg( argc, argv, "-broadcast", "1" ) ) + g_bBroadcast = true; + + if ( g_bBroadcast == false ) + { + const char *pRemoteIPStr = FindArg( argc, argv, "-machine", NULL ); + if ( !pRemoteIPStr || !ConvertStringToIPAddr( pRemoteIPStr, &remoteIP ) ) + { + printf( "%s is not a valid machine name or IP address.\n", pRemoteIPStr ); + return PrintUsage(); + } + } + + iPriority = 0; + const char *pPriorityStr = FindArg( argc, argv, "-priority", NULL ); + if ( pPriorityStr ) + iPriority = atoi( pPriorityStr ); + + if ( iPriority < 0 || iPriority > 1000 ) + { + printf( "%s is not a valid priority.\n", pPriorityStr ); + return PrintUsage(); + } + + const char *pCommand = FindArg( argc, argv, "-command", NULL ); + if ( !pCommand ) + { + return PrintUsage(); + } + for ( iFirstArg=1; iFirstArg < argc; iFirstArg++ ) + { + if ( argv[iFirstArg] == pCommand ) + break; + } + + return 0; +} + + +void SendJobRequest( + ISocket *pSocket, + int argc, + char **argv, + CIPAddr &remoteIP, + int &iPriority, + int &iFirstArg, + int jobID[4] ) +{ + // Build the packet to send out the job. + char packetData[4096]; + bf_write packetBuf; + + // Come up with a unique job ID. + jobID[0] = GetCurMicrosecondsAndSleep( 1 ); + jobID[1] = GetCurMicrosecondsAndSleep( 1 ); + jobID[2] = GetCurMicrosecondsAndSleep( 1 ); + jobID[3] = GetCurMicrosecondsAndSleep( 1 ); + + + // Broadcast out to tell all the machines we want workers. + packetBuf.StartWriting( packetData, sizeof( packetData ) ); + packetBuf.WriteByte( VMPI_PROTOCOL_VERSION ); + + const char *pPassword = FindArg( argc, argv, "-mpi_pw", "" ); + packetBuf.WriteString( pPassword ); + + packetBuf.WriteByte( VMPI_LOOKING_FOR_WORKERS ); + + packetBuf.WriteShort( 0 ); // Tell the port that we're listening on. + // In this case, there is no VMPI master waiting for the app to connect, so + // this parameter doesn't matter. + packetBuf.WriteShort( iPriority ); + + packetBuf.WriteLong( jobID[0] ); + packetBuf.WriteLong( jobID[1] ); + packetBuf.WriteLong( jobID[2] ); + packetBuf.WriteLong( jobID[3] ); + packetBuf.WriteWord( argc-iFirstArg ); // 1 command line argument.. + + // Write the alternate exe name. + for ( int iArg=iFirstArg; iArg < argc; iArg++ ) + packetBuf.WriteString( argv[iArg] ); + + for ( int iBroadcastPort=VMPI_SERVICE_PORT; iBroadcastPort <= VMPI_LAST_SERVICE_PORT; iBroadcastPort++ ) + { + remoteIP.port = iBroadcastPort; + + if ( g_bBroadcast == false ) + pSocket->SendTo( &remoteIP, packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten() ); + else + pSocket->Broadcast( packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten(), iBroadcastPort ); + } + + if ( g_bBroadcast == false ) + printf( "Sent command, waiting for reply...\n" ); + else + printf( "Sent command\n" ); +} + + +bool WaitForJobStart( ISocket *pSocket, const CIPAddr &remoteIP, const int jobID[4] ) +{ + while ( 1 ) + { + CIPAddr senderAddr; + char data[4096]; + int len = -1; + + if ( g_bBroadcast == false ) + pSocket->RecvFrom( data, sizeof( data ), &senderAddr ); + else + pSocket->RecvFrom( data, sizeof( data ), NULL ); + + if ( len == 19 && + memcmp( senderAddr.ip, remoteIP.ip, sizeof( senderAddr.ip ) ) == 0 && + data[1] == VMPI_NOTIFY_START_STATUS && + memcmp( &data[2], jobID, 16 ) == 0 ) + { + if ( data[18] == 0 ) + { + // Wasn't able to run. + printf( "Wasn't able to run on target machine.\n" ); + return false; + } + else + { + // Ok, the process is running now. + printf( "Process running, waiting for completion...\n" ); + return true; + } + } + + Sleep( 100 ); + } +} + + +void WaitForJobEnd( ISocket *pSocket, const CIPAddr &remoteIP, const int jobID[4] ) +{ + while ( 1 ) + { + CIPAddr senderAddr; + char data[4096]; + int len = pSocket->RecvFrom( data, sizeof( data ), &senderAddr ); + if ( len == 18 && + memcmp( senderAddr.ip, remoteIP.ip, sizeof( senderAddr.ip ) ) == 0 && + data[1] == VMPI_NOTIFY_END_STATUS && + memcmp( &data[2], jobID, 16 ) == 0 ) + { + int ret = *((int*)&data[2]); + printf( "Finished [%d].\n", ret ); + break; + } + + Sleep( 100 ); + } +} + + +int main(int argc, char* argv[]) +{ + if ( argc < 4 ) + { + return PrintUsage(); + } + + + // Parse the command line. + CIPAddr remoteIP; + int iFirstArg, iPriority; + int jobID[4]; + + int ret = ParseArgs( argc, argv, remoteIP, iPriority, iFirstArg ); + if ( ret != 0 ) + return ret; + + // Now send the command to the vmpi service on that machine. + ISocket *pSocket = CreateIPSocket(); + if ( !pSocket->BindToAny( 0 ) ) + { + printf( "Error binding a socket.\n" ); + return 1; + } + + SendJobRequest( pSocket, argc, argv, remoteIP, iPriority, iFirstArg, jobID ); + + // Wait for a reply, positive or negative. + if ( g_bBroadcast == false ) + { + if ( !WaitForJobStart( pSocket, remoteIP, jobID ) ) + return 2; + + WaitForJobEnd( pSocket, remoteIP, jobID ); + } + + pSocket->Release(); + return 0; +} + diff --git a/utils/vmpi/testapps/vmpi_launch/vmpi_launch.vpc b/utils/vmpi/testapps/vmpi_launch/vmpi_launch.vpc new file mode 100644 index 0000000..99b642a --- /dev/null +++ b/utils/vmpi/testapps/vmpi_launch/vmpi_launch.vpc @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// VMPI_LAUNCH.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_win32_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,.\,..\.." + } + + $Linker + { + $AdditionalDependencies "ws2_32.lib odbc32.lib odbccp32.lib" + } +} + +$Project "Vmpi_launch" +{ + $Folder "Source Files" + { + $File "..\..\iphelpers.cpp" + $File "StdAfx.cpp" + $File "vmpi_launch.cpp" + } + + $Folder "Header Files" + { + $File "StdAfx.h" + } +} diff --git a/utils/vmpi/testapps/vmpi_ping/StdAfx.cpp b/utils/vmpi/testapps/vmpi_ping/StdAfx.cpp new file mode 100644 index 0000000..94441ca --- /dev/null +++ b/utils/vmpi/testapps/vmpi_ping/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_ping.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/testapps/vmpi_ping/StdAfx.h b/utils/vmpi/testapps/vmpi_ping/StdAfx.h new file mode 100644 index 0000000..1339f3d --- /dev/null +++ b/utils/vmpi/testapps/vmpi_ping/StdAfx.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__04B9E767_FE9B_4F2A_84A3_D6B85737214E__INCLUDED_) +#define AFX_STDAFX_H__04B9E767_FE9B_4F2A_84A3_D6B85737214E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> +#include <stdio.h> + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__04B9E767_FE9B_4F2A_84A3_D6B85737214E__INCLUDED_) diff --git a/utils/vmpi/testapps/vmpi_ping/vmpi_ping.cpp b/utils/vmpi/testapps/vmpi_ping/vmpi_ping.cpp new file mode 100644 index 0000000..17ddd8e --- /dev/null +++ b/utils/vmpi/testapps/vmpi_ping/vmpi_ping.cpp @@ -0,0 +1,196 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_ping.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "iphelpers.h" +#include "vmpi.h" +#include "tier0/platform.h" +#include "bitbuf.h" +#include <conio.h> +#include <stdlib.h> + + +const char* FindArg( int argc, char **argv, const char *pName, const char *pDefault = "" ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pName ) == 0 ) + { + if ( (i+1) < argc ) + return argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + + +int main(int argc, char* argv[]) +{ + CUtlVector<CIPAddr> addrs; + + printf( "\n" ); + printf( "vmpi_ping <option>\n" ); + printf( "option can be:\n" ); + printf( " -stop .. stop any VMPI services\n" ); + printf( " -kill .. kill any processes being run by VMPI\n" ); + printf( " -patch <timeout> .. stops VMPI services for <timeout> seconds\n" ); + printf( " -mpi_pw <password> .. only talk to services with the specified password\n" ); + printf( " -dns .. enable DNS lookups (slows the listing down)\n" ); + printf( " -ShowConsole .. show the console window\n" ); + printf( " -HideConsole .. hide the console window\n" ); + + //Scary to show these to users... + //printf( " -ShowCache .. show the cache directory and its capacity\n" ); + //printf( " -FlushCache .. flush the cache of ALL VMPI services\n" ); + + printf( "\n" ); + + + ISocket *pSocket = CreateIPSocket(); + if ( !pSocket->BindToAny( 0 ) ) + { + printf( "Error binding to a port!\n" ); + return 1; + } + + const char *pPassword = FindArg( argc, argv, "-mpi_pw" ); + + + // Figure out which action they want to take. + int timeout = 0; + char cRequest = VMPI_PING_REQUEST; + if ( FindArg( argc, argv, "-Stop" ) ) + { + cRequest = VMPI_STOP_SERVICE; + } + else if ( FindArg( argc, argv, "-Kill" ) ) + { + cRequest = VMPI_KILL_PROCESS; + } +/* + else if ( FindArg( argc, argv, "-ShowConsole" ) ) + { + cRequest = VMPI_SHOW_CONSOLE_WINDOW; + } + else if ( FindArg( argc, argv, "-HideConsole" ) ) + { + cRequest = VMPI_HIDE_CONSOLE_WINDOW; + } +*/ + else if ( FindArg( argc, argv, "-ShowCache" ) ) + { + cRequest = VMPI_GET_CACHE_INFO; + } + else if ( FindArg( argc, argv, "-FlushCache" ) ) + { + cRequest = VMPI_FLUSH_CACHE; + } + else + { + const char *pTimeout = FindArg( argc, argv, "-patch", "60" ); + if ( pTimeout ) + { + if ( isdigit( pTimeout[0] ) ) + { + cRequest = VMPI_SERVICE_PATCH; + timeout = atoi( pTimeout ); + printf( "Patching with timeout of %d seconds.\n", timeout ); + } + else + { + printf( "-patch requires a timeout parameter.\n" ); + return 1; + } + } + } + + + int nMachines = 0; + printf( "Pinging VMPI Services... press a key to stop.\n\n" ); + while ( !kbhit() ) + { + for ( int i=VMPI_SERVICE_PORT; i <= VMPI_LAST_SERVICE_PORT; i++ ) + { + unsigned char data[256]; + bf_write buf( data, sizeof( data ) ); + buf.WriteByte( VMPI_PROTOCOL_VERSION ); + buf.WriteString( pPassword ); + buf.WriteByte( cRequest ); + + if ( cRequest == VMPI_SERVICE_PATCH ) + buf.WriteLong( timeout ); + + pSocket->Broadcast( data, buf.GetNumBytesWritten(), i ); + } + + while ( 1 ) + { + CIPAddr ipFrom; + char in[256]; + int len = pSocket->RecvFrom( in, sizeof( in ), &ipFrom ); + if ( len == -1 ) + break; + + if ( len >= 2 && + in[0] == VMPI_PROTOCOL_VERSION && + in[1] == VMPI_PING_RESPONSE && + addrs.Find( ipFrom ) == -1 ) + { + char *pStateString = "(unknown)"; + if ( len >= 3 ) + { + if ( in[2] ) + pStateString = "(running)"; + else + pStateString = "(idle) "; + } + + ++nMachines; + char nameStr[256]; + + if ( FindArg( argc, argv, "-dns" ) && ConvertIPAddrToString( &ipFrom, nameStr, sizeof( nameStr ) ) ) + { + printf( "%02d. %s - %s:%d (%d.%d.%d.%d)", + nMachines, pStateString, nameStr, ipFrom.port, + ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3] ); + } + else + { + printf( "%02d. %s - %d.%d.%d.%d:%d", + nMachines, pStateString, ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], ipFrom.port ); + } + + if ( cRequest == VMPI_GET_CACHE_INFO ) + { + // Next var is a 64-bit int with the size of the cache. + char *pCur = &in[3]; + __int64 cacheSize = *((__int64*)pCur); + pCur += sizeof( __int64 ); + + char *pCacheDir = pCur; + + __int64 nMegs = cacheSize / (1024*1024); + printf( "\n\tCache dir: %s, size: %d megs", pCur, nMegs ); + } + + printf( "\n" ); + + addrs.AddToTail( ipFrom ); + } + } + + Sleep( 1000 ); + } + + return 0; +} + diff --git a/utils/vmpi/testapps/vmpi_ping/vmpi_ping.vcproj b/utils/vmpi/testapps/vmpi_ping/vmpi_ping.vcproj new file mode 100644 index 0000000..a2a6dc7 --- /dev/null +++ b/utils/vmpi/testapps/vmpi_ping/vmpi_ping.vcproj @@ -0,0 +1,249 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="vmpi_ping" + ProjectGUID="{D6F83A58-89A2-4F57-9761-32208BD95E83}" + SccProjectName="" + SccAuxPath="" + SccLocalPath="" + SccProvider=""> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\common,..\..\..\common,..\..\..\public,..\..\..\public\tier1,.." + PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + BasicRuntimeChecks="3" + RuntimeLibrary="5" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Debug/vmpi_ping.pch" + AssemblerListingLocation=".\Debug/" + ObjectFile=".\Debug/" + ProgramDataBaseFileName=".\Debug/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + DebugInformationFormat="4" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool" + CommandLine="if exist ..\..\..\..\game\bin\vmpi_ping.exe attrib -r ..\..\..\..\game\bin\vmpi_ping.exe +copy "$(TargetPath)" ..\..\..\..\game\bin +" + Outputs="..\..\..\..\game\bin\vmpi_ping.exe"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Debug/vmpi_ping.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile=".\Debug/vmpi_ping.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Debug/vmpi_ping.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="1" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="FALSE" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="..\..\common,..\..\..\common,..\..\..\public,..\..\..\public\tier1,.." + PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;PROTECTED_THINGS_DISABLE" + StringPooling="TRUE" + RuntimeLibrary="4" + EnableFunctionLevelLinking="TRUE" + UsePrecompiledHeader="3" + PrecompiledHeaderThrough="stdafx.h" + PrecompiledHeaderFile=".\Release/vmpi_ping.pch" + AssemblerListingLocation=".\Release/" + ObjectFile=".\Release/" + ProgramDataBaseFileName=".\Release/" + WarningLevel="3" + SuppressStartupBanner="TRUE" + CompileAs="0"/> + <Tool + Name="VCCustomBuildTool" + CommandLine="if exist ..\..\..\..\game\bin\vmpi_ping.exe attrib -r ..\..\..\..\game\bin\vmpi_ping.exe +copy "$(TargetPath)" ..\..\..\..\game\bin +" + Outputs="..\..\..\..\game\bin\vmpi_ping.exe"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib odbc32.lib odbccp32.lib" + OutputFile=".\Release/vmpi_ping.exe" + LinkIncremental="1" + SuppressStartupBanner="TRUE" + ProgramDatabaseFile=".\Release/vmpi_ping.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool" + TypeLibraryName=".\Release/vmpi_ping.tlb" + HeaderFileName=""/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"> + <File + RelativePath="..\iphelpers.cpp"> + </File> + <File + RelativePath="..\..\..\public\tier0\memoverride.cpp"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0"/> + </FileConfiguration> + </File> + <File + RelativePath="StdAfx.cpp"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1"/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1"/> + </FileConfiguration> + </File> + <File + RelativePath="vmpi_ping.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl"> + <File + RelativePath="..\..\common\iphelpers.h"> + </File> + <File + RelativePath="StdAfx.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"> + </Filter> + <File + RelativePath="ReadMe.txt"> + </File> + <File + RelativePath="..\..\..\lib\public\tier0.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + <File + RelativePath="..\..\..\lib\public\tier1.lib"> + </File> + <File + RelativePath="..\..\..\lib\public\vstdlib.lib"> + <FileConfiguration + Name="Debug|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32"> + <Tool + Name="VCCustomBuildTool" + Description="" + CommandLine=""/> + </FileConfiguration> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/utils/vmpi/threadhelpers.cpp b/utils/vmpi/threadhelpers.cpp new file mode 100644 index 0000000..1205eb8 --- /dev/null +++ b/utils/vmpi/threadhelpers.cpp @@ -0,0 +1,155 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include <windows.h> +#include "threadhelpers.h" +#include "tier0/dbg.h" + + +// -------------------------------------------------------------------------------- // +// CCriticalSection implementation. +// -------------------------------------------------------------------------------- // + +CCriticalSection::CCriticalSection() +{ + Assert( sizeof( CRITICAL_SECTION ) == SIZEOF_CS ); + +#if defined( _DEBUG ) + InitializeCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); +#endif + + InitializeCriticalSection( (CRITICAL_SECTION*)&m_CS ); +} + + +CCriticalSection::~CCriticalSection() +{ + DeleteCriticalSection( (CRITICAL_SECTION*)&m_CS ); + +#if defined( _DEBUG ) + DeleteCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); +#endif +} + + + +void CCriticalSection::Lock() +{ +#if defined( _DEBUG ) + // Check if this one is already locked. + DWORD id = GetCurrentThreadId(); + EnterCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); + Assert( m_Locks.Find( id ) == m_Locks.InvalidIndex() ); + m_Locks.AddToTail( id ); + LeaveCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); +#endif + + EnterCriticalSection( (CRITICAL_SECTION*)&m_CS ); +} + + +void CCriticalSection::Unlock() +{ +#if defined( _DEBUG ) + // Check if this one is already locked. + DWORD id = GetCurrentThreadId(); + EnterCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); + int index = m_Locks.Find( id ); + Assert( index != m_Locks.InvalidIndex() ); + m_Locks.Remove( index ); + LeaveCriticalSection( (CRITICAL_SECTION*)&m_DeadlockProtect ); +#endif + + LeaveCriticalSection( (CRITICAL_SECTION*)&m_CS ); +} + + + +// -------------------------------------------------------------------------------- // +// CCriticalSectionLock implementation. +// -------------------------------------------------------------------------------- // + +CCriticalSectionLock::CCriticalSectionLock( CCriticalSection *pCS ) +{ + m_pCS = pCS; + m_bLocked = false; +} + + +CCriticalSectionLock::~CCriticalSectionLock() +{ + if ( m_bLocked ) + m_pCS->Unlock(); +} + + +void CCriticalSectionLock::Lock() +{ + Assert( !m_bLocked ); + m_bLocked = true; + m_pCS->Lock(); +} + + +void CCriticalSectionLock::Unlock() +{ + Assert( m_bLocked ); + m_bLocked = false; + m_pCS->Unlock(); +} + + +// -------------------------------------------------------------------------------- // +// CEvent implementation. +// -------------------------------------------------------------------------------- // + +CEvent::CEvent() +{ + m_hEvent = NULL; +} + +CEvent::~CEvent() +{ + Term(); +} + +bool CEvent::Init( bool bManualReset, bool bInitialState ) +{ + Term(); + + m_hEvent = (void*)CreateEvent( NULL, bManualReset, bInitialState, NULL ); + return (m_hEvent != NULL); +} + +void CEvent::Term() +{ + if ( m_hEvent ) + { + CloseHandle( (HANDLE)m_hEvent ); + m_hEvent = NULL; + } +} + +void* CEvent::GetEventHandle() const +{ + Assert( m_hEvent ); + return m_hEvent; +} + +bool CEvent::SetEvent() +{ + Assert( m_hEvent ); + return ::SetEvent( (HANDLE)m_hEvent ) != 0; +} + +bool CEvent::ResetEvent() +{ + Assert( m_hEvent ); + return ::ResetEvent( (HANDLE)m_hEvent ) != 0; +} + + diff --git a/utils/vmpi/threadhelpers.h b/utils/vmpi/threadhelpers.h new file mode 100644 index 0000000..bfba74f --- /dev/null +++ b/utils/vmpi/threadhelpers.h @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADHELPERS_H +#define THREADHELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utllinkedlist.h" + + +#define SIZEOF_CS 24 // sizeof( CRITICAL_SECTION ) + + +class CCriticalSection +{ +public: + CCriticalSection(); + ~CCriticalSection(); + + +protected: + + friend class CCriticalSectionLock; + + void Lock(); + void Unlock(); + + +public: + char m_CS[SIZEOF_CS]; + + // Used to protect against deadlock in debug mode. +//#if defined( _DEBUG ) + CUtlLinkedList<unsigned long,int> m_Locks; + char m_DeadlockProtect[SIZEOF_CS]; +//#endif +}; + + +// Use this to lock a critical section. +class CCriticalSectionLock +{ +public: + CCriticalSectionLock( CCriticalSection *pCS ); + ~CCriticalSectionLock(); + void Lock(); + void Unlock(); + +private: + CCriticalSection *m_pCS; + bool m_bLocked; +}; + + +template< class T > +class CCriticalSectionData : private CCriticalSection +{ +public: + // You only have access to the data between Lock() and Unlock(). + T* Lock() + { + CCriticalSection::Lock(); + return &m_Data; + } + + void Unlock() + { + CCriticalSection::Unlock(); + } + +private: + T m_Data; +}; + + + +// ------------------------------------------------------------------------------------------------ // +// CEvent. +// ------------------------------------------------------------------------------------------------ // +class CEvent +{ +public: + CEvent(); + ~CEvent(); + + bool Init( bool bManualReset, bool bInitialState ); + void Term(); + + void* GetEventHandle() const; + + // Signal the event. + bool SetEvent(); + + // Unset the event's signalled status. + bool ResetEvent(); + + +private: + void *m_hEvent; +}; + + +#endif // THREADHELPERS_H diff --git a/utils/vmpi/vmpi.cpp b/utils/vmpi/vmpi.cpp new file mode 100644 index 0000000..ceed5fb --- /dev/null +++ b/utils/vmpi/vmpi.cpp @@ -0,0 +1,2478 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This module implements the subset of MPI that VRAD and VVIS use. +// +// $NoKeywords: $ +//=============================================================================// + +#include <windows.h> +#include <io.h> +#include <conio.h> +#include <sys/stat.h> +#include <stdio.h> +#include <direct.h> +#include "iphelpers.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "vmpi.h" +#include "bitbuf.h" +#include "tier1/strtools.h" +#include "threadhelpers.h" +#include "IThreadedTCPSocket.h" +#include "vstdlib/random.h" +#include "vmpi_distribute_work.h" +#include "filesystem.h" +#include "checksum_md5.h" +#include "tslist.h" +#include "tier0/icommandline.h" + + +#define DEFAULT_MAX_WORKERS 32 // Unless they specify -mpi_MaxWorkers, it will stop accepting workers after it gets this many. +int g_nMaxWorkerCount = DEFAULT_MAX_WORKERS; + +#define VMPI_INTERNAL_PACKET_ID 27 + #define VMPI_INTERNAL_SUBPACKET_MACHINE_NAME 1 + #define VMPI_INTERNAL_SUBPACKET_COMMAND_LINE 2 + #define VMPI_INTERNAL_SUBPACKET_WAITING_FOR_COMMAND_LINE 3 + #define VMPI_INTERNAL_SUBPACKET_GROUPED_PACKET 4 + #define VMPI_INTERNAL_SUBPACKET_TIMING_WAIT_DONE 5 + #define VMPI_INTERNAL_SUBPACKET_VERIFY_EXE_NAME 6 + + +typedef CUtlVector<char> PersistentPacket; + +CCriticalSection g_PersistentPacketsCS; +CUtlLinkedList<PersistentPacket*> g_PersistentPackets; + + +// Command-line parameters list. +#define VMPI_PARAM( paramName, paramFlags, helpText ) {paramName, paramFlags, "-"#paramName, helpText}, +class CVMPIParam +{ +public: + EVMPICmdLineParam m_eParam; + int m_ParamFlags; + const char *m_pName; + const char *m_pHelpText; +}; +static CVMPIParam g_VMPIParams[] = +{ + {k_eVMPICmdLineParam_FirstParam, 0, "k_eVMPICmdLineParam_FirstParam", "unused"}, + {k_eVMPICmdLineParam_VMPIParam, 0, "mpi", "Enable VMPI."}, +#include "vmpi_parameters.h" +}; +#undef VMPI_PARAM + + +// ---------------------------------------------------------------------------------------- // +// Globals. +// ---------------------------------------------------------------------------------------- // + +class CVMPIConnection; + +// Used by -mpi_AutoRestart. +CUtlVector<char*> g_OriginalCommandLineParameters; + +// This queues up all the incoming VMPI messages. +CCriticalSection g_VMPIMessagesCS; +CUtlLinkedList< CTCPPacket*, int > g_VMPIMessages; +CEvent g_VMPIMessagesEvent; // This is set when there are messages in the queue. + +// These are used to notify the main thread when some socket had OnError() called on it. +CUtlLinkedList<CVMPIConnection*,int> g_ErrorSockets; +CEvent g_ErrorSocketsEvent; +CCriticalSection g_ErrorSocketsCS; +bool g_bTimingWaitDone = false; +bool g_bGroupPackets = false; + +#define MAX_VMPI_CONNECTIONS 512 +CVMPIConnection *g_Connections[MAX_VMPI_CONNECTIONS]; +int g_nConnections = 0; +CCriticalSection g_ConnectionsCS; + +// If true, then it will set certain thread priorities low. +bool g_bSetThreadPriorities = true; + +VMPIDispatchFn g_VMPIDispatch[MAX_VMPI_PACKET_IDS]; +CTSList<MessageBuffer *> g_DispatchBuffers; + +VMPIRunMode g_VMPIRunMode = VMPI_RUN_NETWORKED; +VMPIFileSystemMode g_VMPIFileSystemMode = VMPI_FILESYSTEM_TCP; + +static char g_GroupedPacketHeader[] = { VMPI_INTERNAL_PACKET_ID, VMPI_INTERNAL_SUBPACKET_GROUPED_PACKET }; +static unsigned long g_LastFlushGroupedPacketsTime = 0; + +// Set to true if we're running under the SDK (i.e. vmpi_transfer.exe is not found). +bool g_bVMPISDKMode = false; +bool g_bVMPISDKModeSet = false; // If g_bVMPISDKMode has not been set, then VMPI_IsSDKMode just looks for VMPI_Transfer (and doesn't check the command line). + +int g_nBytesSent = 0; +int g_nMessagesSent = 0; +int g_nBytesReceived = 0; +int g_nMessagesReceived = 0; + +int g_nMulticastBytesSent = 0; +int g_nMulticastBytesReceived = 0; + + +CUtlLinkedList<VMPI_Disconnect_Handler,int> g_DisconnectHandlers; + +bool g_bUseMPI = false; +int g_iVMPIVerboseLevel = 0; +bool g_bMPIMaster = false; + +bool g_bMPI_Stats = false; +bool g_bMPI_StatsTextOutput = false; + +char g_CurrentStageString[128] = ""; +CCriticalSection g_CurrentStageCS; + +char g_MasterExeName[MAX_PATH]; +bool g_bReceivedMasterExeName = false; + + +// Change our window text. +HINSTANCE g_hKernel32DLL = NULL; +typedef HWND (*GetConsoleWndFn)(); +GetConsoleWndFn g_pConsoleWndFn = NULL; + + +// ---------------------------------------------------------------------------------------- // +// Classes. +// ---------------------------------------------------------------------------------------- // + +// This class is used while discovering what files the workers need. +class CDependencyInfo +{ +public: + class CDependencyFile + { + public: + char m_Name[MAX_PATH]; + }; + + + // This is the directory where the dependency files live (i.e. all the binaries that the workers need to run the job). + char m_DependencyFilesDir[MAX_PATH]; + + // "vrad.exe", "vvis.exe", etc. + char m_OriginalExeFilename[MAX_PATH]; + + CUtlVector<CDependencyFile*> m_Files; + + +public: + + CDependencyFile* FindFile( const char *pFilename ) + { + for ( int i=0; i < m_Files.Count(); i++ ) + { + if ( stricmp( pFilename, m_Files[i]->m_Name ) == 0 ) + return m_Files[i]; + } + return NULL; + } +}; + + +class CVMPIConnection : public ITCPSocketHandler +{ +public: + CVMPIConnection( int iConnection ) + { + m_iConnection = iConnection; + m_pSocket = NULL; + m_bIsAService = false; + + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", iConnection ); + SetMachineName( str ); + m_JobWorkerID = 0xFFFFFFFF; + + m_bNameSet = false; + } + + ~CVMPIConnection() + { + if ( m_pSocket ) + m_pSocket->Release(); + } + + +public: + + void HandleDisconnect() + { + if ( m_pSocket ) + { + // Copy out the error string. + CCriticalSectionLock csLock( &g_ErrorSocketsCS ); + csLock.Lock(); + char str[512]; + Q_strncpy( str, m_ErrorString.Base(), sizeof( str ) ); + csLock.Unlock(); + + // Tell the app. + FOR_EACH_LL( g_DisconnectHandlers, i ) + g_DisconnectHandlers[i]( m_iConnection, str ); + + // Free our socket. + m_pSocket->Release(); + m_pSocket = NULL; + } + } + + + IThreadedTCPSocket* GetSocket() + { + return m_pSocket; + } + + + void SetMachineName( const char *pName ) + { + m_MachineName.CopyArray( pName, strlen( pName ) + 1 ); + m_bNameSet = true; + } + + const char* GetMachineName() + { + return m_MachineName.Base(); + } + + bool HasMachineNameBeenSet() + { + return m_bNameSet; + } + + +// ITCPSocketHandler implementation (thread-safe stuff). +public: + + virtual void Init( IThreadedTCPSocket *pSocket ) + { + m_pSocket = pSocket; + } + + virtual void OnPacketReceived( CTCPPacket *pPacket ) + { + // Set who this message came from. + pPacket->SetUserData( m_iConnection ); + Assert( m_iConnection >= 0 && m_iConnection < 2048 ); + + // Store it in the global list. + CCriticalSectionLock csLock( &g_VMPIMessagesCS ); + csLock.Lock(); + + g_VMPIMessages.AddToTail( pPacket ); + + if ( g_VMPIMessages.Count() == 1 ) + g_VMPIMessagesEvent.SetEvent(); + } + + virtual void OnError( int errorCode, const char *pErrorString ) + { + if ( !g_bMPIMaster ) + { + Msg( "%s - CVMPIConnection::OnError( %s )\n", GetMachineName(), pErrorString ); + } + + CCriticalSectionLock csLock( &g_ErrorSocketsCS ); + csLock.Lock(); + + m_ErrorString.CopyArray( pErrorString, strlen( pErrorString ) + 1 ); + + g_ErrorSockets.AddToTail( this ); + + // Notify the main thread that a socket is in trouble! + g_ErrorSocketsEvent.SetEvent(); + + // Make sure the main thread picks up this error soon. + InterlockedIncrement( &m_ErrorSignal ); + } + + +public: + + unsigned long m_JobWorkerID; + bool m_bIsAService; // If true, then this is just a service getting the files. Don't count it as an active worker. + + CUtlVector<int> m_GroupedChunkLengths; + CUtlVector<void*> m_GroupedChunks; + + +private: + + CUtlVector<char> m_MachineName; + CUtlVector<char> m_ErrorString; + long m_ErrorSignal; + int m_iConnection; + IThreadedTCPSocket *m_pSocket; + bool m_bNameSet; +}; + + +class CVMPIConnectionCreator : public IHandlerCreator +{ +public: + virtual ITCPSocketHandler* CreateNewHandler() + { + Assert( g_nConnections < MAX_VMPI_CONNECTIONS ); + CVMPIConnection *pRet = new CVMPIConnection( g_nConnections ); + g_Connections[g_nConnections++] = pRet; + return pRet; + } +}; + + + +// ---------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------- // + +const char* VMPI_FindArg( int argc, char **argv, const char *pName, const char *pDefault ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pName ) == 0 ) + { + if ( (i+1) < argc ) + return argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + + +void ParseOptions( int argc, char **argv ) +{ + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_NoTimeout ) ) ) + ThreadedTCP_EnableTimeouts( false ); + + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_DontSetThreadPriorities ) ) ) + { + Msg( "%s found.\n", VMPI_GetParamString( mpi_DontSetThreadPriorities ) ); + g_bSetThreadPriorities = false; + ThreadedTCP_SetTCPSocketThreadPriorities( false ); + } + + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_GroupPackets ) ) ) + { + Msg( "%s found.\n", VMPI_GetParamString( mpi_GroupPackets ) ); + g_bGroupPackets = true; + } + + const char *pTransmitRate = VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_FileTransmitRate ), "1" ); + if ( pTransmitRate ) + { + extern int MULTICAST_TRANSMIT_RATE; + MULTICAST_TRANSMIT_RATE = atoi( pTransmitRate ) * 1024; + } + + const char *pVerbose = VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Verbose ), "1" ); + if ( pVerbose ) + { + if ( pVerbose[0] == '1' ) + g_iVMPIVerboseLevel = 1; + else if ( pVerbose[0] == '2' ) + g_iVMPIVerboseLevel = 2; + } + + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Stats ) ) ) + g_bMPI_Stats = true; + + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Stats_TextOutput ) ) ) + g_bMPI_StatsTextOutput = true; +} + + +void SetupDependencyFilename( CDependencyInfo *pInfo, const char *pPatchDirectory ) +{ + char baseExeFilename[512]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "GetModuleFileName failed." ); + + // If they're in patch mode, then the dependency files come out of a directory they've passed in. + // Otherwise, the files come from the same exe dir we're in (like c:\valve\game\bin). + if ( pPatchDirectory ) + { + V_strncpy( pInfo->m_DependencyFilesDir, pPatchDirectory, sizeof( pInfo->m_DependencyFilesDir ) ); + } + else + { + V_strncpy( pInfo->m_DependencyFilesDir, baseExeFilename, sizeof( pInfo->m_DependencyFilesDir ) ); + V_StripLastDir( pInfo->m_DependencyFilesDir, sizeof( pInfo->m_DependencyFilesDir ) ); + } + + // Get the exe filename. + V_strncpy( pInfo->m_OriginalExeFilename, V_UnqualifiedFileName( baseExeFilename ), sizeof( pInfo->m_OriginalExeFilename ) ); +} + + +bool ReadString( char *pOut, int maxLen, FILE *fp ) +{ + if ( !fgets( pOut, maxLen, fp ) || pOut[0] == 0 ) + return false; + + int len = strlen( pOut ); + if ( pOut[len - 1] == '\n' ) + pOut[len - 1] = 0; + + return true; +} + + +void ParseDependencyFile( CDependencyInfo *pInfo, const char *pDepFilename ) +{ + FILE *fp = fopen( pDepFilename, "rt" ); + if ( !fp ) + Error( "Can't find %s.", pDepFilename ); + + const char *pOptionalPrefix = "optional "; + + char tempStr[MAX_PATH]; + while ( ReadString( tempStr, sizeof( tempStr ), fp ) ) + { + CDependencyInfo::CDependencyFile *pFile = new CDependencyInfo::CDependencyFile; + bool bOptional = false; + if ( strstr( tempStr, "optional " ) == tempStr ) + { + bOptional = true; + Q_strncpy( pFile->m_Name, tempStr + strlen( pOptionalPrefix ), sizeof( pFile->m_Name ) ); + } + else + { + Q_strncpy( pFile->m_Name, tempStr, sizeof( pFile->m_Name ) ); + } + + // Now get the file info. + char fullFilename[MAX_PATH]; + V_ComposeFileName( pInfo->m_DependencyFilesDir, pFile->m_Name, fullFilename, sizeof( fullFilename ) ); + + if ( _access( fullFilename, 0 ) == 0 ) + { + pInfo->m_Files.AddToTail( pFile ); + } + else + { + delete pFile; + + if ( !bOptional ) + Error( "Can't find %s (listed in %s).", fullFilename, pDepFilename ); + } + } + + fclose( fp ); +} + + +void SetupDependenciesForPatch( CDependencyInfo *pInfo, const char *pPatchDirectory ) +{ + char searchStr[MAX_PATH]; + V_ComposeFileName( pPatchDirectory, "*.*", searchStr, sizeof( searchStr ) ); + + _finddata_t data; + long handle = _findfirst( searchStr, &data ); + if ( handle != -1 ) + { + do + { + if ( data.name[0] == '.' || (data.attrib & _A_SUBDIR) != 0 ) + continue; + + CDependencyInfo::CDependencyFile *pFile = new CDependencyInfo::CDependencyFile; + V_strncpy( pFile->m_Name, data.name, sizeof( pFile->m_Name ) ); + pInfo->m_Files.AddToTail( pFile ); + } while( _findnext( handle, &data ) == 0 ); + + _findclose( handle ); + } +} + + +void SetupDependencyInfo( CDependencyInfo *pInfo, const char *pDependencyFilename, bool bPatchMode ) +{ + if ( bPatchMode ) + { + const char *pPatchDirectory = pDependencyFilename; + + SetupDependencyFilename( pInfo, pPatchDirectory ); + SetupDependenciesForPatch( pInfo, pPatchDirectory ); + } + else + { + SetupDependencyFilename( pInfo, NULL ); + + // Parse the dependency file. + char depFilename[MAX_PATH]; + V_ComposeFileName( pInfo->m_DependencyFilesDir, pDependencyFilename, depFilename, sizeof( depFilename ) ); + ParseDependencyFile( pInfo, depFilename ); + } +} + + +int GetCurMicrosecondsAndSleep( int sleepLen ) +{ + Sleep( sleepLen ); + + CCycleCount cnt; + cnt.Sample(); + return cnt.GetMicroseconds(); +} + + +void CountActiveConnections( int *nRegularWorkers, int *nServiceDownloaders ) +{ + *nRegularWorkers = *nServiceDownloaders = 0; + int nTotalConnections = g_nConnections; + for ( int i=0; i < nTotalConnections; i++ ) + { + if ( VMPI_IsProcConnected( i ) ) + { + if ( VMPI_IsProcAService( i ) ) + (*nServiceDownloaders)++; + else + (*nRegularWorkers)++; + } + } +} + +// In this function, we update the window text to tell how many active workers there are. +void UpdateActiveConnectionsText() +{ + if ( !g_bMPIMaster || !g_pConsoleWndFn ) + return; + + HWND hWnd = g_pConsoleWndFn(); + if ( !hWnd ) + return; + + int nRegularWorkers, nDownloaders; + CountActiveConnections( &nRegularWorkers, &nDownloaders ); + + char str[512]; + if ( g_bVMPISDKMode ) + { + V_snprintf( str, sizeof( str ), "VMPI (SDK) - Workers: %d", nRegularWorkers ); + } + else + { + V_snprintf( str, sizeof( str ), "VMPI - Workers: %d, Downloaders: %d", nRegularWorkers, nDownloaders ); + } + SetWindowText( hWnd, str ); +} + + +void VMPI_SendMachineNameTo( int iProc ) +{ + const char *pMyName = VMPI_GetLocalMachineName(); + + unsigned char packetData[512]; + packetData[0] = VMPI_INTERNAL_PACKET_ID; + packetData[1] = VMPI_INTERNAL_SUBPACKET_MACHINE_NAME; + Q_strncpy( (char*)&packetData[2], pMyName, sizeof( packetData ) - 2 ); + VMPI_SendData( packetData, 2 + strlen( pMyName ) + 1, iProc ); +} + +static CVMPIConnection* FindConnectionBySocket( IThreadedTCPSocket *pSocket, bool bLockConnections ) +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + if ( bLockConnections ) + connectionsLock.Lock(); + + for ( int i=0; i < g_nConnections; i++ ) + if ( g_Connections[i]->GetSocket() == pSocket ) + return g_Connections[i]; + + return NULL; +} + +static char* CopyString( const char *pStr ) +{ + int len = V_strlen( pStr ) + 1; + char *pArg = new char[len]; + Q_strncpy( pArg, pStr, len ); + return pArg; +} + +// ---------------------------------------------------------------------------------------- // +// Internal VMPI dispatch.. +// ---------------------------------------------------------------------------------------- // + +void VMPI_SetMachineName( int iProc, const char *pName ); + +CUtlVector<char*> g_WorkerCommandLine; +bool g_bReceivedWorkerCommandLine = false; + + +bool VMPI_InternalDispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + if ( pBuf->getLen() >= 2 ) + { + if ( pBuf->data[1] == VMPI_INTERNAL_SUBPACKET_MACHINE_NAME ) + { + if ( pBuf->getLen() >= 3 ) + { + VMPI_SetMachineName( iSource, &pBuf->data[2] ); + return true; + } + } + else if ( pBuf->data[1] == VMPI_INTERNAL_SUBPACKET_WAITING_FOR_COMMAND_LINE ) + { + if ( !VMPI_IsSDKMode() ) + { + Warning( "Worker %d is running in SDK mode (and the master is not)!\n", iSource ); + } + return true; + } + else if ( pBuf->data[1] == VMPI_INTERNAL_SUBPACKET_COMMAND_LINE ) + { + pBuf->setOffset( 2 ); + + int nArgs; + pBuf->read( &nArgs, sizeof( nArgs ) ); + for ( int i=0; i < nArgs; i++ ) + { + char str[4096]; + if ( pBuf->ReadString( str, sizeof( str ) ) == -1 ) + Error( "Error in ReadString() while reading command line." ); + + g_WorkerCommandLine.AddToTail( CopyString( str ) ); + } + + g_bReceivedWorkerCommandLine = true; + return true; + } + else if ( pBuf->data[1] == VMPI_INTERNAL_SUBPACKET_VERIFY_EXE_NAME ) + { + pBuf->setOffset( 2 ); + + if ( pBuf->ReadString( g_MasterExeName, sizeof( g_MasterExeName ) ) == -1 ) + Error( "Error in ReadString() while reading VMPI_INTERNAL_SUBPACKET_VERIFY_EXE_NAME." ); + + g_bReceivedMasterExeName = true; + return true; + } + else if ( pBuf->data[1] == VMPI_INTERNAL_SUBPACKET_TIMING_WAIT_DONE ) + { + g_bTimingWaitDone = true; + return true; + } + } + + return false; +} +CDispatchReg g_VMPIInternalDispatchReg( VMPI_INTERNAL_PACKET_ID, VMPI_InternalDispatchFn ); // register to handle the messages we want + + +void VMPI_SendCommandLine( int argc, char **argv ) +{ + MessageBuffer mb; + + char cPacketHeader[2] = {VMPI_INTERNAL_PACKET_ID, VMPI_INTERNAL_SUBPACKET_COMMAND_LINE}; + mb.write( cPacketHeader, sizeof( cPacketHeader ) ); + mb.write( &argc, sizeof( argc ) ); + for ( int i=0; i < argc; i++ ) + mb.WriteString( argv[i] ); + + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); +} + +void VMPI_ReceiveCommandLine() +{ + // For verification purposes, tell the master we're trying to get the command line. + unsigned char chData[2] = {VMPI_INTERNAL_PACKET_ID, VMPI_INTERNAL_SUBPACKET_WAITING_FOR_COMMAND_LINE}; + VMPI_SendData( chData, sizeof( chData ), VMPI_MASTER_ID ); + + double startTime = Plat_FloatTime(); + while ( !g_bReceivedWorkerCommandLine ) + { + if ( Plat_FloatTime() - startTime > 30 ) + Error( "VMPI_ReceiveCommandLine: timeout. Is the master running in SDK mode?" ); + + VMPI_DispatchNextMessage( 10 * 1000 ); + } +} + + +void VMPI_SendExeName() +{ + MessageBuffer mb; + + char cPacketHeader[2] = {VMPI_INTERNAL_PACKET_ID, VMPI_INTERNAL_SUBPACKET_VERIFY_EXE_NAME}; + mb.write( cPacketHeader, sizeof( cPacketHeader ) ); + + char baseExeFilename[MAX_PATH], fileBase[MAX_PATH]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "VMPI_CheckSDKMode -> GetModuleFileName failed." ); + + V_FileBase( baseExeFilename, fileBase, sizeof( fileBase ) ); + mb.WriteString( fileBase ); + + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); +} + +void VMPI_ReceiveExeName() +{ + double startTime = Plat_FloatTime(); + while ( !g_bReceivedMasterExeName ) + { + if ( Plat_FloatTime() - startTime > 30 ) + Error( "VMPI_ReceiveExeName: timeout." ); + + VMPI_DispatchNextMessage( 10 * 1000 ); + } + + // Now compare the exe name we got with our own. + char baseExeFilename[MAX_PATH], fileBase[MAX_PATH]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "VMPI_CheckSDKMode -> GetModuleFileName failed." ); + + // Unless we're a vmpi_transfer.. vmpi_transfer can always connect. + V_FileBase( baseExeFilename, fileBase, sizeof( fileBase ) ); + if ( V_stricmp( fileBase, "vmpi_transfer" ) != 0 ) + { + if ( V_stricmp( fileBase, g_MasterExeName ) != 0 ) + { + Error( "VMPI_ReceiveExeName: mismatched exe names (master: %s, me: %s).\nThis usually just means the master finished" + " a job like vvis really fast and started a vrad immediately, and an old vvis worker connected to the new vrad job.", + g_MasterExeName, fileBase ); + } + } +} + + +// ---------------------------------------------------------------------------------------- // +// CMasterBroadcaster +// This class broadcasts messages looking for workers. The app updates it as often as possible +// and it'll add workers as necessary. +// ---------------------------------------------------------------------------------------- // + +#define MASTER_BROADCAST_INTERVAL 600 // Send every N milliseconds. + +class CMasterBroadcaster +{ +public: + + CMasterBroadcaster(); + ~CMasterBroadcaster(); + + bool Init( int argc, char **argv, const char *pDependencyFilename, int nMaxWorkers, VMPIRunMode runMode, bool bPatchMode ); + void Term(); + + // What port is it listening on? + int GetListenPort() const; + + // These can be used to allow more workers on or filter who's able to connect + int GetMaxWorkers() const; + void IncreaseMaxWorkers( int count ); + void SetPassword( const char *pPassword ); + void SetNoTimeoutOption(); + + +private: + + void GetPatchWorkerList( int argc, char **argv ); + + +private: + + class CMasterBroadcastInfo + { + public: + int m_JobID[4]; + char m_Password[256]; + char m_WorkerExeFilename[MAX_PATH]; + CUtlVector<char*> m_Args; + char m_PatchVersion[32]; // 0 if not patching. + bool m_bForcePatch; + }; + + void ThreadFn(); + static DWORD WINAPI StaticThreadFn( LPVOID lpParameter ); + + bool Update(); + void BuildBroadcastPacket( bf_write &buf ); + + +private: + + ITCPConnectSocket *m_pListenSocket; + ITCPConnectSocket *m_pDownloaderListenSocket; + ISocket *m_pSocket; + + DWORD m_LastSendTime; + CMasterBroadcastInfo m_BroadcastInfo; + CUtlVector<CIPAddr> m_PatchWorkerIPs; // If in patch mode, these are the IPs we send the job request to (instead of broadcasting). + bool m_bPatching; + + CVMPIConnectionCreator m_ConnectionCreator; + int m_nMaxWorkers; + + HANDLE m_hThread; + CEvent m_hShutdownEvent; + CEvent m_hShutdownReply; + + VMPIRunMode m_RunMode; + int m_iListenPort; + int m_iDownloaderListenPort; +}; + + +CMasterBroadcaster::CMasterBroadcaster() +{ + m_pListenSocket = NULL; + m_pDownloaderListenSocket = NULL; + m_pSocket = NULL; + m_iListenPort = -1; + m_iDownloaderListenPort = -1; +} + +CMasterBroadcaster::~CMasterBroadcaster() +{ + Term(); +} + + +void CMasterBroadcaster::GetPatchWorkerList( int argc, char **argv ) +{ + m_PatchWorkerIPs.Purge(); + for ( int i=0; i < argc-1; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_PatchWorkers" ) == 0 ) + { + int workerCount = atoi( argv[i+1] ); + for ( int iWorker=0; iWorker < workerCount; iWorker++ ) + { + int iArg = i+2 + iWorker; + if ( iArg >= argc ) + Error( "-mpi_PatchWorkers: %d specified for count, but not enough IPs following.\n", workerCount ); + + int a, b, c, d; + const char *pArg = argv[iArg]; + sscanf( pArg, "%d.%d.%d.%d", &a, &b, &c, &d ); + + CIPAddr addr; + addr.Init( a, b, c, d, 0 ); + m_PatchWorkerIPs.AddToTail( addr ); + } + return; + } + } +} + +bool CMasterBroadcaster::Init( + int argc, + char **argv, + const char *pDependencyFilename, + int nMaxWorkers, + VMPIRunMode runMode, + bool bPatchMode ) +{ + m_RunMode = runMode; + m_nMaxWorkers = nMaxWorkers; + + // Open the file that tells us which binaries we depend on. + CDependencyInfo dependencyInfo; + if ( m_RunMode == VMPI_RUN_NETWORKED && !g_bVMPISDKMode ) + { + SetupDependencyInfo( &dependencyInfo, pDependencyFilename, bPatchMode ); + } + + m_pListenSocket = NULL; + m_pDownloaderListenSocket = NULL; + + const char *pPortStr = VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Port ) ); + if ( pPortStr ) + { + m_iListenPort = atoi( pPortStr ); + m_iDownloaderListenPort = m_iListenPort + 1; + m_pListenSocket = ThreadedTCP_CreateListener( &m_ConnectionCreator, m_iListenPort ); + if ( !g_bVMPISDKMode ) + { + m_pDownloaderListenSocket = ThreadedTCP_CreateListener( &m_ConnectionCreator, m_iDownloaderListenPort ); + } + } + else + { + // Create a socket to listen on. + CCycleCount cnt; + cnt.Sample(); + int iTime = (int)cnt.GetMicroseconds(); + srand( (unsigned)iTime ); + + for ( int iTest=VMPI_MASTER_FIRST_PORT; iTest <= VMPI_MASTER_LAST_PORT; iTest++ ) + { + m_iListenPort = iTest; + m_pListenSocket = ThreadedTCP_CreateListener( &m_ConnectionCreator, m_iListenPort ); + if ( m_pListenSocket ) + break; + } + // No need to create the downloader in SDK mode. + if ( m_pListenSocket && !g_bVMPISDKMode ) + { + for ( int iTest=m_iListenPort+1; iTest <= VMPI_MASTER_LAST_PORT; iTest++ ) + { + m_iDownloaderListenPort = iTest; + if ( m_iDownloaderListenPort == m_iListenPort ) + continue; + + m_pDownloaderListenSocket = ThreadedTCP_CreateListener( &m_ConnectionCreator, m_iDownloaderListenPort ); + if ( m_pDownloaderListenSocket ) + break; + } + } + } + + if ( !m_pListenSocket || (!g_bVMPISDKMode && !m_pDownloaderListenSocket) ) + { + Error( "Can't bind a listen socket in port range [%d, %d].", VMPI_MASTER_PORT_FIRST, VMPI_MASTER_PORT_LAST ); + } + + + // Create a socket to broadcast from unless we're in the SDK in which case we don't broadcast. + m_bPatching = false; + if ( m_RunMode == VMPI_RUN_NETWORKED && !g_bVMPISDKMode ) + { + m_pSocket = CreateIPSocket(); + if ( !m_pSocket->BindToAny( 0 ) ) + Error( "MPI_Init_Master: can't bind a socket" ); + + m_BroadcastInfo.m_bForcePatch = false; + if ( bPatchMode ) + { + m_bPatching = true; + if ( VMPI_FindArg( argc, argv, "-mpi_ForcePatch", NULL ) ) + m_BroadcastInfo.m_bForcePatch = true; + + const char *pArg = VMPI_FindArg( argc, argv, "-mpi_PatchVersion", "0" ); + float iPatchVersion = atof( pArg ); + if ( iPatchVersion <= 0 || iPatchVersion >= ((1 << 15) - 1) ) + { + Error( "-mpi_PatchVersion <val> - val must be between 1.0 and 32767.0" ); + } + + V_strncpy( m_BroadcastInfo.m_PatchVersion, pArg, sizeof( m_BroadcastInfo.m_PatchVersion ) ); + } + else + { + m_BroadcastInfo.m_PatchVersion[0] = 0; + } + + // Come up with a unique job ID. + m_BroadcastInfo.m_JobID[0] = GetCurMicrosecondsAndSleep( 1 ); + m_BroadcastInfo.m_JobID[1] = GetCurMicrosecondsAndSleep( 1 ); + m_BroadcastInfo.m_JobID[2] = GetCurMicrosecondsAndSleep( 1 ); + m_BroadcastInfo.m_JobID[3] = GetCurMicrosecondsAndSleep( 1 ); + + const char *pPassword = VMPI_FindArg( argc, argv, "-mpi_pw", "" ); + Q_strncpy( m_BroadcastInfo.m_Password, pPassword ? pPassword : "", sizeof( m_BroadcastInfo.m_Password ) ); + Q_strncpy( m_BroadcastInfo.m_WorkerExeFilename, dependencyInfo.m_OriginalExeFilename, sizeof( m_BroadcastInfo.m_WorkerExeFilename ) ); + + // Store the command-line args. + m_BroadcastInfo.m_Args.Purge(); + for ( int i=1; i < argc; i++ ) + { + m_BroadcastInfo.m_Args.AddToTail( CopyString( argv[i] ) ); + } + // 0th arg is the exe name. + m_BroadcastInfo.m_Args.InsertBefore( 0, CopyString( m_BroadcastInfo.m_WorkerExeFilename ) ); + + // Now add arguments for each file they need to transmit. The service will use this to get all the files from the master before it starts the app. + for ( int i=0; i < dependencyInfo.m_Files.Count(); i++ ) + { + m_BroadcastInfo.m_Args.InsertAfter( 0, "-mpi_file" ); + m_BroadcastInfo.m_Args.InsertAfter( 1, CopyString( dependencyInfo.m_Files[i]->m_Name ) ); + } + + // Add -mpi_filebase so it can use absolute paths with the filesystem so we get the exact right set of files. + m_BroadcastInfo.m_Args.InsertAfter( 0, "-mpi_filebase" ); + m_BroadcastInfo.m_Args.InsertAfter( 1, CopyString( dependencyInfo.m_DependencyFilesDir ) ); + + if ( bPatchMode ) + { + GetPatchWorkerList( argc, argv ); + } + } + + + // Add ourselves as the first process (rank 0). + m_ConnectionCreator.CreateNewHandler(); + + // Initiate as many connections as we can for a few seconds. + m_LastSendTime = Plat_MSTime() - MASTER_BROADCAST_INTERVAL*2; + + + m_hShutdownEvent.Init( false, false ); + m_hShutdownReply.Init( false, false ); + + DWORD dwThreadID = 0; + m_hThread = CreateThread( + NULL, + 0, + &CMasterBroadcaster::StaticThreadFn, + this, + 0, + &dwThreadID ); + + if ( m_hThread ) + { + SetThreadPriority( m_hThread, THREAD_PRIORITY_HIGHEST ); + return true; + } + else + { + return false; + } +} + + +void CMasterBroadcaster::BuildBroadcastPacket( bf_write &buf ) +{ + // Broadcast out to tell all the machines we want workers. + buf.WriteByte( VMPI_PROTOCOL_VERSION ); + + buf.WriteString( m_BroadcastInfo.m_Password ); + + if ( m_BroadcastInfo.m_PatchVersion[0] == 0 ) + buf.WriteByte( VMPI_LOOKING_FOR_WORKERS ); + else + buf.WriteByte( VMPI_SERVICE_PATCH ); + + buf.WriteString( m_BroadcastInfo.m_PatchVersion ); + buf.WriteLong( m_iListenPort ); // Tell the port that we're listening on. + buf.WriteLong( m_BroadcastInfo.m_JobID[0] ); + buf.WriteLong( m_BroadcastInfo.m_JobID[1] ); + buf.WriteLong( m_BroadcastInfo.m_JobID[2] ); + buf.WriteLong( m_BroadcastInfo.m_JobID[3] ); + buf.WriteWord( m_BroadcastInfo.m_Args.Count() + 2 ); + + // Write the alternate exe name. + buf.WriteString( m_BroadcastInfo.m_WorkerExeFilename ); + + // Write the machine name of the master into the command line. It's ignored by the code, but it's useful + // if a job crashes the workers - by looking at the command line in vmpi_service, you can see who ran the job. + buf.WriteString( "-mpi_MasterName" ); + buf.WriteString( VMPI_GetLocalMachineName() ); + + for ( int i=1; i < m_BroadcastInfo.m_Args.Count(); i++ ) + buf.WriteString( m_BroadcastInfo.m_Args[i] ); + + buf.WriteByte( (unsigned char)m_BroadcastInfo.m_bForcePatch ); + buf.WriteShort( m_iDownloaderListenPort ); // Tell the port that we're listening for downloaders on. +} + +bool CMasterBroadcaster::Update() +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + // Don't accept any more connections when we've hit the limit. + int nActiveConnections, nServiceDownloaders; + CountActiveConnections( &nActiveConnections, &nServiceDownloaders ); + if ( nActiveConnections >= m_nMaxWorkers ) + return false; + + // Only broadcast our presence so often. + if ( m_pSocket ) + { + DWORD curTime = Plat_MSTime(); + if ( curTime - m_LastSendTime >= MASTER_BROADCAST_INTERVAL ) + { + char packetData[512]; + bf_write packetBuf( "packetBuf", packetData, sizeof( packetData ) ); + BuildBroadcastPacket( packetBuf ); + + for ( int iBroadcastPort=VMPI_SERVICE_PORT; iBroadcastPort <= VMPI_LAST_SERVICE_PORT; iBroadcastPort++ ) + { + if ( m_bPatching ) + { + // Only send to this specific list of workers if necessary. + for ( int i=0; i < m_PatchWorkerIPs.Count(); i++ ) + { + CIPAddr addr = m_PatchWorkerIPs[i]; + addr.port = iBroadcastPort; + m_pSocket->SendTo( &addr, packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten() ); + } + } + else + { + m_pSocket->Broadcast( packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten(), iBroadcastPort ); + } + } + + // We don't want them to keep patching over and over. + if ( m_PatchWorkerIPs.Count() > 0 && m_BroadcastInfo.m_bForcePatch ) + m_PatchWorkerIPs.Purge(); + + m_LastSendTime = curTime; + } + } + + // First look for normal workers. + IThreadedTCPSocket *pNewConn = NULL; + bool bRet = m_pListenSocket->Update( &pNewConn, 0 ); + + // Now look for downloaders. + if ( !bRet || !pNewConn ) + { + if ( m_pDownloaderListenSocket ) + { + int nDownloadersAllowed = (m_nMaxWorkers - nActiveConnections) + 8; // Don't allow too many downloaders. + if ( nServiceDownloaders < nDownloadersAllowed ) + bRet = m_pDownloaderListenSocket->Update( &pNewConn, 0 ); + } + } + + if ( bRet && pNewConn ) + { + // Mark this guy as a downloader if necessary. + CIPAddr remoteAddr = pNewConn->GetRemoteAddr(); + if ( remoteAddr.port >= VMPI_SERVICE_DOWNLOADER_PORT_FIRST && remoteAddr.port <= VMPI_SERVICE_DOWNLOADER_PORT_LAST ) + { + CVMPIConnection *pVMPIConnection = FindConnectionBySocket( pNewConn, false ); + if ( pVMPIConnection ) + pVMPIConnection->m_bIsAService = true; + } + + // Send this guy all the persistent packets. + CCriticalSectionLock csLock( &g_PersistentPacketsCS ); + csLock.Lock(); + + FOR_EACH_LL( g_PersistentPackets, iPacket ) + { + PersistentPacket *pPacket = g_PersistentPackets[iPacket]; + VMPI_SendData( pPacket->Base(), pPacket->Count(), g_nConnections-1 ); + } + + UpdateActiveConnectionsText(); + return true; + } + else + { + return false; + } +} + + +void CMasterBroadcaster::ThreadFn() +{ + // Update every 100ms or until the main thread tells us to go away. + while ( WaitForSingleObject( m_hShutdownEvent.GetEventHandle(), 20 ) == WAIT_TIMEOUT ) + { + DWORD startTime = GetTickCount(); + while ( Update() && (GetTickCount() - startTime) < 500 ) + { + } + } + m_hShutdownReply.SetEvent(); +} + + +DWORD CMasterBroadcaster::StaticThreadFn( LPVOID lpParameter ) +{ + ((CMasterBroadcaster*)lpParameter)->ThreadFn(); + return 0; +} + + +void CMasterBroadcaster::Term() +{ + // Shutdown the update thread. + if ( m_hThread ) + { + m_hShutdownEvent.SetEvent(); + WaitForSingleObject( m_hThread, INFINITE ); + CloseHandle( m_hThread ); + m_hThread = 0; + } + + if ( m_pSocket ) + { + m_pSocket->Release(); + m_pSocket = NULL; + } + + if ( m_pListenSocket ) + { + m_pListenSocket->Release(); + m_pListenSocket = NULL; + } + + if ( m_pDownloaderListenSocket ) + { + m_pDownloaderListenSocket->Release(); + m_pDownloaderListenSocket = NULL; + } + + m_iListenPort = -1; + m_iDownloaderListenPort = -1; +} + + +int CMasterBroadcaster::GetListenPort() const +{ + return m_iListenPort; +} + + +int CMasterBroadcaster::GetMaxWorkers() const +{ + return m_nMaxWorkers; +} + + +void CMasterBroadcaster::IncreaseMaxWorkers( int count ) +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + m_nMaxWorkers = min( MAX_VMPI_CONNECTIONS, m_nMaxWorkers + count ); +} + +void CMasterBroadcaster::SetPassword( const char *pPassword ) +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + Q_strncpy( m_BroadcastInfo.m_Password, pPassword, sizeof( m_BroadcastInfo.m_Password ) ); +} + +void CMasterBroadcaster::SetNoTimeoutOption() +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + // Don't re-add the option if it's already there. + for ( int i=1; i < m_BroadcastInfo.m_Args.Count(); i++ ) + { + if ( Q_stricmp( m_BroadcastInfo.m_Args[i], VMPI_GetParamString( mpi_NoTimeout ) ) == 0 ) + return; + } + + m_BroadcastInfo.m_Args.InsertAfter( 0, (char*)VMPI_GetParamString( mpi_NoTimeout ) ); +} + + +CMasterBroadcaster g_MasterBroadcaster; + + +// ---------------------------------------------------------------------------------------- // +// CDispatchReg. +// ---------------------------------------------------------------------------------------- // + +CDispatchReg::CDispatchReg( int iPacketID, VMPIDispatchFn fn ) +{ + Assert( iPacketID >= 0 && iPacketID < MAX_VMPI_PACKET_IDS ); + Assert( !g_VMPIDispatch[iPacketID] ); + g_VMPIDispatch[iPacketID] = fn; +} + + +void VMPI_HandleTimingWait_Worker() +{ + if ( VMPI_IsParamUsed( mpi_TimingWait ) ) + { + Msg( "-mpi_TimingWait specified. Waiting for master to start..." ); + + // Wait for the signal to go. + while ( !g_bTimingWaitDone ) + { + VMPI_DispatchNextMessage( 50 ); + } + + Msg( "\n "); + } +} + + +void VMPI_HandleTimingWait_Master() +{ + if ( VMPI_IsParamUsed( mpi_TimingWait ) ) + { + Msg( "-mpi_TimingWait specified. Waiting for a keypress to continue... " ); + getch(); + Msg( "\n" ); + + unsigned char cPacket[2] = { VMPI_INTERNAL_PACKET_ID, VMPI_INTERNAL_SUBPACKET_TIMING_WAIT_DONE }; + VMPI_SendData( cPacket, sizeof( cPacket ), VMPI_PERSISTENT ); + } +} + + +// ---------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------- // + +bool MPI_Init_Worker( int &argc, char **&argv, const CIPAddr &masterAddr, bool bConnectingAsService ) +{ + g_bMPIMaster = false; + + // Make a connector to try connect to the master. + CVMPIConnectionCreator connectionCreator; + + int iFirstPort = VMPI_WORKER_PORT_FIRST; + int iLastPort = VMPI_WORKER_PORT_LAST; + if ( bConnectingAsService ) + { + iFirstPort = VMPI_SERVICE_DOWNLOADER_PORT_FIRST; + iLastPort = VMPI_SERVICE_DOWNLOADER_PORT_LAST; + } + + // Now wait for a connection. + int nAttempts = 1; +Retry:; + + ITCPConnectSocket *pConnectSocket = NULL; + int iPort; + for ( iPort=iFirstPort; iPort <= iLastPort; iPort++ ) + { + pConnectSocket = ThreadedTCP_CreateConnector( + masterAddr, + CIPAddr( 0, 0, 0, 0, iPort ), + &connectionCreator ); + + if ( pConnectSocket ) + break; + } + if ( !pConnectSocket ) + { + Error( "Can't bind a port in range [%d, %d].", iFirstPort, iLastPort ); + } + + + CWaitTimer wait( 3 ); + while ( 1 ) + { + IThreadedTCPSocket *pSocket = NULL; + if ( pConnectSocket->Update( &pSocket, 100 ) ) + { + if ( pSocket ) + { + // Send the master our machine name. + VMPI_SendMachineNameTo( VMPI_MASTER_ID ); + + // Verify that the exe is correct. + VMPI_ReceiveExeName(); + + if ( g_bVMPISDKMode ) + { + VMPI_ReceiveCommandLine(); + + CommandLine()->CreateCmdLine( g_WorkerCommandLine.Count(), g_WorkerCommandLine.Base() ); + argc = g_WorkerCommandLine.Count(); + argv = g_WorkerCommandLine.Base(); + } + + ParseOptions( g_WorkerCommandLine.Count(), g_WorkerCommandLine.Base() ); + for ( int i=0; i < g_WorkerCommandLine.Count(); i++ ) + { + Msg( "arg %d: %s\n", i, g_WorkerCommandLine[i] ); + } + + VMPI_HandleTimingWait_Worker(); + return true; + } + } + else + { + pConnectSocket->Release(); + Error( "ITCPConnectSocket::Update() errored out" ); + } + + if( wait.ShouldKeepWaiting() ) + Sleep( 100 ); + else + break; + }; + + // Never made a connection, shucks. + pConnectSocket->Release(); + + if ( VMPI_IsParamUsed( mpi_Retry ) ) + { + Msg( "%s found. Retrying connection to %d.%d.%d.%d:%d (attempt %d).\n", VMPI_GetParamString( mpi_Retry ), masterAddr.ip[0], masterAddr.ip[1], masterAddr.ip[2], masterAddr.ip[3], masterAddr.port, nAttempts++ ); + goto Retry; + } + + return false; +} + + +bool SpawnLocalWorker( int argc, char **argv, int iListenPort, bool bShowConsoleWindow ) +{ + char commandLine[4096]; + commandLine[0] = 0; + + // Add the -mpi_worker argument in, then launch the process. + for ( int i=0; i < 9999999; i++ ) + { + char argStr[512]; + + if ( i == 1 ) + { + Q_snprintf( argStr, sizeof( argStr ), "-mpi_worker 127.0.0.1:%d ", iListenPort ); + Q_strncat( commandLine, argStr, sizeof( commandLine ), COPY_ALL_CHARACTERS ); + Q_strncat( commandLine, "-allowdebug ", sizeof( commandLine ), COPY_ALL_CHARACTERS ); + + // Add -mpi_SDKMode if it's needed. This would mostly only occur in a debugging situation + // (someone running out of rel using -mpi_AutoLocalWorker). + if ( VMPI_IsSDKMode() && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_SDKMode ), "" ) ) + { + Q_strncat( commandLine, VMPI_GetParamString( mpi_SDKMode ), sizeof( commandLine ), COPY_ALL_CHARACTERS ); + } + } + + if ( i >= argc ) + break; + + Q_snprintf( argStr, sizeof( argStr ), "\"%s\" ", argv[i] ); + Q_strncat( commandLine, argStr, sizeof( commandLine ), COPY_ALL_CHARACTERS ); + } + + char workingDir[1024]; + if ( !_getcwd( workingDir, sizeof( workingDir ) ) ) + { + Warning( "_getcwd() failed.\n" ); + return false; + } + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( CreateProcess( + NULL, + commandLine, + NULL, // security + NULL, + TRUE, + (bShowConsoleWindow ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW) | IDLE_PRIORITY_CLASS, // flags + NULL, // environment + workingDir, // current directory (use c:\\ because we don't want it to accidentally share + // DLLs like vstdlib with us). + &si, + &pi ) ) + { + return true; + } + else + { + char errStr[1024]; + IP_GetLastErrorString( errStr, sizeof( errStr ) ); + Warning( " - ERROR in CreateProcess (%s)!\n", errStr ); + return false; + } +} + + +bool InitMaster( int argc, char **argv, const char *pDependencyFilename, VMPIRunMode runMode, bool bPatchMode ) +{ + int nMaxWorkers = -1; + const char *pProcCount = VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_WorkerCount ) ); + if ( pProcCount ) + { + nMaxWorkers = atoi( pProcCount ); + Warning( "%s: waiting for %d processes to join.\n", VMPI_GetParamString( mpi_WorkerCount ), nMaxWorkers ); + } + else + { + nMaxWorkers = DEFAULT_MAX_WORKERS; + } + nMaxWorkers = clamp( nMaxWorkers, 2, MAX_VMPI_CONNECTIONS ); + + + g_bMPIMaster = true; + g_nMaxWorkerCount = nMaxWorkers; + + if ( argc <= 0 ) + Error( "MPI_Init_Master: argc <= 0!" ); + + ParseOptions( argc, argv ); + + // Send the base filename of the exe we're running. Sometimes if we run vvis followed by vrad + // really quickly, the old vvis workers can connect to the vrad process and mess with it. + VMPI_SendExeName(); + + // In SDK mode, the master sends the command line to the workers since + // the workers weren't given a full command line by vmpi_service. + if ( VMPI_IsSDKMode() ) + { + VMPI_SendCommandLine( argc, argv ); + } + + if ( !g_MasterBroadcaster.Init( argc, argv, pDependencyFilename, nMaxWorkers, runMode, bPatchMode ) ) + return false; + + bool bRet; + if ( runMode == VMPI_RUN_LOCAL ) + { + bRet = SpawnLocalWorker( argc, argv, g_MasterBroadcaster.GetListenPort(), false ); + } + else + { + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_AutoLocalWorker ), "" ) ) + { + Msg( "%s found. Spawning a local worker automatically.\n", VMPI_GetParamString( mpi_AutoLocalWorker ) ); + SpawnLocalWorker( 1, argv, g_MasterBroadcaster.GetListenPort(), true ); + } + + bRet = true; + } + + VMPI_HandleTimingWait_Master(); + return bRet; +} + + +void VMPI_InitGlobals( int argc, char **argv, VMPIRunMode runMode ) +{ + g_bUseMPI = true; + g_VMPIRunMode = runMode; + + // Init event objects. + g_VMPIMessagesEvent.Init( false, false ); + g_ErrorSocketsEvent.Init( false, false ); + + // Load this for GetConsoleWindow(). + g_hKernel32DLL = LoadLibrary( "kernel32.dll" ); + if ( g_hKernel32DLL ) + { + g_pConsoleWndFn = (GetConsoleWndFn)GetProcAddress( g_hKernel32DLL, "GetConsoleWindow" ); + } + + #if defined( _DEBUG ) + + for ( int iArg=0; iArg < argc; iArg++ ) + { + Warning( "%s\n", argv[iArg] ); + } + + Warning( "\n" ); + + #endif +} + + +bool VMPI_CheckForNonSDKExecutables() +{ + char baseExeFilename[512]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "VMPI_CheckSDKMode -> GetModuleFileName failed." ); + + V_StripLastDir( baseExeFilename, sizeof( baseExeFilename ) ); + V_AppendSlash( baseExeFilename, sizeof( baseExeFilename ) ); + V_strncat( baseExeFilename, "mysql_wrapper.dll", sizeof( baseExeFilename ) ); + + // If vmpi_transfer.exe doesn't exist, then we assume we're in SDK mode. + return ( _access( baseExeFilename, 0 ) == 0 ); +} + + +bool IsValidSDKBinPath( CUtlVector< char* > &outStrings, int *pError ) +{ + *pError = 0; + + // Minimum must have drive:/basedir/steamapps/name/sourcesdk/bin/[ep1|orangebox]/bin/exename + if ( outStrings.Count() < 9 ) + { + *pError = 0; + return false; + } + + if ( V_stricmp( outStrings[outStrings.Count()-2], "bin" ) != 0 ) + { + *pError = 1; + return false; + } + + if ( V_stricmp( outStrings[outStrings.Count()-5], "sourcesdk" ) != 0 ) + { + *pError = 2; + return false; + } + + if ( V_stricmp( outStrings[outStrings.Count()-7], "steamapps" ) != 0 ) + { + *pError = 3; + return false; + } + + // Check the last-access date on clientregistry.blob + char baseSteamPath[MAX_PATH]; + V_strncpy( baseSteamPath, outStrings[0], sizeof( baseSteamPath) ); + for ( int i=1; i < outStrings.Count() - 7; i++ ) + { + V_AppendSlash( baseSteamPath, sizeof( baseSteamPath ) ); + V_strncat( baseSteamPath, outStrings[i], sizeof( baseSteamPath ) ); + } + + char blobPath[MAX_PATH]; + V_ComposeFileName( baseSteamPath, "ClientRegistry.blob", blobPath, sizeof( blobPath ) ); + struct _stat results; + if ( _stat( blobPath, &results ) != 0 ) + { + *pError = 4; + return false; + } + + long curTime; + VCRHook_Time( &curTime ); + int nSecondsSinceLastSteamAccess = curTime - results.st_mtime; + int nSecondsPerDay = 60 * 60 * 24; + int nMaxDaysUnaccessed = 10; + if ( nSecondsSinceLastSteamAccess > nSecondsPerDay*nMaxDaysUnaccessed ) + { + *pError = 5; // NOTE: don't change this error code because the outer function checks for it. + return false; + } + + // Check for some of the files under sourcesdk_content. + char sourcesdkContentPath[MAX_PATH]; + V_strncpy( sourcesdkContentPath, outStrings[0], sizeof( sourcesdkContentPath ) ); + for ( int i=1; i < outStrings.Count() - 5; i++ ) + { + V_AppendSlash( sourcesdkContentPath, sizeof( sourcesdkContentPath ) ); + V_strncat( sourcesdkContentPath, outStrings[i], sizeof( sourcesdkContentPath ) ); + } + V_AppendSlash( sourcesdkContentPath, sizeof( sourcesdkContentPath ) ); + V_strncat( sourcesdkContentPath, "sourcesdk_content", sizeof( sourcesdkContentPath ) ); + + char tempFilename[MAX_PATH], mapsrcFilename[MAX_PATH]; + V_snprintf( tempFilename, sizeof( tempFilename ), "cstrike%cmapsrc", CORRECT_PATH_SEPARATOR ); + V_ComposeFileName( sourcesdkContentPath, tempFilename, mapsrcFilename, sizeof( mapsrcFilename ) ); + if ( _access( mapsrcFilename, 0 ) != 0 ) + { + *pError = 6; + return false; + } + + return true; +} + +void VerifyValidSDKMode() +{ + // Make sure we're running out of the SourceSDK directory and that our SDK directories are filled out. + char baseExeFilename[MAX_PATH]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "VerifyValidSDKMode: GetModuleFileName failed." ); + V_FixSlashes( baseExeFilename ); + + CUtlVector< char* > outStrings; + char strSlash[2] = {CORRECT_PATH_SEPARATOR, 0}; + V_SplitString( baseExeFilename, strSlash, outStrings ); + + int err; + if ( !IsValidSDKBinPath( outStrings, &err ) ) + { + outStrings.PurgeAndDeleteElements(); + + if ( err == 5 ) + Error( "VMPI running in SDK mode but Steam hasn't been run recently. Please run Steam and retry." ); + else + Error( "VMPI running in SDK mode but incorrect SDK install detected (error %d).", err ); + } +} + +void VMPI_CheckSDKMode( int argc, char **argv ) +{ + g_bVMPISDKMode = !VMPI_CheckForNonSDKExecutables(); + g_bVMPISDKModeSet = true; + + // Also check for -mpi_sdkmode (only used in testing). + if ( !g_bVMPISDKMode ) + { + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_SDKMode ), "" ) ) + g_bVMPISDKMode = true; + } + + if ( g_bVMPISDKMode ) + { + VerifyValidSDKMode(); + } + + if ( g_bVMPISDKMode ) + { + Msg( "VMPI running in SDK mode.\n" ); + } +} + + +void VMPI_SetupAutoRestartParameters( int argc, char **argv ) +{ + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_AutoRestart ) ) ) + { + g_OriginalCommandLineParameters.SetSize( argc ); + for ( int i=0; i < argc; i++ ) + { + g_OriginalCommandLineParameters[i] = CopyString( argv[i] ); + } + } +} + + +bool VMPI_HandleAutoRestart() +{ + if ( g_OriginalCommandLineParameters.Count() == 0 ) + return true; + + Msg( "%s found. Auto-restarting.\n", VMPI_GetParamString( mpi_AutoRestart ) ); + DWORD curPriority = GetPriorityClass( GetCurrentProcess() ); + + char commandLine[1024*8]; + commandLine[0] = 0; + + // Add the -mpi_worker argument in, then launch the process. + for ( int i=0; i < g_OriginalCommandLineParameters.Count(); i++ ) + { + char argStr[512]; + Q_snprintf( argStr, sizeof( argStr ), "\"%s\" ", g_OriginalCommandLineParameters[i] ); + Q_strncat( commandLine, argStr, sizeof( commandLine ), COPY_ALL_CHARACTERS ); + } + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( CreateProcess( + NULL, + commandLine, + NULL, // security + NULL, + TRUE, + CREATE_NEW_CONSOLE | curPriority, // flags + NULL, // environment + NULL, + &si, + &pi ) ) + { + g_OriginalCommandLineParameters.Purge(); + return true; + } + else + { + char errStr[1024]; + IP_GetLastErrorString( errStr, sizeof( errStr ) ); + Warning( " - ERROR in CreateProcess (%s)!\n", errStr ); + return false; + } +} + + +bool VMPI_Init( + int &argc, + char **&argv, + const char *pDependencyFilename, + VMPI_Disconnect_Handler handler, + VMPIRunMode runMode, + bool bConnectingAsService + ) +{ + if ( handler ) + VMPI_AddDisconnectHandler( handler ); + + VMPI_SetupAutoRestartParameters( argc, argv ); + + VMPI_CheckSDKMode( argc, argv ); + VMPI_InitGlobals( argc, argv, runMode ); + + // Were we launched by the vmpi service as a worker? + const char *pMasterIP = VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), NULL ); + if ( pMasterIP ) + { + CIPAddr addr; + addr.port = VMPI_MASTER_FIRST_PORT; + if ( !ConvertStringToIPAddr( pMasterIP, &addr ) ) + Error( "Unable to parse or resolve master IP (%s).\n", pMasterIP ); + + return MPI_Init_Worker( argc, argv, addr, bConnectingAsService ); + } + else + { + if ( !pDependencyFilename ) + { + Error( "VMPI started as master, but no dependency filename specified.\n" ); + return false; + } + + return InitMaster( argc, argv, pDependencyFilename, runMode, false ); + } +} + + +void VMPI_Init_PatchMaster( int argc, char **argv ) +{ + const char *pPatchDirectory = VMPI_FindArg( argc, argv, "-mpi_PatchDirectory", NULL ); + if ( !pPatchDirectory ) + Error( "-mpi_PatchDirectory <dir> must be specified if using -PatchHost mode." ); + + VMPI_InitGlobals( argc, argv, VMPI_RUN_NETWORKED ); + + InitMaster( argc, argv, pPatchDirectory, VMPI_RUN_NETWORKED, true ); +} + + +void VMPI_Finalize() +{ + g_MasterBroadcaster.Term(); + + DistributeWork_Cancel(); + + // Get rid of all the sockets. + for ( int iConn=0; iConn < g_nConnections; iConn++ ) + delete g_Connections[iConn]; + + g_nConnections = 0; + + // Get rid of all the packets. + FOR_EACH_LL( g_VMPIMessages, i ) + { + g_VMPIMessages[i]->Release(); + } + g_VMPIMessages.Purge(); + + g_PersistentPackets.PurgeAndDeleteElements(); + + // Get rid of the message buffers + g_DispatchBuffers.Purge(); + + if ( g_hKernel32DLL ) + { + FreeLibrary( g_hKernel32DLL ); + g_hKernel32DLL = NULL; + } + + g_WorkerCommandLine.PurgeAndDeleteElements(); + + VMPI_HandleAutoRestart(); +} + + +VMPIRunMode VMPI_GetRunMode() +{ + return g_VMPIRunMode; +} + + +VMPIFileSystemMode VMPI_GetFileSystemMode() +{ + return g_VMPIFileSystemMode; +} + + +int VMPI_GetCurrentNumberOfConnections() +{ + return g_nConnections; +} + + +void InternalHandleSocketErrors() +{ + // Copy the list of sockets with errors into a local array so we can handle all the errors outside + // the mutex, thus avoiding potential deadlock if any error handlers call Error(). + CUtlVector<CVMPIConnection*> errorSockets; + + CCriticalSectionLock csLock( &g_ErrorSocketsCS ); + csLock.Lock(); + + errorSockets.SetSize( g_ErrorSockets.Count() ); + int iCur = 0; + FOR_EACH_LL( g_ErrorSockets, i ) + { + errorSockets[iCur++] = g_ErrorSockets[i]; + } + + g_ErrorSockets.Purge(); + + csLock.Unlock(); + + // Handle the errors. + for ( int i=0; i < errorSockets.Count(); i++ ) + { + errorSockets[i]->HandleDisconnect(); + } + + UpdateActiveConnectionsText(); +} + + +void VMPI_HandleSocketErrors( unsigned long timeout ) +{ + DWORD ret = WaitForSingleObject( g_ErrorSocketsEvent.GetEventHandle(), timeout ); + if ( ret == WAIT_OBJECT_0 ) + { + InternalHandleSocketErrors(); + } +} + + +// If bWait is false, then this function returns false immediately if there are no messages waiting. +bool VMPI_GetNextMessage( MessageBuffer *pBuf, int *pSource, unsigned long startTimeout ) +{ + HANDLE handles[2] = { g_ErrorSocketsEvent.GetEventHandle(), g_VMPIMessagesEvent.GetEventHandle() }; + + DWORD startTime = Plat_MSTime(); + DWORD timeout = startTimeout; + + while ( 1 ) + { + DWORD ret = WaitForMultipleObjects( ARRAYSIZE( handles ), handles, FALSE, timeout ); + if ( ret == WAIT_TIMEOUT ) + { + return false; + } + else if ( ret == WAIT_OBJECT_0 ) + { + // A socket had an error. Handle all socket errors. + InternalHandleSocketErrors(); + + // Update the timeout. + DWORD delta = Plat_MSTime() - startTime; + if ( delta >= startTimeout ) + return false; + + timeout = startTimeout - delta; + continue; + } + else if ( ret == (WAIT_OBJECT_0 + 1) ) + { + // Read out the next message. + CCriticalSectionLock csLock( &g_VMPIMessagesCS ); + csLock.Lock(); + +GrabNextMessage:; + int iHead = g_VMPIMessages.Head(); + CTCPPacket *pPacket = g_VMPIMessages[iHead]; + g_VMPIMessages.Remove( iHead ); + + // Set the event again if there are more messages waiting. + const char *pBase = pPacket->GetData(); + if ( pPacket->GetLen() >= 6 && (unsigned char)pBase[0] == VMPI_INTERNAL_PACKET_ID && (unsigned char)pBase[1] == VMPI_INTERNAL_SUBPACKET_GROUPED_PACKET ) + { + // Ok, this is a grouped packet. Split it out into a bunch of separate packets. + CUtlVector<CTCPPacket*> groupedPackets; + int iCurOffset = 2; + while ( (iCurOffset+4) <= pPacket->GetLen() ) + { + int curPacketLen = *((int*)&pBase[iCurOffset]); + if ( iCurOffset + curPacketLen > pPacket->GetLen() ) + Error( "Invalid chunked packet\n" ); + + iCurOffset += 4; + + CTCPPacket *pChunkPacket = (CTCPPacket*)malloc( sizeof( CTCPPacket ) + curPacketLen - 1 ); + pChunkPacket->m_Len = curPacketLen; + pChunkPacket->m_UserData = pPacket->m_UserData; + memcpy( pChunkPacket->m_Data, &pBase[iCurOffset], curPacketLen ); + groupedPackets.AddToTail( pChunkPacket ); + + iCurOffset += curPacketLen; + } + + for ( int i=0; i < groupedPackets.Count(); i++ ) + { + g_VMPIMessages.AddToHead( groupedPackets[groupedPackets.Count() - i - 1] ); + } + pPacket->Release(); + goto GrabNextMessage; + } + else + { + if ( g_VMPIMessages.Count() > 0 ) + g_VMPIMessagesEvent.SetEvent(); + } + + csLock.Unlock(); + + // Copy it into their message buffer. + pBuf->setLen( pPacket->GetLen() ); + memcpy( pBuf->data, pPacket->GetData(), pPacket->GetLen() ); + + *pSource = pPacket->GetUserData(); + Assert( *pSource >= 0 && *pSource < g_nConnections ); + + // Update global stats about how much data we've received. + ++g_nMessagesReceived; + g_nBytesReceived += pPacket->GetLen() + 4; // (4 bytes extra for the packet length) + + // Free the memory associated with the packet. + pPacket->Release(); + return true; + } + else + { + Error( "VMPI_GetNextMessage: WaitForSingleObject returned %lu", ret ); + return false; + } + } +} + + +bool VMPI_InternalDispatch( MessageBuffer *pBuf, int iSource ) +{ + if ( pBuf->getLen() >= 1 && + pBuf->data[0] >= 0 && pBuf->data[0] < MAX_VMPI_PACKET_IDS && + g_VMPIDispatch[pBuf->data[0]] ) + { + return g_VMPIDispatch[ pBuf->data[0] ]( pBuf, iSource, pBuf->data[0] ); + + } + else + { + return false; + } +} + +bool VMPI_DispatchNextMessage( unsigned long timeout ) +{ + MessageBuffer *pBuf = NULL; + if ( !g_DispatchBuffers.PopItem( &pBuf ) ) + { + pBuf = new MessageBuffer(); + } + + bool bRetval = true; + while ( 1 ) + { + int iSource; + if ( VMPI_GetNextMessage( pBuf, &iSource, timeout ) ) + { + if ( VMPI_InternalDispatch( pBuf, iSource ) ) + { + break; + } + else + { + // Workers running in service mode don't hook anything except filesystem stuff, so if they happen to be sent something, no problem. + if ( !VMPI_IsProcAService( iSource ) ) + { + // Oops! What is this packet? + Assert( false ); + } + } + } + else + { + bRetval = false; + break; + } + } + + g_DispatchBuffers.PushItem( pBuf ); + return bRetval; +} + + +bool VMPI_DispatchUntil( MessageBuffer *pBuf, int *pSource, int packetID, int subPacketID, bool bWait ) +{ + while ( 1 ) + { + if ( !VMPI_GetNextMessage( pBuf, pSource, bWait ? VMPI_TIMEOUT_INFINITE : 0 ) ) + return false; + + if ( !VMPI_InternalDispatch( pBuf, *pSource ) ) + { + if ( pBuf->getLen() >= 1 && (unsigned char)pBuf->data[0] == packetID ) + { + if ( subPacketID == -1 ) + return true; + + if ( pBuf->getLen() >= 2 && (unsigned char)pBuf->data[1] == subPacketID ) + return true; + } + + // Oops! What is this packet? + // Note: the most common case where this happens is if it finishes a BuildFaceLights run + // and is in an AppBarrier and one of the workers is still finishing up some work given to it. + // It'll be waiting for a barrier packet, and it'll get results. In that case, the packet should + // be discarded like we do here, so maybe this assert won't be necessary. + //Assert( false ); + } + } +} + + +bool VMPI_SendData( void *pData, int nBytes, int iDest, int fVMPISendFlags ) +{ + return VMPI_SendChunks( &pData, &nBytes, 1, iDest, fVMPISendFlags ); +} + + +inline bool VMPI_FilterPacketsForServiceDownloader( CVMPIConnection *pConnection, void const * const *pChunks, const int *pChunkLengths, int nChunks ) +{ + if ( pConnection->m_bIsAService ) + { + // Find the first byte and treat that as the packet ID. + for ( int i=0; i < nChunks; i++ ) + { + if ( pChunkLengths[i] > 0 ) + { + unsigned char cPacketID = *((unsigned char*)pChunks[i]); + if ( cPacketID == VMPI_INTERNAL_PACKET_ID || cPacketID == VMPI_SHARED_PACKET_ID || cPacketID == VMPI_PACKETID_FILESYSTEM ) + return false; + else + return true; + } + } + } + return false; +} + + +void VMPI_GroupPackets( CVMPIConnection *pConn, void const * const *pChunks, const int *pChunkLengths, int nChunks ) +{ + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + // First add the header. + if ( pConn->m_GroupedChunks.Count() == 0 ) + { + pConn->m_GroupedChunks.AddToTail( g_GroupedPacketHeader ); + pConn->m_GroupedChunkLengths.AddToTail( sizeof( g_GroupedPacketHeader ) ); + } + + // Collate the chunks. + int nTotalLength = 0; + for ( int i=0; i < nChunks; i++ ) + nTotalLength += pChunkLengths[i]; + + char *pOut = new char[nTotalLength + 4]; + *((int*)pOut) = nTotalLength; + int iOutByte = 4; + for ( int i=0; i < nChunks; i++ ) + { + memcpy( &pOut[iOutByte], pChunks[i], pChunkLengths[i] ); + iOutByte += pChunkLengths[i]; + } + + pConn->m_GroupedChunks.AddToTail( pOut ); + pConn->m_GroupedChunkLengths.AddToTail( nTotalLength + 4 ); +} + + +void VMPI_FlushGroupedPackets( unsigned long msInterval ) +{ + if ( msInterval != 0 ) + { + unsigned long curTime = Plat_MSTime(); + if ( curTime - g_LastFlushGroupedPacketsTime < msInterval ) + return; + g_LastFlushGroupedPacketsTime = curTime; + } + + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + for ( int i=0; i < g_nConnections; i++ ) + { + CVMPIConnection *pConn = g_Connections[i]; + + if ( !pConn ) + continue; + + IThreadedTCPSocket *pSocket = pConn->GetSocket(); + if ( !pSocket || pConn->m_GroupedChunks.Count() == 0 ) + continue; + + pSocket->SendChunks( pConn->m_GroupedChunks.Base(), pConn->m_GroupedChunkLengths.Base(), pConn->m_GroupedChunks.Count() ); + + // Free the chunks. + for ( int i=1; i < pConn->m_GroupedChunks.Count(); i++ ) + { + free( pConn->m_GroupedChunks[i] ); + } + pConn->m_GroupedChunks.RemoveAll(); + pConn->m_GroupedChunkLengths.RemoveAll(); + } +} + + +bool VMPI_SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks, int iDest, int fVMPISendFlags ) +{ + if ( iDest == VMPI_SEND_TO_ALL ) + { + // Don't want new connections while in here! + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + for ( int i=0; i < g_nConnections; i++ ) + VMPI_SendChunks( pChunks, pChunkLengths, nChunks, i ); + + return true; + } + else if ( iDest == VMPI_PERSISTENT ) + { + // Don't want new connections while in here! + CCriticalSectionLock connectionsLock( &g_ConnectionsCS ); + connectionsLock.Lock(); + + CCriticalSectionLock csLock( &g_PersistentPacketsCS ); + csLock.Lock(); + + // Send the packet to everyone. + for ( int i=0; i < g_nConnections; i++ ) + VMPI_SendChunks( pChunks, pChunkLengths, nChunks, i ); + + // Remember to send it to the new workers. + if ( iDest == VMPI_PERSISTENT ) + { + PersistentPacket *pNew = new PersistentPacket; + for ( int i=0; i < nChunks; i++ ) + pNew->AddMultipleToTail( pChunkLengths[i], (const char*)pChunks[i] ); + + g_PersistentPackets.AddToTail( pNew ); + } + + return true; + } + else + { + g_nMessagesSent++; + g_nBytesSent += 4; // for message tag. + for ( int i=0; i < nChunks; i++ ) + g_nBytesSent += pChunkLengths[i]; + + CVMPIConnection *pConnection = g_Connections[iDest]; + + if ( pConnection ) + { + // If it's a service downloader, only send certain packet IDs. + if ( VMPI_FilterPacketsForServiceDownloader( pConnection, pChunks, pChunkLengths, nChunks ) ) + return true; + + IThreadedTCPSocket *pSocket = pConnection->GetSocket(); + if ( !pSocket ) + return false; + + if ( g_bGroupPackets && (fVMPISendFlags & k_eVMPISendFlags_GroupPackets) ) + { + VMPI_GroupPackets( pConnection, pChunks, pChunkLengths, nChunks ); + return true; + } + else + { + return pSocket->SendChunks( pChunks, pChunkLengths, nChunks ); + } + } + else + { + return false; + } + } +} + + +bool VMPI_Send2Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, int iDest, int fVMPISendFlags ) +{ + const void *pChunks[2] = { pChunk1, pChunk2 }; + int len[2] = { chunk1Len, chunk2Len }; + return VMPI_SendChunks( pChunks, len, ARRAYSIZE( pChunks ), iDest, fVMPISendFlags ); +} + + +bool VMPI_Send3Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, const void *pChunk3, int chunk3Len, int iDest, int fVMPISendFlags ) +{ + const void *pChunks[3] = { pChunk1, pChunk2, pChunk3 }; + int len[3] = { chunk1Len, chunk2Len, chunk3Len }; + return VMPI_SendChunks( pChunks, len, ARRAYSIZE( pChunks ), iDest, fVMPISendFlags ); +} + + +void VMPI_AddDisconnectHandler( VMPI_Disconnect_Handler handler ) +{ + g_DisconnectHandlers.AddToTail( handler ); +} + + +CVMPIConnection* GetConnection( int procID ) +{ + Assert( procID >= 0 && procID < g_nConnections ); + return g_Connections[procID]; +} + + +bool VMPI_IsProcConnected( int procID ) +{ + if ( procID < 0 || procID >= g_nConnections ) + { + Assert( false ); + return false; + } + + return g_Connections[procID]->GetSocket() != NULL; +} + +bool VMPI_IsProcAService( int procID ) +{ + if ( procID < 0 || procID >= g_nConnections ) + { + Assert( false ); + return false; + } + + return g_Connections[procID]->m_bIsAService; +} + +void VMPI_Sleep( unsigned long ms ) +{ + Sleep( ms ); +} + + +const char* VMPI_GetMachineName( int iProc ) +{ + if ( g_bMPIMaster && iProc == VMPI_MASTER_ID ) + return VMPI_GetLocalMachineName(); + + if ( iProc < 0 || iProc >= g_nConnections ) + { + Assert( false ); + return "invalid index"; + } + + return g_Connections[iProc]->GetMachineName(); +} + + +void VMPI_SetMachineName( int iProc, const char *pName ) +{ + if ( iProc < 0 || iProc >= g_nConnections ) + { + Assert( false ); + return; + } + + g_Connections[iProc]->SetMachineName( pName ); +} + + +bool VMPI_HasMachineNameBeenSet( int iProc ) +{ + if ( iProc < 0 || iProc >= g_nConnections ) + { + Assert( false ); + return false; + } + + return g_Connections[iProc]->HasMachineNameBeenSet(); +} + + +const char* VMPI_GetLocalMachineName() +{ + static char cName[MAX_COMPUTERNAME_LENGTH+1]; + DWORD len = sizeof( cName ); + if ( GetComputerName( cName, &len ) ) + return cName; + else + return "(error in GetComputerName)"; +} + + +unsigned long VMPI_GetJobWorkerID( int iProc ) +{ + return GetConnection( iProc )->m_JobWorkerID; +} + + +void VMPI_SetJobWorkerID( int iProc, unsigned long jobWorkerID ) +{ + GetConnection( iProc )->m_JobWorkerID = jobWorkerID; +} + + +void VMPI_GetCurrentStage( char *pOut, int strLen ) +{ + CCriticalSectionLock csLock( &g_CurrentStageCS ); + csLock.Lock(); + Q_strncpy( pOut, g_CurrentStageString, strLen ); +} + + +void VMPI_SetCurrentStage( const char *pCurStage ) +{ + CCriticalSectionLock csLock( &g_CurrentStageCS ); + csLock.Lock(); + Q_strncpy( g_CurrentStageString, pCurStage, sizeof( g_CurrentStageString ) ); +} + + +void VMPI_InviteDebugWorkers() +{ + // Only allow workers with password set to debugworker. + g_MasterBroadcaster.SetPassword( "debugworker" ); + + // Disable timeouts so they can sit in the debugger. + g_MasterBroadcaster.SetNoTimeoutOption(); + ThreadedTCP_EnableTimeouts( false ); + + // Let in some more workers. + g_MasterBroadcaster.IncreaseMaxWorkers( 25 ); +} + + +bool VMPI_IsSDKMode() +{ + if ( g_bVMPISDKModeSet ) + return g_bVMPISDKMode; + else + return !VMPI_CheckForNonSDKExecutables(); +} + + +const char* VMPI_GetParamString( EVMPICmdLineParam eParam ) +{ + if ( eParam <= k_eVMPICmdLineParam_FirstParam || eParam >= k_eVMPICmdLineParam_LastParam ) + { + Assert( false ); + Warning( "Invalid call: VMPI_GetParamString( %d )\n", eParam ); + return "unknown"; + } + else + { + return g_VMPIParams[eParam].m_pName; + } +} + +int VMPI_GetParamFlags( EVMPICmdLineParam eParam ) +{ + if ( eParam <= k_eVMPICmdLineParam_FirstParam || eParam >= k_eVMPICmdLineParam_LastParam ) + { + Assert( false ); + Warning( "Invalid call: VMPI_GetParamString( %d )\n", eParam ); + return 0; + } + else + { + return g_VMPIParams[eParam].m_ParamFlags; + } +} + +bool VMPI_IsParamUsed( EVMPICmdLineParam eParam ) +{ + int iParam = CommandLine()->FindParm( VMPI_GetParamString( eParam ) ); + return iParam != 0; +} + +const char* VMPI_GetParamHelpString( EVMPICmdLineParam eParam ) +{ + if ( eParam <= k_eVMPICmdLineParam_FirstParam || eParam >= k_eVMPICmdLineParam_LastParam ) + { + Assert( false ); + Warning( "Invalid call: VMPI_GetParamHelpString( %d )\n", eParam ); + return "unknown vmpi param"; + } + else + { + return g_VMPIParams[eParam].m_pHelpText; + } +} + + diff --git a/utils/vmpi/vmpi.h b/utils/vmpi/vmpi.h new file mode 100644 index 0000000..cb7316f --- /dev/null +++ b/utils/vmpi/vmpi.h @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_H +#define VMPI_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vmpi_defs.h" +#include "messbuf.h" +#include "iphelpers.h" + + +// These are called to handle incoming messages. +// Return true if you handled the message and false otherwise. +// Note: the first byte in each message is the packet ID. +typedef bool (*VMPIDispatchFn)( MessageBuffer *pBuf, int iSource, int iPacketID ); + +typedef void (*VMPI_Disconnect_Handler)( int procID, const char *pReason ); + + +// Which machine is the master. +#define VMPI_MASTER_ID 0 + +#define VMPI_SEND_TO_ALL -2 +#define VMPI_PERSISTENT -3 // If this is set as the destination for a packet, it is sent to all + // workers, and also to new workers that connect. + +#define MAX_VMPI_PACKET_IDS 32 + + +#define VMPI_TIMEOUT_INFINITE 0xFFFFFFFF + + +// Instantiate one of these to register a dispatch. +class CDispatchReg +{ +public: + CDispatchReg( int iPacketID, VMPIDispatchFn fn ); +}; + + +// Enums for all the command line parameters. +#define VMPI_PARAM_SDK_HIDDEN 0x0001 // Hidden in SDK mode. + +#define VMPI_PARAM( paramName, paramFlags, helpText ) paramName, +enum EVMPICmdLineParam +{ + k_eVMPICmdLineParam_FirstParam=0, + k_eVMPICmdLineParam_VMPIParam, + #include "vmpi_parameters.h" + k_eVMPICmdLineParam_LastParam +}; +#undef VMPI_PARAM + + +// Shared by all the tools. +extern bool g_bUseMPI; +extern bool g_bMPIMaster; // Set to true if we're the master in a VMPI session. +extern int g_iVMPIVerboseLevel; // Higher numbers make it spit out more data. + +extern bool g_bMPI_Stats; // Send stats to the MySQL database? +extern bool g_bMPI_StatsTextOutput; // Send text output in the stats? + +// These can be watched or modified to check bandwidth statistics. +extern int g_nBytesSent; +extern int g_nMessagesSent; +extern int g_nBytesReceived; +extern int g_nMessagesReceived; + +extern int g_nMulticastBytesSent; +extern int g_nMulticastBytesReceived; + +extern int g_nMaxWorkerCount; + + +enum VMPIRunMode +{ + VMPI_RUN_NETWORKED, + VMPI_RUN_LOCAL // Just make a local process and have it do the work. +}; + + +enum VMPIFileSystemMode +{ + VMPI_FILESYSTEM_MULTICAST, // Multicast out, find workers, have them do work. + VMPI_FILESYSTEM_BROADCAST, // Broadcast out, find workers, have them do work. + VMPI_FILESYSTEM_TCP // TCP filesystem. +}; + + +// If this precedes the dependency filename, then it will transfer all the files in the specified directory. +#define VMPI_DEPENDENCY_DIRECTORY_TOKEN '*' + + +// It's good to specify a disconnect handler here immediately. If you don't have a handler +// and the master disconnects, you'll lockup forever inside a dispatch loop because you +// never handled the master disconnecting. +// +// Note: runMode is only relevant for the VMPI master. The worker always connects to the master +// the same way. +bool VMPI_Init( + int &argc, + char **&argv, + const char *pDependencyFilename, + VMPI_Disconnect_Handler handler = NULL, + VMPIRunMode runMode = VMPI_RUN_NETWORKED, // Networked or local?, + bool bConnectingAsService = false + ); + +// Used when hosting a patch. +void VMPI_Init_PatchMaster( int argc, char **argv ); + +void VMPI_Finalize(); + +VMPIRunMode VMPI_GetRunMode(); +VMPIFileSystemMode VMPI_GetFileSystemMode(); + +// Note: this number can change on the master. +int VMPI_GetCurrentNumberOfConnections(); + + +// Dispatch messages until it gets one with the specified packet ID. +// If subPacketID is not set to -1, then the second byte must match that as well. +// +// Note: this WILL dispatch packets with matching packet IDs and give them a chance to handle packets first. +// +// If bWait is true, then this function either succeeds or Error() is called. If it's false, then if the first available message +// is handled by a dispatch, this function returns false. +bool VMPI_DispatchUntil( MessageBuffer *pBuf, int *pSource, int packetID, int subPacketID = -1, bool bWait = true ); + +// This waits for the next message and dispatches it. +// You can specify a timeout in milliseconds. If the timeout expires, the function returns false. +bool VMPI_DispatchNextMessage( unsigned long timeout=VMPI_TIMEOUT_INFINITE ); + +// This should be called periodically in modal loops that don't call other VMPI functions. This will +// check for disconnected sockets and call disconnect handlers so the app can error out if +// it loses all of its connections. +// +// This can be used in place of a Sleep() call by specifying a timeout value. +void VMPI_HandleSocketErrors( unsigned long timeout=0 ); + + + +enum VMPISendFlags +{ + k_eVMPISendFlags_GroupPackets = 0x0001 +}; + +// Use these to send data to one of the machines. +// If iDest is VMPI_SEND_TO_ALL, then the message goes to all the machines. +// Flags is a combination of the VMPISendFlags enums. +bool VMPI_SendData( void *pData, int nBytes, int iDest, int fVMPISendFlags=0 ); +bool VMPI_SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks, int iDest, int fVMPISendFlags=0 ); +bool VMPI_Send2Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, int iDest, int fVMPISendFlags=0 ); // for convenience.. +bool VMPI_Send3Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, const void *pChunk3, int chunk3Len, int iDest, int fVMPISendFlags=0 ); + +// Flush any groups that were queued with k_eVMPISendFlags_GroupPackets. +// If msInterval is > 0, then it will check a timer and only flush that often (so you can call this a lot, and have it check). +void VMPI_FlushGroupedPackets( unsigned long msInterval=0 ); + +// This registers a function that gets called when a connection is terminated ungracefully. +void VMPI_AddDisconnectHandler( VMPI_Disconnect_Handler handler ); + +// Returns false if the process has disconnected ungracefully (disconnect handlers +// would have been called for it too). +bool VMPI_IsProcConnected( int procID ); + +// Returns true if the process is just a service (in which case it should only get file IO traffic). +bool VMPI_IsProcAService( int procID ); + +// Simple wrapper for Sleep() so people can avoid including windows.h +void VMPI_Sleep( unsigned long ms ); + +// VMPI sends machine names around first thing. +const char* VMPI_GetLocalMachineName(); +const char* VMPI_GetMachineName( int iProc ); +bool VMPI_HasMachineNameBeenSet( int iProc ); + +// Returns 0xFFFFFFFF if the ID hasn't been set. +unsigned long VMPI_GetJobWorkerID( int iProc ); +void VMPI_SetJobWorkerID( int iProc, unsigned long jobWorkerID ); + +// Search a command line to find arguments. Looks for pName, and if it finds it, returns the +// argument following it. If pName is the last argument, it returns pDefault. If it doesn't +// find pName, returns NULL. +const char* VMPI_FindArg( int argc, char **argv, const char *pName, const char *pDefault = "" ); + +// (Threadsafe) get and set the current stage. This info winds up in the VMPI database. +void VMPI_GetCurrentStage( char *pOut, int strLen ); +void VMPI_SetCurrentStage( const char *pCurStage ); + +// VMPI is always broadcasting this job in the background. +// This changes the password to 'debugworker' and allows more workers in. +// This can be used if workers are dying on certain work units. Then a programmer +// can run vmpi_service with -superdebug and debug the whole thing. +void VMPI_InviteDebugWorkers(); + +bool VMPI_IsSDKMode(); + +// Lookup a command line parameter string. +const char* VMPI_GetParamString( EVMPICmdLineParam eParam ); +int VMPI_GetParamFlags( EVMPICmdLineParam eParam ); +const char* VMPI_GetParamHelpString( EVMPICmdLineParam eParam ); +bool VMPI_IsParamUsed( EVMPICmdLineParam eParam ); // Returns true if the specified parameter is on the command line. + +// Can be called from error handlers and if -mpi_Restart is used, it'll automatically restart the process. +bool VMPI_HandleAutoRestart(); + + +#endif // VMPI_H diff --git a/utils/vmpi/vmpi.vpc b/utils/vmpi/vmpi.vpc new file mode 100644 index 0000000..2d8fcd5 --- /dev/null +++ b/utils/vmpi/vmpi.vpc @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// VMPI.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,zlib,.\" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;MPI" + } +} + +$Project "Vmpi" +{ + $Folder "Source Files" + { + $File "$SRCDIR\public\filesystem_init.cpp" + $File "..\common\filesystem_tools.cpp" + $File "iphelpers.cpp" + $File "loopback_channel.cpp" + $File "messbuf.cpp" + $File "ThreadedTCPSocket.cpp" + $File "ThreadedTCPSocketEmu.cpp" + $File "threadhelpers.cpp" + $File "vmpi.cpp" + $File "vmpi_distribute_tracker.cpp" + $File "vmpi_distribute_work.cpp" + $File "vmpi_distribute_work_sdk.cpp" + $File "vmpi_distribute_work_default.cpp" + $File "vmpi_filesystem.cpp" + $File "vmpi_filesystem_internal.h" + $File "vmpi_filesystem_master.cpp" + $File "vmpi_filesystem_worker.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier1\bitbuf.h" + $File "ichannel.h" + $File "iphelpers.h" + $File "IThreadedTCPSocket.h" + $File "loopback_channel.h" + $File "messbuf.h" + $File "tcpsocket.h" + $File "ThreadedTCPSocketEmu.h" + $File "threadhelpers.h" + $File "vmpi.h" + $File "vmpi_defs.h" + $File "vmpi_filesystem.h" + } + + $Folder "Link Libraries" + { + $File "ZLib.lib" + } +} diff --git a/utils/vmpi/vmpi_browser_helpers.cpp b/utils/vmpi/vmpi_browser_helpers.cpp new file mode 100644 index 0000000..fed826f --- /dev/null +++ b/utils/vmpi/vmpi_browser_helpers.cpp @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier1/strtools.h" +#include "vmpi_browser_helpers.h" + + + +void FormatTimeString( unsigned long nInputSeconds, char *timeStr, int outLen ) +{ + // Make a string to say how long the thing has been running. + unsigned long minutes = nInputSeconds / 60; + unsigned long nSeconds = nInputSeconds - minutes * 60; + + unsigned long hours = minutes / 60; + minutes -= hours * 60; + + unsigned long days = hours / 24; + hours -= days * 24; + + if ( days && hours ) + { + Q_snprintf( timeStr, outLen, "%dd %dh %dm", days, hours, minutes ); + } + else if ( hours ) + { + Q_snprintf( timeStr, outLen, "%dh %dm", hours, minutes ); + } + else if ( minutes ) + { + Q_snprintf( timeStr, outLen, "%dm %ds", minutes, nSeconds ); + } + else + { + Q_snprintf( timeStr, outLen, "%d seconds", nSeconds ); + } +} + + diff --git a/utils/vmpi/vmpi_browser_helpers.h b/utils/vmpi/vmpi_browser_helpers.h new file mode 100644 index 0000000..ecc0daa --- /dev/null +++ b/utils/vmpi/vmpi_browser_helpers.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_BROWSER_HELPERS_H +#define VMPI_BROWSER_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +void FormatTimeString( unsigned long nSeconds, char *pOut, int outLen ); + + +#endif // VMPI_BROWSER_HELPERS_H diff --git a/utils/vmpi/vmpi_defs.h b/utils/vmpi/vmpi_defs.h new file mode 100644 index 0000000..7d65508 --- /dev/null +++ b/utils/vmpi/vmpi_defs.h @@ -0,0 +1,147 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DEFS_H +#define VMPI_DEFS_H +#ifdef _WIN32 +#pragma once +#endif + + +// This goes in front of all packets. +#define VMPI_PROTOCOL_VERSION 5 + +// This represents the protocol between the service and its UI. +#define VMPI_SERVICE_UI_PROTOCOL_VERSION 1 + +// NOTE: the service version (embedded in vmpi_service.exe as a resource) is the version +// that is used to apply patches. +#define VMPI_SERVICE_IDS_VERSION_STRING 102 // This matches IDS_VERSION_STRING in vmpi_service.exe. + +// Known packet IDs in various systems. +#define VMPI_PACKETID_FILESYSTEM 0 // The file system reserves this packet ID. + // All application traffic must set its first byte to something other + // than this value. +#define VMPI_SHARED_PACKET_ID 10 + + +// Turn this on, and the various service apps will log stuff. +//#define VMPI_SERVICE_LOGS + + +// This value is put in the RunningTimeMS until the job is finished. This is how +// the job_search app knows if a job never finished. +#define RUNNINGTIME_MS_SENTINEL 0xFEDCBAFD + + + +#define VMPI_SERVICE_NAME_INTERNAL "VMPI" +#define VMPI_SERVICE_NAME "Valve MPI Service" + +// Stuff in the registry goes under here (in HKEY_LOCAL_MACHINE). +#define VMPI_SERVICE_KEY "Software\\Valve\\VMPI" +#define SERVICE_INSTALL_LOCATION_KEY "InstallLocation" + +// The VMPI service listens on one of these ports to talk to the UI. +#define VMPI_SERVICE_FIRST_UI_PORT 23300 +#define VMPI_SERVICE_LAST_UI_PORT 23310 + +// Port numbers that the master will use to broadcast unless -mpi_port is used. +#define VMPI_MASTER_FIRST_PORT 23311 +#define VMPI_MASTER_LAST_PORT 23330 + + +// Packet IDs for vmpi_service to talk to UI clients. +#define VMPI_SERVICE_TO_UI_CONSOLE_TEXT 0 // Print some text to the UI's console. +#define VMPI_SERVICE_TO_UI_STATE 1 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_PATCHING 2 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_EXIT 3 // Updates state reflecting whether it's idle, busy, etc. + + // Application state.. these are communicated between the service and the UI. + enum + { + VMPI_SERVICE_STATE_IDLE=0, + VMPI_SERVICE_STATE_BUSY, + VMPI_SERVICE_STATE_DISABLED + }; +#define VMPI_SERVICE_DISABLE 2 // Stop waiting for jobs.. +#define VMPI_SERVICE_ENABLE 3 +#define VMPI_SERVICE_UPDATE_PASSWORD 4 // New password. +#define VMPI_SERVICE_EXIT 5 // User chose "exit" from the menu. Kill the service. +#define VMPI_SERVICE_SKIP_CSX_JOBS 6 +#define VMPI_SERVICE_SCREENSAVER_MODE 7 + + +// The worker service waits on this range of ports. +#define VMPI_SERVICE_PORT 23397 +#define VMPI_LAST_SERVICE_PORT (VMPI_SERVICE_PORT + 15) + + +#define VMPI_WORKER_PORT_FIRST 22340 +#define VMPI_WORKER_PORT_LAST 22350 + +// The VMPI service downloader is still a worker but it uses this port range so the +// master knows it's just downloading the exes. +#define VMPI_SERVICE_DOWNLOADER_PORT_FIRST 22351 +#define VMPI_SERVICE_DOWNLOADER_PORT_LAST 22360 + +// Give it a small range so they can have multiple masters running. +#define VMPI_MASTER_PORT_FIRST 21140 +#define VMPI_MASTER_PORT_LAST 21145 +#define VMPI_MASTER_FILESYSTEM_BROADCAST_PORT 21146 + + + + +// Protocol. + +// The message format is: +// - VMPI_PROTOCOL_VERSION +// - null-terminated password string (or VMPI_PASSWORD_OVERRIDE followed by a zero to process it regardless of pw). +// - packet ID +// - payload + + +#define VMPI_PASSWORD_OVERRIDE -111 + + +#define VMPI_MESSAGE_BASE 71 + + +// This is the broadcast message from the main (rank 0) process looking for workers. +#define VMPI_LOOKING_FOR_WORKERS (VMPI_MESSAGE_BASE+0) + +// This is so an app can find out what machines are running the service. +#define VMPI_PING_REQUEST (VMPI_MESSAGE_BASE+2) +#define VMPI_PING_RESPONSE (VMPI_MESSAGE_BASE+3) + +// This tells the service to quit. +#define VMPI_STOP_SERVICE (VMPI_MESSAGE_BASE+6) + +// This tells the service to kill any process it has running. +#define VMPI_KILL_PROCESS (VMPI_MESSAGE_BASE+7) + +// This tells the service to patch itself. +#define VMPI_SERVICE_PATCH (VMPI_MESSAGE_BASE+8) + +// Sent back to the master via UDP to tell it if its job has started and ended. +#define VMPI_NOTIFY_START_STATUS (VMPI_MESSAGE_BASE+9) +#define VMPI_NOTIFY_END_STATUS (VMPI_MESSAGE_BASE+10) + +#define VMPI_FORCE_PASSWORD_CHANGE (VMPI_MESSAGE_BASE+11) + + +// These states are sent from the service to the services browser. +#define VMPI_STATE_IDLE 0 +#define VMPI_STATE_BUSY 1 +#define VMPI_STATE_PATCHING 2 +#define VMPI_STATE_DISABLED 3 +#define VMPI_STATE_SCREENSAVER_DISABLED 4 +#define VMPI_STATE_DOWNLOADING 5 + + +#endif // VMPI_DEFS_H diff --git a/utils/vmpi/vmpi_dispatch.cpp b/utils/vmpi/vmpi_dispatch.cpp new file mode 100644 index 0000000..b41ec69 --- /dev/null +++ b/utils/vmpi/vmpi_dispatch.cpp @@ -0,0 +1,13 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vmpi_dispatch.h" +#include "tier0/dbg.h" +#include "mpi/mpi.h" +#include "vmpi.h" + + diff --git a/utils/vmpi/vmpi_dispatch.h b/utils/vmpi/vmpi_dispatch.h new file mode 100644 index 0000000..981d4a2 --- /dev/null +++ b/utils/vmpi/vmpi_dispatch.h @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DISPATCH_H +#define VMPI_DISPATCH_H +#ifdef _WIN32 +#pragma once +#endif + + +#endif // VMPI_DISPATCH_H diff --git a/utils/vmpi/vmpi_distribute_tracker.cpp b/utils/vmpi/vmpi_distribute_tracker.cpp new file mode 100644 index 0000000..9a112fd --- /dev/null +++ b/utils/vmpi/vmpi_distribute_tracker.cpp @@ -0,0 +1,579 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <windows.h> +#include <conio.h> +#include <io.h> +#include "vmpi.h" +#include "vmpi_distribute_work.h" +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "utlvector.h" +#include "utllinkedlist.h" + + +#define EVENT_TYPE_SEND_WORK_UNIT 0 +#define EVENT_TYPE_WU_STARTED 1 +#define EVENT_TYPE_WU_COMPLETED 2 + +class CWorkUnitEvent +{ +public: + int m_iEventType; // EVENT_TYPE_ define. + int m_iWorker; + double m_flTime; +}; + + +class CWorkUnit +{ +public: + CWorkUnit() + { + m_iWorkerCompleted = -1; + } + + int m_iWorkerCompleted; // Which worker completed this work unit (-1 if not done yet). + + CUtlVector<CWorkUnitEvent> m_Events; +}; + + +static CUtlVector<CWorkUnit> g_WorkUnits; +static double g_flJobStartTime; +static bool g_bTrackWorkUnitEvents = false; + + +static int CountActiveWorkUnits() +{ + int nActive = 0; + for ( int i=0; i < g_WorkUnits.Count(); i++ ) + { + if ( g_WorkUnits[i].m_iWorkerCompleted == -1 ) + ++nActive; + } + return nActive; +} + + +// ------------------------------------------------------------------------ // +// Graphical functions. +// ------------------------------------------------------------------------ // + +static bool g_bUseGraphics = false; +static HWND g_hWnd = 0; + +static int g_LastSizeX = 600, g_LastSizeY = 600; + +static COLORREF g_StateColors[] = +{ + RGB(50,50,50), + RGB(100,0,0), + RGB(150,0,0), + RGB(0,155,0), + RGB(0,255,0), + RGB(0,0,150), + RGB(0,0,250) +}; + +static HANDLE g_hCreateEvent = 0; +static HANDLE g_hDestroyWindowEvent = 0; +static HANDLE g_hDestroyWindowCompletedEvent = 0; + +static CRITICAL_SECTION g_CS; + +class CWUStatus +{ +public: + CWUStatus() + { + m_iState = 0; + memset( &m_Rect, 0, sizeof( m_Rect ) ); + } + + RECT m_Rect; + int m_iState; // 0 = not sent yet + // 1 = sent, 2 = sent recently + // 3 = done, 4 = done recently + // 5 = started, 6 = started recently + float m_flTransitionTime; +}; +CUtlVector<CWUStatus> g_WUStatus; +int g_nChanges = 0; +int g_nLastDrawnChanges = -1; + +static LRESULT CALLBACK TrackerWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + switch( uMsg ) + { + case WM_PAINT: + { + // Do one pass for each color.. + HBRUSH hStateColors[ARRAYSIZE( g_StateColors )]; + for ( int i=0; i < ARRAYSIZE( hStateColors ); i++ ) + hStateColors[i] = CreateSolidBrush( g_StateColors[i] ); + + // Copy the WU statuses. + CUtlVector<CWUStatus> wuStatus; + EnterCriticalSection( &g_CS ); + + g_nLastDrawnChanges = g_nChanges; + wuStatus.SetSize( g_WUStatus.Count() ); + memcpy( wuStatus.Base(), g_WUStatus.Base(), wuStatus.Count() * sizeof( wuStatus[0] ) ); + + LeaveCriticalSection( &g_CS ); + + PAINTSTRUCT ps; + HDC hDC = BeginPaint( hwnd, &ps ); + for ( int iState=0; iState < ARRAYSIZE( hStateColors ); iState++ ) + { + HGDIOBJ hOldObj = SelectObject( hDC, hStateColors[iState] ); + + for ( int iWU=0; iWU < wuStatus.Count(); iWU++ ) + { + if ( wuStatus[iWU].m_iState != iState ) + continue; + + RECT &rc = wuStatus[iWU].m_Rect; + Rectangle( hDC, rc.left, rc.top, rc.right, rc.bottom ); + } + + SelectObject( hDC, hOldObj ); + DeleteObject( hStateColors[iState] ); + } + EndPaint( hwnd, &ps ); + } + break; + + case WM_SIZE: + { + int width = LOWORD( lParam ); + int height = HIWORD( lParam ); + + g_LastSizeX = width; + g_LastSizeY = height; + + // Figure out the rectangles for everything. + int nWorkUnits = g_WUStatus.Count(); + + // What is the max width of the grid elements so they will fit in the width and height. + int testSize; + for ( testSize=20; testSize > 1; testSize-- ) + { + int nX = width / testSize; + int nY = height / testSize; + if ( nX * nY >= nWorkUnits ) + break; + } + static int minTestSize = 3; + testSize = max( testSize, minTestSize ); + + int xPos=0, yPos=0; + for ( int i=0; i < nWorkUnits; i++ ) + { + g_WUStatus[i].m_Rect.left = xPos; + g_WUStatus[i].m_Rect.top = yPos; + g_WUStatus[i].m_Rect.right = xPos + testSize; + g_WUStatus[i].m_Rect.bottom = yPos + testSize; + + xPos += testSize; + if ( (xPos+testSize) > width ) + { + yPos += testSize; + xPos = 0; + } + } + } + break; + } + + return DefWindowProc( hwnd, uMsg, wParam, lParam ); +} + +static void CheckFlashTimers() +{ + double flCurTime = Plat_FloatTime(); + + EnterCriticalSection( &g_CS ); + + // Check timers for the events that just happened (we show them in a brighter color if they just occurred). + for ( int iWU=0; iWU < g_WUStatus.Count(); iWU++ ) + { + CWUStatus &s = g_WUStatus[iWU]; + + if ( s.m_iState == 2 || s.m_iState == 4 || s.m_iState == 6 ) + { + if ( flCurTime > s.m_flTransitionTime ) + { + s.m_iState -= 1; + ++g_nChanges; + } + } + } + + LeaveCriticalSection( &g_CS ); +} + +static DWORD WINAPI ThreadProc( LPVOID lpParameter ) +{ + // Create the window. + const char *pClassName = "VMPI_Tracker"; + + // Register the application + WNDCLASSEX WndClsEx; + WndClsEx.cbSize = sizeof(WNDCLASSEX); + WndClsEx.style = CS_HREDRAW | CS_VREDRAW; + WndClsEx.lpfnWndProc = TrackerWindowProc; + WndClsEx.cbClsExtra = 0; + WndClsEx.cbWndExtra = 0; + WndClsEx.hIcon = NULL; + WndClsEx.hCursor = LoadCursor(NULL, IDC_ARROW); + WndClsEx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + WndClsEx.lpszMenuName = NULL; + WndClsEx.lpszClassName = pClassName; + WndClsEx.hInstance = (HINSTANCE)GetCurrentProcess(); + WndClsEx.hIconSm = NULL; + RegisterClassEx(&WndClsEx); + + // Create the window. + g_hWnd = CreateWindow( + pClassName, + "VMPI Tracker", + WS_OVERLAPPEDWINDOW, + 0, 0, g_LastSizeX, g_LastSizeY, + NULL, NULL, + (HINSTANCE)GetCurrentProcess(), + NULL ); + + ShowWindow( g_hWnd, SW_SHOW ); + + // Tell the main thread we're ready. + SetEvent( g_hCreateEvent ); + + // Run our main loop. + while ( WaitForSingleObject( g_hDestroyWindowEvent, 200 ) != WAIT_OBJECT_0 ) + { + MSG msg; + while ( PeekMessage( &msg, g_hWnd, 0, 0, PM_REMOVE ) ) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + CheckFlashTimers(); + + if ( g_nChanges != g_nLastDrawnChanges ) + InvalidateRect( g_hWnd, NULL, FALSE ); + } + + // Tell the main thread we're done. + SetEvent( g_hDestroyWindowCompletedEvent ); + return 0; +} + +static void Graphical_Start() +{ + g_bUseGraphics = VMPI_IsParamUsed( mpi_Graphics ); + if ( !g_bUseGraphics ) + return; + + // Setup an event so we'll wait until the window is ready. + if ( !g_hCreateEvent ) + { + g_hCreateEvent = CreateEvent( 0, 0, 0, 0 ); + g_hDestroyWindowEvent = CreateEvent( 0, 0, 0, 0 ); + g_hDestroyWindowCompletedEvent = CreateEvent( 0, 0, 0, 0 ); + InitializeCriticalSection( &g_CS ); + } + ResetEvent( g_hCreateEvent ); + ResetEvent( g_hDestroyWindowCompletedEvent ); + + g_WUStatus.SetSize( g_WorkUnits.Count() ); + for ( int i=0; i < g_WUStatus.Count(); i++ ) + g_WUStatus[i].m_iState = 0; + + // Setup our thread. + CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL ); + + // Wait until the event is signaled. + WaitForSingleObject( g_hCreateEvent, INFINITE ); +} + +static void Graphical_WorkUnitSentToWorker( int iWorkUnit ) +{ + if ( !g_bUseGraphics ) + return; + + EnterCriticalSection( &g_CS ); + + CWUStatus &s = g_WUStatus[iWorkUnit]; + if ( s.m_iState != 3 && s.m_iState != 4 && s.m_iState != 5 && s.m_iState != 6 ) + { + s.m_iState = 2; + s.m_flTransitionTime = Plat_FloatTime() + 0.1f; + ++g_nChanges; + } + + LeaveCriticalSection( &g_CS ); +} + +static void Graphical_WorkUnitStarted( int iWorkUnit ) +{ + if ( !g_bUseGraphics ) + return; + + EnterCriticalSection( &g_CS ); + + if ( g_WUStatus[iWorkUnit].m_iState != 3 && g_WUStatus[iWorkUnit].m_iState != 4 ) + { + g_WUStatus[iWorkUnit].m_iState = 6; + g_WUStatus[iWorkUnit].m_flTransitionTime = Plat_FloatTime() + 0.1f; + ++g_nChanges; + } + + LeaveCriticalSection( &g_CS ); +} + +static void Graphical_WorkUnitCompleted( int iWorkUnit ) +{ + if ( !g_bUseGraphics ) + return; + + EnterCriticalSection( &g_CS ); + g_WUStatus[iWorkUnit].m_iState = 4; + g_WUStatus[iWorkUnit].m_flTransitionTime = Plat_FloatTime() + 0.1f; + ++g_nChanges; + LeaveCriticalSection( &g_CS ); +} + +static void Graphical_End() +{ + if ( !g_bUseGraphics ) + return; + + SetEvent( g_hDestroyWindowEvent ); + WaitForSingleObject( g_hDestroyWindowCompletedEvent, INFINITE ); +} + + +// ------------------------------------------------------------------------ // +// Interface functions. +// ------------------------------------------------------------------------ // + +void VMPITracker_Start( int nWorkUnits ) +{ + g_bTrackWorkUnitEvents = (VMPI_IsParamUsed( mpi_TrackEvents ) || VMPI_IsParamUsed( mpi_Graphics )); + g_flJobStartTime = Plat_FloatTime(); + g_WorkUnits.Purge(); + + if ( g_bTrackWorkUnitEvents ) + { + g_WorkUnits.SetSize( nWorkUnits ); + } + + Graphical_Start(); +} + + +void VMPITracker_WorkUnitSentToWorker( int iWorkUnit, int iWorker ) +{ + if ( g_bTrackWorkUnitEvents ) + { + CWorkUnitEvent event; + event.m_iEventType = EVENT_TYPE_SEND_WORK_UNIT; + event.m_iWorker = iWorker; + event.m_flTime = Plat_FloatTime(); + g_WorkUnits[iWorkUnit].m_Events.AddToTail( event ); + } + + Graphical_WorkUnitSentToWorker( iWorkUnit ); +} + + +void VMPITracker_WorkUnitStarted( int iWorkUnit, int iWorker ) +{ + if ( g_bTrackWorkUnitEvents ) + { + CWorkUnitEvent event; + event.m_iEventType = EVENT_TYPE_WU_STARTED; + event.m_iWorker = iWorker; + event.m_flTime = Plat_FloatTime(); + g_WorkUnits[iWorkUnit].m_Events.AddToTail( event ); + } + + Graphical_WorkUnitStarted( iWorkUnit ); +} + + +void VMPITracker_WorkUnitCompleted( int iWorkUnit, int iWorker ) +{ + if ( g_bTrackWorkUnitEvents ) + { + CWorkUnitEvent event; + event.m_iEventType = EVENT_TYPE_WU_COMPLETED; + event.m_iWorker = iWorker; + event.m_flTime = Plat_FloatTime(); + g_WorkUnits[iWorkUnit].m_Events.AddToTail( event ); + g_WorkUnits[iWorkUnit].m_iWorkerCompleted = iWorker; + } + + Graphical_WorkUnitCompleted( iWorkUnit ); +} + + +void VMPITracker_End() +{ + g_WorkUnits.Purge(); + Graphical_End(); +} + + +bool VMPITracker_WriteDebugFile( const char *pFilename ) +{ + FILE *fp = fopen( pFilename, "wt" ); + if ( fp ) + { + fprintf( fp, "# work units: %d\n", g_WorkUnits.Count() ); + fprintf( fp, "# active work units: %d\n", CountActiveWorkUnits() ); + + fprintf( fp, "\n" ); + fprintf( fp, "--- Events ---" ); + fprintf( fp, "\n" ); + fprintf( fp, "\n" ); + + for ( int i=0; i < g_WorkUnits.Count(); i++ ) + { + CWorkUnit *wu = &g_WorkUnits[i]; + + if ( wu->m_iWorkerCompleted != -1 ) + continue; + + fprintf( fp, " work unit %d\n", i ); + fprintf( fp, "\n" ); + + if ( wu->m_Events.Count() == 0 ) + { + fprintf( fp, " *no events*\n" ); + } + else + { + for ( int iEvent=0; iEvent < wu->m_Events.Count(); iEvent++ ) + { + CWorkUnitEvent *pEvent = &wu->m_Events[iEvent]; + + if ( pEvent->m_iEventType == EVENT_TYPE_WU_STARTED ) + { + fprintf( fp, " started (by worker %s) %.1f seconds ago\n", + VMPI_GetMachineName( wu->m_Events[iEvent].m_iWorker ), + Plat_FloatTime() - wu->m_Events[iEvent].m_flTime ); + } + else if ( pEvent->m_iEventType == EVENT_TYPE_SEND_WORK_UNIT ) + { + fprintf( fp, " sent (to worker %s) %.1f seconds ago\n", + VMPI_GetMachineName( wu->m_Events[iEvent].m_iWorker ), + Plat_FloatTime() - wu->m_Events[iEvent].m_flTime ); + } + else if ( pEvent->m_iEventType == EVENT_TYPE_WU_COMPLETED ) + { + fprintf( fp, " completed (by worker %s) %.1f seconds ago\n", + VMPI_GetMachineName( wu->m_Events[iEvent].m_iWorker ), + Plat_FloatTime() - wu->m_Events[iEvent].m_flTime ); + } + } + } + fprintf( fp, "\n" ); + } + + fclose( fp ); + return true; + } + else + { + return false; + } +} + + +void VMPITracker_HandleDebugKeypresses() +{ + if ( !g_bTrackWorkUnitEvents ) + return; + + if ( !kbhit() ) + return; + + static int iState = 0; + + int key = toupper( getch() ); + if ( iState == 0 ) + { + if ( key == 'D' ) + { + iState = 1; + Warning("\n\n" + "----------------------\n" + "1. Write debug file (ascending filenames).\n" + "2. Write debug file (c:\\vmpi_tracker_0.txt).\n" + "3. Invite debug workers (password: 'debugworker').\n" + "\n" + "0. Exit menu.\n" + "----------------------\n" + "\n" + ); + } + } + else if ( iState == 1 ) + { + if ( key == '1' ) + { + iState = 0; + + int nMaxTries = 128; + char filename[512]; + int iFile = 1; + for ( iFile; iFile < nMaxTries; iFile++ ) + { + Q_snprintf( filename, sizeof( filename ), "c:\\vmpi_tracker_%d.txt", iFile ); + if ( _access( filename, 0 ) != 0 ) + break; + } + if ( iFile == nMaxTries ) + { + Warning( "** Please delete c:\\vmpi_tracker_*.txt and try again.\n" ); + } + else + { + if ( VMPITracker_WriteDebugFile( filename ) ) + Warning( "Wrote %s successfully.\n", filename ); + else + Warning( "Failed to write %s successfully.\n", filename ); + } + } + else if ( key == '2' ) + { + iState = 0; + + const char *filename = "c:\\vmpi_tracker_0.txt"; + if ( VMPITracker_WriteDebugFile( filename ) ) + Warning( "Wrote %s successfully.\n", filename ); + else + Warning( "Failed to write %s successfully.\n", filename ); + } + else if ( key == '3' ) + { + iState = 0; + Warning( "\nInviting debug workers with password 'debugworker'...\nGo ahead and connect them.\n" ); + VMPI_InviteDebugWorkers(); + } + else if ( key == '0' ) + { + iState = 0; + Warning( "\n\nExited menu.\n\n" ); + } + } +} + + diff --git a/utils/vmpi/vmpi_distribute_tracker.h b/utils/vmpi/vmpi_distribute_tracker.h new file mode 100644 index 0000000..98135cb --- /dev/null +++ b/utils/vmpi/vmpi_distribute_tracker.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: vmpi_distribute_work sends events to this module, and this module +// can track the events or display them graphically for debugging. +// +//=============================================================================// + +#ifndef VMPI_DISTRIBUTE_TRACKER_H +#define VMPI_DISTRIBUTE_TRACKER_H +#ifdef _WIN32 +#pragma once +#endif + +// If bDebugMode is set, then it will remember all the VMPI events +// in case you call VMPITracker_WriteDebugFile. +void VMPITracker_Start( int nWorkUnits ); + +void VMPITracker_WorkUnitSentToWorker( int iWorkUnit, int iWorker ); +void VMPITracker_WorkUnitStarted( int iWorkUnit, int iWorker ); +void VMPITracker_WorkUnitCompleted( int iWorkUnit, int iWorker ); +void VMPITracker_End(); + +// This will bring up a little menu they can use to +// write a debug file. +void VMPITracker_HandleDebugKeypresses(); + +bool VMPITracker_WriteDebugFile( const char *pFilename ); + + +#endif // VMPI_DISTRIBUTE_TRACKER_H + + diff --git a/utils/vmpi/vmpi_distribute_work.cpp b/utils/vmpi/vmpi_distribute_work.cpp new file mode 100644 index 0000000..f28e07e --- /dev/null +++ b/utils/vmpi/vmpi_distribute_work.cpp @@ -0,0 +1,628 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <windows.h> +#include "vmpi.h" +#include "vmpi_distribute_work.h" +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "vmpi_dispatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "mathlib/mathlib.h" +#include "threadhelpers.h" +#include "threads.h" +#include "tier1/strtools.h" +#include "tier1/utlmap.h" +#include "tier1/smartptr.h" +#include "tier0/icommandline.h" +#include "cmdlib.h" +#include "vmpi_distribute_tracker.h" +#include "vmpi_distribute_work_internal.h" + + +// To catch some bugs with 32-bit vs 64-bit and etc. +#pragma warning( default : 4244 ) +#pragma warning( default : 4305 ) +#pragma warning( default : 4267 ) +#pragma warning( default : 4311 ) +#pragma warning( default : 4312 ) + + +const int MAX_DW_CALLS = 255; +extern bool g_bSetThreadPriorities; + + +// Subpacket IDs owned by DistributeWork. +#define DW_SUBPACKETID_MASTER_READY 0 +#define DW_SUBPACKETID_WORKER_READY 1 +#define DW_SUBPACKETID_MASTER_FINISHED 2 +#define DW_SUBPACKETID_WU_RESULTS 4 +#define DW_SUBPACKETID_WU_STARTED 6 // A worker telling the master it has started processing a work unit. +// NOTE VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE is where the IWorkUnitDistributorX classes start their subpackets. + + +IWorkUnitDistributorCallbacks *g_pDistributeWorkCallbacks = NULL; + + +static CDSInfo g_DSInfo; +static unsigned short g_iCurDSInfo = (unsigned short)-1; // This is incremented each time DistributeWork is called. +static int g_iMasterFinishedDistributeWorkCall = -1; // The worker stores this to know which DistributeWork() calls the master has finished. +static int g_iMasterReadyForDistributeWorkCall = -1; + +// This is only valid if we're a worker and if the worker currently has threads chewing on work units. +static CDSInfo *g_pCurWorkerThreadsInfo = NULL; + +static CUtlVector<uint64> g_wuCountByProcess; +static uint64 g_totalWUCountByProcess[512]; + +static uint64 g_nWUs; // How many work units there were this time around. +static uint64 g_nCompletedWUs; // How many work units completed. +static uint64 g_nDuplicatedWUs; // How many times a worker sent results for a work unit that was already completed. + +// Set to true if Error() is called and we want to exit early. vrad and vvis check for this in their +// thread functions, so the workers quit early when the master is done rather than finishing up +// potentially time-consuming work units they're working on. +bool g_bVMPIEarlyExit = false; + +static bool g_bMasterDistributingWork = false; + +static IWorkUnitDistributorWorker *g_pCurDistributorWorker = NULL; +static IWorkUnitDistributorMaster *g_pCurDistributorMaster = NULL; + +// For the stats database. +WUIndexType g_ThreadWUs[4] = { ~0ull, ~0ull, ~0ull, ~0ull }; + +class CMasterWorkUnitCompletedList +{ +public: + CUtlVector<WUIndexType> m_CompletedWUs; +}; +static CCriticalSectionData<CMasterWorkUnitCompletedList> g_MasterWorkUnitCompletedList; + + +int SortByWUCount( const void *elem1, const void *elem2 ) +{ + uint64 a = g_wuCountByProcess[ *((const int*)elem1) ]; + uint64 b = g_wuCountByProcess[ *((const int*)elem2) ]; + if ( a < b ) + return 1; + else if ( a == b ) + return 0; + else + return -1; +} + + +void PrepareDistributeWorkHeader( MessageBuffer *pBuf, unsigned char cSubpacketID ) +{ + char cPacketID[2] = { g_DSInfo.m_cPacketID, cSubpacketID }; + pBuf->write( cPacketID, 2 ); + pBuf->write( &g_iCurDSInfo, sizeof( g_iCurDSInfo ) ); +} + + +void ShowMPIStats( + double flTimeSpent, + unsigned long nBytesSent, + unsigned long nBytesReceived, + unsigned long nMessagesSent, + unsigned long nMessagesReceived ) +{ + double flKSent = (nBytesSent + 511) / 1024; + double flKRecv = (nBytesReceived + 511) / 1024; + + bool bShowOutput = VMPI_IsParamUsed( mpi_ShowDistributeWorkStats ); + + bool bOldSuppress = g_bSuppressPrintfOutput; + g_bSuppressPrintfOutput = !bShowOutput; + + Msg( "\n\n--------------------------------------------------------------\n"); + Msg( "Total Time : %.2f\n", flTimeSpent ); + Msg( "Total Bytes Sent : %dk (%.2fk/sec, %d messages)\n", (int)flKSent, flKSent / flTimeSpent, nMessagesSent ); + Msg( "Total Bytes Recv : %dk (%.2fk/sec, %d messages)\n", (int)flKRecv, flKRecv / flTimeSpent, nMessagesReceived ); + if ( g_bMPIMaster ) + { + Msg( "Duplicated WUs : %I64u (%.1f%%)\n", g_nDuplicatedWUs, (float)g_nDuplicatedWUs * 100.0f / g_nWUs ); + + Msg( "\nWU count by proc:\n" ); + + int nProcs = VMPI_GetCurrentNumberOfConnections(); + + CUtlVector<int> sortedProcs; + sortedProcs.SetSize( nProcs ); + for ( int i=0; i < nProcs; i++ ) + sortedProcs[i] = i; + + qsort( sortedProcs.Base(), nProcs, sizeof( int ), SortByWUCount ); + + for ( int i=0; i < nProcs; i++ ) + { + const char *pMachineName = VMPI_GetMachineName( sortedProcs[i] ); + Msg( "%s", pMachineName ); + + char formatStr[512]; + Q_snprintf( formatStr, sizeof( formatStr ), "%%%ds %I64u\n", 30 - strlen( pMachineName ), g_wuCountByProcess[ sortedProcs[i] ] ); + Msg( formatStr, ":" ); + } + } + Msg( "--------------------------------------------------------------\n\n "); + + g_bSuppressPrintfOutput = bOldSuppress; +} + + +void VMPI_DistributeWork_DisconnectHandler( int procID, const char *pReason ) +{ + if ( g_bMasterDistributingWork ) + { + // Show the disconnect in the database but not on the screen. + bool bOldSuppress = g_bSuppressPrintfOutput; + g_bSuppressPrintfOutput = true; + Msg( "VMPI_DistributeWork_DisconnectHandler( %d )\n", procID ); + g_bSuppressPrintfOutput = bOldSuppress; + + // Redistribute the WUs from this guy's partition to another worker. + g_pCurDistributorMaster->DisconnectHandler( procID ); + } +} + + +uint64 VMPI_GetNumWorkUnitsCompleted( int iProc ) +{ + Assert( iProc >= 0 && iProc <= ARRAYSIZE( g_totalWUCountByProcess ) ); + return g_totalWUCountByProcess[iProc]; +} + + +void HandleWorkUnitCompleted( CDSInfo *pInfo, int iSource, WUIndexType iWorkUnit, MessageBuffer *pBuf ) +{ + VMPITracker_WorkUnitCompleted( ( int ) iWorkUnit, iSource ); + + if ( g_pCurDistributorMaster->HandleWorkUnitResults( iWorkUnit ) ) + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "-" ); + + ++ g_nCompletedWUs; + ++ g_wuCountByProcess[iSource]; + ++ g_totalWUCountByProcess[iSource]; + + // Let the master process the incoming WU data. + if ( pBuf ) + { + pInfo->m_MasterInfo.m_ReceiveFn( iWorkUnit, pBuf, iSource ); + } + + UpdatePacifier( float( g_nCompletedWUs ) / pInfo->m_nWorkUnits ); + } + else + { + // Ignore it if we already got the results for this work unit. + ++ g_nDuplicatedWUs; + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "*" ); + } +} + + +bool DistributeWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + unsigned short iCurDistributeWorkCall = *((unsigned short*)&pBuf->data[2]); + if ( iCurDistributeWorkCall >= MAX_DW_CALLS ) + Error( "Got an invalid DistributeWork packet (id: %d, sub: %d) (iCurDW: %d).", pBuf->data[0], pBuf->data[1], iCurDistributeWorkCall ); + + CDSInfo *pInfo = &g_DSInfo; + + pBuf->setOffset( 4 ); + + switch ( pBuf->data[1] ) + { + case DW_SUBPACKETID_MASTER_READY: + { + g_iMasterReadyForDistributeWorkCall = iCurDistributeWorkCall; + return true; + } + + case DW_SUBPACKETID_WORKER_READY: + { + if ( iCurDistributeWorkCall > g_iCurDSInfo || !g_bMPIMaster ) + Error( "State incorrect on master for DW_SUBPACKETID_WORKER_READY packet from %s.", VMPI_GetMachineName( iSource ) ); + + if ( iCurDistributeWorkCall == g_iCurDSInfo ) + { + // Ok, give this guy some WUs. + if ( g_pCurDistributorMaster ) + g_pCurDistributorMaster->OnWorkerReady( iSource ); + } + + return true; + } + + case DW_SUBPACKETID_MASTER_FINISHED: + { + g_iMasterFinishedDistributeWorkCall = iCurDistributeWorkCall; + return true; + } + + // Worker sends this to tell the master it has started on a work unit. + case DW_SUBPACKETID_WU_STARTED: + { + if ( iCurDistributeWorkCall != g_iCurDSInfo ) + return true; + + WUIndexType iWU; + pBuf->read( &iWU, sizeof( iWU ) ); + VMPITracker_WorkUnitStarted( ( int ) iWU, iSource ); + return true; + } + + + case DW_SUBPACKETID_WU_RESULTS: + { + // We only care about work results for the iteration we're in. + if ( iCurDistributeWorkCall != g_iCurDSInfo ) + return true; + + WUIndexType iWorkUnit; + pBuf->read( &iWorkUnit, sizeof( iWorkUnit ) ); + if ( iWorkUnit >= pInfo->m_nWorkUnits ) + { + Error( "DistributeWork: got an invalid work unit index (%I64u for WU count of %I64u).", iWorkUnit, pInfo->m_nWorkUnits ); + } + + HandleWorkUnitCompleted( pInfo, iSource, iWorkUnit, pBuf ); + return true; + } + + default: + { + if ( g_pCurDistributorMaster ) + return g_pCurDistributorMaster->HandlePacket( pBuf, iSource, iCurDistributeWorkCall != g_iCurDSInfo ); + else if ( g_pCurDistributorWorker ) + return g_pCurDistributorWorker->HandlePacket( pBuf, iSource, iCurDistributeWorkCall != g_iCurDSInfo ); + else + return false; + } + } +} + + +EWorkUnitDistributor VMPI_GetActiveWorkUnitDistributor() +{ + if ( VMPI_IsParamUsed( mpi_UseSDKDistributor ) ) + { + Msg( "Found %s.\n", VMPI_GetParamString( mpi_UseSDKDistributor ) ); + return k_eWorkUnitDistributor_SDK; + } + else if ( VMPI_IsParamUsed( mpi_UseDefaultDistributor ) ) + { + Msg( "Found %s.\n", VMPI_GetParamString( mpi_UseDefaultDistributor ) ); + return k_eWorkUnitDistributor_Default; + } + else + { + if ( VMPI_IsSDKMode() ) + return k_eWorkUnitDistributor_SDK; + else + return k_eWorkUnitDistributor_Default; + } +} + + +void PreDistributeWorkSync( CDSInfo *pInfo ) +{ + if ( g_bMPIMaster ) + { + // Send a message telling all the workers we're ready to go on this DistributeWork call. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_MASTER_READY ); + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "PreDistributeWorkSync: waiting for master\n" ); + + // Wait for the master's message saying it's ready to go. + while ( g_iMasterReadyForDistributeWorkCall < g_iCurDSInfo ) + { + VMPI_DispatchNextMessage(); + } + + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "PreDistributeWorkSync: master ready\n" ); + + // Now tell the master we're ready. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_WORKER_READY ); + VMPI_SendData( mb.data, mb.getLen(), VMPI_MASTER_ID ); + } +} + + +void DistributeWork_Master( CDSInfo *pInfo, ProcessWorkUnitFn processFn, ReceiveWorkUnitFn receiveFn ) +{ + pInfo->m_WorkerInfo.m_pProcessFn = processFn; + pInfo->m_MasterInfo.m_ReceiveFn = receiveFn; + + VMPITracker_Start( (int) pInfo->m_nWorkUnits ); + + g_bMasterDistributingWork = true; + g_pCurDistributorMaster->DistributeWork_Master( pInfo ); + g_bMasterDistributingWork = false; + + VMPITracker_End(); + + // Tell all workers to move on. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_MASTER_FINISHED ); + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); + + // Clear the master's local completed work unit list. + CMasterWorkUnitCompletedList *pList = g_MasterWorkUnitCompletedList.Lock(); + pList->m_CompletedWUs.RemoveAll(); + g_MasterWorkUnitCompletedList.Unlock(); +} + + +void NotifyLocalMasterCompletedWorkUnit( WUIndexType iWorkUnit ) +{ + CMasterWorkUnitCompletedList *pList = g_MasterWorkUnitCompletedList.Lock(); + pList->m_CompletedWUs.AddToTail( iWorkUnit ); + g_MasterWorkUnitCompletedList.Unlock(); +} + +void CheckLocalMasterCompletedWorkUnits() +{ + CMasterWorkUnitCompletedList *pList = g_MasterWorkUnitCompletedList.Lock(); + + for ( int i=0; i < pList->m_CompletedWUs.Count(); i++ ) + { + HandleWorkUnitCompleted( &g_DSInfo, 0, pList->m_CompletedWUs[i], NULL ); + } + pList->m_CompletedWUs.RemoveAll(); + + g_MasterWorkUnitCompletedList.Unlock(); +} + + +void TellMasterThatWorkerStartedAWorkUnit( MessageBuffer &mb, CDSInfo *pInfo, WUIndexType iWU ) +{ + mb.setLen( 0 ); + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_WU_STARTED ); + mb.write( &iWU, sizeof( iWU ) ); + VMPI_SendData( mb.data, mb.getLen(), VMPI_MASTER_ID, k_eVMPISendFlags_GroupPackets ); +} + + +void VMPI_WorkerThread( int iThread, void *pUserData ) +{ + CDSInfo *pInfo = (CDSInfo*)pUserData; + CWorkerInfo *pWorkerInfo = &pInfo->m_WorkerInfo; + + + // Get our index for running work units + uint64 idxRunningWorkUnit = (uint64) iThread; + { + CCriticalSectionLock csLock( &pWorkerInfo->m_WorkUnitsRunningCS ); + csLock.Lock(); + pWorkerInfo->m_WorkUnitsRunning.ExpandWindow( idxRunningWorkUnit, ~0ull ); + csLock.Unlock(); + } + + + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_WU_RESULTS ); + + MessageBuffer mbStartedWorkUnit; // Special messagebuffer used to tell the master when we started a work unit. + + while ( g_iMasterFinishedDistributeWorkCall < g_iCurDSInfo && !g_bVMPIEarlyExit ) + { + WUIndexType iWU; + + // Quit out when there are no more work units. + if ( !g_pCurDistributorWorker->GetNextWorkUnit( &iWU ) ) + { + // Wait until there are some WUs to do. This should probably use event handles. + VMPI_Sleep( 10 ); + continue; + } + + CCriticalSectionLock csLock( &pWorkerInfo->m_WorkUnitsRunningCS ); + csLock.Lock(); + + // Check if this WU is not running + WUIndexType const *pBegin = &pWorkerInfo->m_WorkUnitsRunning.Get( 0ull ), *pEnd = pBegin + pWorkerInfo->m_WorkUnitsRunning.PastVisibleIndex(); + WUIndexType const *pRunningWu = GenericFind( pBegin, pEnd, iWU ); + if ( pRunningWu != pEnd ) + continue; + + // We are running it + pWorkerInfo->m_WorkUnitsRunning.Get( idxRunningWorkUnit ) = iWU; + + csLock.Unlock(); + + + // Process this WU and send the results to the master. + mb.setLen( 4 ); + mb.write( &iWU, sizeof( iWU ) ); + + // Set the current WU for the stats database. + if ( iThread >= 0 && iThread < 4 ) + { + g_ThreadWUs[iThread] = iWU; + } + + // Tell the master we're starting on this WU. + TellMasterThatWorkerStartedAWorkUnit( mbStartedWorkUnit, pInfo, iWU ); + + + pWorkerInfo->m_pProcessFn( iThread, iWU, &mb ); + g_pCurDistributorWorker->NoteLocalWorkUnitCompleted( iWU ); + + VMPI_SendData( mb.data, mb.getLen(), VMPI_MASTER_ID, /*k_eVMPISendFlags_GroupPackets*/0 ); + + // Flush grouped packets every once in a while. + //VMPI_FlushGroupedPackets( 1000 ); + } + + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "Worker thread exiting.\n" ); +} + + +void DistributeWork_Worker( CDSInfo *pInfo, ProcessWorkUnitFn processFn ) +{ + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "VMPI_DistributeWork call %d started.\n", g_iCurDSInfo+1 ); + + CWorkerInfo *pWorkerInfo = &pInfo->m_WorkerInfo; + pWorkerInfo->m_pProcessFn = processFn; + + g_pCurWorkerThreadsInfo = pInfo; + g_pCurDistributorWorker->Init( pInfo ); + + // Start a couple threads to do the work. + RunThreads_Start( VMPI_WorkerThread, pInfo, g_bSetThreadPriorities ? k_eRunThreadsPriority_Idle : k_eRunThreadsPriority_UseGlobalState ); + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "RunThreads_Start finished successfully.\n" ); + + if ( VMPI_IsSDKMode() ) + { + Msg( "\n" ); + while ( g_iMasterFinishedDistributeWorkCall < g_iCurDSInfo ) + { + VMPI_DispatchNextMessage( 300 ); + + Msg( "\rThreads status: " ); + for ( int i=0; i < ARRAYSIZE( g_ThreadWUs ); i++ ) + { + if ( g_ThreadWUs[i] != ~0ull ) + Msg( "%d: WU %5d ", i, (int)g_ThreadWUs[i] ); + } + + VMPI_FlushGroupedPackets(); + } + Msg( "\n" ); + } + else + { + while ( g_iMasterFinishedDistributeWorkCall < g_iCurDSInfo ) + { + VMPI_DispatchNextMessage(); + } + } + + + // Close the threads. + g_pCurWorkerThreadsInfo = NULL; + RunThreads_End(); + + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "VMPI_DistributeWork call %d finished.\n", g_iCurDSInfo+1 ); +} + + +// This is called by VMPI_Finalize in case it's shutting down due to an Error() call. +// In this case, it's important that the worker threads here are shut down before VMPI shuts +// down its sockets. +void DistributeWork_Cancel() +{ + if ( g_pCurWorkerThreadsInfo ) + { + Msg( "\nDistributeWork_Cancel saves the day!\n" ); + g_pCurWorkerThreadsInfo->m_bMasterFinished = true; + g_bVMPIEarlyExit = true; + RunThreads_End(); + } +} + + +// Returns time it took to finish the work. +double DistributeWork( + uint64 nWorkUnits, // how many work units to dole out + char cPacketID, + ProcessWorkUnitFn processFn, // workers implement this to process a work unit and send results back + ReceiveWorkUnitFn receiveFn // the master implements this to receive a work unit + ) +{ + ++g_iCurDSInfo; + + if ( g_iCurDSInfo == 0 ) + { + // Register our disconnect handler so we can deal with it if clients bail out. + if ( g_bMPIMaster ) + { + VMPI_AddDisconnectHandler( VMPI_DistributeWork_DisconnectHandler ); + } + } + else if ( g_iCurDSInfo >= MAX_DW_CALLS ) + { + Error( "DistributeWork: called more than %d times.\n", MAX_DW_CALLS ); + } + + CDSInfo *pInfo = &g_DSInfo; + + pInfo->m_cPacketID = cPacketID; + pInfo->m_nWorkUnits = nWorkUnits; + + // Make all the workers wait until the master is ready. + PreDistributeWorkSync( pInfo ); + + g_nWUs = nWorkUnits; + g_nCompletedWUs = 0ull; + g_nDuplicatedWUs = 0ull; + + // Setup stats info. + double flMPIStartTime = Plat_FloatTime(); + g_wuCountByProcess.SetCount( 512 ); + memset( g_wuCountByProcess.Base(), 0, sizeof( int ) * g_wuCountByProcess.Count() ); + + unsigned long nBytesSentStart = g_nBytesSent; + unsigned long nBytesReceivedStart = g_nBytesReceived; + unsigned long nMessagesSentStart = g_nMessagesSent; + unsigned long nMessagesReceivedStart = g_nMessagesReceived; + + EWorkUnitDistributor eWorkUnitDistributor = VMPI_GetActiveWorkUnitDistributor(); + if ( g_bMPIMaster ) + { + Assert( !g_pCurDistributorMaster ); + g_pCurDistributorMaster = ( eWorkUnitDistributor == k_eWorkUnitDistributor_SDK ? CreateWUDistributor_SDKMaster() : CreateWUDistributor_DefaultMaster() ); + + DistributeWork_Master( pInfo, processFn, receiveFn ); + + g_pCurDistributorMaster->Release(); + g_pCurDistributorMaster = NULL; + } + else + { + Assert( !g_pCurDistributorWorker ); + g_pCurDistributorWorker = ( eWorkUnitDistributor == k_eWorkUnitDistributor_SDK ? CreateWUDistributor_SDKWorker() : CreateWUDistributor_DefaultWorker() ); + + DistributeWork_Worker( pInfo, processFn ); + + g_pCurDistributorWorker->Release(); + g_pCurDistributorWorker = NULL; + } + + double flTimeSpent = Plat_FloatTime() - flMPIStartTime; + ShowMPIStats( + flTimeSpent, + g_nBytesSent - nBytesSentStart, + g_nBytesReceived - nBytesReceivedStart, + g_nMessagesSent - nMessagesSentStart, + g_nMessagesReceived - nMessagesReceivedStart + ); + + // Mark that the threads aren't working on anything at the moment. + for ( int i=0; i < ARRAYSIZE( g_ThreadWUs ); i++ ) + g_ThreadWUs[i] = ~0ull; + + return flTimeSpent; +} diff --git a/utils/vmpi/vmpi_distribute_work.h b/utils/vmpi/vmpi_distribute_work.h new file mode 100644 index 0000000..0435460 --- /dev/null +++ b/utils/vmpi/vmpi_distribute_work.h @@ -0,0 +1,89 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_DISTRIBUTE_WORK_H +#define VMPI_DISTRIBUTE_WORK_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "messbuf.h" +#include "utlvector.h" + + +class IWorkUnitDistributorCallbacks +{ +public: + // Called every 200ms or so as it does the work. + // Return true to stop distributing work. + virtual bool Update() { return false; } + + // Called when a subsequent number of work units is completed. + // e.g. results received in the following order will trigger + // the following calls to OnWorkUnitsCompleted: + // Work unit numbers: wu2 wu4 wu5 wu1 wu0 wu6 wu3 + // Calling OnWorkUnitsCompleted with arg: - - - - 3 - 7 + // because when wu0 is received we already have { wu0, wu1, wu2 } so we signal + // that 3 subsequent work units completed, like wise by the time when wu3 is + // received we already have a full set { wu0, wu1, wu2, wu3, wu4, wu5, wu6 } + // and signal that 7 work units completed. + virtual void OnWorkUnitsCompleted( uint64 numWorkUnits ) { return; } +}; + + +enum EWorkUnitDistributor +{ + k_eWorkUnitDistributor_Default, + k_eWorkUnitDistributor_SDK +}; + +// Tells which work unit distributor is going to be used. +EWorkUnitDistributor VMPI_GetActiveWorkUnitDistributor(); + + +// Before calling DistributeWork, you can set this and it'll call your virtual functions. +extern IWorkUnitDistributorCallbacks *g_pDistributeWorkCallbacks; + + +// You must append data to pBuf with the work unit results. +// Note: pBuf will be NULL if this is a local thread doing work on the master. +typedef void (*ProcessWorkUnitFn)( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ); + +// pBuf is ready to read the results written to the buffer in ProcessWorkUnitFn. +typedef void (*ReceiveWorkUnitFn)( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ); + + +// Use a CDispatchReg to register this function with whatever packet ID you give to DistributeWork. +bool DistributeWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ); + + + +// This is the function vrad and vvis use to divide the work units and send them out. +// It maintains a sliding window of work units so it can always keep the clients busy. +// +// The workers implement processFn to do the job work in a work unit. +// This function must send back a packet formatted with: +// cPacketID (char), cSubPacketID (char), iWorkUnit (int), (app-specific data for the results) +// +// The masters implement receiveFn to receive a work unit's results. +// +// Returns time it took to finish the work. +double DistributeWork( + uint64 nWorkUnits, // how many work units to dole out + char cPacketID, // This packet ID must be reserved for DistributeWork and DistributeWorkDispatch + // must be registered with it. + ProcessWorkUnitFn processFn, // workers implement this to process a work unit and send results back + ReceiveWorkUnitFn receiveFn // the master implements this to receive a work unit + ); + + +// VMPI calls this before shutting down because any threads that DistributeWork has running must stop, +// otherwise it can crash if a thread tries to send data in the middle of shutting down. +void DistributeWork_Cancel(); + + +#endif // VMPI_DISTRIBUTE_WORK_H diff --git a/utils/vmpi/vmpi_distribute_work_default.cpp b/utils/vmpi/vmpi_distribute_work_default.cpp new file mode 100644 index 0000000..4a0e2c6 --- /dev/null +++ b/utils/vmpi/vmpi_distribute_work_default.cpp @@ -0,0 +1,602 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vmpi.h" +#include "vmpi_distribute_work.h" +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "vmpi_dispatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "mathlib/mathlib.h" +#include "threadhelpers.h" +#include "threads.h" +#include "tier1/strtools.h" +#include "tier1/utlmap.h" +#include "tier1/smartptr.h" +#include "tier0/icommandline.h" +#include "cmdlib.h" +#include "vmpi_distribute_tracker.h" +#include "vmpi_distribute_work_internal.h" + + +#define DW_SUBPACKETID_WU_ASSIGNMENT (VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE+0) + + + +static int s_numWusToDeal = -1; + +void VMPI_SetWorkUnitsPartitionSize( int numWusToDeal ) +{ + s_numWusToDeal = numWusToDeal; +} + + +class CWorkUnitInfo +{ +public: + WUIndexType m_iWorkUnit; +}; + + +class CWULookupInfo +{ +public: + CWULookupInfo() : m_iWUInfo( -1 ), m_iPartition( -222222 ), m_iPartitionListIndex( -1 ) {} + +public: + int m_iWUInfo; // Index into m_WUInfo. + int m_iPartition; // Which partition it's in. + int m_iPartitionListIndex; // Index into its partition's m_WUs. +}; + + +class CPartitionInfo +{ +public: + typedef CUtlLinkedList< WUIndexType, int > PartitionWUs; + +public: + int m_iPartition; // Index into m_Partitions. + int m_iWorker; // Who owns this partition? + PartitionWUs m_WUs; // Which WUs are in this partition? +}; + + +// Work units tracker to track consecutive finished blocks +class CWorkUnitsTracker +{ +public: + CWorkUnitsTracker() {} + +public: + // Initializes the unit tracker to receive numUnits in future + void PrepareForWorkUnits( uint64 numUnits ); + // Signals that a work unit has been finished + // returns a zero-based index of the next pending work unit + // up to which the task list has been processed fully now + // because the received work unit filled the gap or was the next pending work unit. + // returns 0 to indicate that this work unit is a "faster processed future work unit". + uint64 WorkUnitFinished( uint64 iWorkUnit ); + +public: + enum WUInfo { kNone, kTrigger, kDone }; + CVisibleWindowVector< uint8 > m_arrInfo; +}; + +void CWorkUnitsTracker::PrepareForWorkUnits( uint64 numUnits ) +{ + m_arrInfo.Reset( numUnits + 1 ); + + if ( numUnits ) + { + m_arrInfo.ExpandWindow( 2ull, kNone ); + m_arrInfo.Get( 0ull ) = kTrigger; + } +} + +uint64 CWorkUnitsTracker::WorkUnitFinished( uint64 iWorkUnit ) +{ + uint64 uiResult = uint64( 0 ); + + if ( iWorkUnit >= m_arrInfo.FirstPossibleIndex() && iWorkUnit < m_arrInfo.PastPossibleIndex() ) + { + // Need to access the element + m_arrInfo.ExpandWindow( iWorkUnit + 1, kNone ); + + // Set it done + uint8 &rchThere = m_arrInfo.Get( iWorkUnit ), chThere = rchThere; + rchThere = kDone; + + // Should we trigger? + if ( kTrigger == chThere ) + { + // Go along all "done" work units and trigger the last found one + while ( ( ( ++ iWorkUnit ) < m_arrInfo.PastVisibleIndex() ) && + ( kDone == m_arrInfo.Get( iWorkUnit ) ) ) + continue; + + m_arrInfo.Get( iWorkUnit ) = kTrigger; + m_arrInfo.ShrinkWindow( iWorkUnit - 1 ); + uiResult = iWorkUnit; + } + else if( iWorkUnit == m_arrInfo.FirstPossibleIndex() ) + { + // Go along all "done" work units and shrink including the last found one + while ( ( ( ++ iWorkUnit ) < m_arrInfo.PastVisibleIndex() ) && + ( kDone == m_arrInfo.Get( iWorkUnit ) ) ) + continue; + + m_arrInfo.ShrinkWindow( iWorkUnit - 1 ); + } + } + + return uiResult; +} + +CWorkUnitsTracker g_MasterWorkUnitsTracker; + + + +static bool CompareSoonestWorkUnitSets( CPartitionInfo::PartitionWUs * const &x, CPartitionInfo::PartitionWUs * const &y ) +{ + // Compare by fourth/second/first job in the partitions + WUIndexType missing = ~WUIndexType(0); + WUIndexType jobsX[4] = { missing, missing, missing, missing }; + WUIndexType jobsY[4] = { missing, missing, missing, missing }; + int counter = 0; + + counter = 0; + FOR_EACH_LL( (*x), i ) + { + jobsX[ counter ++ ] = (*x)[i]; + if ( counter >= 4 ) + break; + } + + counter = 0; + FOR_EACH_LL( (*y), i ) + { + jobsY[ counter ++ ] = (*y)[i]; + if ( counter >= 4 ) + break; + } + + // Compare + if ( jobsX[3] != jobsY[3] ) + return ( jobsX[3] < jobsY[3] ); + + if ( jobsX[1] != jobsY[1] ) + return ( jobsX[1] < jobsY[1] ); + + return jobsX[0] < jobsY[0]; +} + + + +class CDistributor_DefaultMaster : public IWorkUnitDistributorMaster +{ +public: + virtual void Release() + { + delete this; + } + + virtual void DistributeWork_Master( CDSInfo *pInfo ) + { + m_pInfo = pInfo; + g_MasterWorkUnitsTracker.PrepareForWorkUnits( m_pInfo->m_nWorkUnits ); + + m_WULookup.Reset( pInfo->m_nWorkUnits ); + while ( m_WULookup.FirstPossibleIndex() < m_WULookup.PastPossibleIndex() ) + { + VMPI_DispatchNextMessage( 200 ); + + VMPITracker_HandleDebugKeypresses(); + + if ( g_pDistributeWorkCallbacks && g_pDistributeWorkCallbacks->Update() ) + break; + } + } + + virtual void OnWorkerReady( int iSource ) + { + AssignWUsToWorker( iSource ); + } + + virtual bool HandleWorkUnitResults( WUIndexType iWorkUnit ) + { + CWULookupInfo *pLookup = NULL; + if ( iWorkUnit >= m_WULookup.FirstPossibleIndex() && iWorkUnit < m_WULookup.PastVisibleIndex() ) + pLookup = &m_WULookup.Get( iWorkUnit ); + + if ( !pLookup || pLookup->m_iWUInfo == -1 ) + return false; + + // Mark this WU finished and remove it from the list of pending WUs. + m_WUInfo.Remove( pLookup->m_iWUInfo ); + pLookup->m_iWUInfo = -1; + + + // Get rid of the WU from its partition. + int iPartition = pLookup->m_iPartition; + CPartitionInfo *pPartition = m_Partitions[iPartition]; + pPartition->m_WUs.Remove( pLookup->m_iPartitionListIndex ); + + // Shrink the window of the lookup work units + if ( iWorkUnit == m_WULookup.FirstPossibleIndex() ) + { + WUIndexType kwu = iWorkUnit; + for ( WUIndexType kwuEnd = m_WULookup.PastVisibleIndex(); kwu < kwuEnd; ++ kwu ) + { + if ( -1 != m_WULookup.Get( kwu ).m_iWUInfo && kwu > iWorkUnit ) + break; + } + m_WULookup.ShrinkWindow( kwu - 1 ); + } + + // Give the worker some new work if need be. + if ( pPartition->m_WUs.Count() == 0 ) + { + int iPartitionWorker = pPartition->m_iWorker; + delete pPartition; + m_Partitions.Remove( iPartition ); + + // If there are any more WUs remaining, give the worker from this partition some more of them. + if ( m_WULookup.FirstPossibleIndex() < m_WULookup.PastPossibleIndex() ) + { + AssignWUsToWorker( iPartitionWorker ); + } + } + + uint64 iDoneWorkUnits = g_MasterWorkUnitsTracker.WorkUnitFinished( iWorkUnit ); + if ( iDoneWorkUnits && g_pDistributeWorkCallbacks ) + { + g_pDistributeWorkCallbacks->OnWorkUnitsCompleted( iDoneWorkUnits ); + } + + return true; + } + + virtual void DisconnectHandler( int workerID ) + { + int iPartitionLookup = FindPartitionByWorker( workerID ); + if ( iPartitionLookup != -1 ) + { + // Mark this guy's partition as unowned so another worker can get it. + CPartitionInfo *pPartition = m_Partitions[iPartitionLookup]; + pPartition->m_iWorker = -1; + } + } + + CPartitionInfo* AddPartition( int iWorker ) + { + CPartitionInfo *pNew = new CPartitionInfo; + pNew->m_iPartition = m_Partitions.AddToTail( pNew ); + pNew->m_iWorker = iWorker; + return pNew; + } + + bool SplitWUsPartition( CPartitionInfo *pPartitionLarge, + CPartitionInfo **ppFirstHalf, CPartitionInfo **ppSecondHalf, + int iFirstHalfWorker, int iSecondHalfWorker ) + { + int nCount = pPartitionLarge->m_WUs.Count(); + + if ( nCount > 1 ) // Allocate the partitions for the two workers + { + *ppFirstHalf = AddPartition( iFirstHalfWorker ); + *ppSecondHalf = AddPartition( iSecondHalfWorker ); + } + else // Specially transfer a partition with too few work units + { + *ppFirstHalf = NULL; + *ppSecondHalf = AddPartition( iSecondHalfWorker ); + } + + // Prepare for transfer + CPartitionInfo *arrNewParts[2] = { *ppFirstHalf ? *ppFirstHalf : *ppSecondHalf, *ppSecondHalf }; + + // Transfer the work units: + // alternate first/second halves + // don't put more than "half deal units" tasks into the second half + // e.g. { 1, 2, 3, 4 } + // becomes: 1st half { 1, 2 }, 2nd half { 3, 4 } + for ( int k = 0; k < nCount; ++ k ) + { + int iHead = pPartitionLarge->m_WUs.Head(); + WUIndexType iWU = pPartitionLarge->m_WUs[ iHead ]; + pPartitionLarge->m_WUs.Remove( iHead ); + + /* + int nHalf = !!( ( k % 2 ) || ( k >= nCount - 1 ) ); + if ( k == 5 ) // no more than 2 jobs to branch off + arrNewParts[ 1 ] = arrNewParts[ 0 ]; + */ + int nHalf = !( k < nCount/2 ); + CPartitionInfo *pTo = arrNewParts[ nHalf ]; + + CWULookupInfo &li = m_WULookup.Get( iWU ); + li.m_iPartition = pTo->m_iPartition; + li.m_iPartitionListIndex = pTo->m_WUs.AddToTail( iWU ); + } + + // LogPartitionsWorkUnits( pInfo ); + return true; + } + + void AssignWUsToWorker( int iWorker ) + { + // Get rid of this worker's old partition. + int iPrevious = FindPartitionByWorker( iWorker ); + if ( iPrevious != -1 ) + { + delete m_Partitions[iPrevious]; + m_Partitions.Remove( iPrevious ); + } + + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "A" ); + + + CVisibleWindowVector< CWULookupInfo > &vlkup = m_WULookup; + if ( CommandLine()->FindParm( "-mpi_NoScheduler" ) ) + { + Warning( "\n\n-mpi_NoScheduler found: Warning - this should only be used for testing and with 1 worker!\n\n" ); + vlkup.ExpandWindow( m_pInfo->m_nWorkUnits ); + CPartitionInfo *pPartition = AddPartition( iWorker ); + for ( int i=0; i < m_pInfo->m_nWorkUnits; i++ ) + { + CWorkUnitInfo info; + info.m_iWorkUnit = i; + + CWULookupInfo &li = vlkup.Get( i ); + li.m_iPartition = pPartition->m_iPartition; + li.m_iPartitionListIndex = pPartition->m_WUs.AddToTail( i ); + li.m_iWUInfo = m_WUInfo.AddToTail( info ); + } + + SendPartitionToWorker( pPartition, iWorker ); + return; + } + + + // Any partitions abandoned by workers? + int iAbandonedPartition = FindPartitionByWorker( -1 ); + if ( -1 != iAbandonedPartition ) + { + CPartitionInfo *pPartition = m_Partitions[ iAbandonedPartition ]; + pPartition->m_iWorker = iWorker; + SendPartitionToWorker( pPartition, iWorker ); + } + + // Any absolutely untouched partitions yet? + else if ( vlkup.PastVisibleIndex() < vlkup.PastPossibleIndex() ) + { + // Figure out how many WUs to include in a batch + int numWusToDeal = s_numWusToDeal; + if ( numWusToDeal <= 0 ) + { + uint64 uiFraction = vlkup.PastPossibleIndex() / g_nMaxWorkerCount; + Assert( uiFraction < INT_MAX/2 ); + + numWusToDeal = int( uiFraction ); + if ( numWusToDeal <= 0 ) + numWusToDeal = 8; + } + + // Allocate room for upcoming work units lookup + WUIndexType iBegin = vlkup.PastVisibleIndex(); + WUIndexType iEnd = min( iBegin + g_nMaxWorkerCount * numWusToDeal, vlkup.PastPossibleIndex() ); + vlkup.ExpandWindow( iEnd - 1 ); + + // Allocate a partition + size_t numPartitions = min( ( size_t )(iEnd - iBegin), ( size_t )g_nMaxWorkerCount ); + CArrayAutoPtr< CPartitionInfo * > spArrPartitions( new CPartitionInfo* [ numPartitions ] ); + CPartitionInfo **arrPartitions = spArrPartitions.Get(); + + arrPartitions[0] = AddPartition( iWorker ); + for ( size_t k = 1; k < numPartitions; ++ k ) + arrPartitions[k] = AddPartition( -1 ); + + // Assign upcoming work units to the partitions. + for ( WUIndexType i = iBegin ; i < iEnd; ++ i ) + { + CWorkUnitInfo info; + info.m_iWorkUnit = i; + + CPartitionInfo *pPartition = arrPartitions[ size_t( (i - iBegin) % numPartitions ) ]; + + CWULookupInfo &li = vlkup.Get( i ); + li.m_iPartition = pPartition->m_iPartition; + li.m_iPartitionListIndex = pPartition->m_WUs.AddToTail( i ); + li.m_iWUInfo = m_WUInfo.AddToTail( info ); + } + + // Now send this guy the WU list in his partition. + SendPartitionToWorker( arrPartitions[0], iWorker ); + } + + // Split one of the last partitions to finish sooner + else + { + // Find a partition to split. + int iPartToSplit = FindSoonestPartition(); + if ( iPartToSplit >= 0 ) + { + CPartitionInfo *pPartition = m_Partitions[ iPartToSplit ]; + + CPartitionInfo *pOldHalf = NULL, *pNewHalf = NULL; + int iOldWorker = pPartition->m_iWorker, iNewWorker = iWorker; + if ( SplitWUsPartition( pPartition, &pOldHalf, &pNewHalf, iOldWorker, iNewWorker ) ) + { + if ( pOldHalf ) + SendPartitionToWorker( pOldHalf, iOldWorker ); + if ( pNewHalf ) + SendPartitionToWorker( pNewHalf, iNewWorker ); + + // Delete the partition that got split + Assert( pPartition->m_WUs.Count() == 0 ); + delete pPartition; + m_Partitions.Remove( iPartToSplit ); + } + } + } + } + + int FindSoonestPartition() + { + CUtlLinkedList < CPartitionInfo *, int > &lst = m_Partitions; + + // Sorted partitions + CUtlMap< CPartitionInfo::PartitionWUs *, int > sortedPartitions ( CompareSoonestWorkUnitSets ); + sortedPartitions.EnsureCapacity( lst.Count() ); + FOR_EACH_LL( lst, i ) + { + sortedPartitions.Insert( &lst[i]->m_WUs, i ); + } + + if ( sortedPartitions.Count() ) + { + return sortedPartitions.Element( sortedPartitions.FirstInorder() ); + } + + return lst.Head(); + } + + int FindPartitionByWorker( int iWorker ) + { + FOR_EACH_LL( m_Partitions, i ) + { + if ( m_Partitions[i]->m_iWorker == iWorker ) + return i; + } + return -1; + } + + void SendPartitionToWorker( CPartitionInfo *pPartition, int iWorker ) + { + // Stuff the next nWUs work units into the buffer. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_WU_ASSIGNMENT ); + + FOR_EACH_LL( pPartition->m_WUs, i ) + { + WUIndexType iWU = pPartition->m_WUs[i]; + mb.write( &iWU, sizeof( iWU ) ); + VMPITracker_WorkUnitSentToWorker( ( int ) iWU, iWorker ); + } + + VMPI_SendData( mb.data, mb.getLen(), iWorker ); + } + + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) + { + return false; + } + +private: + + CDSInfo *m_pInfo; + + CUtlLinkedList<CPartitionInfo*,int> m_Partitions; + CVisibleWindowVector<CWULookupInfo> m_WULookup; // Map work unit index to CWorkUnitInfo. + CUtlLinkedList<CWorkUnitInfo,int> m_WUInfo; // Sorted with most elegible WU at the head. +}; + + + +class CDistributor_DefaultWorker : public IWorkUnitDistributorWorker +{ +public: + virtual void Release() + { + delete this; + } + + virtual void Init( CDSInfo *pInfo ) + { + } + + virtual bool GetNextWorkUnit( WUIndexType *pWUIndex ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + // NOTE: this is called from INSIDE worker threads. + if ( m_WorkUnits.Count() == 0 ) + { + return false; + } + else + { + *pWUIndex = m_WorkUnits[ m_WorkUnits.Head() ]; + m_WorkUnits.Remove( m_WorkUnits.Head() ); + return true; + } + } + + virtual void NoteLocalWorkUnitCompleted( WUIndexType iWU ) + { + } + + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) + { + if ( pBuf->data[1] == DW_SUBPACKETID_WU_ASSIGNMENT ) + { + // If the message wasn't even related to the current DistributeWork() call we're on, ignore it. + if ( bIgnoreContents ) + return true; + + if ( ((pBuf->getLen() - pBuf->getOffset()) % sizeof( WUIndexType )) != 0 ) + { + Error( "DistributeWork: invalid work units packet from master" ); + } + + // Parse out the work unit indices. + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + m_WorkUnits.Purge(); + + int nIndices = (pBuf->getLen() - pBuf->getOffset()) / sizeof( WUIndexType ); + for ( int i=0; i < nIndices; i++ ) + { + WUIndexType iWU; + pBuf->read( &iWU, sizeof( iWU ) ); + + // Add the index to the list. + m_WorkUnits.AddToTail( iWU ); + } + + csLock.Unlock(); + + return true; + } + else + { + return false; + } + } + + // Threads eat up the list of WUs in here. + CCriticalSection m_CS; + CUtlLinkedList<WUIndexType, int> m_WorkUnits; // A list of work units assigned to this worker +}; + + + + +IWorkUnitDistributorMaster* CreateWUDistributor_DefaultMaster() +{ + return new CDistributor_DefaultMaster; +} +IWorkUnitDistributorWorker* CreateWUDistributor_DefaultWorker() +{ + return new CDistributor_DefaultWorker; +} diff --git a/utils/vmpi/vmpi_distribute_work_internal.h b/utils/vmpi/vmpi_distribute_work_internal.h new file mode 100644 index 0000000..74aa906 --- /dev/null +++ b/utils/vmpi/vmpi_distribute_work_internal.h @@ -0,0 +1,251 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef VMPI_DISTRIBUTE_WORK_INTERNAL_H +#define VMPI_DISTRIBUTE_WORK_INTERNAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#define VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE 50 + + +typedef uint64 WUIndexType; +class CDSInfo; +extern bool g_bVMPIEarlyExit; + + +// These classes are overridden to handle the details of communicating and scheduling work units. +class IWorkUnitDistributorMaster +{ +public: + virtual void Release() = 0; + + virtual void DistributeWork_Master( CDSInfo *pInfo ) = 0; + + virtual void OnWorkerReady( int iWorker ) = 0; + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) = 0; + + // Called when the results for a work unit arrive. This function must return false if + // we've already received the results for this work unit. + virtual bool HandleWorkUnitResults( WUIndexType iWorkUnit ) = 0; + + virtual void DisconnectHandler( int workerID ) = 0; +}; + +class IWorkUnitDistributorWorker +{ +public: + virtual void Release() = 0; + + virtual void Init( CDSInfo *pInfo ) = 0; + + // Called by worker threads to get the next work unit to do. + virtual bool GetNextWorkUnit( WUIndexType *pWUIndex ) = 0; + + // Called by the worker threads after a work unit is completed. + virtual void NoteLocalWorkUnitCompleted( WUIndexType iWU ) = 0; + + // Called by the main thread when a packet comes in. + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) = 0; +}; + + + +template < typename T, typename Derived > +class CVisibleWindowVectorT : protected CUtlVector < T > +{ + typedef CUtlVector< T > BaseClass; + +public: + CVisibleWindowVectorT() : m_uiBase( 0 ), m_uiTotal( 0 ) {} + +public: + inline void PostInitElement( uint64 uiRealIdx, T &newElement ) { /* do nothing */ return; } + inline void PostInitElement( uint64 uiRealIdx, T &newElement, T const &x ) { newElement = x; } + +public: + // Resets the content and makes size "uiTotal" + void Reset( uint64 uiTotal ) { + BaseClass::RemoveAll(); + BaseClass::EnsureCapacity( min( 100, (int)uiTotal ) ); + m_uiBase = 0; + m_uiTotal = uiTotal; + } + + // Gets the element from the window, otherwise NULL + T & Get( uint64 idx ) { + T *pElement = (( idx >= m_uiBase && idx < m_uiBase + BaseClass::Count() ) ? ( BaseClass::Base() + size_t( idx - m_uiBase ) ) : NULL ); + Assert( pElement ); + return *pElement; + } + + // Expands the window to see the element by its index + void ExpandWindow( uint64 idxAccessible ) { + Assert( idxAccessible >= m_uiBase ); + if ( idxAccessible >= m_uiBase + BaseClass::Count() ) { + int iOldCount = BaseClass::Count(); + int iAddElements = int(idxAccessible - m_uiBase) - iOldCount + 1; + Assert( iOldCount + iAddElements <= 100000000 /* really need 100 Mb at once? */ ); + BaseClass::AddMultipleToTail( iAddElements ); + for ( int iNewCount = BaseClass::Count(); iOldCount < iNewCount; ++ iOldCount ) + static_cast< Derived * >( this )->PostInitElement( m_uiBase + iOldCount, BaseClass::Element( iOldCount ) ); + } + } + + // Expands the window and initializes the new elements to a given value + void ExpandWindow( uint64 idxAccessible, T const& x ) { + Assert( idxAccessible >= m_uiBase ); + if ( idxAccessible >= m_uiBase + BaseClass::Count() ) { + int iOldCount = BaseClass::Count(); + int iAddElements = int(idxAccessible - m_uiBase) - iOldCount + 1; + Assert( unsigned(iAddElements) <= 50000000 /* growing 50 Mb at once? */ ); + BaseClass::AddMultipleToTail( iAddElements ); + for ( int iNewCount = BaseClass::Count(); iOldCount < iNewCount; ++ iOldCount ) + static_cast< Derived * >( this )->PostInitElement( m_uiBase + iOldCount, BaseClass::Element( iOldCount ), x ); + } + } + + // Shrinks the window to drop some of the elements from the head + void ShrinkWindow( uint64 idxDrop ) { + Assert( idxDrop >= m_uiBase && idxDrop <= m_uiBase + BaseClass::Count() ); + if ( idxDrop >= m_uiBase && idxDrop <= m_uiBase + BaseClass::Count() ) { + int iDropElements = int( idxDrop - m_uiBase ) + 1; + m_uiBase += iDropElements; + BaseClass::RemoveMultiple( 0, min( iDropElements, BaseClass::Count() ) ); + } + } + + // First possible index in this vector (only past dropped items) + uint64 FirstPossibleIndex() const { return m_uiBase; } + + // Last possible index in this vector + uint64 PastPossibleIndex() const { return m_uiTotal; } + // Past visible window index in this vector + uint64 PastVisibleIndex() const { return m_uiBase + BaseClass::Count(); } + +protected: + uint64 m_uiBase, m_uiTotal; +}; + +template < typename T > +class CVisibleWindowVector : public CVisibleWindowVectorT < T, CVisibleWindowVector< T > > +{ +public: + CVisibleWindowVector() {} +}; + + +template < typename T > +T const * GenericFind( T const *pBegin, T const *pEnd, T const &x ) +{ + for ( ; pBegin != pEnd; ++ pBegin ) + if ( *pBegin == x ) + break; + return pBegin; +} + + +template < typename T > +T * GenericFind( T *pBegin, T *pEnd, T const &x ) +{ + for ( ; pBegin != pEnd; ++ pBegin ) + if ( *pBegin == x ) + break; + return pBegin; +} + + +class CWorkerInfo +{ +public: + ProcessWorkUnitFn m_pProcessFn; + CVisibleWindowVector<WUIndexType> m_WorkUnitsRunning; // A list of work units currently running, index is the thread index + CCriticalSection m_WorkUnitsRunningCS; +}; + + +class CMasterInfo +{ +public: + + // Only used by the master. + ReceiveWorkUnitFn m_ReceiveFn; +}; + + +class CDSInfo +{ +public: + inline void WriteWUIndex( WUIndexType iWU, MessageBuffer *pBuf ) + { + if ( m_nWorkUnits <= 0xFFFF ) + { + Assert( iWU <= 0xFFFF ); + unsigned short val = (unsigned short)iWU; + pBuf->write( &val, sizeof( val ) ); + } + else if ( m_nWorkUnits <= 0xFFFFFFFF ) + { + Assert( iWU <= 0xFFFF ); + unsigned int val = (unsigned int)iWU; + pBuf->write( &val, sizeof( val ) ); + } + else + { + pBuf->write( &iWU, sizeof( iWU ) ); + } + } + + inline void ReadWUIndex( WUIndexType *pWU, MessageBuffer *pBuf ) + { + if ( m_nWorkUnits <= 0xFFFF ) + { + unsigned short val; + pBuf->read( &val, sizeof( val ) ); + *pWU = val; + } + else if ( m_nWorkUnits <= 0xFFFFFFFF ) + { + unsigned int val; + pBuf->read( &val, sizeof( val ) ); + *pWU = val; + } + else + { + pBuf->read( pWU, sizeof( *pWU ) ); + } + } + +public: + CWorkerInfo m_WorkerInfo; + CMasterInfo m_MasterInfo; + + bool m_bMasterReady; // Set to true when the master is ready to go. + bool m_bMasterFinished; + WUIndexType m_nWorkUnits; + char m_cPacketID; +}; + + +// Called to write the packet ID, the subpacket ID, and the ID of which DistributeWork() call we're at. +void PrepareDistributeWorkHeader( MessageBuffer *pBuf, unsigned char cSubpacketID ); + +// Called from threads on the master to process a completed work unit. +void NotifyLocalMasterCompletedWorkUnit( WUIndexType iWorkUnit ); +void CheckLocalMasterCompletedWorkUnits(); + + +// Creation functions for different distributors. +IWorkUnitDistributorMaster* CreateWUDistributor_DefaultMaster(); +IWorkUnitDistributorWorker* CreateWUDistributor_DefaultWorker(); + +IWorkUnitDistributorMaster* CreateWUDistributor_SDKMaster(); +IWorkUnitDistributorWorker* CreateWUDistributor_SDKWorker(); + + +#endif // VMPI_DISTRIBUTE_WORK_INTERNAL_H diff --git a/utils/vmpi/vmpi_distribute_work_sdk.cpp b/utils/vmpi/vmpi_distribute_work_sdk.cpp new file mode 100644 index 0000000..71b03e8 --- /dev/null +++ b/utils/vmpi/vmpi_distribute_work_sdk.cpp @@ -0,0 +1,699 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vmpi.h" +#include "vmpi_distribute_work.h" +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "vmpi_dispatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "mathlib/mathlib.h" +#include "threadhelpers.h" +#include "threads.h" +#include "tier1/strtools.h" +#include "tier1/utlmap.h" +#include "tier1/smartptr.h" +#include "tier0/icommandline.h" +#include "cmdlib.h" +#include "vmpi_distribute_tracker.h" +#include "vmpi_distribute_work_internal.h" + + + +#define DW_SUBPACKETID_SHUFFLE (VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE+0) +#define DW_SUBPACKETID_REQUEST_SHUFFLE (VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE+1) +#define DW_SUBPACKETID_WUS_COMPLETED_LIST (VMPI_DISTRIBUTE_WORK_EXTRA_SUBPACKET_BASE+2) + + + +// This is a pretty simple iterator. Basically, it holds a matrix of numbers. +// Each row is assigned to a worker, and the worker just walks through his row. +// +// When a worker reaches the end of his row, it gets a little trickier. +// They'll start doing their neighbor's row +// starting at the back and continue on. At about this time, the master should reshuffle the +// remaining work units to evenly distribute them amongst the workers. +class CWorkUnitWalker +{ +public: + CWorkUnitWalker() + { + m_nWorkUnits = 0; + } + + // This is all that's needed for it to start assigning work units. + void Init( WUIndexType matrixWidth, WUIndexType matrixHeight, WUIndexType nWorkUnits ) + { + m_nWorkUnits = nWorkUnits; + m_MatrixWidth = matrixWidth; + m_MatrixHeight = matrixHeight; + Assert( m_MatrixWidth * m_MatrixHeight >= nWorkUnits ); + + m_WorkerInfos.RemoveAll(); + m_WorkerInfos.EnsureCount( m_MatrixHeight ); + for ( int i=0; i < m_MatrixHeight; i++ ) + { + m_WorkerInfos[i].m_iStartWorkUnit = matrixWidth * i; + m_WorkerInfos[i].m_iWorkUnitOffset = 0; + } + } + + // This is the main function of the shuffler + bool GetNextWorkUnit( int iWorker, WUIndexType *pWUIndex, bool *bWorkerFinishedHisColumn ) + { + if ( iWorker < 0 || iWorker >= m_WorkerInfos.Count() ) + { + Assert( false ); + return false; + } + + // If this worker has walked through all the work units, then he's done. + CWorkerInfo *pWorker = &m_WorkerInfos[iWorker]; + if ( pWorker->m_iWorkUnitOffset >= m_nWorkUnits ) + return false; + + // If we've gone past the end of our work unit list, then we start at the BACK of the other rows of work units + // in the hopes that we won't collide with the guy working there. We also should tell the master to reshuffle. + WUIndexType iWorkUnitOffset = pWorker->m_iWorkUnitOffset; + if ( iWorkUnitOffset >= m_MatrixWidth ) + { + WUIndexType xOffset = iWorkUnitOffset % m_MatrixWidth; + WUIndexType yOffset = iWorkUnitOffset / m_MatrixWidth; + xOffset = m_MatrixWidth - xOffset - 1; + iWorkUnitOffset = yOffset * m_MatrixWidth + xOffset; + *bWorkerFinishedHisColumn = true; + } + else + { + *bWorkerFinishedHisColumn = false; + } + + *pWUIndex = (pWorker->m_iStartWorkUnit + iWorkUnitOffset) % m_nWorkUnits; + ++pWorker->m_iWorkUnitOffset; + return true; + } + + +private: + class CWorkerInfo + { + public: + WUIndexType m_iStartWorkUnit; + WUIndexType m_iWorkUnitOffset; // Which work unit in my list of work units am I working on? + }; + + WUIndexType m_nWorkUnits; + WUIndexType m_MatrixWidth; + WUIndexType m_MatrixHeight; + CUtlVector<CWorkerInfo> m_WorkerInfos; +}; + + +class IShuffleRequester +{ +public: + virtual void RequestShuffle() = 0; +}; + + +// This is updated every time the master decides to reshuffle. +// In-between shuffles, you can call NoteWorkUnitCompleted when a work unit is completed +// and it'll avoid returning that work unit from GetNextWorkUnit again, but it WON'T +class CShuffledWorkUnitWalker +{ +public: + void Init( WUIndexType nWorkUnits, IShuffleRequester *pRequester ) + { + m_iLastShuffleRequest = 0; + m_iCurShuffle = 1; + m_flLastShuffleTime = Plat_FloatTime(); + m_pShuffleRequester = pRequester; + + int nBytes = PAD_NUMBER( nWorkUnits, 8 ) / 8; + m_CompletedWUBits.SetSize( nBytes ); + m_LocalCompletedWUBits.SetSize( nBytes ); + for ( WUIndexType i=0; i < m_CompletedWUBits.Count(); i++ ) + m_LocalCompletedWUBits[i] = m_CompletedWUBits[i] = 0; + + // Setup our list of work units remaining. + for ( WUIndexType iWU=0; iWU < nWorkUnits; iWU++ ) + { + // Note: we're making an assumption here that if we add entries to a CUtlLinkedList in ascending order, their indices + // will be ascending 1-by-1 as well. If that assumption breaks, we can create an extra array here to map WU indices to the linked list indices. + WUIndexType index = m_WorkUnitsRemaining.AddToTail( iWU ); + if ( index != iWU ) + { + Error( "CShuffledWorkUnitWalker: assumption on CUtlLinkedList indexing failed.\n" ); + } + } + } + + void Shuffle( int nWorkers ) + { + if ( nWorkers == 0 ) + return; + + ++m_iCurShuffle; + m_flLastShuffleTime = Plat_FloatTime(); + + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + m_WorkUnitsMap.RemoveAll(); + m_WorkUnitsMap.EnsureCount( m_WorkUnitsRemaining.Count() ); + + // Here's the shuffle. The CWorkUnitWalker is going to walk each worker through its own group from 0-W, + // and our job is to interleave it so when worker 0 goes [0,1,2] and worker 1 goes [100,101,102], they're actually + // doing [0,N,2N] and [1,N+1,2N+1] where N=# of workers. + + // The grid is RxW long, and R*W is >= nWorkUnits. + // R = # units per worker = width of the matrix + // W = # workers = height of the matrix + WUIndexType matrixHeight = nWorkers; + WUIndexType matrixWidth = m_WorkUnitsRemaining.Count() / matrixHeight; + if ( (m_WorkUnitsRemaining.Count() % matrixHeight) != 0 ) + ++matrixWidth; + + Assert( matrixWidth * matrixHeight >= m_WorkUnitsRemaining.Count() ); + + WUIndexType iWorkUnit = 0; + FOR_EACH_LL( m_WorkUnitsRemaining, i ) + { + WUIndexType xCoord = iWorkUnit / matrixHeight; + WUIndexType yCoord = iWorkUnit % matrixHeight; + Assert( xCoord < matrixWidth ); + Assert( yCoord < matrixHeight ); + + m_WorkUnitsMap[yCoord*matrixWidth+xCoord] = m_WorkUnitsRemaining[i]; + ++iWorkUnit; + } + + m_Walker.Init( matrixWidth, matrixHeight, m_WorkUnitsRemaining.Count() ); + } + + // Threadsafe. + bool Thread_IsWorkUnitCompleted( WUIndexType iWU ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + byte val = m_CompletedWUBits[iWU >> 3] & (1 << (iWU & 7)); + return (val != 0); + } + + WUIndexType Thread_NumWorkUnitsRemaining() + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + return m_WorkUnitsRemaining.Count(); + } + + bool Thread_GetNextWorkUnit( int iWorker, WUIndexType *pWUIndex ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + while ( 1 ) + { + WUIndexType iUnmappedWorkUnit; + bool bWorkerFinishedHisColumn; + if ( !m_Walker.GetNextWorkUnit( iWorker, &iUnmappedWorkUnit, &bWorkerFinishedHisColumn ) ) + return false; + + // If we've done all the work units assigned to us in the last shuffle, then request a reshuffle. + if ( bWorkerFinishedHisColumn ) + HandleWorkerFinishedColumn(); + + // Check the pending list. + *pWUIndex = m_WorkUnitsMap[iUnmappedWorkUnit]; + byte bIsCompleted = m_CompletedWUBits[*pWUIndex >> 3] & (1 << (*pWUIndex & 7)); + byte bIsCompletedLocally = m_LocalCompletedWUBits[*pWUIndex >> 3] & (1 << (*pWUIndex & 7)); + if ( !bIsCompleted && !bIsCompletedLocally ) + return true; + } + } + + void HandleWorkerFinishedColumn() + { + if ( m_iLastShuffleRequest != m_iCurShuffle ) + { + double flCurTime = Plat_FloatTime(); + if ( flCurTime - m_flLastShuffleTime > 2.0f ) + { + m_pShuffleRequester->RequestShuffle(); + m_iLastShuffleRequest = m_iCurShuffle; + } + } + } + + void Thread_NoteWorkUnitCompleted( WUIndexType iWU ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + byte val = m_CompletedWUBits[iWU >> 3] & (1 << (iWU & 7)); + if ( val == 0 ) + { + m_WorkUnitsRemaining.Remove( iWU ); + m_CompletedWUBits[iWU >> 3] |= (1 << (iWU & 7)); + } + } + + void Thread_NoteLocalWorkUnitCompleted( WUIndexType iWU ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + m_LocalCompletedWUBits[iWU >> 3] |= (1 << (iWU & 7)); + } + + CRC32_t GetShuffleCRC() + { +#ifdef _DEBUG + static bool bCalcShuffleCRC = true; +#else + static bool bCalcShuffleCRC = VMPI_IsParamUsed( mpi_CalcShuffleCRC ); +#endif + if ( bCalcShuffleCRC ) + { + CCriticalSectionLock csLock( &m_CS ); + csLock.Lock(); + + CRC32_t ret; + CRC32_Init( &ret ); + + FOR_EACH_LL( m_WorkUnitsRemaining, i ) + { + WUIndexType iWorkUnit = m_WorkUnitsRemaining[i]; + CRC32_ProcessBuffer( &ret, &iWorkUnit, sizeof( iWorkUnit ) ); + } + + for ( int i=0; i < m_WorkUnitsMap.Count(); i++ ) + { + WUIndexType iWorkUnit = m_WorkUnitsMap[i]; + CRC32_ProcessBuffer( &ret, &iWorkUnit, sizeof( iWorkUnit ) ); + } + + CRC32_Final( &ret ); + return ret; + } + else + { + return false; + } + } + +private: + // These are PENDING WU completions until we call Shuffle() again, at which point we actually reorder the list + // based on the completed WUs. + CUtlVector<byte> m_CompletedWUBits; // Bit vector of completed WUs. + CUtlLinkedList<WUIndexType, WUIndexType> m_WorkUnitsRemaining; + CUtlVector<WUIndexType> m_WorkUnitsMap; // Maps the 0-N indices in the CWorkUnitWalker to the list of remaining work units. + + // Helps us avoid some duplicates that happen during shuffling if we've completed some WUs and sent them + // to the master, but the master hasn't included them in the DW_SUBPACKETID_WUS_COMPLETED_LIST yet. + CUtlVector<byte> m_LocalCompletedWUBits; // Bit vector of completed WUs. + + // Used to control how frequently we request a reshuffle. + unsigned int m_iCurShuffle; + unsigned int m_iLastShuffleRequest; // The index of the shuffle we last requested a reshuffle on (don't request a reshuffle on the same one). + double m_flLastShuffleTime; + IShuffleRequester *m_pShuffleRequester; + + CWorkUnitWalker m_Walker; + CCriticalSection m_CS; +}; + + + +class CDistributor_SDKMaster : public IWorkUnitDistributorMaster, public IShuffleRequester +{ +public: + virtual void Release() + { + delete this; + } + + static void Master_WorkerThread_Static( int iThread, void *pUserData ) + { + ((CDistributor_SDKMaster*)pUserData)->Master_WorkerThread( iThread ); + } + + void Master_WorkerThread( int iThread ) + { + while ( m_WorkUnitWalker.Thread_NumWorkUnitsRemaining() > 0 && !g_bVMPIEarlyExit ) + { + WUIndexType iWU; + if ( !m_WorkUnitWalker.Thread_GetNextWorkUnit( 0, &iWU ) ) + { + // Wait until there are some WUs to do. + VMPI_Sleep( 10 ); + continue; + } + + // Do this work unit. + m_WorkUnitWalker.Thread_NoteLocalWorkUnitCompleted( iWU ); // We do this before it's completed because otherwise if a Shuffle() occurs, + // the other thread might happen to pickup this work unit and we don't want that. + m_pInfo->m_WorkerInfo.m_pProcessFn( iThread, iWU, NULL ); + NotifyLocalMasterCompletedWorkUnit( iWU ); + } + } + + virtual void DistributeWork_Master( CDSInfo *pInfo ) + { + m_pInfo = pInfo; + m_bForceShuffle = false; + m_bShuffleRequested = false; + m_flLastShuffleRequestServiceTime = Plat_FloatTime(); + + // Spawn idle-priority worker threads right here. + m_bUsingMasterLocalThreads = (pInfo->m_WorkerInfo.m_pProcessFn != 0); + if ( VMPI_IsParamUsed( mpi_NoMasterWorkerThreads ) ) + { + Msg( "%s found. No worker threads will be created.\n", VMPI_GetParamString( mpi_NoMasterWorkerThreads ) ); + m_bUsingMasterLocalThreads = false; + } + m_WorkUnitWalker.Init( pInfo->m_nWorkUnits, this ); + Shuffle(); + + if ( m_bUsingMasterLocalThreads ) + RunThreads_Start( Master_WorkerThread_Static, this, k_eRunThreadsPriority_Idle ); + + uint64 lastShuffleTime = Plat_MSTime(); + while ( m_WorkUnitWalker.Thread_NumWorkUnitsRemaining() > 0 ) + { + VMPI_DispatchNextMessage( 200 ); + CheckLocalMasterCompletedWorkUnits(); + + VMPITracker_HandleDebugKeypresses(); + if ( g_pDistributeWorkCallbacks && g_pDistributeWorkCallbacks->Update() ) + break; + + // Reshuffle the work units optimally every certain interval. + if ( m_bForceShuffle || CheckShuffleRequest() ) + { + Shuffle(); + lastShuffleTime = Plat_MSTime(); + m_bForceShuffle = false; + } + } + + RunThreads_End(); + } + + virtual void RequestShuffle() + { + m_bShuffleRequested = true; + } + + bool CheckShuffleRequest() + { + if ( m_bShuffleRequested ) + { + double flCurTime = Plat_FloatTime(); + if ( flCurTime - m_flLastShuffleRequestServiceTime > 2.0f ) // Only handle shuffle requests every so often. + { + m_flLastShuffleRequestServiceTime = flCurTime; + m_bShuffleRequested = false; + return true; + } + } + + return false; + } + + void Shuffle() + { + // Build a list of who's working. + CUtlVector<unsigned short> whosWorking; + if ( m_bUsingMasterLocalThreads ) + { + whosWorking.AddToTail( VMPI_MASTER_ID ); + Assert( VMPI_MASTER_ID == 0 ); + } + + { + CWorkersReady *pWorkersReady = m_WorkersReadyCS.Lock(); + for ( int i=0; i < pWorkersReady->m_WorkersReady.Count(); i++ ) + { + int iWorker = pWorkersReady->m_WorkersReady[i]; + if ( VMPI_IsProcConnected( iWorker ) ) + whosWorking.AddToTail( iWorker ); + } + m_WorkersReadyCS.Unlock(); + } + + // Before sending the shuffle command, tell any of these active workers about the pending WUs completed. + CWUsCompleted *pWUsCompleted = m_WUsCompletedCS.Lock(); + + m_WUSCompletedMessageBuffer.setLen( 0 ); + if ( BuildWUsCompletedMessage( pWUsCompleted->m_Pending, m_WUSCompletedMessageBuffer ) > 0 ) + { + for ( int i=m_bUsingMasterLocalThreads; i < whosWorking.Count(); i++ ) + { + VMPI_SendData( m_WUSCompletedMessageBuffer.data, m_WUSCompletedMessageBuffer.getLen(), whosWorking[i] ); + } + } + pWUsCompleted->m_Completed.AddMultipleToTail( pWUsCompleted->m_Pending.Count(), pWUsCompleted->m_Pending.Base() ); // Add the pending ones to the full list now. + pWUsCompleted->m_Pending.RemoveAll(); + + m_WUsCompletedCS.Unlock(); + + // Shuffle ourselves. + m_WorkUnitWalker.Shuffle( whosWorking.Count() ); + + // Send the shuffle command to the workers. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_SHUFFLE ); + + unsigned short nWorkers = whosWorking.Count(); + mb.write( &nWorkers, sizeof( nWorkers ) ); + + CRC32_t shuffleCRC = m_WorkUnitWalker.GetShuffleCRC(); + mb.write( &shuffleCRC, sizeof( shuffleCRC ) ); + + // Now for each worker, assign him an index in the shuffle and send the shuffle command. + int workerIDPos = mb.getLen(); + unsigned short id = 0; + mb.write( &id, sizeof( id ) ); + for ( int i=m_bUsingMasterLocalThreads; i < whosWorking.Count(); i++ ) + { + id = (unsigned short)i; + mb.update( workerIDPos, &id, sizeof( id ) ); + VMPI_SendData( mb.data, mb.getLen(), whosWorking[i] ); + } + } + + int BuildWUsCompletedMessage( CUtlVector<WUIndexType> &wusCompleted, MessageBuffer &mb ) + { + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_WUS_COMPLETED_LIST ); + m_pInfo->WriteWUIndex( wusCompleted.Count(), &mb ); + for ( int i=0; i < wusCompleted.Count(); i++ ) + { + m_pInfo->WriteWUIndex( wusCompleted[i], &mb ); + } + return wusCompleted.Count(); + } + + virtual void OnWorkerReady( int iSource ) + { + CWorkersReady *pWorkersReady = m_WorkersReadyCS.Lock(); + if ( pWorkersReady->m_WorkersReady.Find( iSource ) == -1 ) + { + pWorkersReady->m_WorkersReady.AddToTail( iSource ); + + // Get this guy up to speed on which WUs are done. + { + CWUsCompleted *pWUsCompleted = m_WUsCompletedCS.Lock(); + m_WUSCompletedMessageBuffer.setLen( 0 ); + BuildWUsCompletedMessage( pWUsCompleted->m_Completed, m_WUSCompletedMessageBuffer ); + m_WUsCompletedCS.Unlock(); + } + + VMPI_SendData( m_WUSCompletedMessageBuffer.data, m_WUSCompletedMessageBuffer.getLen(), iSource ); + m_bForceShuffle = true; + } + m_WorkersReadyCS.Unlock(); + } + + virtual bool HandleWorkUnitResults( WUIndexType iWorkUnit ) + { + return Thread_HandleWorkUnitResults( iWorkUnit ); + } + + bool Thread_HandleWorkUnitResults( WUIndexType iWorkUnit ) + { + if ( m_WorkUnitWalker.Thread_IsWorkUnitCompleted( iWorkUnit ) ) + { + return false; + } + else + { + m_WorkUnitWalker.Thread_NoteWorkUnitCompleted( iWorkUnit ); + + // We need the lock on here because our own worker threads can call into here. + CWUsCompleted *pWUsCompleted = m_WUsCompletedCS.Lock(); + pWUsCompleted->m_Pending.AddToTail( iWorkUnit ); + m_WUsCompletedCS.Unlock(); + return true; + } + } + + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) + { + if ( pBuf->data[1] == DW_SUBPACKETID_REQUEST_SHUFFLE ) + { + if ( bIgnoreContents ) + return true; + + m_bShuffleRequested = true; + } + return false; + } + + virtual void DisconnectHandler( int workerID ) + { + CWorkersReady *pWorkersReady = m_WorkersReadyCS.Lock(); + + if ( pWorkersReady->m_WorkersReady.Find( workerID ) != -1 ) + m_bForceShuffle = true; + + m_WorkersReadyCS.Unlock(); + } + +public: + CDSInfo *m_pInfo; + + class CWorkersReady + { + public: + CUtlVector<int> m_WorkersReady; // The list of workers who have said they're ready to participate. + }; + CCriticalSectionData<CWorkersReady> m_WorkersReadyCS; + + class CWUsCompleted + { + public: + CUtlVector<WUIndexType> m_Completed; // WUs completed that we have sent to workers. + CUtlVector<WUIndexType> m_Pending; // WUs completed that we haven't sent to workers. + }; + CCriticalSectionData<CWUsCompleted> m_WUsCompletedCS; + MessageBuffer m_WUSCompletedMessageBuffer; // Used to send lists of completed WUs. + int m_bUsingMasterLocalThreads; + + bool m_bForceShuffle; + bool m_bShuffleRequested; + double m_flLastShuffleRequestServiceTime; + + CShuffledWorkUnitWalker m_WorkUnitWalker; +}; + + +class CDistributor_SDKWorker : public IWorkUnitDistributorWorker, public IShuffleRequester +{ +public: + virtual void Init( CDSInfo *pInfo ) + { + m_iMyWorkUnitWalkerID = -1; + m_pInfo = pInfo; + m_WorkUnitWalker.Init( pInfo->m_nWorkUnits, this ); + } + + virtual void Release() + { + delete this; + } + + virtual bool GetNextWorkUnit( WUIndexType *pWUIndex ) + { + // If we don't have an ID yet, we haven't received a Shuffle() command, so we're waiting for that before working. + // TODO: we could do some random WUs here while we're waiting, although that could suck if the WUs take forever to do + // and they're duplicates. + if ( m_iMyWorkUnitWalkerID == -1 ) + return false; + + // Look in our current shuffled list of work units for the next one. + return m_WorkUnitWalker.Thread_GetNextWorkUnit( m_iMyWorkUnitWalkerID, pWUIndex ); + } + + virtual void NoteLocalWorkUnitCompleted( WUIndexType iWU ) + { + m_WorkUnitWalker.Thread_NoteLocalWorkUnitCompleted( iWU ); + } + + virtual bool HandlePacket( MessageBuffer *pBuf, int iSource, bool bIgnoreContents ) + { + // If it's a SHUFFLE message, then shuffle.. + if ( pBuf->data[1] == DW_SUBPACKETID_SHUFFLE ) + { + if ( bIgnoreContents ) + return true; + + unsigned short nWorkers, myID; + CRC32_t shuffleCRC; + pBuf->read( &nWorkers, sizeof( nWorkers ) ); + pBuf->read( &shuffleCRC, sizeof( shuffleCRC ) ); + pBuf->read( &myID, sizeof( myID ) ); + m_iMyWorkUnitWalkerID = myID; + + m_WorkUnitWalker.Shuffle( nWorkers ); + if ( m_WorkUnitWalker.GetShuffleCRC() != shuffleCRC ) + { + static int nWarnings = 1; + if ( ++nWarnings <= 2 ) + Warning( "\nShuffle CRC mismatch\n" ); + } + return true; + } + else if ( pBuf->data[1] == DW_SUBPACKETID_WUS_COMPLETED_LIST ) + { + if ( bIgnoreContents ) + return true; + + WUIndexType nCompleted; + m_pInfo->ReadWUIndex( &nCompleted, pBuf ); + for ( WUIndexType i=0; i < nCompleted; i++ ) + { + WUIndexType iWU; + m_pInfo->ReadWUIndex( &iWU, pBuf ); + m_WorkUnitWalker.Thread_NoteWorkUnitCompleted( iWU ); + } + + return true; + } + + return false; + } + + virtual void RequestShuffle() + { + // Ok.. request a reshuffle. + MessageBuffer mb; + PrepareDistributeWorkHeader( &mb, DW_SUBPACKETID_REQUEST_SHUFFLE ); + VMPI_SendData( mb.data, mb.getLen(), VMPI_MASTER_ID ); + } + +private: + CDSInfo *m_pInfo; + CShuffledWorkUnitWalker m_WorkUnitWalker; + int m_iMyWorkUnitWalkerID; +}; + + + +IWorkUnitDistributorMaster* CreateWUDistributor_SDKMaster() +{ + return new CDistributor_SDKMaster; +} + +IWorkUnitDistributorWorker* CreateWUDistributor_SDKWorker() +{ + return new CDistributor_SDKWorker; +} + diff --git a/utils/vmpi/vmpi_filesystem.cpp b/utils/vmpi/vmpi_filesystem.cpp new file mode 100644 index 0000000..815f918 --- /dev/null +++ b/utils/vmpi/vmpi_filesystem.cpp @@ -0,0 +1,366 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vmpi_filesystem_internal.h" +#include "tier1/utlbuffer.h" + + +bool g_bDisableFileAccess = false; + + +CBaseVMPIFileSystem *g_pBaseVMPIFileSystem = NULL; +IFileSystem *g_pOriginalPassThruFileSystem = NULL; + +void* GetVMPIFileSystem() +{ + return (IBaseFileSystem*)g_pBaseVMPIFileSystem; +} + +EXPOSE_INTERFACE_FN( GetVMPIFileSystem, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION ) + + +IFileSystem* VMPI_FileSystem_Init( int maxMemoryUsage, IFileSystem *pPassThru ) +{ + Assert( g_bUseMPI ); + Assert( !g_pBaseVMPIFileSystem ); + g_pOriginalPassThruFileSystem = pPassThru; + + if ( g_bMPIMaster ) + { + extern CBaseVMPIFileSystem* CreateMasterVMPIFileSystem( int maxMemoryUsage, IFileSystem *pPassThru ); + CreateMasterVMPIFileSystem( maxMemoryUsage, pPassThru ); + } + else + { + extern CBaseVMPIFileSystem* CreateWorkerVMPIFileSystem(); + CreateWorkerVMPIFileSystem(); + } + + // The Create function should have set this. Normally, we'd set g_pBaseVMPIFileSystem right here, but + // the create functions may want to receive some messages, in which case they need to set g_pBaseVMPIFileSystem + // so the packets get routed appropriately. + Assert( g_pBaseVMPIFileSystem ); + return g_pBaseVMPIFileSystem; +} + + +IFileSystem* VMPI_FileSystem_Term() +{ + if ( g_pBaseVMPIFileSystem ) + { + g_pBaseVMPIFileSystem->Release(); + g_pBaseVMPIFileSystem = NULL; + + if ( g_iVMPIVerboseLevel >= 1 ) + { + if ( g_bMPIMaster ) + Msg( "Multicast send: %dk\n", (g_nMulticastBytesSent + 511) / 1024 ); + else + Msg( "Multicast recv: %dk\n", (g_nMulticastBytesReceived + 511) / 1024 ); + } + } + + IFileSystem *pRet = g_pOriginalPassThruFileSystem; + g_pOriginalPassThruFileSystem = NULL; + return pRet; +} + + +void VMPI_FileSystem_DisableFileAccess() +{ + g_bDisableFileAccess = true; +} + + +CreateInterfaceFn VMPI_FileSystem_GetFactory() +{ + return Sys_GetFactoryThis(); +} + + +void VMPI_FileSystem_CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ) +{ + g_pBaseVMPIFileSystem->CreateVirtualFile( pFilename, pData, fileLength ); +} + + +// Register our packet ID. +bool FileSystemRecv( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + if ( g_pBaseVMPIFileSystem ) + return g_pBaseVMPIFileSystem->HandleFileSystemPacket( pBuf, iSource, iPacketID ); + else + return false; +} + + +CDispatchReg g_DispatchReg_FileSystem( VMPI_PACKETID_FILESYSTEM, FileSystemRecv ); + + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CVMPIFile_Memory implementation. +// ------------------------------------------------------------------------------------------------------------------------ // + +void CVMPIFile_Memory::Init( const char *pData, long len, char chMode /* = 'b' */ ) +{ + m_pData = pData; + m_DataLen = len; + m_iCurPos = 0; + m_chMode = chMode; +} + +void CVMPIFile_Memory::Close() +{ + delete this; +} + +void CVMPIFile_Memory::Seek( int pos, FileSystemSeek_t seekType ) +{ + if ( seekType == FILESYSTEM_SEEK_HEAD ) + m_iCurPos = pos; + else if ( seekType == FILESYSTEM_SEEK_CURRENT ) + m_iCurPos += pos; + else + m_iCurPos = m_DataLen - pos; +} + +unsigned int CVMPIFile_Memory::Tell() +{ + return m_iCurPos; +} + +unsigned int CVMPIFile_Memory::Size() +{ + return m_DataLen; +} + +void CVMPIFile_Memory::Flush() +{ +} + +int CVMPIFile_Memory::Read( void* pOutput, int size ) +{ + Assert( m_iCurPos >= 0 ); + int nToRead = min( (int)(m_DataLen - m_iCurPos), size ); + + if ( m_chMode != 't' ) + { + memcpy( pOutput, &m_pData[m_iCurPos], nToRead ); + m_iCurPos += nToRead; + + return nToRead; + } + else + { + int iRead = 0; + const char *pData = m_pData + m_iCurPos; + int len = m_DataLen - m_iCurPos; + + // Perform crlf translation + while ( const char *crlf = ( const char * ) memchr( pData, '\r', len ) ) + { + int canCopy = min( size, crlf - pData ); + memcpy( pOutput, pData, canCopy ); + + m_iCurPos += canCopy; + pData += canCopy; + len -= canCopy; + + iRead += canCopy; + ( char * & ) pOutput += canCopy; + size -= canCopy; + + if ( size && len ) + { + if ( ( len > 1 ) && ( pData[1] == '\n' ) ) + { + ++ m_iCurPos; + ++ pData; + -- len; + } + + * ( char * & ) pOutput = *pData; + + ++ m_iCurPos; + ++ pData; + -- len; + + ++ iRead; + ++ ( char * & ) pOutput; + -- size; + } + else + break; + } + + if ( size && len ) + { + // No crlf characters left + int canCopy = min( size, len ); + memcpy( pOutput, pData, canCopy ); + + m_iCurPos += canCopy; + pData += canCopy; + len -= canCopy; + + iRead += canCopy; + ( char * & ) pOutput += canCopy; + size -= canCopy; + } + + return iRead; + } +} + +int CVMPIFile_Memory::Write( void const* pInput, int size ) +{ + Assert( false ); return 0; +} + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CBaseVMPIFileSystem implementation. +// ------------------------------------------------------------------------------------------------------------------------ // + +CBaseVMPIFileSystem::~CBaseVMPIFileSystem() +{ +} + + +void CBaseVMPIFileSystem::Release() +{ + delete this; +} + + +void CBaseVMPIFileSystem::Close( FileHandle_t file ) +{ + if ( file ) + ((IVMPIFile*)file)->Close(); +} + +int CBaseVMPIFileSystem::Read( void* pOutput, int size, FileHandle_t file ) +{ + return ((IVMPIFile*)file)->Read( pOutput, size ); +} + +int CBaseVMPIFileSystem::Write( void const* pInput, int size, FileHandle_t file ) +{ + return ((IVMPIFile*)file)->Write( pInput, size ); +} + +void CBaseVMPIFileSystem::Seek( FileHandle_t file, int pos, FileSystemSeek_t seekType ) +{ + ((IVMPIFile*)file)->Seek( pos, seekType ); +} + +unsigned int CBaseVMPIFileSystem::Tell( FileHandle_t file ) +{ + return ((IVMPIFile*)file)->Tell(); +} + +unsigned int CBaseVMPIFileSystem::Size( FileHandle_t file ) +{ + return ((IVMPIFile*)file)->Size(); +} + +unsigned int CBaseVMPIFileSystem::Size( const char *pFilename, const char *pathID = 0 ) +{ + FileHandle_t hFile = Open( pFilename, "rb", NULL ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + { + return 0; + } + else + { + unsigned int ret = Size( hFile ); + Close( hFile ); + return ret; + } +} + +bool CBaseVMPIFileSystem::FileExists( const char *pFileName, const char *pPathID ) +{ + FileHandle_t hFile = Open( pFileName, "rb", NULL ); + if ( hFile ) + { + Close( hFile ); + return true; + } + else + { + return false; + } +} + +void CBaseVMPIFileSystem::Flush( FileHandle_t file ) +{ + ((IVMPIFile*)file)->Flush(); +} + +bool CBaseVMPIFileSystem::Precache( const char* pFileName, const char *pPathID ) +{ + return false; +} + + +//----------------------------------------------------------------------------- +// NOTE: This is an exact copy of code in BaseFileSystem.cpp which +// has to be here because they want to call +// the implementation of Open/Size/Read/Write in CBaseVMPIFileSystem +//----------------------------------------------------------------------------- +bool CBaseVMPIFileSystem::ReadFile( const char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc ) +{ + const char *pReadFlags = "rb"; + if ( buf.IsText() && !buf.ContainsCRLF() ) + { + pReadFlags = "rt"; + } + + FileHandle_t fp = Open( pFileName, buf.IsText() ? "rt" : "rb", pPath ); + if ( !fp ) + return false; + + int nBytesToRead = Size( fp ); + if ( nMaxBytes > 0 ) + { + nBytesToRead = min( nMaxBytes, nBytesToRead ); + } + buf.EnsureCapacity( nBytesToRead + buf.TellPut() ); + + if ( nStartingByte != 0 ) + { + Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD ); + } + + int nBytesRead = Read( buf.PeekPut(), nBytesToRead, fp ); + buf.SeekPut( CUtlBuffer::SEEK_CURRENT, nBytesRead ); + + Close( fp ); + return (nBytesRead != 0); +} + +bool CBaseVMPIFileSystem::WriteFile( const char *pFileName, const char *pPath, CUtlBuffer &buf ) +{ + const char *pWriteFlags = "wb"; + if ( buf.IsText() && !buf.ContainsCRLF() ) + { + pWriteFlags = "wt"; + } + + FileHandle_t fp = Open( pFileName, buf.IsText() ? "wt" : "wb", pPath ); + if ( !fp ) + return false; + + int nBytesWritten = Write( buf.Base(), buf.TellPut(), fp ); + + Close( fp ); + return (nBytesWritten != 0); +} + diff --git a/utils/vmpi/vmpi_filesystem.h b/utils/vmpi/vmpi_filesystem.h new file mode 100644 index 0000000..f3c3c8a --- /dev/null +++ b/utils/vmpi/vmpi_filesystem.h @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_FILESYSTEM_H +#define VMPI_FILESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "interface.h" + + +class IFileSystem; +class MessageBuffer; + + +// Use this to read virtual files. +#define VMPI_VIRTUAL_FILES_PATH_ID "VMPI_VIRTUAL_FILES_PATH_ID" + + +// When you hook the file system with VMPI and are a worker, it blocks on file reads +// and uses MPI to communicate with the master to transfer files it needs over. +// +// The filesystem, by default (and it maxFileSystemMemoryUsage is left at zero), +// keeps the contents of the files that get opened in memory. You can pass in a +// value here to put a cap on it, in which case it'll unload the least-recently-used +// files when it hits the limit. +IFileSystem* VMPI_FileSystem_Init( int maxFileSystemMemoryUsage, IFileSystem *pPassThru ); + +// On the master machine, this really should be called before the app shuts down and +// global destructors are called. If it isn't, it might lock up waiting for a thread to exit. +// +// This returns the original filesystem you passed into VMPI_FileSystem_Init so you can uninitialize it. +IFileSystem* VMPI_FileSystem_Term(); + +// Causes it to error out on any Open() calls. +void VMPI_FileSystem_DisableFileAccess(); + +// Returns a factory that will hand out BASEFILESYSTEM_INTERFACE_VERSION when asked for it. +CreateInterfaceFn VMPI_FileSystem_GetFactory(); + +// This function creates a virtual file that workers can then open and read out of. +// NOTE: when reading from the file, you must use VMPI_VIRTUAL_FILES_PATH_ID as the path ID +// or else it won't find the file. +void VMPI_FileSystem_CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ); + + +#endif // VMPI_FILESYSTEM_H diff --git a/utils/vmpi/vmpi_filesystem_internal.h b/utils/vmpi/vmpi_filesystem_internal.h new file mode 100644 index 0000000..25ba2af --- /dev/null +++ b/utils/vmpi/vmpi_filesystem_internal.h @@ -0,0 +1,129 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_FILESYSTEM_INTERNAL_H +#define VMPI_FILESYSTEM_INTERNAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vmpi_filesystem.h" +#include "filesystem.h" +#include "messbuf.h" +#include "iphelpers.h" +#include "vmpi.h" +#include "utlvector.h" +#include "utllinkedlist.h" +#include "filesystem_passthru.h" + + +// Sub packet IDs specific to the VMPI file system. +#define VMPI_FSPACKETID_FILE_REQUEST 1 // Sent by the worker to request a file. +#define VMPI_FSPACKETID_FILE_RESPONSE 2 // Master's response to a file request. +#define VMPI_FSPACKETID_CHUNK_RECEIVED 3 // Sent by workers to tell the master they received a chunk. +#define VMPI_FSPACKETID_FILE_RECEIVED 4 // Sent by workers to tell the master they received the whole file. +#define VMPI_FSPACKETID_MULTICAST_ADDR 5 +#define VMPI_FSPACKETID_FILE_CHUNK 6 // Used to send file data when using TCP. + + +// In TCP mode, we send larger chunks in a sliding window. +#define TCP_CHUNK_QUEUE_LEN 16 + +#define TCP_CHUNK_PAYLOAD_SIZE (16*1024) +#define MULTICAST_CHUNK_PAYLOAD_SIZE (1024*1) +#define MAX_CHUNK_PAYLOAD_SIZE MAX( TCP_CHUNK_PAYLOAD_SIZE, MULTICAST_CHUNK_PAYLOAD_SIZE ) + + +class CMulticastFileInfo +{ +public: + unsigned long m_CompressedSize; + unsigned long m_UncompressedSize; + unsigned short m_FileID; + unsigned short m_nChunks; +}; + + +class CBaseVMPIFileSystem : public CFileSystemPassThru +{ +public: + virtual ~CBaseVMPIFileSystem(); + virtual void Release(); + + virtual void CreateVirtualFile( const char *pFilename, const void *pData, int fileLength ) = 0; + virtual bool HandleFileSystemPacket( MessageBuffer *pBuf, int iSource, int iPacketID ) = 0; + + virtual void Close( FileHandle_t file ); + virtual int Read( void* pOutput, int size, FileHandle_t file ); + virtual int Write( void const* pInput, int size, FileHandle_t file ); + virtual void Seek( FileHandle_t file, int pos, FileSystemSeek_t seekType ); + virtual unsigned int Tell( FileHandle_t file ); + virtual unsigned int Size( FileHandle_t file ); + virtual unsigned int Size( const char *pFilename, const char *pathID ); + virtual bool FileExists( const char *pFileName, const char *pPathID ); + virtual void Flush( FileHandle_t file ); + virtual bool Precache( const char* pFileName, const char *pPathID ); + virtual bool ReadFile( const char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes = 0, int nStartingByte = 0, FSAllocFunc_t pfnAlloc = 0 ); + virtual bool WriteFile( const char *pFileName, const char *pPath, CUtlBuffer &buf ); + +// All the IFileSystem-specific ones pass the calls through. +// The worker opens its own filesystem_stdio fthrough. + +protected: + CIPAddr m_MulticastIP; +}; + + +class IVMPIFile +{ +public: + virtual void Close() = 0; + virtual void Seek( int pos, FileSystemSeek_t seekType ) = 0; + virtual unsigned int Tell() = 0; + virtual unsigned int Size() = 0; + virtual void Flush() = 0; + virtual int Read( void* pOutput, int size ) = 0; + virtual int Write( void const* pInput, int size ) = 0; +}; + + +// Both the workers and masters use this to hand out the file data. +class CVMPIFile_Memory : public IVMPIFile +{ +public: + void Init( const char *pData, long len, char chMode = 'b' ); + virtual void Close(); + virtual void Seek( int pos, FileSystemSeek_t seekType ); + virtual unsigned int Tell(); + virtual unsigned int Size(); + virtual void Flush(); + virtual int Read( void* pOutput, int size ) ; + virtual int Write( void const* pInput, int size ); + +private: + const char *m_pData; + long m_DataLen; + int m_iCurPos; + char m_chMode; // 'b' or 't' +}; + + +// We use different payload sizes if we're using TCP mode vs. multicast/broadcast mode. +inline int VMPI_GetChunkPayloadSize() +{ + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_TCP ) + return TCP_CHUNK_PAYLOAD_SIZE; + else + return MULTICAST_CHUNK_PAYLOAD_SIZE; +} + + +extern bool g_bDisableFileAccess; +extern CBaseVMPIFileSystem *g_pBaseVMPIFileSystem; + + +#endif // VMPI_FILESYSTEM_INTERNAL_H diff --git a/utils/vmpi/vmpi_filesystem_master.cpp b/utils/vmpi/vmpi_filesystem_master.cpp new file mode 100644 index 0000000..f047bf7 --- /dev/null +++ b/utils/vmpi/vmpi_filesystem_master.cpp @@ -0,0 +1,1606 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <winsock2.h> +#include "vmpi_filesystem_internal.h" +#include "zlib.h" +#include "vstdlib/random.h" + + +#define MINIMUM_SLEEP_MS 1 + +// NOTE: This number comes from measurements on our network to find out how fast +// we can broadcast without the network freaking out. +// +// This number can be changed on the command line with the -mpi_FileTransmitRate parameter. +int MULTICAST_TRANSMIT_RATE = (1024*1000); // N megs per second + +// Defines when we'll stop transmitting a file to a client. +// (After we've transmitted the file to the client N times and we haven't heard an ack back for M seconds). +#define MIN_FILE_CYCLE_COUNT 5 +#define CLIENT_FILE_ACK_TIMEOUT 20 + + + +// ------------------------------------------------------------------------------------------------------------------------ // +// Global helpers. +// ------------------------------------------------------------------------------------------------------------------------ // + +static void SendMulticastIP( const CIPAddr *pAddr ) +{ + unsigned char packetID[2] = { VMPI_PACKETID_FILESYSTEM, VMPI_FSPACKETID_MULTICAST_ADDR }; + VMPI_Send2Chunks( + packetID, sizeof( packetID ), + pAddr, sizeof( *pAddr ), + VMPI_PERSISTENT ); +} + + +static bool IsOpeningForWriteAccess( const char *pOptions ) +{ + return strchr( pOptions, 'w' ) || strchr( pOptions, 'a' ) || strchr( pOptions, '+' ); +} + + +// This does a fast zlib compression of the source data into the 'out' buffer. +static bool ZLibCompress( const void *pData, int len, CUtlVector<char> &out ) +{ + if ( len == 0 ) + { + out.Purge(); + return true; + } + + int outStartLen = len; +RETRY:; + + // Prepare the compression stream. + z_stream zs; + memset( &zs, 0, sizeof( zs ) ); + + if ( deflateInit( &zs, 1 ) != Z_OK ) + return false; + + + // Now compress it into the output buffer. + out.SetSize( outStartLen ); + + zs.next_in = (unsigned char*)pData; + zs.avail_in = len; + + zs.next_out = (unsigned char*)out.Base(); + zs.avail_out = out.Count(); + + int ret = deflate( &zs, Z_FINISH ); + deflateEnd( &zs ); + + if ( ret == Z_STREAM_END ) + { + // Get rid of whatever was left over. + out.RemoveMultiple( zs.total_out, out.Count() - zs.total_out ); + return true; + } + else if ( ret == Z_OK ) + { + // Need more space in the output buffer. + outStartLen += 1024 * 128; + goto RETRY; + } + else + { + return false; + } +} + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CVMPIFile_PassThru +// ------------------------------------------------------------------------------------------------------------------------ // + +class CVMPIFile_PassThru : public IVMPIFile +{ +public: + void Init( IBaseFileSystem *pPassThru, FileHandle_t fp ) + { + m_pPassThru = pPassThru; + m_fp = fp; + } + + virtual void Close() + { + m_pPassThru->Close( m_fp ); + delete this; + } + + virtual void Seek( int pos, FileSystemSeek_t seekType ) + { + m_pPassThru->Seek( m_fp, pos, seekType ); + } + + virtual unsigned int Tell() + { + return m_pPassThru->Tell( m_fp ); + } + + virtual unsigned int Size() + { + return m_pPassThru->Size( m_fp ); + } + + virtual void Flush() + { + m_pPassThru->Flush( m_fp ); + } + + virtual int Read( void* pOutput, int size ) + { + return m_pPassThru->Read( pOutput, size, m_fp ); + } + + virtual int Write( void const* pInput, int size ) + { + return m_pPassThru->Write( pInput, size, m_fp ); + } + + +private: + IBaseFileSystem *m_pPassThru; + FileHandle_t m_fp; +}; + + + +// ---------------------------------------------------------------------------------------------------- // +// CTransmitRateMgr coordinates with any other currently-running VMPI jobs, and they all will cut +// down their transmission rate to stay within MULTICAST_TRANSMIT_RATE. +// ---------------------------------------------------------------------------------------------------- // + +#define TRANSMITRATEMGR_BROADCAST_INVERVAL (1.0 / 3.0) // How many times per second we broadcast our presence. +#define TRANSMITRATEMGR_EXPIRE_TIME 0.7 // How long it'll go before deciding a guy is not transmitting anymore. + +static char s_cTransmitRateMgrPacket[] = {2,6,-3,2,1,-66}; + +class CTransmitRateMgr +{ +public: + CTransmitRateMgr(); + + void ReadPackets(); + void BroadcastPresence(); + + double GetMicrosecondsPerByte() const; + +private: + class CMachineRecord + { + public: + unsigned long m_UniqueID; + float m_flLastTime; + }; + CUtlVector<CMachineRecord> m_MachineRecords; + + unsigned long m_UniqueID; + float m_flLastBroadcastTime; + double m_nMicrosecondsPerByte; + ISocket *m_pSocket; +}; + +CTransmitRateMgr::CTransmitRateMgr() +{ + m_nMicrosecondsPerByte = 1000000.0 / (double)MULTICAST_TRANSMIT_RATE; + m_flLastBroadcastTime = 0; + + // Build a (hopefully) unique ID. + m_UniqueID = (unsigned long)this; + CCycleCount cnt; + cnt.Sample(); + m_UniqueID += cnt.GetMicroseconds(); + Sleep( 1 ); + m_UniqueID += cnt.GetMicroseconds(); + + m_pSocket = CreateIPSocket(); + if ( m_pSocket ) + { + m_pSocket->BindToAny( VMPI_MASTER_FILESYSTEM_BROADCAST_PORT ); + } +} + +void CTransmitRateMgr::ReadPackets() +{ + if ( !m_pSocket ) + return; + + float flCurTime = Plat_FloatTime(); + + // First, update/add records. + while ( 1 ) + { + char data[512]; + CIPAddr ipFrom; + int len = m_pSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + if ( len == -1 ) + break; + + if ( len == sizeof( s_cTransmitRateMgrPacket ) + sizeof( unsigned long ) && + memcmp( data, s_cTransmitRateMgrPacket, sizeof( s_cTransmitRateMgrPacket ) ) == 0 ) + { + unsigned long id = *((unsigned long*)&data[sizeof(s_cTransmitRateMgrPacket)]); + if ( id == m_UniqueID ) + continue; + + int i; + for ( i=0; i < m_MachineRecords.Count(); i++ ) + { + if ( m_MachineRecords[i].m_UniqueID == id ) + { + m_MachineRecords[i].m_flLastTime = flCurTime; + break; + } + } + + if ( i == m_MachineRecords.Count() ) + { + int index = m_MachineRecords.AddToTail(); + m_MachineRecords[index].m_UniqueID = id; + m_MachineRecords[index].m_flLastTime = flCurTime; + } + } + } + + // Now, expire any old records. + for ( int i=0; i < m_MachineRecords.Count(); i++ ) + { + if ( (flCurTime - m_MachineRecords[i].m_flLastTime) > TRANSMITRATEMGR_EXPIRE_TIME ) + { + m_MachineRecords.Remove( i ); + --i; + } + } + + // Recalculate our transmit rate (assuming we're receiving our own broadcast packets). + m_nMicrosecondsPerByte = 1000000.0 / (double)(MULTICAST_TRANSMIT_RATE / (m_MachineRecords.Count() + 1)); +} + +void CTransmitRateMgr::BroadcastPresence() +{ + if ( !m_pSocket ) + return; + + float flCurTime = Plat_FloatTime(); + if ( (flCurTime - m_flLastBroadcastTime) < TRANSMITRATEMGR_BROADCAST_INVERVAL ) + return; + + m_flLastBroadcastTime = flCurTime; + + char cData[sizeof( s_cTransmitRateMgrPacket ) + sizeof( unsigned long )]; + memcpy( cData, s_cTransmitRateMgrPacket, sizeof( s_cTransmitRateMgrPacket ) ); + *((unsigned long*)&cData[ sizeof( s_cTransmitRateMgrPacket ) ] ) = m_UniqueID; + + m_pSocket->Broadcast( cData, sizeof( cData ), VMPI_MASTER_FILESYSTEM_BROADCAST_PORT ); +} + +inline double CTransmitRateMgr::GetMicrosecondsPerByte() const +{ + return m_nMicrosecondsPerByte; +} + + +// ---------------------------------------------------------------------------------------------------- // +// CRateLimiter manages waiting for small periods of time between packets so the rate is +// whatever we want it to be. +// +// It also will give up some CPU time to other processes every 50 milliseconds. +// ---------------------------------------------------------------------------------------------------- // + +class CRateLimiter +{ +public: + + CRateLimiter(); + + void GiveUpTimeSlice(); + void NoteExcessTimeTaken( unsigned long excessTimeInMicroseconds ); + + +public: + + DWORD m_SleepIntervalMS; // Give up a timeslice every N milliseconds. + + // Since we sleep once in a while, we time how long the sleep took and we beef + // up the transmit rate until we've accounted for the time lost during the sleep. + DWORD m_AccumulatedSleepMicroseconds; + + // When was the last time we gave up a little bit of CPU to other programs. + CCycleCount m_LastSleepTime; +}; + +CRateLimiter::CRateLimiter() +{ + m_SleepIntervalMS = 50; + m_AccumulatedSleepMicroseconds = 0; + m_LastSleepTime.Sample(); +} + +void CRateLimiter::GiveUpTimeSlice() +{ + // Sleep again? + CCycleCount currentTime, dtSinceLastSleep; + currentTime.Sample(); + CCycleCount::Sub( currentTime, m_LastSleepTime, dtSinceLastSleep ); + + if ( dtSinceLastSleep.GetMilliseconds() >= m_SleepIntervalMS ) + { + CFastTimer sleepTimer; + + sleepTimer.Start(); + Sleep( 10 ); + sleepTimer.End(); + + m_AccumulatedSleepMicroseconds += sleepTimer.GetDuration().GetMicroseconds(); + m_LastSleepTime.Sample(); + } +} + + +void CRateLimiter::NoteExcessTimeTaken( unsigned long excessTimeInMicroseconds ) +{ + // Note: we give up time slices above. + if ( excessTimeInMicroseconds > m_AccumulatedSleepMicroseconds ) + { + excessTimeInMicroseconds -= m_AccumulatedSleepMicroseconds; + m_AccumulatedSleepMicroseconds = 0; + + CCycleCount startCount; + startCount.Sample(); + while ( 1 ) + { + CCycleCount curCount, diff; + curCount.Sample(); + + CCycleCount::Sub( curCount, startCount, diff ); + if ( diff.GetMicroseconds() >= excessTimeInMicroseconds ) + break; + } + } + else + { + m_AccumulatedSleepMicroseconds -= excessTimeInMicroseconds; + excessTimeInMicroseconds = 0; + } +} + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CMasterMulticastThread. +// ------------------------------------------------------------------------------------------------------------------------ // + +class CMasterMulticastThread +{ +public: + + CMasterMulticastThread(); + ~CMasterMulticastThread(); + + // This creates the socket and starts the thread (initially in an idle state since it doesn't + // know of any files anyone wants). + bool Init( IBaseFileSystem *pPassThru, unsigned short localPort, const CIPAddr *pAddr, int maxFileSystemMemoryUsage ); + void Term(); + + // Returns -1 if there is an error. + int FindOrAddFile( const char *pFilename, const char *pPathID ); + const CUtlVector<char>& GetFileData( int iFile ) const; + + // When a client requests a files, this is called to tell the thread to start + // adding chunks from the specified file into the queue it's multicasting. + // + // Returns -1 if the file isn't there. Otherwise, it returns the file ID + // that will be sent along with the file's chunks in the multicast packets. + int AddFileRequest( const char *pFilename, const char *pPathID, int clientID, bool *bZeroLength ); + + // As each client receives multicasted chunks, they ack them so the master can + // stop transmitting any chunks it knows nobody wants. + void OnChunkReceived( int fileID, int clientID, int iChunk ); + void OnFileReceived( int fileID, int clientID ); + + // Call this if a client disconnects so it can stop sending anything this client wants. + void OnClientDisconnect( int clientID, bool bGrabCriticalSection=true ); + + void CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ); + +private: + + class CChunkInfo + { + public: + unsigned short m_iChunk; + unsigned short m_RefCount; // How many clients want this chunk. + unsigned short m_iActiveChunksIndex; // Index into m_ActiveChunks. + }; + + + // This stores a client's reference to a file so it knows which pieces of the file the client needs. + class CClientFileInfo + { + public: + bool NeedsChunk( int i ) const { return (m_ChunksToSend[i>>3] & (1 << (i&7))) != 0; } + + public: + int m_ClientID; + CUtlVector<unsigned char> m_ChunksToSend; // One bit for each chunk that this client still wants. + int m_nChunksLeft; + + // TCP transmission only. + int m_TCP_LastChunkAcked; + int m_TCP_LastChunkSent; + + float m_flTransmitStartTime; + + float m_flLastAckTime; // Last time we heard an ack back from this client about this file. + // If this goes for too long, then we assume that the client is + // in a screwed state, and we stop sending the file to him. + int m_nTimesFileCycled; // How many times has the master multicast thread cycled over this file? + // We won't kick the client until we've cycled over the file a few times + // after the client asked for it. + }; + + + class CMulticastFile + { + public: + ~CMulticastFile() + { + m_Clients.PurgeAndDeleteElements(); + } + + const char* GetFilename() { return m_Filename.Base(); } + const char* GetPathID() { return m_PathID.Base(); } + + + public: + int m_nCycles; // How many times has the multicast thread visited this file? + + // This is sent along with every packet. If a client gets a chunk and doesn't have that file's + // info, the client will receive that file too. + CUtlVector<char> m_Filename; + CUtlVector<char> m_PathID; + + CMulticastFileInfo m_Info; + + // This is stored so the app can read out the uncompressed data. + CUtlVector<char> m_UncompressedData; + + // zlib-compressed file data + CUtlVector<char> m_Data; + + // This gets set to false if we run over our memory limit and start caching file data out. + // Then it'll reload the data if a client requests the file. + bool m_bDataLoaded; + + // m_Chunks holds the chunks by index. + // m_ActiveChunks holds them sorted by whether they're active or not. + // + // Each chunk has a refcount. While the refcount is > 0, the chunk is in the first + // half of m_ActiveChunks. When the refcount gets to 0, the chunk is moved to the end of + // m_ActiveChunks. That way, we can iterate through the chunks that need to be sent and + // stop iterating the first time we hit one with a refcount of 0. + CUtlVector<CChunkInfo> m_Chunks; + CUtlLinkedList<CChunkInfo*,int> m_ActiveChunks; + + // This tells which clients want pieces of this file. + CUtlLinkedList<CClientFileInfo*,int> m_Clients; + }; + + +private: + + static DWORD WINAPI StaticMulticastThread( LPVOID pParameter ); + DWORD MulticastThread(); + + bool CheckClientTimeouts(); + bool Thread_SendFileChunk_Multicast( int *pnBytesSent ); + void Thread_SeekToNextActiveChunk(); + + // In TCP mode, we send new chunks as they are acked. + void TCP_SendNextChunk( CMulticastFile *pFile, CClientFileInfo *pClient ); + + void EnsureMemoryLimit( CMulticastFile *pIgnore ); + + // Called after pFile->m_UncompressedData has been setup. This compresses the data, prepares the header, + // copies the filename, and adds it into the queue for the multicast thread. + int FinishFileSetup( CMulticastFile *pFile, const char *pFilename, const char *pPathID, bool bFileAlreadyExisted ); + + void IncrementChunkRefCount( CMasterMulticastThread::CMulticastFile *pFile, int iChunk ); + void DecrementChunkRefCount( int iFile, int iChunk ); + + int FindFile( const char *pFilename, const char *pPathID ); + + bool FindWarningSuppression( const char *pFilename ); + void AddWarningSuppression( const char *pFilename ); + +private: + + CUtlLinkedList<CMulticastFile*,int> m_Files; + + unsigned long m_nCurMemoryUsage; // Total of all the file data we have loaded. + unsigned long m_nMaxMemoryUsage; // 0 means that there is no limit. + + // This tracks how many chunks we have that want to be sent. + int m_nTotalActiveChunks; + + SOCKET m_Socket; + sockaddr_in m_MulticastAddr; + + HANDLE m_hMainThread; + IBaseFileSystem *m_pPassThru; + + HANDLE m_hThread; + CRITICAL_SECTION m_CS; + + // Events used to communicate with our thread. + HANDLE m_hTermEvent; + + // The thread walks through this as it spews chunks of data. + volatile int m_iCurFile; // Index into m_Files. + volatile int m_iCurActiveChunk; // Current index into CMulticastFile::m_ActiveChunks. + + CUtlLinkedList<char*,int> m_WarningSuppressions; +}; + + +CMasterMulticastThread::CMasterMulticastThread() +{ + m_hThread = m_hMainThread = NULL; + m_Socket = INVALID_SOCKET; + m_nTotalActiveChunks = 0; + m_iCurFile = m_iCurActiveChunk = -1; + m_pPassThru = NULL; + + m_hTermEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + InitializeCriticalSection( &m_CS ); + m_nCurMemoryUsage = m_nMaxMemoryUsage = 0; +} + + +CMasterMulticastThread::~CMasterMulticastThread() +{ + Term(); + + CloseHandle( m_hTermEvent ); + + DeleteCriticalSection( &m_CS ); +} + + +bool CMasterMulticastThread::Init( IBaseFileSystem *pPassThru, unsigned short localPort, const CIPAddr *pAddr, int maxMemoryUsage ) +{ + Term(); + + m_nMaxMemoryUsage = maxMemoryUsage; + Assert( m_nCurMemoryUsage == 0 ); + m_nCurMemoryUsage = 0; + + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_TCP ) + { + // No need for an extra socket in this mode. + m_Socket = INVALID_SOCKET; + } + else + { + // First, create our socket. + m_Socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP ); + if ( m_Socket == INVALID_SOCKET ) + { + Warning( "CMasterMulticastThread::Init - socket() failed\n" ); + return false; + } + + // Bind to INADDR_ANY. + CIPAddr localAddr( 0, 0, 0, 0, localPort ); + + sockaddr_in addr; + IPAddrToSockAddr( &localAddr, &addr ); + + int status = bind( m_Socket, (sockaddr*)&addr, sizeof(addr) ); + if ( status != 0 ) + { + Term(); + Warning( "CMasterMulticastThread::Init - bind( %d.%d.%d.%d:%d ) failed\n", EXPAND_ADDR( *pAddr ) ); + return false; + } + + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_BROADCAST ) + { + // Set up for broadcast + BOOL bBroadcast = TRUE; + if ( setsockopt( m_Socket, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, bBroadcast ) == SOCKET_ERROR ) + { + Term(); + Warning( "CMasterMulticastThread::Init - setsockopt() failed to set broadcast mode\n" ); + return false; + } + } + + // Remember the address we want to send to. + IPAddrToSockAddr( pAddr, &m_MulticastAddr ); + + // Now create our thread. + DWORD dwThreadID = 0; + m_hThread = CreateThread( NULL, 0, &CMasterMulticastThread::StaticMulticastThread, this, 0, &dwThreadID ); + if ( !m_hThread ) + { + Term(); + Warning( "CMasterMulticastThread::Init - CreateThread failed\n" ); + return false; + } + + SetThreadPriority( m_hThread, THREAD_PRIORITY_LOWEST ); + } + + // For debug mode to verify that we don't try to open files while in another thread. + m_hMainThread = GetCurrentThread(); + + m_pPassThru = pPassThru; + return true; +} + + +void CMasterMulticastThread::Term() +{ + // Stop the thread if it is running. + if ( m_hThread ) + { + SetEvent( m_hTermEvent ); + WaitForSingleObject( m_hThread, INFINITE ); + CloseHandle( m_hThread ); + + m_hThread = NULL; + } + + // Close the socket. + if ( m_Socket != INVALID_SOCKET ) + { + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + } + + // Free up other data. + m_Files.PurgeAndDeleteElements(); + m_nCurMemoryUsage = m_nMaxMemoryUsage = 0; +} + + +void CMasterMulticastThread::TCP_SendNextChunk( CMulticastFile *pFile, CClientFileInfo *pClient ) +{ + // No more chunks to send? + if ( (pClient->m_TCP_LastChunkSent+1) >= pFile->m_Info.m_nChunks ) + return; + + // Figure out what data we'd be sending. + int iChunkToSend = pClient->m_TCP_LastChunkSent + 1; + int iStartByte = iChunkToSend * TCP_CHUNK_PAYLOAD_SIZE; + int iEndByte = min( iStartByte + TCP_CHUNK_PAYLOAD_SIZE, pFile->m_Data.Count() ); + + // If the start point is past the end, then we're done sending the file to this client. + if ( iStartByte >= pFile->m_Data.Count() ) + return; + + // Record that we sent this data. + pClient->m_TCP_LastChunkSent = iChunkToSend; + + // Assemble the packet. + unsigned char cPacket[2] = { VMPI_PACKETID_FILESYSTEM, VMPI_FSPACKETID_FILE_CHUNK }; + + const void *chunks[5] = + { + cPacket, + &pFile->m_Info, + &iChunkToSend, + pFile->GetFilename(), + &pFile->m_Data[iStartByte] + }; + + int chunkLengths[5] = + { + sizeof( cPacket ), + sizeof( pFile->m_Info ), + sizeof( m_iCurActiveChunk ), + strlen( pFile->GetFilename() ) + 1, + iEndByte - iStartByte + }; + + VMPI_SendChunks( chunks, chunkLengths, 5, pClient->m_ClientID ); +} + + +int CMasterMulticastThread::AddFileRequest( const char *pFilename, const char *pPathID, int clientID, bool *bZeroLength ) +{ + // Firstly, do we already have this file? + int iFile = FindOrAddFile( pFilename, pPathID ); + if ( iFile == -1 ) + return -1; + + CMulticastFile *pFile = m_Files[iFile]; + + // Now that we have a file setup, merge in this client's info. + EnterCriticalSection( &m_CS ); + + CClientFileInfo *pClient = new CClientFileInfo; + pClient->m_TCP_LastChunkAcked = -1; + pClient->m_TCP_LastChunkSent = -1; + pClient->m_ClientID = clientID; + pClient->m_flLastAckTime = Plat_FloatTime(); + pClient->m_flTransmitStartTime = pClient->m_flLastAckTime; + pClient->m_nTimesFileCycled = 0; + pClient->m_nChunksLeft = pFile->m_Info.m_nChunks; + pClient->m_ChunksToSend.SetSize( PAD_NUMBER( pFile->m_Info.m_nChunks, 8 ) / 8 ); + memset( pClient->m_ChunksToSend.Base(), 0xFF, pClient->m_ChunksToSend.Count() ); + pFile->m_Clients.AddToTail( pClient ); + + for ( int i=0; i < pFile->m_Chunks.Count(); i++ ) + { + IncrementChunkRefCount( pFile, i ); + } + + // In TCP mode, let's get the sliding window started.. + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_TCP ) + { + for ( int iDepth=0; iDepth < TCP_CHUNK_QUEUE_LEN; iDepth++ ) + TCP_SendNextChunk( pFile, pClient ); + } + + LeaveCriticalSection( &m_CS ); + + *bZeroLength = (pFile->m_Info.m_UncompressedSize == 0); + + return iFile; +} + + +void CMasterMulticastThread::OnChunkReceived( int fileID, int clientID, int iChunk ) +{ + if ( !m_Files.IsValidIndex( fileID ) ) + { + Warning( "CMasterMulticastThread::OnChunkReceived: invalid file (%d) from client %d\n", fileID, clientID ); + return; + } + + CMulticastFile *pFile = m_Files[fileID]; + CClientFileInfo *pClient = NULL; + FOR_EACH_LL( pFile->m_Clients, iClient ) + { + if ( pFile->m_Clients[iClient]->m_ClientID == clientID ) + { + pClient = pFile->m_Clients[iClient]; + break; + } + } + if ( !pClient ) + { + // This will spam sometimes if a worker stops responding and we timeout on it, + // but then it comes back alive and starts responding. So let's ignore its packets silently. + //Warning( "CMasterMulticastThread::OnChunkReceived: invalid client ID (%d) for file %s\n", clientID, pFile->GetFilename() ); + return; + } + + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_TCP ) + { + // Send the next chunk, if there is one. + EnterCriticalSection( &m_CS ); + TCP_SendNextChunk( pFile, pClient ); + LeaveCriticalSection( &m_CS ); + } + else + { + if ( !pFile->m_Chunks.IsValidIndex( iChunk ) ) + { + Warning( "CMasterMulticastThread::OnChunkReceived: invalid chunk index (%d) for file %s\n", iChunk, pFile->GetFilename() ); + return; + } + + // Mark that this client doesn't need this chunk anymore. + pClient->m_ChunksToSend[iChunk >> 3] &= ~(1 << (iChunk & 7)); + pClient->m_nChunksLeft--; + + pClient->m_flLastAckTime = Plat_FloatTime(); + if ( pClient->m_nChunksLeft == 0 && g_iVMPIVerboseLevel >= 2 ) + Warning( "Client %d got file %s\n", clientID, pFile->GetFilename() ); + + EnterCriticalSection( &m_CS ); + DecrementChunkRefCount( fileID, iChunk ); + LeaveCriticalSection( &m_CS ); + } +} + + +void CMasterMulticastThread::OnFileReceived( int fileID, int clientID ) +{ + if ( !m_Files.IsValidIndex( fileID ) ) + { + Warning( "CMasterMulticastThread::OnChunkReceived: invalid file (%d) from client %d\n", fileID, clientID ); + return; + } + + CMulticastFile *pFile = m_Files[fileID]; + for ( int i=0; i < pFile->m_Info.m_nChunks; i++ ) + OnChunkReceived( fileID, clientID, i ); +} + + +void CMasterMulticastThread::OnClientDisconnect( int clientID, bool bGrabCriticalSection ) +{ + if ( bGrabCriticalSection ) + EnterCriticalSection( &m_CS ); + + // Remove all references from this client. + FOR_EACH_LL( m_Files, iFile ) + { + CMulticastFile *pFile = m_Files[iFile]; + + FOR_EACH_LL( pFile->m_Clients, iClient ) + { + CClientFileInfo *pClient = pFile->m_Clients[iClient]; + + if ( pClient->m_ClientID != clientID ) + continue; + + // Ok, this is our man. Decrement the refcount of any chunks this client wanted. + for ( int iChunk=0; iChunk < pFile->m_Info.m_nChunks; iChunk++ ) + { + if ( pClient->NeedsChunk( iChunk ) ) + { + DecrementChunkRefCount( iFile, iChunk ); + } + } + + delete pClient; + pFile->m_Clients.Remove( iClient ); + + break; + } + } + + if ( bGrabCriticalSection ) + LeaveCriticalSection( &m_CS ); +} + + +void CMasterMulticastThread::CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ) +{ + const char *pPathID = VMPI_VIRTUAL_FILES_PATH_ID; + + int iFile = FindFile( pFilename, pPathID ); + if ( iFile != -1 ) + Error( "CMasterMulticastThread::CreateVirtualFile( %s ) - file already exists!", pFilename ); + + CMulticastFile *pFile = new CMulticastFile; + pFile->m_UncompressedData.CopyArray( (const char*)pData, fileLength ); + + FinishFileSetup( pFile, pFilename, pPathID, false ); +} + + +DWORD WINAPI CMasterMulticastThread::StaticMulticastThread( LPVOID pParameter ) +{ + return ((CMasterMulticastThread*)pParameter)->MulticastThread(); +} + + +bool CMasterMulticastThread::CheckClientTimeouts() +{ + bool bRet = false; + CMulticastFile *pFile = m_Files[m_iCurFile]; + + float flCurTime = Plat_FloatTime(); + + int iNext; + for( int iCur=pFile->m_Clients.Head(); iCur != pFile->m_Clients.InvalidIndex(); iCur=iNext ) + { + iNext = pFile->m_Clients.Next( iCur ); + CClientFileInfo *pInfo = pFile->m_Clients[iCur]; + + // If the client has already fully received this file, don't bother timing out on it. + if ( pInfo->m_nChunksLeft == 0 ) + continue; + + ++pInfo->m_nTimesFileCycled; + if ( pInfo->m_nTimesFileCycled >= MIN_FILE_CYCLE_COUNT && (flCurTime - pInfo->m_flLastAckTime) > CLIENT_FILE_ACK_TIMEOUT ) + { + // For debug output, get the most recent time we heard any ack from this client at all. + float flMostRecentTime = pInfo->m_flLastAckTime; + FOR_EACH_LL( m_Files, iTestFile ) + { + CMulticastFile *pTestFile = m_Files[iTestFile]; + FOR_EACH_LL( pTestFile->m_Clients, iTestClient ) + { + if ( pTestFile->m_Clients[iTestClient]->m_ClientID == pInfo->m_ClientID ) + { + flMostRecentTime = max( flMostRecentTime, pTestFile->m_Clients[iTestClient]->m_flLastAckTime ); + } + } + } + + Warning( "\nClient %s timed out on file %s (latest: %.2f / cur: %.2f).\n", + VMPI_GetMachineName( pInfo->m_ClientID ), pFile->GetFilename(), flMostRecentTime, flCurTime ); + OnClientDisconnect( pInfo->m_ClientID, false ); + bRet = true; // yes, we booted a client. + } + } + + return bRet; +} + +inline bool CMasterMulticastThread::Thread_SendFileChunk_Multicast( int *pnBytesSent ) +{ + // Send the next chunk (file, size, time, chunk data). + CMulticastFile *pFile = m_Files[m_iCurFile]; + + int iStartByte = m_iCurActiveChunk * MULTICAST_CHUNK_PAYLOAD_SIZE; + int iEndByte = min( iStartByte + MULTICAST_CHUNK_PAYLOAD_SIZE, pFile->m_Data.Count() ); + + WSABUF bufs[4]; + bufs[0].buf = (char*)&pFile->m_Info; + bufs[0].len = sizeof( pFile->m_Info ); + + bufs[1].buf = (char*)&m_iCurActiveChunk; + bufs[1].len = sizeof( m_iCurActiveChunk ); + + bufs[2].buf = (char*)pFile->GetFilename(); + bufs[2].len = strlen( pFile->GetFilename() ) + 1; + + bufs[3].buf = &pFile->m_Data[iStartByte]; + bufs[3].len = iEndByte - iStartByte; + + DWORD nBytesSent = 0; + DWORD nWantedBytes = ( bufs[0].len + bufs[1].len + bufs[2].len + bufs[3].len ); + bool bSuccess; + + if ( m_MulticastAddr.sin_addr.S_un.S_un_b.s_b1 == 127 && + m_MulticastAddr.sin_addr.S_un.S_un_b.s_b2 == 0 && + m_MulticastAddr.sin_addr.S_un.S_un_b.s_b3 == 0 && + m_MulticastAddr.sin_addr.S_un.S_un_b.s_b4 == 1 ) + { + // For some mysterious reason, WSASendTo only sends the first buffer + // if we're sending to 127.0.0.1 (ie: in local mode). + char allData[1024*8]; + if ( nWantedBytes > sizeof( allData ) ) + Error( "nWantedBytes > sizeof( allData )" ); + + memcpy( &allData[0], bufs[0].buf, bufs[0].len ); + memcpy( &allData[bufs[0].len], bufs[1].buf, bufs[1].len ); + memcpy( &allData[bufs[0].len+bufs[1].len], bufs[2].buf, bufs[2].len ); + memcpy( &allData[bufs[0].len+bufs[1].len+bufs[2].len], bufs[3].buf, bufs[3].len ); + int ret = sendto( m_Socket, allData, nWantedBytes, 0, (sockaddr*)&m_MulticastAddr, sizeof( m_MulticastAddr ) ); + bSuccess = (ret == (int)nWantedBytes); + } + else + { + WSASendTo( + m_Socket, + bufs, + ARRAYSIZE( bufs ), + &nBytesSent, + 0, + (sockaddr*)&m_MulticastAddr, + sizeof( m_MulticastAddr ), + NULL, + NULL ); + bSuccess = (nBytesSent == nWantedBytes); + } + + // Handle errors.. let it get a few errors, then quit. + if ( bSuccess ) + { + *pnBytesSent = (int)nBytesSent; + } + else + { + static int nWarnings = 0; + ++nWarnings; + if ( nWarnings < 10 ) + { + Warning( "\nMulticastThread: WSASendTo with %d bytes sent %d bytes.\n", nWantedBytes, nBytesSent ); + + char *lpMsgBuf; + if ( FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (char*)&lpMsgBuf, + 0, + NULL + ) ) + { + Warning( "%s", lpMsgBuf ); + LocalFree( lpMsgBuf ); + } + } + else if ( nWarnings == 10 ) + { + Warning( "\nThis machine's ability to multicast may be broken. Please reboot and try again.\n" ); + } + } + + return bSuccess; +} + + +void CMasterMulticastThread::Thread_SeekToNextActiveChunk() +{ + // Make sure we're on a valid chunk. + if ( m_iCurFile == -1 ) + { + Assert( m_Files.Count() > 0 ); + m_iCurFile = m_Files.Head(); + m_iCurActiveChunk = m_Files[m_iCurFile]->m_ActiveChunks.Head(); + } + + while ( 1 ) + { + if ( m_iCurActiveChunk == m_Files[m_iCurFile]->m_ActiveChunks.InvalidIndex() || + m_Files[m_iCurFile]->m_ActiveChunks[m_iCurActiveChunk]->m_RefCount == 0 ) + { + // Now check for client timeouts. + // (This is kicking clients unjustly for some reason.. need to debug). + if ( CheckClientTimeouts() && m_nTotalActiveChunks == 0 ) + break; + + // Finished with that file. Send the next one. + m_iCurFile = m_Files.Next( m_iCurFile ); + if ( m_iCurFile == m_Files.InvalidIndex() ) + m_iCurFile = m_Files.Head(); + + m_iCurActiveChunk = m_Files[m_iCurFile]->m_ActiveChunks.Head(); + } + + if ( m_iCurActiveChunk != m_Files[m_iCurFile]->m_ActiveChunks.InvalidIndex() ) + { + // Only break if we're on an active chunk. + if ( m_Files[m_iCurFile]->m_ActiveChunks[m_iCurActiveChunk]->m_RefCount != 0 ) + { + break; + } + + m_iCurActiveChunk = m_Files[m_iCurFile]->m_ActiveChunks.Next( m_iCurActiveChunk ); + } + } +} + + +DWORD CMasterMulticastThread::MulticastThread() +{ + CTransmitRateMgr transmitRateMgr; + CRateLimiter rateLimiter; + + + DWORD msToWait = 0; // Only temporarily used if we don't have any data to send. + + while ( WaitForSingleObject( m_hTermEvent, msToWait ) != WAIT_OBJECT_0 ) + { + rateLimiter.GiveUpTimeSlice(); + msToWait = 0; + + EnterCriticalSection( &m_CS ); + + transmitRateMgr.ReadPackets(); + + // If we have nothing to send then kick back for a while. + if ( m_nTotalActiveChunks == 0 ) + { + LeaveCriticalSection( &m_CS ); + msToWait = 50; + continue; + } + + // Ok, now we're active, so send out our presence to other CTransmitRateMgrs on the network. + transmitRateMgr.BroadcastPresence(); + + + // We're going to time how long this chunk took to send. + CFastTimer timer; + timer.Start(); + + Thread_SeekToNextActiveChunk(); + + // We have to do this check a second time here because the CheckClientTimeouts() call may have + // booted our last client. If we don't check it here, we might be transmitting + // something we don't want to transmit. Also, if we don't break out of the loop above, + // it can prevent the process from ever exiting because it'll never exit that while() loop. + if ( m_nTotalActiveChunks == 0 ) + { + LeaveCriticalSection( &m_CS ); + msToWait = 50; + continue; + } + + int nBytesSent = 0; + + bool bSuccess; + bSuccess = Thread_SendFileChunk_Multicast( &nBytesSent ); + + g_nMulticastBytesSent += (int)nBytesSent; + + // Move to the next chunk. + m_iCurActiveChunk = m_Files[m_iCurFile]->m_ActiveChunks.Next( m_iCurActiveChunk ); + + LeaveCriticalSection( &m_CS ); + + + // Measure how long it took to send this. + timer.End(); + unsigned long timeTaken = timer.GetDuration().GetMicroseconds(); + + + // Measure how long it should have taken. + unsigned long estimatedPacketHeaderSize = 32; + unsigned long optimalTimeTaken = (unsigned long)( transmitRateMgr.GetMicrosecondsPerByte() * (nBytesSent + estimatedPacketHeaderSize) ); + + + // If we went faster than we should have, then wait for the difference in time. + if ( timeTaken < optimalTimeTaken ) + { + rateLimiter.NoteExcessTimeTaken( optimalTimeTaken - timeTaken ); + } + } + + return 0; +} + + +void CMasterMulticastThread::IncrementChunkRefCount( CMasterMulticastThread::CMulticastFile *pFile, int iChunk ) +{ + CChunkInfo *pChunk = &pFile->m_Chunks[iChunk]; + + if ( pChunk->m_RefCount == 0 ) + { + ++m_nTotalActiveChunks; + + // Move the chunk to the head of the list since it is now active. + pFile->m_ActiveChunks.Remove( pChunk->m_iActiveChunksIndex ); + pChunk->m_iActiveChunksIndex = pFile->m_ActiveChunks.AddToHead( pChunk ); + } + + pChunk->m_RefCount++; +} + + +void CMasterMulticastThread::DecrementChunkRefCount( int iFile, int iChunk ) +{ + CMulticastFile *pFile = m_Files[iFile]; + CChunkInfo *pChunk = &pFile->m_Chunks[iChunk]; + + if ( pChunk->m_RefCount == 0 ) + { + Error( "CMasterMulticastThread::DecrementChunkRefCount - refcount already zero!\n" ); + } + + pChunk->m_RefCount--; + if ( pChunk->m_RefCount == 0 ) + { + --m_nTotalActiveChunks; + + // If this is the current chunk the thread is reading on, seek up to the next chunk so + // the thread doesn't spin off into the next file and skip its current file's contents. + if ( iFile == m_iCurFile && pChunk->m_iActiveChunksIndex == m_iCurActiveChunk ) + { + m_iCurActiveChunk = pFile->m_ActiveChunks.Next( pChunk->m_iActiveChunksIndex ); + } + + // Move the chunk to the end of the list since it is now inactive. + pFile->m_ActiveChunks.Remove( pChunk->m_iActiveChunksIndex ); + pChunk->m_iActiveChunksIndex = pFile->m_ActiveChunks.AddToTail( pChunk ); + } +} + + +int CMasterMulticastThread::FindFile( const char *pName, const char *pPathID ) +{ + FOR_EACH_LL( m_Files, i ) + { + CMulticastFile *pFile = m_Files[i]; + if ( stricmp( pFile->GetFilename(), pName ) == 0 && stricmp( pFile->GetPathID(), pPathID ) == 0 ) + return i; + } + return -1; +} + + +bool CMasterMulticastThread::FindWarningSuppression( const char *pFilename ) +{ + FOR_EACH_LL( m_WarningSuppressions, i ) + { + if ( Q_stricmp( m_WarningSuppressions[i], pFilename ) == 0 ) + return true; + } + return false; +} + + +void CMasterMulticastThread::AddWarningSuppression( const char *pFilename ) +{ + char *pBlah = new char[ strlen( pFilename ) + 1 ]; + strcpy( pBlah, pFilename ); + m_WarningSuppressions.AddToTail( pBlah ); +} + + +int CMasterMulticastThread::FindOrAddFile( const char *pFilename, const char *pPathID ) +{ + CMulticastFile *pFile = NULL; + bool bFileAlreadyExisted = false; + + // See if we've already opened this file. + int iFile = FindFile( pFilename, pPathID ); + if ( iFile != -1 ) + { + pFile = m_Files[iFile]; + if ( pFile->m_bDataLoaded ) + { + return iFile; + } + else + { + // Ok, we have the file entry, but its data has been freed, so we need to reload it. + EnterCriticalSection( &m_CS ); + bFileAlreadyExisted = true; + } + } + + // Can't open a file outside our main thread, because we have to talk to the filesystem + // and the filesystem doesn't support that. + Assert( GetCurrentThread() == m_hMainThread ); + + // When the worker originally asked for the path ID, they could pass NULL and it would come through as "". + // Now set it back to null for the filesystem we're passing the call to. + FileHandle_t fp = m_pPassThru->Open( pFilename, "rb", pPathID[0] == 0 ? NULL : pPathID ); + if ( !fp ) + { + if ( bFileAlreadyExisted ) + LeaveCriticalSection( &m_CS ); + + return -1; + } + + if ( !bFileAlreadyExisted ) + pFile = new CMulticastFile; + + pFile->m_UncompressedData.SetSize( m_pPassThru->Size( fp ) ); + m_pPassThru->Read( pFile->m_UncompressedData.Base(), pFile->m_UncompressedData.Count(), fp ); + m_pPassThru->Close( fp ); + + int iRet = FinishFileSetup( pFile, pFilename, pPathID, bFileAlreadyExisted ); + if ( bFileAlreadyExisted ) + LeaveCriticalSection( &m_CS ); + + return iRet; +} + + +int CMasterMulticastThread::FinishFileSetup( CMulticastFile *pFile, const char *pFilename, const char *pPathID, bool bFileAlreadyExisted ) +{ + // Compress the file's contents. + if ( !ZLibCompress( pFile->m_UncompressedData.Base(), pFile->m_UncompressedData.Count(), pFile->m_Data ) ) + { + delete pFile; + return -1; + } + + pFile->m_bDataLoaded = true; + int chunkSize = VMPI_GetChunkPayloadSize(); + + // Get this file in the queue. + if ( !bFileAlreadyExisted ) + { + pFile->m_Filename.SetSize( strlen( pFilename ) + 1 ); + strcpy( pFile->m_Filename.Base(), pFilename ); + + pFile->m_PathID.SetSize( strlen( pPathID ) + 1 ); + strcpy( pFile->m_PathID.Base(), pPathID ); + + pFile->m_nCycles = 0; + + pFile->m_Info.m_CompressedSize = pFile->m_Data.Count(); + pFile->m_Info.m_UncompressedSize = pFile->m_UncompressedData.Count(); + + pFile->m_Info.m_nChunks = PAD_NUMBER( pFile->m_Info.m_CompressedSize, chunkSize ) / chunkSize; + + // Initialize the chunks. + pFile->m_Chunks.SetSize( pFile->m_Info.m_nChunks ); + for ( int i=0; i < pFile->m_Chunks.Count(); i++ ) + { + CChunkInfo *pChunk = &pFile->m_Chunks[i]; + + pChunk->m_iChunk = (unsigned short)i; + pChunk->m_RefCount = 0; + pChunk->m_iActiveChunksIndex = pFile->m_ActiveChunks.AddToTail( pChunk ); + } + + EnterCriticalSection( &m_CS ); + } + + // Boot some other file out of memory if we're out of space. + m_nCurMemoryUsage += ( pFile->m_Info.m_CompressedSize + pFile->m_Info.m_UncompressedSize ); + EnsureMemoryLimit( pFile ); + + if ( !bFileAlreadyExisted ) + { + pFile->m_Info.m_FileID = m_Files.AddToTail( pFile ); + LeaveCriticalSection( &m_CS ); + } + + return pFile->m_Info.m_FileID; +} + + +void CMasterMulticastThread::EnsureMemoryLimit( CMulticastFile *pIgnore ) +{ + if ( m_nMaxMemoryUsage != 0 && m_nCurMemoryUsage > m_nMaxMemoryUsage ) + { + // Free all the files that we can. + FOR_EACH_LL( m_Files, iFile ) + { + CMulticastFile *pFile = m_Files[iFile]; + if ( pFile == pIgnore || !pFile->m_bDataLoaded ) + continue; + + if ( pFile->m_ActiveChunks.Count() == 0 ) + { + m_nCurMemoryUsage -= ( pFile->m_Info.m_CompressedSize + pFile->m_Info.m_UncompressedSize ); + + pFile->m_Data.Purge(); + pFile->m_UncompressedData.Purge(); + pFile->m_bDataLoaded = false; + } + } + } +} + + +const CUtlVector<char>& CMasterMulticastThread::GetFileData( int iFile ) const +{ + return m_Files[iFile]->m_UncompressedData; +} + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CMasterVMPIFileSystem implementation. +// ------------------------------------------------------------------------------------------------------------------------ // + +class CMasterVMPIFileSystem : public CBaseVMPIFileSystem +{ +public: + CMasterVMPIFileSystem(); + virtual ~CMasterVMPIFileSystem(); + + bool Init( int maxMemoryUsage, IFileSystem *pPassThru ); + virtual void Term(); + + virtual FileHandle_t Open( const char *pFilename, const char *pOptions, const char *pathID ); + virtual bool HandleFileSystemPacket( MessageBuffer *pBuf, int iSource, int iPacketID ); + + virtual void CreateVirtualFile( const char *pFilename, const void *pData, int fileLength ); + + virtual CSysModule *LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ); + virtual void UnloadModule( CSysModule *pModule ); + + +private: + + static void OnClientDisconnect( int procID, const char *pReason ); + + +private: + CMasterMulticastThread m_MasterThread; + IFileSystem *m_pMasterVMPIFileSystemPassThru; + + static CMasterVMPIFileSystem *s_pMasterVMPIFileSystem; +}; + + +CMasterVMPIFileSystem *CMasterVMPIFileSystem::s_pMasterVMPIFileSystem = NULL; + + +CBaseVMPIFileSystem* CreateMasterVMPIFileSystem( int maxMemoryUsage, IFileSystem *pPassThru ) +{ + CMasterVMPIFileSystem *pRet = new CMasterVMPIFileSystem; + g_pBaseVMPIFileSystem = pRet; + if ( pRet->Init( maxMemoryUsage, pPassThru ) ) + { + return pRet; + } + else + { + delete pRet; + g_pBaseVMPIFileSystem = NULL; + return NULL; + } +} + + +CMasterVMPIFileSystem::CMasterVMPIFileSystem() +{ + Assert( !s_pMasterVMPIFileSystem ); + s_pMasterVMPIFileSystem = this; +} + + +CMasterVMPIFileSystem::~CMasterVMPIFileSystem() +{ + Assert( s_pMasterVMPIFileSystem == this ); + s_pMasterVMPIFileSystem = NULL; +} + + +bool CMasterVMPIFileSystem::Init( int maxMemoryUsage, IFileSystem *pPassThru ) +{ + // Only init the BASE filesystem passthru. Leave the IFileSystem passthru using NULL so it'll crash + // immediately if they try to use a function we don't support. + InitPassThru( pPassThru, true ); + m_pMasterVMPIFileSystemPassThru = pPassThru; + + // Pick a random IP in the multicast range (224.0.0.2 to 239.255.255.255); + CCycleCount cnt; + cnt.Sample(); + RandomSeed( (int)cnt.GetMicroseconds() ); + + int localPort = 23412; // This can be anything. + + unsigned short port = RandomInt( 22000, 25000 ); + if ( VMPI_GetRunMode() == VMPI_RUN_NETWORKED ) + { + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_MULTICAST ) + { + m_MulticastIP.port = port; + m_MulticastIP.ip[0] = (unsigned char)RandomInt( 225, 238 ); + m_MulticastIP.ip[1] = (unsigned char)RandomInt( 0, 255 ); + m_MulticastIP.ip[2] = (unsigned char)RandomInt( 0, 255 ); + m_MulticastIP.ip[3] = (unsigned char)RandomInt( 3, 255 ); + } + else if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_BROADCAST ) + { + m_MulticastIP.Init( 0xFF, 0xFF, 0xFF, 0xFF, port ); + } + } + else + { + // Doesn't matter.. we don't use the multicast IP in TCP mode. + m_MulticastIP.Init( 0, 0, 0, 0, 0 ); + } + + if ( !m_MasterThread.Init( pPassThru, localPort, &m_MulticastIP, maxMemoryUsage ) ) + return false; + + // Send out the multicast addr to all the clients. + SendMulticastIP( &m_MulticastIP ); + + // Make sure we're notified when a client disconnects so we can unlink them from the + // multicast thread's structures. + VMPI_AddDisconnectHandler( &CMasterVMPIFileSystem::OnClientDisconnect ); + return true; +} + + +void CMasterVMPIFileSystem::Term() +{ + m_MasterThread.Term(); +} + + +FileHandle_t CMasterVMPIFileSystem::Open( const char *pFilename, const char *pOptions, const char *pPathID ) +{ + Assert( g_bUseMPI ); + + if ( g_bDisableFileAccess ) + Error( "Open( %s, %s ) - file access has been disabled.", pFilename, pOptions ); + + // Use a stdio file if they want to write to it. + bool bWriteAccess = IsOpeningForWriteAccess( pOptions ); + if ( bWriteAccess ) + { + FileHandle_t fp = m_pBaseFileSystemPassThru->Open( pFilename, pOptions, pPathID ); + if ( fp == FILESYSTEM_INVALID_HANDLE ) + return FILESYSTEM_INVALID_HANDLE; + + CVMPIFile_PassThru *pFile = new CVMPIFile_PassThru; + pFile->Init( m_pBaseFileSystemPassThru, fp ); + return (FileHandle_t)pFile; + } + + // Internally, we require path IDs to be non-null. We'll convert it back to null whenever we make filesystem calls though. + if ( !pPathID ) + pPathID = ""; + + // Have our multicast thread load all the data so it's there when workers want it. + int iFile = m_MasterThread.FindOrAddFile( pFilename, pPathID ); + if ( iFile == -1 ) + return FILESYSTEM_INVALID_HANDLE; + + const CUtlVector<char> &data = m_MasterThread.GetFileData( iFile ); + + CVMPIFile_Memory *pFile = new CVMPIFile_Memory; + pFile->Init( data.Base(), data.Count(), strchr( pOptions, 't' ) ? 't' : 'b' ); + return (FileHandle_t)pFile; +} + + +void CMasterVMPIFileSystem::OnClientDisconnect( int procID, const char *pReason ) +{ + s_pMasterVMPIFileSystem->m_MasterThread.OnClientDisconnect( procID ); +} + + +void CMasterVMPIFileSystem::CreateVirtualFile( const char *pFilename, const void *pData, int fileLength ) +{ + m_MasterThread.CreateVirtualFile( pFilename, pData, fileLength ); +} + +bool CMasterVMPIFileSystem::HandleFileSystemPacket( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + // Handle this packet. + int subPacketID = pBuf->data[1]; + switch( subPacketID ) + { + case VMPI_FSPACKETID_FILE_REQUEST: + { + int requestID = *((int*)&pBuf->data[2]); + const char *pFilename = (const char*)&pBuf->data[6]; + const char *pPathID = (const char*)pFilename + strlen( pFilename ) + 1; + + if ( g_iVMPIVerboseLevel >= 2 ) + Msg( "Client %d requested '%s'\n", iSource, pFilename ); + + bool bZeroLength; + int fileID = m_MasterThread.AddFileRequest( pFilename, pPathID, iSource, &bZeroLength ); + + // Send back the file ID. + unsigned char cPacket[2] = { VMPI_PACKETID_FILESYSTEM, VMPI_FSPACKETID_FILE_RESPONSE }; + void *pChunks[4] = { cPacket, &requestID, &fileID, &bZeroLength }; + int chunkLen[4] = { sizeof( cPacket ), sizeof( requestID ), sizeof( fileID ), sizeof( bZeroLength ) }; + + VMPI_SendChunks( pChunks, chunkLen, ARRAYSIZE( pChunks ), iSource ); + } + return true; + + case VMPI_FSPACKETID_CHUNK_RECEIVED: + { + unsigned short *pFileID = (unsigned short*)&pBuf->data[2]; + unsigned short *pChunkID = pFileID+1; + + int nChunks = (pBuf->getLen() - 2) / 4; + for ( int i=0; i < nChunks; i++ ) + { + m_MasterThread.OnChunkReceived( *pFileID, iSource, *pChunkID ); + pFileID += 2; + pChunkID += 2; + } + } + return true; + + case VMPI_FSPACKETID_FILE_RECEIVED: + { + unsigned short *pFileID = (unsigned short*)&pBuf->data[2]; + m_MasterThread.OnFileReceived( *pFileID, iSource ); + } + return true; + + default: + return false; + } +} + + +CSysModule* CMasterVMPIFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ) +{ + return m_pMasterVMPIFileSystemPassThru->LoadModule( pFileName, pPathID, bValidatedDllOnly ); +} + +void CMasterVMPIFileSystem::UnloadModule( CSysModule *pModule ) +{ + m_pMasterVMPIFileSystemPassThru->UnloadModule( pModule ); +} + diff --git a/utils/vmpi/vmpi_filesystem_worker.cpp b/utils/vmpi/vmpi_filesystem_worker.cpp new file mode 100644 index 0000000..ef5820d --- /dev/null +++ b/utils/vmpi/vmpi_filesystem_worker.cpp @@ -0,0 +1,815 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include <winsock2.h> +#include "vmpi_filesystem_internal.h" +#include "threadhelpers.h" +#include "zlib.h" + + +#define NUM_BUFFERED_CHUNK_ACKS 512 +#define ACK_FLUSH_INTERVAL 500 // Flush the ack queue twice per second. + + +static bool g_bReceivedMulticastIP = false; +static CIPAddr g_MulticastIP; + + +CCriticalSection g_FileResponsesCS; + +class CFileResponse +{ +public: + int m_RequestID; + int m_Response; + bool m_bZeroLength; +}; + +CUtlVector<CFileResponse> g_FileResponses; +int g_RequestID = 0; + + +class CFileChunkPacket +{ +public: + int m_Len; + char m_Data[1]; +}; +CUtlLinkedList<CFileChunkPacket*, int> g_FileChunkPackets; // This is also protected by g_FileResponsesCS. + + +// ------------------------------------------------------------------------------------------------------------------------ // +// Classes. +// ------------------------------------------------------------------------------------------------------------------------ // + +class CWorkerFile +{ +public: + const char* GetFilename() { return m_Filename.Base(); } + const char* GetPathID() { return m_PathID.Base(); } + bool IsReadyToRead() const { return m_nChunksToReceive == 0; } + + +public: + CFastTimer m_Timer; // To see how long it takes to download the file. + + // This has to be sent explicitly as part of the file info or else the protocol + // breaks on empty files. + bool m_bZeroLength; + + // This is false until we get any packets about the file. In the packets, + // we find out what the size is supposed to be. + bool m_bGotCompressedSize; + + // The ID the master uses to refer to this file. + int m_FileID; + + CUtlVector<char> m_Filename; + CUtlVector<char> m_PathID; + + // First data comes in here, then when it's all there, it is inflated into m_UncompressedData. + CUtlVector<char> m_CompressedData; + + // 1 bit for each chunk. + CUtlVector<unsigned char> m_ChunksReceived; + + // When this is zero, the file is done being received and m_UncompressedData is valid. + int m_nChunksToReceive; + CUtlVector<char> m_UncompressedData; +}; + + + +// ------------------------------------------------------------------------------------------------------------------------ // +// Global helpers. +// ------------------------------------------------------------------------------------------------------------------------ // + +static void RecvMulticastIP( CIPAddr *pAddr ) +{ + while ( !g_bReceivedMulticastIP ) + VMPI_DispatchNextMessage(); + + *pAddr = g_MulticastIP; +} + + +static bool ZLibDecompress( const void *pInput, int inputLen, void *pOut, int outLen ) +{ + if ( inputLen == 0 ) + { + // Zero-length file? + return true; + } + + z_stream decompressStream; + + // Initialize the decompression stream. + memset( &decompressStream, 0, sizeof( decompressStream ) ); + if ( inflateInit( &decompressStream ) != Z_OK ) + return false; + + // Decompress all this stuff and write it to the file. + decompressStream.next_in = (unsigned char*)pInput; + decompressStream.avail_in = inputLen; + + char *pOutChar = (char*)pOut; + while ( decompressStream.avail_in ) + { + decompressStream.total_out = 0; + decompressStream.next_out = (unsigned char*)pOutChar; + decompressStream.avail_out = outLen - (pOutChar - (char*)pOut); + + int ret = inflate( &decompressStream, Z_NO_FLUSH ); + if ( ret != Z_OK && ret != Z_STREAM_END ) + return false; + + + pOutChar += decompressStream.total_out; + + if ( ret == Z_STREAM_END ) + { + if ( (pOutChar - (char*)pOut) == outLen ) + { + return true; + } + else + { + Assert( false ); + return false; + } + } + } + + Assert( false ); // Should have gotten to Z_STREAM_END. + return false; +} + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CWorkerMulticastListener implementation. +// ------------------------------------------------------------------------------------------------------------------------ // + +class CWorkerMulticastListener +{ +public: + CWorkerMulticastListener() + { + m_nUnfinishedFiles = 0; + } + + ~CWorkerMulticastListener() + { + Term(); + } + + bool Init( const CIPAddr &mcAddr ) + { + m_MulticastAddr = mcAddr; + m_hMainThread = GetCurrentThread(); + return true; + } + + void Term() + { + m_WorkerFiles.PurgeAndDeleteElements(); + } + + + CWorkerFile* RequestFileFromServer( const char *pFilename, const char *pPathID ) + { + Assert( pPathID ); + Assert( FindWorkerFile( pFilename, pPathID ) == NULL ); + + // Send a request to the master to find out if this file even exists. + CCriticalSectionLock csLock( &g_FileResponsesCS ); + csLock.Lock(); + int requestID = g_RequestID++; + csLock.Unlock(); + + unsigned char packetID[2] = { VMPI_PACKETID_FILESYSTEM, VMPI_FSPACKETID_FILE_REQUEST }; + const void *pChunks[4] = { packetID, &requestID, (void*)pFilename, pPathID }; + int chunkLengths[4] = { sizeof( packetID ), sizeof( requestID ), strlen( pFilename ) + 1, strlen( pPathID ) + 1 }; + VMPI_SendChunks( pChunks, chunkLengths, ARRAYSIZE( pChunks ), 0 ); + + // Wait for the file ID to come back. + CFileResponse response; + response.m_Response = -1; + response.m_bZeroLength = true; + + // We're in a worker thread.. the main thread should be dispatching all the messages, so let it + // do that until we get our response. + while ( 1 ) + { + bool bGotIt = false; + csLock.Lock(); + for ( int iResponse=0; iResponse < g_FileResponses.Count(); iResponse++ ) + { + if ( g_FileResponses[iResponse].m_RequestID == requestID ) + { + response = g_FileResponses[iResponse]; + g_FileResponses.Remove( iResponse ); + bGotIt = true; + break; + } + } + csLock.Unlock(); + + if ( bGotIt ) + break; + + if ( GetCurrentThread() == m_hMainThread ) + VMPI_DispatchNextMessage( 20 ); + else + Sleep( 20 ); + } + + // If we get -1 back, it means the file doesn't exist. + int fileID = response.m_Response; + if ( fileID == -1 ) + return NULL; + + CWorkerFile *pTestFile = new CWorkerFile; + + pTestFile->m_Filename.SetSize( strlen( pFilename ) + 1 ); + strcpy( pTestFile->m_Filename.Base(), pFilename ); + + pTestFile->m_PathID.SetSize( strlen( pPathID ) + 1 ); + strcpy( pTestFile->m_PathID.Base(), pPathID ); + + pTestFile->m_FileID = fileID; + pTestFile->m_nChunksToReceive = 9999; + pTestFile->m_Timer.Start(); + m_WorkerFiles.AddToTail( pTestFile ); + pTestFile->m_bGotCompressedSize = false; + pTestFile->m_bZeroLength = response.m_bZeroLength; + + ++m_nUnfinishedFiles; + + return pTestFile; + } + + void FlushAckChunks( unsigned short chunksToAck[NUM_BUFFERED_CHUNK_ACKS][2], int &nChunksToAck, DWORD &lastAckTime ) + { + if ( nChunksToAck ) + { + // Tell the master we received this chunk. + unsigned char packetID[2] = { VMPI_PACKETID_FILESYSTEM, VMPI_FSPACKETID_CHUNK_RECEIVED }; + void *pChunks[2] = { packetID, chunksToAck }; + int chunkLengths[2] = { sizeof( packetID ), nChunksToAck * 4 }; + VMPI_SendChunks( pChunks, chunkLengths, 2, 0 ); + nChunksToAck = 0; + } + + lastAckTime = GetTickCount(); + } + + void MaybeFlushAckChunks( unsigned short chunksToAck[NUM_BUFFERED_CHUNK_ACKS][2], int &nChunksToAck, DWORD &lastAckTime ) + { + if ( nChunksToAck && GetTickCount() - lastAckTime > ACK_FLUSH_INTERVAL ) + FlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + } + + void AddAckChunk( + unsigned short chunksToAck[NUM_BUFFERED_CHUNK_ACKS][2], + int &nChunksToAck, + DWORD &lastAckTime, + int fileID, + int iChunk ) + { + chunksToAck[nChunksToAck][0] = (unsigned short)fileID; + chunksToAck[nChunksToAck][1] = (unsigned short)iChunk; + + // TCP filesystem acks all chunks immediately so it'll send more. + ++nChunksToAck; + if ( nChunksToAck == NUM_BUFFERED_CHUNK_ACKS || VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_TCP ) + { + FlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + } + } + + // Returns the length of the packet's data or -1 if there is nothing. + int CheckFileChunkPackets( char *data, int dataSize ) + { + // Using TCP.. pop the next received packet off the stack. + CCriticalSectionLock csLock( &g_FileResponsesCS ); + csLock.Lock(); + if ( g_FileChunkPackets.Count() <= 0 ) + return -1; + + CFileChunkPacket *pPacket = g_FileChunkPackets[ g_FileChunkPackets.Head() ]; + g_FileChunkPackets.Remove( g_FileChunkPackets.Head() ); + + // Yes, this is inefficient, but the amount of data we're handling here is tiny so the + // effect is negligible. + int len; + if ( pPacket->m_Len > dataSize ) + { + len = -1; + Warning( "CWorkerMulticastListener::ListenFor: Got a section of data too long (%d bytes).", pPacket->m_Len ); + } + else + { + memcpy( data, pPacket->m_Data, pPacket->m_Len ); + len = pPacket->m_Len; + } + + free( pPacket ); + return len; + } + + void ShowSDKWorkerMsg( const char *pMsg, ... ) + { + if ( !g_bMPIMaster && VMPI_IsSDKMode() ) + { + va_list marker; + va_start( marker, pMsg ); + char str[4096]; + V_vsnprintf( str, sizeof( str ), pMsg, marker ); + va_end( marker ); + Msg( "%s", str ); + } + } + + // This is the main function the workers use to pick files out of the multicast stream. + // The app is waiting for a specific file, but we receive and ack any files we can until + // we get the file they're looking for, then we return. + // + // NOTE: ideally, this would be in a thread, but it adds lots of complications and may + // not be worth it. + CWorkerFile* ListenFor( const char *pFilename, const char *pPathID ) + { + CWorkerFile *pFile = FindWorkerFile( pFilename, pPathID ); + if ( !pFile ) + { + // Ok, we haven't requested this file yet. Create an entry for it and + // tell the master we'd like this file. + pFile = RequestFileFromServer( pFilename, pPathID ); + if ( !pFile ) + return NULL; + + // If it's zero-length, we can return right now. + if ( pFile->m_bZeroLength ) + { + --m_nUnfinishedFiles; + return pFile; + } + } + + // Setup a filename to print some debug spew with. + char printableFilename[58]; + if ( V_strlen( pFilename ) > ARRAYSIZE( printableFilename ) - 1 ) + { + V_strncpy( printableFilename, "[...]", sizeof( printableFilename ) ); + V_strncat( printableFilename, &pFilename[V_strlen(pFilename) - ARRAYSIZE(printableFilename) + 1 + V_strlen(printableFilename)], sizeof( printableFilename ) ); + } + else + { + V_strncpy( printableFilename, pFilename, sizeof( printableFilename ) ); + } + ShowSDKWorkerMsg( "\rRecv %s (0%%) ", printableFilename ); + int iChunkPayloadSize = VMPI_GetChunkPayloadSize(); + + // Now start listening to the stream. + // Note: no need to setup anything when in TCP mode - we just use the regular + // VMPI dispatch stuff to handle that. + ISocket *pSocket = NULL; + if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_MULTICAST ) + { + pSocket = CreateMulticastListenSocket( m_MulticastAddr ); + + if ( !pSocket ) + { + char str[512]; + IP_GetLastErrorString( str, sizeof( str ) ); + Warning( "CreateMulticastListenSocket (%d.%d.%d.%d:%d) failed\n%s\n", EXPAND_ADDR( m_MulticastAddr ), str ); + return NULL; + } + } + else if ( VMPI_GetFileSystemMode() == VMPI_FILESYSTEM_BROADCAST ) + { + pSocket = CreateIPSocket(); + if ( !pSocket->BindToAny( m_MulticastAddr.port ) ) + { + pSocket->Release(); + pSocket = NULL; + } + } + + unsigned short chunksToAck[NUM_BUFFERED_CHUNK_ACKS][2]; + int nChunksToAck = 0; + DWORD lastAckTime = GetTickCount(); + + // Now just receive multicast data until this file has been received. + while ( m_nUnfinishedFiles > 0 ) + { + char data[MAX_CHUNK_PAYLOAD_SIZE+1024]; + int len = -1; + + if ( pSocket ) + { + CIPAddr ipFrom; + len = pSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + } + else + { + len = CheckFileChunkPackets( data, sizeof( data ) ); + } + + if ( len == -1 ) + { + // Sleep for 10ms and also handle socket errors. + Sleep( 0 ); + VMPI_DispatchNextMessage( 10 ); + continue; + } + + g_nMulticastBytesReceived += len; + + // Alrighty. Figure out what the deal is with this file. + CMulticastFileInfo *pInfo = (CMulticastFileInfo*)data; + int *piChunk = (int*)( pInfo + 1 ); + const char *pTestFilename = (const char*)( piChunk + 1 ); + const char *pPayload = pTestFilename + strlen( pFilename ) + 1; + int payloadLen = len - ( pPayload - data ); + if ( payloadLen < 0 ) + { + Warning( "CWorkerMulticastListener::ListenFor: invalid packet received on multicast group\n" ); + continue; + } + + + if ( pInfo->m_FileID != pFile->m_FileID ) + continue; + + CWorkerFile *pTestFile = FindWorkerFile( pInfo->m_FileID ); + if ( !pTestFile ) + Error( "FindWorkerFile( %s ) failed\n", pTestFilename ); + + // TODO: reenable this code and disable the if right above here. + // We always get "invalid payload length" errors on the workers when using this, but + // I haven't been able to figure out why yet. + /* + // Put the data into whatever file it belongs in. + if ( !pTestFile ) + { + pTestFile = RequestFileFromServer( pTestFilename ); + if ( !pTestFile ) + continue; + } + */ + + // Is this the first packet about this file? + if ( !pTestFile->m_bGotCompressedSize ) + { + pTestFile->m_bGotCompressedSize = true; + pTestFile->m_CompressedData.SetSize( pInfo->m_CompressedSize ); + pTestFile->m_UncompressedData.SetSize( pInfo->m_UncompressedSize ); + pTestFile->m_ChunksReceived.SetSize( PAD_NUMBER( pInfo->m_nChunks, 8 ) / 8 ); + pTestFile->m_nChunksToReceive = pInfo->m_nChunks; + memset( pTestFile->m_ChunksReceived.Base(), 0, pTestFile->m_ChunksReceived.Count() ); + } + + // Validate the chunk index and uncompressed size. + int iChunk = *piChunk; + if ( iChunk < 0 || iChunk >= pInfo->m_nChunks ) + { + Error( "ListenFor(): invalid chunk index (%d) for file '%s'\n", iChunk, pTestFilename ); + } + + // Only handle this if we didn't already received the chunk. + if ( !(pTestFile->m_ChunksReceived[iChunk >> 3] & (1 << (iChunk & 7))) ) + { + // Make sure the file is properly setup to receive the data into. + if ( (int)pInfo->m_UncompressedSize != pTestFile->m_UncompressedData.Count() || + (int)pInfo->m_CompressedSize != pTestFile->m_CompressedData.Count() ) + { + Error( "ListenFor(): invalid compressed or uncompressed size.\n" + "pInfo = '%s', pTestFile = '%s'\n" + "Compressed (pInfo = %d, pTestFile = %d)\n" + "Uncompressed (pInfo = %d, pTestFile = %d)\n", + pTestFilename, + pTestFile->GetFilename(), + pInfo->m_CompressedSize, + pTestFile->m_CompressedData.Count(), + pInfo->m_UncompressedSize, + pTestFile->m_UncompressedData.Count() + ); + } + + int iChunkStart = iChunk * iChunkPayloadSize; + int iChunkEnd = min( iChunkStart + iChunkPayloadSize, pTestFile->m_CompressedData.Count() ); + int chunkLen = iChunkEnd - iChunkStart; + + if ( chunkLen != payloadLen ) + { + Error( "ListenFor(): invalid payload length for '%s' (%d should be %d)\n" + "pInfo = '%s', pTestFile = '%s'\n" + "Chunk %d out of %d. Compressed size: %d\n", + pTestFile->GetFilename(), + payloadLen, + chunkLen, + pTestFilename, + pTestFile->GetFilename(), + iChunk, + pInfo->m_nChunks, + pInfo->m_CompressedSize + ); + } + + memcpy( &pTestFile->m_CompressedData[iChunkStart], pPayload, chunkLen ); + pTestFile->m_ChunksReceived[iChunk >> 3] |= (1 << (iChunk & 7)); + + --pTestFile->m_nChunksToReceive; + + if ( pTestFile == pFile ) + { + int percent = 100 - (100 * pFile->m_nChunksToReceive) / pInfo->m_nChunks; + ShowSDKWorkerMsg( "\rRecv %s (%d%%) [chunk %d/%d] ", printableFilename, percent, pInfo->m_nChunks - pFile->m_nChunksToReceive, pInfo->m_nChunks ); + } + + // Remember to ack what we received. + AddAckChunk( chunksToAck, nChunksToAck, lastAckTime, pInfo->m_FileID, iChunk ); + + // If we're done receiving the data, unpack it. + if ( pTestFile->m_nChunksToReceive == 0 ) + { + // Ack the file. + FlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + + pTestFile->m_Timer.End(); + + pTestFile->m_UncompressedData.SetSize( pInfo->m_UncompressedSize ); + --m_nUnfinishedFiles; + + if ( !ZLibDecompress( + pTestFile->m_CompressedData.Base(), + pTestFile->m_CompressedData.Count(), + pTestFile->m_UncompressedData.Base(), + pTestFile->m_UncompressedData.Count() ) ) + { + if ( pSocket ) + pSocket->Release(); + FlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + Error( "ZLibDecompress failed.\n" ); + return NULL; + } + + char str[512]; + V_snprintf( str, sizeof( str ), "Got %s (%dk) in %.2fs", + printableFilename, + (pTestFile->m_UncompressedData.Count() + 511) / 1024, + pTestFile->m_Timer.GetDuration().GetSeconds() + ); + Msg( "\r%-79s\n", str ); + + // Won't be needing this anymore. + pTestFile->m_CompressedData.Purge(); + } + } + + MaybeFlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + } + + Assert( pFile->IsReadyToRead() ); + FlushAckChunks( chunksToAck, nChunksToAck, lastAckTime ); + if ( pSocket ) + pSocket->Release(); + + return pFile; + } + + CWorkerFile* FindWorkerFile( const char *pFilename, const char *pPathID ) + { + FOR_EACH_LL( m_WorkerFiles, i ) + { + CWorkerFile *pWorkerFile = m_WorkerFiles[i]; + + if ( stricmp( pWorkerFile->GetFilename(), pFilename ) == 0 && stricmp( pWorkerFile->GetPathID(), pPathID ) == 0 ) + return pWorkerFile; + } + return NULL; + } + + CWorkerFile* FindWorkerFile( int fileID ) + { + FOR_EACH_LL( m_WorkerFiles, i ) + { + if ( m_WorkerFiles[i]->m_FileID == fileID ) + return m_WorkerFiles[i]; + } + return NULL; + } + + +private: + CIPAddr m_MulticastAddr; + + CUtlLinkedList<CWorkerFile*, int> m_WorkerFiles; + + HANDLE m_hMainThread; + + // How many files do we have open that we haven't finished receiving from the server yet? + // We always keep waiting for data until this is zero. + int m_nUnfinishedFiles; +}; + + + +// ------------------------------------------------------------------------------------------------------------------------ // +// CWorkerVMPIFileSystem implementation. +// ------------------------------------------------------------------------------------------------------------------------ // + +class CWorkerVMPIFileSystem : public CBaseVMPIFileSystem +{ +public: + InitReturnVal_t Init(); + virtual void Term(); + + virtual FileHandle_t Open( const char *pFilename, const char *pOptions, const char *pathID ); + virtual bool HandleFileSystemPacket( MessageBuffer *pBuf, int iSource, int iPacketID ); + + virtual void CreateVirtualFile( const char *pFilename, const void *pData, int fileLength ); + virtual long GetFileTime( const char *pFileName, const char *pathID ); + virtual bool IsFileWritable( const char *pFileName, const char *pPathID ); + virtual bool SetFileWritable( char const *pFileName, bool writable, const char *pPathID ); + + virtual CSysModule *LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ); + virtual void UnloadModule( CSysModule *pModule ); + +private: + CWorkerMulticastListener m_Listener; +}; + + +CBaseVMPIFileSystem* CreateWorkerVMPIFileSystem() +{ + CWorkerVMPIFileSystem *pRet = new CWorkerVMPIFileSystem; + g_pBaseVMPIFileSystem = pRet; + if ( pRet->Init() ) + { + return pRet; + } + else + { + delete pRet; + g_pBaseVMPIFileSystem = NULL; + return NULL; + } +} + + +InitReturnVal_t CWorkerVMPIFileSystem::Init() +{ + // Get the multicast addr to listen on. + CIPAddr mcAddr; + RecvMulticastIP( &mcAddr ); + + return m_Listener.Init( mcAddr ) ? INIT_OK : INIT_FAILED; +} + + +void CWorkerVMPIFileSystem::Term() +{ + m_Listener.Term(); +} + + +FileHandle_t CWorkerVMPIFileSystem::Open( const char *pFilename, const char *pOptions, const char *pathID ) +{ + Assert( g_bUseMPI ); + + // When it finally asks the filesystem for a file, it'll pass NULL for pathID if it's "". + if ( !pathID ) + pathID = ""; + + if ( g_bDisableFileAccess ) + Error( "Open( %s, %s ) - file access has been disabled.", pFilename, pOptions ); + + // Workers can't open anything for write access. + bool bWriteAccess = (Q_stristr( pOptions, "w" ) != 0); + if ( bWriteAccess ) + return FILESYSTEM_INVALID_HANDLE; + + // Do we have this file's data already? + CWorkerFile *pFile = m_Listener.FindWorkerFile( pFilename, pathID ); + if ( !pFile || !pFile->IsReadyToRead() ) + { + // Ok, start listening to the multicast stream until we get the file we want. + + // NOTE: it might make sense here to have the client ask for a list of ALL the files that + // the master currently has and wait to receive all of them (so we don't come back a bunch + // of times and listen + + // NOTE NOTE: really, the best way to do this is to have a thread on the workers that sits there + // and listens to the multicast stream. Any time the master opens a new file up, it assumes + // all the workers need the file, and it starts to send it on the multicast stream until + // the worker threads respond that they all have it. + // + // (NOTE: this probably means that the clients would have to ack the chunks on a UDP socket that + // the thread owns). + // + // This would simplify all the worries about a client missing half the stream and having to + // wait for another cycle through it. + pFile = m_Listener.ListenFor( pFilename, pathID ); + + if ( !pFile ) + { + return FILESYSTEM_INVALID_HANDLE; + } + } + + // Ok! Got the file. now setup a memory stream they can read out of it with. + CVMPIFile_Memory *pOut = new CVMPIFile_Memory; + pOut->Init( pFile->m_UncompressedData.Base(), pFile->m_UncompressedData.Count(), strchr( pOptions, 't' ) ? 't' : 'b' ); + return (FileHandle_t)pOut; +} + + +void CWorkerVMPIFileSystem::CreateVirtualFile( const char *pFilename, const void *pData, int fileLength ) +{ + Error( "CreateVirtualFile not supported in VMPI worker filesystem." ); +} + + +long CWorkerVMPIFileSystem::GetFileTime( const char *pFileName, const char *pathID ) +{ + Error( "GetFileTime not supported in VMPI worker filesystem." ); + return 0; +} + + +bool CWorkerVMPIFileSystem::IsFileWritable( const char *pFileName, const char *pPathID ) +{ + Error( "GetFileTime not supported in VMPI worker filesystem." ); + return false; +} + + +bool CWorkerVMPIFileSystem::SetFileWritable( char const *pFileName, bool writable, const char *pPathID ) +{ + Error( "GetFileTime not supported in VMPI worker filesystem." ); + return false; +} + +bool CWorkerVMPIFileSystem::HandleFileSystemPacket( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + // Handle this packet. + int subPacketID = pBuf->data[1]; + switch( subPacketID ) + { + case VMPI_FSPACKETID_MULTICAST_ADDR: + { + char *pInPos = &pBuf->data[2]; + + g_MulticastIP = *((CIPAddr*)pInPos); + pInPos += sizeof( g_MulticastIP ); + + g_bReceivedMulticastIP = true; + } + return true; + + case VMPI_FSPACKETID_FILE_RESPONSE: + { + CCriticalSectionLock csLock( &g_FileResponsesCS ); + csLock.Lock(); + + CFileResponse res; + res.m_RequestID = *((int*)&pBuf->data[2]); + res.m_Response = *((int*)&pBuf->data[6]); + res.m_bZeroLength = *((bool*)&pBuf->data[10]); + + g_FileResponses.AddToTail( res ); + } + return true; + + case VMPI_FSPACKETID_FILE_CHUNK: + { + int nDataBytes = pBuf->getLen() - 2; + + CFileChunkPacket *pPacket = (CFileChunkPacket*)malloc( sizeof( CFileChunkPacket ) + nDataBytes - 1 ); + memcpy( pPacket->m_Data, &pBuf->data[2], nDataBytes ); + pPacket->m_Len = nDataBytes; + + CCriticalSectionLock csLock( &g_FileResponsesCS ); + csLock.Lock(); + g_FileChunkPackets.AddToTail( pPacket ); + } + return true; + + default: + return false; + } +} + +CSysModule* CWorkerVMPIFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ) +{ + return Sys_LoadModule( pFileName ); +} + +void CWorkerVMPIFileSystem::UnloadModule( CSysModule *pModule ) +{ + Sys_UnloadModule( pModule ); +} diff --git a/utils/vmpi/vmpi_job_search/JobSearchDlg.cpp b/utils/vmpi/vmpi_job_search/JobSearchDlg.cpp new file mode 100644 index 0000000..e98b726 --- /dev/null +++ b/utils/vmpi/vmpi_job_search/JobSearchDlg.cpp @@ -0,0 +1,473 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// JobSearchDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "JobSearchDlg.h" +#include "imysqlwrapper.h" +#include "tier1/strtools.h" +#include "utllinkedlist.h" +#include "vmpi_browser_helpers.h" +#include "vmpi_defs.h" +#include "net_view_thread.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +// These are stored with jobs to help with sorting and to remember the job ID. +class CJobInfo +{ +public: + unsigned long m_JobID; + CString m_StartTimeUnformatted; + CString m_MachineName; + CString m_BSPFilename; + DWORD m_RunningTimeMS; +}; + + + +///////////////////////////////////////////////////////////////////////////// +// CJobSearchDlg dialog + + +CJobSearchDlg::CJobSearchDlg(CWnd* pParent /*=NULL*/) + : CDialog(CJobSearchDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CJobSearchDlg) + //}}AFX_DATA_INIT + m_pSQL = NULL; + m_hMySQLDLL = NULL; +} + + +CJobSearchDlg::~CJobSearchDlg() +{ + if ( m_pSQL ) + { + m_pSQL->Release(); + } + + if ( m_hMySQLDLL ) + { + Sys_UnloadModule( m_hMySQLDLL ); + } +} + + +void CJobSearchDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CJobSearchDlg) + DDX_Control(pDX, IDC_WORKER_LIST, m_WorkerList); + DDX_Control(pDX, IDC_USER_LIST, m_UserList); + DDX_Control(pDX, IDC_JOBS_LIST, m_JobsList); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CJobSearchDlg, CDialog) + //{{AFX_MSG_MAP(CJobSearchDlg) + ON_NOTIFY(NM_DBLCLK, IDC_JOBS_LIST, OnDblclkJobsList) + ON_LBN_DBLCLK(IDC_USER_LIST, OnDblclkUserList) + ON_LBN_DBLCLK(IDC_WORKER_LIST, OnDblclkWorkerList) + ON_BN_CLICKED(IDC_QUIT, OnQuit) + ON_WM_SIZE() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +int CJobSearchDlg::GetSelectedJobIndex() +{ + POSITION pos = m_JobsList.GetFirstSelectedItemPosition(); + if ( pos ) + return m_JobsList.GetNextSelectedItem( pos ); + else + return -1; +} + + +///////////////////////////////////////////////////////////////////////////// +// CJobSearchDlg message handlers + +void CJobSearchDlg::OnDblclkJobsList(NMHDR* pNMHDR, LRESULT* pResult) +{ + int iItem = GetSelectedJobIndex(); + if ( iItem != -1 ) + { + CJobInfo *pInfo = (CJobInfo*)m_JobsList.GetItemData( iItem ); + + CString cmdLine; + cmdLine.Format( "vmpi_job_watch -JobID %d -dbname \"%s\" -hostname \"%s\" -username \"%s\"", + pInfo->m_JobID, (const char*)m_DBName, (const char*)m_HostName, (const char*)m_UserName ); + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( !CreateProcess( + NULL, + (char*)(const char*)cmdLine, + NULL, // security + NULL, + TRUE, + 0, // flags + NULL, // environment + NULL, // current directory + &si, + &pi ) ) + { + CString errStr; + errStr.Format( "Error launching '%s'", cmdLine.GetBuffer() ); + MessageBox( errStr, "Error", MB_OK ); + } + } + + *pResult = 0; +} + + +static int CALLBACK JobsSortFn( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CJobInfo *pInfo1 = (CJobInfo*)iItem1; + CJobInfo *pInfo2 = (CJobInfo*)iItem2; + + return strcmp( pInfo2->m_StartTimeUnformatted, pInfo1->m_StartTimeUnformatted ); +} + + +void CJobSearchDlg::ClearJobsList() +{ + // First, delete all the JobInfo structures we have in it. + int nItems = m_JobsList.GetItemCount(); + for ( int i=0; i < nItems; i++ ) + { + CJobInfo *pInfo = (CJobInfo*)m_JobsList.GetItemData( i ); + delete pInfo; + } + + m_JobsList.DeleteAllItems(); +} + + +void CJobSearchDlg::RepopulateJobsList() +{ + // It's assumed coming into this routine that the caller just executed a query that we can iterate over. + ClearJobsList(); + + CUtlLinkedList<CJobInfo*, int> jobInfos; + while ( GetMySQL()->NextRow() ) + { + CJobInfo *pInfo = new CJobInfo; + pInfo->m_StartTimeUnformatted = GetMySQL()->GetColumnValue( "StartTime" ).String(); + pInfo->m_JobID = GetMySQL()->GetColumnValue( "JobID" ).Int32(); + pInfo->m_MachineName = GetMySQL()->GetColumnValue( "MachineName" ).String(); + pInfo->m_BSPFilename = GetMySQL()->GetColumnValue( "BSPFilename" ).String(); + pInfo->m_RunningTimeMS = GetMySQL()->GetColumnValue( "RunningTimeMS" ).Int32(); + + jobInfos.AddToTail( pInfo ); + } + + + FOR_EACH_LL( jobInfos, j ) + { + CJobInfo *pInfo = jobInfos[j]; + + // Add the item. + int iItem = m_JobsList.InsertItem( 0, "", NULL ); + + // Associate it with the job structure. + m_JobsList.SetItemData( iItem, (DWORD)pInfo ); + + char dateStr[128]; + const char *pDate = pInfo->m_StartTimeUnformatted; + if ( strlen( pDate ) == 14 ) // yyyymmddhhmmss + { + Q_snprintf( dateStr, sizeof( dateStr ), "%c%c/%c%c %c%c:%c%c:00", + pDate[4], pDate[5], + pDate[6], pDate[7], + pDate[8], pDate[9], + pDate[10], pDate[11] ); + } + + m_JobsList.SetItemText( iItem, 0, dateStr ); + m_JobsList.SetItemText( iItem, 1, pInfo->m_MachineName ); + m_JobsList.SetItemText( iItem, 2, pInfo->m_BSPFilename ); + + char timeStr[512]; + if ( pInfo->m_RunningTimeMS == RUNNINGTIME_MS_SENTINEL ) + { + Q_strncpy( timeStr, "?", sizeof( timeStr ) ); + } + else + { + FormatTimeString( pInfo->m_RunningTimeMS / 1000, timeStr, sizeof( timeStr ) ); + } + m_JobsList.SetItemText( iItem, 3, timeStr ); + + char jobIDStr[512]; + Q_snprintf( jobIDStr, sizeof( jobIDStr ), "%d", pInfo->m_JobID ); + m_JobsList.SetItemText( iItem, 4, jobIDStr ); + } + + m_JobsList.SortItems( JobsSortFn, (LPARAM)&m_JobsList ); +} + + +void CJobSearchDlg::OnDblclkUserList() +{ + int sel = m_UserList.GetCurSel(); + if ( sel != LB_ERR ) + { + CString computerName; + m_UserList.GetText( sel, computerName ); + + // Look for jobs that this user initiated. + char query[4096]; + Q_snprintf( query, sizeof( query ), "select RunningTimeMS, JobID, BSPFilename, StartTime, MachineName from job_master_start where MachineName=\"%s\"", (const char*)computerName ); + GetMySQL()->Execute( query ); + + RepopulateJobsList(); + } +} + +void CJobSearchDlg::OnDblclkWorkerList() +{ + int sel = m_WorkerList.GetCurSel(); + if ( sel != LB_ERR ) + { + CString computerName; + m_WorkerList.GetText( sel, computerName ); + + // This query does: + // 1. Take the workers with the specified MachineName. + // 2. Only use IsMaster = 0. + // 3. Now get all the job_master_start records with the same JobID. + char query[4096]; + Q_snprintf( query, sizeof( query ), "select job_master_start.RunningTimeMS, job_master_start.JobID, job_master_start.BSPFilename, job_master_start.StartTime, job_master_start.MachineName " + "from job_master_start, job_worker_start " + "where job_worker_start.MachineName = \"%s\" and " + "IsMaster = 0 and " + "job_master_start.JobID = job_worker_start.JobID", + (const char*)computerName ); + GetMySQL()->Execute( query ); + + RepopulateJobsList(); + } +} + +bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) +{ + int i=0; + for ( i; i < strSize-2; i++ ) + { + if ( fread( &pStr[i], 1, 1, fp ) != 1 || + pStr[i] == '\n' ) + { + break; + } + } + + pStr[i] = 0; + return i != 0; +} + +const char* FindArg( const char *pArgName, const char *pDefault="" ) +{ + for ( int i=1; i < __argc; i++ ) + { + if ( Q_stricmp( pArgName, __argv[i] ) == 0 ) + { + if ( (i+1) < __argc ) + return __argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + +BOOL CJobSearchDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + m_JobsList.SetExtendedStyle( LVS_EX_FULLROWSELECT ); + + char str[512]; + + // Init the mysql database. + const char *pDBName = FindArg( "-dbname", NULL ); + const char *pHostName = FindArg( "-hostname", NULL ); + const char *pUserName = FindArg( "-username", NULL ); + + if ( pDBName && pHostName && pUserName ) + { + m_DBName = pDBName; + m_HostName = pHostName; + m_UserName = pUserName; + } + else + { + // Load the dbinfo_browser.txt file to get the database information. + const char *pFilename = FindArg( "-dbinfo", NULL ); + if ( !pFilename ) + pFilename = "dbinfo_job_search.txt"; + + FILE *fp = fopen( pFilename, "rt" ); + if ( !fp ) + { + Q_snprintf( str, sizeof( str ), "Can't open '%s' for database info.", pFilename ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + char hostName[512], dbName[512], userName[512]; + if ( !ReadStringFromFile( fp, hostName, sizeof( hostName ) ) || + !ReadStringFromFile( fp, dbName, sizeof( dbName ) ) || + !ReadStringFromFile( fp, userName, sizeof( userName ) ) + ) + { + fclose( fp ); + Q_snprintf( str, sizeof( str ), "'%s' has invalid format.", pFilename ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + m_DBName = dbName; + m_HostName = hostName; + m_UserName = userName; + + fclose( fp ); + } + + // Get the mysql interface. + if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &m_hMySQLDLL, (void**)&m_pSQL ) ) + return false; + + if ( !m_pSQL->InitMySQL( m_DBName, m_HostName, m_UserName ) ) + { + Q_snprintf( str, sizeof( str ), "Can't init MYSQL db (db = '%s', host = '%s', user = '%s')", (const char*)m_DBName, (const char*)m_HostName, (const char*)m_UserName ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + // Setup the headers for the job info list. + struct + { + char *pText; + int width; + } titles[] = + { + {"Date", 100}, + {"User", 100}, + {"BSP Filename", 100}, + {"Running Time", 100}, + {"Job ID", 100} + }; + for ( int i=0; i < ARRAYSIZE( titles ); i++ ) + { + m_JobsList.InsertColumn( i, titles[i].pText, LVCFMT_LEFT, titles[i].width, i ); + } + + + CUtlVector<char*> computerNames; + CNetViewThread netView; + netView.Init(); + DWORD startTime = GetTickCount(); + while ( 1 ) + { + netView.GetComputerNames( computerNames ); + if ( computerNames.Count() > 0 ) + break; + + Sleep( 30 ); + if ( GetTickCount() - startTime > 5000 ) + { + Q_snprintf( str, sizeof( str ), "Unable to get computer names Can't init MYSQL db (db = '%s', host = '%s', user = '%s')", (const char*)m_DBName, (const char*)m_HostName, (const char*)m_UserName ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + } + + PopulateWorkerList( computerNames ); + PopulateUserList( computerNames ); + + + // Auto-select a worker? + const char *pSelectWorker = FindArg( "-SelectWorker", NULL ); + if ( pSelectWorker ) + { + int index = m_WorkerList.FindString( -1, pSelectWorker ); + if ( index != LB_ERR ) + { + m_WorkerList.SetCurSel( index ); + OnDblclkWorkerList(); + } + } + + + // Setup our anchors. + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_SEARCH_BY_USER_PANEL ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_USER_LIST ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKER_PANEL ), ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKER_LIST ), ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_JOBS_PANEL ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_RIGHT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_JOBS_LIST ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_RIGHT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_QUIT ), ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM, ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM ); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CJobSearchDlg::PopulateWorkerList( CUtlVector<char*> &computerNames ) +{ + m_WorkerList.ResetContent(); + for ( int i=0; i < computerNames.Count(); i++ ) + { + m_WorkerList.AddString( computerNames[i] ); + } +} + +void CJobSearchDlg::PopulateUserList( CUtlVector<char*> &computerNames ) +{ + m_UserList.ResetContent(); + for ( int i=0; i < computerNames.Count(); i++ ) + { + m_UserList.AddString( computerNames[i] ); + } +} + + + +void CJobSearchDlg::OnQuit() +{ + EndDialog( 0 ); +} + +void CJobSearchDlg::OnSize(UINT nType, int cx, int cy) +{ + CDialog::OnSize(nType, cx, cy); + + m_AnchorMgr.UpdateAnchors( this ); +} diff --git a/utils/vmpi/vmpi_job_search/JobSearchDlg.h b/utils/vmpi/vmpi_job_search/JobSearchDlg.h new file mode 100644 index 0000000..6430b8b --- /dev/null +++ b/utils/vmpi/vmpi_job_search/JobSearchDlg.h @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_JOBSEARCHDLG_H__833A83FF_35CC_42B5_AEB3_AE31C7FDF492__INCLUDED_) +#define AFX_JOBSEARCHDLG_H__833A83FF_35CC_42B5_AEB3_AE31C7FDF492__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// JobSearchDlg.h : header file +// + +#include "resource.h" +#include "imysqlwrapper.h" +#include "window_anchor_mgr.h" + + +///////////////////////////////////////////////////////////////////////////// +// CJobSearchDlg dialog + +class CJobSearchDlg : public CDialog +{ +// Construction +public: + CJobSearchDlg(CWnd* pParent = NULL); // standard constructor + virtual ~CJobSearchDlg(); + +// Dialog Data + //{{AFX_DATA(CJobSearchDlg) + enum { IDD = IDD_VMPI_JOB_SEARCH }; + CListBox m_WorkerList; + CListBox m_UserList; + CListCtrl m_JobsList; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CJobSearchDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + void ClearJobsList(); + void RepopulateJobsList(); + + void PopulateWorkerList( CUtlVector<char*> &computerNames ); + void PopulateUserList( CUtlVector<char*> &computerNames ); + + int GetSelectedJobIndex(); + + + // Info on how we connected to the database so we can pass it to apps we launch. + CString m_DBName, m_HostName, m_UserName; + + + IMySQL* GetMySQL() { return m_pSQL; } + IMySQL *m_pSQL; + CSysModule *m_hMySQLDLL; + + CWindowAnchorMgr m_AnchorMgr; + + + // Generated message map functions + //{{AFX_MSG(CJobSearchDlg) + afx_msg void OnDblclkJobsList(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnDblclkUserList(); + afx_msg void OnDblclkWorkerList(); + virtual BOOL OnInitDialog(); + afx_msg void OnQuit(); + afx_msg void OnSize(UINT nType, int cx, int cy); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_JOBSEARCHDLG_H__833A83FF_35CC_42B5_AEB3_AE31C7FDF492__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_search/StdAfx.cpp b/utils/vmpi/vmpi_job_search/StdAfx.cpp new file mode 100644 index 0000000..de47b55 --- /dev/null +++ b/utils/vmpi/vmpi_job_search/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_browser_job_search.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/utils/vmpi/vmpi_job_search/StdAfx.h b/utils/vmpi/vmpi_job_search/StdAfx.h new file mode 100644 index 0000000..88ecf03 --- /dev/null +++ b/utils/vmpi/vmpi_job_search/StdAfx.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__2CCE1890_EB30_4887_B493_6CAB022977E4__INCLUDED_) +#define AFX_STDAFX_H__2CCE1890_EB30_4887_B493_6CAB022977E4__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include "tier0/basetypes.h" + +#include <afxwin.h> // MFC core and standard components +#include <afxext.h> // MFC extensions +#include <afxdisp.h> // MFC Automation classes +#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include <afxcmn.h> // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__2CCE1890_EB30_4887_B493_6CAB022977E4__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.ico b/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.ico Binary files differnew file mode 100644 index 0000000..7eef0bc --- /dev/null +++ b/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.ico diff --git a/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.rc2 b/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.rc2 new file mode 100644 index 0000000..03d7a3c --- /dev/null +++ b/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.rc2 @@ -0,0 +1,13 @@ +// +// VMPI_BROWSER_JOB_SEARCH.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/utils/vmpi/vmpi_job_search/resource.h b/utils/vmpi/vmpi_job_search/resource.h new file mode 100644 index 0000000..0fceaed --- /dev/null +++ b/utils/vmpi/vmpi_job_search/resource.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by vmpi_browser_job_search.rc +// +#define IDD_VMPI_BROWSER_JOB_SEARCH_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_VMPI_JOB_SEARCH 136 +#define IDC_QUIT 1000 +#define IDC_SEARCH_BY_USER_PANEL 1001 +#define IDC_WORKER_PANEL 1002 +#define IDC_JOBS_PANEL 1003 +#define IDC_USER_LIST 1015 +#define IDC_WORKER_LIST 1016 +#define IDC_JOBS_LIST 1017 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.cpp b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.cpp new file mode 100644 index 0000000..c6fd4a1 --- /dev/null +++ b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.cpp @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_search.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "vmpi_browser_job_search.h" +#include "JobSearchDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobSearchApp + +BEGIN_MESSAGE_MAP(CVMPIBrowserJobSearchApp, CWinApp) + //{{AFX_MSG_MAP(CVMPIBrowserJobSearchApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobSearchApp construction + +CVMPIBrowserJobSearchApp::CVMPIBrowserJobSearchApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CVMPIBrowserJobSearchApp object + +CVMPIBrowserJobSearchApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobSearchApp initialization + +BOOL CVMPIBrowserJobSearchApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + + CJobSearchDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.h b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.h new file mode 100644 index 0000000..721b41c --- /dev/null +++ b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_search.h : main header file for the VMPI_BROWSER_JOB_SEARCH application +// + +#if !defined(AFX_VMPI_BROWSER_JOB_SEARCH_H__96197957_586A_4F10_ACEA_EBBB9E6FD619__INCLUDED_) +#define AFX_VMPI_BROWSER_JOB_SEARCH_H__96197957_586A_4F10_ACEA_EBBB9E6FD619__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobSearchApp: +// See vmpi_browser_job_search.cpp for the implementation of this class +// + +class CVMPIBrowserJobSearchApp : public CWinApp +{ +public: + CVMPIBrowserJobSearchApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVMPIBrowserJobSearchApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVMPIBrowserJobSearchApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VMPI_BROWSER_JOB_SEARCH_H__96197957_586A_4F10_ACEA_EBBB9E6FD619__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.rc b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.rc new file mode 100644 index 0000000..4ed763a --- /dev/null +++ b/utils/vmpi/vmpi_job_search/vmpi_browser_job_search.rc @@ -0,0 +1,184 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\vmpi_browser_job_search.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\vmpi_browser_job_search.ico" + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "vmpi_browser_job_search MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "vmpi_browser_job_search\0" + VALUE "LegalCopyright", "Copyright (C) 2003\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "vmpi_browser_job_search.EXE\0" + VALUE "ProductName", "vmpi_browser_job_search Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_VMPI_JOB_SEARCH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 400 + TOPMARGIN, 7 + BOTTOMMARGIN, 338 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_VMPI_JOB_SEARCH DIALOGEX 0, 0, 407, 345 +STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | + WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Job Search" +FONT 8, "MS Sans Serif" +BEGIN + GROUPBOX "Jobs",IDC_JOBS_PANEL,7,153,393,169 + LISTBOX IDC_USER_LIST,13,17,180,128,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Search By User",IDC_SEARCH_BY_USER_PANEL,7,7,192,143 + LISTBOX IDC_WORKER_LIST,211,17,183,128,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Search By Worker",IDC_WORKER_PANEL,205,7,195,143 + CONTROL "List3",IDC_JOBS_LIST,"SysListView32",LVS_REPORT | + LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP,14,164,380, + 153 + PUSHBUTTON "&Quit",IDC_QUIT,192,327,23,14 +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\vmpi_browser_job_search.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_job_search/vmpi_job_search.vpc b/utils/vmpi/vmpi_job_search/vmpi_job_search.vpc new file mode 100644 index 0000000..01dba8b --- /dev/null +++ b/utils/vmpi/vmpi_job_search/vmpi_job_search.vpc @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// vmpi_job_search.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vmpi_job_search" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\,..\mysql\include" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;WINVER=0x501;NO_WARN_MBCS_MFC_DEPRECATION" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $PrecompiledHeaderFile "Debug/vmpi_browser_job_search.pch" + $EnableC++Exceptions "Yes (/EHsc)" + } +} + +$Configuration "Debug" +{ + $Linker + { + // Deprecated MBCS MFC libraries for VS 2013 (nafxcw.lib and nafxcwd.lib) can be downloaded from http://go.microsoft.com/?linkid=9832071 + $AdditionalDependencies "$BASE nafxcwd.lib" + $IgnoreSpecificLibrary "nafxcw.lib libcmt.lib" + } +} + +$Configuration "Release" +{ + $Linker + { + // Deprecated MBCS MFC libraries for VS 2013 (nafxcw.lib and nafxcwd.lib) can be downloaded from http://go.microsoft.com/?linkid=9832071 + $AdditionalDependencies "$BASE nafxcw.lib libcmt.lib" + $IgnoreSpecificLibrary "nafxcwd.lib libcmtd.lib" + } +} + +$Project "vmpi_job_search" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "..\net_view_thread.cpp" + $File "vmpi_browser_job_search.cpp" + $File "vmpi_browser_job_search.rc" + $File "..\window_anchor_mgr.cpp" + + $File "JobSearchDlg.cpp" \ + "..\vmpi_browser_helpers.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Not Using Precompiled Headers" + } + } + } + + $File "StdAfx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + } + + $Folder "Header Files" + { + $File "JobSearchDlg.h" + $File "..\mysql_wrapper.h" + $File "..\net_view_thread.h" + $File "Resource.h" + $File "StdAfx.h" + $File "..\vmpi_browser_helpers.h" + $File "vmpi_browser_job_search.h" + $File "..\window_anchor_mgr.h" + } + + $Folder "Resource Files" + { + $File "res\vmpi_browser_job_search.ico" + $File "res\vmpi_browser_job_search.rc2" + } +} diff --git a/utils/vmpi/vmpi_job_watch/GraphControl.cpp b/utils/vmpi/vmpi_job_watch/GraphControl.cpp new file mode 100644 index 0000000..44d7e57 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/GraphControl.cpp @@ -0,0 +1,246 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// GraphControl.cpp : implementation file +// + +#include "stdafx.h" +#include "vmpi_browser_job_watch.h" +#include "GraphControl.h" +#include "mathlib/mathlib.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CGraphControl + +CGraphControl::CGraphControl() +{ +} + +CGraphControl::~CGraphControl() +{ +} + + +BEGIN_MESSAGE_MAP(CGraphControl, CWnd) + //{{AFX_MSG_MAP(CGraphControl) + ON_WM_PAINT() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +void CGraphControl::Clear() +{ + CRect rcClient; + GetClientRect( rcClient ); + + CDC *pDC = GetDC(); + + CBrush brush( RGB( 0, 0, 0 ) ); + CBrush *pOldBrush = pDC->SelectObject( &brush ); + pDC->Rectangle( 0, 0, rcClient.Width(), rcClient.Height() ); + pDC->SelectObject( pOldBrush ); + + ReleaseDC( pDC ); +} + +void CGraphControl::Render( CDC *pDC ) +{ + // Clear the background. + CRect rcClient; + GetClientRect( rcClient ); + + + CBrush brush( RGB( 0, 0, 0 ) ); + CBrush *pOldBrush = pDC->SelectObject( &brush ); + pDC->Rectangle( 0, 0, rcClient.Width(), rcClient.Height() ); + pDC->SelectObject( pOldBrush ); + + + + // Work backwards from the right side to the left. + int nIntervals = rcClient.Width(); + DWORD intervalMS = 500; // one interval per pixel + DWORD startTime = 0xFFFFFFFF, endTime = 0; + + // First, find which order of magnitude to use on the vertical scale by finding the maximum value. + for ( int iEntry=0; iEntry < m_Entries.Count(); iEntry++ ) + { + DWORD msTime = m_Entries[iEntry].m_msTime; + startTime = min( startTime, msTime ); + endTime = max( endTime, msTime ); + } + + int curTime = (int)endTime - nIntervals*intervalMS; + + + + CGraphEntry prevEntry, curEntry; + prevEntry.m_msTime = curEntry.m_msTime = -1; + + CUtlVector<POINT> sentPoints; + CUtlVector<POINT> receivedPoints; + + int iCurEntry = -1; + int nMaxBytesSent = -1, nMaxBytesReceived = -1; + + for ( int x=0; x < nIntervals; x++ ) + { + if ( curTime >= 0 ) + { + // Now find the graph_entry for the time we're at. + while ( prevEntry.m_msTime == -1 || curTime > curEntry.m_msTime ) + { + ++iCurEntry; + if ( iCurEntry >= m_Entries.Count() ) + goto ENDLOOP; + + prevEntry = curEntry; + curEntry = m_Entries[iCurEntry]; + } + + if ( curTime >= prevEntry.m_msTime && curTime <= curEntry.m_msTime ) + { + // Interpolate the bytes sent. + int nBytesSent = (int)RemapVal( + curTime, + prevEntry.m_msTime, curEntry.m_msTime, + prevEntry.m_nBytesSent, curEntry.m_nBytesSent ); + + POINT sentPoint = { x, nBytesSent }; + sentPoints.AddToTail( sentPoint ); + nMaxBytesSent = max( nMaxBytesSent, nBytesSent ); + + + int nBytesReceived = (int)RemapVal( + curTime, + prevEntry.m_msTime, curEntry.m_msTime, + prevEntry.m_nBytesReceived, curEntry.m_nBytesReceived ); + + POINT receivedPoint = { x, nBytesReceived }; + receivedPoints.AddToTail( receivedPoint ); + nMaxBytesReceived = max( nMaxBytesReceived, nBytesReceived ); + } + } + + curTime += intervalMS; + } + + ENDLOOP:; + + + // Now normalize all the values. + int largest = max( nMaxBytesSent, nMaxBytesReceived ); + int topValue = (largest*11) / 10; +/* + DWORD nZeros; + for( nZeros = 1; nZeros < 20; nZeros++ ) + { + if ( largest < pow( 10, nZeros ) ) + break; + } + + // Now find the value at the top of the graph. We choose the smallest enclosing tenth of the + // order of magnitude we're at (so if we were at 1,000,000, and our max value was 350,000, we'd choose 400,000). + int iTenth; + int topValue; + for ( iTenth=1; iTenth <= 10; iTenth++ ) + { + topValue = (DWORD)( pow( 10, nZeros-1 ) * iTenth ); + if ( topValue >= largest ) + break; + } +*/ + + for ( int iSample=0; iSample < sentPoints.Count(); iSample++ ) + { + double flHeight; + + flHeight = ((double)sentPoints[iSample].y / topValue) * (rcClient.Height() - 1); + sentPoints[iSample].y = (int)( rcClient.Height() - flHeight ); + + flHeight = ((double)receivedPoints[iSample].y / topValue) * (rcClient.Height() - 1); + receivedPoints[iSample].y = (int)( rcClient.Height() - flHeight ); + } + + + // Draw some horizontal lines dividing the space. + int nLines = 10; + for ( int iLine=0; iLine <= nLines; iLine++ ) + { + CPen penLine; + COLORREF color; + if ( iLine == 0 || iLine == nLines/2 ) + color = RGB( 0, 220, 0 ); + else + color = RGB( 0, 100, 0 ); + + penLine.CreatePen( PS_SOLID, 1, color ); + CPen *pOldPen = pDC->SelectObject( &penLine ); + + int y = (iLine * rcClient.Height()) / nLines; + pDC->MoveTo( 0, y ); + pDC->LineTo( rcClient.Width(), y ); + + pDC->SelectObject( pOldPen ); + } + + + // Now draw the lines for the data. + CPen penSent( PS_SOLID, 1, RGB( 0, 255, 0 ) ); + CPen *pOldPen = pDC->SelectObject( &penSent ); + pDC->Polyline( sentPoints.Base(), sentPoints.Count() ); + pDC->SelectObject( pOldPen ); + + CPen penReceived( PS_SOLID, 1, RGB( 255, 255, 0 ) ); + pOldPen = pDC->SelectObject( &penReceived ); + pDC->Polyline( receivedPoints.Base(), receivedPoints.Count() ); + pDC->SelectObject( pOldPen ); + + + + // Draw text labels. + pDC->SetTextColor( RGB( 200, 200, 200 ) ); + pDC->SetBkColor( 0 ); + + CString str; + str.Format( "%dk", (topValue+511) / 1024 ); + pDC->ExtTextOut( 0, 1, 0, NULL, str, NULL ); + + str.Format( "%dk", (topValue+511) / 1024 / 2 ); + pDC->ExtTextOut( 0, rcClient.Height()/2 + 1, 0, NULL, str, NULL ); +} + + +void CGraphControl::Fill( CUtlVector<CGraphEntry> &entries ) +{ + CDC *pDC = GetDC(); + if ( !pDC ) + return; + + m_Entries = entries; + + Render( pDC ); + + ReleaseDC( pDC ); +} + + +///////////////////////////////////////////////////////////////////////////// +// CGraphControl message handlers + +void CGraphControl::OnPaint() +{ + CPaintDC dc(this); // device context for painting + + Render( &dc ); +} diff --git a/utils/vmpi/vmpi_job_watch/GraphControl.h b/utils/vmpi/vmpi_job_watch/GraphControl.h new file mode 100644 index 0000000..b8fcb88 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/GraphControl.h @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_GRAPHCONTROL_H__9B50B827_F24D_4C5A_BA6E_A591A64E404D__INCLUDED_) +#define AFX_GRAPHCONTROL_H__9B50B827_F24D_4C5A_BA6E_A591A64E404D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// GraphControl.h : header file +// + +#include "utlvector.h" + + +class CGraphEntry +{ +public: + CGraphEntry() : + m_msTime( 0 ), + m_nBytesSent( 0 ), + m_nBytesReceived( 0 ) + { + } + + int m_msTime; + int m_nBytesSent; + int m_nBytesReceived; +}; + + +///////////////////////////////////////////////////////////////////////////// +// CGraphControl window + +class CGraphControl : public CWnd +{ +// Construction +public: + CGraphControl(); + +// Attributes +public: + +// Operations +public: + + void Clear(); + + // This function assumes you've already run the query and the graph_entry's are selected in. + void Fill( CUtlVector<CGraphEntry> &entries ); + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CGraphControl) + //}}AFX_VIRTUAL + +// Implementation +public: + virtual ~CGraphControl(); + + +protected: + + void Render( CDC *pDC ); + + CUtlVector<CGraphEntry> m_Entries; + + // Generated message map functions +protected: + //{{AFX_MSG(CGraphControl) + afx_msg void OnPaint(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_GRAPHCONTROL_H__9B50B827_F24D_4C5A_BA6E_A591A64E404D__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_watch/JobWatchDlg.cpp b/utils/vmpi/vmpi_job_watch/JobWatchDlg.cpp new file mode 100644 index 0000000..4c7909b --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/JobWatchDlg.cpp @@ -0,0 +1,648 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// JobWatchDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "JobWatchDlg.h" +#include "tier1/strtools.h" +#include "consolewnd.h" +#include "vmpi_browser_helpers.h" + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +#define IMPLEMENT_SORT_NUMBER_FN( FnName, VarName ) \ + static int CALLBACK FnName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) \ + { \ + CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; \ + CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; \ + return pInfo1->VarName > pInfo2->VarName; \ + } + +static int CALLBACK SortByName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; + CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; + + return strcmp( pInfo1->m_ComputerName, pInfo2->m_ComputerName ); +} + +static int CALLBACK SortByCurrentStage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CWorkerInfo *pInfo1 = (CWorkerInfo*)iItem1; + CWorkerInfo *pInfo2 = (CWorkerInfo*)iItem2; + + return strcmp( pInfo1->m_CurrentStage, pInfo2->m_CurrentStage ); +} + +IMPLEMENT_SORT_NUMBER_FN( SortByConnected, m_bConnected ) +IMPLEMENT_SORT_NUMBER_FN( SortByWorkUnitsDone, m_nWorkUnitsDone ); +IMPLEMENT_SORT_NUMBER_FN( SortByRunningTime, m_RunningTimeMS ); +IMPLEMENT_SORT_NUMBER_FN( SortByThread0WU, m_ThreadWUs[0] ); +IMPLEMENT_SORT_NUMBER_FN( SortByThread1WU, m_ThreadWUs[1] ); +IMPLEMENT_SORT_NUMBER_FN( SortByThread2WU, m_ThreadWUs[2] ); +IMPLEMENT_SORT_NUMBER_FN( SortByThread3WU, m_ThreadWUs[3] ); + +typedef int (CALLBACK *ServicesSortFn)( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ); + +struct +{ + char *pText; + int width; + ServicesSortFn sortFn; +} g_ColumnInfos[] = +{ + {"Computer Name", 150, SortByName}, + {"Connected", 70, SortByConnected}, + {"Work Units Done", 100, SortByWorkUnitsDone}, + {"Running Time", 80, SortByRunningTime}, + {"Current Stage", 180, SortByCurrentStage}, + {"Thread 0", 70, SortByThread0WU}, + {"Thread 1", 70, SortByThread1WU}, + {"Thread 2", 70, SortByThread2WU}, + {"Thread 3", 70, SortByThread3WU} +}; + +#define COLUMN_COMPUTER_NAME 0 +#define COLUMN_CONNECTED 1 +#define COLUMN_WORK_UNITS_DONE 2 +#define COLUMN_RUNNING_TIME 3 +#define COLUMN_CURRENT_STAGE 4 +#define COLUMN_THREAD0_WU 5 +#define COLUMN_THREAD1_WU 6 +#define COLUMN_THREAD2_WU 7 +#define COLUMN_THREAD3_WU 8 + +int g_iSortColumn = 0; + + +///////////////////////////////////////////////////////////////////////////// +// CJobWatchDlg dialog + + +CJobWatchDlg::CJobWatchDlg(CWnd* pParent /*=NULL*/) + : CIdleDialog(CJobWatchDlg::IDD, pParent) +{ + m_CurMessageIndex = 0; + m_CurGraphTime = 0; + m_pSQL = NULL; + m_hMySQLDLL = NULL; + m_CurWorkerTextToken = 0; + m_LastQueryTime = 0; + + //{{AFX_DATA_INIT(CJobWatchDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +CJobWatchDlg::~CJobWatchDlg() +{ + if ( m_pSQL ) + { + m_pSQL->Release(); + } + + if ( m_hMySQLDLL ) + { + Sys_UnloadModule( m_hMySQLDLL ); + } +} + + +void CJobWatchDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CJobWatchDlg) + DDX_Control(pDX, IDC_WORKERS, m_Workers); + DDX_Control(pDX, IDC_TEXTOUTPUT, m_TextOutput); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CJobWatchDlg, CIdleDialog) + //{{AFX_MSG_MAP(CJobWatchDlg) + ON_LBN_SELCHANGE(IDC_WORKERS, OnSelChangeWorkers) + ON_WM_SIZE() + ON_NOTIFY(LVN_ODSTATECHANGED, IDC_WORKERS, OnOdstatechangedWorkers) + ON_NOTIFY(LVN_ITEMCHANGED, IDC_WORKERS, OnItemchangedWorkers) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CJobWatchDlg message handlers + +const char* FindArg( const char *pArgName, const char *pDefault="" ) +{ + for ( int i=1; i < __argc; i++ ) + { + if ( Q_stricmp( pArgName, __argv[i] ) == 0 ) + { + if ( (i+1) < __argc ) + return __argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + + +bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) +{ + int i=0; + for ( i; i < strSize-2; i++ ) + { + if ( fread( &pStr[i], 1, 1, fp ) != 1 || + pStr[i] == '\n' ) + { + break; + } + } + + pStr[i] = 0; + return i != 0; +} + + +BOOL CJobWatchDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + + m_Workers.SetExtendedStyle( LVS_EX_FULLROWSELECT ); + + // Setup the headers. + for ( int i=0; i < ARRAYSIZE( g_ColumnInfos ); i++ ) + { + m_Workers.InsertColumn( i, g_ColumnInfos[i].pText, LVCFMT_LEFT, g_ColumnInfos[i].width, i ); + } + + + m_GraphControl.SubclassDlgItem( IDC_GRAPH_AREA, this ); + + + CString str; + + // Get all our startup info from the command line. + const char *pJobID = FindArg( "-JobID", NULL ); + const char *pDBName = FindArg( "-dbname", NULL ); + const char *pHostName = FindArg( "-hostname", NULL ); + const char *pUserName = FindArg( "-username", NULL ); + + if ( !pJobID ) + { + str.Format( "Missing a command line parameter (-JobID or -dbname or -hostname or -username)" ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 1 ); + return FALSE; + } + + char hostName[512], dbName[512], userName[512]; + if ( !pDBName || !pHostName || !pUserName ) + { + char errString[512]; + + // If they don't specify the DB info, get it from where + const char *pFilename = "dbinfo_job_search.txt"; + FILE *fp = fopen( pFilename, "rt" ); + if ( !fp ) + { + Q_snprintf( errString, sizeof( errString ), "Can't open '%s' for database info.", pFilename ); + MessageBox( errString, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + if ( !ReadStringFromFile( fp, hostName, sizeof( hostName ) ) || + !ReadStringFromFile( fp, dbName, sizeof( dbName ) ) || + !ReadStringFromFile( fp, userName, sizeof( userName ) ) + ) + { + fclose( fp ); + Q_snprintf( errString, sizeof( errString ), "'%s' has invalid format.", pFilename ); + MessageBox( errString, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + pDBName = dbName; + pHostName = hostName; + pUserName = userName; + + fclose( fp ); + } + + m_JobID = atoi( pJobID ); + + // Get the mysql interface. + IMySQL *pSQL; + if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &m_hMySQLDLL, (void**)&pSQL ) ) + return false; + + if ( !pSQL->InitMySQL( pDBName, pHostName, pUserName ) ) + { + pSQL->Release(); + str.Format( "Can't init MYSQL db (db = '%s', host = '%s', user = '%s')", pDBName, pHostName, pUserName ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + m_pSQL = CreateMySQLAsync( pSQL ); + if ( !m_pSQL ) + { + pSQL->Release(); + str.Format( "Can't create IMySQLAsync" ); + MessageBox( str, "Error", MB_OK ); + EndDialog( 0 ); + return FALSE; + } + + + memset( m_bQueriesInProgress, 0, sizeof( m_bQueriesInProgress ) ); + + + // (Init the idle processor so we can update text and graphs). + StartIdleProcessing( 100 ); + + // Fill in the command line control. + char cmdLine[2048]; + Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %s -hostname %s -dbname %s -username %s", + pJobID, pHostName, pDBName, pUserName ); + SetDlgItemText( IDC_COMMAND_LINE, cmdLine ); + + // Setup anchors. + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKERS_PANEL ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_WORKERS ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_WIDTH_PERCENT, ANCHOR_HEIGHT_PERCENT ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_TEXT_OUTPUT_PANEL ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_TEXTOUTPUT ), ANCHOR_LEFT, ANCHOR_HEIGHT_PERCENT, ANCHOR_WIDTH_PERCENT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_GRAPHS_PANEL ), ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); + m_AnchorMgr.AddAnchor( this, &m_GraphControl, ANCHOR_WIDTH_PERCENT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_HEIGHT_PERCENT ); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +CWorkerInfo* CJobWatchDlg::FindWorkerByID( unsigned long jobWorkerID ) +{ + int nIndex = -1; + while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) + { + CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); + if ( pInfo->m_JobWorkerID == jobWorkerID ) + return pInfo; + } + + return NULL; +} + + +CWorkerInfo* CJobWatchDlg::FindWorkerByMachineName( const char *pMachineName ) +{ + int nIndex = -1; + while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) + { + CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); + if ( Q_stricmp( pInfo->m_ComputerName, pMachineName ) == 0 ) + return pInfo; + } + + return NULL; +} + + +void CJobWatchDlg::SetWorkerListItemInt( int nIndex, int iColumn, int value ) +{ + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", value ); + m_Workers.SetItemText( nIndex, iColumn, str ); +} + + +void CJobWatchDlg::UpdateWorkersList() +{ + int nIndex = -1; + while ( ( nIndex = m_Workers.GetNextItem( nIndex, LVNI_ALL ) ) != -1 ) + { + CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( nIndex ); + + char *pConnectedStr = pInfo->m_bConnected ? "yes" : "no"; + m_Workers.SetItemText( nIndex, COLUMN_CONNECTED, pConnectedStr ); + + SetWorkerListItemInt( nIndex, COLUMN_WORK_UNITS_DONE, pInfo->m_nWorkUnitsDone ); + + char timeStr[1024]; + FormatTimeString( pInfo->m_RunningTimeMS / 1000, timeStr, sizeof( timeStr ) ); + m_Workers.SetItemText( nIndex, COLUMN_RUNNING_TIME, timeStr ); + + // Current stage. + SetWorkerListItemInt( nIndex, COLUMN_WORK_UNITS_DONE, pInfo->m_nWorkUnitsDone ); + + m_Workers.SetItemText( nIndex, COLUMN_CURRENT_STAGE, pInfo->m_CurrentStage ); + + SetWorkerListItemInt( nIndex, COLUMN_THREAD0_WU, pInfo->m_ThreadWUs[0] ); + SetWorkerListItemInt( nIndex, COLUMN_THREAD1_WU, pInfo->m_ThreadWUs[1] ); + SetWorkerListItemInt( nIndex, COLUMN_THREAD2_WU, pInfo->m_ThreadWUs[2] ); + SetWorkerListItemInt( nIndex, COLUMN_THREAD3_WU, pInfo->m_ThreadWUs[3] ); + } + + ResortItems(); +} + + +bool CJobWatchDlg::GetCurJobWorkerID( unsigned long &id ) +{ + POSITION pos = m_Workers.GetFirstSelectedItemPosition(); + if ( !pos ) + return false; + + int index = m_Workers.GetNextSelectedItem( pos ); + CWorkerInfo *pInfo = (CWorkerInfo*)m_Workers.GetItemData( index ); + id = pInfo->m_JobWorkerID; + return true; +} + + +void CJobWatchDlg::OnSelChangeWorkers() +{ + // Clear the text output and invalidate any old queries for text. + int nLen = m_TextOutput.SendMessage( EM_GETLIMITTEXT, 0, 0 ); + m_TextOutput.SendMessage( EM_SETSEL, 0, nLen ); + m_TextOutput.SendMessage( EM_REPLACESEL, FALSE, (LPARAM)"" ); + + m_CurMessageIndex = 0; + m_CurWorkerTextToken++; + m_LastQueryTime = 0; // force a query. + + m_GraphControl.Clear(); + m_CurGraphTime = -1; +} + + +void CJobWatchDlg::ResortItems() +{ + m_Workers.SortItems( g_ColumnInfos[g_iSortColumn].sortFn, (LPARAM)this ); +} + + + +void CJobWatchDlg::OnIdle() +{ + // Issue any queries that we need to. + DWORD curTime = GetTickCount(); + if ( curTime - m_LastQueryTime >= 1000 ) + { + m_LastQueryTime = curTime; + char query[2048]; + + unsigned long jobWorkerID; + bool bJobWorkerIDValid = GetCurJobWorkerID( jobWorkerID ); + + if ( !m_bQueriesInProgress[QUERY_TEXT] && bJobWorkerIDValid ) + { + Q_snprintf( query, sizeof( query ), "select * from text_messages where JobWorkerID=%lu and MessageIndex >= %lu", jobWorkerID, m_CurMessageIndex ); + m_pSQL->Execute( query, (void*)(QUERY_TEXT | (m_CurWorkerTextToken << 16)) ); + m_bQueriesInProgress[QUERY_TEXT] = true; + } + + if ( !m_bQueriesInProgress[QUERY_GRAPH] && bJobWorkerIDValid ) + { + Q_snprintf( query, sizeof( query ), "select * from graph_entry where JobWorkerID=%lu", jobWorkerID ); + m_pSQL->Execute( query, (void*)QUERY_GRAPH ); + m_bQueriesInProgress[QUERY_GRAPH] = true; + } + + if ( !m_bQueriesInProgress[QUERY_WORKER_STATS] ) + { + Q_snprintf( query, sizeof( query ), "select JobWorkerID, WorkerState, NumWorkUnits, " + "RunningTimeMS, CurrentStage, Thread0WU, Thread1WU, Thread2WU, Thread3WU, IsMaster, MachineName " + " from job_worker_start where JobID=%lu", m_JobID ); + + m_pSQL->Execute( query, (void*)QUERY_WORKER_STATS ); + m_bQueriesInProgress[QUERY_WORKER_STATS] = true; + } + } + + + // Pickup query results. + CQueryResults results; + while ( m_pSQL->GetNextResults( results ) && results.m_pResults ) + { + int iQueryID = ((int)results.m_pUserData) & 0xFFFF; + int iExtraData = ((int)results.m_pUserData) >> 16; + + if ( results.m_pResults ) + { + if ( iQueryID == QUERY_TEXT ) + { + if ( iExtraData == m_CurWorkerTextToken ) + { + ProcessQueryResults_Text( results.m_pResults ); + } + } + else if ( iQueryID == QUERY_GRAPH ) + { + ProcessQueryResults_Graph( results.m_pResults ); + } + else if ( iQueryID == QUERY_WORKER_STATS ) + { + ProcessQueryResults_WorkerStats( results.m_pResults ); + } + + results.m_pResults->Release(); + } + + m_bQueriesInProgress[iQueryID] = false; + } +} + + +void CJobWatchDlg::ProcessQueryResults_WorkerStats( IMySQLRowSet *pSet ) +{ + bool bChange = false; + while ( pSet->NextRow() ) + { + int iColumn = 0; + int workerID = pSet->GetColumnValue_Int( iColumn++ ); + int workerState = pSet->GetColumnValue_Int( iColumn++ ); + int nWorkUnits = pSet->GetColumnValue_Int( iColumn++ ); + unsigned long runningTimeMS = pSet->GetColumnValue_Int( iColumn++ ); + const char *pCurrentStage = pSet->GetColumnValue_String( iColumn++ ); + int iThread0WU = pSet->GetColumnValue_Int( iColumn++ ); + int iThread1WU = pSet->GetColumnValue_Int( iColumn++ ); + int iThread2WU = pSet->GetColumnValue_Int( iColumn++ ); + int iThread3WU = pSet->GetColumnValue_Int( iColumn++ ); + int bIsMaster = pSet->GetColumnValue_Int( iColumn++ ); + const char *pMachineName = pSet->GetColumnValue_String( iColumn ); + + CWorkerInfo *pInfo = FindWorkerByID( workerID ); + if ( pInfo ) + { + if ( workerState != pInfo->m_bConnected || + nWorkUnits != pInfo->m_nWorkUnitsDone || + runningTimeMS != pInfo->m_RunningTimeMS || + stricmp( pCurrentStage, pInfo->m_CurrentStage ) != 0 || + iThread0WU != pInfo->m_ThreadWUs[0] || + iThread1WU != pInfo->m_ThreadWUs[1] || + iThread2WU != pInfo->m_ThreadWUs[2] || + iThread3WU != pInfo->m_ThreadWUs[3] + ) + { + bChange = true; + pInfo->m_bConnected = workerState; + pInfo->m_nWorkUnitsDone = nWorkUnits; + pInfo->m_RunningTimeMS = runningTimeMS; + pInfo->m_CurrentStage = pCurrentStage; + pInfo->m_ThreadWUs[0] = iThread0WU; + pInfo->m_ThreadWUs[1] = iThread1WU; + pInfo->m_ThreadWUs[2] = iThread2WU; + pInfo->m_ThreadWUs[3] = iThread3WU; + } + } + else + { + // Add a new entry. + CWorkerInfo *pInfo = new CWorkerInfo; + pInfo->m_ComputerName = pMachineName; + pInfo->m_bConnected = false; + pInfo->m_nWorkUnitsDone = 0; + pInfo->m_RunningTimeMS = 0; + pInfo->m_JobWorkerID = workerID; + + int index; + if ( bIsMaster ) + { + char tempStr[512]; + Q_snprintf( tempStr, sizeof( tempStr ), "%s [master]", (const char*)pInfo->m_ComputerName ); + index = m_Workers.InsertItem( COLUMN_COMPUTER_NAME, tempStr, NULL ); + } + else + { + index = m_Workers.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); + } + + m_Workers.SetItemData( index, (DWORD)pInfo ); + bChange = true; + } + } + + if ( bChange ) + { + UpdateWorkersList(); + } +} + + +void CJobWatchDlg::ProcessQueryResults_Text( IMySQLRowSet *pSet ) +{ + CUtlVector<char> text; + + while ( pSet->NextRow() ) + { + const char *pTextStr = pSet->GetColumnValue( "text" ).String(); + int len = strlen( pTextStr ); + text.AddMultipleToTail( len, pTextStr ); + + m_CurMessageIndex = pSet->GetColumnValue( "MessageIndex" ).Int32() + 1; + } + + text.AddToTail( 0 ); + FormatAndSendToEditControl( m_TextOutput.GetSafeHwnd(), text.Base() ); +} + + +void CJobWatchDlg::ProcessQueryResults_Graph( IMySQLRowSet *pSet ) +{ + int iMSTime = pSet->GetColumnIndex( "MSSinceJobStart" ); + int iBytesSent = pSet->GetColumnIndex( "BytesSent" ); + int iBytesReceived = pSet->GetColumnIndex( "BytesReceived" ); + + // See if there's anything new. + CUtlVector<CGraphEntry> entries; + + int highest = m_CurGraphTime; + while ( pSet->NextRow() ) + { + CGraphEntry entry; + entry.m_msTime = pSet->GetColumnValue( iMSTime ).Int32(); + entry.m_nBytesSent = pSet->GetColumnValue( iBytesSent ).Int32(); + entry.m_nBytesReceived = pSet->GetColumnValue( iBytesReceived ).Int32(); + entries.AddToTail( entry ); + + highest = max( highest, entry.m_msTime ); + } + + if ( highest > m_CurGraphTime ) + { + m_CurGraphTime = highest; + + m_GraphControl.Clear(); + m_GraphControl.Fill( entries ); + } +} + + +void CJobWatchDlg::OnSize(UINT nType, int cx, int cy) +{ + CIdleDialog::OnSize(nType, cx, cy); + + m_AnchorMgr.UpdateAnchors( this ); +} + + +BOOL CJobWatchDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) +{ + NMHDR *pHdr = (NMHDR*)lParam; + if ( pHdr->idFrom == IDC_WORKERS ) + { + if ( pHdr->code == LVN_COLUMNCLICK ) + { + LPNMLISTVIEW pListView = (LPNMLISTVIEW)lParam; + + // Now sort by this column. + g_iSortColumn = max( 0, min( pListView->iSubItem, (int)ARRAYSIZE( g_ColumnInfos ) - 1 ) ); + ResortItems(); + } + } + + return CIdleDialog::OnNotify(wParam, lParam, pResult); +} + +void CJobWatchDlg::OnOdstatechangedWorkers(NMHDR* pNMHDR, LRESULT* pResult) +{ + NMLVODSTATECHANGE* pStateChanged = (NMLVODSTATECHANGE*)pNMHDR; + + if ( !( pStateChanged->uOldState & LVIS_SELECTED ) && ( pStateChanged->uNewState & LVIS_SELECTED ) ) + { + OnSelChangeWorkers(); + } + + *pResult = 0; +} + +void CJobWatchDlg::OnItemchangedWorkers(NMHDR* pNMHDR, LRESULT* pResult) +{ + NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; + + if ( !( pNMListView->uOldState & LVIS_SELECTED ) && ( pNMListView->uNewState & LVIS_SELECTED ) ) + { + OnSelChangeWorkers(); + } + + *pResult = 0; +} diff --git a/utils/vmpi/vmpi_job_watch/JobWatchDlg.h b/utils/vmpi/vmpi_job_watch/JobWatchDlg.h new file mode 100644 index 0000000..6bec50b --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/JobWatchDlg.h @@ -0,0 +1,134 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_JOBWATCHDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_) +#define AFX_JOBWATCHDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// JobWatchDlg.h : header file +// + + +#include "idle_dialog.h" +#include "resource.h" +#include "utlvector.h" +#include "imysqlwrapper.h" +#include "GraphControl.h" +#include "window_anchor_mgr.h" +#include "mysql_async.h" + + +class CWorkerInfo +{ +public: + CWorkerInfo() + { + m_bConnected = false; + m_nWorkUnitsDone = 0; + m_JobWorkerID = 0xFFFFFFFF; + m_RunningTimeMS = 0; + m_ThreadWUs[0] = m_ThreadWUs[1] = m_ThreadWUs[2] = m_ThreadWUs[3] = -1; + } + + CString m_ComputerName; + int m_bConnected; + int m_nWorkUnitsDone; + unsigned long m_JobWorkerID; + unsigned long m_RunningTimeMS; + CString m_CurrentStage; + int m_ThreadWUs[4]; +}; + + + +///////////////////////////////////////////////////////////////////////////// +// CJobWatchDlg dialog + +class CJobWatchDlg : public CIdleDialog +{ +// Construction +public: + CJobWatchDlg( CWnd* pParent = NULL); // standard constructor + virtual ~CJobWatchDlg(); + +// Dialog Data + //{{AFX_DATA(CJobWatchDlg) + enum { IDD = IDD_JOB_WATCH }; + CListCtrl m_Workers; + CEdit m_TextOutput; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CJobWatchDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); + //}}AFX_VIRTUAL + +// Implementation +protected: + + virtual void OnIdle(); + void RefreshWorkerStats(); + CWorkerInfo* FindWorkerByID( unsigned long jobWorkerID ); + CWorkerInfo* FindWorkerByMachineName( const char *pMachineName ); + void SetWorkerListItemInt( int nIndex, int iColumn, int value ); + void UpdateWorkersList(); + void ResortItems(); + + // Query IDs. + enum + { + QUERY_TEXT=0, + QUERY_GRAPH, + QUERY_WORKER_STATS, + NUM_QUERIES + }; + + void ProcessQueryResults_Graph( IMySQLRowSet *pSet ); + void ProcessQueryResults_Text( IMySQLRowSet *pSet ); + void ProcessQueryResults_WorkerStats( IMySQLRowSet *pSet ); + + bool m_bQueriesInProgress[NUM_QUERIES]; + + // This is our connection to the mysql database. + IMySQLAsync *m_pSQL; + CSysModule *m_hMySQLDLL; + + CWindowAnchorMgr m_AnchorMgr; + + + bool GetCurJobWorkerID( unsigned long &id ); + + CGraphControl m_GraphControl; + unsigned long m_JobID; + int m_CurGraphTime; + + int m_CurMessageIndex; + int m_CurWorkerTextToken; // used to let it ignore old text in the thread's queue + + DWORD m_LastQueryTime; // Last time we made a query. + + // Generated message map functions + //{{AFX_MSG(CJobWatchDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnSelChangeWorkers(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnOdstatechangedWorkers(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnItemchangedWorkers(NMHDR* pNMHDR, LRESULT* pResult); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_JOBWATCHDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_watch/StdAfx.cpp b/utils/vmpi/vmpi_job_watch/StdAfx.cpp new file mode 100644 index 0000000..86ac49b --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_browser_job_watch.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/utils/vmpi/vmpi_job_watch/StdAfx.h b/utils/vmpi/vmpi_job_watch/StdAfx.h new file mode 100644 index 0000000..3c1d23e --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/StdAfx.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_) +#define AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include "tier0/basetypes.h" + +#include <afxwin.h> // MFC core and standard components +#include <afxext.h> // MFC extensions +#include <afxdisp.h> // MFC Automation classes +#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include <afxcmn.h> // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.ico b/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.ico Binary files differnew file mode 100644 index 0000000..7eef0bc --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.ico diff --git a/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.rc2 b/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.rc2 new file mode 100644 index 0000000..6e93fd0 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.rc2 @@ -0,0 +1,13 @@ +// +// VMPI_BROWSER_JOB_WATCH.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/utils/vmpi/vmpi_job_watch/resource.h b/utils/vmpi/vmpi_job_watch/resource.h new file mode 100644 index 0000000..c81a23e --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/resource.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by vmpi_browser_job_watch.rc +// +#define IDD_VMPI_BROWSER_JOB_WATCH_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_JOB_WATCH 135 +#define IDC_WORKERS 1001 +#define IDC_GRAPH_AREA 1002 +#define IDC_TEXTOUTPUT 1005 +#define IDC_WORKERS_PANEL 1006 +#define IDC_TEXT_OUTPUT_PANEL 1007 +#define IDC_GRAPHS_PANEL 1008 +#define IDC_COMMAND_LINE 1009 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1010 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.cpp b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.cpp new file mode 100644 index 0000000..efdf515 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.cpp @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_watch.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "vmpi_browser_job_watch.h" +#include "JobWatchDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobWatchApp + +BEGIN_MESSAGE_MAP(CVMPIBrowserJobWatchApp, CWinApp) + //{{AFX_MSG_MAP(CVMPIBrowserJobWatchApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobWatchApp construction + +CVMPIBrowserJobWatchApp::CVMPIBrowserJobWatchApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CVMPIBrowserJobWatchApp object + +CVMPIBrowserJobWatchApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobWatchApp initialization + +BOOL CVMPIBrowserJobWatchApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + + CJobWatchDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.h b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.h new file mode 100644 index 0000000..bd55384 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_watch.h : main header file for the VMPI_BROWSER_JOB_WATCH application +// + +#if !defined(AFX_VMPI_BROWSER_JOB_WATCH_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_) +#define AFX_VMPI_BROWSER_JOB_WATCH_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobWatchApp: +// See vmpi_browser_job_watch.cpp for the implementation of this class +// + +class CVMPIBrowserJobWatchApp : public CWinApp +{ +public: + CVMPIBrowserJobWatchApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVMPIBrowserJobWatchApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVMPIBrowserJobWatchApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VMPI_BROWSER_JOB_WATCH_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_) diff --git a/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.rc b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.rc new file mode 100644 index 0000000..56c3fc7 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.rc @@ -0,0 +1,183 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\vmpi_browser_job_watch.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON DISCARDABLE "res\\vmpi_browser_job_watch.ico" + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "vmpi_browser_job_watch MFC Application\0" + VALUE "FileVersion", "1, 0, 0, 1\0" + VALUE "InternalName", "vmpi_browser_job_watch\0" + VALUE "LegalCopyright", "Copyright (C) 2003\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "vmpi_browser_job_watch.EXE\0" + VALUE "ProductName", "vmpi_browser_job_watch Application\0" + VALUE "ProductVersion", "1, 0, 0, 1\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_JOB_WATCH DIALOG DISCARDABLE 0, 0, 592, 366 +STYLE WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU | WS_THICKFRAME +CAPTION "Job Watch" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_TEXTOUTPUT,13,177,291,179,ES_MULTILINE | ES_READONLY | + WS_VSCROLL | WS_HSCROLL + GROUPBOX "Workers",IDC_WORKERS_PANEL,7,32,578,132 + GROUPBOX "Text Output",IDC_TEXT_OUTPUT_PANEL,7,164,303,195 + GROUPBOX "Graphs",IDC_GRAPHS_PANEL,318,164,267,195 + CONTROL "",IDC_GRAPH_AREA,"Static",SS_BLACKFRAME,323,176,255,179 + CONTROL "List1",IDC_WORKERS,"SysListView32",LVS_REPORT | + LVS_SORTASCENDING | WS_BORDER | WS_TABSTOP,13,41,564,118 + LTEXT "Command Line:",IDC_STATIC,7,9,50,8 + EDITTEXT IDC_COMMAND_LINE,60,7,364,14,ES_AUTOHSCROLL | + ES_READONLY +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_JOB_WATCH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 585 + TOPMARGIN, 7 + BOTTOMMARGIN, 359 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\vmpi_browser_job_watch.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_job_watch/vmpi_job_watch.vpc b/utils/vmpi/vmpi_job_watch/vmpi_job_watch.vpc new file mode 100644 index 0000000..9d8f372 --- /dev/null +++ b/utils/vmpi/vmpi_job_watch/vmpi_job_watch.vpc @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// vmpi_job_watch.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vmpi_job_watch" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,.\,..\,..\..\common,..\mysql\include" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;WINVER=0x501;NO_WARN_MBCS_MFC_DEPRECATION" + $EnableC++Exceptions "Yes (/EHsc)" + } +} + +$Configuration "Debug" +{ + $Linker + { + // Deprecated MBCS MFC libraries for VS 2013 (nafxcw.lib and nafxcwd.lib) can be downloaded from http://go.microsoft.com/?linkid=9832071 + $AdditionalDependencies "$BASE nafxcwd.lib" + $IgnoreSpecificLibrary "nafxcw.lib libcmt.lib" + } +} + +$Configuration "Release" +{ + $Linker + { + // Deprecated MBCS MFC libraries for VS 2013 (nafxcw.lib and nafxcwd.lib) can be downloaded from http://go.microsoft.com/?linkid=9832071 + $AdditionalDependencies "$BASE nafxcw.lib libcmt.lib" + $IgnoreSpecificLibrary "nafxcwd.lib libcmtd.lib" + } +} + +$Project "vmpi_job_watch" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "..\..\common\consolewnd.cpp" + $File "GraphControl.cpp" + $File "..\idle_dialog.cpp" + $File "JobWatchDlg.cpp" + $File "..\mysql_async.cpp" + $File "..\vmpi_browser_helpers.cpp" + $File "vmpi_browser_job_watch.cpp" + $File "vmpi_browser_job_watch.rc" + $File "..\win_idle.cpp" + $File "..\window_anchor_mgr.cpp" + } + + $Folder "Header Files" + { + $File "..\..\common\consolewnd.h" + $File "GraphControl.h" + $File "..\idle_dialog.h" + $File "JobWatchDlg.h" + $File "..\mysql_async.h" + $File "..\mysql_wrapper.h" + $File "Resource.h" + $File "StdAfx.h" + $File "vmpi_browser_job_watch.h" + $File "..\win_idle.h" + } + + $Folder "Resource Files" + { + $File "res\vmpi_browser_job_watch.ico" + $File "res\vmpi_browser_job_watch.rc2" + } +} diff --git a/utils/vmpi/vmpi_parameters.h b/utils/vmpi/vmpi_parameters.h new file mode 100644 index 0000000..e6f0abc --- /dev/null +++ b/utils/vmpi/vmpi_parameters.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +VMPI_PARAM( mpi_Worker, 0, "Workers use this to connect to a VMPI job. Specify the IP address of the master. Example: -mpi_worker 1.2.3.4 or -mpi_worker 1.2.3.4:242" ) +VMPI_PARAM( mpi_Port, 0, "Use this on the master to force it to bind to a specified port. Otherwise it binds to 23311 (and ascending port numbers if 23311 doesn't work)." ) +VMPI_PARAM( mpi_Graphics, 0, "Show a graphical representation of work units [grey=work unit not sent yet, red=sent, green=completed, blue=in-process]" ) +VMPI_PARAM( mpi_Retry, 0, "Use this on the worker to have it retry connecting to the master forever. Otherwise it will exit if it can't connect to the master immediately." ) +VMPI_PARAM( mpi_AutoRestart, 0, "Use this on the worker to have it restart with the same command line parameters after completing a job. Useful in conjunction with -mpi_Retry to have an always-on worker ready to do work." ) +VMPI_PARAM( mpi_TrackEvents, 0, "Enables a debug menu during jobs (press D to access). Note: -mpi_Graphics automatically enables -mpi_TrackEvents." ) +VMPI_PARAM( mpi_ShowDistributeWorkStats, 0, "After finishing a stage in the work unit processing, shows statistics." ) +VMPI_PARAM( mpi_TimingWait, 0, "Causes the master to wait for a keypress to start so workers can connect before it starts. Used for performance measurements." ) +VMPI_PARAM( mpi_WorkerCount, 0, "Set the maximum number of workers allowed in the job." ) +VMPI_PARAM( mpi_AutoLocalWorker, 0, "Used on the master's machine. Automatically spawn a worker on the local machine. Used for testing." ) +VMPI_PARAM( mpi_FileTransmitRate, 0, "VMPI file transmission rate in kB/sec." ) +VMPI_PARAM( mpi_Verbose, 0, "Set to 0, 1, or 2 to control verbosity of debug output." ) +VMPI_PARAM( mpi_NoMasterWorkerThreads, 0, "Don't process work units locally (in the master). Only used by the SDK work unit distributor." ) +VMPI_PARAM( mpi_SDKMode, VMPI_PARAM_SDK_HIDDEN, "Force VMPI to run in SDK mode." ) +VMPI_PARAM( mpi_UseSDKDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the SDK work unit distributor. Optimized for low numbers of workers and higher latency. Note that this will automatically be used in SDK distributions." ) +VMPI_PARAM( mpi_UseDefaultDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the default work unit distributor. Optimized for high numbers of workers, higher numbers of work units, and lower latency. Note that this will automatically be used in non-SDK distributions." ) +VMPI_PARAM( mpi_NoTimeout, VMPI_PARAM_SDK_HIDDEN, "Don't timeout VMPI sockets. Used for testing." ) +VMPI_PARAM( mpi_DontSetThreadPriorities, VMPI_PARAM_SDK_HIDDEN, "Don't set worker thread priorities to idle." ) +VMPI_PARAM( mpi_GroupPackets, VMPI_PARAM_SDK_HIDDEN, "Delay and group some of the worker packets instead of sending immediately." ) +VMPI_PARAM( mpi_Stats, VMPI_PARAM_SDK_HIDDEN, "Enables the use of a database to store compile statistics." ) +VMPI_PARAM( mpi_Stats_TextOutput, VMPI_PARAM_SDK_HIDDEN, "Enables the workers storing all of their text output into the stats database." ) +VMPI_PARAM( mpi_pw, VMPI_PARAM_SDK_HIDDEN, "Non-SDK only. Sets a password on the VMPI job. Workers must also use the same -mpi_pw [password] argument or else the master will ignore their requests to join the job." ) +VMPI_PARAM( mpi_CalcShuffleCRC, VMPI_PARAM_SDK_HIDDEN, "Calculate a CRC for shuffled work unit arrays in the SDK work unit distributor." ) +VMPI_PARAM( mpi_Job_Watch, VMPI_PARAM_SDK_HIDDEN, "Automatically launches vmpi_job_watch.exe on the job." ) +VMPI_PARAM( mpi_Local, VMPI_PARAM_SDK_HIDDEN, "Similar to -mpi_AutoLocalWorker, but the automatically-spawned worker's console window is hidden." )
\ No newline at end of file diff --git a/utils/vmpi/vmpi_service/StdAfx.cpp b/utils/vmpi/vmpi_service/StdAfx.cpp new file mode 100644 index 0000000..f3f31b0 --- /dev/null +++ b/utils/vmpi/vmpi_service/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_service.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/vmpi_service/StdAfx.h b/utils/vmpi/vmpi_service/StdAfx.h new file mode 100644 index 0000000..b490c37 --- /dev/null +++ b/utils/vmpi/vmpi_service/StdAfx.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_) +#define AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> +#include <winsock2.h> +#include <shellapi.h> +#include <winuser.h> +#include "basetypes.h" +#include <stdio.h> +#include "iphelpers.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A1923E9A_F174_4448_8004_33888CD7DC88__INCLUDED_) diff --git a/utils/vmpi/vmpi_service/perf_counters.cpp b/utils/vmpi/vmpi_service/perf_counters.cpp new file mode 100644 index 0000000..3d98d2a --- /dev/null +++ b/utils/vmpi/vmpi_service/perf_counters.cpp @@ -0,0 +1,500 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "stdafx.h" +#include "tier1/utldict.h" +#include <pdh.h> +#include <pdhmsg.h> +#include "perf_counters.h" + + +#if 1 + +class CPerfTracker : public IPerfTracker +{ +public: + + CPerfTracker() + { + m_hProcessorTimeCounter = NULL; + m_dwProcessID = 0; + if ( PdhOpenQuery( NULL, 0, &m_hQuery ) != ERROR_SUCCESS ) + m_hQuery = NULL; + + SYSTEM_INFO info; + GetSystemInfo( &info ); + m_nProcessors = (int)info.dwNumberOfProcessors; + } + + ~CPerfTracker() + { + if ( m_hQuery ) + PdhCloseQuery( m_hQuery ); + } + + virtual void Init( unsigned long dwProcessID ) + { + Term(); + + m_dwProcessID = dwProcessID; + + char instanceName[512]; + if ( GetInstanceNameFromProcessID( m_dwProcessID, instanceName, sizeof( instanceName ) ) ) + { + // Create a counter to watch this process' time. + char str[512]; + V_snprintf( str, sizeof( str ), "\\Process(%s)\\%% Processor Time", instanceName ); + if ( PdhAddCounter( m_hQuery, str, 0, &m_hProcessorTimeCounter ) != ERROR_SUCCESS ) + { + m_hProcessorTimeCounter = NULL; + } + + V_snprintf( str, sizeof( str ), "\\Process(%s)\\Private Bytes", instanceName ); + if ( PdhAddCounter( m_hQuery, str, 0, &m_hPrivateBytesCounter ) != ERROR_SUCCESS ) + { + m_hPrivateBytesCounter = NULL; + } + } + } + + void Term() + { + if ( m_hProcessorTimeCounter ) + PdhRemoveCounter( m_hProcessorTimeCounter ); + + if ( m_hPrivateBytesCounter ) + PdhRemoveCounter( m_hPrivateBytesCounter ); + + m_hProcessorTimeCounter = NULL; + m_hPrivateBytesCounter = NULL; + } + + virtual void Release() + { + delete this; + } + + virtual unsigned long GetProcessID() + { + return m_dwProcessID; + } + + virtual void GetPerfData( int &processorPercentage, int &memoryUsageMegabytes ) + { + processorPercentage = 101; + memoryUsageMegabytes = 0; + + // Collect query data.. + PDH_STATUS ret = PdhCollectQueryData( m_hQuery ); + if ( ret != ERROR_SUCCESS ) + return; + + // Check processor usage. + DWORD dwType; + PDH_FMT_COUNTERVALUE counterValue; + if ( PdhGetFormattedCounterValue( m_hProcessorTimeCounter, PDH_FMT_LONG | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS ) + processorPercentage = counterValue.longValue / m_nProcessors; + else + processorPercentage = 101; + + // Check memory usage. + if ( PdhGetFormattedCounterValue( m_hPrivateBytesCounter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &dwType, &counterValue ) == ERROR_SUCCESS ) + memoryUsageMegabytes = (int)(counterValue.doubleValue / (1024.0 * 1024.0)); + else + memoryUsageMegabytes = 0; + } + + +private: + + bool GetInstanceNameFromProcessID( DWORD processID, char *instanceName, int instanceNameLen ) + { + instanceName[0] = 0; + + bool bRet = false; + + // This refreshes the object list. If we don't do this, it won't get new process IDs correctly. + DWORD dummy = 0; + PdhEnumObjects( NULL, NULL, NULL, &dummy, PERF_DETAIL_NOVICE, true ); + + // Find out how much data we need. + DWORD counterListLen=2, instanceListLen=2; + char *counterList = new char[counterListLen]; + char *instanceList = new char[instanceListLen]; + PDH_STATUS stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 ); + if ( stat == PDH_MORE_DATA ) + { + delete [] counterList; + delete [] instanceList; + char *counterList = new char[counterListLen]; + char *instanceList = new char[instanceListLen]; + + stat = PdhEnumObjectItems( NULL, NULL, "Process", counterList, &counterListLen, instanceList, &instanceListLen, PERF_DETAIL_NOVICE, 0 ); + if ( stat == ERROR_SUCCESS ) + { + // We need the # of each one.. + CUtlDict<int,int> counts; + + // The instance name list is a bunch of strings terminated with nulls. The final one has two nulls after it. + // Walk through the list and get the process ID associated with each instance name. + const char *pCur = instanceList; + while ( *pCur ) + { + int index = counts.Find( pCur ); + if ( index == counts.InvalidIndex() ) + counts.Insert( pCur, 1 ); + else + counts[index]++; + + pCur += strlen( pCur ) + 1; + } + + // Each instance (like "vrad") might have multiple versions, like if you're running multiple vrad processes at the same time. + for ( int i=counts.First(); i != counts.InvalidIndex(); i=counts.Next( i ) ) + { + const char *pInstanceName = counts.GetElementName( i ); + int nInstances = counts[i]; + for ( int iInstance=0; iInstance < nInstances; iInstance++ ) + { + char testInstanceName[256], fullObjectName[256]; + V_snprintf( testInstanceName, sizeof( testInstanceName ), "%s#%d", pInstanceName, iInstance ); + V_snprintf( fullObjectName, sizeof( fullObjectName ), "\\Process(%s)\\ID Process", testInstanceName ); + + HCOUNTER hCounter = NULL; + stat = PdhAddCounter( m_hQuery, fullObjectName, 0, &hCounter ); + if ( stat == ERROR_SUCCESS ) + { + stat = PdhCollectQueryData( m_hQuery ); + if ( stat == ERROR_SUCCESS ) + { + DWORD dwType; + PDH_FMT_COUNTERVALUE counterValue; + stat = PdhGetFormattedCounterValue( hCounter, PDH_FMT_LONG, &dwType, &counterValue ); + if ( stat == 0 && counterValue.longValue == (long)processID ) + { + // Finall! We found it. + V_strncpy( instanceName, testInstanceName, instanceNameLen ); + bRet = true; + PdhRemoveCounter( hCounter ); + break; + } + } + + PdhRemoveCounter( hCounter ); + } + } + + if ( bRet ) + break; + } + } + + delete [] counterList; + delete [] instanceList; + } + + return bRet; + } + +private: + DWORD m_dwProcessID; + PDH_HQUERY m_hQuery; + HCOUNTER m_hProcessorTimeCounter; + HCOUNTER m_hPrivateBytesCounter; + int m_nProcessors; +}; + + +IPerfTracker* CreatePerfTracker() +{ + return new CPerfTracker; +} + + +#else + +#include <winperf.h> + +// --------------------------------------------------------------------------------------------------------------------- // +// NOTE: THIS IS THE OLD, UGLY WAY TO DO IT. +// --------------------------------------------------------------------------------------------------------------------- // + +class CPerfTracker +{ +public: + CPerfTracker(); + void Init( unsigned long dwProcessID ); + + unsigned long GetProcessID(); + + // Get the percentage of CPU time that the process is using. + int GetCPUPercentage(); + +private: + DWORD m_dwProcessID; + LONGLONG m_lnOldValue; + LARGE_INTEGER m_OldPerfTime100nSec; + int m_nProcessors; +}; + +#define TOTALBYTES 100*1024 +#define BYTEINCREMENT 10*1024 + +#define SYSTEM_OBJECT_INDEX 2 // 'System' object +#define PROCESS_OBJECT_INDEX 230 // 'Process' object +#define PROCESSOR_OBJECT_INDEX 238 // 'Processor' object +#define TOTAL_PROCESSOR_TIME_COUNTER_INDEX 240 // '% Total processor time' counter (valid in WinNT under 'System' object) +#define PROCESSOR_TIME_COUNTER_INDEX 6 // '% processor time' counter (for Win2K/XP) + + +// +// The performance data is accessed through the registry key +// HKEY_PEFORMANCE_DATA. +// However, although we use the registry to collect performance data, +// the data is not stored in the registry database. +// Instead, calling the registry functions with the HKEY_PEFORMANCE_DATA key +// causes the system to collect the data from the appropriate system +// object managers. +// +// QueryPerformanceData allocates memory block for getting the +// performance data. +// +// +void QueryPerformanceData(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex) +{ + // + // Since i want to use the same allocated area for each query, + // i declare CBuffer as static. + // The allocated is changed only when RegQueryValueEx return ERROR_MORE_DATA + // + static CUtlVector<char> Buffer; + if ( Buffer.Count() == 0 ) + Buffer.SetSize( TOTALBYTES ); + + DWORD BufferSize = Buffer.Count(); + LONG lRes; + + char keyName[32]; + V_snprintf(keyName, sizeof(keyName), "%d",dwObjectIndex); + + memset( Buffer.Base(), 0, Buffer.Count() ); + while( (lRes = RegQueryValueEx( HKEY_PERFORMANCE_DATA, + keyName, + NULL, + NULL, + (LPBYTE)Buffer.Base(), + &BufferSize )) == ERROR_MORE_DATA ) + { + // Get a buffer that is big enough. + BufferSize += BYTEINCREMENT; + Buffer.SetSize( BufferSize ); + } + + *pPerfData = (PPERF_DATA_BLOCK)Buffer.Base(); +} + + +/***************************************************************** + * * + * Functions used to navigate through the performance data. * + * * + *****************************************************************/ + +inline PPERF_OBJECT_TYPE FirstObject( PPERF_DATA_BLOCK PerfData ) +{ + return( (PPERF_OBJECT_TYPE)((PBYTE)PerfData + PerfData->HeaderLength) ); +} + +inline PPERF_OBJECT_TYPE NextObject( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_OBJECT_TYPE)((PBYTE)PerfObj + PerfObj->TotalByteLength) ); +} + +inline PPERF_COUNTER_DEFINITION FirstCounter( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_COUNTER_DEFINITION) ((PBYTE)PerfObj + PerfObj->HeaderLength) ); +} + +inline PPERF_COUNTER_DEFINITION NextCounter( PPERF_COUNTER_DEFINITION PerfCntr ) +{ + return( (PPERF_COUNTER_DEFINITION)((PBYTE)PerfCntr + PerfCntr->ByteLength) ); +} + +inline PPERF_INSTANCE_DEFINITION FirstInstance( PPERF_OBJECT_TYPE PerfObj ) +{ + return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfObj + PerfObj->DefinitionLength) ); +} + +inline PPERF_INSTANCE_DEFINITION NextInstance( PPERF_INSTANCE_DEFINITION PerfInst ) +{ + PPERF_COUNTER_BLOCK PerfCntrBlk; + + PerfCntrBlk = (PPERF_COUNTER_BLOCK)((PBYTE)PerfInst + PerfInst->ByteLength); + + return( (PPERF_INSTANCE_DEFINITION)((PBYTE)PerfCntrBlk + PerfCntrBlk->ByteLength) ); +} + + +template<class T> +T GetCounterValueForProcessID(PPERF_OBJECT_TYPE pPerfObj, DWORD dwCounterIndex, DWORD dwProcessID) +{ + unsigned long PROC_ID_COUNTER = 784; + + BOOL bProcessIDExist = FALSE; + PPERF_COUNTER_DEFINITION pPerfCntr = NULL; + PPERF_COUNTER_DEFINITION pTheRequestedPerfCntr = NULL; + PPERF_COUNTER_DEFINITION pProcIDPerfCntr = NULL; + PPERF_INSTANCE_DEFINITION pPerfInst = NULL; + PPERF_COUNTER_BLOCK pCounterBlock = NULL; + + // Get the first counter. + + pPerfCntr = FirstCounter( pPerfObj ); + + for( DWORD j=0; j < pPerfObj->NumCounters; j++ ) + { + if (pPerfCntr->CounterNameTitleIndex == PROC_ID_COUNTER) + { + pProcIDPerfCntr = pPerfCntr; + if (pTheRequestedPerfCntr) + break; + } + + if (pPerfCntr->CounterNameTitleIndex == dwCounterIndex) + { + pTheRequestedPerfCntr = pPerfCntr; + if (pProcIDPerfCntr) + break; + } + + // Get the next counter. + + pPerfCntr = NextCounter( pPerfCntr ); + } + + if( pPerfObj->NumInstances == PERF_NO_INSTANCES ) + { + pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfObj + pPerfObj->DefinitionLength); + } + else + { + pPerfInst = FirstInstance( pPerfObj ); + + for( int k=0; k < pPerfObj->NumInstances; k++ ) + { + pCounterBlock = (PPERF_COUNTER_BLOCK) ((LPBYTE) pPerfInst + pPerfInst->ByteLength); + if (pCounterBlock) + { + DWORD processID = *(DWORD*)((LPBYTE) pCounterBlock + pProcIDPerfCntr->CounterOffset); + if (processID == dwProcessID) + { + bProcessIDExist = TRUE; + break; + } + } + + // Get the next instance. + pPerfInst = NextInstance( pPerfInst ); + } + } + + if (bProcessIDExist && pCounterBlock) + { + T *lnValue = NULL; + lnValue = (T*)((LPBYTE) pCounterBlock + pTheRequestedPerfCntr->CounterOffset); + return *lnValue; + } + return -1; +} + + +template<class T> +T GetCounterValueForProcessID(PERF_DATA_BLOCK **pPerfData, DWORD dwObjectIndex, DWORD dwCounterIndex, DWORD dwProcessID) +{ + QueryPerformanceData(pPerfData, dwObjectIndex, dwCounterIndex); + + PPERF_OBJECT_TYPE pPerfObj = NULL; + T lnValue = {0}; + + // Get the first object type. + pPerfObj = FirstObject( *pPerfData ); + + // Look for the given object index + + for( DWORD i=0; i < (*pPerfData)->NumObjectTypes; i++ ) + { + + if (pPerfObj->ObjectNameTitleIndex == dwObjectIndex) + { + lnValue = GetCounterValueForProcessID<T>(pPerfObj, dwCounterIndex, dwProcessID); + break; + } + + pPerfObj = NextObject( pPerfObj ); + } + return lnValue; +} + + +// ------------------------------------------------------------------------------------------- // +// CPerfTracker implementation. +// ------------------------------------------------------------------------------------------- // + +CPerfTracker::CPerfTracker() +{ + Init( 0 ); + + SYSTEM_INFO info; + GetSystemInfo( &info ); + m_nProcessors = (int)info.dwNumberOfProcessors; +} + + +void CPerfTracker::Init( unsigned long dwProcessID ) +{ + m_dwProcessID = dwProcessID; + m_lnOldValue = 0; +} + + +unsigned long CPerfTracker::GetProcessID() +{ + return m_dwProcessID; +} + + +int CPerfTracker::GetCPUPercentage() +{ + DWORD dwObjectIndex = PROCESS_OBJECT_INDEX; + DWORD dwCpuUsageIndex = PROCESSOR_TIME_COUNTER_INDEX; + + PPERF_DATA_BLOCK pPerfData = NULL; + LONGLONG lnNewValue = GetCounterValueForProcessID<LONGLONG>( &pPerfData, dwObjectIndex, dwCpuUsageIndex, m_dwProcessID ); + LARGE_INTEGER NewPerfTime100nSec = pPerfData->PerfTime100nSec; + + if ( m_lnOldValue == 0 ) + { + m_lnOldValue = lnNewValue; + m_OldPerfTime100nSec = NewPerfTime100nSec; + return 0; + } + + LONGLONG lnValueDelta = lnNewValue - m_lnOldValue; + double DeltaPerfTime100nSec = (double)NewPerfTime100nSec.QuadPart - (double)m_OldPerfTime100nSec.QuadPart; + + m_lnOldValue = lnNewValue; + m_OldPerfTime100nSec = NewPerfTime100nSec; + + double a = (double)lnValueDelta / DeltaPerfTime100nSec; + + int CpuUsage = (int) (a*100); + if (CpuUsage < 0) + return 0; + + return CpuUsage / m_nProcessors; +} + +#endif
\ No newline at end of file diff --git a/utils/vmpi/vmpi_service/perf_counters.h b/utils/vmpi/vmpi_service/perf_counters.h new file mode 100644 index 0000000..aa80014 --- /dev/null +++ b/utils/vmpi/vmpi_service/perf_counters.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef PERF_COUNTERS_H +#define PERF_COUNTERS_H +#ifdef _WIN32 +#pragma once +#endif + + +class IPerfTracker +{ +public: + virtual void Init( unsigned long dwProcessID ) = 0; + virtual void Release() = 0; + + virtual unsigned long GetProcessID() = 0; + virtual void GetPerfData( int &processorPercentage, int &memoryUsageMegabytes ) = 0; +}; + + +IPerfTracker* CreatePerfTracker(); + + +#endif // PERF_COUNTERS_H diff --git a/utils/vmpi/vmpi_service/resource.h b/utils/vmpi/vmpi_service/resource.h new file mode 100644 index 0000000..492680b --- /dev/null +++ b/utils/vmpi/vmpi_service/resource.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Vmpi_service.rc +// +#define IDS_STRING102 102 +#define IDS_VERSION_STRING 102 // *** If this changes, change the matching value in vmpi_defs.h! + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_service/service_conn_mgr.cpp b/utils/vmpi/vmpi_service/service_conn_mgr.cpp new file mode 100644 index 0000000..c8b48ca --- /dev/null +++ b/utils/vmpi/vmpi_service/service_conn_mgr.cpp @@ -0,0 +1,234 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "service_conn_mgr.h" +#include "vmpi.h" +#include "tier0/dbg.h" +#include "tcpsocket_helpers.h" + + +#define SERVICECONNMGR_CONNECT_ATTEMPT_INTERVAL 1000 + + + +// ------------------------------------------------------------------------------------------- // +// CServiceConn. +// ------------------------------------------------------------------------------------------- // + +CServiceConn::CServiceConn() +{ + m_pSocket = NULL; +} + + +CServiceConn::~CServiceConn() +{ + if ( m_pSocket ) + m_pSocket->Release(); +} + + +// ------------------------------------------------------------------------------------------- // +// CServiceConnMgr. +// ------------------------------------------------------------------------------------------- // + + +CServiceConnMgr::CServiceConnMgr() +{ + m_bServer = false; + m_bShuttingDown = false; + m_pListenSocket = NULL; +} + + +CServiceConnMgr::~CServiceConnMgr() +{ + Term(); +} + + +bool CServiceConnMgr::InitServer() +{ + Term(); + + m_bServer = true; + + // Create a socket to listen on. + for ( int iPort=VMPI_SERVICE_FIRST_UI_PORT; iPort <= VMPI_SERVICE_LAST_UI_PORT; iPort++ ) + { + m_pListenSocket = CreateTCPListenSocketEmu( iPort, 5 ); + if ( m_pListenSocket ) + break; + } + if ( !m_pListenSocket ) + return false; + + return true; +} + + +bool CServiceConnMgr::InitClient() +{ + Term(); + + m_bServer = false; + + AttemptConnect(); + return true; +} + + +void CServiceConnMgr::Term() +{ + m_bShuttingDown = true; // This prevents some reentrancy. + + // Get rid of our registry key. + if ( m_pListenSocket ) + { + m_pListenSocket->Release(); + m_pListenSocket = NULL; + } + + m_Connections.PurgeAndDeleteElements(); + + m_bShuttingDown = false; +} + + +bool CServiceConnMgr::IsConnected() +{ + return m_Connections.Count() != 0; +} + + +void CServiceConnMgr::Update() +{ + DWORD curTime = GetTickCount(); + + // Connect if we're an unconnected client. + if ( m_bServer ) + { + if ( m_pListenSocket ) + { + // Listen for more connections. + while ( 1 ) + { + CIPAddr addr; + ITCPSocket *pSocket = m_pListenSocket->UpdateListen( &addr ); + if ( !pSocket ) + break; + + CServiceConn *pConn = new CServiceConn; + pConn->m_ID = m_Connections.AddToTail( pConn ); + pConn->m_LastRecvTime = curTime; + pConn->m_pSocket = pSocket; + + OnNewConnection( pConn->m_ID ); + } + } + } + else + { + if ( !IsConnected() && curTime - m_LastConnectAttemptTime >= SERVICECONNMGR_CONNECT_ATTEMPT_INTERVAL ) + { + AttemptConnect(); + } + } + + // Check for timeouts and send acks. + int iNext; + for ( int iCur=m_Connections.Head(); iCur != m_Connections.InvalidIndex(); iCur=iNext ) + { + iNext = m_Connections.Next( iCur ); + CServiceConn *pConn = m_Connections[iCur]; + + if ( pConn->m_pSocket->IsConnected() ) + { + DWORD startTime = GetTickCount(); + CUtlVector<unsigned char> data; + while ( pConn->m_pSocket->Recv( data ) ) + { + HandlePacket( (char*)data.Base(), data.Count() ); + + // Don't sit in this loop too long. + if ( (GetTickCount() - startTime) > 50 ) + break; + } + } + else + { + OnTerminateConnection( iCur ); + m_Connections.Remove( iCur ); + delete pConn; + } + } +} + + +void CServiceConnMgr::SendPacket( int id, const void *pData, int len ) +{ + if ( id == -1 ) + { + FOR_EACH_LL( m_Connections, i ) + { + m_Connections[i]->m_pSocket->Send( pData, len ); + } + } + else + { + m_Connections[id]->m_pSocket->Send( pData, len ); + } +} + + +void CServiceConnMgr::AttemptConnect() +{ + m_LastConnectAttemptTime = GetTickCount(); + + + ITCPSocket *pSocket = NULL; + for ( int iPort=VMPI_SERVICE_FIRST_UI_PORT; iPort <= VMPI_SERVICE_LAST_UI_PORT; iPort++ ) + { + pSocket = CreateTCPSocketEmu(); + if ( !pSocket || !pSocket->BindToAny( 0 ) ) + return; + + CIPAddr addr( 127, 0, 0, 1, iPort ); + if ( TCPSocket_Connect( pSocket, &addr, 0.1 ) ) + break; + + pSocket->Release(); + pSocket = NULL; + } + + if ( pSocket ) + { + CServiceConn *pConn = new CServiceConn; + pConn->m_ID = m_Connections.AddToTail( pConn ); + pConn->m_LastRecvTime = GetTickCount(); + pConn->m_pSocket = pSocket; + + OnNewConnection( pConn->m_ID ); + } +} + + +void CServiceConnMgr::OnNewConnection( int id ) +{ +} + + +void CServiceConnMgr::OnTerminateConnection( int id ) +{ +} + + +void CServiceConnMgr::HandlePacket( const char *pData, int len ) +{ +} + diff --git a/utils/vmpi/vmpi_service/service_conn_mgr.h b/utils/vmpi/vmpi_service/service_conn_mgr.h new file mode 100644 index 0000000..c5f9558 --- /dev/null +++ b/utils/vmpi/vmpi_service/service_conn_mgr.h @@ -0,0 +1,92 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SERVICE_CONN_MGR_H +#define SERVICE_CONN_MGR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utllinkedlist.h" +#include "utlvector.h" +#include "tcpsocket.h" +#include "ThreadedTCPSocketEmu.h" + + +class CServiceConn +{ +public: + CServiceConn(); + ~CServiceConn(); + + int m_ID; + ITCPSocket *m_pSocket; + DWORD m_LastRecvTime; +}; + + +// ------------------------------------------------------------------------------------------ // +// CServiceConnMgr. This class manages connections to all the UIs (there should only be one UI at +// any given time, but it's conceivable that multiple people can be logged into NT servers +// simultaneously). +// ------------------------------------------------------------------------------------------ // + +class CServiceConnMgr +{ +public: + + CServiceConnMgr(); + ~CServiceConnMgr(); + + bool InitServer(); // Registers as a systemwide server. + bool InitClient(); // Connects to the server. + void Term(); + + // Returns true if there are any connections. If you used InitClient() and there are + // no connections, it will continuously try to connect with a server. + bool IsConnected(); + + // This should be called as often as possible. It checks for dead connections and it + // handles incoming packets from UIs. + void Update(); + + // This sends out a message. If id is -1, then it sends to all connections. + void SendPacket( int id, const void *pData, int len ); + + +// Overridables. +public: + + virtual void OnNewConnection( int id ); + virtual void OnTerminateConnection( int id ); + virtual void HandlePacket( const char *pData, int len ); + + +private: + + void AttemptConnect(); + + +private: + + CUtlLinkedList<CServiceConn*, int> m_Connections; + + bool m_bShuttingDown; + + // This tells if we're running as a client or server. + bool m_bServer; + + // If we're a client, this is the last time we tried to connect. + DWORD m_LastConnectAttemptTime; + + // If we're the server, this is the socket we listen on. + ITCPListenSocket *m_pListenSocket; +}; + + +#endif // SERVICE_CONN_MGR_H diff --git a/utils/vmpi/vmpi_service/service_helpers.cpp b/utils/vmpi/vmpi_service/service_helpers.cpp new file mode 100644 index 0000000..a20c5df --- /dev/null +++ b/utils/vmpi/vmpi_service/service_helpers.cpp @@ -0,0 +1,181 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "service_helpers.h" + + +static CRITICAL_SECTION g_CtrlHandlerMutex; + +static void (*g_pInternalServiceFn)( void *pParam ) = NULL; +static void *g_pInternalServiceParam = NULL; + +static volatile bool g_bShouldExit = false; + + +SERVICE_STATUS MyServiceStatus; +SERVICE_STATUS_HANDLE MyServiceStatusHandle = NULL; + + +void WINAPI MyServiceCtrlHandler( DWORD Opcode ) +{ + DWORD status; + + switch(Opcode) + { + case SERVICE_CONTROL_STOP: + // Do whatever it takes to stop here. + ServiceHelpers_ExitEarly(); + + MyServiceStatus.dwWin32ExitCode = 0; + MyServiceStatus.dwCurrentState = SERVICE_STOPPED; + + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } + + Msg( "[MY_SERVICE] Leaving MyService \n" ); + return; + + case SERVICE_CONTROL_INTERROGATE: + // Fall through to send current status. + break; + + default: + Msg("[MY_SERVICE] Unrecognized opcode %ld\n", Opcode ); + } + + // Send current status. + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } +} + + +void WINAPI MyServiceStart( DWORD argc, LPTSTR *argv ) +{ + DWORD status; + + MyServiceStatus.dwServiceType = SERVICE_WIN32; + MyServiceStatus.dwCurrentState = SERVICE_START_PENDING; + MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + MyServiceStatus.dwWin32ExitCode = 0; + MyServiceStatus.dwServiceSpecificExitCode = 0; + MyServiceStatus.dwCheckPoint = 0; + MyServiceStatus.dwWaitHint = 0; + + MyServiceStatusHandle = RegisterServiceCtrlHandler( "MyService", MyServiceCtrlHandler ); + if ( MyServiceStatusHandle == (SERVICE_STATUS_HANDLE)0 ) + { + Msg("[MY_SERVICE] RegisterServiceCtrlHandler failed %d\n", GetLastError() ); + return; + } + + // Initialization complete - report running status. + MyServiceStatus.dwCurrentState = SERVICE_RUNNING; + + if ( !SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ) ) + { + status = GetLastError(); + Msg( "[MY_SERVICE] SetServiceStatus error %ld\n", status ); + } + + + // Run the app's main in-thread loop. + g_pInternalServiceFn( g_pInternalServiceParam ); + + + // Tell the SCM that we're stopped. + MyServiceStatus.dwCurrentState = SERVICE_STOPPED; + MyServiceStatus.dwWin32ExitCode = NO_ERROR; + MyServiceStatus.dwServiceSpecificExitCode = 0; + SetServiceStatus( MyServiceStatusHandle, &MyServiceStatus ); + + + // This is where the service does its work. + Msg( "[MY_SERVICE] Returning the Main Thread \n" ); +} + + +void ServiceHelpers_Init() +{ + InitializeCriticalSection( &g_CtrlHandlerMutex ); +} + + +bool ServiceHelpers_StartService( const char *pServiceName, void (*pFn)( void *pParam ), void *pParam ) +{ + // Ok, just run the service. + const SERVICE_TABLE_ENTRY DispatchTable[2] = + { + { (char*)pServiceName, MyServiceStart }, + { NULL, NULL } + }; + + g_pInternalServiceFn = pFn; + g_pInternalServiceParam = pParam; + + if ( StartServiceCtrlDispatcher( DispatchTable ) ) + { + return true; + } + else + { + Msg( "StartServiceCtrlDispatcher error = '%s'\n", GetLastErrorString() ); + return false; + } +} + + +void ServiceHelpers_ExitEarly() +{ + EnterCriticalSection( &g_CtrlHandlerMutex ); + g_bShouldExit = true; + LeaveCriticalSection( &g_CtrlHandlerMutex ); +} + + +bool ServiceHelpers_ShouldExit() +{ + EnterCriticalSection( &g_CtrlHandlerMutex ); + bool bRet = g_bShouldExit; + LeaveCriticalSection( &g_CtrlHandlerMutex ); + + return bRet; +} + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + diff --git a/utils/vmpi/vmpi_service/service_helpers.h b/utils/vmpi/vmpi_service/service_helpers.h new file mode 100644 index 0000000..dc70f28 --- /dev/null +++ b/utils/vmpi/vmpi_service/service_helpers.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SERVICE_HELPERS_H +#define SERVICE_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Call this if you want to use the ExitEarly() and ShouldExit() helpers. +void ServiceHelpers_Init(); + +// Start this app in the service control manager. +// +// The service will run in a thread. If the service starts successfully, then +// it will call pFn and pass in pParam. Inside there, you should loop until +// ShouldServiceExit() returns true. +bool ServiceHelpers_StartService( const char *pServiceName, void (*pFn)( void *pParam ), void *pParam ); + + +// Call this to exit the service early. This will make ShouldServiceExit() return true, +// and your main thread function should pick it up and exit. +// +// NOTE: this can be used even if the service isn't running as long as you call ServiceHelpers_Init(). +void ServiceHelpers_ExitEarly(); + +// Your thread loop should call this each time around. If this function returns true, +// then your thread function should return, causing the service to exit. +// +// NOTE: this can be used even if the service isn't running as long as you call ServiceHelpers_Init(). +bool ServiceHelpers_ShouldExit(); + + +// This function wants a better home. +char* GetLastErrorString(); + + +#endif // SERVICE_HELPERS_H diff --git a/utils/vmpi/vmpi_service/vmpi_service.cpp b/utils/vmpi/vmpi_service/vmpi_service.cpp new file mode 100644 index 0000000..03f7eea --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.cpp @@ -0,0 +1,1710 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_service.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include "vmpi.h" +#include "iphelpers.h" +#include "bitbuf.h" +#include "tier1/strtools.h" +#include "interface.h" +#include "ilaunchabledll.h" +#include "resource.h" +#include "consolewnd.h" +#include <io.h> +#include "utllinkedlist.h" +#include "service_helpers.h" +#include "vmpi_filesystem.h" +#include "service_conn_mgr.h" +#include "resource.h" +#include "perf_counters.h" +#include "tier0/icommandline.h" + + +// If we couldn't get into a job (maybe they weren't accepting more workers at the time), +// then we wait this long and retry the connection. +#define JOB_MEMORY_DURATION 60 + + +char g_VersionString[64]; // From the IDS_VERSION_STRING string. +HKEY g_hVMPIServiceKey = NULL; // HKML/Software/Valve/VMPI + +double g_flLastKillProcessTime = 0; + +char *g_pPassword = NULL; // Set if this service is using a pw. +ISocket *g_pSocket = NULL; +int g_SocketPort = -1; // Which port we were able to bind the port on. + +char g_RunningProcess_ExeName[MAX_PATH] = {0}; +char g_RunningProcess_MapName[MAX_PATH] = {0}; +HANDLE g_hRunningProcess = NULL; +HANDLE g_hRunningThread = NULL; +DWORD g_dwRunningProcessId = 0; +IPerfTracker *g_pPerfTracker = NULL; // Tracks CPU usage. + +// When this is true, it will launch new processes invisibly. +bool g_bHideNewProcessWindows = true; + +HINSTANCE g_hInstance = NULL; +int g_iBoundPort = -1; + +bool g_bScreensaverMode = false; // If this is true, then it'll act like the service is disabled while + // a screensaver isn't running. + +// If this is on, it runs the exes out of c:/hl2/bin instead of the network. If the exes are built in debug, +// this makes it possible to catch nasty crashes. +bool g_bSuperDebugMode = false; + + +bool g_bScreensaverRunning = false; // Updated each frame to tell if the screensaver is running. + + +// GetTickCount() at the time the app was started. +DWORD g_AppStartTime = 0; + +// GetTickCount() at the time the service ran a worker app. +DWORD g_CreateProcessTime = 0; + + +CIPAddr g_CurRespondAddr; +int g_CurJobID[4]; +int g_CurJobPriority = -1; // VMPI priority of the currently-running job. + +// The directory we're running in. +char g_BaseAppPath[MAX_PATH]; +char g_FileCachePath[MAX_PATH]; // [base app path]\vmpi_service_cache. + + +// Different modes this app can run in. +#define RUNMODE_INSTALL 0 +#define RUNMODE_CONSOLE 1 +#define RUNMODE_SERVICE 2 + +int g_RunMode = RUNMODE_CONSOLE; + +bool g_bMinimized = false; // true if they run with -minimized. +int g_iCurState = VMPI_SERVICE_STATE_IDLE; +char g_CurMasterName[512] = {0}; + + + +////// +// This block of variables is setup while we wait for the downloader to finish. +// When the downloading is complete, we launch the app using these variables. +////// + +#ifdef _DEBUG + #define MAX_DOWNLOADER_TIME_ALLOWED 300000 // If the downloader takes longer than this, kill it. +#else + #define MAX_DOWNLOADER_TIME_ALLOWED 30 // If the downloader takes longer than this, kill it. +#endif + +// If this is non-NULL, then there is NOT a VMPI worker app running currently.. the downloader +HANDLE g_Waiting_hProcess = NULL; +float g_Waiting_StartTime = 0; +CUtlVector<char*> g_Waiting_Argv; +int g_Waiting_Priority = 0; +bool g_Waiting_bShowAppWindow = false; +bool g_Waiting_bPatching = 0; // If this is nonzero, then we're downloading so we can apply a patch. + + +// Used to track the services browsers that have been talking to us lately. +#define SERVICES_BROWSER_TIMEOUT 10 // We remove a services browser from the list if we don't hear from it for this long. +class CServicesBrowserInfo +{ +public: + float m_flLastPingTime; // Last time they talked to us. + CIPAddr m_Addr; // Their IP address. +}; +CUtlVector<CServicesBrowserInfo> g_ServicesBrowsers; + + +void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom ); +void KillRunningProcess( const char *pReason, bool bGoToIdle ); +void LoadStateFromRegistry(); +void SaveStateToRegistry(); + + +void SetPassword( const char *pPassword ) +{ + delete [] g_pPassword; + if ( pPassword ) + { + int len = V_strlen( pPassword ) + 1; + g_pPassword = new char[len]; + V_strncpy( g_pPassword, pPassword, len ); + } + else + { + g_pPassword = NULL; + } +} + + +// ------------------------------------------------------------------------------------------ // +// This handles connection to clients. +// ------------------------------------------------------------------------------------------ // + +class CVMPIServiceConnMgr : public CServiceConnMgr +{ +public: + + virtual void OnNewConnection( int id ); + virtual void HandlePacket( const char *pData, int len ); + void SendCurStateTo( int id ); + + +public: + + void AddConsoleOutput( const char *pMsg ); + + void SetAppState( int iState ); +}; + + +void CVMPIServiceConnMgr::AddConsoleOutput( const char *pMsg ) +{ + // Tell clients of the new text string. + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_CONSOLE_TEXT ); + data.AddMultipleToTail( strlen( pMsg ) + 1, pMsg ); + SendPacket( -1, data.Base(), data.Count() ); +} + + +void CVMPIServiceConnMgr::SetAppState( int iState ) +{ + // Update our state and send it to the clients. + g_iCurState = iState; + SendCurStateTo( -1 ); +} + + +void CVMPIServiceConnMgr::OnNewConnection( int id ) +{ + // Send our current state to the new connection. + Msg( "(debug) Made a new connection!\n" ); + SendCurStateTo( id ); +} + + +void CVMPIServiceConnMgr::SendCurStateTo( int id ) +{ + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_STATE ); + data.AddMultipleToTail( sizeof( g_iCurState ), (char*)&g_iCurState ); + data.AddToTail( (char)g_bScreensaverMode ); + + if ( g_pPassword ) + data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword ); + else + data.AddToTail( 0 ); + + SendPacket( -1, data.Base(), data.Count() ); +} + + +void CVMPIServiceConnMgr::HandlePacket( const char *pData, int len ) +{ + switch( pData[0] ) + { + case VMPI_KILL_PROCESS: + { + HandlePacket_KILL_PROCESS( NULL ); + } + break; + + case VMPI_SERVICE_DISABLE: + { + KillRunningProcess( "Got a VMPI_SERVICE_DISABLE packet", true ); + SetAppState( VMPI_SERVICE_STATE_DISABLED ); + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_ENABLE: + { + if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED ) + { + SetAppState( VMPI_SERVICE_STATE_IDLE ); + } + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_UPDATE_PASSWORD: + { + const char *pStr = pData + 1; + SetPassword( pStr ); + + // Send out the new state. + SendCurStateTo( -1 ); + } + break; + + case VMPI_SERVICE_SCREENSAVER_MODE: + { + g_bScreensaverMode = (pData[1] != 0); + SendCurStateTo( -1 ); + SaveStateToRegistry(); + } + break; + + case VMPI_SERVICE_EXIT: + { + Msg( "Got a VMPI_SERVICE_EXIT packet.\n "); + ServiceHelpers_ExitEarly(); + } + break; + } +} + +// This is allocated by the service thread and only used in there. +CVMPIServiceConnMgr *g_pConnMgr = NULL; + + +// ------------------------------------------------------------------------------------------ // +// Persistent state stuff. +// ------------------------------------------------------------------------------------------ // + +void LoadStateFromRegistry() +{ + if ( g_hVMPIServiceKey ) + { + DWORD val = 0; + DWORD type = REG_DWORD; + DWORD size = sizeof( val ); + + if ( RegQueryValueEx( + g_hVMPIServiceKey, + "ScreensaverMode", + 0, + &type, + (unsigned char*)&val, + &size ) == ERROR_SUCCESS && + type == REG_DWORD && + size == sizeof( val ) ) + { + g_bScreensaverMode = (val != 0); + } + + if ( RegQueryValueEx( + g_hVMPIServiceKey, + "Disabled", + 0, + &type, + (unsigned char*)&val, + &size ) == ERROR_SUCCESS && + type == REG_DWORD && + size == sizeof( val ) && + val != 0 ) + { + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_DISABLED ); + } + } +} + +void SaveStateToRegistry() +{ + if ( g_hVMPIServiceKey ) + { + DWORD val; + + val = g_bScreensaverMode; + RegSetValueEx( + g_hVMPIServiceKey, + "ScreensaverMode", + 0, + REG_DWORD, + (unsigned char*)&val, + sizeof( val ) ); + + val = (g_iCurState == VMPI_SERVICE_STATE_DISABLED); + RegSetValueEx( + g_hVMPIServiceKey, + "Disabled", + 0, + REG_DWORD, + (unsigned char*)&val, + sizeof( val ) ); + } +} + + +// ------------------------------------------------------------------------------------------ // +// Helper functions. +// ------------------------------------------------------------------------------------------ // + +char* FindArg( int argc, char **argv, const char *pArgName, char *pDefaultValue="" ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pArgName ) == 0 ) + { + if ( (i+1) >= argc ) + return pDefaultValue; + else + return argv[i+1]; + } + } + return NULL; +} + + +SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const char *pMsg ) +{ + // Put the message in status.txt. +#ifdef VMPI_SERVICE_LOGS + static FILE *fp = fopen( "c:\\vmpi_service.log", "wt" ); + if ( fp ) + { + fprintf( fp, "%s", pMsg ); + fflush( fp ); + } +#endif + + + // Print it to the console. + if ( g_pConnMgr ) + g_pConnMgr->AddConsoleOutput( pMsg ); + + // Output to the debug console. + OutputDebugString( pMsg ); + + if ( spewType == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( spewType == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +char* CopyString( const char *pStr ) +{ + int len = V_strlen( pStr ) + 1; + char *pRet = new char[len]; + V_strncpy( pRet, pStr, len ); + return pRet; +} + +void AppendArg( CUtlVector<char*> &newArgv, const char *pIn ) +{ + newArgv.AddToTail( CopyString( pIn ) ); +} + + +void SendStartStatus( bool bStatus ) +{ + for ( int i=0; i < 3; i++ ) + { + char data[4096]; + bf_write dataBuf( data, sizeof( data ) ); + dataBuf.WriteByte( VMPI_PROTOCOL_VERSION ); + dataBuf.WriteByte( VMPI_NOTIFY_START_STATUS ); + dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) ); + dataBuf.WriteByte( bStatus ); + g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() ); + + Sleep( 50 ); + } +} + + +void SendEndStatus() +{ + for ( int i=0; i < 3; i++ ) + { + char data[4096]; + bf_write dataBuf( data, sizeof( data ) ); + dataBuf.WriteByte( VMPI_PROTOCOL_VERSION ); + dataBuf.WriteByte( VMPI_NOTIFY_END_STATUS ); + dataBuf.WriteBytes( g_CurJobID, sizeof( g_CurJobID ) ); + g_pSocket->SendTo( &g_CurRespondAddr, data, dataBuf.GetNumBytesWritten() ); + + Sleep( 50 ); + } +} + + +void KillRunningProcess( const char *pReason, bool bGoToIdle ) +{ + // Kill the downloader if it's running. + if ( g_Waiting_hProcess ) + { + TerminateProcess( g_Waiting_hProcess, 1 ); + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + } + + if ( !g_hRunningProcess ) + return; + + if ( pReason ) + Msg( pReason ); + + SendEndStatus(); + TerminateProcess( g_hRunningProcess, 1 ); + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + + // Yep. Now we can start a new one. + CloseHandle( g_hRunningThread ); + g_hRunningThread = NULL; + + CloseHandle( g_hRunningProcess ); + g_hRunningProcess = NULL; + + g_CurJobPriority = -1; + + if ( bGoToIdle ) + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); +} + + +// ------------------------------------------------------------------------------------------ // +// Job memory stuff. +// ------------------------------------------------------------------------------------------ // + +// CJobMemory is used to track which jobs we ran (or tried to run). +// We remember which jobs we did because Winsock likes to queue up the job packets on +// our socket, so if we don't remember which jobs we ran, we'd run the job a bunch of times. +class CJobMemory +{ +public: + int m_ID[4]; // Random ID that comes from the server. + float m_Time; +}; + +CUtlLinkedList<CJobMemory, int> g_JobMemories; + + +bool FindJobMemory( int id[4] ) +{ + int iNext; + for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext ) + { + iNext = g_JobMemories.Next( i ); + + CJobMemory *pJob = &g_JobMemories[i]; + if ( memcmp( pJob->m_ID, id, sizeof( pJob->m_ID ) ) == 0 ) + return true; + } + return false; +} + + +void TimeoutJobIDs() +{ + double flCurTime = Plat_FloatTime(); + + int iNext; + for ( int i=g_JobMemories.Head(); i != g_JobMemories.InvalidIndex(); i=iNext ) + { + iNext = g_JobMemories.Next( i ); + + if ( (flCurTime - g_JobMemories[i].m_Time) > JOB_MEMORY_DURATION ) + g_JobMemories.Remove( i ); + } +} + + +void AddJobMemory( int id[4] ) +{ + TimeoutJobIDs(); + + CJobMemory job; + memcpy( job.m_ID, id, sizeof( job.m_ID ) ); + job.m_Time = Plat_FloatTime(); + g_JobMemories.AddToTail( job ); +} + + +bool CheckJobID( bf_read &buf, int jobID[4] ) +{ + TimeoutJobIDs(); + + jobID[0] = buf.ReadLong(); + jobID[1] = buf.ReadLong(); + jobID[2] = buf.ReadLong(); + jobID[3] = buf.ReadLong(); + if ( FindJobMemory( jobID ) || buf.IsOverflowed() ) + { + return false; + } + + return true; +} + + + + +// ------------------------------------------------------------------------------------------ // +// The main VMPI code. +// ------------------------------------------------------------------------------------------ // + +void VMPI_Waiter_Term() +{ + KillRunningProcess( NULL, false ); + if ( g_pConnMgr ) + { + g_pConnMgr->Term(); + delete g_pConnMgr; + g_pConnMgr = NULL; + } + + if ( g_pSocket ) + { + g_pSocket->Release(); + g_pSocket = NULL; + } + + g_pPerfTracker->Release(); + g_pPerfTracker = NULL; +} + + +bool VMPI_Waiter_Init() +{ + // Run as idle priority. + HKEY hKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); + DWORD dwVal = 0; + DWORD dummyType = REG_DWORD; + DWORD dwValLen = sizeof( dwVal ); + if ( RegQueryValueEx( hKey, "LowPriority", NULL, &dummyType, (LPBYTE)&dwVal, &dwValLen ) == ERROR_SUCCESS ) + { + if ( dwVal ) + { + SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); + } + } + else + { + RegSetValueEx( hKey, "LowPriority", 0, REG_DWORD, (unsigned char*)&dwVal, sizeof( dwVal ) ); + } + + g_pConnMgr = new CVMPIServiceConnMgr; + + if ( !g_pConnMgr->InitServer() ) + Msg( "ERROR INITIALIZING CONNMGR\n" ); + + g_pSocket = CreateIPSocket(); + if ( !g_pSocket ) + { + Msg( "Error creating a socket.\n" ); + return false; + } + + // Bind to the first port we find in the range [VMPI_SERVICE_PORT, VMPI_LAST_SERVICE_PORT]. + int iTest; + for ( iTest=VMPI_SERVICE_PORT; iTest <= VMPI_LAST_SERVICE_PORT; iTest++ ) + { + g_SocketPort = iTest; + if ( g_pSocket->BindToAny( iTest ) ) + break; + } + if ( iTest == VMPI_LAST_SERVICE_PORT ) + { + Msg( "Error binding a socket to port %d.\n", VMPI_SERVICE_PORT ); + VMPI_Waiter_Term(); + return false; + } + + g_iBoundPort = iTest; + g_pPerfTracker = CreatePerfTracker(); + return true; +} + + +void RunInDLL( const char *pFilename, CUtlVector<char*> &newArgv ) +{ + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY ); + + bool bSuccess = false; + CSysModule *pModule = Sys_LoadModule( pFilename ); + if ( pModule ) + { + CreateInterfaceFn fn = Sys_GetFactory( pModule ); + if ( fn ) + { + ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, NULL ); + if( pDLL ) + { + // Do this here because the executables we would have launched usually would do it. + CommandLine()->CreateCmdLine( newArgv.Count(), newArgv.Base() ); + pDLL->main( newArgv.Count(), newArgv.Base() ); + bSuccess = true; + SpewOutputFunc( MySpewOutputFunc ); + } + } + + Sys_UnloadModule( pModule ); + } + + if ( !bSuccess ) + { + Msg( "Error running VRAD (or VVIS) out of DLL '%s'\n", pFilename ); + } + + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); +} + + +void GetArgsFromBuffer( + bf_read &buf, + CUtlVector<char*> &newArgv, + bool *bShowAppWindow ) +{ + int nArgs = buf.ReadWord(); + + bool bSpewArgs = false; + + for ( int iArg=0; iArg < nArgs; iArg++ ) + { + char argStr[512]; + buf.ReadString( argStr, sizeof( argStr ) ); + + AppendArg( newArgv, argStr ); + if ( stricmp( argStr, "-mpi_verbose" ) == 0 ) + bSpewArgs = true; + + if ( stricmp( argStr, "-mpi_ShowAppWindow" ) == 0 ) + *bShowAppWindow = true; + } + + if ( bSpewArgs ) + { + Msg( "nArgs: %d\n", newArgv.Count() ); + for ( int i=0; i < newArgv.Count(); i++ ) + Msg( "Arg %d: %s\n", i, newArgv[i] ); + } +} + + +bool GetDLLFilename( CUtlVector<char*> &newArgv, char pDLLFilename[MAX_PATH] ) +{ + char *argStr = newArgv[0]; + int argLen = strlen( argStr ); + if ( argLen <= 4 ) + return false; + + if ( Q_stricmp( &argStr[argLen-4], ".exe" ) != 0 ) + return false; + + char baseFilename[MAX_PATH]; + Q_strncpy( baseFilename, argStr, MAX_PATH ); + baseFilename[ min( MAX_PATH-1, argLen-4 ) ] = 0; + + // First try _dll.dll (src_main), then try .dll (rel). + V_snprintf( pDLLFilename, MAX_PATH, "%s_dll.dll", baseFilename ); + if ( _access( pDLLFilename, 0 ) != 0 ) + { + V_snprintf( pDLLFilename, MAX_PATH, "%s.dll", baseFilename ); + } + + return true; +} + +void BuildCommandLineFromArgs( CUtlVector<char*> &newArgv, char *pOut, int outLen ) +{ + pOut[0] = 0; + + for ( int i=0; i < newArgv.Count(); i++ ) + { + char argStr[512]; + if ( strlen( newArgv[i] ) > 0 && newArgv[i][strlen(newArgv[i])-1] == '\\' ) + Q_snprintf( argStr, sizeof( argStr ), "\"%s\\\" ", newArgv[i] ); + else + Q_snprintf( argStr, sizeof( argStr ), "\"%s\" ", newArgv[i] ); + + Q_strncat( pOut, argStr, outLen, COPY_ALL_CHARACTERS ); + } +} + +bool RunProcessFromArgs( CUtlVector<char*> &newArgv, bool bShowAppWindow, bool bCreateSuspended, const char *pWorkingDir, PROCESS_INFORMATION *pOut ) +{ + char commandLine[2048]; + BuildCommandLineFromArgs( newArgv, commandLine, sizeof( commandLine ) ); + + Msg( "Running '%s'\n", commandLine ); + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + memset( pOut, 0, sizeof( *pOut ) ); + + DWORD dwFlags = 0;//IDLE_PRIORITY_CLASS; + if ( bShowAppWindow ) + dwFlags |= CREATE_NEW_CONSOLE; + else + dwFlags |= CREATE_NO_WINDOW; + + if ( bCreateSuspended ) + dwFlags |= CREATE_SUSPENDED; + + UINT oldMode = SetErrorMode( SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS ); + + BOOL bRet = CreateProcess( + NULL, + commandLine, + NULL, // security + NULL, + TRUE, + dwFlags | IDLE_PRIORITY_CLASS, // flags + NULL, // environment + pWorkingDir, + &si, + pOut ); + + SetErrorMode( oldMode ); + return (bRet != FALSE); +} + + +void RunProcessAtCommandLine( + CUtlVector<char*> &newArgv, + bool bShowAppWindow, + bool bCreateSuspended, + int iPriority ) +{ + // current directory (use c:\\ because we don't want it to accidentally share + // DLLs like vstdlib with us). PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi; + if ( RunProcessFromArgs( newArgv, bShowAppWindow, bCreateSuspended, g_FileCachePath, &pi ) ) + { + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_BUSY ); + + if ( newArgv.Count() > 0 && newArgv[0] ) + { + V_FileBase( newArgv[0], g_RunningProcess_ExeName, sizeof( g_RunningProcess_ExeName ) ); + + if ( V_stricmp( g_RunningProcess_ExeName, "vrad" ) == 0 || V_stricmp( g_RunningProcess_ExeName, "vvis" ) == 0 ) + V_FileBase( newArgv[newArgv.Count()-1], g_RunningProcess_MapName, sizeof( g_RunningProcess_MapName ) ); + } + + g_hRunningProcess = pi.hProcess; + g_hRunningThread = pi.hThread; + g_dwRunningProcessId = pi.dwProcessId; + g_pPerfTracker->Init( g_dwRunningProcessId ); + g_CurJobPriority = iPriority; + g_CreateProcessTime = GetTickCount(); + + SendStartStatus( true ); + } + else + { + Msg( " - ERROR in CreateProcess (%s)!\n", GetLastErrorString() ); + SendStartStatus( false ); + g_CurJobPriority = -1; + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + } +} + + +bool WaitForProcessToExit() +{ + if ( g_hRunningProcess ) + { + // Did the process complete yet? + if ( WaitForSingleObject( g_hRunningProcess, 0 ) == WAIT_TIMEOUT ) + { + // Nope.. keep waiting. + return true; + } + else + { + Msg( "Finished!\n "); + + SendEndStatus(); + + // Change back to the 'waiting' icon. + if ( g_pConnMgr ) + g_pConnMgr->SetAppState( VMPI_SERVICE_STATE_IDLE ); + g_CurJobPriority = -1; + + // Yep. Now we can start a new one. + CloseHandle( g_hRunningThread ); + CloseHandle( g_hRunningProcess ); + g_hRunningProcess = g_hRunningThread = NULL; + g_RunningProcess_ExeName[0] = g_RunningProcess_MapName[0] = 0; + } + } + + return false; +} + + +void HandleWindowMessages() +{ + MSG msg; + while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + +void GetRunningProcessStats( int &processorPercentage, int &memoryUsageMegabytes ) +{ + static int lastProcessorPercentage = 0; + static int lastMemory = 0; + if ( g_hRunningProcess ) + { + // Only update this every couple seconds. It's not too expensive (about 800 microseconds), but we don't + // need to do it a whole lot. + static DWORD lastReturnTime = GetTickCount(); + DWORD curTime = GetTickCount(); + if ( (curTime - lastReturnTime) >= 1000 ) + { + lastReturnTime = curTime; + g_pPerfTracker->GetPerfData( lastProcessorPercentage, lastMemory ); + } + } + else + { + lastProcessorPercentage = lastMemory = 0; + } + + processorPercentage = lastProcessorPercentage; + memoryUsageMegabytes = lastMemory; +} + + +void BuildPingHeader( CUtlVector<char> &data, char packetID, int iState ) +{ + // Figure out the computer's name. + char computerName[128]; + DWORD computerNameLen = sizeof( computerName ); + GetComputerName( computerName, &computerNameLen ); + + // Ping back at them. + data.AddToTail( VMPI_PROTOCOL_VERSION ); + data.AddToTail( packetID ); + data.AddToTail( (char)iState ); + + DWORD liveTime = GetTickCount() - g_AppStartTime; + data.AddMultipleToTail( sizeof( liveTime ), (char*)&liveTime ); + + data.AddMultipleToTail( sizeof( g_SocketPort ), (char*)&g_SocketPort ); + data.AddMultipleToTail( strlen( computerName ) + 1, computerName ); + + if ( g_hRunningProcess ) + data.AddMultipleToTail( strlen( g_CurMasterName ) + 1, g_CurMasterName ); + else + data.AddMultipleToTail( 1, "" ); + + // Write in how long the worker app has been running. + DWORD appRunTime = 0; + if ( g_hRunningProcess ) + appRunTime = GetTickCount() - g_CreateProcessTime; + + data.AddMultipleToTail( sizeof( appRunTime ), (char*)&appRunTime ); + + // Finally, write the password. + if ( g_pPassword ) + data.AddMultipleToTail( strlen( g_pPassword ) + 1, g_pPassword ); + else + data.AddToTail( 0 ); + + data.AddMultipleToTail( V_strlen( g_VersionString ) + 1, g_VersionString ); + + int processorPercentage, memoryUsageMegabytes; + GetRunningProcessStats( processorPercentage, memoryUsageMegabytes ); + + // Write processor percentage. + data.AddToTail( (char)processorPercentage ); + + // Write the EXE name. + data.AddMultipleToTail( V_strlen( g_RunningProcess_ExeName ) + 1, g_RunningProcess_ExeName ); + + // Write memory usage. + short memUsageShort = (short)memoryUsageMegabytes; + data.AddMultipleToTail( sizeof( memUsageShort ), (const char*)&memUsageShort ); + + // Write the map name. + data.AddMultipleToTail( V_strlen( g_RunningProcess_MapName ) + 1, g_RunningProcess_MapName ); +} + + +// This tracks a list +void AddServicesBrowserIP( const CIPAddr &ipFrom ) +{ + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + if ( g_ServicesBrowsers[i].m_Addr == ipFrom ) + { + g_ServicesBrowsers[i].m_flLastPingTime = Plat_FloatTime(); + return; + } + } + CServicesBrowserInfo info; + info.m_Addr = ipFrom; + info.m_flLastPingTime = Plat_FloatTime(); + g_ServicesBrowsers.AddToTail( info ); +} + + +void UpdateServicesBrowserIPs() +{ + double curTime = Plat_FloatTime(); + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + if ( (curTime - g_ServicesBrowsers[i].m_flLastPingTime) >= SERVICES_BROWSER_TIMEOUT ) + { + g_ServicesBrowsers.Remove( i ); + --i; + break; + } + } +} + + +void SendStateToServicesBrowsers() +{ + int curState; + if ( g_hRunningProcess ) + { + if ( g_Waiting_bPatching ) + curState = VMPI_STATE_PATCHING; + else + curState = VMPI_STATE_BUSY; + } + else if ( g_Waiting_hProcess ) + { + if ( g_Waiting_bPatching ) + curState = VMPI_STATE_PATCHING; + else + curState = VMPI_STATE_DOWNLOADING; + } + else if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED ) + { + curState = VMPI_STATE_DISABLED; + } + else if ( g_bScreensaverMode && !g_bScreensaverRunning ) + { + curState = VMPI_STATE_SCREENSAVER_DISABLED; + } + else + { + curState = VMPI_STATE_IDLE; + } + + CUtlVector<char> data; + BuildPingHeader( data, VMPI_PING_RESPONSE, curState ); + + for ( int i=0; i < g_ServicesBrowsers.Count(); i++ ) + { + g_pSocket->SendTo( &g_ServicesBrowsers[i].m_Addr, data.Base(), data.Count() ); + } +} + + +void StopUI() +{ + char cPacket[2] = {VMPI_SERVICE_UI_PROTOCOL_VERSION, VMPI_SERVICE_TO_UI_EXIT}; + if ( g_pConnMgr ) + g_pConnMgr->SendPacket( -1, &cPacket, sizeof( cPacket ) ); + + // Wait for a bit for the connection to go away. + DWORD startTime = GetTickCount(); + while ( GetTickCount()-startTime < 2000 ) + { + if ( g_pConnMgr ) + { + g_pConnMgr->Update(); + if ( !g_pConnMgr->IsConnected() ) + break; + else + Sleep( 10 ); + } + } +} + + +void CheckScreensaverRunning() +{ + // We want to let patching finish even if we're in screensaver mode. + if ( g_Waiting_hProcess && g_Waiting_bPatching ) + return; + + BOOL bRunning = false; + SystemParametersInfo( SPI_GETSCREENSAVERRUNNING, 0, &bRunning, 0 ); + + g_bScreensaverRunning = (bRunning != 0); + if ( !g_bScreensaverRunning && g_bScreensaverMode ) + { + KillRunningProcess( "Screensaver not running", true ); + } +} + + +void AdjustSuperDebugArgs( CUtlVector<char*> &args ) +{ + // Get the directory this exe was run out of. + char filename[512]; + if ( GetModuleFileName( GetModuleHandle( NULL ), filename, sizeof( filename ) ) == 0 ) + return; + + char *pLastSlash = filename; + char *pCurPos = filename; + while ( *pCurPos ) + { + if ( *pCurPos == '/' || *pCurPos == '\\' ) + pLastSlash = pCurPos; + ++pCurPos; + } + *pLastSlash = 0; + + // In superdebug mode, run it out of c:/hl2/bin. + const char *pBase = args[0]; + const char *pBaseCur = pBase; + while ( *pBaseCur ) + { + if ( *pBaseCur == '/' || *pBaseCur == '\\' || *pBaseCur == ':' ) + { + pBase = pBaseCur+1; + pBaseCur = pBase; + } + ++pBaseCur; + } + + int maxLen = 64 + strlen( pBase ) + 1; + char *pNewFilename = new char[maxLen]; + _snprintf( pNewFilename, maxLen, "%s\\%s", filename, pBase ); + delete args[0]; + args[0] = pNewFilename; + + + // Now insert -allowdebug. + const char *pAllowDebug = "-allowdebug"; + char *pToInsert = new char[ strlen( pAllowDebug ) + 1 ]; + strcpy( pToInsert, pAllowDebug ); + args.InsertAfter( 0, pToInsert ); + +} + + +// -------------------------------------------------------------------------------- // +// Purpose: Launches vmpi_transfer.exe to download the required +// files from the master so we can launch. +// +// If successful, it sets hProcess to the process handle of the downloader. +// When that process terminates, we look for [cache dir]\ReadyToGo.txt and if it's +// there, then we start the job. +// -------------------------------------------------------------------------------- // +bool StartDownloadingAppFiles( + CUtlVector<char*> &newArgv, + char *cacheDir, + int cacheDirLen, + bool bShowAppWindow, + HANDLE *hProcess, + bool bPatching ) +{ + *hProcess = NULL; + + V_strncpy( cacheDir, g_FileCachePath, cacheDirLen ); + + // For now, cache dir is always the same. It's [current directory]\cache. + if ( _access( cacheDir, 0 ) != 0 ) + { + if ( !CreateDirectory( cacheDir, NULL ) && GetLastError() != ERROR_ALREADY_EXISTS ) + { + Warning( "Unable to create cache directory: %s.\n", cacheDir ); + return false; + } + } + + // Clear all the files in the directory. + char searchStr[MAX_PATH]; + V_ComposeFileName( cacheDir, "*.*", searchStr, sizeof( searchStr ) ); + _finddata_t findData; + intptr_t ret = _findfirst( searchStr, &findData ); + if ( ret != -1 ) + { + do + { + if ( findData.name[0] == '.' ) + continue; + + char fullFilename[MAX_PATH]; + V_ComposeFileName( cacheDir, findData.name, fullFilename, sizeof( fullFilename ) ); + if ( _unlink( fullFilename ) != 0 ) + { + Warning( "_unlink( %s ) failed.\n", fullFilename ); + return false; + } + } while ( _findnext( ret, &findData ) == 0 ); + + _findclose( ret ); + } + + // Change the EXE name to an absolute path to exactly where it is in the cache directory. + int maxExeNameLen = 1024; + char *pExeName = new char[maxExeNameLen]; + if ( bPatching ) + { + V_ComposeFileName( cacheDir, "vmpi_service_install.exe", pExeName, maxExeNameLen ); + + // Add args for the installer. + newArgv.InsertAfter( 0, CopyString( "-DontTouchUI" ) ); + + // When patching, we can't start the UI and the installer can't because we're running in the local system account + // and the UI is running on the account of whoever logged in. So what we do is send a message to the UI telling it + // to run <cacheDir>\WaitAndRestart and restart itself in N seconds. + newArgv.InsertAfter( 0, CopyString( "-Install_Quiet" ) ); + } + else + { + V_ComposeFileName( cacheDir, newArgv[0], pExeName, maxExeNameLen ); + } + + delete newArgv[0]; + newArgv[0] = pExeName; + + char fullExeFilename[MAX_PATH]; + V_ComposeFileName( g_BaseAppPath, "vmpi_transfer.exe", fullExeFilename, sizeof( fullExeFilename ) ); + + CUtlVector<char*> downloaderArgs; + downloaderArgs.AddToTail( fullExeFilename ); +#if defined( _DEBUG ) + downloaderArgs.AddToTail( "-allowdebug" ); +#endif + downloaderArgs.AddToTail( "-CachePath" ); // Tell it where to download the files to. + downloaderArgs.AddToTail( cacheDir ); + + // Pass all the -mpi_worker, -mpi_file, -mpi_filebase args into the downloader app. + for ( int i=1; i < (int)newArgv.Count()-1; i++ ) + { + if ( V_stricmp( newArgv[i], "-mpi_filebase" ) == 0 || V_stricmp( newArgv[i], "-mpi_file" ) == 0 ) + { + downloaderArgs.AddToTail( newArgv[i] ); + downloaderArgs.AddToTail( newArgv[i+1] ); + newArgv.Remove( i ); + newArgv.Remove( i ); + --i; + } + else if ( V_stricmp( newArgv[i], "-mpi_worker" ) == 0 ) + { + // We need this arg so it knows what IP to connect to, but we want to leave it in the final launch args too. + downloaderArgs.AddToTail( newArgv[i] ); + downloaderArgs.AddToTail( newArgv[i+1] ); + ++i; + } + } + + // Transfer each file. + PROCESS_INFORMATION pi; + if ( !RunProcessFromArgs( downloaderArgs, bShowAppWindow, false, g_BaseAppPath, &pi ) ) + return false; + + *hProcess = pi.hProcess; + return true; +} + + +void SendPatchCommandToUIs( DWORD dwInstallerProcessId ) +{ + Msg( "SendPatchCommandToUIs\n "); + + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UI_PROTOCOL_VERSION ); + data.AddToTail( VMPI_SERVICE_TO_UI_PATCHING ); + + // This arg tells the UI whether to exit after running the command or not. + data.AddToTail( 1 ); + + // First argument is the working directory, which is the cache path in this case. + data.AddMultipleToTail( V_strlen( g_FileCachePath ) + 1, g_FileCachePath ); + + // Second argument is the command line. + char waitAndRestartExe[MAX_PATH], serviceUIExe[MAX_PATH], commandLine[1024 * 8]; + V_ComposeFileName( g_FileCachePath, "WaitAndRestart.exe", waitAndRestartExe, sizeof( waitAndRestartExe ) ); + V_ComposeFileName( g_BaseAppPath, "vmpi_service_ui.exe", serviceUIExe, sizeof( serviceUIExe ) ); // We're running the UI from the same directory this exe is in. + char strSeconds[64]; + V_snprintf( strSeconds, sizeof( strSeconds ), "*%lu", dwInstallerProcessId ); + + // IMPORTANT to use BuildCommandLineFromArgs here because it'll handle slashes and quotes correctly. + // If we don't do that, the command often won't work. + CUtlVector<char*> args; + args.AddToTail( waitAndRestartExe ); + args.AddToTail( strSeconds ); + args.AddToTail( g_BaseAppPath ); + args.AddToTail( serviceUIExe ); + BuildCommandLineFromArgs( args, commandLine, sizeof( commandLine ) ); + data.AddMultipleToTail( V_strlen( commandLine ) + 1, commandLine ); + + if ( g_pConnMgr ) + { + g_pConnMgr->SendPacket( -1, data.Base(), data.Count() ); + Sleep( 1000 ); // Make sure this packet goes out. + } +} + + +// Returns true if the service was just patched and should exit. +bool CheckDownloaderFinished() +{ + if ( !g_Waiting_hProcess ) + return false; + + // Check if the downloader has timed out and kill it if necessary. + if ( Plat_FloatTime() - g_Waiting_StartTime > MAX_DOWNLOADER_TIME_ALLOWED ) + { + TerminateProcess( g_Waiting_hProcess, 1 ); + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + return false; + } + + // Check if it's done. + if ( WaitForSingleObject( g_Waiting_hProcess, 0 ) != WAIT_OBJECT_0 ) + return false; + + CloseHandle( g_Waiting_hProcess ); + g_Waiting_hProcess = NULL; + + // Ok, it's done. Did it finish successfully? + char testFilename[MAX_PATH]; + V_ComposeFileName( g_FileCachePath, "ReadyToGo.txt", testFilename, sizeof( testFilename ) ); + if ( _access( testFilename, 0 ) != 0 ) + return false; + + // Ok, the downloader finished successfully. Run the worker app. + if ( g_bSuperDebugMode ) + AdjustSuperDebugArgs( g_Waiting_Argv ); + + // Figure out the name of the master machine. + V_strncpy( g_CurMasterName, "<unknown>", sizeof( g_CurMasterName ) ); + for ( int iArg=1; iArg < g_Waiting_Argv.Count()-1; iArg++ ) + { + if ( stricmp( g_Waiting_Argv[iArg], "-mpi_MasterName" ) == 0 ) + { + Q_strncpy( g_CurMasterName, g_Waiting_Argv[iArg+1], sizeof( g_CurMasterName ) ); + } + } + + char DLLFilename[MAX_PATH]; + if ( FindArg( __argc, __argv, "-TryDLLMode" ) && + g_RunMode == RUNMODE_CONSOLE && + GetDLLFilename( g_Waiting_Argv, DLLFilename ) && + !g_Waiting_bPatching ) + { + // This is just a helper for debugging. If it's VRAD, we can run it + // in-process as a DLL instead of running it as a separate EXE. + RunInDLL( DLLFilename, g_Waiting_Argv ); + } + else + { + // Run the (hopefully!) MPI app they specified. + RunProcessAtCommandLine( g_Waiting_Argv, g_Waiting_bShowAppWindow, g_Waiting_bPatching, g_Waiting_Priority ); + + if ( g_Waiting_bPatching ) + { + // Tell any currently-running UI apps to patch themselves and quit ASAP so the installer can finish. + SendPatchCommandToUIs( g_dwRunningProcessId ); + + ResumeThread( g_hRunningThread ); // We started the installer suspended so we could make sure we'd send out the patch command. + + // We just ran the installer, but let's forget about it, otherwise we'll kill its process when we exit here. + CloseHandle( g_hRunningProcess ); + CloseHandle( g_hRunningThread ) ; + g_hRunningProcess = g_hRunningThread = NULL; + g_RunningProcess_ExeName[0] = 0; + g_RunningProcess_MapName[0] = 0; + + ServiceHelpers_ExitEarly(); + return true; + } + } + + g_Waiting_Argv.PurgeAndDeleteElements(); + return false; +} + + +void HandlePacket_LOOKING_FOR_WORKERS( bf_read &buf, const CIPAddr &ipFrom ) +{ + // If we're downloading files for a job request, don't process any more "looking for workers" packets. + if ( g_Waiting_hProcess ) + return; + + // This will be a nonzero-length string if patching. + char versionString[512]; + buf.ReadString( versionString, sizeof( versionString ) ); + + int iPort = buf.ReadShort(); + int iPriority = buf.ReadShort(); + + // Make sure we don't run the same job more than once. + if ( !CheckJobID( buf, g_CurJobID ) ) + return; + + CUtlVector<char*> newArgv; + GetArgsFromBuffer( buf, newArgv, &g_Waiting_bShowAppWindow ); + + bool bForcePatch = false; + if ( buf.GetNumBytesLeft() >= 1 ) + bForcePatch = (buf.ReadByte() != 0); + + int iDownloaderPort = iPort; + if ( buf.GetNumBytesLeft() >= 2 ) + iDownloaderPort = buf.ReadShort(); + + // Add these arguments after the executable filename to tell the program + // that it's an MPI worker and who to connect to. + char strDownloaderIP[128], strMainIP[128]; + V_snprintf( strDownloaderIP, sizeof( strDownloaderIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iDownloaderPort ); + V_snprintf( strMainIP, sizeof( strMainIP ), "%d.%d.%d.%d:%d", ipFrom.ip[0], ipFrom.ip[1], ipFrom.ip[2], ipFrom.ip[3], iPort ); + + // (-mpi is already on the command line of whoever ran the app). + // AppendArg( commandLine, sizeof( commandLine ), "-mpi" ); + newArgv.InsertAfter( 0, CopyString( "-mpi_worker" ) ); + newArgv.InsertAfter( 1, CopyString( strDownloaderIP ) ); + + + // If the version string is set, then this is a patch. + bool bPatching = false; + if ( versionString[0] != 0 ) + { + bPatching = true; + + // Check that we haven't applied this patch version yet. This case usually happens right after we've applied a patch + // and we're restarting. The vmpi_transfer master is still pinging us telling us to patch, but we don't want to + // reapply this patch. + if ( atof( versionString ) <= atof( g_VersionString ) && !bForcePatch ) + { + newArgv.PurgeAndDeleteElements(); + return; + } + + // Ok, it's a new version. Get rid of whatever was running before. + KillRunningProcess( "Starting a patch..", true ); + } + + // If there's already a job running, only interrupt it if this new one has a higher priority. + if ( WaitForProcessToExit() ) + { + if ( iPriority > g_CurJobPriority ) + { + KillRunningProcess( "Interrupted by a higher priority process", true ); + } + else + { + // This means we're already running a job with equal to or greater priority than + // the one that has been requested. We're going to ignore this request. + newArgv.PurgeAndDeleteElements(); + return; + } + } + + // Responses go here. + g_CurRespondAddr = ipFrom; + + // Also look for -mpi_ShowAppWindow in the args to the service. + if ( !g_Waiting_bShowAppWindow && FindArg( __argc, __argv, "-mpi_ShowAppWindow" ) ) + g_Waiting_bShowAppWindow = true; + + // Copy all the files from the master and put them in our cache dir to run with. + char cacheDir[MAX_PATH]; + if ( StartDownloadingAppFiles( newArgv, cacheDir, sizeof( cacheDir ), g_Waiting_bShowAppWindow, &g_Waiting_hProcess, bPatching ) ) + { + // After it's downloaded, we want it to switch to the main connection port. + if ( newArgv.Count() >= 3 && V_stricmp( newArgv[2], strDownloaderIP ) == 0 ) + { + delete newArgv[2]; + newArgv[2] = CopyString( strMainIP ); + } + + g_Waiting_StartTime = Plat_FloatTime(); + g_Waiting_Argv.PurgeAndDeleteElements(); + g_Waiting_Argv = newArgv; + g_Waiting_Priority = iPriority; + g_Waiting_bPatching = bPatching; + newArgv.Purge(); + } + else + { + newArgv.PurgeAndDeleteElements(); + } + + // Remember that we tried to run this job so we don't try to run it again. + AddJobMemory( g_CurJobID ); + + SendStateToServicesBrowsers(); +} + + +void HandlePacket_STOP_SERVICE( bf_read &buf, const CIPAddr &ipFrom ) +{ + Msg( "Got a STOP_SERVICE packet. Shutting down...\n" ); + + CWaitTimer timer( 1 ); + while ( 1 ) + { + AddServicesBrowserIP( ipFrom ); + SendStateToServicesBrowsers(); + + if ( timer.ShouldKeepWaiting() ) + Sleep( 200 ); + else + break; + } + + StopUI(); + ServiceHelpers_ExitEarly(); +} + + +void HandlePacket_KILL_PROCESS( const CIPAddr *ipFrom ) +{ + if ( Plat_FloatTime() - g_flLastKillProcessTime > 5 ) + { + KillRunningProcess( "Got a KILL_PROCESS packet. Stopping the worker executable.\n", true ); + + if ( ipFrom ) + { + AddServicesBrowserIP( *ipFrom ); + SendStateToServicesBrowsers(); + } + + g_flLastKillProcessTime = Plat_FloatTime(); + } +} + + +void HandlePacket_FORCE_PASSWORD_CHANGE( bf_read &buf, const CIPAddr &ipFrom ) +{ + char newPassword[512]; + buf.ReadString( newPassword, sizeof( newPassword ) ); + + Msg( "Got a FORCE_PASSWORD_CHANGE (%s) packet.\n", newPassword ); + + SetPassword( newPassword ); + if ( g_pConnMgr ) + g_pConnMgr->SendCurStateTo( -1 ); +} + + +void VMPI_Waiter_Update() +{ + CheckScreensaverRunning(); + HandleWindowMessages(); + UpdateServicesBrowserIPs(); + + while ( 1 ) + { + WaitForProcessToExit(); + if ( CheckDownloaderFinished() ) + return; + + // Recv off the socket first so it clears the queue while we're waiting for the process to exit. + char data[4096]; + CIPAddr ipFrom; + int len = g_pSocket->RecvFrom( data, sizeof( data ), &ipFrom ); + + // Any incoming packets? + if ( len <= 0 ) + break; + + bf_read buf( data, len ); + if ( buf.ReadByte() != VMPI_PROTOCOL_VERSION ) + continue; + + // Only handle packets with the right password. + char pwString[256]; + buf.ReadString( pwString, sizeof( pwString ) ); + + int packetID = buf.ReadByte(); + + if ( pwString[0] == VMPI_PASSWORD_OVERRIDE ) + { + // Always process these packets regardless of the password (these usually come from + // the installer when it is trying to stop a previously-running instance). + } + else if ( packetID == VMPI_LOOKING_FOR_WORKERS ) + { + if ( pwString[0] == 0 ) + { + if ( g_pPassword && g_pPassword[0] != 0 ) + continue; + } + else + { + if ( !g_pPassword || stricmp( g_pPassword, pwString ) != 0 ) + continue; + } + } + + // VMPI_KILL_PROCESS is checked before everything. + if ( packetID == VMPI_KILL_PROCESS ) + { + HandlePacket_KILL_PROCESS( &ipFrom ); + } + else if ( packetID == VMPI_PING_REQUEST ) + { + AddServicesBrowserIP( ipFrom ); + SendStateToServicesBrowsers(); + } + else if ( packetID == VMPI_STOP_SERVICE ) + { + HandlePacket_STOP_SERVICE( buf, ipFrom ); + return; + } + else if ( packetID == VMPI_SERVICE_PATCH ) + { + // The key to doing this here is that we ignore whether we're disabled or in screensaver mode.. we always handle + // the patch command (unless we've already handled this job ID OR if we've already applied this patch version). + HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom ); + } + else if ( packetID == VMPI_FORCE_PASSWORD_CHANGE ) + { + HandlePacket_FORCE_PASSWORD_CHANGE( buf, ipFrom ); + } + + // If they've told us not to wait for jobs, then ignore the packet. + if ( g_iCurState == VMPI_SERVICE_STATE_DISABLED || (g_bScreensaverMode && !g_bScreensaverRunning) ) + continue; + + if ( packetID == VMPI_LOOKING_FOR_WORKERS ) + { + HandlePacket_LOOKING_FOR_WORKERS( buf, ipFrom ); + } + } +} + + +// ------------------------------------------------------------------------------------------------ // +// Startup and service code. +// ------------------------------------------------------------------------------------------------ // + +void RunMainLoop() +{ + // This is the service's main loop. + while ( 1 ) + { + // If the service has been told to exit, then just exit. + if ( ServiceHelpers_ShouldExit() ) + break; + + VMPI_Waiter_Update(); + g_pConnMgr->Update(); + + Sleep( 50 ); + } +} + + +void InternalRunService() +{ + if ( !VMPI_Waiter_Init() ) + return; + + RunMainLoop(); + VMPI_Waiter_Term(); +} + + +// This function runs us as a console app. Useful for debugging or if you want to run more +// than one instance of VRAD on the same machine. +void RunAsNonServiceApp() +{ + InternalRunService(); +} + + +// This function runs inside the service thread. +void ServiceThreadFn( void *pParam ) +{ + InternalRunService(); +} + + +// This function works with the service manager and runs as a system service. +void RunService() +{ + if( !ServiceHelpers_StartService( VMPI_SERVICE_NAME_INTERNAL, ServiceThreadFn, NULL ) ) + { + Msg( "Service manager not started. Running as console app.\n" ); + g_RunMode = RUNMODE_CONSOLE; + InternalRunService(); + } +} + +int APIENTRY WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nCmdShow) +{ + // Hook spew output. + SpewOutputFunc( MySpewOutputFunc ); + + // Get access to the registry.. + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &g_hVMPIServiceKey ); + + // Setup our version string. + LoadString( hInstance, VMPI_SERVICE_IDS_VERSION_STRING, g_VersionString, sizeof( g_VersionString ) ); + + // Setup the base app path. + if ( !GetModuleFileName( GetModuleHandle( NULL ), g_BaseAppPath, sizeof( g_BaseAppPath ) ) ) + { + Warning( "GetModuleFileName failed.\n" ); + return false; + } + V_StripLastDir( g_BaseAppPath, sizeof( g_BaseAppPath ) ); + + // Setup the cache path. + V_ComposeFileName( g_BaseAppPath, "vmpi_service_cache", g_FileCachePath, sizeof( g_FileCachePath ) ); + + + const char *pArg = FindArg( __argc, __argv, "-mpi_pw", NULL ); + SetPassword( pArg ); + + if ( FindArg( __argc, __argv, "-console" ) ) + { + g_RunMode = RUNMODE_CONSOLE; + } + else + { + g_RunMode = RUNMODE_SERVICE; + } + + if ( FindArg( __argc, __argv, "-superdebug" ) ) + g_bSuperDebugMode = true; + + g_AppStartTime = GetTickCount(); + g_bMinimized = FindArg( __argc, __argv, "-minimized" ) != NULL; + + ServiceHelpers_Init(); + g_hInstance = hInstance; + + LoadStateFromRegistry(); + + // Install the service? + if ( g_RunMode == RUNMODE_CONSOLE ) + { + RunAsNonServiceApp(); + } + else + { + RunService(); + } + + return 0; +} + diff --git a/utils/vmpi/vmpi_service/vmpi_service.h b/utils/vmpi/vmpi_service/vmpi_service.h new file mode 100644 index 0000000..17c8178 --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined(AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_) +#define AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "resource.h" + + +#endif // !defined(AFX_VMPI_SERVICE_H__0EE084DB_9164_4DC2_9E95_CF25D32AAA7B__INCLUDED_) diff --git a/utils/vmpi/vmpi_service/vmpi_service.rc b/utils/vmpi/vmpi_service/vmpi_service.rc new file mode 100644 index 0000000..bb7debf --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.rc @@ -0,0 +1,74 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_VERSION_STRING "3.3" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_service/vmpi_service.vpc b/utils/vmpi/vmpi_service/vmpi_service.vpc new file mode 100644 index 0000000..7c5d4ba --- /dev/null +++ b/utils/vmpi/vmpi_service/vmpi_service.vpc @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// VMPI_SERVICE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\..\common,..\" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE pdh.lib ws2_32.lib odbc32.lib odbccp32.lib" + } +} + +$Project "Vmpi_service" +{ + $Folder "Source Files" + { + $File "..\iphelpers.cpp" + $File "service_conn_mgr.cpp" + $File "service_helpers.cpp" + $File "perf_counters.cpp" + $File "vmpi_service.rc" + $File "StdAfx.cpp" + $File "..\tcpsocket_helpers.cpp" + $File "..\ThreadedTCPSocket.cpp" + $File "..\ThreadedTCPSocketEmu.cpp" + $File "..\threadhelpers.cpp" + $File "vmpi_service.cpp" + } + + $Folder "Header Files" + { + $File "service_conn_mgr.h" + $File "service_helpers.h" + $File "perf_counters.h" + $File "StdAfx.h" + $File "resource.h" + $File "vmpi_service.h" + } + + $Folder "Resource Files" + { + $File "..\vmpi_service_ui\idi_busy_icon.ico" + $File "..\vmpi_service_ui\idi_disabled_icon.ico" + $File "..\vmpi_service_ui\idi_waiting_icon.ico" + $File "..\vmpi_service_ui\vmpi_service.ico" + } +} diff --git a/utils/vmpi/vmpi_service_install/ServiceInstallDlg.cpp b/utils/vmpi/vmpi_service_install/ServiceInstallDlg.cpp new file mode 100644 index 0000000..6a22176 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/ServiceInstallDlg.cpp @@ -0,0 +1,1043 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// JobWatchDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ServiceInstallDlg.h" +#include "tier1/strtools.h" + + +#define DEFAULT_INSTALL_LOCATION "C:\\Program Files\\Valve\\vmpi_service" + +#define HLKM_WINDOWS_RUN_KEY "Software\\Microsoft\\Windows\\CurrentVersion\\Run" +#define VMPI_SERVICE_VALUE_NAME "VMPI Service" +#define VMPI_SERVICE_UI_VALUE_NAME "VMPI Service UI" + +// These are the files required for installation. +char *g_pInstallFiles[] = +{ + "vmpi_service.exe", + "vmpi_service_ui.exe", + "WaitAndRestart.exe", + "vmpi_service_install.exe", + "tier0.dll", + "vmpi_transfer.exe", + "filesystem_stdio.dll", + "vstdlib.dll" +}; + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + + +HWND g_hMessageControl = NULL; +HKEY g_hVMPIKey = NULL; // hklm/software/valve/vmpi. +bool g_bNoOutput = false; +bool g_bDontTouchUI = false; +bool g_bReinstalling = false; + +FILE *g_fpLog = NULL; + + +char* FindArg( int argc, char **argv, const char *pArgName, char *pDefaultValue="" ) +{ + for ( int i=0; i < argc; i++ ) + { + if ( stricmp( argv[i], pArgName ) == 0 ) + { + if ( (i+1) >= argc ) + return pDefaultValue; + else + return argv[i+1]; + } + } + return NULL; +} + +void CloseLog() +{ + if ( g_fpLog ) + { + fflush( g_fpLog ); + fclose( g_fpLog ); + flushall(); + g_fpLog = NULL; + } +} + +void OpenLog() +{ + CloseLog(); + g_fpLog = fopen( "c:\\vmpi_service_install.log", "wt" ); +} + + +void AddToLog( const char *pMsg ) +{ + if ( g_fpLog ) + { + fprintf( g_fpLog, "%s", pMsg ); + } +} + + +SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const tchar *pMsg ) +{ + AddToLog( pMsg ); + + if ( spewType == SPEW_MESSAGE || spewType == SPEW_WARNING ) + { + // Format the message and send it to the control. + CUtlVector<char> msg; + msg.SetSize( V_strlen( pMsg )*2 + 1 ); + + char *pOut = msg.Base(); + const char *pIn = pMsg; + while ( *pIn ) + { + if ( *pIn == '\n' ) + { + *pOut++ = '\r'; + *pOut++ = '\n'; + } + else + { + *pOut++ = *pIn; + } + ++pIn; + } + *pOut = 0; + + int nLen = (int)SendMessage( g_hMessageControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( g_hMessageControl, EM_SETSEL, nLen, nLen ); + SendMessage( g_hMessageControl, EM_REPLACESEL, FALSE, (LPARAM)msg.Base() ); + } + + // Show a message box for warnings and errors. + if ( spewType == SPEW_ERROR || spewType == SPEW_WARNING ) + { + if ( !g_bNoOutput ) + AfxMessageBox( pMsg, MB_OK ); + } + + if ( spewType == SPEW_ERROR ) + { + CloseLog(); + TerminateProcess( GetCurrentProcess(), 2 ); + } + + return SPEW_CONTINUE; +} + + +void ScanDirectory( const char *pDirName, CUtlVector<CString> &subDirs, CUtlVector<CString> &files ) +{ + subDirs.Purge(); + files.Purge(); + + char strPattern[MAX_PATH]; + V_ComposeFileName( pDirName, "*.*", strPattern, sizeof( strPattern ) ); + + WIN32_FIND_DATA fileInfo; // File information + HANDLE hFile = ::FindFirstFile( strPattern, &fileInfo ); + if ( hFile == INVALID_HANDLE_VALUE ) + return; + + do + { + if ( fileInfo.cFileName[0] == '.' ) + continue; + + if ( fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + subDirs.AddToTail( fileInfo.cFileName ); + else + files.AddToTail( fileInfo.cFileName ); + } while( ::FindNextFile(hFile, &fileInfo) ); + + ::FindClose( hFile ); +} + + +int DeleteDirectory( const char *pRootDir, bool bDeleteSubdirectories, char errorFile[MAX_PATH] ) +{ + errorFile[0] = 0; + + CUtlVector<CString> subDirs, files; + ScanDirectory( pRootDir, subDirs, files ); + + // First nuke any subdirectories. + if ( bDeleteSubdirectories && !g_bReinstalling ) + { + for ( int i=0; i < subDirs.Count(); i++ ) + { + char fullName[MAX_PATH]; + V_ComposeFileName( pRootDir, subDirs[i], fullName, sizeof( fullName ) ); + + // Delete subdirectory + int iRC = DeleteDirectory( fullName, bDeleteSubdirectories, errorFile ); + if ( iRC ) + return iRC; + } + } + + for ( int i=0; i < files.Count(); i++ ) + { + char fullName[MAX_PATH]; + V_ComposeFileName( pRootDir, files[i], fullName, sizeof( fullName ) ); + + // Set file attributes + if ( !SetFileAttributes( fullName, FILE_ATTRIBUTE_NORMAL ) ) + return GetLastError(); + + // Delete file + if ( !DeleteFile( fullName ) ) + { + V_strncpy( errorFile, fullName, MAX_PATH ); + return GetLastError(); + } + } + + if( !g_bReinstalling ) + { + // Set directory attributes + if ( !SetFileAttributes( pRootDir, FILE_ATTRIBUTE_NORMAL ) ) + return GetLastError(); + + // Delete directory + if ( !RemoveDirectory( pRootDir ) ) + return GetLastError(); + } + + return 0; +} + + +bool CreateDirectory_R( const char *pDirName ) +{ + char chPrevDir[MAX_PATH]; + V_strncpy( chPrevDir, pDirName, sizeof( chPrevDir ) ); + if ( V_StripLastDir( chPrevDir, sizeof( chPrevDir ) ) ) + { + if ( V_stricmp( chPrevDir, ".\\" ) != 0 && V_stricmp( chPrevDir, "./" ) != 0 ) + if ( !CreateDirectory_R( chPrevDir ) ) + return false; + } + + if ( _access( pDirName, 0 ) == 0 ) + return true; + + return CreateDirectory( pDirName, NULL ) || GetLastError() == ERROR_ALREADY_EXISTS; +} + + +bool SetupStartMenuSubFolderName( const char *pSubFolderName, char *pOut, int outLen ) +{ + LPITEMIDLIST pidl; + + // Get a pointer to an item ID list that represents the path of a special folder + HRESULT hr = SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_PROGRAMS, &pidl); + if ( hr != S_OK ) + return false; + + // Convert the item ID list's binary representation into a file system path + char szPath[_MAX_PATH]; + BOOL f = SHGetPathFromIDList(pidl, szPath); + + // Free the LPITEMIDLIST they gave us. + LPMALLOC pMalloc; + hr = SHGetMalloc(&pMalloc); + pMalloc->Free(pidl); + pMalloc->Release(); + + if ( f ) + { + V_ComposeFileName( szPath, pSubFolderName, pOut, outLen ); + return true; + } + else + { + return false; + } +} + +bool CreateStartMenuLink( const char *pSubFolderName, const char *pLinkName, const char *pLinkTarget, const char *pArguments ) +{ + char fullFolderName[MAX_PATH]; + if ( !SetupStartMenuSubFolderName( pSubFolderName, fullFolderName, sizeof( fullFolderName ) ) ) + return false; + + // Create the folder if necessary. + if ( !CreateDirectory_R( fullFolderName ) ) + { + Msg( "CreateStartMenuLink failed - can't create directory %s.\n", fullFolderName ); + return false; + } + + IShellLink* psl = NULL; + + // Get a pointer to the IShellLink interface. + bool bRet = false; + CoInitialize( NULL ); + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<void**>(&psl)); + if (SUCCEEDED(hres)) + { + psl->SetPath( pLinkTarget ); // Set the path to the shortcut target + if ( pArguments ) + psl->SetArguments( pArguments ); + + // Query IShellLink for the IPersistFile interface for saving + //the shortcut in persistent storage. + IPersistFile* ppf = NULL; + hres = psl->QueryInterface( IID_IPersistFile, reinterpret_cast<void**>(&ppf) ); + if (SUCCEEDED(hres)) + { + // Setup the filename for the link. + char linkFilename[MAX_PATH]; + V_ComposeFileName( fullFolderName, pLinkName, linkFilename, sizeof( linkFilename ) ); + V_strncat( linkFilename, ".lnk", sizeof( linkFilename ) ); + + // Ensure that the string is ANSI. + WCHAR wsz[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, linkFilename, -1, wsz, MAX_PATH); + + // Save the link by calling IPersistFile::Save. + hres = ppf->Save( wsz, TRUE ); + ppf->Release(); + bRet = true; + } + + psl->Release(); + } + CoUninitialize(); + return bRet; +} + + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + +bool LaunchApp( char *pCommandLine, const char *pBaseDir ) +{ + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + return CreateProcess( NULL, pCommandLine, NULL, NULL, FALSE, 0, NULL, pBaseDir, &si, &pi ) != 0; +} + + +bool StartVMPIServiceUI( const char *pInstallLocation ) +{ + if ( g_bDontTouchUI ) + { + Msg( "StartVMPIServiceUI: Ignoring due to -DontTouchUI.\n" ); + return true; + } + + char cmdLine[MAX_PATH]; + V_ComposeFileName( pInstallLocation, "vmpi_service_ui.exe", cmdLine, sizeof( cmdLine ) ); + return LaunchApp( cmdLine, pInstallLocation ); +} + + +bool StartVMPIService( SC_HANDLE hSCManager ) +{ + bool bRet = true; + + // First, get rid of an old service with the same name. + SC_HANDLE hMyService = OpenService( hSCManager, VMPI_SERVICE_NAME_INTERNAL, SERVICE_START ); + if ( hMyService ) + { + if ( StartService( hMyService, NULL, NULL ) ) + { + Msg( "Started!\n" ); + } + else + { + Error( "Can't start the service.\n" ); + bRet = false; + } + } + else + { + Error( "Can't open service: %s\n", VMPI_SERVICE_NAME_INTERNAL ); + bRet = false; + } + + CloseServiceHandle( hMyService ); + return bRet; +} + + +bool StopRunningApp() +{ + if ( g_bDontTouchUI ) + { + Msg( "StopRunningApp: -DontTouchUI was specified, so exiting before stopping the app.\n" ); + return true; + } + + // Send the + ISocket *pSocket = CreateIPSocket(); + if ( pSocket ) + { + if ( pSocket->BindToAny( 0 ) ) + { + CUtlVector<char> protocolVersions; + protocolVersions.AddToTail( VMPI_PROTOCOL_VERSION ); + if ( VMPI_PROTOCOL_VERSION == 5 ) + protocolVersions.AddToTail( 4 ); // We want this installer to kill the previous services too. + + for ( int iProtocolVersion=0; iProtocolVersion < protocolVersions.Count(); iProtocolVersion++ ) + { + char cPacket[4] = + { + protocolVersions[iProtocolVersion], + VMPI_PASSWORD_OVERRIDE, // (force it to accept this message). + 0, + VMPI_STOP_SERVICE + }; + + CIPAddr addr( 127, 0, 0, 1, 0 ); + + for ( int iPort=VMPI_SERVICE_PORT; iPort <= VMPI_LAST_SERVICE_PORT; iPort++ ) + { + addr.port = iPort; + pSocket->SendTo( &addr, cPacket, sizeof( cPacket ) ); + } + } + + // Give it a sec to get the message and shutdown in case we're restarting. + Sleep( 2000 ); + + + // This is the overkill method. If it didn't shutdown gracefully, kill it. + HMODULE hInst = LoadLibrary( "psapi.dll" ); + if ( hInst ) + { + typedef BOOL (WINAPI *EnumProcessesFn)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded); + typedef BOOL (WINAPI *EnumProcessModulesFn)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); + typedef DWORD (WINAPI *GetModuleBaseNameFn)( HANDLE hProcess, HMODULE hModule, LPTSTR lpBaseName, DWORD nSize ); + + EnumProcessesFn EnumProcesses = (EnumProcessesFn)GetProcAddress( hInst, "EnumProcesses" ); + EnumProcessModulesFn EnumProcessModules = (EnumProcessModulesFn)GetProcAddress( hInst, "EnumProcessModules" ); + GetModuleBaseNameFn GetModuleBaseName = (GetModuleBaseNameFn)GetProcAddress( hInst, "GetModuleBaseNameA" ); + if ( EnumProcessModules && EnumProcesses ) + { + // Now just to make sure, kill the processes we're interested in. + DWORD procIDs[1024]; + DWORD nBytes; + if ( EnumProcesses( procIDs, sizeof( procIDs ), &nBytes ) ) + { + DWORD nProcs = nBytes / sizeof( procIDs[0] ); + for ( DWORD i=0; i < nProcs; i++ ) + { + HANDLE hProc = OpenProcess( PROCESS_ALL_ACCESS, FALSE, procIDs[i] ); + if ( hProc ) + { + HMODULE hModules[1024]; + if ( EnumProcessModules( hProc, hModules, sizeof( hModules ), &nBytes ) ) + { + DWORD nModules = nBytes / sizeof( hModules[0] ); + for ( DWORD iModule=0; iModule < nModules; iModule++ ) + { + char filename[512]; + if ( GetModuleBaseName( hProc, hModules[iModule], filename, sizeof( filename ) ) ) + { + if ( Q_stristr( filename, "vmpi_service.exe" ) || Q_stristr( filename, "vmpi_service_ui.exe" ) ) + { + TerminateProcess( hProc, 1 ); + CloseHandle( hProc ); + hProc = NULL; + break; + } + } + } + } + + CloseHandle( hProc ); + } + } + } + } + + FreeLibrary( hInst ); + } + } + + pSocket->Release(); + } + + return true; +} + + +bool StopOrDeleteService( SC_HANDLE hSCManager, bool bDelete ) +{ + bool bRet = true; + + // First, get rid of an old service with the same name. + SC_HANDLE hOldService = OpenService( hSCManager, VMPI_SERVICE_NAME_INTERNAL, SERVICE_STOP | DELETE ); + if ( hOldService ) + { + // Stop the service. + Msg( "Found the service already running.\n" ); + Msg( "Stopping service...\n" ); + SERVICE_STATUS status; + ControlService( hOldService, SERVICE_CONTROL_STOP, &status ); + + if ( bDelete ) + { + Msg( "Deleting service...\n" ); + bool bExitedNicely = false; + DWORD startTime = GetTickCount(); + while ( 1 ) + { + BOOL bRet = DeleteService( hOldService ); + if ( !bRet || bRet == ERROR_SERVICE_MARKED_FOR_DELETE ) + { + Msg( "Deleted old service.\n" ); + bExitedNicely = true; + break; + } + + // Wait for the service to stop for 8 seconds. + if ( GetTickCount() - startTime > 8000 ) + break; + } + + if ( !bExitedNicely ) + { + Error( "Couldn't delete the old '%s' service! Error: %s.\n", VMPI_SERVICE_NAME, GetLastErrorString() ); + bRet = false; + } + } + + CloseServiceHandle( hOldService ); + } + + return bRet; +} + + +bool GetExistingInstallationLocation( CString &strInstallLocation ) +{ + char buf[1024]; + DWORD bufSize = sizeof( buf ); + DWORD dwType; + if ( RegQueryValueEx( g_hVMPIKey, SERVICE_INSTALL_LOCATION_KEY, NULL, &dwType, (LPBYTE)buf, &bufSize ) == ERROR_SUCCESS ) + { + if ( dwType == REG_SZ ) + { + strInstallLocation = buf; + return true; + } + } + + return false; +} + +void RemoveRegistryKeys() +{ + // Delete the run values (that tells it to run the app when the user logs in). + HKEY hKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, HLKM_WINDOWS_RUN_KEY, &hKey ); + RegDeleteValue( hKey, VMPI_SERVICE_VALUE_NAME ); + RegDeleteValue( hKey, VMPI_SERVICE_UI_VALUE_NAME ); + + // Get rid of the "InstallLocation" value. + RegDeleteValue( g_hVMPIKey, SERVICE_INSTALL_LOCATION_KEY ); +} + + +bool IsAnInstallFile( const char *pName ) +{ + for ( int i=0; i < ARRAYSIZE( g_pInstallFiles ); i++ ) + { + if ( V_stricmp( g_pInstallFiles[i], pName ) == 0 ) + return true; + } + return false; +} + + +bool AnyNonInstallFilesInDirectory( const char *strInstallLocation ) +{ + char searchStr[MAX_PATH]; + V_ComposeFileName( strInstallLocation, "*.*", searchStr, sizeof( searchStr ) ); + + _finddata_t data; + long handle = _findfirst( searchStr, &data ); + if ( handle != -1 ) + { + do + { + if ( data.name[0] == '.' || (data.attrib & _A_SUBDIR) != 0 ) + continue; + + if ( !IsAnInstallFile( data.name ) ) + return true; + + } while( _findnext( handle, &data ) == 0 ); + + _findclose( handle ); + } + return false; +} + + +///////////////////////////////////////////////////////////////////////////// +// CServiceInstallDlg dialog + + +CServiceInstallDlg::CServiceInstallDlg(CWnd* pParent /*=NULL*/) + : CDialog(CServiceInstallDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CServiceInstallDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +CServiceInstallDlg::~CServiceInstallDlg() +{ +} + + +void CServiceInstallDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CServiceInstallDlg) + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CServiceInstallDlg, CDialog) + //{{AFX_MSG_MAP(CServiceInstallDlg) + ON_BN_CLICKED(IDC_CANCEL_BUTTON, OnCancel) + ON_BN_CLICKED(IDC_INSTALL_BUTTON, OnInstall) + ON_BN_CLICKED(IDC_UNINSTALL_BUTTON2, OnUninstall) + ON_BN_CLICKED(IDC_START_EXISTING_BUTTON, OnStartExisting) + ON_BN_CLICKED(IDC_STOP_EXISTING_BUTTON, OnStopExisting) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CServiceInstallDlg message handlers + +const char* FindArg( const char *pArgName, const char *pDefault="" ) +{ + for ( int i=1; i < __argc; i++ ) + { + if ( Q_stricmp( pArgName, __argv[i] ) == 0 ) + { + if ( (i+1) < __argc ) + return __argv[i+1]; + else + return pDefault; + } + } + return NULL; +} + + +BOOL CServiceInstallDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + HICON hIcon = LoadIcon( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_MAINFRAME ) ); + SetIcon( hIcon, true ); + + OpenLog(); + + // Setup the registry key for the install location. + if ( RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &g_hVMPIKey ) != ERROR_SUCCESS ) + { + Error( "Can't open registry key: %s.", VMPI_SERVICE_KEY ); + return FALSE; + } + + VerifyInstallFiles(); + + g_hMessageControl = ::GetDlgItem( GetSafeHwnd(), IDC_TEXTOUTPUT ); + SpewOutputFunc( MySpewOutputFunc ); + + // Init the service manager. + m_hSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); + if ( !m_hSCManager ) + { + Error( "OpenSCManager failed (%s)!\n", GetLastErrorString() ); + return FALSE; + } + + + // See if there is a previous installation. + CString strInstallLocation; + if ( GetExistingInstallationLocation( strInstallLocation ) ) + SetDlgItemText( IDC_INSTALL_LOCATION, strInstallLocation ); + else + SetDlgItemText( IDC_INSTALL_LOCATION, DEFAULT_INSTALL_LOCATION ); + + // Now, if they passed in a command line option . + if ( FindArg( __argc, __argv, "-install_quiet" ) ) + { + g_bReinstalling = true; + g_bNoOutput = true; + g_bDontTouchUI = (FindArg( __argc, __argv, "-DontTouchUI", NULL ) != 0); + OnInstall(); + EndDialog( 0 ); + } + else if ( FindArg( __argc, __argv, "-uninstall_quiet" ) ) + { + g_bNoOutput = true; + DoUninstall( false ); + EndDialog( 0 ); + } + else if ( FindArg( __argc, __argv, "-start" ) ) + { + OnStartExisting(); + } + + else if ( FindArg( __argc, __argv, "-stop" ) ) + { + OnStopExisting(); + } + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +void CServiceInstallDlg::OnCancel(void) +{ + EndDialog( 1 ); +} + +// This function registers the service with the service manager. +bool InstallService( SC_HANDLE hSCManager, const char *pBaseDir ) +{ + char filename[512], uiFilename[512]; + V_ComposeFileName( pBaseDir, "vmpi_service.exe", filename, sizeof( filename ) ); + V_ComposeFileName( pBaseDir, "vmpi_service_ui.exe", uiFilename, sizeof( uiFilename ) ); + + + // Try a to reinstall the service for up to 5 seconds. + Msg( "Creating new service...\n" ); + + SC_HANDLE hMyService = NULL; + DWORD startTime = GetTickCount(); + while ( GetTickCount() - startTime < 5000 ) + { + // Now reinstall it. + hMyService = CreateService( + hSCManager, // Service Control Manager database. + VMPI_SERVICE_NAME_INTERNAL, // Service name. + VMPI_SERVICE_NAME, // Display name. + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, // Start automatically on system bootup. + SERVICE_ERROR_NORMAL, + filename, // Executable to register for the service. + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // account + NULL // password + ); + + if ( hMyService ) + break; + else + Sleep( 300 ); + } + + if ( !hMyService ) + { + Warning( "CreateService failed (%s)!\n", GetLastErrorString() ); + CloseServiceHandle( hSCManager ); + return false; + } + + + // Now setup the UI executable to run when their system starts. + HKEY hUIKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, HLKM_WINDOWS_RUN_KEY, &hUIKey ); + if ( !hUIKey || RegSetValueEx( hUIKey, VMPI_SERVICE_UI_VALUE_NAME, 0, REG_SZ, (unsigned char*)uiFilename, strlen( uiFilename) + 1 ) != ERROR_SUCCESS ) + { + Warning( "Can't install registry key for %s\n", uiFilename ); + return false; + } + + CloseServiceHandle( hMyService ); + return true; +} + + +void SetupStartMenuLinks( const char *pInstallerFilename ) +{ + CreateStartMenuLink( "Valve\\VMPI", "Start VMPI Service", pInstallerFilename, "-start" ); + CreateStartMenuLink( "Valve\\VMPI", "Stop VMPI Service", pInstallerFilename, "-stop" ); + CreateStartMenuLink( "Valve\\VMPI", "Uninstall VMPI", pInstallerFilename, NULL ); +} + + +void RemoveStartMenuLinks() +{ + char fullFolderName[MAX_PATH]; + if ( !SetupStartMenuSubFolderName( "Valve\\VMPI", fullFolderName, sizeof( fullFolderName ) ) ) + return; + + char errorFile[MAX_PATH]; + if ( !DeleteDirectory( fullFolderName, true, errorFile ) ) + { + Msg( "Unable to remove Start Menu items in %s.\n", fullFolderName ); + } +} + + +void CServiceInstallDlg::OnInstall() +{ + // Get the install location. + Msg( "Verifying install location.\n" ); + CString strInstallLocation; + if ( !GetDlgItemText( IDC_INSTALL_LOCATION, strInstallLocation ) ) + { + Error( "Can't get install location." ); + return; + } + + if ( strchr( strInstallLocation, ':' ) == NULL ) + { + Warning( "Install location must be an absolute path (include a colon)." ); + return; + } + + // Stop the existing service. + if ( !DoUninstall( false ) ) + return; + + // Create the directory. + Msg( "Creating install directory %s.\n", strInstallLocation ); + if ( !CreateDirectory_R( strInstallLocation ) ) + { + Warning( "Unable to create directory: %s.", (const char*)strInstallLocation ); + return; + } + + // Copy the files down. + Msg( "Copying files.\n" ); + char chDir[MAX_PATH]; + GetModuleFileName( NULL, chDir, sizeof( chDir ) ); + V_StripFilename( chDir ); + for ( int i=0; i < ARRAYSIZE( g_pInstallFiles ); i++ ) + { + char srcFilename[MAX_PATH], destFilename[MAX_PATH]; + V_ComposeFileName( chDir, g_pInstallFiles[i], srcFilename, sizeof( srcFilename ) ); + V_ComposeFileName( strInstallLocation, g_pInstallFiles[i], destFilename, sizeof( destFilename ) ); + + if ( !CopyFile( srcFilename, destFilename, FALSE ) ) + { + Sleep( 2000 ); + + if ( !CopyFile( srcFilename, destFilename, FALSE ) ) + { + Error( "CopyFile() failed.\nSrc: %s\nDest: %s\n%s", srcFilename, destFilename, GetLastErrorString() ); + return; + } + } + } + + // Register the service. + if ( !InstallService( m_hSCManager, strInstallLocation ) ) + return; + + // Write the new location to the registry. + Msg( "Updating registry.\n" ); + if ( RegSetValueEx( g_hVMPIKey, SERVICE_INSTALL_LOCATION_KEY, 0, REG_SZ, (BYTE*)(const char*)strInstallLocation, V_strlen( strInstallLocation ) + 1 ) != ERROR_SUCCESS ) + { + Error( "RegSetValueEx( %s, %s ) failed.", SERVICE_INSTALL_LOCATION_KEY, (const char*)strInstallLocation ); + return; + } + + // Setup start menu links. + char installerFilename[MAX_PATH]; + V_ComposeFileName( strInstallLocation, "vmpi_service_install.exe", installerFilename, sizeof( installerFilename ) ); + SetupStartMenuLinks( installerFilename ); + + // Start the new service. + Msg( "Starting new service.\n" ); + if ( DoStartExisting() ) + { + Warning( "Installed successfully!" ); + } +} + +bool CServiceInstallDlg::DoUninstall( bool bShowMessage ) +{ + // Figure out where to uninstall from. + CString strInstallLocation; + if ( !GetDlgItemText( IDC_INSTALL_LOCATION, strInstallLocation ) ) + { + Error( "Can't get install location." ); + return false; + } + + if ( _access( strInstallLocation, 0 ) == 0 && !g_bNoOutput ) + { + // Don't ask if they care if we delete all the files in that directory if the only exes in there are the install exes. + if ( AnyNonInstallFilesInDirectory( strInstallLocation ) ) + { + char str[512]; + V_snprintf( str, sizeof( str ), "Warning: this will delete all files under this directory: \n%s\nContinue?", strInstallLocation ); + if ( AfxMessageBox( str, MB_YESNO ) != IDYES ) + return false; + } + } + + // Stop both the service and the win app. + bool bDone = StopRunningApp() && StopOrDeleteService( m_hSCManager, true ); + if ( !bDone ) + return false; + + bool bSuccess = true; + RemoveRegistryKeys(); + char errorFile[MAX_PATH]; + if ( !NukeDirectory( strInstallLocation, errorFile ) ) + { + // When reinstalling, the service may not be done exiting, so give it a sec. + Sleep( 2000 ); + if ( !NukeDirectory( strInstallLocation, errorFile ) ) + { + if ( errorFile[0] ) + Msg( "NukeDirectory( %s ) failed.\nError on file: %s\n", strInstallLocation, errorFile ); + else + Msg( "NukeDirectory( %s ) failed.\n", strInstallLocation ); + + Msg( "Uninstall complete, but files are left over in %s.\n", strInstallLocation ); + + bSuccess = false; + } + } + + RemoveStartMenuLinks(); + + if ( bShowMessage && bSuccess ) + AfxMessageBox( "Uninstall successful." ); + + return true; +} + +void CServiceInstallDlg::OnUninstall() +{ + DoUninstall( true ); +} + +void CServiceInstallDlg::OnStartExisting() +{ + if ( DoStartExisting() ) + AfxMessageBox( "Started successfully." ); +} + +bool CServiceInstallDlg::DoStartExisting() +{ + StopRunningApp(); + StopOrDeleteService( m_hSCManager, false ); + + CString strInstallLocation; + if ( !GetExistingInstallationLocation( strInstallLocation ) ) + { + Error( "The VMPI service is not installed." ); + return false; + } + + if ( StartVMPIService( m_hSCManager ) ) + { + return StartVMPIServiceUI( strInstallLocation ); + } + else + { + return false; + } +} + +void CServiceInstallDlg::OnStopExisting() +{ + + // Stop the app but don't delete it. + bool bDone = StopRunningApp() && StopOrDeleteService( m_hSCManager, false ); + if ( bDone ) + { + AfxMessageBox( "Service successfully stopped." ); + } +} + +bool CServiceInstallDlg::NukeDirectory( const char *pDir, char errorFile[MAX_PATH] ) +{ + // If the directory doesn't exist anyways, then return true.. + if ( _access( pDir, 0 ) != 0 ) + return true; + + return DeleteDirectory( pDir, true, errorFile ) == 0; +} + + +void CServiceInstallDlg::VerifyInstallFiles() +{ + char chDir[MAX_PATH]; + GetModuleFileName( NULL, chDir, sizeof( chDir ) ); + V_StripFilename( chDir ); + + for ( int i=0; i < ARRAYSIZE( g_pInstallFiles ); i++ ) + { + char filename[MAX_PATH]; + V_ComposeFileName( chDir, g_pInstallFiles[i], filename, sizeof( filename ) ); + + if ( _access( filename, 0 ) != 0 ) + { + char szErrorMessage[MAX_PATH]; + + V_snprintf( szErrorMessage, sizeof( szErrorMessage ), "Required installation file missing: %s", filename ); + + AfxMessageBox( szErrorMessage ); + return; + } + } +} + diff --git a/utils/vmpi/vmpi_service_install/ServiceInstallDlg.h b/utils/vmpi/vmpi_service_install/ServiceInstallDlg.h new file mode 100644 index 0000000..01dc433 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/ServiceInstallDlg.h @@ -0,0 +1,72 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_SERVICEINSTALLDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_) +#define AFX_SERVICEINSTALLDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// JobWatchDlg.h : header file +// + + +#include "resource.h" +#include "tier1/utlvector.h" + + + +///////////////////////////////////////////////////////////////////////////// +// CJobWatchDlg dialog + +class CServiceInstallDlg : public CDialog +{ +// Construction +public: + CServiceInstallDlg( CWnd* pParent = NULL); // standard constructor + virtual ~CServiceInstallDlg(); + +// Dialog Data + //{{AFX_DATA(CJobWatchDlg) + enum { IDD = IDD_SERVICE_INSTALL_DIALOG}; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CJobWatchDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + SC_HANDLE m_hSCManager; + + void VerifyInstallFiles(); + + bool DoStartExisting(); + bool NukeDirectory( const char *pDir, char errorFile[MAX_PATH] ); + bool DoUninstall( bool bShowMessage ); + + // Generated message map functions + //{{AFX_MSG(CJobWatchDlg) + virtual BOOL OnInitDialog(); + virtual void OnCancel(); + virtual void OnInstall(); + virtual void OnUninstall(); + virtual void OnStartExisting(); + virtual void OnStopExisting(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SERVICEINSTALLDLG_H__761BDEEF_D549_4F10_817C_1C1FAF9FCA47__INCLUDED_) diff --git a/utils/vmpi/vmpi_service_install/StdAfx.cpp b/utils/vmpi/vmpi_service_install/StdAfx.cpp new file mode 100644 index 0000000..e10b100 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_service_install.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/vmpi_service_install/StdAfx.h b/utils/vmpi/vmpi_service_install/StdAfx.h new file mode 100644 index 0000000..d243112 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/StdAfx.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_) +#define AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include "tier0/basetypes.h" + +#include <afxwin.h> // MFC core and standard components +#include <afxext.h> // MFC extensions +#include <afxdisp.h> // MFC Automation classes +#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include <afxcmn.h> // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include <winsvc.h> +#include <io.h> + +#include "iphelpers.h" +#include "vmpi.h" + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__E8FBDA6A_CE57_4416_8329_90155CD6CEC3__INCLUDED_) diff --git a/utils/vmpi/vmpi_service_install/res/vmpi.ico b/utils/vmpi/vmpi_service_install/res/vmpi.ico Binary files differnew file mode 100644 index 0000000..24dc2cf --- /dev/null +++ b/utils/vmpi/vmpi_service_install/res/vmpi.ico diff --git a/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico Binary files differnew file mode 100644 index 0000000..7eef0bc --- /dev/null +++ b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico diff --git a/utils/vmpi/vmpi_service_install/res/vmpi_service_install.rc2 b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.rc2 new file mode 100644 index 0000000..6e93fd0 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.rc2 @@ -0,0 +1,13 @@ +// +// VMPI_BROWSER_JOB_WATCH.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/utils/vmpi/vmpi_service_install/resource.h b/utils/vmpi/vmpi_service_install/resource.h new file mode 100644 index 0000000..9330dcb --- /dev/null +++ b/utils/vmpi/vmpi_service_install/resource.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by vmpi_service_install.rc +// +#define IDD_SERVICE_INSTALL_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_SERVICE_INSTALL 135 +#define IDC_TEXTOUTPUT 200 +#define IDC_COMMAND_LINE 201 +#define IDC_INSTALL_LOCATION 201 +#define IDC_INSTALL_BUTTON 1011 +#define IDC_CANCEL_BUTTON 1012 +#define IDC_UNINSTALL_BUTTON2 1013 +#define IDC_START_EXISTING_BUTTON 1014 +#define IDC_STOP_EXISTING_BUTTON 1015 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 131 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1012 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_service_install/vmpi_service_install.cpp b/utils/vmpi/vmpi_service_install/vmpi_service_install.cpp new file mode 100644 index 0000000..f366efa --- /dev/null +++ b/utils/vmpi/vmpi_service_install/vmpi_service_install.cpp @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_watch.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "vmpi_service_install.h" +#include "ServiceInstallDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CServiceInstallApp + +BEGIN_MESSAGE_MAP(CServiceInstallApp, CWinApp) + //{{AFX_MSG_MAP(CServiceInstallApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CServiceInstallApp construction + +CServiceInstallApp::CServiceInstallApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CServiceInstallApp object + +CServiceInstallApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CServiceInstallApp initialization + +BOOL CServiceInstallApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + + CServiceInstallDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/utils/vmpi/vmpi_service_install/vmpi_service_install.h b/utils/vmpi/vmpi_service_install/vmpi_service_install.h new file mode 100644 index 0000000..e29f338 --- /dev/null +++ b/utils/vmpi/vmpi_service_install/vmpi_service_install.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_job_watch.h : main header file for the VMPI_BROWSER_JOB_WATCH application +// + +#if !defined(AFX_VMPI_SERVICE_INSTALL_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_) +#define AFX_VMPI_SERVICE_INSTALL_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserJobWatchApp: +// See vmpi_browser_job_watch.cpp for the implementation of this class +// + +class CServiceInstallApp : public CWinApp +{ +public: + CServiceInstallApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVMPIBrowserJobWatchApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVMPIBrowserJobWatchApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VMPI_SERVICE_INSTALL_H__1DF22047_F615_4799_913A_222E3701BE5E__INCLUDED_) diff --git a/utils/vmpi/vmpi_service_install/vmpi_service_install.rc b/utils/vmpi/vmpi_service_install/vmpi_service_install.rc new file mode 100644 index 0000000..f9d59aa --- /dev/null +++ b/utils/vmpi/vmpi_service_install/vmpi_service_install.rc @@ -0,0 +1,173 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\vmpi_service_install.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON "res\\vmpi.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "FileDescription", "vmpi_service_install MFC Application" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "vmpi_service_install" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "vmpi_service_install.EXE" + VALUE "ProductName", "vmpi_service_install Application" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SERVICE_INSTALL_DIALOG DIALOGEX 0, 0, 467, 366 +STYLE DS_SETFONT | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "VMPI Service Installer" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_INSTALL_LOCATION,69,52,388,14,ES_AUTOHSCROLL + PUSHBUTTON "&Install / Update",IDC_INSTALL_BUTTON,9,78,107,14 + PUSHBUTTON "&Uninstall",IDC_UNINSTALL_BUTTON2,9,103,50,14 + PUSHBUTTON "&Start Existing",IDC_START_EXISTING_BUTTON,66,103,50,14 + PUSHBUTTON "S&top Existing",IDC_STOP_EXISTING_BUTTON,123,103,50,14 + PUSHBUTTON "&Quit",IDC_CANCEL_BUTTON,180,103,50,14 + EDITTEXT IDC_TEXTOUTPUT,9,132,448,224,ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL + LTEXT "Install Directory:",IDC_STATIC,9,55,51,8 + LTEXT "This application will install the VMPI service onto your local machine.\nPlease enter a location to install it and click the Install button.",IDC_STATIC,15,9,442,36 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SERVICE_INSTALL_DIALOG, DIALOG + BEGIN + VERTGUIDE, 9 + VERTGUIDE, 457 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\vmpi_service_install.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_service_install/vmpi_service_install.vpc b/utils/vmpi/vmpi_service_install/vmpi_service_install.vpc new file mode 100644 index 0000000..2c40ddb --- /dev/null +++ b/utils/vmpi/vmpi_service_install/vmpi_service_install.vpc @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// VMPI_SERVICE_INSTALL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vmpi_service_install" + +$Include "$SRCDIR\vpc_scripts\source_exe_win_win32_base.vpc" + +$Configuration "Debug" +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,.\,..\,..\..\common,..\..\..\public" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;WINVER=0x400" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "stdafx.h" + } + + $Linker + { + $AdditionalDependencies "nafxcwd.lib" + $IgnoreSpecificLibrary "nafxcw.lib libcmt.lib libcmtd.lib" + } +} + +$Configuration "Release" +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,.\,..\,..\..\common,..\..\..\public" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;WINVER=0x400" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "stdafx.h" + } + + $Linker + { + $AdditionalDependencies "nafxcw.lib libcmt.lib" + $IgnoreSpecificLibrary "nafxcwd.lib libcmtd.lib" + } +} + +$Project "Vmpi_service_install" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "StdAfx.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + + $File "ServiceInstallDlg.cpp" + $File "vmpi_service_install.cpp" + $File "vmpi_service_install.rc" + } + + $Folder "Header Files" + { + $File "ServiceInstallDlg.h" + $File "Resource.h" + $File "StdAfx.h" + $File "vmpi_service_install.h" + } + + $Folder "Resource Files" + { + $File "res\vmpi_service_install.ico" + $File "res\vmpi_service_install.rc2" + } + + $Folder "Link Libraries" + { + $DynamicFile "$SRCDIR\lib\public\vmpi.lib" + } +} diff --git a/utils/vmpi/vmpi_service_ui/StdAfx.cpp b/utils/vmpi/vmpi_service_ui/StdAfx.cpp new file mode 100644 index 0000000..d22d5d4 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_service_ui.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/vmpi/vmpi_service_ui/StdAfx.h b/utils/vmpi/vmpi_service_ui/StdAfx.h new file mode 100644 index 0000000..31b5d12 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/StdAfx.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) +#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include <windows.h> + + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) diff --git a/utils/vmpi/vmpi_service_ui/idi_busy_icon.ico b/utils/vmpi/vmpi_service_ui/idi_busy_icon.ico Binary files differnew file mode 100644 index 0000000..ac62654 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/idi_busy_icon.ico diff --git a/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico b/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico Binary files differnew file mode 100644 index 0000000..e49e952 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico diff --git a/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico b/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico Binary files differnew file mode 100644 index 0000000..a52165d --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico diff --git a/utils/vmpi/vmpi_service_ui/resource.h b/utils/vmpi/vmpi_service_ui/resource.h new file mode 100644 index 0000000..95b752b --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/resource.h @@ -0,0 +1,47 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by vmpi_service_ui.rc +// +#define IDC_MYICON 2 +#define IDI_DISABLED_ICON 101 +#define IDD_VMPI_SERVICE_DIALOG 102 +#define IDI_BUSY_ICON 102 +#define IDD_ABOUTBOX 103 +#define IDS_APP_TITLE 103 +#define IDR_POPUP_MENU 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_WAITING_ICON 105 +#define IDS_HELLO 106 +#define IDI_VMPI_SERVICE 107 +#define IDI_SMALL 108 +#define IDC_VMPI_SERVICE 109 +#define IDR_MAINFRAME 128 +#define IDD_VMPI_SERVICE 129 +#define IDD_SET_PASSWORD 130 +#define IDI_UNCONNECTED 132 +#define ID_KILLCURRENTJOB 133 +#define IDC_DEBUG_OUTPUT 1000 +#define IDC_PASSWORD 1001 +#define ID_SET_PASSWORD 32771 +#define ID_IGNORECSXCOMPILES 32772 +#define ID_HIGHLIGHT_ICON_WHEN_BUSY 32773 +#define ID_SCREENSAVER_MODE 32774 +#define ID_ENABLE_WORKER 40001 +#define ID_DISABLE_WORKER 40003 +#define ID_EXIT_SERVICE 40004 +#define ID_SHOW_CONSOLE_WINDOW 40005 +#define ID_HIDE_CONSOLE_WINDOW 40006 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 133 +#define _APS_NEXT_COMMAND_VALUE 32775 +#define _APS_NEXT_CONTROL_VALUE 1002 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/utils/vmpi/vmpi_service_ui/shell_icon_mgr.cpp b/utils/vmpi/vmpi_service_ui/shell_icon_mgr.cpp new file mode 100644 index 0000000..cfdf759 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/shell_icon_mgr.cpp @@ -0,0 +1,187 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "shell_icon_mgr.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" +#include "shellapi.h" + + +#define SHELLICONMGR_CLASSNAME "VMPI_ShellIconMgr" + + + +CShellIconMgr::CShellIconMgr() +{ + m_hWnd = NULL; + m_hWndClass = NULL; + m_uTaskbarRestart = (UINT)-1; + m_iCurIconResourceID = 0; + m_pHelper = NULL; +} + + +CShellIconMgr::~CShellIconMgr() +{ + Term(); +} + + +bool CShellIconMgr::Init( + IShellIconMgrHelper *pHelper, + const char *pToolTip, + int iCallbackMessage, + int iIconResourceID ) +{ + Term(); + + m_pHelper = pHelper; + m_iCallbackMessage = iCallbackMessage; + m_pToolTip = pToolTip; + + // Create the window class. + WNDCLASS wndclass; + memset( &wndclass, 0, sizeof( wndclass ) ); + wndclass.lpfnWndProc = &CShellIconMgr::StaticWindowProc; + wndclass.hInstance = (HINSTANCE)pHelper->GetHInstance(); + wndclass.hIcon = NULL; + wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); + wndclass.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH ); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = SHELLICONMGR_CLASSNAME; + m_hWndClass = RegisterClass( &wndclass ); + if ( !m_hWndClass ) + { + Assert( false ); + Warning( "RegisterClass failed.\n" ); + Term(); + return false; + } + + + // Create the window. + m_hWnd = CreateWindow( + SHELLICONMGR_CLASSNAME, + SHELLICONMGR_CLASSNAME, + WS_DISABLED, // we only want shell notify messages + 0, 0, 0, 0, // position + NULL, // parent + NULL, // menu + pHelper->GetHInstance(), // hInstance + this // lpParam + ); + if ( !m_hWnd ) + { + Assert( false ); + Warning( "CreateWindow failed.\n" ); + Term(); + return false; + } + + m_uTaskbarRestart = RegisterWindowMessage( TEXT( "TaskbarCreated" ) ); + SetWindowLong( m_hWnd, GWL_USERDATA, (LONG)this ); + UpdateWindow( m_hWnd ); + + // Don't handle errors here because the taskbar may not be created yet if we are a service. + m_iCurIconResourceID = iIconResourceID; + CreateTrayIcon(); + return true; +} + + +void CShellIconMgr::Term() +{ + if ( m_hWndClass ) + { + UnregisterClass( SHELLICONMGR_CLASSNAME, m_pHelper->GetHInstance() ); + m_hWndClass = NULL; + } + + if ( m_hWnd ) + { + // Remove the shell icon if there is one. + NOTIFYICONDATA data; + memset( &data, 0, sizeof( data ) ); + data.cbSize = sizeof( data ); + data.hWnd = m_hWnd; + Shell_NotifyIcon( NIM_DELETE, &data ); + + DestroyWindow( m_hWnd ); + m_hWnd = NULL; + } +} + + +void CShellIconMgr::ChangeIcon( int iIconResourceID ) +{ + NOTIFYICONDATA data; + + memset( &data, 0, sizeof( data ) ); + data.cbSize = sizeof( data ); + data.uFlags = NIF_ICON; + data.hWnd = m_hWnd; + data.hIcon = LoadIcon( m_pHelper->GetHInstance(), MAKEINTRESOURCE( iIconResourceID ) ); + + Shell_NotifyIcon( NIM_MODIFY, &data ); + + m_iCurIconResourceID = iIconResourceID; +} + + +void CShellIconMgr::CreateTrayIcon() +{ + // Now create the shell icon. + NOTIFYICONDATA data; + + memset( &data, 0, sizeof( data ) ); + data.cbSize = sizeof( data ); + data.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; + data.uCallbackMessage = m_iCallbackMessage; + data.hWnd = m_hWnd; + data.hIcon = LoadIcon( m_pHelper->GetHInstance(), MAKEINTRESOURCE( m_iCurIconResourceID ) ); + Q_strncpy( data.szTip, m_pToolTip, sizeof( data.szTip ) ); + + Shell_NotifyIcon( NIM_ADD, &data ); +} + + +LRESULT CShellIconMgr::WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + if ( hWnd != m_hWnd ) + { + return DefWindowProc( hWnd, uMsg, wParam, lParam ); + } + + if ( uMsg == m_uTaskbarRestart ) + { + CreateTrayIcon(); + return 0; + } + + return m_pHelper->WindowProc( hWnd, uMsg, wParam, lParam ); +} + + +LRESULT CShellIconMgr::StaticWindowProc( + HWND hwnd, // handle to window + UINT uMsg, // message identifier + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ) +{ + CShellIconMgr *pMgr = (CShellIconMgr*)GetWindowLong( hwnd, GWL_USERDATA ); + if ( pMgr ) + { + return pMgr->WindowProc( hwnd, uMsg, wParam, lParam ); + } + else + { + return DefWindowProc( hwnd, uMsg, wParam, lParam ); + } +} + diff --git a/utils/vmpi/vmpi_service_ui/shell_icon_mgr.h b/utils/vmpi/vmpi_service_ui/shell_icon_mgr.h new file mode 100644 index 0000000..81188d4 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/shell_icon_mgr.h @@ -0,0 +1,66 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef SHELL_ICON_MGR_H +#define SHELL_ICON_MGR_H +#ifdef _WIN32 +#pragma once +#endif + + +class IShellIconMgrHelper +{ +public: + virtual HINSTANCE GetHInstance() = 0; + virtual int WindowProc( void *hWnd, int uMsg, long wParam, long lParam ) = 0; +}; + +class CShellIconMgr +{ +public: + + CShellIconMgr(); + ~CShellIconMgr(); + + bool Init( + IShellIconMgrHelper *pHelper, + const char *pToolTip, // This must be allocated by the caller and must be valid as + // long as the CShellIconMgr exists. + int iCallbackMessage, + int iIconResourceID ); + + void Term(); + + void ChangeIcon( int iIconResourceID ); + + +private: + + void CreateTrayIcon(); + + LRESULT WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + + static LRESULT CALLBACK StaticWindowProc( + HWND hwnd, // handle to window + UINT uMsg, // message identifier + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ); + + HWND m_hWnd; // Invisible window to get timer and shell icon messages. + ATOM m_hWndClass; + + UINT m_uTaskbarRestart; // This message is sent to us when the taskbar is created. + int m_iCurIconResourceID; + int m_iCallbackMessage; + const char *m_pToolTip; + + IShellIconMgrHelper *m_pHelper; +}; + + +#endif // SHELL_ICON_MGR_H diff --git a/utils/vmpi/vmpi_service_ui/unconnec.ico b/utils/vmpi/vmpi_service_ui/unconnec.ico Binary files differnew file mode 100644 index 0000000..5cfdcdb --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/unconnec.ico diff --git a/utils/vmpi/vmpi_service_ui/vmpi_service.ico b/utils/vmpi/vmpi_service_ui/vmpi_service.ico Binary files differnew file mode 100644 index 0000000..3868835 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/vmpi_service.ico diff --git a/utils/vmpi/vmpi_service_ui/vmpi_service_ui.cpp b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.cpp new file mode 100644 index 0000000..7bb756f --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.cpp @@ -0,0 +1,617 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_service_ui.cpp : Defines the entry point for the application. +// + +#include "stdafx.h" +#include "consolewnd.h" +#include "resource.h" +#include "tier0/dbg.h" +#include "tier1/strtools.h" +#include "shell_icon_mgr.h" +#include "vmpi.h" +#include "service_conn_mgr.h" +#include <io.h> +#include <time.h> + + + +void UpdatePopupMenuState(); + + + +const char *g_pIconTooltip = VMPI_SERVICE_NAME; + +IConsoleWnd *g_pConsoleWnd = NULL; +HINSTANCE g_hInstance = NULL; + + +#define MYWM_NOTIFYICON (WM_APP+100) +CShellIconMgr g_ShellIconMgr; + +bool g_bHighlightIconWhenBusy = false; + + +// STATE THE SERVICE OWNS. +int g_iCurState = 0; // One of the VMPI_SERVICE_STATE_ defines. +char *g_pPassword = NULL; +bool g_bScreensaverMode = false; + + +void LogString( const char *pStr, ... ) +{ +#ifdef VMPI_SERVICE_LOGS + char str[4096]; + va_list marker; + va_start( marker, pStr ); + _vsnprintf( str, sizeof( str ), pStr, marker ); + va_end( marker ); + + static FILE *fp = fopen( "c:\\vmpi_service_ui.log", "wt" ); + if ( fp ) + { + fprintf( fp, "%s", str ); + fflush( fp ); + } +#endif +} + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + return err; +} + + +// ------------------------------------------------------------------------------------------ // +// Persistent state in the registry. +// ------------------------------------------------------------------------------------------ // +void LoadStateFromRegistry() +{ + HKEY hKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); + if ( hKey ) + { + DWORD val = 0; + DWORD type = REG_DWORD; + DWORD size = sizeof( val ); + if ( RegQueryValueEx( + hKey, + "HighlightIconWhenBusy", + 0, + &type, + (unsigned char*)&val, + &size ) == ERROR_SUCCESS && + type == REG_DWORD && + size == sizeof( val ) ) + { + g_bHighlightIconWhenBusy = (val != 0); + } + } +} + +void SaveStateToRegistry() +{ + HKEY hKey = NULL; + RegCreateKey( HKEY_LOCAL_MACHINE, VMPI_SERVICE_KEY, &hKey ); + if ( hKey ) + { + DWORD val = g_bHighlightIconWhenBusy; + RegSetValueEx( + hKey, + "HighlightIconWhenBusy", + 0, + REG_DWORD, + (unsigned char*)&val, + sizeof( val ) ); + } +} + + +// ------------------------------------------------------------------------------------------ // +// Our CServiceConnMgr packet handler. +// ------------------------------------------------------------------------------------------ // + +void UpdateAppIcon() +{ + if ( g_iCurState == VMPI_SERVICE_STATE_IDLE ) + { + g_ShellIconMgr.ChangeIcon( IDI_WAITING_ICON ); + } + else if ( g_iCurState == VMPI_SERVICE_STATE_BUSY ) + { + if ( g_bHighlightIconWhenBusy ) + g_ShellIconMgr.ChangeIcon( IDI_BUSY_ICON ); + else + g_ShellIconMgr.ChangeIcon( IDI_WAITING_ICON ); + } + else + { + g_ShellIconMgr.ChangeIcon( IDI_DISABLED_ICON ); + } +} + + +class CUIConnMgr : public CServiceConnMgr +{ +public: + virtual void HandlePacket( const char *pData, int len ); +}; + + +void CUIConnMgr::HandlePacket( const char *pData, int len ) +{ + if ( pData[0] != VMPI_SERVICE_UI_PROTOCOL_VERSION ) + return; + + int packetID = pData[1]; + + int offset = 2; + if ( packetID == VMPI_SERVICE_TO_UI_CONSOLE_TEXT ) + { + Msg( &pData[offset] ); + } + else if ( packetID == VMPI_SERVICE_TO_UI_STATE ) + { + // Get the new state out.. + g_iCurState = *((int*)&pData[offset]); + offset += 4; + + LogString( "New UI state: %d.\n", g_iCurState ); + + // Update our icon. + UpdateAppIcon(); + + g_bScreensaverMode = (*((char*)&pData[offset]) != 0); + ++offset; + + // Store the current password. + if ( g_pPassword ) + delete [] g_pPassword; + + const char *pStr = &pData[offset]; + g_pPassword = new char[ strlen( pStr ) + 1 ]; + strcpy( g_pPassword, pStr ); + offset += strlen( pStr ) + 1; + + UpdatePopupMenuState(); + } + else if ( packetID == VMPI_SERVICE_TO_UI_PATCHING ) + { + LogString( "Got a VMPI_SERVICE_TO_UI_PATCHING packet.\n" ); + + int bExitAfter = pData[offset]; + ++offset; + + char workingDir[MAX_PATH], commandLine[4096]; + V_strncpy( workingDir, &pData[offset], sizeof( workingDir ) ); + offset += V_strlen( workingDir ) + 1; + + V_strncpy( commandLine, &pData[offset], sizeof( commandLine ) ); + offset += V_strlen( commandLine ) + 1; + + // Run whatever they said to run. + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + if ( CreateProcess( NULL, commandLine, NULL, NULL, false, CREATE_NO_WINDOW, NULL, workingDir, &si, &pi ) ) + { + LogString( "CreateProcess succeeded:\n%s\n", commandLine ); + + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + + if ( bExitAfter ) + PostQuitMessage( 0 ); + } + else + { + LogString( "CreateProcess failed: %s", GetLastErrorString() ); + } + } + else if ( packetID == VMPI_SERVICE_TO_UI_EXIT ) + { + // Exit. + PostQuitMessage( 0 ); + } +} + + +// ------------------------------------------------------------------------------------------ // +// Helpers. +// ------------------------------------------------------------------------------------------ // + +void InternalSpew( const char *pMsg ) +{ + if ( g_pConsoleWnd ) + { + g_pConsoleWnd->PrintToConsole( pMsg ); + } + + OutputDebugString( pMsg ); +} + + +SpewRetval_t MySpewOutputFunc( SpewType_t spewType, const char *pMsg ) +{ + // Prepend the time. + time_t aclock; + time( &aclock ); + struct tm *newtime = localtime( &aclock ); + + // Get rid of the \n. + char timeString[512]; + Q_strncpy( timeString, asctime( newtime ), sizeof( timeString ) ); + char *pEnd = strstr( timeString, "\n" ); + if ( pEnd ) + *pEnd = 0; + InternalSpew( timeString ); + + InternalSpew( " - " ); + InternalSpew( pMsg ); + + if ( spewType == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( spewType == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + + +CUIConnMgr g_ConnMgr; + +void InitConsoleWindow() +{ + // Only initialize it once. + if ( g_pConsoleWnd ) + return; + + g_pConsoleWnd = CreateConsoleWnd( + g_hInstance, + IDD_VMPI_SERVICE, + IDC_DEBUG_OUTPUT, + false ); +} + + +// ------------------------------------------------------------------------------------------ // +// Implementation of IShellIconMgrHelper. +// ------------------------------------------------------------------------------------------ // + +HMENU g_hMenu = NULL; +HMENU g_hPopupMenu = NULL; // This is just a submenu of g_hMenu. + + +bool LoadPopupMenu() +{ + g_hMenu = LoadMenu( g_hInstance, MAKEINTRESOURCE( IDR_POPUP_MENU ) ); + if ( !g_hMenu ) + { + Assert( false ); + Warning( "LoadMenu failed.\n" ); + return false; + } + + g_hPopupMenu = GetSubMenu( g_hMenu, 0 ); + return true; +} + + +void TermPopupMenu() +{ + if ( g_hMenu ) + { + DestroyMenu( g_hMenu ); + g_hMenu = NULL; + } + g_hPopupMenu = NULL; +} + + +void UpdatePopupMenuState() +{ + bool bEnabled = (g_iCurState == VMPI_SERVICE_STATE_IDLE || g_iCurState == VMPI_SERVICE_STATE_BUSY); + EnableMenuItem( g_hPopupMenu, ID_ENABLE_WORKER, bEnabled ? MF_GRAYED : MF_ENABLED ); + EnableMenuItem( g_hPopupMenu, ID_DISABLE_WORKER, !bEnabled ? MF_GRAYED : MF_ENABLED ); + + // Enable or disable console items. + EnableMenuItem( g_hPopupMenu, ID_SHOW_CONSOLE_WINDOW, g_pConsoleWnd->IsVisible() ? MF_GRAYED : MF_ENABLED ); + EnableMenuItem( g_hPopupMenu, ID_HIDE_CONSOLE_WINDOW, !g_pConsoleWnd->IsVisible() ? MF_GRAYED : MF_ENABLED ); + + CheckMenuItem( g_hPopupMenu, ID_SCREENSAVER_MODE, g_bScreensaverMode ? MF_CHECKED : MF_UNCHECKED ); + CheckMenuItem( g_hPopupMenu, ID_HIGHLIGHT_ICON_WHEN_BUSY, g_bHighlightIconWhenBusy ? MF_CHECKED : MF_UNCHECKED ); +} + + +int CALLBACK SetPasswordDlgProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter +) +{ + switch( uMsg ) + { + case WM_INITDIALOG: + { + if ( g_pPassword ) + { + HWND hWnd = GetDlgItem( hwndDlg, IDC_PASSWORD ); + SetWindowText( hWnd, g_pPassword ); + } + } + break; + + case WM_COMMAND: + { + switch( wParam ) + { + case IDOK: + { + // Set our new password. + HWND hWnd = GetDlgItem( hwndDlg, IDC_PASSWORD ); + if ( hWnd ) + { + char tempBuf[512]; + GetWindowText( hWnd, tempBuf, sizeof( tempBuf ) ); + + // Send it to the service. + CUtlVector<char> data; + data.AddToTail( VMPI_SERVICE_UPDATE_PASSWORD ); + data.AddMultipleToTail( strlen( tempBuf ) + 1, tempBuf ); + g_ConnMgr.SendPacket( -1, data.Base(), data.Count() ); + } + EndDialog( hwndDlg, 0 ); + } + break; + + case IDCANCEL: + { + EndDialog( hwndDlg, 0 ); + } + break; + } + } + break; + } + + return FALSE; +} + + +class CShellIconMgrHelper : public IShellIconMgrHelper +{ +public: + virtual HINSTANCE GetHInstance() + { + return g_hInstance; + } + + virtual int WindowProc( void *pWnd, int uMsg, long wParam, long lParam ) + { + HWND hWnd = (HWND)pWnd; + + switch( uMsg ) + { + // Right button brings up the popup menu. + case MYWM_NOTIFYICON: + { + if ( lParam == WM_RBUTTONDOWN ) + { + POINT cursorPos; + GetCursorPos( &cursorPos ); + + UpdatePopupMenuState(); + + // Make a popup menu. + SetForegroundWindow( hWnd ); + TrackPopupMenu( g_hPopupMenu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, cursorPos.x, cursorPos.y, 0, hWnd, NULL ); + return 0; + } + else if ( lParam == WM_LBUTTONDOWN ) + { + // Left button brings up the console. + g_pConsoleWnd->SetVisible( true ); + UpdatePopupMenuState(); + return 0; + } + } + break; + + case WM_COMMAND: + { + switch( wParam ) + { + case ID_ENABLE_WORKER: + { + char cPacket = VMPI_SERVICE_ENABLE; + g_ConnMgr.SendPacket( -1, &cPacket, 1 ); + } + break; + + case ID_DISABLE_WORKER: + { + char cPacket = VMPI_SERVICE_DISABLE; + g_ConnMgr.SendPacket( -1, &cPacket, 1 ); + } + break; + + case ID_KILLCURRENTJOB: + { + char cPacket = VMPI_KILL_PROCESS; + g_ConnMgr.SendPacket( -1, &cPacket, 1 ); + } + break; + + case ID_HIGHLIGHT_ICON_WHEN_BUSY: + { + g_bHighlightIconWhenBusy = !g_bHighlightIconWhenBusy; + SaveStateToRegistry(); + UpdateAppIcon(); + UpdatePopupMenuState(); + } + break; + + case ID_SCREENSAVER_MODE: + { + g_bScreensaverMode = !g_bScreensaverMode; + char cPacket[2] = { VMPI_SERVICE_SCREENSAVER_MODE, g_bScreensaverMode }; + g_ConnMgr.SendPacket( -1, cPacket, sizeof( cPacket ) ); + } + break; + + + case ID_SHOW_CONSOLE_WINDOW: + { + g_pConsoleWnd->SetVisible( true ); + UpdatePopupMenuState(); + } + break; + + case ID_HIDE_CONSOLE_WINDOW: + { + g_pConsoleWnd->SetVisible( false ); + UpdatePopupMenuState(); + } + break; + + case ID_SET_PASSWORD: + { + DialogBox( g_hInstance, MAKEINTRESOURCE( IDD_SET_PASSWORD ), NULL, SetPasswordDlgProc ); + } + break; + + case ID_EXIT_SERVICE: + { + // Quit the service app.. + char cPacket = VMPI_SERVICE_EXIT; + g_ConnMgr.SendPacket( -1, &cPacket, 1 ); + + // Stop showing the icon. + g_ShellIconMgr.Term(); + + // Wait for a bit for the connection to go away. + DWORD startTime = GetTickCount(); + while ( GetTickCount()-startTime < 2000 ) + { + g_ConnMgr.Update(); + if ( !g_ConnMgr.IsConnected() ) + break; + else + Sleep( 10 ); + } + + // Quit the UI app. + PostQuitMessage( 0 ); + return 1; + } + break; + } + } + break; + } + + return DefWindowProc( (HWND)hWnd, uMsg, wParam, lParam ); + } +}; + +CShellIconMgrHelper g_ShellIconMgrHelper; + + + +int APIENTRY WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nCmdShow) +{ + g_hInstance = hInstance; + + + LogString( "vmpi_service_ui startup.\n" ); + + // Don't run multiple instances. + HANDLE hMutex = CreateMutex( NULL, FALSE, "vmpi_service_ui_mutex" ); + if ( hMutex && GetLastError() == ERROR_ALREADY_EXISTS ) + return 1; + + + // Hook spew output. + SpewOutputFunc( MySpewOutputFunc ); + InitConsoleWindow(); + LogString( "Setup console window.\n" ); + + LoadStateFromRegistry(); + + // Setup the popup menu. + if( !LoadPopupMenu() ) + { + return false; + } + UpdatePopupMenuState(); + + + // Setup the tray icon. + Msg( "Waiting for jobs...\n" ); + if ( !g_ShellIconMgr.Init( &g_ShellIconMgrHelper, g_pIconTooltip, MYWM_NOTIFYICON, IDI_WAITING_ICON ) ) + { + return false; + } + + + // Connect to the VMPI service. + g_ConnMgr.InitClient(); + + LogString( "Entering main loop.\n" ); + + while ( 1 ) + { + MSG msg; + msg.message = !WM_QUIT; // So it doesn't accidentally exit. + while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + if ( msg.message == WM_QUIT ) + break; + + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + if ( msg.message == WM_QUIT ) + break; + + g_ConnMgr.Update(); + if ( !g_ConnMgr.IsConnected() ) + { + g_ShellIconMgr.ChangeIcon( IDI_UNCONNECTED ); + } + + Sleep( 30 ); + } + + // Important that we call this instead of letting the destructor do it because it deletes its + // socket and it needs to cleanup some threads. + g_ConnMgr.Term(); + + g_ShellIconMgr.Term(); + + return 0; +} + + + diff --git a/utils/vmpi/vmpi_service_ui/vmpi_service_ui.rc b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.rc new file mode 100644 index 0000000..c3a6c62 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.rc @@ -0,0 +1,171 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_DISABLED_ICON ICON "idi_disabled_icon.ico" +IDI_VMPI_SERVICE ICON "vmpi_service.ICO" +IDI_BUSY_ICON ICON "idi_busy_icon.ico" +IDI_WAITING_ICON ICON "idi_waiting_icon.ico" +IDI_UNCONNECTED ICON "unconnec.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_POPUP_MENU MENU +BEGIN + POPUP "item1" + BEGIN + MENUITEM "&Enable", ID_ENABLE_WORKER + MENUITEM "&Disable", ID_DISABLE_WORKER + MENUITEM "&Kill Current Job", ID_KILLCURRENTJOB + MENUITEM SEPARATOR + MENUITEM "&Show Console Window", ID_SHOW_CONSOLE_WINDOW + MENUITEM "&Hide Console Window", ID_HIDE_CONSOLE_WINDOW + MENUITEM SEPARATOR + MENUITEM "Set &Password", ID_SET_PASSWORD + MENUITEM "Highlight Icon When Busy", ID_HIGHLIGHT_ICON_WHEN_BUSY + MENUITEM "Screensaver Mode", ID_SCREENSAVER_MODE + MENUITEM SEPARATOR + MENUITEM "&Exit Service", ID_EXIT_SERVICE + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""resource.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_VMPI_SERVICE DIALOG 0, 0, 454, 225 +STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Valve MPI Service" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_DEBUG_OUTPUT,7,7,440,211,ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL +END + +IDD_SET_PASSWORD DIALOG 0, 0, 134, 50 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Set Password (-mpi_pw)" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_PASSWORD,7,7,120,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,29,50,14 + PUSHBUTTON "Cancel",IDCANCEL,77,29,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_VMPI_SERVICE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 447 + TOPMARGIN, 7 + BOTTOMMARGIN, 218 + END + + IDD_SET_PASSWORD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 127 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_APP_TITLE "vmpi_service" + IDS_HELLO "Hello World!" + IDC_VMPI_SERVICE "VMPI_SERVICE" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_service_ui/vmpi_service_ui.vpc b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.vpc new file mode 100644 index 0000000..0f33d66 --- /dev/null +++ b/utils/vmpi/vmpi_service_ui/vmpi_service_ui.vpc @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// VMPI_SERVICE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\..\common,..\,..\vmpi_service" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib odbc32.lib odbccp32.lib" + } +} + +$Project "Vmpi_service_ui" +{ + $Folder "Source Files" + { + $File "..\iphelpers.cpp" + $File "shell_icon_mgr.cpp" + $File "vmpi_service_ui.cpp" + $File "vmpi_service_ui.rc" + $File "StdAfx.cpp" + $File "..\tcpsocket_helpers.cpp" + $File "..\ThreadedTCPSocket.cpp" + $File "..\..\common\consolewnd.cpp" + $File "..\vmpi_service\service_conn_mgr.cpp" + $File "..\ThreadedTCPSocketEmu.cpp" + $File "..\threadhelpers.cpp" + } + + $Folder "Header Files" + { + $File "shell_icon_mgr.h" + $File "resource.h" + $File "StdAfx.h" + } + + $Folder "Resource Files" + { + $File "idi_busy_icon.ico" + $File "idi_disabled_icon.ico" + $File "idi_waiting_icon.ico" + $File "unconnec.ico" + $File "vmpi_service.ico" + } +} diff --git a/utils/vmpi/vmpi_services_watch/PatchTimeout.cpp b/utils/vmpi/vmpi_services_watch/PatchTimeout.cpp new file mode 100644 index 0000000..df242d8 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/PatchTimeout.cpp @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// PatchTimeout.cpp : implementation file +// + +#include "stdafx.h" +#include "PatchTimeout.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CPatchTimeout dialog + + +CPatchTimeout::CPatchTimeout(CWnd* pParent /*=NULL*/) + : CDialog(CPatchTimeout::IDD, pParent) +{ + //{{AFX_DATA_INIT(CPatchTimeout) + //}}AFX_DATA_INIT +} + + +void CPatchTimeout::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CPatchTimeout) + DDX_Text(pDX, IDC_COMMAND_LINE, m_PatchDirectory); + DDX_Text(pDX, IDC_VMPI_TRANSFER_DIRECTORY, m_VMPITransferDirectory); + DDX_Check(pDX, IDC_FORCE_PATCH, m_bForcePatch); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CPatchTimeout, CDialog) + //{{AFX_MSG_MAP(CPatchTimeout) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CPatchTimeout message handlers diff --git a/utils/vmpi/vmpi_services_watch/PatchTimeout.h b/utils/vmpi/vmpi_services_watch/PatchTimeout.h new file mode 100644 index 0000000..1efb171 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/PatchTimeout.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_PATCHTIMEOUT_H__2D87CBF2_AC88_4F23_BB43_CC8A5C248B64__INCLUDED_) +#define AFX_PATCHTIMEOUT_H__2D87CBF2_AC88_4F23_BB43_CC8A5C248B64__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// PatchTimeout.h : header file +// + +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +// CPatchTimeout dialog + +class CPatchTimeout : public CDialog +{ +// Construction +public: + CPatchTimeout(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CPatchTimeout) + enum { IDD = IDD_TIMEOUT }; + CString m_PatchDirectory; + CString m_VMPITransferDirectory; + int m_bForcePatch; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CPatchTimeout) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CPatchTimeout) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PATCHTIMEOUT_H__2D87CBF2_AC88_4F23_BB43_CC8A5C248B64__INCLUDED_) diff --git a/utils/vmpi/vmpi_services_watch/ServicesDlg.cpp b/utils/vmpi/vmpi_services_watch/ServicesDlg.cpp new file mode 100644 index 0000000..dd8ea5f --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/ServicesDlg.cpp @@ -0,0 +1,1161 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ServicesDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "ServicesDlg.h" +#include "vmpi.h" +#include "bitbuf.h" +#include "tier1/strtools.h" +#include "patchtimeout.h" +#include "SetPasswordDlg.h" +#include "vmpi_browser_helpers.h" +#include <io.h> + + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +#define SERVICE_OFF_TIMEOUT (20*1000) // If we haven't heard from a service in this long, + // then we assume the service is off. + +#define SERVICES_PING_INTERVAL (3*1000) // ping the services every so often + +#define SERVICE_MAX_UPDATE_INTERVAL (8*1000) // Update each service in the listbox at least this often. + + + +int V_AfxMessageBox( int mbType, const char *pFormat, ... ) +{ + char msg[4096]; + va_list marker; + va_start( marker, pFormat ); + _vsnprintf( msg, sizeof( msg ), pFormat, marker ); + va_end( marker ); + + return AfxMessageBox( msg, mbType ); +} + + +bool CServiceInfo::IsOff() const +{ + return (Plat_MSTime() - m_LastPingTimeMS) > SERVICE_OFF_TIMEOUT; +} + + +// Returns the argument following pName. +// If pName is the last argument on the command line, returns pEndArgDefault. +// Returns NULL if there is no argument with pName. +const char* FindArg( const char *pName, const char *pEndArgDefault="" ) +{ + for ( int i=0; i < __argc; i++ ) + { + if ( stricmp( pName, __argv[i] ) == 0 ) + { + if ( (i+1) < __argc ) + return __argv[i+1]; + else + return pEndArgDefault; + } + } + return NULL; +} + +void AppendStr( char *dest, int destSize, const char *pFormat, ... ) +{ + char str[4096]; + va_list marker; + va_start( marker, pFormat ); + _vsnprintf( str, sizeof( str ), pFormat, marker ); + va_end( marker ); + str[sizeof( str ) - 1] = 0; + + V_strncat( dest, str, destSize ); +} + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + +const char* GetStatusString( CServiceInfo *pInfo ) +{ + if ( pInfo->IsOff() ) + return "off"; + else if ( pInfo->m_iState == VMPI_STATE_BUSY ) + return "busy"; + else if ( pInfo->m_iState == VMPI_STATE_PATCHING ) + return "patching"; + else if ( pInfo->m_iState == VMPI_STATE_DISABLED ) + return "disabled"; + else if ( pInfo->m_iState == VMPI_STATE_SCREENSAVER_DISABLED ) + return "disabled (screensaver)"; + else if ( pInfo->m_iState == VMPI_STATE_DOWNLOADING ) + return "downloading"; + else + return "idle"; +} + + +// --------------------------------------------------------------------------------------------------------- // +// Column sort functions. +// --------------------------------------------------------------------------------------------------------- // +typedef int (CALLBACK *ServicesSortFn)( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ); + +static int CALLBACK SortByName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return strcmp( pInfo1->m_ComputerName, pInfo2->m_ComputerName ); +} + +static int CALLBACK SortByStatus( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( GetStatusString( pInfo2 ), GetStatusString( pInfo1 ) ); +} + +static int CALLBACK SortByRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + int v1 = pInfo1->m_LiveTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list. + int v2 = pInfo2->m_LiveTimeMS / (1000*60); + if ( v2 > v1 ) + return 1; + else if ( v2 < v1 ) + return -1; + else + return 0; +} + +static int CALLBACK SortByWorkerAppRunningTime( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + int v1 = pInfo1->m_WorkerAppTimeMS / (1000*60); // Sort on minutes so it doesn't constantly resort the list. + int v2 = pInfo2->m_WorkerAppTimeMS / (1000*60); + if ( v2 > v1 ) + return 1; + else if ( v2 < v1 ) + return -1; + else + return 0; +} + +static int CALLBACK SortByMasterName( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( pInfo2->m_MasterName, pInfo1->m_MasterName ); +} + +static int CALLBACK SortByProtocol( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return pInfo1->m_ProtocolVersion > pInfo2->m_ProtocolVersion; +} + +static int CALLBACK SortByPassword( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( pInfo2->m_Password, pInfo1->m_Password ); +} + +static int CALLBACK SortByServiceVersion( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( pInfo2->m_ServiceVersion, pInfo1->m_ServiceVersion ); +} + +static int CALLBACK SortByExe( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( pInfo2->m_ExeName, pInfo1->m_ExeName ); +} + +static int CALLBACK SortByMap( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + return -strcmp( pInfo2->m_MapName, pInfo1->m_MapName ); +} + +static int CALLBACK SortByCPUPercentage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + if ( pInfo2->m_CPUPercentage > pInfo1->m_CPUPercentage ) + return 1; + else if ( pInfo2->m_CPUPercentage < pInfo1->m_CPUPercentage ) + return -1; + else + return 0; +} + +static int CALLBACK SortByMemUsage( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + CServiceInfo *pInfo1 = (CServiceInfo*)iItem1; + CServiceInfo *pInfo2 = (CServiceInfo*)iItem2; + + if ( pInfo2->m_MemUsageMB > pInfo1->m_MemUsageMB ) + return 1; + else if ( pInfo2->m_MemUsageMB < pInfo1->m_MemUsageMB ) + return -1; + else + return 0; +} + +// --------------------------------------------------------------------------------------------------------- // +// Column information. +// --------------------------------------------------------------------------------------------------------- // + +struct +{ + char *pText; + int width; + ServicesSortFn sortFn; +} g_ColumnInfos[] = +{ + {"Computer Name", 150, SortByName}, + {"Status", 95, SortByStatus}, + {"Service Run Time", 100, SortByRunningTime}, + {"Worker App Run Time", 125, SortByWorkerAppRunningTime}, + {"Master", 150, SortByMasterName}, + {"Password", 60, SortByPassword}, + {"Protocol", 60, SortByProtocol}, + {"Version", 50, SortByServiceVersion}, + {"CPU", 50, SortByCPUPercentage}, + {"Memory (MB)", 80, SortByMemUsage}, + {"Exe", 80, SortByExe}, + {"Map", 90, SortByMap}, +}; +#define COLUMN_COMPUTER_NAME 0 +#define COLUMN_STATUS 1 +#define COLUMN_RUNNING_TIME 2 +#define COLUMN_WORKER_APP_RUNNING_TIME 3 +#define COLUMN_MASTER_NAME 4 +#define COLUMN_PASSWORD 5 +#define COLUMN_PROTOCOL_VERSION 6 +#define COLUMN_SERVICE_VERSION 7 +#define COLUMN_CPU_PERCENTAGE 8 +#define COLUMN_MEM_USAGE 9 +#define COLUMN_EXE_NAME 10 +#define COLUMN_MAP_NAME 11 + + +// Used to sort all the columns. +// When they click on a column, we add that index to entry 0 in here. +// Then when sorting, if 2 columns are equal, we move to the next sort function. +CUtlVector<int> g_SortColumns; + +static void PushSortColumn( int iColumn ) +{ + // First, get rid of all entries with this same index. + int iPrev = g_SortColumns.Find( iColumn ); + if ( iPrev != g_SortColumns.InvalidIndex() ) + g_SortColumns.Remove( iPrev ); + + // Now add this one. + g_SortColumns.AddToTail( iColumn ); +} + +static int CALLBACK MainSortFn( LPARAM iItem1, LPARAM iItem2, LPARAM lpParam ) +{ + int curStatus = 0; + for ( int i = (int)g_SortColumns.Count()-1; i >= 0; i-- ) + { + curStatus = g_ColumnInfos[g_SortColumns[i]].sortFn( iItem1, iItem2, lpParam ); + if ( curStatus != 0 ) + break; + } + return curStatus; +} + + +///////////////////////////////////////////////////////////////////////////// +// CServicesDlg dialog + + +CServicesDlg::CServicesDlg(CWnd* pParent /*=NULL*/) + : CIdleDialog(CServicesDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CServicesDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + m_pServicesPingSocket = NULL; +} + + +void CServicesDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CServicesDlg) + DDX_Control(pDX, IDC_NUM_SERVICES, m_NumServicesControl); + DDX_Control(pDX, IDC_NUM_DISABLED_SERVICES, m_NumDisabledServicesControl); + DDX_Control(pDX, IDC_NUM_WORKING_SERVICES, m_NumWorkingServicesControl); + DDX_Control(pDX, IDC_NUM_WAITING_SERVICES, m_NumWaitingServicesControl); + DDX_Control(pDX, IDC_NUM_OFF_SERVICES, m_NumOffServicesControl); + DDX_Control(pDX, IDC_CURRENT_PASSWORD, m_PasswordDisplay); + DDX_Control(pDX, IDC_SERVICES_LIST, m_ServicesList); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CServicesDlg, CIdleDialog) + //{{AFX_MSG_MAP(CServicesDlg) + ON_BN_CLICKED(ID_PATCH_SERVICES, OnPatchServices) + ON_BN_CLICKED(ID_STOP_SERVICES, OnStopServices) + ON_BN_CLICKED(ID_STOP_JOBS, OnStopJobs) + ON_BN_CLICKED(ID_FILTER_BY_PASSWORD, OnFilterByPassword) + ON_BN_CLICKED(ID_FORCE_PASSWORD, OnForcePassword) + ON_BN_CLICKED(ID_COPY_TO_CLIPBOARD, OnCopyToClipboard) + ON_NOTIFY(NM_DBLCLK, IDC_SERVICES_LIST, OnDblclkServicesList) + ON_WM_SIZE() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CServicesDlg message handlers + +BOOL CServicesDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Initially, just sort by computer name. + PushSortColumn( COLUMN_COMPUTER_NAME ); + + HICON hIcon = LoadIcon( AfxGetInstanceHandle(), MAKEINTRESOURCE( IDR_MAINFRAME ) ); + SetIcon( hIcon, true ); + + m_ServicesList.SetExtendedStyle( LVS_EX_FULLROWSELECT ); + + // Setup the headers. + for ( int i=0; i < ARRAYSIZE( g_ColumnInfos ); i++ ) + { + m_ServicesList.InsertColumn( i, g_ColumnInfos[i].pText, LVCFMT_LEFT, g_ColumnInfos[i].width, i ); + } + + m_pServicesPingSocket = CreateIPSocket(); + if ( m_pServicesPingSocket ) + { + m_pServicesPingSocket->BindToAny( 0 ); + } + + m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL; + StartIdleProcessing( 100 ); // get idle messages every half second + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_DISABLED_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WORKING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_WAITING_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_NUM_OFF_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD_LABEL ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_CURRENT_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_PATCH_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_SERVICES ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_STOP_JOBS ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FORCE_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_FILTER_BY_PASSWORD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( ID_COPY_TO_CLIPBOARD ), ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_BOTTOM ); + + m_AnchorMgr.AddAnchor( this, GetDlgItem( IDC_SERVICES_LIST ), ANCHOR_LEFT, ANCHOR_TOP, ANCHOR_RIGHT, ANCHOR_BOTTOM ); + + // Unless they specify admin mode, hide all the controls that can mess with the services. + if ( !FindArg( "-Admin" ) ) + { + ::ShowWindow( ::GetDlgItem( m_hWnd, ID_PATCH_SERVICES ), SW_HIDE ); + ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_SERVICES ), SW_HIDE ); + ::ShowWindow( ::GetDlgItem( m_hWnd, ID_STOP_JOBS ), SW_HIDE ); + ::ShowWindow( ::GetDlgItem( m_hWnd, ID_FORCE_PASSWORD ), SW_HIDE ); + } + + m_NetViewThread.Init(); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + + +void CServicesDlg::BuildVMPIPingPacket( CUtlVector<char> &out, char cPacketID, unsigned char protocolVersion, bool bIgnorePassword ) +{ + out.Purge(); + out.AddToTail( protocolVersion ); + + const char *pPassword = m_Password; + if ( pPassword[0] == 0 || bIgnorePassword ) + { + // If they haven't set a password to filter by, then we want all services to report in. + out.AddToTail( VMPI_PASSWORD_OVERRIDE ); + out.AddToTail( 0 ); + } + else + { + out.AddMultipleToTail( strlen( pPassword ) + 1, pPassword ); // password. + } + + out.AddToTail( cPacketID ); +} + + +void CServicesDlg::UpdateServicesFromNetMessages() +{ + while ( 1 ) + { + char in[1024]; + CIPAddr ipFrom; + + int len = m_pServicesPingSocket->RecvFrom( in, sizeof( in ), &ipFrom ); + if ( len < 4 ) + break; + + bf_read buf( in, len ); + unsigned char protocolVersion = buf.ReadByte(); + if ( protocolVersion == 4 || protocolVersion == VMPI_PROTOCOL_VERSION ) // Protocol version 4 is almost the same. + { + int packetID = buf.ReadByte(); + if ( packetID == VMPI_PING_RESPONSE ) + { + int iState = buf.ReadByte(); + unsigned long liveTimeMS = (unsigned long)buf.ReadLong(); + + int iPort = buf.ReadLong(); + char computerName[512]; + buf.ReadString( computerName, sizeof( computerName ) ); + + char masterName[512]; + if ( buf.GetNumBitsLeft() ) + buf.ReadString( masterName, sizeof( masterName ) ); + else + masterName[0] = 0; + + unsigned long workerAppTimeMS = 0; + if ( buf.GetNumBitsLeft() ) + workerAppTimeMS = buf.ReadLong(); + + char password[512] = {0}; + if ( protocolVersion == VMPI_PROTOCOL_VERSION ) + buf.ReadString( password, sizeof( password ) ); + + char serviceVersion[32]; + if ( protocolVersion == VMPI_PROTOCOL_VERSION ) + buf.ReadString( serviceVersion, sizeof( serviceVersion ) ); + else + V_strncpy( serviceVersion, "old", sizeof( serviceVersion ) ); + + int cpuPercentage = -1; + if ( buf.GetNumBytesLeft() >= 1 ) + cpuPercentage = buf.ReadByte(); + + char exeName[512]; + if ( buf.GetNumBytesLeft() >= 1 ) + buf.ReadString( exeName, sizeof( exeName ) ); + else + V_strncpy( exeName, "-", sizeof( exeName ) ); + + short memUsage = -1; + if ( buf.GetNumBytesLeft() >= 2 ) + memUsage = buf.ReadShort(); + + char mapName[256]; + if ( buf.GetNumBytesLeft() >= 1 ) + buf.ReadString( mapName, sizeof( mapName ) ); + else + V_strncpy( mapName, "-", sizeof( mapName ) ); + + + CServiceInfo *pInfo = FindServiceByComputerName( computerName ); + if ( !pInfo ) + { + pInfo = new CServiceInfo; + m_Services.AddToTail( pInfo ); + pInfo->m_ComputerName = computerName; + + pInfo->m_pLastStatusText = NULL; + + pInfo->m_Addr.port = iPort; + pInfo->m_LastPingTimeMS = Plat_MSTime(); + pInfo->m_MasterName = "?"; + + int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); + m_ServicesList.SetItemData( iItem, (DWORD)pInfo ); + + // Update the display of # of services. + UpdateServiceCountDisplay(); + } + + pInfo->m_ProtocolVersion = protocolVersion; + V_strncpy( pInfo->m_ServiceVersion, serviceVersion, sizeof( pInfo->m_ServiceVersion ) ); + pInfo->m_Addr = ipFrom; + pInfo->m_CPUPercentage = cpuPercentage; + pInfo->m_MemUsageMB = memUsage; + pInfo->m_ExeName = exeName; + pInfo->m_MapName = mapName; + pInfo->m_MasterName = masterName; + pInfo->m_LiveTimeMS = liveTimeMS; + pInfo->m_WorkerAppTimeMS = workerAppTimeMS; + pInfo->m_iState = iState; + pInfo->m_LastPingTimeMS = Plat_MSTime(); + pInfo->m_Password = password; + + UpdateServiceInListbox( pInfo ); + } + } + } +} + + +void CServicesDlg::UpdateServicesFromNetView() +{ + CUtlVector<char*> computerNames; + m_NetViewThread.GetComputerNames( computerNames ); + + for ( int i=0; i < computerNames.Count(); i++ ) + { + CServiceInfo *pInfo = FindServiceByComputerName( computerNames[i] ); + if ( !pInfo ) + { + pInfo = new CServiceInfo; + m_Services.AddToTail( pInfo ); + pInfo->m_ComputerName = computerNames[i]; + pInfo->m_LastPingTimeMS = Plat_MSTime() - SERVICE_OFF_TIMEOUT*2; // so it's marked as "off" + pInfo->m_LastLiveTimeMS = Plat_MSTime(); + pInfo->m_LiveTimeMS = 0; + pInfo->m_WorkerAppTimeMS = 0; + pInfo->m_ProtocolVersion = 0; + pInfo->m_ServiceVersion[0] = 0; + pInfo->m_CPUPercentage = -1; + pInfo->m_MemUsageMB = -1; + + int iItem = m_ServicesList.InsertItem( COLUMN_COMPUTER_NAME, pInfo->m_ComputerName, NULL ); + m_ServicesList.SetItemData( iItem, (DWORD)pInfo ); + + // Update the display of # of services. + UpdateServiceCountDisplay(); + + UpdateServiceInListbox( pInfo ); + } + } + + computerNames.PurgeAndDeleteElements(); +} + + +void CServicesDlg::OnIdle() +{ + DWORD curTime = GetTickCount(); + + if ( !m_pServicesPingSocket ) + return; + + // Broadcast out to all the services? + if ( curTime - m_dwLastServicesPing >= SERVICES_PING_INTERVAL ) + { + m_dwLastServicesPing = curTime; + + for ( int i=VMPI_SERVICE_PORT; i <= VMPI_LAST_SERVICE_PORT; i++ ) + { + CUtlVector<char> data; + BuildVMPIPingPacket( data, VMPI_PING_REQUEST ); + m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i ); + + // Also send out a version 4 one because we understand a version 4 response. + BuildVMPIPingPacket( data, VMPI_PING_REQUEST, 4 ); + m_pServicesPingSocket->Broadcast( data.Base(), data.Count(), i ); + } + + UpdateServiceCountDisplay(); + } + + m_ServicesList.SetRedraw( false ); + + // Check for messages from services. + UpdateServicesFromNetMessages(); + + // Issue a "net view" command, parse the output, and add any computers it lists. + // This lets us figure out which PCs on the network are not running the service. + UpdateServicesFromNetView(); + + + FOR_EACH_LL( m_Services, iService ) + { + CServiceInfo *pInfo = m_Services[iService]; + if ( Plat_MSTime() - pInfo->m_LastUpdateTime > SERVICE_MAX_UPDATE_INTERVAL ) + { + UpdateServiceInListbox( pInfo ); + } + } + + if ( m_bListChanged ) + { + ResortItems(); + m_bListChanged = false; + } + + m_ServicesList.SetRedraw( true ); +} + + +CServiceInfo* CServicesDlg::FindServiceByComputerName( const char *pComputerName ) +{ + FOR_EACH_LL( m_Services, i ) + { + if ( Q_stricmp( m_Services[i]->m_ComputerName, pComputerName ) == 0 ) + return m_Services[i]; + } + return NULL; +} + + +void CServicesDlg::SendToSelectedServices( const char *pData, int len ) +{ + POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); + while ( pos ) + { + int iItem = m_ServicesList.GetNextSelectedItem( pos ); + + CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); + m_pServicesPingSocket->SendTo( &pInfo->m_Addr, pData, len ); + } +} + + +void UpdateItemText( CListCtrl &ctrl, int iItem, int iColumn, const char *pNewVal ) +{ + CString str = ctrl.GetItemText( iItem, iColumn ); + if ( V_stricmp( str, pNewVal ) != 0 ) + ctrl.SetItemText( iItem, iColumn, pNewVal ); +} + + +void CServicesDlg::UpdateServiceInListbox( CServiceInfo *pInfo ) +{ + // First, find this item in the listbox. + LVFINDINFO info; + info.flags = LVFI_PARAM; + info.lParam = (LPARAM)pInfo; + int iItem = m_ServicesList.FindItem( &info ); + if ( iItem != -1 ) + { + UpdateItemText( m_ServicesList, iItem, COLUMN_COMPUTER_NAME, pInfo->m_ComputerName ); + + const char *pText = GetStatusString( pInfo ); + UpdateItemText( m_ServicesList, iItem, COLUMN_STATUS, pText ); + + char timeStr[512]; + FormatTimeString( pInfo->m_LiveTimeMS / 1000, timeStr, sizeof( timeStr ) ); + UpdateItemText( m_ServicesList, iItem, COLUMN_RUNNING_TIME, timeStr ); + + FormatTimeString( pInfo->m_WorkerAppTimeMS / 1000, timeStr, sizeof( timeStr ) ); + UpdateItemText( m_ServicesList, iItem, COLUMN_WORKER_APP_RUNNING_TIME, timeStr ); + + UpdateItemText( m_ServicesList, iItem, COLUMN_MASTER_NAME, pInfo->m_MasterName ); + + char str[512]; + V_snprintf( str, sizeof( str ), "%d", pInfo->m_ProtocolVersion ); + UpdateItemText( m_ServicesList, iItem, COLUMN_PROTOCOL_VERSION, str ); + + UpdateItemText( m_ServicesList, iItem, COLUMN_PASSWORD, pInfo->m_Password ); + UpdateItemText( m_ServicesList, iItem, COLUMN_SERVICE_VERSION, pInfo->m_ServiceVersion ); + + if ( pInfo->m_CPUPercentage == -1 ) + V_snprintf( str, sizeof( str ), "-" ); + else if ( pInfo->m_CPUPercentage == 101 ) + V_snprintf( str, sizeof( str ), "(err)" ); + else + V_snprintf( str, sizeof( str ), "%d%%", pInfo->m_CPUPercentage ); + UpdateItemText( m_ServicesList, iItem, COLUMN_CPU_PERCENTAGE, str ); + + UpdateItemText( m_ServicesList, iItem, COLUMN_EXE_NAME, pInfo->m_ExeName ); + UpdateItemText( m_ServicesList, iItem, COLUMN_MAP_NAME, pInfo->m_MapName ); + + if ( pInfo->m_MemUsageMB == -1 ) + V_snprintf( str, sizeof( str ), "-" ); + else + V_snprintf( str, sizeof( str ), "%d", pInfo->m_MemUsageMB ); + UpdateItemText( m_ServicesList, iItem, COLUMN_MEM_USAGE, str ); + + pInfo->m_pLastStatusText = pText; + pInfo->m_LastLiveTimeMS = pInfo->m_LiveTimeMS; + pInfo->m_LastMasterName = pInfo->m_MasterName; + pInfo->m_LastUpdateTime = Plat_MSTime(); + + // Detect changes. + if ( !m_bListChanged && iItem > 0 ) + { + CServiceInfo *pPrevItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem-1 ); + if ( pPrevItem && MainSortFn( (LPARAM)pPrevItem, (LPARAM)pInfo, NULL ) > 0 ) + m_bListChanged = true; + } + + if ( !m_bListChanged && (iItem+1) < m_ServicesList.GetItemCount() ) + { + CServiceInfo *pNextItem = (CServiceInfo*)m_ServicesList.GetItemData( iItem+1 ); + if ( pNextItem && MainSortFn( (LPARAM)pInfo, (LPARAM)pNextItem, NULL ) > 0 ) + m_bListChanged = true; + } + } +} + + +void CServicesDlg::ResortItems() +{ + m_ServicesList.SortItems( MainSortFn, (LPARAM)this ); +} + + +void CServicesDlg::UpdateServiceCountDisplay() +{ + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", m_Services.Count() ); + m_NumServicesControl.SetWindowText( str ); + + // Now count the various types. + int nDisabled = 0, nWorking = 0, nWaiting = 0, nOff = 0; + FOR_EACH_LL( m_Services, i ) + { + if ( m_Services[i]->IsOff() ) + { + ++nOff; + } + else if ( m_Services[i]->m_iState == VMPI_STATE_BUSY || m_Services[i]->m_iState == VMPI_STATE_DOWNLOADING ) + { + ++nWorking; + } + else if ( m_Services[i]->m_iState == VMPI_STATE_IDLE ) + { + ++nWaiting; + } + else + { + ++nDisabled; + } + } + + Q_snprintf( str, sizeof( str ), "%d", nDisabled ); + m_NumDisabledServicesControl.SetWindowText( str ); + + Q_snprintf( str, sizeof( str ), "%d", nWorking ); + m_NumWorkingServicesControl.SetWindowText( str ); + + Q_snprintf( str, sizeof( str ), "%d", nWaiting ); + m_NumWaitingServicesControl.SetWindowText( str ); + + Q_snprintf( str, sizeof( str ), "%d", nOff ); + m_NumOffServicesControl.SetWindowText( str ); +} + + +// This monstrosity is here because of the way they bundle string resources into groups in an exe file. +// See http://support.microsoft.com/kb/q196774/. +bool FindStringResourceEx( HINSTANCE hinst, UINT uId, UINT langId, char *pStr, int outLen ) +{ + // Convert the string ID into a bundle number + bool bRet = false; + HRSRC hrsrc = FindResourceEx(hinst, RT_STRING, MAKEINTRESOURCE(uId / 16 + 1), langId); + if (hrsrc) + { + HGLOBAL hglob = LoadResource(hinst, hrsrc); + if (hglob) + { + LPCWSTR pwsz = reinterpret_cast<LPCWSTR>( LockResource(hglob) ); + if (pwsz) + { + // okay now walk the string table + for (UINT i = 0; i < (uId & 15); i++) + { + pwsz += 1 + (UINT)*pwsz; + } + + // First word in the resource is the length and the rest is the data. + int nChars = min( (int)pwsz[0], outLen-1 ); + ++pwsz; + V_wcstostr( pwsz, nChars, pStr, outLen ); + pStr[nChars] = 0; + bRet = true; + } + FreeResource(hglob); + } + } + return bRet; +} + + +int CheckServiceVersion( const char *pPatchDir, char *pServiceVersion, int maxServiceVersionLen ) +{ + char filename[MAX_PATH]; + V_ComposeFileName( pPatchDir, "vmpi_service.exe", filename, sizeof( filename ) ); + + int ret = IDCANCEL; + HINSTANCE hInst = LoadLibrary( filename ); + if ( hInst ) + { + bool bFound = FindStringResourceEx( hInst, VMPI_SERVICE_IDS_VERSION_STRING, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), pServiceVersion, maxServiceVersionLen ); + if ( bFound ) + { + ret = V_AfxMessageBox( MB_YESNOCANCEL, "Service version in %s is %s.\n\nIs this correct?", filename, pServiceVersion ); + } + else + { + V_AfxMessageBox( MB_OK, "Can't get IDS_VERSION_STRING resource from %s.", filename ); + } + + FreeLibrary( hInst ); + } + else + { + V_AfxMessageBox( MB_OK, "Can't load %s to get service version.", filename ); + } + + return ret; +} + +void CServicesDlg::OnPatchServices() +{ + // Inquire about the timeout. + CPatchTimeout dlg; + dlg.m_PatchDirectory = "\\\\fileserver\\vmpi\\testservice"; + dlg.m_VMPITransferDirectory = dlg.m_PatchDirectory; + dlg.m_bForcePatch = false; + +TryAgain:; + + if ( dlg.DoModal() == IDOK ) + { + // Launch the transfer app. + char commandLine[32 * 1024] = {0}; + + char transferExe[MAX_PATH]; + V_ComposeFileName( dlg.m_VMPITransferDirectory, "vmpi_transfer.exe", transferExe, sizeof( transferExe ) ); + if ( _access( transferExe, 0 ) != 0 ) + { + V_AfxMessageBox( MB_OK, "Can't find '%s' to run the patch.", transferExe ); + goto TryAgain; + } + + char strServiceVersion[64]; + int ret = CheckServiceVersion( dlg.m_PatchDirectory, strServiceVersion, sizeof( strServiceVersion ) ); + if ( ret == IDCANCEL ) + return; + else if ( ret == IDNO ) + goto TryAgain; + + AppendStr( commandLine, sizeof( commandLine ), "\"%s\" -PatchHost", transferExe ); + AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchVersion %s", strServiceVersion ); + AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchDirectory \"%s\"", (const char*)dlg.m_PatchDirectory ); + + if ( dlg.m_bForcePatch ) + AppendStr( commandLine, sizeof( commandLine ), " -mpi_ForcePatch" ); + + // Collect the list of addresses. + CUtlVector<CIPAddr> addrs; + POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); + while ( pos ) + { + int iItem = m_ServicesList.GetNextSelectedItem( pos ); + + CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); + if ( pInfo->m_Addr.ip[0] != 0 ) // "off" services won't have an IP + addrs.AddToTail( pInfo->m_Addr ); + } + if ( addrs.Count() == 0 ) + { + AfxMessageBox( "No workers selected, or they all are off." ); + return; + } + + AppendStr( commandLine, sizeof( commandLine ), " -mpi_PatchWorkers %d", addrs.Count() ); + for ( int i=0; i < addrs.Count(); i++ ) + { + AppendStr( commandLine, sizeof( commandLine ), " %d.%d.%d.%d", addrs[i].ip[0], addrs[i].ip[1], addrs[i].ip[2], addrs[i].ip[3] ); + } + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( CreateProcess( NULL, commandLine, + NULL, NULL, false, + 0, + NULL, + (const char *)dlg.m_PatchDirectory, + &si, + &pi ) ) + { + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + + V_AfxMessageBox( MB_OK, "Patch master successfully started.\nServices patching now.\nClose the patch master console app when finished." ); + } + else + { + V_AfxMessageBox( MB_OK, "Error starting patch master: %s", GetLastErrorString() ); + } + } +} + + +void CServicesDlg::OnStopServices() +{ + if ( MessageBox( "Warning: if you stop these services, you won't be able to control them from this application, and must restart them manually. Contine?", "Warning", MB_YESNO ) == IDYES ) + { + CUtlVector<char> data; + BuildVMPIPingPacket( data, VMPI_STOP_SERVICE ); + SendToSelectedServices( data.Base(), data.Count() ); + } +} + +void CServicesDlg::OnStopJobs() +{ + CUtlVector<char> data; + BuildVMPIPingPacket( data, VMPI_KILL_PROCESS ); + SendToSelectedServices( data.Base(), data.Count() ); +} + + +void CServicesDlg::OnFilterByPassword() +{ + CSetPasswordDlg dlg( IDD_SET_PASSWORD ); + dlg.m_Password = m_Password; + + if ( dlg.DoModal() == IDOK ) + { + m_Password = dlg.m_Password; + m_PasswordDisplay.SetWindowText( m_Password ); + + m_Services.PurgeAndDeleteElements(); + m_ServicesList.DeleteAllItems(); + + UpdateServiceCountDisplay(); + + // Re-ping everyone immediately. + m_dwLastServicesPing = GetTickCount() - SERVICES_PING_INTERVAL; + } +} + +// This sets a new password on the selected services. +void CServicesDlg::OnForcePassword() +{ + CSetPasswordDlg dlg( IDD_FORCE_PASSWORD ); + dlg.m_Password = "password"; + + if ( dlg.DoModal() == IDOK ) + { + CUtlVector<char> data; + + BuildVMPIPingPacket( data, VMPI_FORCE_PASSWORD_CHANGE, VMPI_PROTOCOL_VERSION, true ); + const char *pNewPassword = dlg.m_Password; + data.AddMultipleToTail( V_strlen( pNewPassword ) + 1, pNewPassword ); + + SendToSelectedServices( data.Base(), data.Count() ); + } +} + +void CServicesDlg::OnDblclkServicesList(NMHDR* pNMHDR, LRESULT* pResult) +{ + POSITION pos = m_ServicesList.GetFirstSelectedItemPosition(); + if ( pos ) + { + int iItem = m_ServicesList.GetNextSelectedItem( pos ); + if ( iItem != -1 ) + { + CServiceInfo *pInfo = (CServiceInfo*)m_ServicesList.GetItemData( iItem ); + if ( pInfo ) + { + // Launch vmpi_browser_job_search and have it auto-select this worker. + char cmdLine[1024]; + Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_search -SelectWorker %s", (const char*)pInfo->m_ComputerName ); + + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + if ( !CreateProcess( + NULL, + (char*)(const char*)cmdLine, + NULL, // security + NULL, + TRUE, + 0, // flags + NULL, // environment + NULL, // current directory + &si, + &pi ) ) + { + char err[512]; + Q_snprintf( err, sizeof( err ), "Can't run '%s'", (LPCTSTR)cmdLine ); + MessageBox( err, "Error", MB_OK ); + } + } + } + } + + *pResult = 0; +} + +void CServicesDlg::OnSize(UINT nType, int cx, int cy) +{ + CIdleDialog::OnSize(nType, cx, cy); + + m_AnchorMgr.UpdateAnchors( this ); +} + + +BOOL CServicesDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) +{ + NMHDR *pHdr = (NMHDR*)lParam; + if ( pHdr->idFrom == IDC_SERVICES_LIST ) + { + if ( pHdr->code == LVN_COLUMNCLICK ) + { + LPNMLISTVIEW pListView = (LPNMLISTVIEW)lParam; + + // Now sort by this column. + int iSortColumn = max( 0, min( pListView->iSubItem, (int)ARRAYSIZE( g_ColumnInfos ) - 1 ) ); + PushSortColumn( iSortColumn ); + ResortItems(); + } + } + + return CIdleDialog::OnNotify(wParam, lParam, pResult); +} + + +void CServicesDlg::BuildClipboardText( CUtlVector<char> &clipboardText ) +{ + // Add the header information. + CHeaderCtrl *pHeader = m_ServicesList.GetHeaderCtrl(); + for ( int i=0; i < pHeader->GetItemCount(); i++ ) + { + char tempBuffer[512]; + HDITEM item; + memset( &item, 0, sizeof( item ) ); + item.mask = HDI_TEXT; + item.pszText = tempBuffer; + item.cchTextMax = sizeof( tempBuffer ) - 1; + + if ( !pHeader->GetItem( i, &item ) ) + item.pszText = "<bug>"; + + clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText ); + clipboardText.AddToTail( '\t' ); + } + clipboardText.AddMultipleToTail( 2, "\r\n" ); + + // Now add each line of data. + int nItem = -1; + while ( (nItem = m_ServicesList.GetNextItem( nItem, LVNI_ALL )) != -1 ) + { + char tempBuffer[512]; + LVITEM item; + memset( &item, 0, sizeof( item ) ); + item.mask = LVIF_TEXT; + item.iItem = nItem; + item.pszText = tempBuffer; + item.cchTextMax = sizeof( tempBuffer ) - 1; + + for ( int i=0; i < pHeader->GetItemCount(); i++ ) + { + item.iSubItem = i; + if ( !m_ServicesList.GetItem( &item ) ) + { + item.pszText = "<bug>"; + } + + clipboardText.AddMultipleToTail( strlen( item.pszText ), item.pszText ); + clipboardText.AddToTail( '\t' ); + } + clipboardText.AddMultipleToTail( 2, "\r\n" ); + } + clipboardText.AddToTail( 0 ); +} + + +void CServicesDlg::OnCopyToClipboard() +{ + // Open and clear the clipboard. + if ( !OpenClipboard() ) + return; + + EmptyClipboard(); + + // Setup the clipboard text. + CUtlVector<char> clipboardText; + BuildClipboardText( clipboardText ); + + // Put the clipboard text into a global memory object. + HANDLE hMem = GlobalAlloc( GMEM_MOVEABLE, clipboardText.Count() ); + void *ptr = GlobalLock( hMem ); + memcpy( ptr, clipboardText.Base(), clipboardText.Count() ); + GlobalUnlock( hMem ); + + // Put it in the clipboard. + SetClipboardData( CF_TEXT, hMem ); + + // Cleanup. + GlobalFree( hMem ); + CloseClipboard(); +} diff --git a/utils/vmpi/vmpi_services_watch/ServicesDlg.h b/utils/vmpi/vmpi_services_watch/ServicesDlg.h new file mode 100644 index 0000000..8ff35e3 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/ServicesDlg.h @@ -0,0 +1,150 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_SERVICESDLG_H__FD755233_0A7A_4CBB_BA5E_A5D0B3B5F830__INCLUDED_) +#define AFX_SERVICESDLG_H__FD755233_0A7A_4CBB_BA5E_A5D0B3B5F830__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// ServicesDlg.h : header file +// + +#include "iphelpers.h" +#include "idle_dialog.h" +#include "utllinkedlist.h" +#include "resource.h" +#include "window_anchor_mgr.h" +#include "net_view_thread.h" +#include "vmpi_defs.h" + + +class CServiceInfo +{ +public: + + bool IsOff() const; // Returns true if the time since we've heard from this guy is too long. + + +public: + + CString m_ComputerName; + CString m_MasterName; + CString m_Password; + int m_iState; + + // Since the live time is always changing, we only update it every 10 seconds or so. + DWORD m_LiveTimeMS; // How long the service has been running (in milliseconds). + + DWORD m_WorkerAppTimeMS; // How long the worker app has been running (0 if it's not running). + + DWORD m_LastPingTimeMS; // Last time we heard from this machine. Used to detect if the service + // is off or not. + + // Used to detect if we need to re-sort the list. + const char *m_pLastStatusText; + DWORD m_LastLiveTimeMS; + CString m_LastMasterName; + + int m_CPUPercentage; + CString m_ExeName; + CString m_MapName; + int m_MemUsageMB; + + // Last time we updated the service in the listbox.. used to make sure we update its on/off status + // every once in a while. + DWORD m_LastUpdateTime; + + int m_ProtocolVersion; // i.e. the service's VMPI_SERVICE_PROTOCOL_VERSION. + char m_ServiceVersion[32]; // Version string. + + CIPAddr m_Addr; +}; + + + +///////////////////////////////////////////////////////////////////////////// +// CServicesDlg dialog + +class CServicesDlg : public CIdleDialog +{ +// Construction +public: + CServicesDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CServicesDlg) + enum { IDD = IDD_SERVICES }; + CStatic m_NumServicesControl; + CStatic m_NumDisabledServicesControl; + CStatic m_NumWorkingServicesControl; + CStatic m_NumWaitingServicesControl; + CStatic m_NumOffServicesControl; + CStatic m_PasswordDisplay; + CListCtrl m_ServicesList; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CServicesDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); + //}}AFX_VIRTUAL + +// Implementation +protected: + void BuildVMPIPingPacket( CUtlVector<char> &out, char cPacketID, unsigned char cProtocolVersion=VMPI_PROTOCOL_VERSION, bool bIgnorePassword=false ); + + virtual void OnIdle(); + + + CServiceInfo* FindServiceByComputerName( const char *pComputerName ); + void SendToSelectedServices( const char *pData, int len ); + void UpdateServiceInListbox( CServiceInfo *pInfo ); + void UpdateServiceCountDisplay(); + void ResortItems(); + + void UpdateServicesFromNetMessages(); + void UpdateServicesFromNetView(); + void BuildClipboardText( CUtlVector<char> &clipboardText ); + + + ISocket *m_pServicesPingSocket; + DWORD m_dwLastServicesPing; // Last time we pinged all the services. + + // Restricts the password so we only see a particular set of VMPI services. + CString m_Password; + + CUtlLinkedList<CServiceInfo*, int> m_Services; + CNetViewThread m_NetViewThread; + + + CWindowAnchorMgr m_AnchorMgr; + + bool m_bListChanged; // Used to detect if we need to re-sort the list. + + // Generated message map functions + //{{AFX_MSG(CServicesDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPatchServices(); + afx_msg void OnStopServices(); + afx_msg void OnStopJobs(); + afx_msg void OnFilterByPassword(); + afx_msg void OnForcePassword(); + afx_msg void OnCopyToClipboard(); + afx_msg void OnDblclkServicesList(NMHDR* pNMHDR, LRESULT* pResult); + afx_msg void OnSize(UINT nType, int cx, int cy); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SERVICESDLG_H__FD755233_0A7A_4CBB_BA5E_A5D0B3B5F830__INCLUDED_) diff --git a/utils/vmpi/vmpi_services_watch/SetPasswordDlg.cpp b/utils/vmpi/vmpi_services_watch/SetPasswordDlg.cpp new file mode 100644 index 0000000..ebe0b6a --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/SetPasswordDlg.cpp @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// SetPasswordDlg.cpp : implementation file +// + +#include "stdafx.h" +#include "SetPasswordDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg dialog + + +CSetPasswordDlg::CSetPasswordDlg(int dlgID, CWnd* pParent /*=NULL*/) + : CDialog(dlgID, pParent) +{ + //{{AFX_DATA_INIT(CSetPasswordDlg) + m_Password = _T(""); + //}}AFX_DATA_INIT +} + + +void CSetPasswordDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CSetPasswordDlg) + DDX_Text(pDX, IDC_PASSWORD, m_Password); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CSetPasswordDlg, CDialog) + //{{AFX_MSG_MAP(CSetPasswordDlg) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg message handlers diff --git a/utils/vmpi/vmpi_services_watch/SetPasswordDlg.h b/utils/vmpi/vmpi_services_watch/SetPasswordDlg.h new file mode 100644 index 0000000..f738af1 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/SetPasswordDlg.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#if !defined(AFX_SETPASSWORDDLG_H__A349B943_49B8_4C7E_863B_BF1929AD5443__INCLUDED_) +#define AFX_SETPASSWORDDLG_H__A349B943_49B8_4C7E_863B_BF1929AD5443__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// SetPasswordDlg.h : header file +// + +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +// CSetPasswordDlg dialog + +class CSetPasswordDlg : public CDialog +{ +// Construction +public: + CSetPasswordDlg(int dlgID, CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CSetPasswordDlg) + CString m_Password; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CSetPasswordDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CSetPasswordDlg) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_SETPASSWORDDLG_H__A349B943_49B8_4C7E_863B_BF1929AD5443__INCLUDED_) diff --git a/utils/vmpi/vmpi_services_watch/StdAfx.cpp b/utils/vmpi/vmpi_services_watch/StdAfx.cpp new file mode 100644 index 0000000..5ee32bc --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vmpi_browser_services.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/utils/vmpi/vmpi_services_watch/StdAfx.h b/utils/vmpi/vmpi_services_watch/StdAfx.h new file mode 100644 index 0000000..a73914f --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/StdAfx.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__BC4FB757_77BB_4E3A_8842_290307F41798__INCLUDED_) +#define AFX_STDAFX_H__BC4FB757_77BB_4E3A_8842_290307F41798__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include "tier0/wchartypes.h" +#include <afxwin.h> // MFC core and standard components +#include <afxext.h> // MFC extensions +#include <afxdisp.h> // MFC Automation classes +#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include <afxcmn.h> // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__BC4FB757_77BB_4E3A_8842_290307F41798__INCLUDED_) diff --git a/utils/vmpi/vmpi_services_watch/res/vmpi.ico b/utils/vmpi/vmpi_services_watch/res/vmpi.ico Binary files differnew file mode 100644 index 0000000..09720c0 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/res/vmpi.ico diff --git a/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico Binary files differnew file mode 100644 index 0000000..7eef0bc --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico diff --git a/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.rc2 b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.rc2 new file mode 100644 index 0000000..a322a2e --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.rc2 @@ -0,0 +1,13 @@ +// +// VMPI_BROWSER_SERVICES.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/utils/vmpi/vmpi_services_watch/resource.h b/utils/vmpi/vmpi_services_watch/resource.h new file mode 100644 index 0000000..1f172da --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/resource.h @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by vmpi_browser_services.rc +// +#define IDD_VMPI_BROWSER_SERVICES_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDD_SET_PASSWORD 130 +#define IDD_SERVICES 133 +#define IDD_TIMEOUT 134 +#define IDD_FORCE_PASSWORD 135 +#define IDC_CURRENT_PASSWORD_LABEL 1000 +#define IDC_PASSWORD 1001 +#define IDC_NUM_SERVICES_LABEL 1001 +#define IDC_NUM_DISABLED_SERVICES_LABEL 1002 +#define IDC_COMMAND_LINE 1002 +#define IDC_NUM_WORKING_SERVICES_LABEL 1003 +#define IDC_VMPI_TRANSFER_DIRECTORY 1003 +#define IDC_NUM_WAITING_SERVICES_LABEL 1004 +#define IDC_CHECK1 1004 +#define IDC_FORCE_PATCH 1004 +#define IDC_TIMEOUT_SECONDS 1005 +#define IDC_NUM_OFF_SERVICES_LABEL 1005 +#define IDC_SERVICES_LIST 1009 +#define ID_PATCH_SERVICES 1010 +#define ID_STOP_SERVICES 1011 +#define ID_STOP_JOBS 1012 +#define ID_CHANGE_PASSWORD 1013 +#define ID_FILTER_BY_PASSWORD 1013 +#define IDC_CURRENT_PASSWORD 1014 +#define ID_COPY_TO_CLIPBOARD 1015 +#define IDC_NUM_SERVICES 1016 +#define IDC_NUM_DISABLED_SERVICES 1017 +#define IDC_NUM_WORKING_SERVICES 1018 +#define IDC_NUM_WAITING_SERVICES 1019 +#define IDC_NUM_OFF_SERVICES 1020 +#define ID_FORCE_PASSWORD 1022 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/vmpi/vmpi_services_watch/vmpi_browser_services.cpp b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.cpp new file mode 100644 index 0000000..168e4ef --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.cpp @@ -0,0 +1,81 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_services.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "vmpi_browser_services.h" +#include "ServicesDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserServicesApp + +BEGIN_MESSAGE_MAP(CVMPIBrowserServicesApp, CWinApp) + //{{AFX_MSG_MAP(CVMPIBrowserServicesApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserServicesApp construction + +CVMPIBrowserServicesApp::CVMPIBrowserServicesApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CVMPIBrowserServicesApp object + +CVMPIBrowserServicesApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserServicesApp initialization + +BOOL CVMPIBrowserServicesApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#else + Enable3dControlsStatic(); // Call this when linking to MFC statically +#endif + + CServicesDlg dlg; + m_pMainWnd = &dlg; + int nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/utils/vmpi/vmpi_services_watch/vmpi_browser_services.h b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.h new file mode 100644 index 0000000..6bfe0f8 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vmpi_browser_services.h : main header file for the VMPI_BROWSER_SERVICES application +// + +#if !defined(AFX_VMPI_BROWSER_SERVICES_H__B03E2165_4E70_48AC_A991_EB0289A3471E__INCLUDED_) +#define AFX_VMPI_BROWSER_SERVICES_H__B03E2165_4E70_48AC_A991_EB0289A3471E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CVMPIBrowserServicesApp: +// See vmpi_browser_services.cpp for the implementation of this class +// + +class CVMPIBrowserServicesApp : public CWinApp +{ +public: + CVMPIBrowserServicesApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CVMPIBrowserServicesApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CVMPIBrowserServicesApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_VMPI_BROWSER_SERVICES_H__B03E2165_4E70_48AC_A991_EB0289A3471E__INCLUDED_) diff --git a/utils/vmpi/vmpi_services_watch/vmpi_browser_services.rc b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.rc new file mode 100644 index 0000000..e3e3245 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/vmpi_browser_services.rc @@ -0,0 +1,249 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\vmpi_browser_services.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON "res\\vmpi.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "FileDescription", "vmpi_browser_services MFC Application" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "vmpi_browser_services" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "vmpi_browser_services.EXE" + VALUE "ProductName", "vmpi_browser_services Application" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SERVICES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 791 + TOPMARGIN, 7 + BOTTOMMARGIN, 246 + END + + IDD_TIMEOUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 355 + TOPMARGIN, 7 + BOTTOMMARGIN, 159 + END + + IDD_SET_PASSWORD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 212 + TOPMARGIN, 7 + BOTTOMMARGIN, 91 + END + + IDD_FORCE_PASSWORD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 212 + TOPMARGIN, 7 + BOTTOMMARGIN, 71 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SERVICES DIALOGEX 0, 0, 798, 253 +STYLE DS_SETFONT | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "VMPI Services" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + CONTROL "List1",IDC_SERVICES_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | WS_BORDER | WS_TABSTOP,7,7,784,155 + DEFPUSHBUTTON "Filter By Password",ID_FILTER_BY_PASSWORD,715,166,76,14 + DEFPUSHBUTTON "Copy To Clipboard",ID_COPY_TO_CLIPBOARD,7,232,76,14 + DEFPUSHBUTTON "Force Password",ID_FORCE_PASSWORD,184,232,76,14 + DEFPUSHBUTTON "Stop Service(s)",ID_STOP_SERVICES,361,232,76,14 + DEFPUSHBUTTON "Stop Job(s)",ID_STOP_JOBS,538,232,76,14 + DEFPUSHBUTTON "Patch Services",ID_PATCH_SERVICES,715,232,76,14 + LTEXT "Filtering By Password:",IDC_CURRENT_PASSWORD_LABEL,128,169,70,8 + LTEXT "",IDC_CURRENT_PASSWORD,198,169,55,8 + LTEXT "# Machines:",IDC_NUM_SERVICES_LABEL,7,169,40,8 + LTEXT "2",IDC_NUM_SERVICES,47,169,55,8 + LTEXT "# Disabled:",IDC_NUM_DISABLED_SERVICES_LABEL,7,181,37,8 + LTEXT "2",IDC_NUM_DISABLED_SERVICES,47,181,55,8 + LTEXT "# Working:",IDC_NUM_WORKING_SERVICES_LABEL,7,192,36,8 + LTEXT "2",IDC_NUM_WORKING_SERVICES,47,192,55,8 + LTEXT "# Waiting:",IDC_NUM_WAITING_SERVICES_LABEL,7,203,34,8 + LTEXT "2",IDC_NUM_WAITING_SERVICES,47,203,55,8 + LTEXT "# Off:",IDC_NUM_OFF_SERVICES_LABEL,7,214,19,8 + LTEXT "2",IDC_NUM_OFF_SERVICES,47,214,55,8 +END + +IDD_TIMEOUT DIALOGEX 0, 0, 362, 166 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Patch" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_COMMAND_LINE,93,78,262,14,ES_AUTOHSCROLL + EDITTEXT IDC_VMPI_TRANSFER_DIRECTORY,93,98,262,14,ES_AUTOHSCROLL + CONTROL "Force Patch (otherwise, the service will ignore the patch if the new version is not higher)",IDC_FORCE_PATCH, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,121,291,10 + DEFPUSHBUTTON "OK",IDOK,123,145,50,14 + PUSHBUTTON "Cancel",IDCANCEL,189,145,50,14 + LTEXT "This will setup all the selected services to be patched.",IDC_STATIC,7,7,348,11 + LTEXT "Patch Directory:",IDC_STATIC,7,81,52,8 + LTEXT "First, it will run vmpi_transfer from the VMPI_Transfer directory. This is kept separate because the vmpi_transfer.exe in that directory might be a different version than the new one in the patch directory.",IDC_STATIC,7,21,348,18 + LTEXT "VMPI_Transfer Directory:",IDC_STATIC,7,101,80,8 + LTEXT "The selected services will download the patch files, then run vmpi_service_install.",IDC_STATIC,7,42,348,13 +END + +IDD_SET_PASSWORD DIALOGEX 0, 0, 219, 98 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Filter By Password" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_PASSWORD,7,56,205,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,77,50,14 + PUSHBUTTON "Cancel",IDCANCEL,162,77,50,14 + LTEXT "This will cause the services browser to only show services that are using the specified password.\n\nIf you enter a blank password, then all services will be shown regardless of their password.",IDC_CURRENT_PASSWORD_LABEL,7,7,195,46 +END + +IDD_FORCE_PASSWORD DIALOGEX 0, 0, 219, 78 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Force Password" +FONT 8, "MS Sans Serif", 0, 0, 0x0 +BEGIN + EDITTEXT IDC_PASSWORD,7,38,205,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,7,57,50,14 + PUSHBUTTON "Cancel",IDCANCEL,162,57,50,14 + LTEXT "This will set the specified password on all selected services. VMPI jobs must run with -mpi_pw <password> to use those services.",IDC_CURRENT_PASSWORD_LABEL,7,7,195,27 +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\vmpi_browser_services.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/vmpi/vmpi_services_watch/vmpi_services_watch.vpc b/utils/vmpi/vmpi_services_watch/vmpi_services_watch.vpc new file mode 100644 index 0000000..a58e2a6 --- /dev/null +++ b/utils/vmpi/vmpi_services_watch/vmpi_services_watch.vpc @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// vmpi_services_watch.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vmpi_services_watch" + +$Include "$SRCDIR\vpc_scripts\source_exe_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;.\;..\;..\..\common" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;WINVER=0x501;NO_WARN_MBCS_MFC_DEPRECATION" + $EnableC++Exceptions "Yes (/EHsc)" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" + } +} + +$Project "vmpi_services_watch" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + + $File "..\idle_dialog.cpp" + $File "..\net_view_thread.cpp" + $File "PatchTimeout.cpp" + $File "ServicesDlg.cpp" + $File "SetPasswordDlg.cpp" + $File "..\vmpi_browser_helpers.cpp" + $File "vmpi_browser_services.cpp" + $File "vmpi_browser_services.rc" + $File "..\win_idle.cpp" + $File "..\window_anchor_mgr.cpp" + $File "StdAfx.cpp" + } + + $Folder "Header Files" + { + $File "..\idle_dialog.h" + $File "..\net_view_thread.h" + $File "PatchTimeout.h" + $File "Resource.h" + $File "ServicesDlg.h" + $File "SetPasswordDlg.h" + $File "StdAfx.h" + $File "vmpi_browser_services.h" + $File "..\vmpi_defs.h" + $File "..\win_idle.h" + $File "..\window_anchor_mgr.h" + } + + $Folder "Resources" + { + $File "res\vmpi_browser_services.ico" + $File "res\vmpi_browser_services.rc2" + } + + $Folder "Link Libraries" + { + $Lib vmpi + } +} diff --git a/utils/vmpi/vmpi_transfer/vmpi_transfer.cpp b/utils/vmpi/vmpi_transfer/vmpi_transfer.cpp new file mode 100644 index 0000000..fadf3fd --- /dev/null +++ b/utils/vmpi/vmpi_transfer/vmpi_transfer.cpp @@ -0,0 +1,172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include <windows.h> +#include "vmpi.h" +#include "vmpi_transfer.h" +#include "cmdlib.h" +#include "tier0/icommandline.h" +#include "vmpi_tools_shared.h" +#include "tools_minidump.h" +#include <conio.h> + + +void MyDisconnectHandler( int procID, const char *pReason ) +{ + Error( "Premature disconnect.\n" ); +} + +void DownloadFile( const char *pCachePath, const char *pRemoteFileBase, const char *pFilename ) +{ + // Setup local and remote filenames. + char remoteFilename[MAX_PATH]; + char localFilename[MAX_PATH]; + V_ComposeFileName( pRemoteFileBase, pFilename, remoteFilename, sizeof( remoteFilename ) ); + V_ComposeFileName( pCachePath, pFilename, localFilename, sizeof( localFilename ) ); + + // Read the file in. + FileHandle_t fpSrc = g_pFileSystem->Open( remoteFilename, "rb" ); + if ( fpSrc == FILESYSTEM_INVALID_HANDLE ) + { + Error( "Unable to open %s on master.\n", remoteFilename ); + } + + unsigned int fileSize = g_pFileSystem->Size( fpSrc ); + CUtlVector<char> data; + data.SetSize( fileSize ); + g_pFileSystem->Read( data.Base(), fileSize, fpSrc ); + g_pFileSystem->Close( fpSrc ); + + // Now write the file to disk. + FILE *fpDest = fopen( localFilename, "wb" ); + if ( !fpDest ) + { + Error( "Can't open %s for writing.\n", localFilename ); + } + fwrite( data.Base(), 1, data.Count(), fpDest ); + fclose( fpDest ); + +Warning( "Got file: %s\n", pFilename ); +} + +#if 0 +SpewRetval_t MySpewFunc( SpewType_t spewType, const tchar *pMsg ) +{ + printf( "%s", pMsg ); + if ( spewType == SPEW_ERROR ) + { + printf( "\nWaiting for keypress to quit...\n" ); + getch(); + TerminateProcess( GetCurrentProcess(), 1 ); + } + + return SPEW_CONTINUE; +} +#endif + +int RunVMPITransferWorker( int argc, char **argv ) +{ + if ( !VMPI_Init( argc, argv, NULL, MyDisconnectHandler, VMPI_RUN_NETWORKED, true ) ) + { + return 1; + } + + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + + if ( !FileSystem_Init( ".", 0, FS_INIT_COMPATIBILITY_MODE ) ) + return 1; + + ICommandLine *pCommandLine = CommandLine(); + + // Look for the cache path and file base args. + const char *pCachePath = pCommandLine->ParmValue( "-CachePath", (char*)NULL ); + if ( !pCachePath ) + Error( "No -CachePath specified." ); + + const char *pRemoteFileBase = pCommandLine->ParmValue( "-mpi_filebase", (char*)NULL ); + if ( !pRemoteFileBase ) + Error( "No -mpi_filebase specified." ); + + // Now just ask the master for each file. + for ( int i=1; i < pCommandLine->ParmCount()-1; i++ ) + { + const char *pParm = pCommandLine->GetParm( i ); + if ( V_stricmp( pParm, "-mpi_file" ) == 0 ) + { + const char *pNextParm = pCommandLine->GetParm( i+1 ); + DownloadFile( pCachePath, pRemoteFileBase, pNextParm ); + ++i; + } + } + + // Ok, we're done. Write the status file so the service knows all the files are ready to go. + char statusFilename[MAX_PATH]; + V_ComposeFileName( pCachePath, "ReadyToGo.txt", statusFilename, sizeof( statusFilename ) ); + FILE *fp = fopen( statusFilename, "wb" ); + fclose( fp ); + + return 0; +} + + +// In this mode, we just initialize VMPI appropriately, and it'll host out the specified files. +// The command line to vmpi_transfer is -PatchHost -PatchDirectory <directory> +// Sample: vmpi_transfer -PatchHost -mpi_PatchDirectory \\fileserver\vmpi\patch1 -mpi_PatchWorkers <count> <ip1> <ip2>... +// Then it'll tell those workers to connect and it'll send them the files in the specified directory. +int RunVMPITransferMaster( int argc, char **argv ) +{ + // Since we didn't use -mpi_worker on the command line, VMPI will init as the master. + // We put a special character in front of the dependency filename, which tells it the dependencies + // consist of every file in the specified directory. + VMPI_Init_PatchMaster( argc, argv ); + + if ( !FileSystem_Init( ".", 0, FS_INIT_COMPATIBILITY_MODE ) ) + return 1; + + Msg( "Hosting patch files. Press ESC to exit. " ); + while ( 1 ) + { + VMPI_DispatchNextMessage( 100 ); + if ( kbhit() ) + { + if ( getch() == 27 ) + break; + } + } + + return 0; +} + + +// --------------------------------------------------------------------------------- // +// Purpose: This app is used by vmpi_service to acquire the executables for +// a VMPI job. When the service is asked to join a job, it runs this program +// to connect to the VMPI master and download all the exes for the job. +// +// This app is ALSO used to do patches. vmpi_browser_services runs it with a list +// of machines it wants to patch. Then it runs as the VMPI master and instead of +// broadcasting its presence, it sends messages to the specific list of machines. +// --------------------------------------------------------------------------------- // +int main( int argc, char **argv ) +{ + InstallSpewFunction(); + CommandLine()->CreateCmdLine( argc, argv ); + + int ret; + if ( CommandLine()->FindParm( "-PatchHost" ) == 0 ) + { + ret = RunVMPITransferWorker( argc, argv ); + } + else + { + ret = RunVMPITransferMaster( argc, argv ); + } + + CmdLib_Cleanup(); + return ret; +} + + diff --git a/utils/vmpi/vmpi_transfer/vmpi_transfer.h b/utils/vmpi/vmpi_transfer/vmpi_transfer.h new file mode 100644 index 0000000..95c8b67 --- /dev/null +++ b/utils/vmpi/vmpi_transfer/vmpi_transfer.h @@ -0,0 +1,14 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef VMPI_TRANSFER_H +#define VMPI_TRANSFER_H +#ifdef _WIN32 +#pragma once +#endif + + +#endif // VMPI_TRANSFER_H diff --git a/utils/vmpi/vmpi_transfer/vmpi_transfer.vpc b/utils/vmpi/vmpi_transfer/vmpi_transfer.vpc new file mode 100644 index 0000000..baa9cc6 --- /dev/null +++ b/utils/vmpi/vmpi_transfer/vmpi_transfer.vpc @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// VMPI_TRANSFER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vmpi_transfer" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\..\common,.." + $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" + } +} + +$Project "vmpi_transfer" +{ + $Folder "Source Files" + { + $File "..\..\common\vmpi_tools_shared.cpp" + $File "..\..\common\vmpi_tools_shared.h" + $File "vmpi_transfer.cpp" + + $Folder "Common Files" + { + $File "..\..\common\threads.cpp" + $File "..\..\common\pacifier.cpp" + $File "..\..\common\cmdlib.cpp" + $File "..\..\common\tools_minidump.cpp" + $File "..\..\common\tools_minidump.h" + } + + $Folder "Public Files" + { + $File "$SRCDIR\public\filesystem_helpers.cpp" + } + } + + $Folder "Header Files" + { + $File "vmpi_transfer.h" + + $Folder "Common Header Files" + { + $File "..\..\common\cmdlib.h" + $File "..\iphelpers.h" + $File "..\messbuf.h" + $File "..\mysql_wrapper.h" + $File "..\threadhelpers.h" + $File "..\vmpi_defs.h" + $File "..\vmpi_dispatch.h" + $File "..\vmpi_distribute_work.h" + $File "..\vmpi_filesystem.h" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\mathlib\ANORMS.H" + $File "$SRCDIR\public\basehandle.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier1\bitbuf.h" + $File "$SRCDIR\public\bitvec.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\builddisp.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\tier1\characterset.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier1\checksum_md5.h" + $File "$SRCDIR\public\ChunkFile.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\mathlib\compressed_vector.h" + $File "$SRCDIR\public\const.h" + $File "$SRCDIR\public\coordsize.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\disp_common.h" + $File "$SRCDIR\public\disp_powerinfo.h" + $File "$SRCDIR\public\disp_vertindex.h" + $File "$SRCDIR\public\DispColl_Common.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "$SRCDIR\public\gametrace.h" + $File "$SRCDIR\public\mathlib\halton.h" + $File "$SRCDIR\public\materialsystem\hardwareverts.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\ihandleentity.h" + $File "$SRCDIR\public\materialsystem\imaterial.h" + $File "$SRCDIR\public\materialsystem\imaterialsystem.h" + $File "$SRCDIR\public\materialsystem\imaterialvar.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\iscratchpad3d.h" + $File "$SRCDIR\public\ivraddll.h" + $File "$SRCDIR\public\materialsystem\materialsystem_config.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\optimize.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "$SRCDIR\public\ScratchPad3D.h" + $File "$SRCDIR\public\ScratchPadUtils.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\tokenreader.h" + $File "$SRCDIR\public\trace.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utldict.h" + $File "$SRCDIR\public\tier1\utlhash.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "..\vmpi.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\tier0\vprof.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\vtf\vtf.h" + $File "$SRCDIR\public\wadtypes.h" + $File "$SRCDIR\public\worldsize.h" + } + } + + $Folder "Link Libraries" + { + $Lib tier2 + $Lib vmpi + } +} diff --git a/utils/vmpi/win_idle.cpp b/utils/vmpi/win_idle.cpp new file mode 100644 index 0000000..5b453bc --- /dev/null +++ b/utils/vmpi/win_idle.cpp @@ -0,0 +1,122 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// Class for sending idle messages to a window + +#include "stdafx.h" +#include "win_idle.h" + +// Stub function to get into the object's main thread loop +DWORD WINAPI CWinIdle::ThreadStub(LPVOID pIdle) +{ + return ((CWinIdle *)pIdle)->RunIdle(); +} + +CWinIdle::CWinIdle() : + m_hIdleThread(NULL), + m_hIdleEvent(NULL), + m_hStopEvent(NULL), + m_hWnd(0), + m_uMsg(0), + m_dwDelay(0) +{ +} + +CWinIdle::~CWinIdle() +{ + EndIdle(); +} + +DWORD CWinIdle::RunIdle() +{ + // Set up an event list + HANDLE aEvents[2]; + + aEvents[0] = m_hStopEvent; + aEvents[1] = m_hIdleEvent; + + // Wait for a stop or idle event + while (WaitForMultipleObjects(2, aEvents, FALSE, INFINITE) != WAIT_OBJECT_0) + { + // Send an idle message + PostMessage(m_hWnd, m_uMsg, m_wParam, m_lParam); + // Wait for a bit... + Sleep(m_dwDelay); + } + + return 0; +} + +BOOL CWinIdle::StartIdle(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam, DWORD dwDelay) +{ + // Make sure it's not already running + if (m_hIdleThread) + return FALSE; + + // Make sure they send in a valid handle.. + if (!hWnd) + return FALSE; + + // Create the events + m_hIdleEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Make sure the events got created + if ((!m_hIdleEvent) || (!m_hStopEvent)) + return FALSE; + + // Create the thread + DWORD dwThreadID; + m_hIdleThread = CreateThread(NULL, 0, CWinIdle::ThreadStub, (void *)this, 0, &dwThreadID); + + if (m_hIdleThread) + { + SetThreadPriority(m_hIdleThread, THREAD_PRIORITY_IDLE); + + m_hWnd = hWnd; + m_uMsg = uMessage; + m_wParam = wParam; + m_lParam = lParam; + + m_dwDelay = dwDelay; + } + + return m_hIdleThread != 0; +} + +BOOL CWinIdle::EndIdle() +{ + // Make sure it's running + if (!m_hIdleThread) + return FALSE; + + // Stop the idle thread + SetEvent(m_hStopEvent); + WaitForSingleObject(m_hIdleThread, INFINITE); + CloseHandle(m_hIdleThread); + + // Get rid of the event objects + CloseHandle(m_hIdleEvent); + CloseHandle(m_hStopEvent); + + // Set everything back to 0 + m_hIdleEvent = 0; + m_hStopEvent = 0; + m_hIdleThread = 0; + + return TRUE; +} + +void CWinIdle::NextIdle() +{ + // Make sure the thread's running + if (!m_hIdleThread) + return; + + // Signal an idle message + SetEvent(m_hIdleEvent); +} diff --git a/utils/vmpi/win_idle.h b/utils/vmpi/win_idle.h new file mode 100644 index 0000000..6e2e3f3 --- /dev/null +++ b/utils/vmpi/win_idle.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// WinIdle.h - Defines a class for sending idle messages to a window from a secondary thread + +#ifndef __WINIDLE_H__ +#define __WINIDLE_H__ + + +class CWinIdle +{ +protected: + HANDLE m_hIdleEvent, m_hStopEvent; + + HWND m_hWnd; + UINT m_uMsg; + WPARAM m_wParam; + LPARAM m_lParam; + + DWORD m_dwDelay; + + HANDLE m_hIdleThread; + + // The thread calling stub + static DWORD WINAPI ThreadStub(LPVOID pIdle); + // The actual idle loop + virtual DWORD RunIdle(); + +public: + CWinIdle(); + virtual ~CWinIdle(); + + inline DWORD GetDelay() {return m_dwDelay;} + inline void SetDelay(DWORD delay) {m_dwDelay = delay;} + + // Member access + virtual HANDLE GetThreadHandle() const { return m_hIdleThread; }; + + // Start idling, and define the message and window to use + // Returns TRUE on success + virtual BOOL StartIdle(HWND hWnd, UINT uMessage, WPARAM wParam = 0, LPARAM lParam = 0, DWORD dwDelay = 0); + // Stop idling + // Returns TRUE on success + virtual BOOL EndIdle(); + // Notify the idle process that the message was received. + // Note : If this function is not called, the idle thread will not send any messages + virtual void NextIdle(); +}; + + +// Used to slow down the idle thread while dialogs are up. +class IdleChanger +{ +public: + IdleChanger(CWinIdle *pIdle, DWORD msDelay) + { + m_pIdle = pIdle; + m_OldDelay = pIdle->GetDelay(); + pIdle->SetDelay(msDelay); + } + + ~IdleChanger() + { + m_pIdle->SetDelay(m_OldDelay); + } + + CWinIdle *m_pIdle; + DWORD m_OldDelay; +}; + + + +#endif //__WINIDLE_H__ + diff --git a/utils/vmpi/window_anchor_mgr.cpp b/utils/vmpi/window_anchor_mgr.cpp new file mode 100644 index 0000000..2e76daf --- /dev/null +++ b/utils/vmpi/window_anchor_mgr.cpp @@ -0,0 +1,94 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "window_anchor_mgr.h" + + +// ------------------------------------------------------------------------------------ // +// CWindowAnchor. +// ------------------------------------------------------------------------------------ // + +bool CWindowAnchor::Init( CWnd *pParentWnd, CWnd *pChildWnd, int aLeft, int aTop, int aRight, int aBottom ) +{ + m_pWnd = pChildWnd; + + m_aLeft = aLeft; + m_aTop = aTop; + m_aRight = aRight; + m_aBottom = aBottom; + + if ( m_pWnd && pParentWnd ) + { + pParentWnd->GetWindowRect( m_ParentRect ); + pChildWnd->GetWindowRect( m_Rect ); + return true; + } + else + { + return false; + } +} + + +void AnchorElement( long *pOut, CRect &rcParent, int startElement, CRect &rcParentStart, int flags ) +{ + if ( flags == ANCHOR_LEFT ) + *pOut = rcParent.left + ( startElement - rcParentStart.left ); + else if ( flags == ANCHOR_TOP ) + *pOut = rcParent.top + ( startElement - rcParentStart.top ); + else if ( flags == ANCHOR_RIGHT ) + *pOut = rcParent.right - ( rcParentStart.right - startElement ); + else if ( flags == ANCHOR_BOTTOM ) + *pOut = rcParent.bottom - ( rcParentStart.bottom - startElement ); + else if ( flags == ANCHOR_WIDTH_PERCENT ) + *pOut = rcParent.left + (rcParent.Width() * ( startElement - rcParentStart.left )) / rcParentStart.Width(); + else if ( flags == ANCHOR_HEIGHT_PERCENT ) + *pOut = rcParent.top + (rcParent.Height() * ( startElement - rcParentStart.top )) / rcParentStart.Height(); +} + + +void CWindowAnchor::Update( CWnd *pParentWnd ) +{ + if ( !m_pWnd ) + return; + + CRect rcParent; + pParentWnd->GetWindowRect( rcParent ); + + CRect rcNew; + AnchorElement( &rcNew.left, rcParent, m_Rect.left, m_ParentRect, m_aLeft ); + AnchorElement( &rcNew.top, rcParent, m_Rect.top, m_ParentRect, m_aTop ); + AnchorElement( &rcNew.right, rcParent, m_Rect.right, m_ParentRect, m_aRight ); + AnchorElement( &rcNew.bottom, rcParent, m_Rect.bottom, m_ParentRect, m_aBottom ); + + pParentWnd->ScreenToClient( rcNew ); + m_pWnd->SetWindowPos( NULL, rcNew.left, rcNew.top, rcNew.Width(), rcNew.Height(), SWP_NOZORDER ); + m_pWnd->InvalidateRect( NULL ); +} + + + +// ------------------------------------------------------------------------------------ // +// CWindowAnchorMgr. +// ------------------------------------------------------------------------------------ // + +bool CWindowAnchorMgr::AddAnchor( CWnd *pParentWnd, CWnd *pChildWnd, int aLeft, int aTop, int aRight, int aBottom ) +{ + int index = m_Anchors.AddToTail(); + return m_Anchors[index].Init( pParentWnd, pChildWnd, aLeft, aTop, aRight, aBottom ); +} + + +void CWindowAnchorMgr::UpdateAnchors( CWnd *pParentWnd ) +{ + FOR_EACH_LL( m_Anchors, i ) + { + m_Anchors[i].Update( pParentWnd ); + } +} + diff --git a/utils/vmpi/window_anchor_mgr.h b/utils/vmpi/window_anchor_mgr.h new file mode 100644 index 0000000..5d5014c --- /dev/null +++ b/utils/vmpi/window_anchor_mgr.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WINDOW_ANCHOR_MGR_H +#define WINDOW_ANCHOR_MGR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utllinkedlist.h" + + +enum +{ + ANCHOR_LEFT = 1, + ANCHOR_RIGHT, + ANCHOR_TOP, + ANCHOR_BOTTOM, + ANCHOR_WIDTH_PERCENT, + ANCHOR_HEIGHT_PERCENT +}; + + +class CWindowAnchor +{ +public: + + bool Init( CWnd *pParentWnd, CWnd *pChildWnd, int aLeft, int aTop, int aRight, int aBottom ); + void Update( CWnd *pParentWnd ); + + +private: + CWnd *m_pWnd; + CRect m_Rect; // The rectangle in client coordinates of the parent. + CRect m_ParentRect; + + int m_aLeft, m_aTop, m_aRight, m_aBottom; +}; + + +class CWindowAnchorMgr +{ +public: + + bool AddAnchor( CWnd *pParentWnd, CWnd *pChildWnd, int aLeft, int aTop, int aRight, int aBottom ); + void UpdateAnchors( CWnd *pParentWnd ); + + +private: + CUtlLinkedList<CWindowAnchor,int> m_Anchors; +}; + + +#endif // WINDOW_ANCHOR_MGR_H |