2010-06-18 16:15:33 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
|
|
*
|
2010-07-13 18:19:42 +00:00
|
|
|
* This program 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.
|
2010-06-18 16:15:33 +00:00
|
|
|
*
|
2010-07-13 18:19:42 +00:00
|
|
|
* This program 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.
|
2010-06-18 16:15:33 +00:00
|
|
|
*
|
|
|
|
* See the GNU General Public License for more details.
|
|
|
|
*
|
2010-07-13 18:19:42 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
|
|
* 02111-1307, USA.
|
2010-06-18 16:15:33 +00:00
|
|
|
*
|
|
|
|
* =======================================================================
|
|
|
|
*
|
|
|
|
* This file implements the game media download from the server
|
|
|
|
*
|
|
|
|
* =======================================================================
|
2012-04-29 13:57:33 +00:00
|
|
|
*/
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-04-29 13:57:33 +00:00
|
|
|
#include "header/client.h"
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
extern cvar_t *allow_download;
|
|
|
|
extern cvar_t *allow_download_players;
|
|
|
|
extern cvar_t *allow_download_models;
|
|
|
|
extern cvar_t *allow_download_sounds;
|
|
|
|
extern cvar_t *allow_download_maps;
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
extern int precache_check;
|
|
|
|
extern int precache_spawncount;
|
|
|
|
extern int precache_tex;
|
|
|
|
extern int precache_model_skin;
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
extern byte *precache_model;
|
2010-06-18 16:15:33 +00:00
|
|
|
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
// Forces all downloads to UDP.
|
2019-09-04 16:02:06 +00:00
|
|
|
static qboolean forceudp;
|
|
|
|
|
|
|
|
// Gives HTTP downloads a second chance after
|
|
|
|
// we've fallen trough to UDP downloads.
|
|
|
|
static qboolean httpSecondChance = true;
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
|
2019-02-17 08:28:26 +00:00
|
|
|
/* This - and some more code down below - is the 'Crazy Fallback
|
2019-01-24 17:00:01 +00:00
|
|
|
Magic'. First we're trying to download all files over HTTP with
|
|
|
|
r1q2-style URLs. If we encountered errors we reset the complete
|
|
|
|
precacher state and retry with HTTP and q2pro-style URLs. If we
|
|
|
|
still got errors we're falling back to UDP. So:
|
|
|
|
- 0: Virgin state, r1q2-style URLs.
|
|
|
|
- 1: Second iteration, q2pro-style URL.
|
|
|
|
- 3: Third iteration, UDP downloads. */
|
|
|
|
static unsigned int precacherIteration;
|
|
|
|
|
|
|
|
// r1q2 searches the global filelist at /, q2pro at /gamedir...
|
2019-09-04 16:02:06 +00:00
|
|
|
static qboolean gamedirForFilelist;
|
2019-01-24 17:00:01 +00:00
|
|
|
|
2010-06-18 16:15:33 +00:00
|
|
|
static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
|
|
|
|
|
2012-04-29 13:57:33 +00:00
|
|
|
#define PLAYER_MULT 5
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
/* ENV_CNT is map load, ENV_CNT+1 is first env map */
|
|
|
|
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
|
2012-07-22 13:34:45 +00:00
|
|
|
#define TEXTURE_CNT (ENV_CNT + 13)
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
void
|
|
|
|
CL_RequestNextDownload(void)
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
2012-06-02 07:31:27 +00:00
|
|
|
unsigned int map_checksum; /* for detecting cheater maps */
|
2010-06-18 16:15:33 +00:00
|
|
|
char fn[MAX_OSPATH];
|
|
|
|
dmdl_t *pheader;
|
|
|
|
|
2019-01-24 14:57:11 +00:00
|
|
|
if (precacherIteration == 0)
|
|
|
|
{
|
|
|
|
#if USE_CURL
|
|
|
|
// r1q2-style URLs.
|
2019-02-17 08:28:26 +00:00
|
|
|
Q_strlcpy(dlquirks.gamedir, cl.gamedir, sizeof(dlquirks.gamedir));
|
2019-01-24 14:57:11 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else if (precacherIteration == 1)
|
|
|
|
{
|
|
|
|
#if USE_CURL
|
|
|
|
// q2pro-style URLs.
|
|
|
|
if (cl.gamedir[0] == '\0')
|
|
|
|
{
|
2019-02-17 08:28:26 +00:00
|
|
|
Q_strlcpy(dlquirks.gamedir, BASEDIRNAME, sizeof(dlquirks.gamedir));
|
2019-01-24 14:57:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-02-17 08:28:26 +00:00
|
|
|
Q_strlcpy(dlquirks.gamedir, cl.gamedir, sizeof(dlquirks.gamedir));
|
2019-01-24 14:57:11 +00:00
|
|
|
}
|
2019-01-24 17:00:01 +00:00
|
|
|
|
|
|
|
// Force another try with the filelist.
|
2019-02-17 08:28:26 +00:00
|
|
|
dlquirks.filelist = true;
|
2019-01-24 17:00:01 +00:00
|
|
|
gamedirForFilelist = true;
|
2019-01-24 14:57:11 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else if (precacherIteration == 2)
|
|
|
|
{
|
|
|
|
// UDP Fallback.
|
|
|
|
forceudp = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Cannot get here.
|
|
|
|
assert(1 && "Recursed from UDP fallback case");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-06-18 16:15:33 +00:00
|
|
|
if (cls.state != ca_connected)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
return;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (!allow_download->value && (precache_check < ENV_CNT))
|
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
precache_check = ENV_CNT;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (precache_check == CS_MODELS)
|
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
precache_check = CS_MODELS + 2;
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (allow_download_maps->value)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS + 1]))
|
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
return; /* started a download */
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((precache_check >= CS_MODELS) &&
|
|
|
|
(precache_check < CS_MODELS + MAX_MODELS))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (allow_download_models->value)
|
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
while (precache_check < CS_MODELS + MAX_MODELS &&
|
|
|
|
cl.configstrings[precache_check][0])
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((cl.configstrings[precache_check][0] == '*') ||
|
|
|
|
(cl.configstrings[precache_check][0] == '#'))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
precache_check++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (precache_model_skin == 0)
|
|
|
|
{
|
|
|
|
if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check]))
|
|
|
|
{
|
|
|
|
precache_model_skin = 1;
|
|
|
|
return; /* started a download */
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_model_skin = 1;
|
|
|
|
}
|
|
|
|
|
2018-11-20 17:15:19 +00:00
|
|
|
#ifdef USE_CURL
|
|
|
|
/* Wait for the models to download before checking * skins. */
|
|
|
|
if (CL_PendingHTTPDownloads())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-06-18 16:15:33 +00:00
|
|
|
/* checking for skins in the model */
|
|
|
|
if (!precache_model)
|
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
FS_LoadFile(cl.configstrings[precache_check],
|
|
|
|
(void **)&precache_model);
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (!precache_model)
|
|
|
|
{
|
|
|
|
precache_model_skin = 0;
|
|
|
|
precache_check++;
|
|
|
|
continue; /* couldn't load it */
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (LittleLong(*(unsigned *)precache_model) !=
|
|
|
|
IDALIASHEADER)
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
/* not an alias model */
|
|
|
|
FS_FreeFile(precache_model);
|
|
|
|
precache_model = 0;
|
|
|
|
precache_model_skin = 0;
|
|
|
|
precache_check++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pheader = (dmdl_t *)precache_model;
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (LittleLong(pheader->version) != ALIAS_VERSION)
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
precache_check++;
|
|
|
|
precache_model_skin = 0;
|
|
|
|
continue; /* couldn't load it */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pheader = (dmdl_t *)precache_model;
|
|
|
|
|
|
|
|
while (precache_model_skin - 1 < LittleLong(pheader->num_skins))
|
|
|
|
{
|
|
|
|
if (!CL_CheckOrDownloadFile((char *)precache_model +
|
2012-07-22 13:34:45 +00:00
|
|
|
LittleLong(pheader->ofs_skins) +
|
|
|
|
(precache_model_skin - 1) * MAX_SKINNAME))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
precache_model_skin++;
|
|
|
|
return; /* started a download */
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_model_skin++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (precache_model)
|
|
|
|
{
|
|
|
|
FS_FreeFile(precache_model);
|
|
|
|
precache_model = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_model_skin = 0;
|
|
|
|
|
|
|
|
precache_check++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_check = CS_SOUNDS;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((precache_check >= CS_SOUNDS) &&
|
|
|
|
(precache_check < CS_SOUNDS + MAX_SOUNDS))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (allow_download_sounds->value)
|
|
|
|
{
|
|
|
|
if (precache_check == CS_SOUNDS)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
precache_check++;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
while (precache_check < CS_SOUNDS + MAX_SOUNDS &&
|
|
|
|
cl.configstrings[precache_check][0])
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (cl.configstrings[precache_check][0] == '*')
|
|
|
|
{
|
|
|
|
precache_check++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
Com_sprintf(fn, sizeof(fn), "sound/%s",
|
|
|
|
cl.configstrings[precache_check++]);
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
return; /* started a download */
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_check = CS_IMAGES;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((precache_check >= CS_IMAGES) &&
|
|
|
|
(precache_check < CS_IMAGES + MAX_IMAGES))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (precache_check == CS_IMAGES)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
precache_check++;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
while (precache_check < CS_IMAGES + MAX_IMAGES &&
|
|
|
|
cl.configstrings[precache_check][0])
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
Com_sprintf(fn, sizeof(fn), "pics/%s.pcx",
|
|
|
|
cl.configstrings[precache_check++]);
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
return; /* started a download */
|
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
precache_check = CS_PLAYERSKINS;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
/* skins are special, since a player has three
|
|
|
|
things to download: model, weapon model and
|
|
|
|
skin so precache_check is now *3 */
|
|
|
|
if ((precache_check >= CS_PLAYERSKINS) &&
|
|
|
|
(precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (allow_download_players->value)
|
|
|
|
{
|
|
|
|
while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
|
|
|
|
{
|
|
|
|
int i, n;
|
|
|
|
char model[MAX_QPATH], skin[MAX_QPATH], *p;
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
i = (precache_check - CS_PLAYERSKINS) / PLAYER_MULT;
|
|
|
|
n = (precache_check - CS_PLAYERSKINS) % PLAYER_MULT;
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (!cl.configstrings[CS_PLAYERSKINS + i][0])
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((p = strchr(cl.configstrings[CS_PLAYERSKINS + i], '\\')) != NULL)
|
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
p++;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
else
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
p = cl.configstrings[CS_PLAYERSKINS + i];
|
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
strcpy(model, p);
|
|
|
|
|
|
|
|
p = strchr(model, '/');
|
|
|
|
|
|
|
|
if (!p)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
p = strchr(model, '\\');
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (p)
|
|
|
|
{
|
|
|
|
*p++ = 0;
|
|
|
|
strcpy(skin, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
*skin = 0;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
switch (n)
|
|
|
|
{
|
|
|
|
case 0: /* model */
|
|
|
|
Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);
|
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
case 1: /* weapon model */
|
|
|
|
Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);
|
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
case 2: /* weapon skin */
|
|
|
|
Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model);
|
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
case 3: /* skin */
|
|
|
|
Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin);
|
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
case 4: /* skin_i */
|
|
|
|
Com_sprintf(fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin);
|
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
|
|
|
{
|
|
|
|
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
|
2012-07-22 13:34:45 +00:00
|
|
|
return; /* started a download */
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* move on to next model */
|
|
|
|
precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
|
2018-11-20 17:15:19 +00:00
|
|
|
#ifdef USE_CURL
|
|
|
|
/* Wait for pending downloads. */
|
|
|
|
if (CL_PendingHTTPDownloads())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
|
|
|
|
|
2019-02-17 08:28:26 +00:00
|
|
|
if (dlquirks.error)
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
{
|
2019-02-17 08:28:26 +00:00
|
|
|
dlquirks.error = false;
|
|
|
|
|
2019-01-24 14:57:11 +00:00
|
|
|
/* Mkay, there were download errors. Let's start over. */
|
|
|
|
precacherIteration++;
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
CL_ResetPrecacheCheck();
|
|
|
|
CL_RequestNextDownload();
|
|
|
|
return;
|
|
|
|
}
|
2018-11-20 17:15:19 +00:00
|
|
|
#endif
|
|
|
|
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
/* precache phase completed */
|
2019-07-21 01:02:22 +00:00
|
|
|
precache_check = ENV_CNT + 1;
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
|
2019-07-21 01:02:22 +00:00
|
|
|
CM_LoadMap(cl.configstrings[CS_MODELS + 1], true, &map_checksum);
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2019-07-21 01:02:22 +00:00
|
|
|
if (map_checksum != (int)strtol(cl.configstrings[CS_MAPCHECKSUM], (char **)NULL, 10))
|
|
|
|
{
|
|
|
|
Com_Error(ERR_DROP, "Local map version differs from server: %i != '%s'\n",
|
|
|
|
map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
|
|
|
|
return;
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if ((precache_check > ENV_CNT) && (precache_check < TEXTURE_CNT))
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
|
|
|
if (allow_download->value && allow_download_maps->value)
|
|
|
|
{
|
|
|
|
while (precache_check < TEXTURE_CNT)
|
|
|
|
{
|
|
|
|
int n = precache_check++ - ENV_CNT - 1;
|
|
|
|
|
|
|
|
if (n & 1)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx",
|
2012-07-22 13:34:45 +00:00
|
|
|
cl.configstrings[CS_SKY], env_suf[n / 2]);
|
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
else
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
Com_sprintf(fn, sizeof(fn), "env/%s%s.tga",
|
2012-07-22 13:34:45 +00:00
|
|
|
cl.configstrings[CS_SKY], env_suf[n / 2]);
|
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
return;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
precache_check = TEXTURE_CNT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (precache_check == TEXTURE_CNT)
|
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
precache_check = TEXTURE_CNT + 1;
|
2010-06-18 16:15:33 +00:00
|
|
|
precache_tex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* confirm existance of textures, download any that don't exist */
|
2012-07-22 13:34:45 +00:00
|
|
|
if (precache_check == TEXTURE_CNT + 1)
|
2010-06-18 16:15:33 +00:00
|
|
|
{
|
2012-07-22 13:34:45 +00:00
|
|
|
extern int numtexinfo;
|
|
|
|
extern mapsurface_t map_surfaces[];
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (allow_download->value && allow_download_maps->value)
|
|
|
|
{
|
|
|
|
while (precache_tex < numtexinfo)
|
|
|
|
{
|
|
|
|
char fn[MAX_OSPATH];
|
|
|
|
|
2013-05-18 16:59:12 +00:00
|
|
|
sprintf(fn, "textures/%s.wal",
|
2012-07-22 13:34:45 +00:00
|
|
|
map_surfaces[precache_tex++].rname);
|
2010-06-18 16:15:33 +00:00
|
|
|
|
|
|
|
if (!CL_CheckOrDownloadFile(fn))
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
2010-06-18 16:15:33 +00:00
|
|
|
return; /* started a download */
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
precache_check = TEXTURE_CNT + 999;
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 17:15:19 +00:00
|
|
|
#ifdef USE_CURL
|
|
|
|
/* Wait for pending downloads. */
|
|
|
|
if (CL_PendingHTTPDownloads())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2019-01-24 14:57:11 +00:00
|
|
|
/* This map is done, start over for next map. */
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
forceudp = false;
|
2019-01-24 14:57:11 +00:00
|
|
|
precacherIteration = 0;
|
2019-01-24 17:00:01 +00:00
|
|
|
gamedirForFilelist = false;
|
2019-09-04 16:02:06 +00:00
|
|
|
httpSecondChance = true;
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
|
2019-02-11 18:20:55 +00:00
|
|
|
#ifdef USE_CURL
|
2019-02-17 08:28:26 +00:00
|
|
|
dlquirks.filelist = true;
|
2019-02-11 18:20:55 +00:00
|
|
|
#endif
|
|
|
|
|
2018-11-20 17:15:19 +00:00
|
|
|
CL_RegisterSounds();
|
2012-07-22 13:34:45 +00:00
|
|
|
CL_PrepRefresh();
|
2010-06-18 16:15:33 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
MSG_WriteString(&cls.netchan.message, va("begin %i\n", precache_spawncount));
|
2016-08-04 15:36:42 +00:00
|
|
|
cls.forcePacket = true;
|
2010-06-18 16:15:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
void
|
|
|
|
CL_DownloadFileName(char *dest, int destlen, char *fn)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
if (strncmp(fn, "players", 7) == 0)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
Com_sprintf(dest, destlen, "%s/%s", BASEDIRNAME, fn);
|
|
|
|
}
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
else
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
Com_sprintf(dest, destlen, "%s/%s", FS_Gamedir(), fn);
|
|
|
|
}
|
2010-06-18 16:34:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns true if the file exists, otherwise it attempts
|
|
|
|
* to start a download from the server.
|
2012-07-22 13:34:45 +00:00
|
|
|
*/
|
|
|
|
qboolean
|
|
|
|
CL_CheckOrDownloadFile(char *filename)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
char name[MAX_OSPATH];
|
|
|
|
char *ptr;
|
|
|
|
|
2010-06-18 16:34:07 +00:00
|
|
|
/* fix backslashes - this is mostly für UNIX comaptiblity */
|
2012-07-22 13:34:45 +00:00
|
|
|
while ((ptr = strchr(filename, '\\')))
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
*ptr = '/';
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (FS_LoadFile(filename, NULL) != -1)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
/* it exists, no need to download */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-10-28 06:31:06 +00:00
|
|
|
if (strstr(filename, "..") || strstr(filename, ":") || (*filename == '.') || (*filename == '/'))
|
2015-10-27 16:38:28 +00:00
|
|
|
{
|
|
|
|
Com_Printf("Refusing to download a path with ..: %s\n", filename);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-11-20 17:15:19 +00:00
|
|
|
#ifdef USE_CURL
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
if (!forceudp)
|
2018-11-20 17:15:19 +00:00
|
|
|
{
|
2019-01-24 17:00:01 +00:00
|
|
|
if (CL_QueueHTTPDownload(filename, gamedirForFilelist))
|
Implement fallback logic from HTTP downloading to UDP.
This looks easy, but is rather hacky... Downloading is implemented
through the precacher. The server sends an asset list, while loading
the map another one is generated. CL_RequestNextDownload() goes
through this list, in the order models / maps -> sounds -> images,
calls CL_CheckOrDownloadFile() for each file. CL_CheckOrDownloadFile()
checks if the file is already there, return true if it is and false
if not. If the return code is false CL_RequestNextDownload() itself
returns, it's called again by CL_ParseDownload() as soon as the just
queued file finished downloading. This way all missing files are
downloaded one after the other, when CL_RequestNextDownload() finally
reaches it's end (all files are there) it send 'begin' to the server,
thus putting the client into the game.
HTTP downloads are parallel, so CL_RequestNextDownload() cannot track
which files are there and which are missing. The work around for that is
to queue the file but have CL_CheckOrDownloadFile() return true. So
CL_RequestNextDownload() thinks the file is already there, continues
with the next one, until all missing files are queued. After that it
polls CL_PendingHTTPDownloads() and sends the 'begin' as soon as all
HTTP downloads are finished.
If a HTTP download fails we cannot just queue it as UDP download,
because the precacher things that the file is already there. And we
can't tell the precacher that it's not because the precacher tracks
files only by the number of downloaded files per asste type and not
their name. Just decreasing the number of downloaded files isn't
possible since the precacher may have progressed to the next asset
type.
So: On the HTTP side it's tracked if there was an error or not. After
CL_RequestNextDownload() has queued all files and waited for all HTTP
downloads to finish it checks the HTTP error status. If there was an
error the precacher state is reset and CL_RequestNextDownload() recurses
into itself to take another run. All files that couldn't be downloaded
are queued again, this time as UDP downloads.
2019-01-16 12:00:43 +00:00
|
|
|
{
|
|
|
|
/* We return true so that the precache check
|
|
|
|
keeps feeding us more files. Since we have
|
|
|
|
multiple HTTP connections we want to
|
|
|
|
minimize latency and be constantly sending
|
|
|
|
requests, not one at a time. */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* There're 2 cases:
|
|
|
|
- forceudp was set after a 404. In this case we
|
|
|
|
want to retry that single file over UDP and
|
|
|
|
all later files over HTTP.
|
|
|
|
- forceudp was set after another error code.
|
|
|
|
In that case the HTTP code aborts all HTTP
|
|
|
|
downloads and CL_QueueHTTPDownload() returns
|
|
|
|
false. */
|
|
|
|
forceudp = false;
|
2019-01-24 17:00:01 +00:00
|
|
|
|
2019-09-04 16:02:06 +00:00
|
|
|
/* This is one of the nasty special cases. A r1q2
|
|
|
|
server might miss only one file. This missing
|
|
|
|
file may lead to a fallthrough to q2pro URLs,
|
|
|
|
since it isn't a q2pro server all files would
|
|
|
|
yield error 404 and we're falling back to UDP
|
|
|
|
downloads. To work around this we need to start
|
|
|
|
over with the r1q2 case and see what happens.
|
|
|
|
But we can't do that unconditionally, because
|
|
|
|
we would run in endless loops r1q2 -> q2pro ->
|
|
|
|
UDP -> r1q2. So hack in a variable that allows
|
|
|
|
for one and only one second chance. If the r1q2
|
|
|
|
server is missing more than file we've lost and
|
|
|
|
we're doing unnecessary UDP downloads. */
|
|
|
|
if (httpSecondChance)
|
|
|
|
{
|
|
|
|
precacherIteration = 0;
|
|
|
|
httpSecondChance = false;
|
|
|
|
}
|
2018-11-20 17:15:19 +00:00
|
|
|
}
|
|
|
|
#endif
|
2012-07-22 13:34:45 +00:00
|
|
|
strcpy(cls.downloadname, filename);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
/* download to a temp name, and only rename
|
|
|
|
to the real name when done, so if interrupted
|
|
|
|
a runt file wont be left */
|
2012-07-22 13:34:45 +00:00
|
|
|
COM_StripExtension(cls.downloadname, cls.downloadtempname);
|
2013-05-18 16:59:12 +00:00
|
|
|
strcat(cls.downloadtempname, ".tmp");
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
/* check to see if we already have a tmp for this
|
|
|
|
file, if so, try to resume and open the file if
|
|
|
|
not opened yet */
|
2010-06-18 16:34:07 +00:00
|
|
|
CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
|
|
|
|
|
2018-02-05 06:43:26 +00:00
|
|
|
fp = Q_fopen(name, "r+b");
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (fp)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
/* it exists */
|
|
|
|
int len;
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
|
|
len = ftell(fp);
|
|
|
|
|
|
|
|
cls.download = fp;
|
|
|
|
|
|
|
|
/* give the server an offset to start the download */
|
2012-07-22 13:34:45 +00:00
|
|
|
Com_Printf("Resuming %s\n", cls.downloadname);
|
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
MSG_WriteString(&cls.netchan.message, va("download %s %i", cls.downloadname, len));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Com_Printf("Downloading %s\n", cls.downloadname);
|
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
MSG_WriteString(&cls.netchan.message, va("download %s", cls.downloadname));
|
2010-06-18 16:34:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cls.downloadnumber++;
|
2016-08-04 15:36:42 +00:00
|
|
|
cls.forcePacket = true;
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Request a download from the server
|
|
|
|
*/
|
2012-07-22 13:34:45 +00:00
|
|
|
void
|
|
|
|
CL_Download_f(void)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
char filename[MAX_OSPATH];
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (Cmd_Argc() != 2)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
Com_Printf("Usage: download <filename>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (strstr(filename, ".."))
|
|
|
|
{
|
|
|
|
Com_Printf("Refusing to download a path with ..\n");
|
2010-06-18 16:34:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (FS_LoadFile(filename, NULL) != -1)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
/* it exists, no need to download */
|
|
|
|
Com_Printf("File already exists.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
strcpy(cls.downloadname, filename);
|
|
|
|
Com_Printf("Downloading %s\n", cls.downloadname);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
/* download to a temp name, and only rename
|
|
|
|
to the real name when done, so if interrupted
|
|
|
|
a runt file wont be left */
|
2012-07-22 13:34:45 +00:00
|
|
|
COM_StripExtension(cls.downloadname, cls.downloadtempname);
|
2013-05-18 16:59:12 +00:00
|
|
|
strcat(cls.downloadtempname, ".tmp");
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
MSG_WriteString(&cls.netchan.message, va("download %s", cls.downloadname));
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
cls.downloadnumber++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A download message has been received from the server
|
|
|
|
*/
|
2012-07-22 13:34:45 +00:00
|
|
|
void
|
|
|
|
CL_ParseDownload(void)
|
|
|
|
{
|
|
|
|
int size, percent;
|
|
|
|
char name[MAX_OSPATH];
|
|
|
|
int r;
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
/* read the data */
|
2012-07-22 13:34:45 +00:00
|
|
|
size = MSG_ReadShort(&net_message);
|
|
|
|
percent = MSG_ReadByte(&net_message);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (size == -1)
|
|
|
|
{
|
|
|
|
Com_Printf("Server does not have this file.\n");
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (cls.download)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
/* if here, we tried to resume a
|
2012-07-22 13:34:45 +00:00
|
|
|
* file but the server said no */
|
|
|
|
fclose(cls.download);
|
2010-06-18 16:34:07 +00:00
|
|
|
cls.download = NULL;
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
CL_RequestNextDownload();
|
2010-06-18 16:34:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* open the file if not opened yet */
|
2012-07-22 13:34:45 +00:00
|
|
|
if (!cls.download)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
FS_CreatePath(name);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2018-02-05 06:43:26 +00:00
|
|
|
cls.download = Q_fopen(name, "wb");
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (!cls.download)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
net_message.readcount += size;
|
2012-07-22 13:34:45 +00:00
|
|
|
Com_Printf("Failed to open %s\n", cls.downloadtempname);
|
|
|
|
CL_RequestNextDownload();
|
2010-06-18 16:34:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
fwrite(net_message.data + net_message.readcount, 1, size, cls.download);
|
2010-06-18 16:34:07 +00:00
|
|
|
net_message.readcount += size;
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
if (percent != 100)
|
|
|
|
{
|
2010-06-18 16:34:07 +00:00
|
|
|
/* request next block */
|
|
|
|
cls.downloadpercent = percent;
|
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
SZ_Print(&cls.netchan.message, "nextdl");
|
2016-08-04 15:36:42 +00:00
|
|
|
cls.forcePacket = true;
|
2012-07-22 13:34:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char oldn[MAX_OSPATH];
|
|
|
|
char newn[MAX_OSPATH];
|
2010-06-18 16:34:07 +00:00
|
|
|
|
2012-07-22 13:34:45 +00:00
|
|
|
fclose(cls.download);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
/* rename the temp file to it's final name */
|
|
|
|
CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
|
|
|
|
CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
|
2018-12-18 18:11:39 +00:00
|
|
|
r = Sys_Rename(oldn, newn);
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
if (r)
|
2012-07-22 13:34:45 +00:00
|
|
|
{
|
|
|
|
Com_Printf("failed to rename.\n");
|
|
|
|
}
|
2010-06-18 16:34:07 +00:00
|
|
|
|
|
|
|
cls.download = NULL;
|
|
|
|
cls.downloadpercent = 0;
|
|
|
|
|
|
|
|
/* get another file if needed */
|
2012-07-22 13:34:45 +00:00
|
|
|
CL_RequestNextDownload();
|
2010-06-18 16:34:07 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-22 13:34:45 +00:00
|
|
|
|