mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
3ce18b138c
Simplified loading and saving of cvars in slider menu control. Enlarged text in Game, Multiplayer, and Options menus. Fixed repeat of failed file causing HTTP downloads to restart. Added cl_zoommode cvar to simplify Lazarus zoom command. Changed zoom command to use new cl_zoommode cvar in default Lazarus and missionpack DLLs. Removed unused "crossh" cvar in default Lazarus and missionpack DLLs. Fixed Makron not having a classname when spawned from Jorg in default Lazarus and missionpack DLLs. Made Tactician Gunner ignore small amounts of damage in missionpack DLL.
1460 lines
35 KiB
C
1460 lines
35 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1997-2001 Id Software, Inc.
|
|
|
|
This file is part of Quake 2 source code.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
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
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "client.h"
|
|
|
|
#ifdef USE_CURL
|
|
|
|
enum
|
|
{
|
|
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;
|
|
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
|
|
|
|
/*
|
|
===============================
|
|
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.
|
|
*/
|
|
|
|
|
|
// 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 256
|
|
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
|
|
|
|
|
|
/*
|
|
===============
|
|
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, sizeof(cls.downloadname), dl->queueEntry->quakePath);
|
|
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, len, ptr);
|
|
|
|
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;
|
|
|
|
// 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.
|
|
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;
|
|
if (size > sizeof(buff)) // Q_strncpyz takes size, not size-1
|
|
size = sizeof(buff);
|
|
Q_strncpyz (buff, size, data);
|
|
Com_Printf ("[HTTP] DEBUG: %s\n", buff);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*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
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
char remoteFilePath[MAX_OSPATH]; // Knightmare added
|
|
char escapedFilePath[MAX_QPATH*4];
|
|
|
|
// yet another hack to accomodate filelists, how i wish i could push :(
|
|
// NULL file handle indicates filelist.
|
|
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
|
|
|
|
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);
|
|
|
|
// CL_EscapeHTTPPath (dl->filePath, escapedFilePath);
|
|
CL_EscapeHTTPPath (remoteFilePath, escapedFilePath);
|
|
|
|
// strncat (dl->filePath, ".tmp");
|
|
Q_strncatz (dl->filePath, sizeof(dl->filePath), ".tmp");
|
|
|
|
FS_CreatePath (dl->filePath);
|
|
|
|
// don't bother with http resume... too annoying if server doesn't support it.
|
|
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
|
|
// CL_RemoveHTTPDownload (entry->quakePath);
|
|
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;
|
|
char *fixedURL = NULL;
|
|
size_t URLlen;
|
|
|
|
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;
|
|
|
|
// 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, sizeof(cls.downloadServer), fixedURL);
|
|
free(fixedURL);
|
|
fixedURL = NULL;
|
|
|
|
// 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;
|
|
downloadError = false; // YQ2 UDP fallback addition- reset this for new server
|
|
|
|
CL_InitFailedHTTPDownloadList (); // Knightmare- init failed HTTP downloads list
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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.
|
|
===============
|
|
*/
|
|
qboolean CL_QueueHTTPDownload (const char *quakePath, qboolean filelistUseGamedir)
|
|
{
|
|
size_t len;
|
|
dlqueue_t *q, *check, *last;
|
|
qboolean needList = false, isPak = false, isFilelist = false;
|
|
|
|
// no http server (or we got booted)
|
|
if (!cls.downloadServer[0] || abortDownloads || thisMapAbort || !cl_http_downloads->integer)
|
|
return false;
|
|
|
|
// needList = false;
|
|
|
|
// first download queued, so we want the mod filelist
|
|
// if ( !cls.downloadQueue.next && cl_http_filelists->integer ) {
|
|
if ( downloadFileList && cl_http_filelists->integer ) {
|
|
needList = true;
|
|
downloadFileList = false;
|
|
}
|
|
|
|
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;
|
|
|
|
// 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 true;
|
|
}
|
|
}
|
|
|
|
if (isFilelist) // Knightmare- always insert filelist at head of queue
|
|
{
|
|
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;
|
|
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
q = &cls.downloadQueue;
|
|
while (q->next)
|
|
{
|
|
q = q->next;
|
|
|
|
// 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;
|
|
}
|
|
|
|
q->state = DLQ_STATE_NOT_STARTED;
|
|
// Q_strncpyz (q->quakePath, sizeof(q->quakePath)-1, quakePath);
|
|
Q_strncpyz (q->quakePath, sizeof(q->quakePath), quakePath);
|
|
q->isPak = isPak; // Knightmare added
|
|
|
|
if (needList)
|
|
{
|
|
// 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
|
|
}
|
|
|
|
// 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.
|
|
// 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");
|
|
}
|
|
|
|
// special case for map file lists, I really wanted a server-push mechanism for this, but oh well
|
|
len = strlen (quakePath);
|
|
if (cl_http_filelists->integer && len > 4 && !Q_stricmp ((char *)(quakePath + len - 4), ".bsp"))
|
|
{
|
|
char listPath[MAX_OSPATH];
|
|
char filePath[MAX_OSPATH];
|
|
|
|
// 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);
|
|
|
|
COM_StripExtension (filePath, listPath, sizeof(listPath));
|
|
// strncat (listPath, ".filelist");
|
|
Q_strncatz (listPath, sizeof(listPath), ".filelist");
|
|
|
|
CL_QueueHTTPDownload (listPath, false);
|
|
}
|
|
|
|
// if a download entry has made it this far, CL_FinishHTTPDownload is guaranteed to be called.
|
|
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") )
|
|
{
|
|
Com_Printf ("[HTTP] NOTICE: Filelist is requesting a pak file (%s)\n", path);
|
|
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"))
|
|
{
|
|
Com_Printf ("[HTTP] WARNING: Illegal file type '%s' in filelist.\n", MakePrintable(path, length));
|
|
return;
|
|
}
|
|
|
|
if (path[0] == '@')
|
|
{
|
|
if (pak)
|
|
{
|
|
Com_Printf ("[HTTP] WARNING: @ prefix used on a pak file (%s) in filelist.\n", MakePrintable(path, length));
|
|
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, '/')))
|
|
{
|
|
Com_Printf ("[HTTP] WARNING: Illegal path '%s' in filelist.\n", MakePrintable(path, length));
|
|
return;
|
|
}
|
|
|
|
// by definition paks are game-local
|
|
if (gameLocal || pak)
|
|
{
|
|
qboolean exists;
|
|
FILE *f;
|
|
char gamePath[MAX_OSPATH];
|
|
|
|
if (pak)
|
|
{
|
|
Com_sprintf (gamePath, sizeof(gamePath),"%s/%s", FS_Downloaddir(), path); // was FS_Gamedir()
|
|
f = fopen (gamePath, "rb");
|
|
if (!f)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Com_Printf ("[HTTP] NOTICE: pak file (%s) specified in filelist already exists\n", gamePath);
|
|
exists = true;
|
|
fclose (f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// exists = FS_ExistsInGameDir (path);
|
|
exists = (FS_DownloadFileExists (path) || FS_LocalFileExists(path));
|
|
}
|
|
|
|
if (!exists)
|
|
{
|
|
if (CL_QueueHTTPDownload (path, false))
|
|
{
|
|
// 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)
|
|
{
|
|
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;
|
|
}*/
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Com_Printf ("[HTTP] NOTICE: requested file (%s) already exists\n", path);
|
|
}
|
|
}
|
|
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;
|
|
|
|
if (!cl_http_filelists->integer)
|
|
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)
|
|
{
|
|
// Knightmare- don't check for paks inside other paks!
|
|
if (!q->isPak && FS_LoadFile (q->quakePath, NULL) != -1)
|
|
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;
|
|
size_t len;
|
|
|
|
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;
|
|
|
|
// filelist processing is done on read
|
|
if (dl->file)
|
|
isFile = true;
|
|
else
|
|
isFile = false;
|
|
|
|
if (isFile)
|
|
{
|
|
fclose (dl->file);
|
|
dl->file = NULL;
|
|
}
|
|
|
|
// might be aborted
|
|
if (pendingCount)
|
|
pendingCount--;
|
|
handleCount--;
|
|
// Com_Printf ("finished dl: hc = %d\n", LOG_GENERAL, handleCount);
|
|
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)
|
|
{
|
|
len = strlen (dl->queueEntry->quakePath);
|
|
if ( len > 4 && ( !strcmp (dl->queueEntry->quakePath + len - 4, ".pak") || !strcmp (dl->queueEntry->quakePath + len - 4, ".pk3")) )
|
|
downloading_pak = false;
|
|
|
|
if (isFile) {
|
|
remove (dl->filePath);
|
|
}
|
|
Com_Printf ("[HTTP] (%s): 404 File Not Found [%d remaining files]\n", dl->queueEntry->quakePath, pendingCount);
|
|
/* curl_easy_getinfo (curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
|
|
|
|
// Knightmare- ignore this, doesn't need to be fatal
|
|
if (fileSize > 512)
|
|
{
|
|
// ick
|
|
isFile = false;
|
|
result = CURLE_FILESIZE_EXCEEDED;
|
|
Com_Printf ("Oversized 404 body received (%d bytes), aborting HTTP downloading.\n", (int)fileSize);
|
|
}
|
|
else */
|
|
{
|
|
curl_multi_remove_handle (multi, dl->curl);
|
|
|
|
// 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") )
|
|
{
|
|
Com_Printf ("[HTTP]: failed to download %s, falling back to UDP until next map.\n", dl->queueEntry->quakePath);
|
|
thisMapAbort = true;
|
|
CL_CancelHTTPDownloads (false);
|
|
CL_ResetPrecacheCheck ();
|
|
}
|
|
else { */
|
|
// Knightmare- added this entry to faild HTTP downloads list
|
|
CL_AddToFailedHTTPDownloadList (dl->queueEntry->quakePath);
|
|
|
|
// Remove queue entry from CURL multihandle queue
|
|
CL_RemoveDownloadFromQueue (dl->queueEntry);
|
|
dl->queueEntry = NULL;
|
|
// }
|
|
// end Knightmare
|
|
// YQ2 UDP fallback addition
|
|
if (isFile) {
|
|
downloadError = true;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
else if (responseCode == 200)
|
|
{
|
|
if (!isFile && !abortDownloads)
|
|
CL_ParseFileList (dl);
|
|
break;
|
|
}
|
|
|
|
// every other code is treated as fatal, fallthrough here
|
|
Com_Printf ("[HTTP] Bad response code %d for %s, aborting HTTP downloading.\n", responseCode, dl->queueEntry->quakePath);
|
|
|
|
// fatal error, disable http
|
|
case CURLE_COULDNT_RESOLVE_HOST:
|
|
case CURLE_COULDNT_CONNECT:
|
|
case CURLE_COULDNT_RESOLVE_PROXY:
|
|
if (isFile) {
|
|
remove (dl->filePath);
|
|
}
|
|
// Com_Printf ("[HTTP] Fatal error: %s\n", curl_easy_strerror (result));
|
|
Com_Printf ("[HTTP] Fatal error: %s\n", CURL_ERROR(result));
|
|
curl_multi_remove_handle (multi, dl->curl);
|
|
if (abortDownloads)
|
|
continue;
|
|
CL_CancelHTTPDownloads (true);
|
|
continue;
|
|
default:
|
|
len = strlen (dl->queueEntry->quakePath);
|
|
if (len > 4 && (!strcmp (dl->queueEntry->quakePath + len - 4, ".pak") || !strcmp (dl->queueEntry->quakePath + len - 4, ".pk3")) )
|
|
downloading_pak = false;
|
|
if (isFile) {
|
|
remove (dl->filePath);
|
|
}
|
|
// Com_Printf ("[HTTP] download failed: %s\n", curl_easy_strerror (result));
|
|
Com_Printf ("[HTTP] download failed: %s\n", CURL_ERROR(result));
|
|
curl_multi_remove_handle (multi, dl->curl);
|
|
continue;
|
|
}
|
|
|
|
if (isFile)
|
|
{
|
|
// rename the temp file
|
|
Com_sprintf (tempName, sizeof(tempName), "%s/%s", FS_Downloaddir(), dl->queueEntry->quakePath); // was FS_Gamedir()
|
|
|
|
if (rename (dl->filePath, tempName))
|
|
Com_Printf ("[HTTP] Failed to rename %s for some odd reason...", dl->filePath);
|
|
|
|
// a pak file is very special...
|
|
i = strlen (tempName);
|
|
if ( !strcmp (tempName + i - 4, ".pak") || !strcmp (tempName + i - 4, ".pk3") )
|
|
{
|
|
// FS_FlushCache ();
|
|
// FS_ReloadPAKs ();
|
|
// Knightmare- just add the pk3 / pak file
|
|
if (!strcmp (tempName + i - 4, ".pk3"))
|
|
FS_AddPK3File (tempName, false);
|
|
else
|
|
FS_AddPAKFile (tempName, false);
|
|
|
|
CL_ReVerifyHTTPQueue ();
|
|
downloading_pak = false;
|
|
}
|
|
}
|
|
|
|
// show some stats
|
|
curl_easy_getinfo (curl, CURLINFO_TOTAL_TIME, &timeTaken);
|
|
curl_easy_getinfo (curl, CURLINFO_SIZE_DOWNLOAD, &fileSize);
|
|
|
|
// 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.
|
|
curl_multi_remove_handle (multi, dl->curl);
|
|
|
|
Com_Printf ("[HTTP] (%s): %.f bytes, %.2fkB/sec [%d remaining files]\n", dl->queueEntry->quakePath, fileSize, (fileSize / 1024.0) / timeTaken, pendingCount);
|
|
}
|
|
while (msgs_in_queue > 0);
|
|
|
|
// 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, sizeof(cls.downloadServerRetry), cls.downloadServer);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
|
|
// Knightmare- also re-init failed HTTP download list
|
|
// here, as we'll be downloading on a different path
|
|
CL_InitFailedHTTPDownloadList ();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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, sizeof(remoteGamedir), gamedir);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
|
|
// Com_Printf ("handle %d, pending %d\n", LOG_GENERAL, handleCount, pendingCount);
|
|
|
|
// not enough downloads running, queue some more!
|
|
if (pendingCount && (abortDownloads == HTTPDL_ABORT_NONE) &&
|
|
!downloading_pak && (handleCount < cl_http_max_connections->integer) )
|
|
CL_StartNextHTTPDownload ();
|
|
|
|
do
|
|
{
|
|
ret = curl_multi_perform (multi, &newHandleCount);
|
|
if (newHandleCount < handleCount)
|
|
{
|
|
// Com_Printf ("runnd dl: hc = %d, nc = %d\n", LOG_GENERAL, handleCount, newHandleCount);
|
|
// hmm, something either finished or errored out.
|
|
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);
|
|
}
|
|
|
|
// not enough downloads running, queue some more!
|
|
if (pendingCount && (abortDownloads == HTTPDL_ABORT_NONE) &&
|
|
!downloading_pak && (handleCount < cl_http_max_connections->integer) )
|
|
CL_StartNextHTTPDownload ();
|
|
}
|
|
|
|
#endif
|