//========= 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 );
}