File: qcommon\files.c
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 #include "qcommon.h"
22
23 // define this to dissalow any data but the demo pak file
24 //#define NO_ADDONS
25
26 // if a packfile directory differs from this, it is assumed to be hacked
27 // Full version
28 #define PAK0_CHECKSUM 0x40e614e0
29 // Demo
30 //#define PAK0_CHECKSUM 0xb2c6d7ea
31 // OEM
32 //#define PAK0_CHECKSUM 0x78e135c
33
34 /*
35 =============================================================================
36
37 QUAKE FILESYSTEM
38
39 =============================================================================
40 */
41
42
43 //
44 // in memory
45 //
46
47 typedef struct
48 {
49 char name[MAX_QPATH];
50 int filepos, filelen;
51 } packfile_t;
52
53 typedef struct pack_s
54 {
55 char filename[MAX_OSPATH];
56 FILE *handle;
57 int numfiles;
58 packfile_t *files;
59 } pack_t;
60
61 char fs_gamedir[MAX_OSPATH];
62 cvar_t *fs_basedir;
63 cvar_t *fs_cddir;
64 cvar_t *fs_gamedirvar;
65
66 typedef struct filelink_s
67 {
68 struct filelink_s *next;
69 char *from;
70 int fromlength;
71 char *to;
72 } filelink_t;
73
74 filelink_t *fs_links;
75
76 typedef struct searchpath_s
77 {
78 char filename[MAX_OSPATH];
79 pack_t *pack; // only one of filename / pack will be used
80 struct searchpath_s *next;
81 } searchpath_t;
82
83 searchpath_t *fs_searchpaths;
84 searchpath_t *fs_base_searchpaths; // without gamedirs
85
86
87 /*
88
89 All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources.
90
91 The "base directory" is the path to the directory holding the quake.exe and all game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory. The base directory is
92 only used during filesystem initialization.
93
94 The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to. This can be overridden with the "-game" command line parameter. The game directory can never be changed while quake is executing. This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't.
95
96 */
97
98
99 /*
100 ================
101 FS_filelength
102 ================
103 */
104 int FS_filelength (FILE *f)
105 {
106 int pos;
107 int end;
108
109 pos = ftell (f);
110 fseek (f, 0, SEEK_END);
111 end = ftell (f);
112 fseek (f, pos, SEEK_SET);
113
114 return end;
115 }
116
117
118 /*
119 ============
120 FS_CreatePath
121
122 Creates any directories needed to store the given filename
123 ============
124 */
125 void FS_CreatePath (char *path)
126 {
127 char *ofs;
128
129 for (ofs = path+1 ; *ofs ; ofs++)
130 {
131 if (*ofs == '/')
132 { // create the directory
133 *ofs = 0;
134 Sys_Mkdir (path);
135 *ofs = '/';
136 }
137 }
138 }
139
140
141 /*
142 ==============
143 FS_FCloseFile
144
145 For some reason, other dll's can't just cal fclose()
146 on files returned by FS_FOpenFile...
147 ==============
148 */
149 void FS_FCloseFile (FILE *f)
150 {
151 fclose (f);
152 }
153
154
155 // RAFAEL
156 /*
157 Developer_searchpath
158 */
159 int Developer_searchpath (int who)
160 {
161
162 int ch;
163 // PMM - warning removal
164 // char *start;
165 searchpath_t *search;
166
167 if (who == 1) // xatrix
168 ch = 'x';
169 else if (who == 2)
170 ch = 'r';
171
172 for (search = fs_searchpaths ; search ; search = search->next)
173 {
174 if (strstr (search->filename, "xatrix"))
175 return 1;
176
177 if (strstr (search->filename, "rogue"))
178 return 2;
179 /*
180 start = strchr (search->filename, ch);
181
182 if (start == NULL)
183 continue;
184
185 if (strcmp (start ,"xatrix") == 0)
186 return (1);
187 */
188 }
189 return (0);
190
191 }
192
193
194 /*
195 ===========
196 FS_FOpenFile
197
198 Finds the file in the search path.
199 returns filesize and an open FILE *
200 Used for streaming data out of either a pak file or
201 a seperate file.
202 ===========
203 */
204 int file_from_pak = 0;
205 #ifndef NO_ADDONS
206 int FS_FOpenFile (char *filename, FILE **file)
207 {
208 searchpath_t *search;
209 char netpath[MAX_OSPATH];
210 pack_t *pak;
211 int i;
212 filelink_t *link;
213
214 file_from_pak = 0;
215
216 // check for links first
217 for (link = fs_links ; link ; link=link->next)
218 {
219 if (!strncmp (filename, link->from, link->fromlength))
220 {
221 Com_sprintf (netpath, sizeof(netpath), "%s%s",link->to, filename+link->fromlength);
222 *file = fopen (netpath, "rb");
223 if (*file)
224 {
225 Com_DPrintf ("link file: %s\n",netpath);
226 return FS_filelength (*file);
227 }
228 return -1;
229 }
230 }
231
232 //
233 // search through the path, one element at a time
234 //
235 for (search = fs_searchpaths ; search ; search = search->next)
236 {
237 // is the element a pak file?
238 if (search->pack)
239 {
240 // look through all the pak file elements
241 pak = search->pack;
242 for (i=0 ; i<pak->numfiles ; i++)
243 if (!Q_strcasecmp (pak->files[i].name, filename))
244 { // found it!
245 file_from_pak = 1;
246 Com_DPrintf ("PackFile: %s : %s\n",pak->filename, filename);
247 // open a new file on the pakfile
248 *file = fopen (pak->filename, "rb");
249 if (!*file)
250 Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->filename);
251 fseek (*file, pak->files[i].filepos, SEEK_SET);
252 return pak->files[i].filelen;
253 }
254 }
255 else
256 {
257 // check a file in the directory tree
258
259 Com_sprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename);
260
261 *file = fopen (netpath, "rb");
262 if (!*file)
263 continue;
264
265 Com_DPrintf ("FindFile: %s\n",netpath);
266
267 return FS_filelength (*file);
268 }
269
270 }
271
272 Com_DPrintf ("FindFile: can't find %s\n", filename);
273
274 *file = NULL;
275 return -1;
276 }
277
278 #else
279
280 // this is just for demos to prevent add on hacking
281
282 int FS_FOpenFile (char *filename, FILE **file)
283 {
284 searchpath_t *search;
285 char netpath[MAX_OSPATH];
286 pack_t *pak;
287 int i;
288
289 file_from_pak = 0;
290
291 // get config from directory, everything else from pak
292 if (!strcmp(filename, "config.cfg") || !strncmp(filename, "players/", 8))
293 {
294 Com_sprintf (netpath, sizeof(netpath), "%s/%s",FS_Gamedir(), filename);
295
296 *file = fopen (netpath, "rb");
297 if (!*file)
298 return -1;
299
300 Com_DPrintf ("FindFile: %s\n",netpath);
301
302 return FS_filelength (*file);
303 }
304
305 for (search = fs_searchpaths ; search ; search = search->next)
306 if (search->pack)
307 break;
308 if (!search)
309 {
310 *file = NULL;
311 return -1;
312 }
313
314 pak = search->pack;
315 for (i=0 ; i<pak->numfiles ; i++)
316 if (!Q_strcasecmp (pak->files[i].name, filename))
317 { // found it!
318 file_from_pak = 1;
319 Com_DPrintf ("PackFile: %s : %s\n",pak->filename, filename);
320 // open a new file on the pakfile
321 *file = fopen (pak->filename, "rb");
322 if (!*file)
323 Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->filename);
324 fseek (*file, pak->files[i].filepos, SEEK_SET);
325 return pak->files[i].filelen;
326 }
327
328 Com_DPrintf ("FindFile: can't find %s\n", filename);
329
330 *file = NULL;
331 return -1;
332 }
333 334 #endif
335
336
337 /*
338 =================
339 FS_ReadFile
340
341 Properly handles partial reads
342 =================
343 */
344 void CDAudio_Stop(void);
345 #define MAX_READ 0x10000 // read in blocks of 64k
346 void FS_Read (void *buffer, int len, FILE *f)
347 {
348 int block, remaining;
349 int read;
350 byte *buf;
351 int tries;
352
353 buf = (byte *)buffer;
354
355 // read in chunks for progress bar
356 remaining = len;
357 tries = 0;
358 while (remaining)
359 {
360 block = remaining;
361 if (block > MAX_READ)
362 block = MAX_READ;
363 read = fread (buf, 1, block, f);
364 if (read == 0)
365 {
366 // we might have been trying to read from a CD
367 if (!tries)
368 {
369 tries = 1;
370 CDAudio_Stop();
371 }
372 else
373 Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
374 }
375
376 if (read == -1)
377 Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
378
379 // do some progress bar thing here...
380
381 remaining -= read;
382 buf += read;
383 }
384 }
385
386 /*
387 ============
388 FS_LoadFile
389
390 Filename are reletive to the quake search path
391 a null buffer will just return the file length without loading
392 ============
393 */
394 int FS_LoadFile (char *path, void **buffer)
395 {
396 FILE *h;
397 byte *buf;
398 int len;
399
400 buf = NULL; // quiet compiler warning
401
402 // look for it in the filesystem or pack files
403 len = FS_FOpenFile (path, &h);
404 if (!h)
405 {
406 if (buffer)
407 *buffer = NULL;
408 return -1;
409 }
410
411 if (!buffer)
412 {
413 fclose (h);
414 return len;
415 }
416
417 buf = Z_Malloc(len);
418 *buffer = buf;
419
420 FS_Read (buf, len, h);
421
422 fclose (h);
423
424 return len;
425 }
426
427
428 /*
429 =============
430 FS_FreeFile
431 =============
432 */
433 void FS_FreeFile (void *buffer)
434 {
435 Z_Free (buffer);
436 }
437
438 /*
439 =================
440 FS_LoadPackFile
441
442 Takes an explicit (not game tree related) path to a pak file.
443
444 Loads the header and directory, adding the files at the beginning
445 of the list so they override previous pack files.
446 =================
447 */
448 pack_t *FS_LoadPackFile (char *packfile)
449 {
450 dpackheader_t header;
451 int i;
452 packfile_t *newfiles;
453 int numpackfiles;
454 pack_t *pack;
455 FILE *packhandle;
456 dpackfile_t info[MAX_FILES_IN_PACK];
457 unsigned checksum;
458
459 packhandle = fopen(packfile, "rb");
460 if (!packhandle)
461 return NULL;
462
463 fread (&header, 1, sizeof(header), packhandle);
464 if (LittleLong(header.ident) != IDPAKHEADER)
465 Com_Error (ERR_FATAL, "%s is not a packfile", packfile);
466 header.dirofs = LittleLong (header.dirofs);
467 header.dirlen = LittleLong (header.dirlen);
468
469 numpackfiles = header.dirlen / sizeof(dpackfile_t);
470
471 if (numpackfiles > MAX_FILES_IN_PACK)
472 Com_Error (ERR_FATAL, "%s has %i files", packfile, numpackfiles);
473
474 newfiles = Z_Malloc (numpackfiles * sizeof(packfile_t));
475
476 fseek (packhandle, header.dirofs, SEEK_SET);
477 fread (info, 1, header.dirlen, packhandle);
478
479 // crc the directory to check for modifications
480 checksum = Com_BlockChecksum ((void *)info, header.dirlen);
481
482 #ifdef NO_ADDONS
485 #endif
486 // parse the directory
487 for (i=0 ; i<numpackfiles ; i++)
488 {
489 strcpy (newfiles[i].name, info[i].name);
490 newfiles[i].filepos = LittleLong(info[i].filepos);
491 newfiles[i].filelen = LittleLong(info[i].filelen);
492 }
493
494 pack = Z_Malloc (sizeof (pack_t));
495 strcpy (pack->filename, packfile);
496 pack->handle = packhandle;
497 pack->numfiles = numpackfiles;
498 pack->files = newfiles;
499
500 Com_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
501 return pack;
502 }
503
504
505 /*
506 ================
507 FS_AddGameDirectory
508
509 Sets fs_gamedir, adds the directory to the head of the path,
510 then loads and adds pak1.pak pak2.pak ...
511 ================
512 */
513 void FS_AddGameDirectory (char *dir)
514 {
515 int i;
516 searchpath_t *search;
517 pack_t *pak;
518 char pakfile[MAX_OSPATH];
519
520 strcpy (fs_gamedir, dir);
521
522 //
523 // add the directory to the search path
524 //
525 search = Z_Malloc (sizeof(searchpath_t));
526 strcpy (search->filename, dir);
527 search->next = fs_searchpaths;
528 fs_searchpaths = search;
529
530 //
531 // add any pak files in the format pak0.pak pak1.pak, ...
532 //
533 for (i=0; i<10; i++)
534 {
535 Com_sprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", dir, i);
536 pak = FS_LoadPackFile (pakfile);
537 if (!pak)
538 continue;
539 search = Z_Malloc (sizeof(searchpath_t));
540 search->pack = pak;
541 search->next = fs_searchpaths;
542 fs_searchpaths = search;
543 }
544
545
546 }
547
548 /*
549 ============
550 FS_Gamedir
551
552 Called to find where to write a file (demos, savegames, etc)
553 ============
554 */
555 char *FS_Gamedir (void)
556 {
557 if (*fs_gamedir)
558 return fs_gamedir;
559 else
560 return BASEDIRNAME;
561 }
562
563 /*
564 =============
565 FS_ExecAutoexec
566 =============
567 */
568 void FS_ExecAutoexec (void)
569 {
570 char *dir;
571 char name [MAX_QPATH];
572
573 dir = Cvar_VariableString("gamedir");
574 if (*dir)
575 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir);
576 else
577 Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME);
578 if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM))
579 Cbuf_AddText ("exec autoexec.cfg\n");
580 Sys_FindClose();
581 }
582
583
584 /*
585 ================
586 FS_SetGamedir
587
588 Sets the gamedir and path to a different directory.
589 ================
590 */
591 void FS_SetGamedir (char *dir)
592 {
593 searchpath_t *next;
594
595 if (strstr(dir, "..") || strstr(dir, "/")
596 || strstr(dir, "\\") || strstr(dir, ":") )
597 {
598 Com_Printf ("Gamedir should be a single filename, not a path\n");
599 return;
600 }
601
602 //
603 // free up any current game dir info
604 //
605 while (fs_searchpaths != fs_base_searchpaths)
606 {
607 if (fs_searchpaths->pack)
608 {
609 fclose (fs_searchpaths->pack->handle);
610 Z_Free (fs_searchpaths->pack->files);
611 Z_Free (fs_searchpaths->pack);
612 }
613 next = fs_searchpaths->next;
614 Z_Free (fs_searchpaths);
615 fs_searchpaths = next;
616 }
617
618 //
619 // flush all data, so it will be forced to reload
620 //
621 if (dedicated && !dedicated->value)
622 Cbuf_AddText ("vid_restart\nsnd_restart\n");
623
624 Com_sprintf (fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir);
625
626 if (!strcmp(dir,BASEDIRNAME) || (*dir == 0))
627 {
628 Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET);
629 Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
630 }
631 else
632 {
633 Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET);
634 if (fs_cddir->string[0])
635 FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) );
636 FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) );
637 }
638 }
639
640
641 /*
642 ================
643 FS_Link_f
644
645 Creates a filelink_t
646 ================
647 */
648 void FS_Link_f (void)
649 {
650 filelink_t *l, **prev;
651
652 if (Cmd_Argc() != 3)
653 {
654 Com_Printf ("USAGE: link <from> <to>\n");
655 return;
656 }
657
658 // see if the link already exists
659 prev = &fs_links;
660 for (l=fs_links ; l ; l=l->next)
661 {
662 if (!strcmp (l->from, Cmd_Argv(1)))
663 {
664 Z_Free (l->to);
665 if (!strlen(Cmd_Argv(2)))
666 { // delete it
667 *prev = l->next;
668 Z_Free (l->from);
669 Z_Free (l);
670 return;
671 }
672 l->to = CopyString (Cmd_Argv(2));
673 return;
674 }
675 prev = &l->next;
676 }
677
678 // create a new link
679 l = Z_Malloc(sizeof(*l));
680 l->next = fs_links;
681 fs_links = l;
682 l->from = CopyString(Cmd_Argv(1));
683 l->fromlength = strlen(l->from);
684 l->to = CopyString(Cmd_Argv(2));
685 }
686
687 /*
688 ** FS_ListFiles
689 */
690 char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave )
691 {
692 char *s;
693 int nfiles = 0;
694 char **list = 0;
695
696 s = Sys_FindFirst( findname, musthave, canthave );
697 while ( s )
698 {
699 if ( s[strlen(s)-1] != '.' )
700 nfiles++;
701 s = Sys_FindNext( musthave, canthave );
702 }
703 Sys_FindClose ();
704
705 if ( !nfiles )
706 return NULL;
707
708 nfiles++; // add space for a guard
709 *numfiles = nfiles;
710
711 list = malloc( sizeof( char * ) * nfiles );
712 memset( list, 0, sizeof( char * ) * nfiles );
713
714 s = Sys_FindFirst( findname, musthave, canthave );
715 nfiles = 0;
716 while ( s )
717 {
718 if ( s[strlen(s)-1] != '.' )
719 {
720 list[nfiles] = strdup( s );
721 #ifdef _WIN32
722 strlwr( list[nfiles] );
723 #endif
724 nfiles++;
725 }
726 s = Sys_FindNext( musthave, canthave );
727 }
728 Sys_FindClose ();
729
730 return list;
731 }
732
733 /*
734 ** FS_Dir_f
735 */
736 void FS_Dir_f( void )
737 {
738 char *path = NULL;
739 char findname[1024];
740 char wildcard[1024] = "*.*";
741 char **dirnames;
742 int ndirs;
743
744 if ( Cmd_Argc() != 1 )
745 {
746 strcpy( wildcard, Cmd_Argv( 1 ) );
747 }
748
749 while ( ( path = FS_NextPath( path ) ) != NULL )
750 {
751 char *tmp = findname;
752
753 Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard );
754
755 while ( *tmp != 0 )
756 {
757 if ( *tmp == '\\' )
758 *tmp = '/';
759 tmp++;
760 }
761 Com_Printf( "Directory of %s\n", findname );
762 Com_Printf( "----\n" );
763
764 if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 )
765 {
766 int i;
767
768 for ( i = 0; i < ndirs-1; i++ )
769 {
770 if ( strrchr( dirnames[i], '/' ) )
771 Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 );
772 else
773 Com_Printf( "%s\n", dirnames[i] );
774
775 free( dirnames[i] );
776 }
777 free( dirnames );
778 }
779 Com_Printf( "\n" );
780 };
781 }
782
783 /*
784 ============
785 FS_Path_f
786
787 ============
788 */
789 void FS_Path_f (void)
790 {
791 searchpath_t *s;
792 filelink_t *l;
793
794 Com_Printf ("Current search path:\n");
795 for (s=fs_searchpaths ; s ; s=s->next)
796 {
797 if (s == fs_base_searchpaths)
798 Com_Printf ("----------\n");
799 if (s->pack)
800 Com_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
801 else
802 Com_Printf ("%s\n", s->filename);
803 }
804
805 Com_Printf ("\nLinks:\n");
806 for (l=fs_links ; l ; l=l->next)
807 Com_Printf ("%s : %s\n", l->from, l->to);
808 }
809
810 /*
811 ================
812 FS_NextPath
813
814 Allows enumerating all of the directories in the search path
815 ================
816 */
817 char *FS_NextPath (char *prevpath)
818 {
819 searchpath_t *s;
820 char *prev;
821
822 if (!prevpath)
823 return fs_gamedir;
824
825 prev = fs_gamedir;
826 for (s=fs_searchpaths ; s ; s=s->next)
827 {
828 if (s->pack)
829 continue;
830 if (prevpath == prev)
831 return s->filename;
832 prev = s->filename;
833 }
834
835 return NULL;
836 }
837
838
839 /*
840 ================
841 FS_InitFilesystem
842 ================
843 */
844 void FS_InitFilesystem (void)
845 {
846 Cmd_AddCommand ("path", FS_Path_f);
847 Cmd_AddCommand ("link", FS_Link_f);
848 Cmd_AddCommand ("dir", FS_Dir_f );
849
850 //
851 // basedir <path>
852 // allows the game to run from outside the data tree
853 //
854 fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET);
855
856 //
857 // cddir <path>
858 // Logically concatenates the cddir after the basedir for
859 // allows the game to run from outside the data tree
860 //
861 fs_cddir = Cvar_Get ("cddir", "", CVAR_NOSET);
862 if (fs_cddir->string[0])
863 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) );
864
865 //
866 // start up with baseq2 by default
867 //
868 FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) );
869
870 // any set gamedirs will be freed up to here
871 fs_base_searchpaths = fs_searchpaths;
872
873 // check for game override
874 fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO);
875 if (fs_gamedirvar->string[0])
876 FS_SetGamedir (fs_gamedirvar->string);
877 }
878
879
880
881