1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19 
20 //
21 // cl_download.c
22 //
23 
24 #include "cl_local.h"
25 
26 static int	cl_downloadCheck; // for autodownload of precache items
27 static int	cl_downloadSpawnCount;
28 static int	cl_downloadTexNum;
29 
30 static byte	*cl_downloadModel; // used for skin checking in alias models
31 static int	cl_downloadModelSkin;
32 
33 #define PLAYER_MULT 5
34 
35 // ENV_CNT is map load, ENV_CNT+1 is first env map
36 #define ENV_CNT (CS_PLAYERSKINS + MAX_CS_CLIENTS * PLAYER_MULT)
37 #define TEXTURE_CNT (ENV_CNT+13)
38 
39 /*
40 =====================================================================
41 
42 	DOWNLOAD PARSING AND HANDLING
43 
44 =====================================================================
45 */
46 
47 /*
48 ===============
49 CL_DownloadFileName
50 ===============
51 */
CL_DownloadFileName(char * dest,int destLen,char * fileName)52 static void CL_DownloadFileName (char *dest, int destLen, char *fileName)
53 {
54 	if (cl_downloadToBase->intVal) {
55 		Q_snprintfz (dest, destLen, BASE_MODDIRNAME "/%s", fileName);
56 		return;
57 	}
58 
59 	Q_snprintfz (dest, destLen, "%s/%s", FS_Gamedir(), fileName);
60 }
61 
62 
63 /*
64 ===============
65 CL_CheckOrDownloadFile
66 
67 Returns qTrue if the file exists, otherwise it attempts to start a download from the server.
68 ===============
69 */
CL_CheckOrDownloadFile(char * fileName)70 qBool CL_CheckOrDownloadFile (char *fileName)
71 {
72 	FILE	*fp;
73 	char	tempName[MAX_OSPATH];
74 
75 	// Don't download if there's ".." in the path
76 #if 0 // FIXME: looks like iD used this on some of it's models! (see boss2.bsp)
77 	if (strstr (fileName, "..")) {
78 		Com_Printf (PRNT_WARNING, "Refusing to check a path with '..' (%s)\n", fileName);
79 		return qTrue;
80 	}
81 #endif
82 	if (strchr (fileName, ' ')) {
83 		Com_Printf (PRNT_WARNING, "Refusing to check a path containing spaces (%s)\n", fileName);
84 		return qTrue;
85 	}
86 	if (strchr (fileName, ':')) {
87 		Com_Printf (PRNT_WARNING, "Refusing to check a path containing a colon (%s)\n", fileName);
88 		return qTrue;
89 	}
90 	if (fileName[0] == '/') {
91 		Com_Printf (PRNT_WARNING, "Refusing to check a path starting with '/' (%s)\n", fileName);
92 		return qTrue;
93 	}
94 
95 	// No need to redownload a file that already exists
96 	if (FS_FileExists (fileName) != -1)
97 		return qTrue;
98 
99 #ifdef CL_HTTPDL
100 	// Check with the download server
101 	if (CL_HTTPDL_QueueDownload (fileName))
102 		return qTrue;
103 #endif
104 
105 	// Don't attempt to download another file with UDP
106 	if (cls.download.file) {
107 		Com_Printf (PRNT_WARNING, "Refusing to download while a file is already downloading (%s)\n", fileName);
108 		return qTrue;
109 	}
110 
111 	// Copy a normalized version of the filename
112 	Com_NormalizePath (cls.download.name, sizeof (cls.download.name), fileName);
113 
114 	// Verify the final path is legal
115 	if (cls.download.name[0] == '/') {
116 		Com_Printf (PRNT_WARNING, "Refusing to download a path starting with '/' (%s)\n", cls.download.name);
117 		return qTrue;
118 	}
119 	if (cls.download.name[strlen(cls.download.name)-1] == '/') {
120 		Com_Printf (PRNT_WARNING, "Refusing to download a path ending with '/' (%s)\n", cls.download.name);
121 		return qTrue;
122 	}
123 
124 	// Download to a temp filename and rename when done (so if it's interrupted a runt wont be left)
125 	Com_StripExtension (cls.download.tempName, sizeof (cls.download.tempName), cls.download.name);
126 	Q_strcatz (cls.download.tempName, ".tmp", sizeof (cls.download.tempName));
127 
128 	// Resume if there's already a temp file
129 	CL_DownloadFileName (tempName, sizeof (tempName), cls.download.tempName);
130 	fp = fopen (tempName, "r+b");
131 	if (fp) {
132 		// It exists
133 		int		len;
134 
135 		fseek (fp, 0, SEEK_END);
136 		len = ftell (fp);
137 
138 		cls.download.file = fp;
139 
140 		// Give the server an offset to start the download
141 		Com_Printf (0, "Resuming %s\n", cls.download.name);
142 
143 		MSG_WriteByte (&cls.netChan.message, CLC_STRINGCMD);
144 		if (cls.serverProtocol == ENHANCED_PROTOCOL_VERSION)
145 			MSG_WriteString (&cls.netChan.message, Q_VarArgs ("download \"%s\" %i udp-zlib", cls.download.name, len));
146 		else
147 			MSG_WriteString (&cls.netChan.message, Q_VarArgs ("download \"%s\" %i", cls.download.name, len));
148 	}
149 	else {
150 		Com_Printf (0, "Downloading %s\n", cls.download.name);
151 
152 		MSG_WriteByte (&cls.netChan.message, CLC_STRINGCMD);
153 		if (cls.serverProtocol == ENHANCED_PROTOCOL_VERSION)
154 			MSG_WriteString (&cls.netChan.message, Q_VarArgs ("download \"%s\" 0 udp-zlib", cls.download.name));
155 		else
156 			MSG_WriteString (&cls.netChan.message, Q_VarArgs ("download \"%s\"", cls.download.name));
157 	}
158 
159 	cls.forcePacket = qTrue;
160 	return qFalse;
161 }
162 
163 
164 /*
165 =====================
166 CL_ParseDownload
167 
168 A download message has been received from the server
169 =====================
170 */
CL_ParseDownload(qBool compressed)171 void CL_ParseDownload (qBool compressed)
172 {
173 	int		size, percent;
174 	char	name[MAX_OSPATH];
175 
176 	// Read the data
177 	size = MSG_ReadShort (&cls.netMessage);
178 	percent = MSG_ReadByte (&cls.netMessage);
179 	if (size < 0) {
180 		if (size == -1)
181 			Com_Printf (PRNT_WARNING, "Server does not have this file.\n");
182 		else
183 			Com_Printf (PRNT_ERROR, "Bad download data from server.\n");
184 
185 		// Nuke the temp file name
186 		cls.download.tempName[0] = '\0';
187 		cls.download.name[0] = '\0';
188 
189 		if (cls.download.file) {
190 			// If here, we tried to resume a file but the server said no
191 			fclose (cls.download.file);
192 			cls.download.file = NULL;
193 		}
194 		CL_RequestNextDownload ();
195 		return;
196 	}
197 
198 	// Open the file if not opened yet
199 	if (!cls.download.file) {
200 		if (!cls.download.tempName[0]) {
201 			Com_Printf (PRNT_WARNING, "Received download packet without requesting it first, ignoring.\n");
202 			return;
203 		}
204 
205 		CL_DownloadFileName (name, sizeof (name), cls.download.tempName);
206 
207 		FS_CreatePath (name);
208 
209 		cls.download.file = fopen (name, "wb");
210 		if (!cls.download.file) {
211 			cls.netMessage.readCount += size;
212 			Com_Printf (PRNT_WARNING, "Failed to open %s\n", cls.download.tempName);
213 			CL_RequestNextDownload ();
214 			return;
215 		}
216 	}
217 
218 	// Insert block
219  	if (compressed) {
220  		uint16	uncompressedLen;
221  		byte	uncompressed[0xFFFF];
222 
223  		uncompressedLen = MSG_ReadShort (&cls.netMessage);
224  		if (!uncompressedLen)
225 			Com_Error (ERR_DROP, "CL_ParseDownload: uncompressedLen == 0");
226 
227  		FS_ZLibDecompress (cls.netMessage.data + cls.netMessage.readCount, size, uncompressed, uncompressedLen, -15);
228  		fwrite (uncompressed, 1, uncompressedLen, cls.download.file);
229  		Com_DevPrintf (0, "SVC_ZDOWNLOAD(%s): %d -> %d\n", cls.download.name, size, uncompressedLen);
230  	}
231  	else {
232 		fwrite (cls.netMessage.data + cls.netMessage.readCount, 1, size, cls.download.file);
233 	}
234 
235 	cls.netMessage.readCount += size;
236 
237 	if (percent != 100) {
238 		// Request next block
239 		cls.download.percent = percent;
240 
241 		MSG_WriteByte (&cls.netChan.message, CLC_STRINGCMD);
242 		MSG_WriteStringCat (&cls.netChan.message, "nextdl");
243 		cls.forcePacket = qTrue;
244 	}
245 	else {
246 		char	oldName[MAX_OSPATH];
247 		char	newName[MAX_OSPATH];
248 		int		rn;
249 
250 		fclose (cls.download.file);
251 
252 		// Rename the temp file to it's final name
253 		CL_DownloadFileName (oldName, sizeof (oldName), cls.download.tempName);
254 		CL_DownloadFileName (newName, sizeof (newName), cls.download.name);
255 		rn = rename (oldName, newName);
256 		if (rn)
257 			Com_Printf (PRNT_ERROR, "Failed to rename!\n");
258 		else
259 			Com_Printf (0, "Download of %s completed\n", newName);
260 
261 		cls.download.file = NULL;
262 		cls.download.percent = 0;
263 		cls.download.name[0] = '\0';
264 		cls.download.tempName[0] = '\0';
265 
266 		// Get another file if needed
267 		CL_RequestNextDownload ();
268 	}
269 }
270 
271 
272 /*
273 ==============
274 CL_ResetDownload
275 
276 Can only be called by CL_Precache_f!
277 ==============
278 */
CL_ResetDownload(void)279 void CL_ResetDownload (void)
280 {
281 	cl_downloadCheck = CS_MODELS;
282 	cl_downloadSpawnCount = atoi (Cmd_Argv (1));
283 	cl_downloadModel = 0;
284 	cl_downloadModelSkin = 0;
285 }
286 
287 
288 /*
289 ==============
290 CL_RequestNextDownload
291 ==============
292 */
CL_RequestNextDownload(void)293 void CL_RequestNextDownload (void)
294 {
295 	static const char	*skySuffix[6] = { "rt", "bk", "lf", "ft", "up", "dn" };
296 	uint32			mapCheckSum;		// for detecting cheater maps
297 	char			fileName[MAX_OSPATH];
298 	dMd2Header_t	*md2Header;
299 	int				fileLen;
300 
301 	if (Com_ClientState () != CA_CONNECTED)
302 		return;
303 
304 	//
305 	// If downloading is off by option, skip to loading the map
306 	//
307 	if (!allow_download->intVal && cl_downloadCheck < ENV_CNT)
308 		cl_downloadCheck = ENV_CNT;
309 
310 	if (cl_downloadCheck == CS_MODELS) {
311 		// Confirm map
312 		cl_downloadCheck = CS_MODELS+2; // 0 isn't used
313 		if (allow_download_maps->intVal) {
314 			if (!CL_CheckOrDownloadFile (cl.configStrings[CS_MODELS+1]))
315 				return; // started a download
316 		}
317 	}
318 
319 	//
320 	// Models
321 	//
322 	if (cl_downloadCheck >= CS_MODELS && cl_downloadCheck < CS_MODELS+MAX_CS_MODELS) {
323 		if (allow_download_models->intVal) {
324 			while (cl_downloadCheck < CS_MODELS+MAX_CS_MODELS && cl.configStrings[cl_downloadCheck][0]) {
325 				if (cl.configStrings[cl_downloadCheck][0] == '*' || cl.configStrings[cl_downloadCheck][0] == '#') {
326 					cl_downloadCheck++;
327 					continue;
328 				}
329 				if (cl_downloadModelSkin == 0) {
330 					if (!CL_CheckOrDownloadFile (cl.configStrings[cl_downloadCheck])) {
331 						cl_downloadModelSkin = 1;
332 						return; // Started a download
333 					}
334 					cl_downloadModelSkin = 1;
335 				}
336 
337 				// Checking for skins in the model
338 				if (!cl_downloadModel) {
339 					fileLen = FS_LoadFile (cl.configStrings[cl_downloadCheck], (void **)&cl_downloadModel, NULL);
340 					if (!cl_downloadModel || fileLen <= 0) {
341 						cl_downloadModelSkin = 0;
342 						cl_downloadCheck++;
343 						cl_downloadModel = NULL;
344 						continue; // Couldn't load it
345 					}
346 					// Hacks! yay!
347 					if (LittleLong (*(uint32 *)cl_downloadModel) != MD2_HEADER) {
348 						cl_downloadModelSkin = 0;
349 						cl_downloadCheck++;
350 
351 						FS_FreeFile (cl_downloadModel);
352 						cl_downloadModel = NULL;
353 						continue; // Not an alias model
354 					}
355 					md2Header = (dMd2Header_t *)cl_downloadModel;
356 					if (LittleLong (md2Header->version) != MD2_MODEL_VERSION) {
357 						cl_downloadCheck++;
358 						cl_downloadModelSkin = 0;
359 
360 						FS_FreeFile (cl_downloadModel);
361 						cl_downloadModel = NULL;
362 						continue; // Couldn't load it
363 					}
364 				}
365 
366 				md2Header = (dMd2Header_t *)cl_downloadModel;
367 				while (cl_downloadModelSkin - 1 < LittleLong (md2Header->numSkins)) {
368 					if (!CL_CheckOrDownloadFile ((char *)cl_downloadModel +
369 						LittleLong (md2Header->ofsSkins) +
370 						(cl_downloadModelSkin - 1)*MD2_MAX_SKINNAME)) {
371 						cl_downloadModelSkin++;
372 
373 						FS_FreeFile (cl_downloadModel);
374 						cl_downloadModel = NULL;
375 						return; // Started a download
376 					}
377 					cl_downloadModelSkin++;
378 				}
379 
380 				if (cl_downloadModel) {
381 					FS_FreeFile (cl_downloadModel);
382 					cl_downloadModel = NULL;
383 				}
384 
385 				cl_downloadModelSkin = 0;
386 				cl_downloadCheck++;
387 			}
388 		}
389 
390 		cl_downloadCheck = CS_SOUNDS;
391 	}
392 
393 	//
394 	// Sound
395 	//
396 	if (cl_downloadCheck >= CS_SOUNDS && cl_downloadCheck < CS_SOUNDS+MAX_CS_SOUNDS) {
397 		if (allow_download_sounds->intVal) {
398 			if (cl_downloadCheck == CS_SOUNDS)
399 				cl_downloadCheck++; // zero is blank
400 
401 			while (cl_downloadCheck < CS_SOUNDS+MAX_CS_SOUNDS && cl.configStrings[cl_downloadCheck][0]) {
402 				if (cl.configStrings[cl_downloadCheck][0] == '*') {
403 					cl_downloadCheck++;
404 					continue;
405 				}
406 
407 				Q_snprintfz (fileName, sizeof (fileName), "sound/%s", cl.configStrings[cl_downloadCheck++]);
408 				if (!CL_CheckOrDownloadFile (fileName))
409 					return; // started a download
410 			}
411 		}
412 
413 		cl_downloadCheck = CS_IMAGES;
414 	}
415 
416 	//
417 	// Images
418 	//
419 	if (cl_downloadCheck >= CS_IMAGES && cl_downloadCheck < CS_IMAGES+MAX_CS_IMAGES) {
420 		if (cl_downloadCheck == CS_IMAGES)
421 			cl_downloadCheck++; // zero is blank
422 
423 		while (cl_downloadCheck < CS_IMAGES+MAX_CS_IMAGES && cl.configStrings[cl_downloadCheck][0]) {
424 			Q_snprintfz (fileName, sizeof (fileName), "pics/%s.pcx", cl.configStrings[cl_downloadCheck++]);
425 			if (!CL_CheckOrDownloadFile (fileName))
426 				return; // started a download
427 		}
428 
429 		cl_downloadCheck = CS_PLAYERSKINS;
430 	}
431 
432 	//
433 	// Skins are special, since a player has three things to download:
434 	// - model
435 	// - weapon model
436 	// - weapon skin
437 	// - skin
438 	// - icon
439 	// So cl_downloadCheck is now * 5
440 	//
441 	if (cl_downloadCheck >= CS_PLAYERSKINS && cl_downloadCheck < CS_PLAYERSKINS+MAX_CS_CLIENTS*PLAYER_MULT) {
442 		if (allow_download_players->intVal) {
443 			while (cl_downloadCheck < CS_PLAYERSKINS + MAX_CS_CLIENTS * PLAYER_MULT) {
444 				int		i, n;
445 				char	model[MAX_QPATH];
446 				char	skin[MAX_QPATH];
447 				char	*p;
448 
449 				i = (cl_downloadCheck - CS_PLAYERSKINS)/PLAYER_MULT;
450 				n = (cl_downloadCheck - CS_PLAYERSKINS)%PLAYER_MULT;
451 
452 				if (!cl.configStrings[CS_PLAYERSKINS+i][0]) {
453 					cl_downloadCheck = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
454 					continue;
455 				}
456 
457 				if ((p = strchr(cl.configStrings[CS_PLAYERSKINS+i], '\\')) != NULL)
458 					p++;
459 				else
460 					p = cl.configStrings[CS_PLAYERSKINS+i];
461 
462 				Q_strncpyz (model, p, sizeof (model));
463 				p = strchr (model, '/');
464 				if (!p)
465 					p = strchr (model, '\\');
466 				if (p) {
467 					*p++ = 0;
468 					Q_strncpyz (skin, p, sizeof (skin));
469 				}
470 				else
471 					*skin = 0;
472 
473 				switch (n) {
474 				case 0:
475 					// Model
476 					Q_snprintfz (fileName, sizeof (fileName), "players/%s/tris.md2", model);
477 					if (!CL_CheckOrDownloadFile (fileName)) {
478 						cl_downloadCheck = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
479 						return; // started a download
480 					}
481 					n++;
482 					// FALL THROUGH
483 
484 				case 1:
485 					// Weapon model
486 					Q_snprintfz (fileName, sizeof (fileName), "players/%s/weapon.md2", model);
487 					if (!CL_CheckOrDownloadFile (fileName)) {
488 						cl_downloadCheck = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
489 						return; // started a download
490 					}
491 					n++;
492 					// FALL THROUGH
493 
494 				case 2:
495 					// Weapon skin
496 					Q_snprintfz (fileName, sizeof (fileName), "players/%s/weapon.pcx", model);
497 					if (!CL_CheckOrDownloadFile (fileName)) {
498 						cl_downloadCheck = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
499 						return; // started a download
500 					}
501 					n++;
502 					// FALL THROUGH
503 
504 				case 3:
505 					// Skin
506 					Q_snprintfz (fileName, sizeof (fileName), "players/%s/%s.pcx", model, skin);
507 					if (!CL_CheckOrDownloadFile (fileName)) {
508 						cl_downloadCheck = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
509 						return; // started a download
510 					}
511 					n++;
512 					// FALL THROUGH
513 
514 				case 4:
515 					// Skin_i
516 					Q_snprintfz (fileName, sizeof (fileName), "players/%s/%s_i.pcx", model, skin);
517 					if (!CL_CheckOrDownloadFile (fileName)) {
518 						cl_downloadCheck = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
519 						return; // started a download
520 					}
521 
522 					// Move on to next model
523 					cl_downloadCheck = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
524 				}
525 			}
526 		}
527 
528 		// Precache phase completed
529 		cl_downloadCheck = ENV_CNT;
530 	}
531 
532 	//
533 	// Map
534 	//
535 	if (cl_downloadCheck == ENV_CNT) {
536 		cl_downloadCheck++;
537 
538 		CM_LoadMap (cl.configStrings[CS_MODELS+1], qTrue, &mapCheckSum);
539 		if (mapCheckSum != atoi(cl.configStrings[CS_MAPCHECKSUM])) {
540 			Com_Error (ERR_DROP, "Local map version differs from server: %i != '%s'", mapCheckSum, cl.configStrings[CS_MAPCHECKSUM]);
541 			return;
542 		}
543 	}
544 
545 	//
546 	// Sky box env
547 	//
548 	if (cl_downloadCheck > ENV_CNT && cl_downloadCheck < TEXTURE_CNT) {
549 		if (allow_download->intVal && allow_download_maps->intVal) {
550 			while (cl_downloadCheck < TEXTURE_CNT) {
551 				int n = cl_downloadCheck++ - ENV_CNT - 1;
552 
553 				if (n & 1)
554 					Q_snprintfz (fileName, sizeof (fileName), "env/%s%s.pcx", cl.configStrings[CS_SKY], skySuffix[n/2]);
555 				else
556 					Q_snprintfz (fileName, sizeof (fileName), "env/%s%s.tga", cl.configStrings[CS_SKY], skySuffix[n/2]);
557 
558 				if (!CL_CheckOrDownloadFile (fileName))
559 					return; // Started a download
560 			}
561 		}
562 
563 		cl_downloadCheck = TEXTURE_CNT;
564 	}
565 
566 	if (cl_downloadCheck == TEXTURE_CNT) {
567 		cl_downloadCheck++;
568 		cl_downloadTexNum = 0;
569 	}
570 
571 	//
572 	// Confirm existance of textures, download any that don't exist
573 	//
574 	if (cl_downloadCheck == TEXTURE_CNT+1) {
575 		int			numTexInfo;
576 
577 		numTexInfo = CM_NumTexInfo ();
578 		if (allow_download->intVal && allow_download_maps->intVal) {
579 			while (cl_downloadTexNum < numTexInfo) {
580 				Q_snprintfz (fileName, sizeof (fileName), "textures/%s.wal", CM_SurfRName (cl_downloadTexNum++));
581 				if (!CL_CheckOrDownloadFile (fileName))
582 					return; // Started a download
583 			}
584 		}
585 
586 		cl_downloadCheck = TEXTURE_CNT+999;
587 	}
588 
589 	// All done!
590 	CL_CGModule_LoadMap ();
591 
592 	MSG_WriteByte (&cls.netChan.message, CLC_STRINGCMD);
593 	MSG_WriteString (&cls.netChan.message, Q_VarArgs ("begin %i\n", cl_downloadSpawnCount));
594 	cls.forcePacket = qTrue;
595 }
596 
597 /*
598 =======================================================================
599 
600 	HTTP DOWNLOADING
601 
602 =======================================================================
603 */
604 
605 #ifdef CL_HTTPDL
606 
607 /*
608 ==============
609 CL_HTTPDL_Init
610 ==============
611 */
CL_HTTPDL_Init(void)612 void CL_HTTPDL_Init (void)
613 {
614 }
615 
616 
617 /*
618 ==============
619 CL_HTTPDL_SetServer
620 ==============
621 */
CL_HTTPDL_SetServer(char * url)622 void CL_HTTPDL_SetServer (char *url)
623 {
624 }
625 
626 
627 /*
628 ==============
629 CL_HTTPDL_CancelDownloads
630 ==============
631 */
CL_HTTPDL_CancelDownloads(qBool permKill)632 void CL_HTTPDL_CancelDownloads (qBool permKill)
633 {
634 }
635 
636 
637 /*
638 ==============
639 CL_HTTPDL_QueueDownload
640 ==============
641 */
CL_HTTPDL_QueueDownload(char * file)642 qBool CL_HTTPDL_QueueDownload (char *file)
643 {
644 	return qFalse;
645 }
646 
647 
648 /*
649 ==============
650 CL_HTTPDL_PendingDownloads
651 ==============
652 */
CL_HTTPDL_PendingDownloads(void)653 qBool CL_HTTPDL_PendingDownloads (void)
654 {
655 	return qFalse;
656 }
657 
658 
659 /*
660 ==============
661 CL_HTTPDL_Cleanup
662 ==============
663 */
CL_HTTPDL_Cleanup(qBool shutdown)664 void CL_HTTPDL_Cleanup (qBool shutdown)
665 {
666 }
667 
668 
669 /*
670 ==============
671 CL_HTTPDL_RunDownloads
672 ==============
673 */
CL_HTTPDL_RunDownloads(void)674 void CL_HTTPDL_RunDownloads (void)
675 {
676 }
677 
678 #endif
679