2019-03-13 19:20:07 +00:00
/*
2020-06-04 21:01:28 +00:00
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2019-03-13 19:20:07 +00:00
Copyright ( C ) 1997 - 2001 Id Software , Inc .
2020-06-04 21:01:28 +00:00
This file is part of Quake 2 source code .
2019-03-13 19:20:07 +00:00
2020-06-04 21:01:28 +00:00
Quake 2 source code is free software ; you can redistribute it
and / or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation ; either version 2 of the License ,
or ( at your option ) any later version .
2019-03-13 19:20:07 +00:00
2020-06-04 21:01:28 +00:00
Quake 2 source code is distributed in the hope that it will be
useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
2019-03-13 19:20:07 +00:00
You should have received a copy of the GNU General Public License
2020-06-04 21:01:28 +00:00
along with Quake 2 source code ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2019-03-13 19:20:07 +00:00
*/
# include "client.h"
# ifdef USE_CURL
2020-08-11 06:08:18 +00:00
enum
2019-03-13 19:20:07 +00:00
{
HTTPDL_ABORT_NONE ,
HTTPDL_ABORT_SOFT ,
HTTPDL_ABORT_HARD
} ;
static CURLM * multi = NULL ;
static int handleCount = 0 ;
static int pendingCount = 0 ;
static int abortDownloads = HTTPDL_ABORT_NONE ;
static qboolean downloading_pak = false ;
static qboolean httpDown = false ;
2020-04-30 06:10:27 +00:00
static qboolean thisMapAbort = false ; // Knightmare- whether to fall back to UDP for this map
static int prevSize ; // Knightmare- for KBps counter
static qboolean downloadError = false ; // YQ2 UDP fallback addition
static qboolean downloadFileList = true ; // YQ2 addition for downloading filelist once
static char remoteGamedir [ MAX_QPATH ] ; // YQ2 addition for Q2Pro downloads
2019-03-13 19:20:07 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
R1Q2 HTTP Downloading Functions
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
HTTP downloading is used if the server provides a content
server URL in the connect message . Any missing content the
client needs will then use the HTTP server instead of auto
downloading via UDP . CURL is used to enable multiple files
to be downloaded in parallel to improve performance on high
latency links when small files such as textures are needed .
Since CURL natively supports gzip content encoding , any files
on the HTTP server should ideally be gzipped to conserve
bandwidth .
*/
2020-05-05 01:24:51 +00:00
// Knightmare- store the names of last HTTP downloads from this server that failed
// This is needed because some player model download failures can cause endless HTTP download loops
# define NUM_FAIL_DLDS 64
char lastFailedHTTPDownload [ NUM_FAIL_DLDS ] [ MAX_OSPATH ] ;
static unsigned failed_HTTP_Dl_ListIndex ;
/*
= = = = = = = = = = = = = = =
CL_InitFailedHTTPDownloadList
= = = = = = = = = = = = = = =
*/
void CL_InitFailedHTTPDownloadList ( void )
{
int i ;
for ( i = 0 ; i < NUM_FAIL_DLDS ; i + + )
Com_sprintf ( lastFailedHTTPDownload [ i ] , sizeof ( lastFailedHTTPDownload [ i ] ) , " \0 " ) ;
failed_HTTP_Dl_ListIndex = 0 ;
}
/*
= = = = = = = = = = = = = = =
CL_CheckHTTPDownloadFailed
= = = = = = = = = = = = = = =
*/
qboolean CL_CheckHTTPDownloadFailed ( char * name )
{
int i ;
for ( i = 0 ; i < NUM_FAIL_DLDS ; i + + )
if ( ( strlen ( lastFailedHTTPDownload [ i ] ) > 0 ) & & ! strcmp ( name , lastFailedHTTPDownload [ i ] ) )
{ // we already tried downlaoding this, server didn't have it
return true ;
}
return false ;
}
/*
= = = = = = = = = = = = = = =
CL_AddToFailedHTTPDownloadList
= = = = = = = = = = = = = = =
*/
void CL_AddToFailedHTTPDownloadList ( char * name )
{
int i ;
qboolean found = false ;
// check if this name is already in the table
for ( i = 0 ; i < NUM_FAIL_DLDS ; i + + )
if ( ( strlen ( lastFailedHTTPDownload [ i ] ) > 0 ) & & ! strcmp ( name , lastFailedHTTPDownload [ i ] ) )
{
found = true ;
break ;
}
// if it isn't already in the table, then we need to add it
if ( ! found )
{
Com_sprintf ( lastFailedHTTPDownload [ failed_HTTP_Dl_ListIndex ] , sizeof ( lastFailedHTTPDownload [ failed_HTTP_Dl_ListIndex ] ) , " %s " , name ) ;
failed_HTTP_Dl_ListIndex + + ;
// wrap around to start of list
if ( failed_HTTP_Dl_ListIndex > = NUM_FAIL_DLDS )
failed_HTTP_Dl_ListIndex = 0 ;
}
}
// end Knightmare
2019-03-13 19:20:07 +00:00
/*
= = = = = = = = = = = = = = =
CL_HTTP_Reset_KBps_counter
Just a wrapper for CL_Download_Reset_KBps_counter ( ) ,
also resets prevSize .
= = = = = = = = = = = = = = =
*/
static void CL_HTTP_Reset_KBps_counter ( void )
{
prevSize = 0 ;
CL_Download_Reset_KBps_counter ( ) ;
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_Calculate_KBps
Essentially a wrapper for CL_Download_Calcualte_KBps ( ) ,
calcuates bytes since last update and calls the above .
= = = = = = = = = = = = = = =
*/
static void CL_HTTP_Calculate_KBps ( int curSize , int totalSize )
{
int byteDistance = curSize - prevSize ;
CL_Download_Calculate_KBps ( byteDistance , totalSize ) ;
prevSize = curSize ;
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_Progress
libcurl callback to update progress info . Mainly just used as
a way to cancel the transfer if required .
= = = = = = = = = = = = = = =
*/
static int /*EXPORT*/ CL_HTTP_Progress ( void * clientp , double dltotal , double dlnow , double ultotal , double ulnow )
{
dlhandle_t * dl ;
dl = ( dlhandle_t * ) clientp ;
dl - > position = ( unsigned ) dlnow ;
// don't care which download shows as long as something does :)
if ( ! abortDownloads )
{
Q_strncpyz ( cls . downloadname , dl - > queueEntry - > quakePath , sizeof ( cls . downloadname ) ) ;
cls . downloadposition = dl - > position ;
if ( dltotal ) {
CL_HTTP_Calculate_KBps ( ( int ) dlnow , ( int ) dltotal ) ; // Knightmare- added KB/s counter
cls . downloadpercent = ( int ) ( ( dlnow / dltotal ) * 100.0f ) ;
}
else
cls . downloadpercent = 0 ;
}
return abortDownloads ;
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_Header
libcurl callback to update header info .
= = = = = = = = = = = = = = =
*/
static size_t /*EXPORT*/ CL_HTTP_Header ( void * ptr , size_t size , size_t nmemb , void * stream )
{
char headerBuff [ 1024 ] ;
size_t bytes ;
size_t len ;
bytes = size * nmemb ;
if ( bytes < = 16 )
return bytes ;
//memset (headerBuff, 0, sizeof(headerBuff));
//memcpy (headerBuff, ptr, min(bytes, sizeof(headerBuff)-1));
if ( bytes < sizeof ( headerBuff ) - 1 )
len = bytes ;
else
len = sizeof ( headerBuff ) - 1 ;
Q_strncpyz ( headerBuff , ptr , len ) ;
if ( ! Q_strncasecmp ( headerBuff , " Content-Length: " , 16 ) )
{
dlhandle_t * dl ;
dl = ( dlhandle_t * ) stream ;
if ( dl - > file )
dl - > fileSize = strtoul ( headerBuff + 16 , NULL , 10 ) ;
}
return bytes ;
}
/*
= = = = = = = = = = = = = = =
CL_EscapeHTTPPath
Properly escapes a path with HTTP % encoding . libcurl ' s function
seems to treat ' / ' and such as illegal chars and encodes almost
the entire URL . . .
= = = = = = = = = = = = = = =
*/
static void CL_EscapeHTTPPath ( const char * filePath , char * escaped )
{
int i ;
size_t len ;
char * p ;
p = escaped ;
len = strlen ( filePath ) ;
for ( i = 0 ; i < len ; i + + )
{
if ( ! isalnum ( filePath [ i ] ) & & filePath [ i ] ! = ' ; ' & & filePath [ i ] ! = ' / ' & &
filePath [ i ] ! = ' ? ' & & filePath [ i ] ! = ' : ' & & filePath [ i ] ! = ' @ ' & & filePath [ i ] ! = ' & ' & &
filePath [ i ] ! = ' = ' & & filePath [ i ] ! = ' + ' & & filePath [ i ] ! = ' $ ' & & filePath [ i ] ! = ' , ' & &
filePath [ i ] ! = ' [ ' & & filePath [ i ] ! = ' ] ' & & filePath [ i ] ! = ' - ' & & filePath [ i ] ! = ' _ ' & &
filePath [ i ] ! = ' . ' & & filePath [ i ] ! = ' ! ' & & filePath [ i ] ! = ' ~ ' & & filePath [ i ] ! = ' * ' & &
filePath [ i ] ! = ' \' ' & & filePath [ i ] ! = ' ( ' & & filePath [ i ] ! = ' ) ' )
{
sprintf ( p , " %%%02x " , filePath [ i ] ) ;
p + = 3 ;
}
else
{
* p = filePath [ i ] ;
p + + ;
}
}
p [ 0 ] = 0 ;
2020-04-30 06:10:27 +00:00
// using ./ in a url is legal, but all browsers condense the path and some IDS / request
// filtering systems act a bit funky if http requests come in with uncondensed paths.
2019-03-13 19:20:07 +00:00
len = strlen ( escaped ) ;
p = escaped ;
while ( ( p = strstr ( p , " ./ " ) ) )
{
memmove ( p , p + 2 , len - ( p - escaped ) - 1 ) ;
len - = 2 ;
}
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_Recv
libcurl callback for filelists .
= = = = = = = = = = = = = = =
*/
static size_t /*EXPORT*/ CL_HTTP_Recv ( void * ptr , size_t size , size_t nmemb , void * stream )
{
size_t bytes ;
dlhandle_t * dl ;
dl = ( dlhandle_t * ) stream ;
bytes = size * nmemb ;
if ( ! dl - > fileSize )
{
dl - > fileSize = bytes > 131072 ? bytes : 131072 ;
// dl->tempBuffer = Z_TagMalloc ((int)dl->fileSize, TAGMALLOC_CLIENT_DOWNLOAD);
dl - > tempBuffer = Z_TagMalloc ( ( int ) dl - > fileSize , 0 ) ;
}
else if ( dl - > position + bytes > = dl - > fileSize - 1 )
{
char * tmp ;
tmp = dl - > tempBuffer ;
// dl->tempBuffer = Z_TagMalloc ((int)(dl->fileSize*2), TAGMALLOC_CLIENT_DOWNLOAD);
dl - > tempBuffer = Z_TagMalloc ( ( int ) ( dl - > fileSize * 2 ) , 0 ) ;
memcpy ( dl - > tempBuffer , tmp , dl - > fileSize ) ;
Z_Free ( tmp ) ;
dl - > fileSize * = 2 ;
}
memcpy ( dl - > tempBuffer + dl - > position , ptr , bytes ) ;
dl - > position + = bytes ;
dl - > tempBuffer [ dl - > position ] = 0 ;
return bytes ;
}
int /*EXPORT*/ CL_CURL_Debug ( CURL * c , curl_infotype type , char * data , size_t size , void * ptr )
{
if ( type = = CURLINFO_TEXT )
{
char buff [ 4096 ] ;
if ( size > sizeof ( buff ) - 1 )
size = sizeof ( buff ) - 1 ;
Q_strncpyz ( buff , data , size ) ;
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] DEBUG: %s \n " , buff ) ;
2019-03-13 19:20:07 +00:00
}
return 0 ;
}
2020-04-30 06:10:27 +00:00
/*void CL_RemoveHTTPDownload (const char *quakePath)
{
} */
/*
= = = = = = = = = = = = = = =
CL_RemoveDownloadFromQueue
Adapted from Yamagi Quake2 .
Removes an entry from the download queue .
= = = = = = = = = = = = = = =
*/
# if 1
qboolean CL_RemoveDownloadFromQueue ( dlqueue_t * entry )
{
dlqueue_t * last = & cls . downloadQueue ;
dlqueue_t * cur = last - > next ;
while ( cur )
{
if ( last - > next = = entry )
{
last - > next = cur - > next ;
Z_Free ( cur ) ;
cur = NULL ;
return true ;
}
last = cur ;
cur = cur - > next ;
}
return false ;
}
# endif
2019-03-13 19:20:07 +00:00
/*
= = = = = = = = = = = = = = =
CL_StartHTTPDownload
Actually starts a download by adding it to the curl multi
handle .
= = = = = = = = = = = = = = =
*/
static void CL_StartHTTPDownload ( dlqueue_t * entry , dlhandle_t * dl )
{
size_t len ;
2020-04-30 06:10:27 +00:00
char remoteFilePath [ MAX_OSPATH ] ; // Knightmare added
2019-03-13 19:20:07 +00:00
char escapedFilePath [ MAX_QPATH * 4 ] ;
2020-04-30 06:10:27 +00:00
// yet another hack to accomodate filelists, how i wish i could push :(
// NULL file handle indicates filelist.
2019-03-13 19:20:07 +00:00
len = strlen ( entry - > quakePath ) ;
if ( len > 9 & & ! strcmp ( entry - > quakePath + len - 9 , " .filelist " ) )
{
dl - > file = NULL ;
CL_EscapeHTTPPath ( entry - > quakePath , escapedFilePath ) ;
}
else
{
CL_HTTP_Reset_KBps_counter ( ) ; // Knightmare- for KB/s counter
2020-04-30 06:10:27 +00:00
Com_sprintf ( dl - > filePath , sizeof ( dl - > filePath ) , " %s/%s " , FS_Downloaddir ( ) , entry - > quakePath ) ; // was FS_Gamedir()
// Com_sprintf (remoteFilePath, sizeof(remoteFilePath), "/%s/%s", cl.gamedir, entry->quakePath); // always use cl.gamedir (with leading slash) for remote server path
// YQ2 Q2pro download addition
// Use remoteGamedir for remote server path if set
if ( remoteGamedir [ 0 ] = = ' \0 ' )
Com_sprintf ( remoteFilePath , sizeof ( remoteFilePath ) , " /%s " , entry - > quakePath ) ;
else
Com_sprintf ( remoteFilePath , sizeof ( remoteFilePath ) , " /%s/%s " , remoteGamedir , entry - > quakePath ) ;
2019-03-13 19:20:07 +00:00
2020-04-30 06:10:27 +00:00
// CL_EscapeHTTPPath (dl->filePath, escapedFilePath);
CL_EscapeHTTPPath ( remoteFilePath , escapedFilePath ) ;
2019-03-13 19:20:07 +00:00
// strncat (dl->filePath, ".tmp");
Q_strncatz ( dl - > filePath , " .tmp " , sizeof ( dl - > filePath ) ) ;
FS_CreatePath ( dl - > filePath ) ;
2020-04-30 06:10:27 +00:00
// don't bother with http resume... too annoying if server doesn't support it.
2019-03-13 19:20:07 +00:00
dl - > file = fopen ( dl - > filePath , " wb " ) ;
if ( ! dl - > file )
{
Com_Printf ( " CL_StartHTTPDownload: Couldn't open %s for writing. \n " , dl - > filePath ) ;
entry - > state = DLQ_STATE_DONE ;
pendingCount - - ; // Knightmare- fix for curl_update limbo from [HCI]Maraa'kate
2020-04-30 06:10:27 +00:00
// CL_RemoveHTTPDownload (entry->quakePath);
2019-03-13 19:20:07 +00:00
return ;
}
}
dl - > tempBuffer = NULL ;
dl - > speed = 0 ;
dl - > fileSize = 0 ;
dl - > position = 0 ;
dl - > queueEntry = entry ;
if ( ! dl - > curl )
dl - > curl = curl_easy_init ( ) ;
Com_sprintf ( dl - > URL , sizeof ( dl - > URL ) , " %s%s " , cls . downloadServer , escapedFilePath ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_ENCODING , " " ) ;
//curl_easy_setopt (dl->curl, CURLOPT_DEBUGFUNCTION, CL_CURL_Debug);
//curl_easy_setopt (dl->curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt ( dl - > curl , CURLOPT_NOPROGRESS , 0 ) ;
if ( dl - > file )
{
curl_easy_setopt ( dl - > curl , CURLOPT_WRITEDATA , dl - > file ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_WRITEFUNCTION , NULL ) ;
}
else
{
curl_easy_setopt ( dl - > curl , CURLOPT_WRITEDATA , dl ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_WRITEFUNCTION , CL_HTTP_Recv ) ;
}
curl_easy_setopt ( dl - > curl , CURLOPT_PROXY , cl_http_proxy - > string ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_FOLLOWLOCATION , 1 ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_MAXREDIRS , 5 ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_WRITEHEADER , dl ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_HEADERFUNCTION , CL_HTTP_Header ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_PROGRESSFUNCTION , CL_HTTP_Progress ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_PROGRESSDATA , dl ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_USERAGENT , Cvar_VariableString ( " version " ) ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_REFERER , cls . downloadReferer ) ;
curl_easy_setopt ( dl - > curl , CURLOPT_URL , dl - > URL ) ;
if ( curl_multi_add_handle ( multi , dl - > curl ) ! = CURLM_OK )
{
Com_Printf ( " curl_multi_add_handle: error \n " ) ;
dl - > queueEntry - > state = DLQ_STATE_DONE ;
return ;
}
handleCount + + ;
//Com_Printf ("started dl: hc = %d\n", LOG_GENERAL, handleCount);
Com_DPrintf ( " CL_StartHTTPDownload: Fetching %s... \n " , dl - > URL ) ;
dl - > queueEntry - > state = DLQ_STATE_RUNNING ;
}
/*
= = = = = = = = = = = = = = =
CL_InitHTTPDownloads
Init libcurl and multi handle .
= = = = = = = = = = = = = = =
*/
void CL_InitHTTPDownloads ( void )
{
curl_global_init ( CURL_GLOBAL_NOTHING ) ;
//Com_Printf ("%s initialized.\n", LOG_CLIENT, curl_version());
}
/*
= = = = = = = = = = = = = = =
CL_SetHTTPServer
A new server is specified , so we nuke all our state .
= = = = = = = = = = = = = = =
*/
void CL_SetHTTPServer ( const char * URL )
{
dlqueue_t * q , * last ;
2020-04-30 06:10:27 +00:00
char * fixedURL = NULL ;
size_t URLlen ;
2019-03-13 19:20:07 +00:00
CL_HTTP_Cleanup ( false ) ;
q = & cls . downloadQueue ;
last = NULL ;
while ( q - > next )
{
q = q - > next ;
if ( last )
Z_Free ( last ) ;
last = q ;
}
if ( last )
Z_Free ( last ) ;
if ( multi )
Com_Error ( ERR_DROP , " CL_SetHTTPServer: Still have old handle " ) ;
multi = curl_multi_init ( ) ;
memset ( & cls . downloadQueue , 0 , sizeof ( cls . downloadQueue ) ) ;
abortDownloads = HTTPDL_ABORT_NONE ;
handleCount = pendingCount = 0 ;
2020-04-30 06:10:27 +00:00
// strncpy (cls.downloadServer, URL, sizeof(cls.downloadServer)-1);
// from YQ2: remove trailing '/' from URL
URLlen = strlen ( URL ) ;
fixedURL = strdup ( URL ) ;
if ( fixedURL [ URLlen - 1 ] = = ' / ' ) {
fixedURL [ URLlen - 1 ] = ' \0 ' ;
}
// From Q2Pro- ignore non-HTTP DL server URLs
if ( ( strncmp ( fixedURL , " http:// " , 7 ) ! = 0 ) & & ( strncmp ( fixedURL , " https:// " , 8 ) ! = 0 ) ) {
Com_Printf ( " [HTTP] Ignoring download server with non-HTTP protocol. \n " ) ;
return ;
}
Q_strncpyz ( cls . downloadServer , fixedURL , sizeof ( cls . downloadServer ) ) ;
free ( fixedURL ) ;
fixedURL = NULL ;
2019-03-13 19:20:07 +00:00
// FS: Added because Whale's Weapons HTTP server rejects you after a lot of 404s. Then you lose HTTP until a hard reconnect.
cls . downloadServerRetry [ 0 ] = 0 ;
2020-04-30 06:10:27 +00:00
downloadError = false ; // YQ2 UDP fallback addition- reset this for new server
2020-05-05 01:24:51 +00:00
CL_InitFailedHTTPDownloadList ( ) ; // Knightmare- init failed HTTP downloads list
2019-03-13 19:20:07 +00:00
}
2020-05-05 01:24:51 +00:00
2019-03-13 19:20:07 +00:00
/*
= = = = = = = = = = = = = = =
CL_CancelHTTPDownloads
Cancel all downloads and nuke the queue .
= = = = = = = = = = = = = = =
*/
void CL_CancelHTTPDownloads ( qboolean permKill )
{
dlqueue_t * q ;
if ( permKill )
{
CL_ResetPrecacheCheck ( ) ;
abortDownloads = HTTPDL_ABORT_HARD ;
}
else
abortDownloads = HTTPDL_ABORT_SOFT ;
q = & cls . downloadQueue ;
while ( q - > next )
{
q = q - > next ;
if ( q - > state = = DLQ_STATE_NOT_STARTED )
q - > state = DLQ_STATE_DONE ;
}
if ( ! pendingCount & & ! handleCount & & abortDownloads = = HTTPDL_ABORT_HARD )
cls . downloadServer [ 0 ] = 0 ;
pendingCount = 0 ;
}
/*
= = = = = = = = = = = = = = =
CL_QueueHTTPDownload
Called from the precache check to queue a download . Return value of
false will cause standard UDP downloading to be used instead .
= = = = = = = = = = = = = = =
*/
2020-04-30 06:10:27 +00:00
qboolean CL_QueueHTTPDownload ( const char * quakePath , qboolean filelistUseGamedir )
2019-03-13 19:20:07 +00:00
{
size_t len ;
2019-05-30 02:40:01 +00:00
dlqueue_t * q , * check , * last ;
2020-04-30 06:10:27 +00:00
qboolean needList = false , isPak = false , isFilelist = false ;
2019-03-13 19:20:07 +00:00
// no http server (or we got booted)
2020-04-30 06:10:27 +00:00
if ( ! cls . downloadServer [ 0 ] | | abortDownloads | | thisMapAbort | | ! cl_http_downloads - > integer )
2019-03-13 19:20:07 +00:00
return false ;
2020-04-30 06:10:27 +00:00
// needList = false;
2019-03-13 19:20:07 +00:00
// first download queued, so we want the mod filelist
2020-04-30 06:10:27 +00:00
// if ( !cls.downloadQueue.next && cl_http_filelists->integer ) {
if ( downloadFileList & & cl_http_filelists - > integer ) {
2019-03-13 19:20:07 +00:00
needList = true ;
2020-04-30 06:10:27 +00:00
downloadFileList = false ;
}
2019-03-13 19:20:07 +00:00
2019-05-30 02:40:01 +00:00
len = strlen ( quakePath ) ;
if ( len > 4 & & ( ! Q_stricmp ( ( char * ) quakePath + len - 4 , " .pak " ) | | ! Q_stricmp ( ( char * ) quakePath + len - 4 , " .pk3 " ) ) )
isPak = true ;
if ( len > 9 & & ! Q_stricmp ( ( char * ) quakePath + len - 9 , " .filelist " ) )
isFilelist = true ;
2019-03-13 19:20:07 +00:00
2020-05-05 01:24:51 +00:00
// Knightmare- don't try again to download via HTTP a file that failed
if ( ! isFilelist /*&& !needList*/ ) {
if ( CL_CheckHTTPDownloadFailed ( ( char * ) quakePath ) ) {
Com_Printf ( " [HTTP] Refusing to download %s again, already in failed HTTP download list. \n " , quakePath ) ;
return false ;
}
}
2019-05-30 02:40:01 +00:00
if ( isFilelist ) // Knightmare- always insert filelist at head of queue
2019-03-13 19:20:07 +00:00
{
2019-05-30 02:40:01 +00:00
q = Z_TagMalloc ( sizeof ( dlqueue_t ) , 0 ) ;
q - > next = cls . downloadQueue . next ;
cls . downloadQueue . next = q ;
}
else if ( isPak ) // Knightmare- insert paks near head of queue, before first non-pak
{
last = & cls . downloadQueue ;
check = cls . downloadQueue . next ;
while ( check )
{
// avoid sending duplicate requests
if ( ! strcmp ( quakePath , check - > quakePath ) )
return true ;
2019-03-13 19:20:07 +00:00
2019-05-30 02:40:01 +00:00
if ( ! check - > isPak ) // insert before this entry
break ;
last = check ;
check = check - > next ;
}
q = Z_TagMalloc ( sizeof ( dlqueue_t ) , 0 ) ;
q - > next = check ;
last - > next = q ;
2019-03-13 19:20:07 +00:00
}
2019-05-30 02:40:01 +00:00
else
{
q = & cls . downloadQueue ;
while ( q - > next )
{
q = q - > next ;
2019-03-13 19:20:07 +00:00
2019-05-30 02:40:01 +00:00
// avoid sending duplicate requests
if ( ! strcmp ( quakePath , q - > quakePath ) )
return true ;
}
// q->next = Z_TagMalloc (sizeof(*q), TAGMALLOC_CLIENT_DOWNLOAD);
q - > next = Z_TagMalloc ( sizeof ( dlqueue_t ) , 0 ) ;
q = q - > next ;
q - > next = NULL ;
}
2019-03-13 19:20:07 +00:00
q - > state = DLQ_STATE_NOT_STARTED ;
2019-05-30 02:40:01 +00:00
// Q_strncpyz (q->quakePath, quakePath, sizeof(q->quakePath)-1);
Q_strncpyz ( q - > quakePath , quakePath , sizeof ( q - > quakePath ) ) ;
q - > isPak = isPak ; // Knightmare added
2019-03-13 19:20:07 +00:00
if ( needList )
{
2020-04-30 06:10:27 +00:00
// grab the filelist
// CL_QueueHTTPDownload (va("%s.filelist", cl.gamedir));
// YQ2 Q2pro download addition
if ( filelistUseGamedir ) {
CL_QueueHTTPDownload ( va ( " /%s/.filelist " , remoteGamedir ) , false ) ;
}
else {
// YQ2 uses /.filelist here instead of /<modname>.filelist, but I've found that doesn't work on R1Q2 servers
CL_QueueHTTPDownload ( va ( " /%s.filelist " , cl . gamedir ) , false ) ; // YQ2 change- added leading slash
}
2019-03-13 19:20:07 +00:00
2019-05-30 02:40:01 +00:00
// this is a nasty hack to let the server know what we're doing so admins don't
// get confused by a ton of people stuck in CNCT state. it's assumed the server
// is running r1q2 if we're even able to do http downloading so hopefully this
// won't spew an error msg.
2019-03-13 19:20:07 +00:00
// MSG_BeginWriting (clc_stringcmd);
// MSG_WriteString ("download http\n");
// MSG_EndWriting (&cls.netchan.message);
MSG_WriteByte ( & cls . netchan . message , clc_stringcmd ) ;
MSG_WriteString ( & cls . netchan . message , " download http \n " ) ;
}
2020-04-30 06:10:27 +00:00
// special case for map file lists, I really wanted a server-push mechanism for this, but oh well
2019-03-13 19:20:07 +00:00
len = strlen ( quakePath ) ;
2020-04-30 06:10:27 +00:00
if ( cl_http_filelists - > integer & & len > 4 & & ! Q_stricmp ( ( char * ) ( quakePath + len - 4 ) , " .bsp " ) )
2019-03-13 19:20:07 +00:00
{
char listPath [ MAX_OSPATH ] ;
char filePath [ MAX_OSPATH ] ;
2020-04-30 06:10:27 +00:00
// Com_sprintf (filePath, sizeof(filePath), "%s/%s", cl.gamedir, quakePath);
// YQ2 Q2pro download addition
// Use remoteGamedir for remote server path if set
if ( remoteGamedir [ 0 ] = = ' \0 ' )
Com_sprintf ( filePath , sizeof ( filePath ) , " /%s " , quakePath ) ;
else
Com_sprintf ( filePath , sizeof ( filePath ) , " /%s/%s " , remoteGamedir , quakePath ) ;
2019-03-13 19:20:07 +00:00
2019-10-08 07:39:06 +00:00
COM_StripExtension ( filePath , listPath , sizeof ( listPath ) ) ;
2019-03-13 19:20:07 +00:00
// strncat (listPath, ".filelist");
Q_strncatz ( listPath , " .filelist " , sizeof ( listPath ) ) ;
2020-04-30 06:10:27 +00:00
CL_QueueHTTPDownload ( listPath , false ) ;
2019-03-13 19:20:07 +00:00
}
2019-05-30 02:40:01 +00:00
// if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called.
2019-03-13 19:20:07 +00:00
pendingCount + + ;
return true ;
}
/*
= = = = = = = = = = = = = = =
CL_PendingHTTPDownloads
See if we ' re still busy with some downloads . Called by precacher just
before it loads the map since we could be downloading the map . If we ' re
busy still , it ' ll wait and CL_FinishHTTPDownload will pick up from where
it left .
= = = = = = = = = = = = = = =
*/
qboolean CL_PendingHTTPDownloads ( void )
{
dlqueue_t * q ;
if ( ! cls . downloadServer [ 0 ] )
return false ;
return pendingCount + handleCount ;
q = & cls . downloadQueue ;
while ( q - > next )
{
q = q - > next ;
if ( q - > state ! = DLQ_STATE_DONE )
return true ;
}
return false ;
}
/*
= = = = = = = = = = = = = = =
CL_CheckAndQueueDownload
Validate a path supplied by a filelist .
= = = = = = = = = = = = = = =
*/
static void CL_CheckAndQueueDownload ( char * path )
{
size_t length ;
char * ext ;
qboolean pak ;
qboolean gameLocal ;
StripHighBits ( path , 1 ) ;
length = strlen ( path ) ;
if ( length > = MAX_QPATH )
return ;
ext = strrchr ( path , ' . ' ) ;
if ( ! ext )
return ;
ext + + ;
if ( ! ext [ 0 ] )
return ;
Q_strlwr ( ext ) ;
if ( ! strcmp ( ext , " pak " ) | | ! strcmp ( ext , " pk3 " ) )
{
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] NOTICE: Filelist is requesting a pak file (%s) \n " , path ) ;
2019-03-13 19:20:07 +00:00
pak = true ;
}
else
pak = false ;
if ( ! pak & & strcmp ( ext , " pcx " ) & & strcmp ( ext , " wal " ) & & strcmp ( ext , " wav " ) & & strcmp ( ext , " md2 " ) & &
strcmp ( ext , " sp2 " ) & & strcmp ( ext , " tga " ) & & strcmp ( ext , " png " ) & & strcmp ( ext , " jpg " ) & &
strcmp ( ext , " bsp " ) & & strcmp ( ext , " ent " ) & & strcmp ( ext , " txt " ) & & strcmp ( ext , " dm2 " ) & &
strcmp ( ext , " loc " ) & & strcmp ( ext , " md3 " ) & & strcmp ( ext , " script " ) & & strcmp ( ext , " shader " ) )
{
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] WARNING: Illegal file type '%s' in filelist. \n " , MakePrintable ( path , length ) ) ;
2019-03-13 19:20:07 +00:00
return ;
}
if ( path [ 0 ] = = ' @ ' )
{
if ( pak )
{
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] WARNING: @ prefix used on a pak file (%s) in filelist. \n " , MakePrintable ( path , length ) ) ;
2019-03-13 19:20:07 +00:00
return ;
}
gameLocal = true ;
path + + ;
length - - ;
}
else
gameLocal = false ;
if ( strstr ( path , " .. " ) | | ! IsValidChar ( path [ 0 ] ) | | ! IsValidChar ( path [ length - 1 ] ) | | strstr ( path , " // " ) | |
strchr ( path , ' \\ ' ) | | ( ! pak & & ! strchr ( path , ' / ' ) ) | | ( pak & & strchr ( path , ' / ' ) ) )
{
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] WARNING: Illegal path '%s' in filelist. \n " , MakePrintable ( path , length ) ) ;
2019-03-13 19:20:07 +00:00
return ;
}
// by definition paks are game-local
if ( gameLocal | | pak )
{
qboolean exists ;
FILE * f ;
char gamePath [ MAX_OSPATH ] ;
if ( pak )
{
2020-04-30 06:10:27 +00:00
Com_sprintf ( gamePath , sizeof ( gamePath ) , " %s/%s " , FS_Downloaddir ( ) , path ) ; // was FS_Gamedir()
2019-03-13 19:20:07 +00:00
f = fopen ( gamePath , " rb " ) ;
if ( ! f )
{
2020-04-30 06:10:27 +00:00
if ( ! stricmp ( FS_Downloaddir ( ) , FS_Gamedir ( ) ) ) // if fs_gamedir and fs_downloaddir are the same, don't bother trying fs_gamedir
{
exists = false ;
}
else
{
Com_sprintf ( gamePath , sizeof ( gamePath ) , " %s/%s " , FS_Gamedir ( ) , path ) ;
f = fopen ( gamePath , " rb " ) ;
if ( ! f )
{
exists = false ;
}
else
{
// Com_Printf ("[HTTP] NOTICE: pak file (%s) specified in filelist already exists\n", gamePath);
exists = true ;
fclose ( f ) ;
}
}
2019-03-13 19:20:07 +00:00
}
else
{
2019-06-10 16:16:13 +00:00
// Com_Printf ("[HTTP] NOTICE: pak file (%s) specified in filelist already exists\n", gamePath);
2019-03-13 19:20:07 +00:00
exists = true ;
fclose ( f ) ;
}
}
else
{
// exists = FS_ExistsInGameDir (path);
2020-04-30 06:10:27 +00:00
exists = ( FS_DownloadFileExists ( path ) | | FS_LocalFileExists ( path ) ) ;
2019-03-13 19:20:07 +00:00
}
if ( ! exists )
{
2020-04-30 06:10:27 +00:00
if ( CL_QueueHTTPDownload ( path , false ) )
2019-03-13 19:20:07 +00:00
{
2019-05-30 02:40:01 +00:00
// Paks get bumped to the top and HTTP switches to single downloading.
// This prevents someone on 28k dialup trying to do both the main .pak
// and referenced configstrings data at once.
// Knightmare- moved this functionality inside CL_QueueHTTPDownload()
/* if (pak)
2019-03-13 19:20:07 +00:00
{
dlqueue_t * q , * last ;
last = q = & cls . downloadQueue ;
while ( q - > next )
{
last = q ;
q = q - > next ;
}
last - > next = NULL ;
q - > next = cls . downloadQueue . next ;
cls . downloadQueue . next = q ;
2019-05-30 02:40:01 +00:00
} */
2019-03-13 19:20:07 +00:00
}
}
else
{
2019-06-10 16:16:13 +00:00
// Com_Printf ("[HTTP] NOTICE: requested file (%s) already exists\n", path);
2019-03-13 19:20:07 +00:00
}
}
else
{
CL_CheckOrDownloadFile ( path ) ;
}
}
/*
= = = = = = = = = = = = = = =
CL_ParseFileList
A filelist is in memory , scan and validate it and queue up the files .
= = = = = = = = = = = = = = =
*/
static void CL_ParseFileList ( dlhandle_t * dl )
{
char * list ;
char * p ;
2020-04-30 06:10:27 +00:00
if ( ! cl_http_filelists - > integer )
2019-03-13 19:20:07 +00:00
return ;
list = dl - > tempBuffer ;
for ( ; ; )
{
p = strchr ( list , ' \n ' ) ;
if ( p )
{
p [ 0 ] = 0 ;
if ( list [ 0 ] )
CL_CheckAndQueueDownload ( list ) ;
list = p + 1 ;
}
else
{
if ( list [ 0 ] )
CL_CheckAndQueueDownload ( list ) ;
break ;
}
}
Z_Free ( dl - > tempBuffer ) ;
dl - > tempBuffer = NULL ;
}
/*
= = = = = = = = = = = = = = =
CL_ReVerifyHTTPQueue
A pak file just downloaded , let ' s see if we can remove some stuff from
the queue which is in the . pak .
= = = = = = = = = = = = = = =
*/
static void CL_ReVerifyHTTPQueue ( void )
{
dlqueue_t * q ;
q = & cls . downloadQueue ;
pendingCount = 0 ;
while ( q - > next )
{
q = q - > next ;
if ( q - > state = = DLQ_STATE_NOT_STARTED )
{
2019-05-30 02:40:01 +00:00
// Knightmare- don't check for paks inside other paks!
if ( ! q - > isPak & & FS_LoadFile ( q - > quakePath , NULL ) ! = - 1 )
2019-03-13 19:20:07 +00:00
q - > state = DLQ_STATE_DONE ;
else
pendingCount + + ;
}
}
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_Cleanup
Quake II is exiting or we ' re changing servers . Clean up .
= = = = = = = = = = = = = = =
*/
void CL_HTTP_Cleanup ( qboolean fullShutdown )
{
dlhandle_t * dl ;
int i ;
if ( fullShutdown & & httpDown )
return ;
for ( i = 0 ; i < 4 ; i + + )
{
dl = & cls . HTTPHandles [ i ] ;
if ( dl - > file )
{
fclose ( dl - > file ) ;
remove ( dl - > filePath ) ;
dl - > file = NULL ;
}
if ( dl - > tempBuffer )
{
Z_Free ( dl - > tempBuffer ) ;
dl - > tempBuffer = NULL ;
}
if ( dl - > curl )
{
if ( multi )
curl_multi_remove_handle ( multi , dl - > curl ) ;
curl_easy_cleanup ( dl - > curl ) ;
dl - > curl = NULL ;
}
dl - > queueEntry = NULL ;
}
if ( multi )
{
curl_multi_cleanup ( multi ) ;
multi = NULL ;
}
if ( fullShutdown )
{
curl_global_cleanup ( ) ;
httpDown = true ;
}
}
// Knightmare added
/*
= = = = = = = = = = = = = = =
CL_HTTP_ResetMapAbort
Resets the thisMapAbort boolean .
= = = = = = = = = = = = = = =
*/
void CL_HTTP_ResetMapAbort ( void )
{
thisMapAbort = false ;
}
// end Knightmare
/*
= = = = = = = = = = = = = = =
CL_FinishHTTPDownload
A download finished , find out what it was , whether there were any errors and
if so , how severe . If none , rename file and other such stuff .
= = = = = = = = = = = = = = =
*/
static void CL_FinishHTTPDownload ( void )
{
size_t i ;
int msgs_in_queue ;
CURLMsg * msg ;
CURLcode result ;
dlhandle_t * dl ;
CURL * curl ;
long responseCode ;
double timeTaken ;
double fileSize ;
char tempName [ MAX_OSPATH ] ;
qboolean isFile ;
2020-04-30 06:10:27 +00:00
size_t len ;
2019-03-13 19:20:07 +00:00
do
{
msg = curl_multi_info_read ( multi , & msgs_in_queue ) ;
if ( ! msg )
{
Com_Printf ( " CL_FinishHTTPDownload: Odd, no message for us... \n " ) ;
return ;
}
if ( msg - > msg ! = CURLMSG_DONE )
{
Com_Printf ( " CL_FinishHTTPDownload: Got some weird message... \n " ) ;
continue ;
}
curl = msg - > easy_handle ;
// curl doesn't provide reverse-lookup of the void * ptr, so search for it
for ( i = 0 ; i < 4 ; i + + )
{
if ( cls . HTTPHandles [ i ] . curl = = curl )
{
dl = & cls . HTTPHandles [ i ] ;
break ;
}
}
if ( i = = 4 )
Com_Error ( ERR_DROP , " CL_FinishHTTPDownload: Handle not found " ) ;
// we mark everything as done even if it errored to prevent multiple
// attempts.
dl - > queueEntry - > state = DLQ_STATE_DONE ;
2020-04-30 06:10:27 +00:00
// filelist processing is done on read
2019-03-13 19:20:07 +00:00
if ( dl - > file )
isFile = true ;
else
isFile = false ;
if ( isFile )
{
fclose ( dl - > file ) ;
dl - > file = NULL ;
}
2020-04-30 06:10:27 +00:00
// might be aborted
2019-03-13 19:20:07 +00:00
if ( pendingCount )
pendingCount - - ;
handleCount - - ;
2020-04-30 06:10:27 +00:00
// Com_Printf ("finished dl: hc = %d\n", LOG_GENERAL, handleCount);
2019-03-13 19:20:07 +00:00
cls . downloadname [ 0 ] = 0 ;
cls . downloadposition = 0 ;
result = msg - > data . result ;
switch ( result )
{
// for some reason curl returns CURLE_OK for a 404...
case CURLE_HTTP_RETURNED_ERROR :
case CURLE_OK :
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & responseCode ) ;
if ( responseCode = = 404 )
{
2020-04-30 06:10:27 +00:00
len = strlen ( dl - > queueEntry - > quakePath ) ;
if ( len > 4 & & ( ! strcmp ( dl - > queueEntry - > quakePath + len - 4 , " .pak " ) | | ! strcmp ( dl - > queueEntry - > quakePath + len - 4 , " .pk3 " ) ) )
2019-03-13 19:20:07 +00:00
downloading_pak = false ;
2019-04-05 05:25:40 +00:00
if ( isFile ) {
2019-03-13 19:20:07 +00:00
remove ( dl - > filePath ) ;
2019-04-05 05:25:40 +00:00
}
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] (%s): 404 File Not Found [%d remaining files] \n " , dl - > queueEntry - > quakePath , pendingCount ) ;
2019-04-05 05:25:40 +00:00
/* curl_easy_getinfo (curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
// Knightmare- ignore this, doesn't need to be fatal
2019-03-13 19:20:07 +00:00
if ( fileSize > 512 )
{
// ick
isFile = false ;
result = CURLE_FILESIZE_EXCEEDED ;
Com_Printf ( " Oversized 404 body received (%d bytes), aborting HTTP downloading. \n " , ( int ) fileSize ) ;
}
2019-04-05 05:25:40 +00:00
else */
2019-03-13 19:20:07 +00:00
{
curl_multi_remove_handle ( multi , dl - > curl ) ;
2020-04-30 06:10:27 +00:00
// Fall back to UDP download for this map if failure on .bsp
/* if ( !strncmp(dl->queueEntry->quakePath, "maps/", 5) && !strcmp(dl->queueEntry->quakePath + i - 4, ".bsp") )
2019-03-13 19:20:07 +00:00
{
2020-04-30 06:10:27 +00:00
Com_Printf ( " [HTTP]: failed to download %s, falling back to UDP until next map. \n " , dl - > queueEntry - > quakePath ) ;
2019-03-13 19:20:07 +00:00
thisMapAbort = true ;
CL_CancelHTTPDownloads ( false ) ;
CL_ResetPrecacheCheck ( ) ;
}
2020-04-30 06:10:27 +00:00
else { */
2020-05-05 01:24:51 +00:00
// Knightmare- added this entry to faild HTTP downloads list
CL_AddToFailedHTTPDownloadList ( dl - > queueEntry - > quakePath ) ;
2020-04-30 06:10:27 +00:00
// Remove queue entry from CURL multihandle queue
CL_RemoveDownloadFromQueue ( dl - > queueEntry ) ;
dl - > queueEntry = NULL ;
// }
2019-03-13 19:20:07 +00:00
// end Knightmare
2020-04-30 06:10:27 +00:00
// YQ2 UDP fallback addition
if ( isFile ) {
downloadError = true ;
}
2019-03-13 19:20:07 +00:00
continue ;
}
}
else if ( responseCode = = 200 )
{
if ( ! isFile & & ! abortDownloads )
CL_ParseFileList ( dl ) ;
break ;
}
2019-04-05 05:25:40 +00:00
// every other code is treated as fatal, fallthrough here
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] Bad response code %d for %s, aborting HTTP downloading. \n " , responseCode , dl - > queueEntry - > quakePath ) ;
2019-03-13 19:20:07 +00:00
2020-04-30 06:10:27 +00:00
// fatal error, disable http
2019-03-13 19:20:07 +00:00
case CURLE_COULDNT_RESOLVE_HOST :
case CURLE_COULDNT_CONNECT :
case CURLE_COULDNT_RESOLVE_PROXY :
2019-04-05 05:25:40 +00:00
if ( isFile ) {
2019-03-13 19:20:07 +00:00
remove ( dl - > filePath ) ;
2019-04-05 05:25:40 +00:00
}
2019-05-30 02:40:01 +00:00
// Com_Printf ("[HTTP] Fatal error: %s\n", curl_easy_strerror (result));
Com_Printf ( " [HTTP] Fatal error: %s \n " , CURL_ERROR ( result ) ) ;
2019-03-13 19:20:07 +00:00
curl_multi_remove_handle ( multi , dl - > curl ) ;
if ( abortDownloads )
continue ;
CL_CancelHTTPDownloads ( true ) ;
continue ;
default :
2020-04-30 06:10:27 +00:00
len = strlen ( dl - > queueEntry - > quakePath ) ;
if ( len > 4 & & ( ! strcmp ( dl - > queueEntry - > quakePath + len - 4 , " .pak " ) | | ! strcmp ( dl - > queueEntry - > quakePath + len - 4 , " .pk3 " ) ) )
2019-03-13 19:20:07 +00:00
downloading_pak = false ;
2019-04-05 05:25:40 +00:00
if ( isFile ) {
2019-03-13 19:20:07 +00:00
remove ( dl - > filePath ) ;
2019-04-05 05:25:40 +00:00
}
2019-05-30 02:40:01 +00:00
// Com_Printf ("[HTTP] download failed: %s\n", curl_easy_strerror (result));
Com_Printf ( " [HTTP] download failed: %s \n " , CURL_ERROR ( result ) ) ;
2019-03-13 19:20:07 +00:00
curl_multi_remove_handle ( multi , dl - > curl ) ;
continue ;
}
if ( isFile )
{
2020-04-30 06:10:27 +00:00
// rename the temp file
Com_sprintf ( tempName , sizeof ( tempName ) , " %s/%s " , FS_Downloaddir ( ) , dl - > queueEntry - > quakePath ) ; // was FS_Gamedir()
2019-03-13 19:20:07 +00:00
if ( rename ( dl - > filePath , tempName ) )
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] Failed to rename %s for some odd reason... " , dl - > filePath ) ;
2019-03-13 19:20:07 +00:00
// a pak file is very special...
i = strlen ( tempName ) ;
if ( ! strcmp ( tempName + i - 4 , " .pak " ) | | ! strcmp ( tempName + i - 4 , " .pk3 " ) )
{
// FS_FlushCache ();
// FS_ReloadPAKs ();
2020-04-30 06:10:27 +00:00
// Knightmare- just add the pk3 / pak file
2019-03-13 19:20:07 +00:00
if ( ! strcmp ( tempName + i - 4 , " .pk3 " ) )
2020-04-30 06:10:27 +00:00
FS_AddPK3File ( tempName , false ) ;
2019-03-13 19:20:07 +00:00
else
2020-04-30 06:10:27 +00:00
FS_AddPAKFile ( tempName , false ) ;
2019-03-13 19:20:07 +00:00
CL_ReVerifyHTTPQueue ( ) ;
downloading_pak = false ;
}
}
2020-04-30 06:10:27 +00:00
// show some stats
2019-03-13 19:20:07 +00:00
curl_easy_getinfo ( curl , CURLINFO_TOTAL_TIME , & timeTaken ) ;
curl_easy_getinfo ( curl , CURLINFO_SIZE_DOWNLOAD , & fileSize ) ;
2020-04-30 06:10:27 +00:00
// FIXME:
// Technically i shouldn't need to do this as curl will auto reuse the
// existing handle when you change the URL. however, the handleCount goes
// all weird when reusing a download slot in this way. if you can figure
// out why, please let me know.
2019-03-13 19:20:07 +00:00
curl_multi_remove_handle ( multi , dl - > curl ) ;
2019-05-30 02:40:01 +00:00
Com_Printf ( " [HTTP] (%s): %.f bytes, %.2fkB/sec [%d remaining files] \n " , dl - > queueEntry - > quakePath , fileSize , ( fileSize / 1024.0 ) / timeTaken , pendingCount ) ;
2020-04-30 06:10:27 +00:00
}
while ( msgs_in_queue > 0 ) ;
2019-03-13 19:20:07 +00:00
// FS_FlushCache ();
if ( handleCount = = 0 )
{
if ( abortDownloads = = HTTPDL_ABORT_SOFT )
abortDownloads = HTTPDL_ABORT_NONE ;
else if ( abortDownloads = = HTTPDL_ABORT_HARD ) {
// FS: Added because Whale's Weapons HTTP server rejects you after a lot of 404s. Then you lose HTTP until a hard reconnect.
Q_strncpyz ( cls . downloadServerRetry , cls . downloadServer , sizeof ( cls . downloadServerRetry ) ) ;
cls . downloadServer [ 0 ] = 0 ;
}
}
// done current batch, see if we have more to dl - maybe a .bsp needs downloaded
if ( cls . state = = ca_connected & & ! CL_PendingHTTPDownloads ( ) )
CL_RequestNextDownload ( ) ;
}
/*
= = = = = = = = = = = = = = =
CL_GetFreeDLHandle
Find a free download handle to start another queue entry on .
= = = = = = = = = = = = = = =
*/
static dlhandle_t * CL_GetFreeDLHandle ( void )
{
dlhandle_t * dl ;
int i ;
for ( i = 0 ; i < 4 ; i + + )
{
dl = & cls . HTTPHandles [ i ] ;
if ( ! dl - > queueEntry | | dl - > queueEntry - > state = = DLQ_STATE_DONE )
return dl ;
}
return NULL ;
}
/*
= = = = = = = = = = = = = = =
CL_StartNextHTTPDownload
Start another HTTP download if possible .
= = = = = = = = = = = = = = =
*/
static void CL_StartNextHTTPDownload ( void )
{
dlqueue_t * q ;
q = & cls . downloadQueue ;
while ( q - > next )
{
q = q - > next ;
if ( q - > state = = DLQ_STATE_NOT_STARTED )
{
size_t len ;
dlhandle_t * dl ;
dl = CL_GetFreeDLHandle ( ) ;
if ( ! dl )
return ;
CL_StartHTTPDownload ( q , dl ) ;
// ugly hack for pak file single downloading
len = strlen ( q - > quakePath ) ;
if ( len > 4 & & ( ! Q_stricmp ( q - > quakePath + len - 4 , " .pak " ) | | ! Q_stricmp ( q - > quakePath + len - 4 , " .pk3 " ) ) )
downloading_pak = true ;
break ;
}
}
}
2020-04-30 06:10:27 +00:00
/*
= = = = = = = = = = = = = = =
CL_CheckHTTPError
YQ2 UDP fallback addition
Checks if thre was an error .
Returns true if yes , false if no .
Resets flag if it was set .
= = = = = = = = = = = = = = =
*/
qboolean CL_CheckHTTPError ( void )
{
if ( downloadError ) {
downloadError = false ;
return true ;
}
else {
return false ;
}
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_EnableGenericFilelist
YQ2 UDP fallback addition
Enables generic filelist download
starting with the next file .
= = = = = = = = = = = = = = =
*/
void CL_HTTP_EnableGenericFilelist ( void )
{
downloadFileList = true ;
2020-05-05 01:24:51 +00:00
// Knightmare- also re-init failed HTTP download list
// here, as we'll be downloading on a different path
CL_InitFailedHTTPDownloadList ( ) ;
2020-04-30 06:10:27 +00:00
}
/*
= = = = = = = = = = = = = = =
CL_HTTP_SetDownloadGamedir
YQ2 Q2pro download addition
Sets the gamedir to be used by the URL generator
to determine the remote file path .
= = = = = = = = = = = = = = =
*/
void CL_HTTP_SetDownloadGamedir ( const char * gamedir )
{
Q_strncpyz ( remoteGamedir , gamedir , sizeof ( remoteGamedir ) ) ;
}
2019-03-13 19:20:07 +00:00
/*
= = = = = = = = = = = = = = =
CL_RunHTTPDownloads
This calls curl_multi_perform do actually do stuff . Called every frame while
connecting to minimise latency . Also starts new downloads if we ' re not doing
the maximum already .
= = = = = = = = = = = = = = =
*/
void CL_RunHTTPDownloads ( void )
{
int newHandleCount ;
CURLMcode ret ;
if ( ! cls . downloadServer [ 0 ] )
return ;
2020-04-30 06:10:27 +00:00
// Com_Printf ("handle %d, pending %d\n", LOG_GENERAL, handleCount, pendingCount);
2019-03-13 19:20:07 +00:00
2020-04-30 06:10:27 +00:00
// not enough downloads running, queue some more!
if ( pendingCount & & ( abortDownloads = = HTTPDL_ABORT_NONE ) & &
! downloading_pak & & ( handleCount < cl_http_max_connections - > integer ) )
2019-03-13 19:20:07 +00:00
CL_StartNextHTTPDownload ( ) ;
do
{
ret = curl_multi_perform ( multi , & newHandleCount ) ;
if ( newHandleCount < handleCount )
{
2020-04-30 06:10:27 +00:00
// Com_Printf ("runnd dl: hc = %d, nc = %d\n", LOG_GENERAL, handleCount, newHandleCount);
// hmm, something either finished or errored out.
2019-03-13 19:20:07 +00:00
CL_FinishHTTPDownload ( ) ;
handleCount = newHandleCount ;
}
}
while ( ret = = CURLM_CALL_MULTI_PERFORM ) ;
if ( ret ! = CURLM_OK )
{
Com_Printf ( " curl_multi_perform error. Aborting HTTP downloads. \n " ) ;
CL_CancelHTTPDownloads ( true ) ;
}
2020-04-30 06:10:27 +00:00
// not enough downloads running, queue some more!
if ( pendingCount & & ( abortDownloads = = HTTPDL_ABORT_NONE ) & &
! downloading_pak & & ( handleCount < cl_http_max_connections - > integer ) )
2019-03-13 19:20:07 +00:00
CL_StartNextHTTPDownload ( ) ;
}
# endif