summaryrefslogtreecommitdiff
path: root/utils/vmpi
diff options
context:
space:
mode:
Diffstat (limited to 'utils/vmpi')
-rw-r--r--utils/vmpi/IThreadedTCPSocket.h173
-rw-r--r--utils/vmpi/ThreadedTCPSocket.cpp1085
-rw-r--r--utils/vmpi/ThreadedTCPSocketEmu.cpp344
-rw-r--r--utils/vmpi/ThreadedTCPSocketEmu.h26
-rw-r--r--utils/vmpi/WaitAndRestart/StdAfx.cpp15
-rw-r--r--utils/vmpi/WaitAndRestart/StdAfx.h32
-rw-r--r--utils/vmpi/WaitAndRestart/WaitAndRestart.cpp166
-rw-r--r--utils/vmpi/WaitAndRestart/waitandrestart.vpc32
-rw-r--r--utils/vmpi/ZLib.libbin0 -> 83274 bytes
-rw-r--r--utils/vmpi/ZLib/deflate.h318
-rw-r--r--utils/vmpi/ZLib/infblock.h39
-rw-r--r--utils/vmpi/ZLib/infcodes.h27
-rw-r--r--utils/vmpi/ZLib/inffast.h17
-rw-r--r--utils/vmpi/ZLib/inffixed.h151
-rw-r--r--utils/vmpi/ZLib/inftrees.h58
-rw-r--r--utils/vmpi/ZLib/infutil.h98
-rw-r--r--utils/vmpi/ZLib/trees.h128
-rw-r--r--utils/vmpi/ZLib/zconf.h279
-rw-r--r--utils/vmpi/ZLib/zlib.h893
-rw-r--r--utils/vmpi/ZLib/zutil.h220
-rw-r--r--utils/vmpi/ichannel.h49
-rw-r--r--utils/vmpi/idle_dialog.cpp46
-rw-r--r--utils/vmpi/idle_dialog.h48
-rw-r--r--utils/vmpi/imysqlwrapper.h115
-rw-r--r--utils/vmpi/iphelpers.cpp610
-rw-r--r--utils/vmpi/iphelpers.h162
-rw-r--r--utils/vmpi/loopback_channel.cpp98
-rw-r--r--utils/vmpi/loopback_channel.h22
-rw-r--r--utils/vmpi/messagemgr.cpp300
-rw-r--r--utils/vmpi/messagemgr.h39
-rw-r--r--utils/vmpi/messbuf.cpp279
-rw-r--r--utils/vmpi/messbuf.h52
-rw-r--r--utils/vmpi/mysql_async.cpp275
-rw-r--r--utils/vmpi/mysql_async.h56
-rw-r--r--utils/vmpi/mysql_wrapper.h104
-rw-r--r--utils/vmpi/net_view_thread.cpp208
-rw-r--r--utils/vmpi/net_view_thread.h45
-rw-r--r--utils/vmpi/tcpsocket.cpp1178
-rw-r--r--utils/vmpi/tcpsocket.h84
-rw-r--r--utils/vmpi/tcpsocket_helpers.cpp48
-rw-r--r--utils/vmpi/tcpsocket_helpers.h21
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageRecvMgr.h22
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatch.cpp77
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatch.h56
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatch.rc194
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatch.vcproj282
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatchDlg.cpp324
-rw-r--r--utils/vmpi/testapps/MessageWatch/MessageWatchDlg.h100
-rw-r--r--utils/vmpi/testapps/MessageWatch/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/MessageWatch/StdAfx.h33
-rw-r--r--utils/vmpi/testapps/MessageWatch/res/MessageWatch.icobin0 -> 766 bytes
-rw-r--r--utils/vmpi/testapps/MessageWatch/res/MessageWatch.rc213
-rw-r--r--utils/vmpi/testapps/MessageWatch/resource.h29
-rw-r--r--utils/vmpi/testapps/MessageWatch/win_idle.cpp123
-rw-r--r--utils/vmpi/testapps/MessageWatch/win_idle.h78
-rw-r--r--utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/ThreadedTCPSocketTest/StdAfx.h31
-rw-r--r--utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.cpp198
-rw-r--r--utils/vmpi/testapps/ThreadedTCPSocketTest/ThreadedTCPSocketTest.vcproj253
-rw-r--r--utils/vmpi/testapps/pingpong/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/pingpong/StdAfx.h29
-rw-r--r--utils/vmpi/testapps/pingpong/pingpong.cpp308
-rw-r--r--utils/vmpi/testapps/pingpong/pingpong.vcproj245
-rw-r--r--utils/vmpi/testapps/socket_stresstest/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/socket_stresstest/StdAfx.h33
-rw-r--r--utils/vmpi/testapps/socket_stresstest/socket_stresstest.cpp274
-rw-r--r--utils/vmpi/testapps/socket_stresstest/socket_stresstest.vcproj210
-rw-r--r--utils/vmpi/testapps/vmpi_launch/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/vmpi_launch/StdAfx.h30
-rw-r--r--utils/vmpi/testapps/vmpi_launch/vmpi_launch.cpp256
-rw-r--r--utils/vmpi/testapps/vmpi_launch/vmpi_launch.vpc38
-rw-r--r--utils/vmpi/testapps/vmpi_ping/StdAfx.cpp15
-rw-r--r--utils/vmpi/testapps/vmpi_ping/StdAfx.h30
-rw-r--r--utils/vmpi/testapps/vmpi_ping/vmpi_ping.cpp196
-rw-r--r--utils/vmpi/testapps/vmpi_ping/vmpi_ping.vcproj249
-rw-r--r--utils/vmpi/threadhelpers.cpp155
-rw-r--r--utils/vmpi/threadhelpers.h110
-rw-r--r--utils/vmpi/vmpi.cpp2478
-rw-r--r--utils/vmpi/vmpi.h217
-rw-r--r--utils/vmpi/vmpi.vpc62
-rw-r--r--utils/vmpi/vmpi_browser_helpers.cpp43
-rw-r--r--utils/vmpi/vmpi_browser_helpers.h18
-rw-r--r--utils/vmpi/vmpi_defs.h147
-rw-r--r--utils/vmpi/vmpi_dispatch.cpp13
-rw-r--r--utils/vmpi/vmpi_dispatch.h15
-rw-r--r--utils/vmpi/vmpi_distribute_tracker.cpp579
-rw-r--r--utils/vmpi/vmpi_distribute_tracker.h32
-rw-r--r--utils/vmpi/vmpi_distribute_work.cpp628
-rw-r--r--utils/vmpi/vmpi_distribute_work.h89
-rw-r--r--utils/vmpi/vmpi_distribute_work_default.cpp602
-rw-r--r--utils/vmpi/vmpi_distribute_work_internal.h251
-rw-r--r--utils/vmpi/vmpi_distribute_work_sdk.cpp699
-rw-r--r--utils/vmpi/vmpi_filesystem.cpp366
-rw-r--r--utils/vmpi/vmpi_filesystem.h53
-rw-r--r--utils/vmpi/vmpi_filesystem_internal.h129
-rw-r--r--utils/vmpi/vmpi_filesystem_master.cpp1606
-rw-r--r--utils/vmpi/vmpi_filesystem_worker.cpp815
-rw-r--r--utils/vmpi/vmpi_job_search/JobSearchDlg.cpp473
-rw-r--r--utils/vmpi/vmpi_job_search/JobSearchDlg.h86
-rw-r--r--utils/vmpi/vmpi_job_search/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_job_search/StdAfx.h36
-rw-r--r--utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.icobin0 -> 1078 bytes
-rw-r--r--utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.rc213
-rw-r--r--utils/vmpi/vmpi_job_search/resource.h32
-rw-r--r--utils/vmpi/vmpi_job_search/vmpi_browser_job_search.cpp75
-rw-r--r--utils/vmpi/vmpi_job_search/vmpi_browser_job_search.h56
-rw-r--r--utils/vmpi/vmpi_job_search/vmpi_browser_job_search.rc184
-rw-r--r--utils/vmpi/vmpi_job_search/vmpi_job_search.vpc97
-rw-r--r--utils/vmpi/vmpi_job_watch/GraphControl.cpp246
-rw-r--r--utils/vmpi/vmpi_job_watch/GraphControl.h86
-rw-r--r--utils/vmpi/vmpi_job_watch/JobWatchDlg.cpp648
-rw-r--r--utils/vmpi/vmpi_job_watch/JobWatchDlg.h134
-rw-r--r--utils/vmpi/vmpi_job_watch/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_job_watch/StdAfx.h36
-rw-r--r--utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.icobin0 -> 1078 bytes
-rw-r--r--utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.rc213
-rw-r--r--utils/vmpi/vmpi_job_watch/resource.h32
-rw-r--r--utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.cpp75
-rw-r--r--utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.h56
-rw-r--r--utils/vmpi/vmpi_job_watch/vmpi_browser_job_watch.rc183
-rw-r--r--utils/vmpi/vmpi_job_watch/vmpi_job_watch.vpc80
-rw-r--r--utils/vmpi/vmpi_parameters.h31
-rw-r--r--utils/vmpi/vmpi_service/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_service/StdAfx.h35
-rw-r--r--utils/vmpi/vmpi_service/perf_counters.cpp500
-rw-r--r--utils/vmpi/vmpi_service/perf_counters.h28
-rw-r--r--utils/vmpi/vmpi_service/resource.h18
-rw-r--r--utils/vmpi/vmpi_service/service_conn_mgr.cpp234
-rw-r--r--utils/vmpi/vmpi_service/service_conn_mgr.h92
-rw-r--r--utils/vmpi/vmpi_service/service_helpers.cpp181
-rw-r--r--utils/vmpi/vmpi_service/service_helpers.h43
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.cpp1710
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.h19
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.rc74
-rw-r--r--utils/vmpi/vmpi_service/vmpi_service.vpc60
-rw-r--r--utils/vmpi/vmpi_service_install/ServiceInstallDlg.cpp1043
-rw-r--r--utils/vmpi/vmpi_service_install/ServiceInstallDlg.h72
-rw-r--r--utils/vmpi/vmpi_service_install/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_service_install/StdAfx.h41
-rw-r--r--utils/vmpi/vmpi_service_install/res/vmpi.icobin0 -> 3310 bytes
-rw-r--r--utils/vmpi/vmpi_service_install/res/vmpi_service_install.icobin0 -> 1078 bytes
-rw-r--r--utils/vmpi/vmpi_service_install/res/vmpi_service_install.rc213
-rw-r--r--utils/vmpi/vmpi_service_install/resource.h27
-rw-r--r--utils/vmpi/vmpi_service_install/vmpi_service_install.cpp75
-rw-r--r--utils/vmpi/vmpi_service_install/vmpi_service_install.h56
-rw-r--r--utils/vmpi/vmpi_service_install/vmpi_service_install.rc173
-rw-r--r--utils/vmpi/vmpi_service_install/vmpi_service_install.vpc87
-rw-r--r--utils/vmpi/vmpi_service_ui/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_service_ui/StdAfx.h30
-rw-r--r--utils/vmpi/vmpi_service_ui/idi_busy_icon.icobin0 -> 318 bytes
-rw-r--r--utils/vmpi/vmpi_service_ui/idi_disabled_icon.icobin0 -> 318 bytes
-rw-r--r--utils/vmpi/vmpi_service_ui/idi_waiting_icon.icobin0 -> 318 bytes
-rw-r--r--utils/vmpi/vmpi_service_ui/resource.h47
-rw-r--r--utils/vmpi/vmpi_service_ui/shell_icon_mgr.cpp187
-rw-r--r--utils/vmpi/vmpi_service_ui/shell_icon_mgr.h66
-rw-r--r--utils/vmpi/vmpi_service_ui/unconnec.icobin0 -> 318 bytes
-rw-r--r--utils/vmpi/vmpi_service_ui/vmpi_service.icobin0 -> 1078 bytes
-rw-r--r--utils/vmpi/vmpi_service_ui/vmpi_service_ui.cpp617
-rw-r--r--utils/vmpi/vmpi_service_ui/vmpi_service_ui.rc171
-rw-r--r--utils/vmpi/vmpi_service_ui/vmpi_service_ui.vpc58
-rw-r--r--utils/vmpi/vmpi_services_watch/PatchTimeout.cpp50
-rw-r--r--utils/vmpi/vmpi_services_watch/PatchTimeout.h57
-rw-r--r--utils/vmpi/vmpi_services_watch/ServicesDlg.cpp1161
-rw-r--r--utils/vmpi/vmpi_services_watch/ServicesDlg.h150
-rw-r--r--utils/vmpi/vmpi_services_watch/SetPasswordDlg.cpp49
-rw-r--r--utils/vmpi/vmpi_services_watch/SetPasswordDlg.h54
-rw-r--r--utils/vmpi/vmpi_services_watch/StdAfx.cpp15
-rw-r--r--utils/vmpi/vmpi_services_watch/StdAfx.h35
-rw-r--r--utils/vmpi/vmpi_services_watch/res/vmpi.icobin0 -> 3310 bytes
-rw-r--r--utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.icobin0 -> 1078 bytes
-rw-r--r--utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.rc213
-rw-r--r--utils/vmpi/vmpi_services_watch/resource.h48
-rw-r--r--utils/vmpi/vmpi_services_watch/vmpi_browser_services.cpp81
-rw-r--r--utils/vmpi/vmpi_services_watch/vmpi_browser_services.h56
-rw-r--r--utils/vmpi/vmpi_services_watch/vmpi_browser_services.rc249
-rw-r--r--utils/vmpi/vmpi_services_watch/vmpi_services_watch.vpc72
-rw-r--r--utils/vmpi/vmpi_transfer/vmpi_transfer.cpp172
-rw-r--r--utils/vmpi/vmpi_transfer/vmpi_transfer.h14
-rw-r--r--utils/vmpi/vmpi_transfer/vmpi_transfer.vpc155
-rw-r--r--utils/vmpi/win_idle.cpp122
-rw-r--r--utils/vmpi/win_idle.h78
-rw-r--r--utils/vmpi/window_anchor_mgr.cpp94
-rw-r--r--utils/vmpi/window_anchor_mgr.h59
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
new file mode 100644
index 0000000..23c3dfd
--- /dev/null
+++ b/utils/vmpi/ZLib.lib
Binary files differ
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 &quot;$(TargetPath)&quot; ..\..\..\..\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 &quot;$(TargetPath)&quot; ..\..\..\..\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
new file mode 100644
index 0000000..dc0d87d
--- /dev/null
+++ b/utils/vmpi/testapps/MessageWatch/res/MessageWatch.ico
Binary files differ
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 &quot;$(TargetPath)&quot; ..\..\..\..\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 &quot;$(TargetPath)&quot; ..\..\..\..\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
new file mode 100644
index 0000000..7eef0bc
--- /dev/null
+++ b/utils/vmpi/vmpi_job_search/res/vmpi_browser_job_search.ico
Binary files differ
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
new file mode 100644
index 0000000..7eef0bc
--- /dev/null
+++ b/utils/vmpi/vmpi_job_watch/res/vmpi_browser_job_watch.ico
Binary files differ
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
new file mode 100644
index 0000000..24dc2cf
--- /dev/null
+++ b/utils/vmpi/vmpi_service_install/res/vmpi.ico
Binary files differ
diff --git a/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico
new file mode 100644
index 0000000..7eef0bc
--- /dev/null
+++ b/utils/vmpi/vmpi_service_install/res/vmpi_service_install.ico
Binary files differ
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
new file mode 100644
index 0000000..ac62654
--- /dev/null
+++ b/utils/vmpi/vmpi_service_ui/idi_busy_icon.ico
Binary files differ
diff --git a/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico b/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico
new file mode 100644
index 0000000..e49e952
--- /dev/null
+++ b/utils/vmpi/vmpi_service_ui/idi_disabled_icon.ico
Binary files differ
diff --git a/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico b/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico
new file mode 100644
index 0000000..a52165d
--- /dev/null
+++ b/utils/vmpi/vmpi_service_ui/idi_waiting_icon.ico
Binary files differ
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
new file mode 100644
index 0000000..5cfdcdb
--- /dev/null
+++ b/utils/vmpi/vmpi_service_ui/unconnec.ico
Binary files differ
diff --git a/utils/vmpi/vmpi_service_ui/vmpi_service.ico b/utils/vmpi/vmpi_service_ui/vmpi_service.ico
new file mode 100644
index 0000000..3868835
--- /dev/null
+++ b/utils/vmpi/vmpi_service_ui/vmpi_service.ico
Binary files differ
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
new file mode 100644
index 0000000..09720c0
--- /dev/null
+++ b/utils/vmpi/vmpi_services_watch/res/vmpi.ico
Binary files differ
diff --git a/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico
new file mode 100644
index 0000000..7eef0bc
--- /dev/null
+++ b/utils/vmpi/vmpi_services_watch/res/vmpi_browser_services.ico
Binary files differ
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