diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /replay/cl_downloader.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'replay/cl_downloader.cpp')
| -rw-r--r-- | replay/cl_downloader.cpp | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/replay/cl_downloader.cpp b/replay/cl_downloader.cpp new file mode 100644 index 0000000..9e375da --- /dev/null +++ b/replay/cl_downloader.cpp @@ -0,0 +1,300 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#if defined( WIN32 ) +#include "winlite.h" +#include <WinInet.h> +#endif + +#include "cl_downloader.h" +#include "engine/requestcontext.h" +#include "replaysystem.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//---------------------------------------------------------------------------------------- + +extern IEngineClientReplay *g_pEngineClient; +extern IEngineReplay *g_pEngine; + +//---------------------------------------------------------------------------------------- + +CHttpDownloader::CHttpDownloader( IDownloadHandler *pHandler ) +: m_pHandler( pHandler ), + m_flNextThinkTime( 0.0f ), + m_uBytesDownloaded( 0 ), + m_uSize( 0 ), + m_pThreadState( NULL ), + m_bDone( false ), + m_nHttpError( HTTP_ERROR_NONE ), + m_nHttpStatus( HTTP_INVALID ), + m_pBytesDownloaded( NULL ), + m_pUserData( NULL ) +{ +} + +CHttpDownloader::~CHttpDownloader() +{ + CleanupThreadIfDone(); +} + +bool CHttpDownloader::CleanupThreadIfDone() +{ + if ( !m_pThreadState || !m_pThreadState->threadDone ) + return false; + + // NOTE: The context's "data" member will have already been cleaned up by the + // download thread at this point. + delete m_pThreadState; + m_pThreadState = NULL; + + return true; +} + +bool CHttpDownloader::BeginDownload( const char *pURL, const char *pGamePath, void *pUserData, uint32 *pBytesDownloaded ) +{ + if ( !pURL || !pURL[0] ) + return false; + + m_pThreadState = new RequestContext_t(); + if ( !m_pThreadState ) + return false; + + // Cache any user data + m_pUserData = pUserData; + + // Cache bytes downloaded + m_pBytesDownloaded = pBytesDownloaded; + + // Setup request context + Replay_CrackURL( pURL, m_pThreadState->baseURL, m_pThreadState->urlPath ); + m_pThreadState->bAsHTTP = true; + + if ( pGamePath ) + { + m_pThreadState->bSuppressFileWrite = false; + V_strcpy( m_pThreadState->gamePath, pGamePath ); + + // Generate the actual filename to save. Well, it's not + // absolute, but this will work. + V_strcpy_safe( m_pThreadState->absLocalPath, g_pEngine->GetGameDir() ); + V_AppendSlash( m_pThreadState->absLocalPath, sizeof(m_pThreadState->absLocalPath) ); + V_strcat_safe( m_pThreadState->absLocalPath, pGamePath ); + } + else + { + m_pThreadState->bSuppressFileWrite = true; + } + + // Cache URL - for debugging + V_strcpy( m_szURL, pURL ); + + // Spawn the download thread + extern IDownloadSystem *g_pDownloadSystem; + return g_pDownloadSystem->CreateDownloadThread( m_pThreadState ) != 0; +} + +void CHttpDownloader::AbortDownloadAndCleanup() +{ + // Make sure that this function isn't executed simultaneously by + // multiple threads in order to avoid use-after-free crashes during + // shutdown. + AUTO_LOCK( m_lock ); + + if ( !m_pThreadState ) + return; + + // Thread already completed? + if ( m_pThreadState->threadDone ) + { + CleanupThreadIfDone(); + return; + } + + // Loop until the thread cleans up + m_pThreadState->shouldStop = true; + while ( !m_pThreadState->threadDone ) + ; + + // Cache state for handler + m_nHttpError = m_pThreadState->error; + m_nHttpStatus = HTTP_ABORTED; // Force this to be safe + m_uBytesDownloaded = 0; + m_uSize = m_pThreadState->nBytesTotal; + m_bDone = true; + + InvokeHandler(); + CleanupThreadIfDone(); +} + +void CHttpDownloader::Think() +{ + const float flHostTime = g_pEngine->GetHostTime(); + if ( m_flNextThinkTime > flHostTime ) + return; + + if ( !m_pThreadState ) + return; + + // If thread is done, cleanup now + if ( CleanupThreadIfDone() ) + return; + + // If we haven't already set shouldStop, check the download status + if ( !m_pThreadState->shouldStop ) + { + // Security measure: make sure the file size isn't outrageous + const bool bEvilFileSize = m_pThreadState->nBytesTotal && + m_pThreadState->nBytesTotal >= DOWNLOAD_MAX_SIZE; +#if _DEBUG + extern ConVar replay_simulate_evil_download_size; + if ( replay_simulate_evil_download_size.GetBool() || bEvilFileSize ) +#else + if ( bEvilFileSize ) +#endif + { + AbortDownloadAndCleanup(); + return; + } + + bool bConnecting = false; // For fall-through in HTTP_CONNECTING case. + +#if _DEBUG + extern ConVar replay_simulatedownloadfailure; + if ( replay_simulatedownloadfailure.GetInt() == 1 ) + { + m_pThreadState->status = HTTP_ERROR; + } +#endif + + switch ( m_pThreadState->status ) + { + case HTTP_CONNECTING: + + // Call connecting handler + if ( m_pHandler ) + { + m_pHandler->OnConnecting( this ); + } + + bConnecting = true; + + // Fall-through + + case HTTP_FETCH: + + m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent; + m_uSize = m_pThreadState->nBytesTotal; + + Assert( m_uBytesDownloaded <= m_uSize ); + + // Call fetch handle + if ( !bConnecting && m_pHandler ) + { + m_pHandler->OnFetch( this ); + } + + break; + + case HTTP_ABORTED: + case HTTP_DONE: + case HTTP_ERROR: + + // Cache state + m_nHttpError = m_pThreadState->error; + m_nHttpStatus = m_pThreadState->status; + m_uBytesDownloaded = (uint32)m_pThreadState->nBytesCurrent; + m_uSize = m_pThreadState->nBytesTotal; // NOTE: Need to do this here in the case that a file is small enough that we never hit HTTP_FETCH + m_bDone = true; + + // Call handler + InvokeHandler(); + + // Tell the thread to cleanup so we can free it + m_pThreadState->shouldStop = true; + + break; + } + } + + // Write bytes for user if changed + if ( m_pBytesDownloaded && *m_pBytesDownloaded != m_uBytesDownloaded ) + { + *m_pBytesDownloaded = m_uBytesDownloaded; + IF_REPLAY_DBG( Warning( "%s: Downloaded %i/%i bytes\n", m_szURL, m_uBytesDownloaded, m_uSize ) ); + } + + // Set next think time + m_flNextThinkTime = flHostTime + 0.1f; +} + +void CHttpDownloader::InvokeHandler() +{ + if ( !m_pHandler ) + return; + + // NOTE: Don't delete the downloader in OnDownloadComplete()! + m_pHandler->OnDownloadComplete( this, m_pThreadState->data ); +} + +// This does not increment the "ErrorCounter" field and should only be called from code +// that eventually calls into OGS_ReportGenericError(). +KeyValues *CHttpDownloader::GetOgsRow( int nErrorCounter ) const +{ + KeyValues *pResult = new KeyValues( "TF2ReplayHttpDownloadErrors" ); + pResult->SetInt( "ErrorCounter", nErrorCounter ); + pResult->SetInt( "BytesDownloaded", (int)m_uBytesDownloaded ); + pResult->SetInt( "BytesTotal", (int)m_uSize ); + pResult->SetInt( "HttpStatus", m_nHttpStatus ); + pResult->SetInt( "HttpError", m_nHttpError ); + pResult->SetString( "URL", m_szURL ); + + return pResult; +} + +/*static*/ const char *CHttpDownloader::GetHttpErrorToken( HTTPError_t nError ) +{ + switch ( nError ) + { + case HTTP_ERROR_ZERO_LENGTH_FILE: return "#HTTPError_ZeroLengthFile"; + case HTTP_ERROR_CONNECTION_CLOSED: return "#HTTPError_ConnectionClosed"; + case HTTP_ERROR_INVALID_URL: return "#HTTPError_InvalidURL"; + case HTTP_ERROR_INVALID_PROTOCOL: return "#HTTPError_InvalidProtocol"; + case HTTP_ERROR_CANT_BIND_SOCKET: return "#HTTPError_CantBindSocket"; + case HTTP_ERROR_CANT_CONNECT: return "#HTTPError_CantConnect"; + case HTTP_ERROR_NO_HEADERS: return "#HTTPError_NoHeaders"; + case HTTP_ERROR_FILE_NONEXISTENT: return "#HTTPError_NonExistent"; + } + + return "#HTTPError_Unknown"; +} + +//---------------------------------------------------------------------------------------- + +#ifdef _DEBUG + +CHttpDownloader *g_pTestDownload = NULL; +ConVar replay_forcedownloadurl( "replay_forcedownloadurl", "" ); + +CON_COMMAND( replay_testdownloader_start, "" ) +{ + const char *pGamePath = Replay_va( "%s%s", CL_GetRecordingSessionBlockManager()->GetSavePath(), "testdownload" ); + + g_pTestDownload = new CHttpDownloader(); + g_pTestDownload->BeginDownload( args[1], pGamePath ); +} + +CON_COMMAND( replay_testdownloader_abort, "" ) +{ + if ( !g_pTestDownload ) + return; + + g_pTestDownload->AbortDownloadAndCleanup(); + + delete g_pTestDownload; + g_pTestDownload = NULL; +} + +#endif
\ No newline at end of file |