thirtyflightsofloving/client/cl_download.c

1295 lines
36 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
===========================================================================
*/
// cl_download.c -- client autodownload code
// moved from cl_main.c and cl_parse.c
#include "client.h"
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;
// Knightmare- whether to allow downloading 24-bit textures
extern cvar_t *allow_download_textures_24bit;
int precache_check; // for autodownload of precache items
int precache_spawncount;
int precache_tex;
int precache_model_skin;
int precache_pak; // Knightmare added
byte *precache_model; // used for skin checking in alias models
#ifdef USE_CURL
qboolean precache_forceUDP = false; // force all downloads to UDP
qboolean filelistUseGamedir = false;
// HTTP Fallback handling
// 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.
// Iteration 1: Initial state, R1Q2-style URLs.
// Iteration 2: Q2Pro-style URLs.
// Iteration 3: UDP downloads.
typedef enum {
ITER_HTTP_R1Q2 = 0,
ITER_HTTP_Q2PRO = 1,
ITER_UDP = 2,
} precacheIteration_t;
static precacheIteration_t precache_iteration = ITER_HTTP_R1Q2;
#endif // USE_CURL
#define PLAYER_MULT 22
// ENV_CNT is map load, ENV_CNT+1 is first env map
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT+13)
// Knightmare- old configstrings for version 34 client compatibility
#define OLD_ENV_CNT (OLD_CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define OLD_TEXTURE_CNT (OLD_ENV_CNT+13)
static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
void CL_InitFailedDownloadList (void);
/*
=================
CL_RequestNextDownload
=================
*/
void CL_RequestNextDownload (void)
{
unsigned map_checksum; // for detecting cheater maps
char fn[MAX_OSPATH];
dmd2_t *md2header;
dmd3_t *md3header;
dmd3mesh_t *md3mesh;
dspr2_t *sp2header;
char *skinname;
int cs_sounds, cs_playerskins, cs_images;
int max_models, max_sounds, max_images;
int env_cnt, texture_cnt;
if (cls.state != ca_connected)
return;
// clear failed download list
if (precache_check == CS_MODELS)
CL_InitFailedDownloadList ();
// Knightmare- hack for connected to server using old protocol
// Changed config strings require different parsing
if ( LegacyProtocol() )
{
cs_sounds = OLD_CS_SOUNDS;
cs_playerskins = OLD_CS_PLAYERSKINS;
cs_images = OLD_CS_IMAGES;
max_models = OLD_MAX_MODELS;
max_sounds = OLD_MAX_SOUNDS;
max_images = OLD_MAX_IMAGES;
env_cnt = OLD_ENV_CNT;
texture_cnt = OLD_TEXTURE_CNT;
}
else
{
cs_sounds = CS_SOUNDS;
cs_playerskins = CS_PLAYERSKINS;
cs_images = CS_IMAGES;
max_models = MAX_MODELS;
max_sounds = MAX_SOUNDS;
max_images = MAX_IMAGES;
env_cnt = ENV_CNT;
texture_cnt = TEXTURE_CNT;
}
// YQ2 Q2pro download addition
#ifdef USE_CURL
if (precache_iteration == ITER_HTTP_R1Q2) {
CL_HTTP_SetDownloadGamedir (cl.gamedir);
}
else if (precache_iteration == ITER_HTTP_Q2PRO)
{
if (cl.gamedir[0] == '\0')
CL_HTTP_SetDownloadGamedir (BASEDIRNAME);
else
CL_HTTP_SetDownloadGamedir (cl.gamedir);
}
else { // if (precache_iteration == ITER_UDP)
precache_forceUDP = true;
}
#endif // USE_CURL
// Skip to loading map if downloading disabled or on local server
if ( (Com_ServerState() || !allow_download->integer) && precache_check < env_cnt)
precache_check = env_cnt;
// Try downloading pk3 file for current map from server, hack by Jay Dolan
if ( !LegacyProtocol() && precache_check == CS_MODELS && precache_pak == 0 )
{
precache_pak++;
if (strlen(cl.configstrings[CS_PAKFILE])) {
if ( !CL_CheckOrDownloadFile(cl.configstrings[CS_PAKFILE]) )
return; // started a download
}
}
// ZOID
if (precache_check == CS_MODELS) { // confirm map
precache_check = CS_MODELS+2; // 0 isn't used
if (allow_download_maps->integer) {
if ( !CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS+1]) )
return; // started a download
}
}
if (precache_check >= CS_MODELS && precache_check < CS_MODELS+max_models)
{
if (allow_download_models->integer)
{
while (precache_check < CS_MODELS+max_models &&
cl.configstrings[precache_check][0])
{
if (cl.configstrings[precache_check][0] == '*' ||
cl.configstrings[precache_check][0] == '#') {
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;
}
#ifdef USE_CURL // HTTP downloading from R1Q2
// pending downloads (models), let's wait here before we can check skins.
if ( CL_PendingHTTPDownloads() ) {
return;
}
#endif // USE_CURL
// checking for skins in the model
if (!precache_model)
{
FS_LoadFile (cl.configstrings[precache_check], (void **)&precache_model);
if (!precache_model) {
precache_model_skin = 0;
precache_check++;
continue; // couldn't load it
}
if (LittleLong(*(unsigned *)precache_model) != IDMD2HEADER)
{ // is it an md3?
if (LittleLong(*(unsigned *)precache_model) != IDMD3HEADER)
{ // is it a sprite?
if (LittleLong(*(unsigned *)precache_model) != IDSP2HEADER)
{
// not a recognized model
FS_FreeFile(precache_model);
precache_model = 0;
precache_model_skin = 0;
precache_check++;
continue;
}
else
{ // get sprite header
sp2header = (dspr2_t *)precache_model;
if (LittleLong (sp2header->version) != SP2_VERSION)
{ // not a recognized sprite
FS_FreeFile(precache_model);
precache_model = 0;
precache_check++;
precache_model_skin = 0;
continue; // couldn't load it
}
}
}
else
{ // get md3 header
md3header = (dmd3_t *)precache_model;
if (LittleLong (md3header->version) != MD3_ALIAS_VERSION)
{ // not a recognized md3
FS_FreeFile(precache_model);
precache_model = 0;
precache_check++;
precache_model_skin = 0;
continue; // couldn't load it
}
}
}
else
{ // get md2 header
md2header = (dmd2_t *)precache_model;
if (LittleLong (md2header->version) != MD2_ALIAS_VERSION)
{ // not a recognized md2
FS_FreeFile(precache_model);
precache_model = 0;
precache_check++;
precache_model_skin = 0;
continue; // couldn't load it
}
}
}
if (LittleLong(*(unsigned *)precache_model) == IDMD2HEADER) // md2
{
md2header = (dmd2_t *)precache_model;
while (precache_model_skin - 1 < LittleLong(md2header->num_skins))
{
skinname = (char *)precache_model + LittleLong(md2header->ofs_skins) +
(precache_model_skin - 1)*MD2_MAX_SKINNAME;
// r1ch: spam warning for models that are broken
if (strchr (skinname, '\\'))
Com_Printf ("Warning, model %s with incorrectly linked skin: %s\n", cl.configstrings[precache_check], skinname);
else if (strlen(skinname) > MD2_MAX_SKINNAME-1)
Com_Error (ERR_DROP, "Model %s has too long a skin path: %s", cl.configstrings[precache_check], skinname);
if ( !CL_CheckOrDownloadFile(skinname) )
{
precache_model_skin++;
return; // started a download
}
precache_model_skin++;
}
}
else if (LittleLong(*(unsigned *)precache_model) == IDMD3HEADER) // md3
{
md3header = (dmd3_t *)precache_model;
while (precache_model_skin - 1 < LittleLong(md3header->num_skins))
{
int i;
md3mesh = (dmd3mesh_t *)((byte *)md3header + LittleLong(md3header->ofs_meshes));
for ( i = 0; i < md3header->num_meshes; i++)
{
if (precache_model_skin - 1 >= LittleLong(md3header->num_skins))
break;
skinname = (char *)precache_model + LittleLong(md3mesh->ofs_skins) +
(precache_model_skin - 1)*MD3_MAX_PATH;
// r1ch: spam warning for models that are broken
if (strchr (skinname, '\\'))
Com_Printf ("Warning, model %s with incorrectly linked skin: %s\n", cl.configstrings[precache_check], skinname);
else if (strlen(skinname) > MD3_MAX_PATH-1)
Com_Error (ERR_DROP, "Model %s has too long a skin path: %s", cl.configstrings[precache_check], skinname);
if ( !CL_CheckOrDownloadFile(skinname) )
{
precache_model_skin++;
return; // started a download
}
precache_model_skin++;
md3mesh = (dmd3mesh_t *)((byte *)md3mesh + LittleLong (md3mesh->meshsize));
}
}
}
else // if (LittleLong(*(unsigned *)precache_model) == IDSP2HEADER) // sp2
{
sp2header = (dspr2_t *)precache_model;
while (precache_model_skin - 1 < LittleLong(sp2header->numframes))
{
skinname = sp2header->frames[(precache_model_skin - 1)].name;
// r1ch: spam warning for models that are broken
if (strchr (skinname, '\\'))
Com_Printf ("Warning, sprite %s with incorrectly linked skin: %s\n", cl.configstrings[precache_check], skinname);
else if (strlen(skinname) > MD2_MAX_SKINNAME-1)
Com_Error (ERR_DROP, "Sprite %s has too long a skin path: %s", cl.configstrings[precache_check], skinname);
if ( !CL_CheckOrDownloadFile(skinname) )
{
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;
}
if (precache_check >= cs_sounds && precache_check < cs_sounds+max_sounds)
{
if (allow_download_sounds->integer) {
if (precache_check == cs_sounds)
precache_check++; // zero is blank
while (precache_check < cs_sounds+max_sounds &&
cl.configstrings[precache_check][0]) {
if (cl.configstrings[precache_check][0] == '*') {
precache_check++;
continue;
}
Com_sprintf(fn, sizeof(fn), "sound/%s", cl.configstrings[precache_check++]);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
precache_check = cs_images;
}
if (precache_check >= cs_images && precache_check < CS_IMAGES+max_images)
{
if (precache_check == cs_images)
precache_check++; // zero is blank
while (precache_check < cs_images+max_images &&
cl.configstrings[precache_check][0]) {
Com_sprintf(fn, sizeof(fn), "pics/%s.pcx", cl.configstrings[precache_check++]);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
precache_check = cs_playerskins;
}
// 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)
{
if (allow_download_players->integer)
{
while (precache_check < cs_playerskins + MAX_CLIENTS * PLAYER_MULT)
{
int i, j, n, len;
char model[MAX_QPATH], skin[MAX_QPATH], *p;
qboolean badSkinName = false;
i = (precache_check - cs_playerskins)/PLAYER_MULT;
n = (precache_check - cs_playerskins)%PLAYER_MULT;
// from R1Q2- skip invalid player skins data
if (i >= cl.maxclients) {
precache_check = env_cnt;
continue;
}
if (!cl.configstrings[cs_playerskins+i][0]) {
precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
continue;
}
if ((p = strchr(cl.configstrings[cs_playerskins+i], '\\')) != NULL) {
p++;
}
else {
p = cl.configstrings[cs_playerskins+i];
}
Q_strncpyz (model, sizeof(model), p);
p = strchr(model, '/');
if (!p)
p = strchr(model, '\\');
if (p)
{
*p++ = 0;
if (!p[0] || !model[0]) {
precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
continue;
}
else {
Q_strncpyz (skin, sizeof(skin), p);
}
}
else {
// *skin = 0;
precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
continue;
}
// from R1Q2: check model and skin name for invalid chars
len = (int)strlen(model);
for (j = 0; j < len; j++)
{
if ( !IsValidChar(model[j]) ) {
Com_Printf (S_COLOR_YELLOW"Bad character '%c' in player model '%s'\n", model[j], model);
badSkinName = true;
}
}
len = (int)strlen(skin);
for (j = 0; j < len; j++)
{
if ( !IsValidChar(skin[j]) ) {
Com_Printf (S_COLOR_YELLOW"Bad character '%c' in player skin '%s'\n", skin[j], skin);
badSkinName = true;
}
}
if (badSkinName) {
precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
continue;
}
// end R1Q2 name check
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; // started a download
}
n++;
/*FALL THROUGH*/
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; // started a download
}
n++;
/*FALL THROUGH*/
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; // started a download
}
n++;
/*FALL THROUGH*/
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; // started a download
}
n++;
/*FALL THROUGH*/
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;
return; // started a download
}
// Knightmare- added support for downloading player sounds
n++;
/*FALL THROUGH*/
case 5: // death1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/death1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 6;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 6: // death2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/death2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 7;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 7: // death3.wav
Com_sprintf(fn, sizeof(fn), "players/%s/death3.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 8;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 8: // death4.wav
Com_sprintf(fn, sizeof(fn), "players/%s/death4.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 9;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 9: // fall1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/fall1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 10;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 10: // fall2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/fall2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 11;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 11: // gurp1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/gurp1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 12;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 12: // gurp2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/gurp2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 13;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 13: // jump1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/jump1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 14;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 14: // pain25_1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain25_1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 15;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 15: // pain25_2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain25_2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 16;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 16: // pain50_1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain50_1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 17;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 17: // pain50_2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain50_2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 18;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 18: // pain75_1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain75_1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 19;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 19: // pain75_2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain75_2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 20;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 20: // pain100_1.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain100_1.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 21;
return; // started a download
}
n++;
/*FALL THROUGH*/
case 21: // pain100_2.wav
Com_sprintf(fn, sizeof(fn), "players/%s/pain100_2.wav", model);
if ( !CL_CheckOrDownloadFile(fn) ) {
precache_check = cs_playerskins + i * PLAYER_MULT + 22;
return; // started a download
}
// n = 23;
// end Knightmare
// move on to next model
// precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
}
// move on to next model
precache_check = cs_playerskins + (i + 1) * PLAYER_MULT;
}
}
// precache phase completed
precache_check = env_cnt;
}
#ifdef USE_CURL // HTTP downloading from R1Q2
// pending downloads (possibly the map), let's wait here.
if ( CL_PendingHTTPDownloads() ) {
return;
}
// YQ2 UDP fallback addition
if ( CL_CheckHTTPError() && cl_http_fallback->integer ) // Download errors detected, so start over with next iteration
{
precache_iteration++;
if (precache_iteration == ITER_HTTP_Q2PRO) {
Com_Printf ("[HTTP] One or more HTTP downloads failed on R1Q2 path, falling back to Q2Pro path.\n");
// try filelist again with a different path
filelistUseGamedir = true;
CL_HTTP_EnableGenericFilelist ();
}
else // if (precache_iteration == ITER_UDP)
Com_Printf ("[HTTP] One or more HTTP downloads failed on both R1Q2 and Q2Pro paths, falling back to UDP.\n");
CL_ResetPrecacheCheck ();
CL_RequestNextDownload ();
return;
}
#endif // USE_CURL
if (precache_check == env_cnt)
{
if ( Com_ServerState() ) // if on local server, skip checking textures
precache_check = texture_cnt+999;
else
precache_check = env_cnt + 1;
CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);
if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM])) {
Com_Error (ERR_DROP, "Local map version differs from server: %i != '%s'\n",
map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
return;
}
}
if (precache_check > env_cnt && precache_check < texture_cnt)
{
if (allow_download->integer && allow_download_maps->integer)
{
while (precache_check < texture_cnt)
{
int n = precache_check++ - env_cnt - 1;
if (n & 1)
Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx",
cl.configstrings[CS_SKY], env_suf[n/2]);
else
Com_sprintf(fn, sizeof(fn), "env/%s%s.tga",
cl.configstrings[CS_SKY], env_suf[n/2]);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
precache_check = texture_cnt;
}
if (precache_check == texture_cnt) {
precache_check = texture_cnt+1;
precache_tex = 0;
}
// confirm existance of .wal textures, download any that don't exist
if (precache_check == texture_cnt+1)
{ // from qcommon/cmodel.c
extern int numtexinfo;
extern mapsurface_t map_surfaces[];
if (allow_download->integer && allow_download_maps->integer)
{
while (precache_tex < numtexinfo)
{
char fn[MAX_OSPATH];
Com_sprintf(fn, sizeof(fn), "textures/%s.wal", map_surfaces[precache_tex++].rname);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
// precache_check = texture_cnt+999;
precache_check = texture_cnt+2;
precache_tex = 0;
}
// confirm existance of .tga textures, try to download any that don't exist
if (precache_check == texture_cnt+2)
{ // from qcommon/cmodel.c
extern int numtexinfo;
extern mapsurface_t map_surfaces[];
if (allow_download->integer && allow_download_maps->integer
&& allow_download_textures_24bit->integer)
{
while (precache_tex < numtexinfo)
{
char fn[MAX_OSPATH];
Com_sprintf(fn, sizeof(fn), "textures/%s.tga", map_surfaces[precache_tex++].rname);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
precache_check = texture_cnt+3;
precache_tex = 0;
}
#ifdef PNG_SUPPORT
// confirm existance of .png textures, try to download any that don't exist
if (precache_check == texture_cnt+3)
{ // from qcommon/cmodel.c
extern int numtexinfo;
extern mapsurface_t map_surfaces[];
if (allow_download->integer && allow_download_maps->integer
&& allow_download_textures_24bit->integer)
{
while (precache_tex < numtexinfo)
{
char fn[MAX_OSPATH];
Com_sprintf(fn, sizeof(fn), "textures/%s.png", map_surfaces[precache_tex++].rname);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
precache_check = texture_cnt+4;
precache_tex = 0;
}
// confirm existance of .jpg textures, try to download any that don't exist
if (precache_check == texture_cnt+4)
#else // PNG_SUPPORT
// confirm existance of .jpg textures, try to download any that don't exist
if (precache_check == texture_cnt+3)
#endif // PNG_SUPPORT
{ // from qcommon/cmodel.c
extern int numtexinfo;
extern mapsurface_t map_surfaces[];
if (allow_download->integer && allow_download_maps->integer
&& allow_download_textures_24bit->integer)
{
while (precache_tex < numtexinfo)
{
char fn[MAX_OSPATH];
Com_sprintf(fn, sizeof(fn), "textures/%s.jpg", map_surfaces[precache_tex++].rname);
if ( !CL_CheckOrDownloadFile(fn) )
return; // started a download
}
}
precache_check = texture_cnt+999;
}
// ZOID
#ifdef USE_CURL // HTTP downloading from R1Q2
// pending downloads (possibly textures), let's wait here.
if ( CL_PendingHTTPDownloads() ) {
return;
}
// This map is done downloading, reset HTTP for next map
precache_forceUDP = false;
filelistUseGamedir = false;
precache_iteration = ITER_HTTP_R1Q2;
CL_HTTP_EnableGenericFilelist (); // re-enable generic filelists for next map
#endif // USE_CURL
CL_RegisterSounds ();
CL_PrepRefresh ();
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount) );
cls.forcePacket = true;
}
//=============================================================================
/*
===============
CL_DownloadFileName
===============
*/
void CL_DownloadFileName (char *dest, int destlen, const char *fn)
{
if ( !stricmp(FS_Downloaddir(), FS_Gamedir()) ) // use basedir/gamedir if fs_downloaddir is the same as fs_gamedir
{
if (strncmp(fn, "players", 7) == 0)
Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
else
Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn); // was FS_Gamedir()
}
else
Com_sprintf (dest, destlen, "%s/%s", FS_Downloaddir(), fn);
}
// Knightmare- store the names of last downloads that failed
#define NUM_FAIL_DLDS 512
char lastFailedDownload[NUM_FAIL_DLDS][MAX_OSPATH];
static unsigned failedDlListIndex;
/*
===============
CL_InitFailedDownloadList
===============
*/
void CL_InitFailedDownloadList (void)
{
int i;
for (i=0; i<NUM_FAIL_DLDS; i++)
Com_sprintf(lastFailedDownload[i], sizeof(lastFailedDownload[i]), "\0");
failedDlListIndex = 0;
}
/*
===============
CL_CheckDownloadFailed
===============
*/
qboolean CL_CheckDownloadFailed (const char *name)
{
int i;
for (i=0; i<NUM_FAIL_DLDS; i++)
if ( (strlen(lastFailedDownload[i]) > 0) && !strcmp(name, lastFailedDownload[i]) )
{ // we already tried downlaoding this, server didn't have it
return true;
}
return false;
}
/*
===============
CL_AddToFailedDownloadList
===============
*/
void CL_AddToFailedDownloadList (const 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(lastFailedDownload[i]) > 0) && !strcmp(name, lastFailedDownload[i]) )
{
found = true;
break;
}
// if it isn't already in the table, then we need to add it
if (!found)
{
Com_sprintf(lastFailedDownload[failedDlListIndex], sizeof(lastFailedDownload[failedDlListIndex]), "%s", name);
failedDlListIndex++;
// wrap around to start of list
if (failedDlListIndex >= NUM_FAIL_DLDS)
failedDlListIndex = 0;
}
}
/*
===============
CL_CheckOrDownloadFile
Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean CL_CheckOrDownloadFile (const char *filename)
{
FILE *fp;
char name[MAX_OSPATH];
int len; // Knightmare added
char s[128];
//int i;
if (strstr (filename, ".."))
{
Com_Printf ("Refusing to download a path with ..\n");
return true;
}
if (FS_LoadFile (filename, NULL) != -1)
{ // it exists, no need to download
return true;
}
// don't try again to download a file that just failed
// if (CL_CheckDownloadFailed(filename))
// return true;
#ifdef PNG_SUPPORT
// don't download a .png texture which already has a .tga counterpart
len = (int)strlen(filename);
// strncpy(s, filename);
Q_strncpyz (s, sizeof(s), filename);
if (strstr(s, "textures/") && !strcmp(s+len-4, ".png")) // look if we have a .png texture
{
s[len-3]='t'; s[len-2]='g'; s[len-1]='a'; // replace extension
if (FS_LoadFile (s, NULL) != -1) // check for .tga counterpart
return true;
}
#endif // PNG_SUPPORT
// don't download a .jpg texture which already has a .tga or .png counterpart
len = (int)strlen(filename);
// strncpy(s, filename);
Q_strncpyz (s, sizeof(s), filename);
if (strstr(s, "textures/") && !strcmp(s+len-4, ".jpg")) // look if we have a .jpg texture
{
s[len-3]='t'; s[len-2]='g'; s[len-1]='a'; // replace extension
if (FS_LoadFile (s, NULL) != -1) // check for .tga counterpart
return true;
#ifdef PNG_SUPPORT
s[len-3]='p'; s[len-2]='n'; s[len-1]='g'; // replace extension
if (FS_LoadFile (s, NULL) != -1) // check for .png counterpart
return true;
#endif // PNG_SUPPORT
}
#ifdef USE_CURL // HTTP downloading from R1Q2
if (!precache_forceUDP) // YQ2 UDP fallback additon
{
if ( CL_QueueHTTPDownload(filename, filelistUseGamedir) )
{
// Knightmare- If we downloaded a player model, force reload of player models in UI
if ( strncmp(filename, "players/", 8) == 0 ) {
Com_DPrintf ("CL_CheckOrDownloadFile: downloading a player model, forcing reload of player models in UI.\n");
cls.refreshPlayerModels = true;
}
// 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 // YQ2 UDP fallback additon
{
/* There are 2 cases that get us here:
1. forceUDP was set after a 404. In this case we want to retry that
single file over UDP and all later files over HTTP.
2. forceUDP was set after another error code. In that case the HTTP
code aborts all HTTP downloads and CL_QueueHTTPDownload() returns false.
*/
precache_forceUDP = false;
/* We might be connected to an r1q2-style HTTP server that missed just one file.
So reset the precacher iteration counter to start over.
*/
precache_iteration = ITER_HTTP_R1Q2;
}
// else
// {
#endif // USE_CURL
// don't try again to download a file that just failed
if (CL_CheckDownloadFailed(filename))
return true;
// strncpy (cls.downloadname, filename);
Q_strncpyz (cls.downloadname, sizeof(cls.downloadname), filename);
// download to a temp name, and only rename
// to the real name when done, so if interrupted
// a runt file wont be left
COM_StripExtension (cls.downloadname, cls.downloadtempname, sizeof(cls.downloadtempname));
// strncat (cls.downloadtempname, ".tmp");
Q_strncatz (cls.downloadtempname, sizeof(cls.downloadtempname), ".tmp");
//ZOID
// check to see if we already have a tmp for this file, if so, try to resume
// open the file if not opened yet
CL_DownloadFileName (name, sizeof(name), cls.downloadtempname);
// FS_CreatePath (name);
fp = fopen (name, "r+b");
if (fp)
{ // it exists
int len;
fseek(fp, 0, SEEK_END);
len = ftell(fp);
cls.download = fp;
// give the server an offset to start the download
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));
}
cls.downloadnumber++;
cls.forcePacket = true;
// Knightmare- If we downloaded a player model, force reload of player models in UI
if ( strncmp(filename, "players/", 8) == 0 ) {
Com_DPrintf ("CL_CheckOrDownloadFile: downloading a player model, forcing reload of player models in UI.\n");
cls.refreshPlayerModels = true;
}
return false;
#ifdef USE_CURL // HTTP downloading from R1Q2
// }
#endif // USE_CURL
}
/*
===============
CL_Download_f
Request a download from the server
===============
*/
void CL_Download_f (void)
{
char filename[MAX_OSPATH];
if (Cmd_Argc() != 2) {
Com_Printf("Usage: download <filename>\n");
return;
}
Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
if (strstr (filename, ".."))
{
Com_Printf ("Refusing to download a path with ..\n");
return;
}
if (FS_LoadFile (filename, NULL) != -1)
{ // it exists, no need to download
Com_Printf("File already exists.\n");
return;
}
// strncpy (cls.downloadname, filename);
Q_strncpyz (cls.downloadname, sizeof(cls.downloadname), filename);
Com_Printf ("Downloading %s\n", cls.downloadname);
// download to a temp name, and only rename
// to the real name when done, so if interrupted
// a runt file wont be left
COM_StripExtension (cls.downloadname, cls.downloadtempname, sizeof(cls.downloadtempname));
// strncat (cls.downloadtempname, ".tmp");
Q_strncatz (cls.downloadtempname, sizeof(cls.downloadtempname), ".tmp");
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message,
va("download %s", cls.downloadname));
cls.downloadnumber++;
}
//=============================================================================
/*
=====================
CL_ParseDownload
A download message has been received from the server
=====================
*/
void CL_ParseDownload (void)
{
int size, percent;
char name[MAX_OSPATH];
int r; // i
// read the data
size = MSG_ReadShort (&net_message);
percent = MSG_ReadByte (&net_message);
if (size == -1)
{
Com_Printf ("Server does not have this file.\n");
if (cls.downloadname) // Knightmare- save name of failed download
CL_AddToFailedDownloadList (cls.downloadname);
if (cls.download)
{
// if here, we tried to resume a file but the server said no
fclose (cls.download);
cls.download = NULL;
}
CL_RequestNextDownload ();
return;
}
// open the file if not opened yet
if (!cls.download)
{
CL_Download_Reset_KBps_counter (); // Knightmare- for KB/s counter
CL_DownloadFileName (name, sizeof(name), cls.downloadtempname);
FS_CreatePath (name);
cls.download = fopen (name, "wb");
if (!cls.download)
{
net_message.readcount += size;
Com_Printf ("Failed to open %s\n", cls.downloadtempname);
CL_RequestNextDownload ();
return;
}
}
fwrite (net_message.data + net_message.readcount, 1, size, cls.download);
net_message.readcount += size;
if (percent != 100)
{
// request next block
// change display routines by zoid
#if 0
Com_Printf (".");
if (10*(percent/10) != cls.downloadpercent)
{
cls.downloadpercent = 10*(percent/10);
Com_Printf ("%i%%", cls.downloadpercent);
}
#endif
CL_Download_Calculate_KBps (size, 0); // Knightmare- for KB/s counter
cls.downloadpercent = percent;
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, "nextdl");
cls.forcePacket = true;
}
else
{
char oldn[MAX_OSPATH];
char newn[MAX_OSPATH];
// Com_Printf ("100%%\n");
fclose (cls.download);
// rename the temp file to it's final name
CL_DownloadFileName (oldn, sizeof(oldn), cls.downloadtempname);
CL_DownloadFileName (newn, sizeof(newn), cls.downloadname);
r = rename (oldn, newn);
if (r)
Com_Printf ("failed to rename.\n");
cls.download = NULL;
cls.downloadpercent = 0;
// add new pk3s to search paths, hack by Jay Dolan
if (strstr(newn, ".pk3"))
FS_AddPK3File (newn, false);
// get another file if needed
CL_RequestNextDownload ();
}
}
//=============================================================================
// Download speed counter
typedef struct {
int prevTime;
int bytesRead;
int byteCount;
float timeCount;
float prevTimeCount;
float startTime;
} dlSpeedInfo_t;
dlSpeedInfo_t dlSpeedInfo;
/*
=====================
CL_Download_Reset_KBps_counter
=====================
*/
void CL_Download_Reset_KBps_counter (void)
{
dlSpeedInfo.timeCount = dlSpeedInfo.prevTime = dlSpeedInfo.prevTimeCount = dlSpeedInfo.bytesRead = dlSpeedInfo.byteCount = 0;
dlSpeedInfo.startTime = (float)cls.realtime;
cls.downloadrate = 0;
}
/*
=====================
CL_Download_Calculate_KBps
=====================
*/
void CL_Download_Calculate_KBps (int byteDistance, int totalSize)
{
float timeDistance = (float)(cls.realtime - dlSpeedInfo.prevTime);
float totalTime = (dlSpeedInfo.timeCount - dlSpeedInfo.startTime) / 1000.0f;
dlSpeedInfo.timeCount += timeDistance;
dlSpeedInfo.byteCount += byteDistance;
dlSpeedInfo.bytesRead += byteDistance;
if (totalTime >= 1.0f)
{
cls.downloadrate = (float)dlSpeedInfo.byteCount / 1024.0f;
Com_DPrintf ("Rate: %4.2fKB/s, Downloaded %4.2fKB of %4.2fKB\n", cls.downloadrate, (float)dlSpeedInfo.bytesRead/1024.0, (float)totalSize/1024.0);
dlSpeedInfo.byteCount = 0;
dlSpeedInfo.startTime = (float)cls.realtime;
}
dlSpeedInfo.prevTime = cls.realtime;
}