/* * Copyright (C) 1997-2001 Id Software, Inc. * * 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. * * 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. * * See the GNU General Public License for more details. * * 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. * * ======================================================================= * * This file implements the game media download from the server * * ======================================================================= */ #include "header/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; extern int precache_check; extern int precache_spawncount; extern int precache_tex; extern int precache_model_skin; extern byte *precache_model; static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; #define PLAYER_MULT 5 /* 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) void CL_RequestNextDownload (void) { unsigned map_checksum; /* for detecting cheater maps */ char fn[MAX_OSPATH]; dmdl_t *pheader; if (cls.state != ca_connected) return; if (!allow_download->value && precache_check < ENV_CNT) precache_check = ENV_CNT; if (precache_check == CS_MODELS) { precache_check = CS_MODELS+2; if (allow_download_maps->value) 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->value) { 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; } /* 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) != IDALIASHEADER) { /* not an alias model */ FS_FreeFile(precache_model); precache_model = 0; precache_model_skin = 0; precache_check++; continue; } pheader = (dmdl_t *)precache_model; if (LittleLong (pheader->version) != ALIAS_VERSION) { 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 + LittleLong(pheader->ofs_skins) + (precache_model_skin - 1)*MAX_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->value) { if (precache_check == CS_SOUNDS) precache_check++; 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++; 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->value) { while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) { int i, n; char model[MAX_QPATH], skin[MAX_QPATH], *p; i = (precache_check - CS_PLAYERSKINS)/PLAYER_MULT; n = (precache_check - CS_PLAYERSKINS)%PLAYER_MULT; 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]; strcpy(model, p); p = strchr(model, '/'); if (!p) p = strchr(model, '\\'); if (p) { *p++ = 0; strcpy(skin, p); } else *skin = 0; 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; return; // started a download } /* move on to next model */ precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT; } } } /* precache phase completed */ precache_check = ENV_CNT; } if (precache_check == ENV_CNT) { 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->value && allow_download_maps->value) { 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; } } precache_check = TEXTURE_CNT; } if (precache_check == TEXTURE_CNT) { precache_check = TEXTURE_CNT+1; precache_tex = 0; } /* confirm existance of textures, download any that don't exist */ if (precache_check == TEXTURE_CNT+1) { extern int numtexinfo; extern mapsurface_t map_surfaces[]; if (allow_download->value && allow_download_maps->value) { while (precache_tex < numtexinfo) { char fn[MAX_OSPATH]; sprintf(fn, "textures/%s.wal", map_surfaces[precache_tex++].rname); if (!CL_CheckOrDownloadFile(fn)) return; /* started a download */ } } precache_check = TEXTURE_CNT+999; } CL_RegisterSounds (); CL_PrepRefresh (); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount) ); } void CL_DownloadFileName(char *dest, int destlen, char *fn) { if (strncmp(fn, "players", 7) == 0) Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn); else Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn); } /* * Returns true if the file exists, otherwise it attempts * to start a download from the server. */ qboolean CL_CheckOrDownloadFile (char *filename) { FILE *fp; char name[MAX_OSPATH]; char *ptr; if (strstr (filename, "..")) { Com_Printf ("Refusing to download a path with ..\n"); return true; } /* fix backslashes - this is mostly für UNIX comaptiblity */ while ((ptr=strchr(filename,'\\'))) { *ptr = '/'; } if (FS_LoadFile (filename, NULL) != -1) { /* it exists, no need to download */ return true; } strcpy (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); strcat (cls.downloadtempname, ".tmp"); /* 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 */ CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); 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++; return false; } /* * Request a download from the server */ void CL_Download_f (void) { char filename[MAX_OSPATH]; if (Cmd_Argc() != 2) { Com_Printf("Usage: download \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; } strcpy (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); strcat (cls.downloadtempname, ".tmp"); MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, va("download %s", cls.downloadname)); cls.downloadnumber++; } /* * A download message has been received from the server */ void CL_ParseDownload (void) { int size, percent; char name[MAX_OSPATH]; int r; /* 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.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_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 */ cls.downloadpercent = percent; MSG_WriteByte (&cls.netchan.message, clc_stringcmd); SZ_Print (&cls.netchan.message, "nextdl"); } else { char oldn[MAX_OSPATH]; char newn[MAX_OSPATH]; 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; /* get another file if needed */ CL_RequestNextDownload (); } }