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