source-engine/engine/sv_logofile.cpp

350 lines
8.8 KiB
C++
Raw Normal View History

2020-04-23 00:56:21 +08:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "quakedef.h"
#include "sv_client.h"
#include "logofile_shared.h"
#include "server.h"
#include "filetransfermgr.h"
#include "filesystem_engine.h"
ConVar sv_logo_rate( "sv_logo_rate", "1024", 0, "How fast (bytes per second) the server sends logo files to clients." );
class CPendingFile
{
public:
CRC32_t m_nLogoFileCRC;
bool m_bWaitingForServerToGetFile;
};
class CPerClientLogoInfo
{
public:
CPerClientLogoInfo()
{
m_bLogoFileCRCValid = false;
m_bSendFileInProgress = false;
}
// This client's logo info.
bool m_bLogoFileCRCValid;
int m_nLogoFileCRC;
// Are we sending this client a file right now?
bool m_bSendFileInProgress;
// Files that this client has requested but we aren't able to send yet.
CUtlVector<CPendingFile> m_PendingFiles;
};
class CServerFileTransferMgr : public CFileTransferMgr
{
public:
virtual bool SendChunk( INetChannel *pDest, const void *pData, int len )
{
SVC_LogoFileData fileData;
fileData.m_Data.CopyArray( (const char*)pData, len );
return pDest->SendNetMsg( fileData, true );
}
virtual void OnSendCancelled( FileTransferID_t id )
{
}
CGameClient* GetClientByNetChannel( INetChannel *pChan )
{
for ( int i=0; i < sv.clients.Count(); i++ )
{
CGameClient *pClient = sv.Client( i );
if ( pClient && pClient->GetNetChannel() == pChan )
return pClient;
}
return NULL;
}
virtual void OnFinishedSending(
INetChannel *pDest,
const void *pUserData,
int userDataLen,
FileTransferID_t id )
{
// Start sending the next file to this guy.
CGameClient *pClient = GetClientByNetChannel( pDest );
if ( pClient )
{
pClient->m_pLogoInfo->m_bSendFileInProgress = false;
UpdatePendingFiles();
}
else
{
Warning( "OnFinishedSending: can't get CGameClient from INetChannel.\n" );
}
}
virtual void OnFileReceived(
INetChannel *pChan,
const void *pUserData,
int userDataLength,
const char *pFileData,
int fileLength )
{
// Ok, now the server has received a file the client sent. First, validate the VTF.
if ( !LogoFile_IsValidVTFFile( pFileData, fileLength ) )
{
Warning( "CServerFileTransferMgr::OnFileReceived: received an invalid logo file from a client.\n" );
return;
}
if ( userDataLength < sizeof( CRC32_t ) )
{
Warning( "CServerFileTransferMgr::OnFileReceived: invalid userDataLength (%d).\n", userDataLength );
}
CRC32_t crcValue = *((CRC32_t*)pUserData);
// Save this file in our cache.
if ( SaveCRCFileToCache( crcValue, pFileData, fileLength ) )
{
// Start transfers to any clients that we can now.
MarkPendingFilesWithCRC( crcValue );
UpdatePendingFiles();
}
}
// If any clients are waiting on this file, mark them so they know they can be sent the file now.
void MarkPendingFilesWithCRC( CRC32_t crcValue )
{
for ( int i=0; i < sv.clients.Count(); i++ )
{
CGameClient *pClient = sv.Client( i );
if ( !pClient || !pClient->m_pLogoInfo )
continue;
for ( int i=0; i < pClient->m_pLogoInfo->m_PendingFiles.Count(); i++ )
{
CPendingFile *pFile = &pClient->m_pLogoInfo->m_PendingFiles[i];
if ( pFile->m_nLogoFileCRC == crcValue )
pFile->m_bWaitingForServerToGetFile = false;
}
}
}
bool SaveCRCFileToCache( CRC32_t crcValue, const void *pFileData, int fileLength )
{
CLogoFilename logohex( crcValue, true );
FileHandle_t hFile = g_pFileSystem->Open( logohex.m_Filename, "wb" );
if ( hFile == FILESYSTEM_INVALID_HANDLE )
{
Warning( "SaveCRCFileToCache: couldn't open '%s' for writing.\n", logohex.m_Filename );
return false;
}
else
{
int writeRet = g_pFileSystem->Write( pFileData, fileLength, hFile );
g_pFileSystem->Close( hFile );
// If we couldn't write it, then delete it.
if ( writeRet == fileLength )
{
return true;
}
else
{
Warning( "SaveCRCFileToCache: couldn't write data (%d should be %d).\n", writeRet, fileLength );
return false;
}
}
}
void UpdatePendingFiles()
{
CUtlVector<char> fileData;
CRC32_t lastCRC = 0;
// Find clients who want to receive this file.
for ( int i=0; i < sv.clients.Count(); i++ )
{
CGameClient *pClient = sv.Client( i );
if ( !pClient || !pClient->m_pLogoInfo )
continue;
// Are we already sending the client a file?
if ( pClient->m_pLogoInfo->m_bSendFileInProgress )
continue;
for ( int iFile=0; iFile < pClient->m_pLogoInfo->m_PendingFiles.Count(); iFile++ )
{
CPendingFile *pFile = &pClient->m_pLogoInfo->m_PendingFiles[iFile];
// If we still have to wait for the server to get this file, then stop.
if ( pFile->m_bWaitingForServerToGetFile )
continue;
pClient->m_pLogoInfo->m_PendingFiles.Remove( iFile );
// Load the file, if we haven't already.
if ( fileData.Count() == 0 || lastCRC != pFile->m_nLogoFileCRC )
{
// Remember the last CRC so we don't have to reopen the file if
// this one is going to a bunch of clients in a row.
lastCRC = pFile->m_nLogoFileCRC;
if ( !LogoFile_ReadFile( pFile->m_nLogoFileCRC, fileData ) )
break;
}
StartSending(
pClient->GetNetChannel(),
&lastCRC,
sizeof( lastCRC ),
fileData.Base(),
fileData.Count(),
sv_logo_rate.GetInt()
);
pClient->m_pLogoInfo->m_bSendFileInProgress = true;
break;
}
}
}
};
CServerFileTransferMgr g_ServerFileTransferMgr;
bool SV_LogoFile_HasLogoFile( CRC32_t crcValue )
{
CLogoFilename logohex( crcValue, true );
return g_pFileSystem->FileExists( logohex.m_Filename );
}
PROCESS_MSG_SERVER( CLC_LogoFileData )
{
g_ServerFileTransferMgr.HandleReceivedData( m_Client->GetNetChannel(), m_Data.Base(), m_Data.Count() );
return true;
} };
PROCESS_MSG_SERVER( CLC_LogoFileRequest )
{
// The client is requesting that we send it a specific logo file.
int index = m_Client->m_pLogoInfo->m_PendingFiles.AddToTail();
CPendingFile &file = m_Client->m_pLogoInfo->m_PendingFiles[index];
file.m_nLogoFileCRC = m_nLogoFileCRC;
file.m_bWaitingForServerToGetFile = SV_LogoFile_HasLogoFile( file.m_nLogoFileCRC );
// Start sending it if it's time..
g_ServerFileTransferMgr.UpdatePendingFiles();
return true;
} };
CPerClientLogoInfo* SV_LogoFile_CreatePerClientLogoInfo()
{
CPerClientLogoInfo *pInfo = new CPerClientLogoInfo;
pInfo->m_bLogoFileCRCValid = false;
return pInfo;
}
void SV_LogoFile_DeletePerClientLogoInfo( CPerClientLogoInfo *pInfo )
{
delete pInfo;
}
void SV_LogoFile_HandleClientDisconnect( CGameClient *pClient )
{
g_ServerFileTransferMgr.HandleClientDisconnect( pClient->GetNetChannel() );
}
void SV_LogoFile_NewConnection( INetChannel *chan, CGameClient *pGameClient )
{
REGISTER_MSG_SERVER( CLC_LogoFileRequest );
}
bool SV_LogoFile_IsDownloadingLogoFile( CRC32_t crcValue )
{
for ( int i=g_ServerFileTransferMgr.FirstIncoming(); i != g_ServerFileTransferMgr.InvalidIncoming(); i=g_ServerFileTransferMgr.NextIncoming( i ) )
{
const void *pData;
int dataLen;
g_ServerFileTransferMgr.GetIncomingUserData( i, pData, dataLen );
CRC32_t *pTestValue = (CRC32_t*)pData;
if ( *pTestValue == crcValue )
return true;
}
return false;
}
void SV_LogoFile_OnConnect( CGameClient *pSenderClient, bool bValid, CRC32_t crcValue )
{
pSenderClient->m_pLogoInfo->m_bLogoFileCRCValid = bValid;
pSenderClient->m_pLogoInfo->m_nLogoFileCRC = crcValue;
if ( bValid )
{
// Does the server need this file? If so, request it.
if ( !SV_LogoFile_HasLogoFile( crcValue ) && !SV_LogoFile_IsDownloadingLogoFile( crcValue ) )
{
SVC_LogoFileRequest fileRequest;
fileRequest.m_nLogoFileCRC = crcValue;
if ( !pSenderClient->SendNetMsg( fileRequest, true ) )
{
Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
return;
}
}
// Tell all clients (except the sending client) about this logo.
SVC_LogoFileCRC logoNotify;
logoNotify.m_nLogoFileCRC = crcValue;
for ( int i=0; i < sv.clients.Count(); i++ )
{
CGameClient *pClient = sv.Client( i );
if ( !pClient || pClient == pSenderClient )
continue;
bool bReliable = true;
if ( !pClient->SendNetMsg( logoNotify, bReliable ) )
{
Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
return;
}
}
}
// Also, tell this client about all other client CRCs so it can aks for the one it needs.
for ( int i=0; i < sv.clients.Count(); i++ )
{
CGameClient *pClient = sv.Client( i );
if ( !pClient || pClient == pSenderClient || !pClient->m_pLogoInfo->m_bLogoFileCRCValid )
continue;
SVC_LogoFileCRC logoNotify;
logoNotify.m_nLogoFileCRC = pClient->m_pLogoInfo->m_nLogoFileCRC;
bool bReliable = true;
if ( !pSenderClient->SendNetMsg( logoNotify, bReliable ) )
{
Host_Error( "SV_LogoFile_OnConnect: Reliable broadcast message would overflow client" );
return;
}
}
}