]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
create directories when opening any file for writing, because it's a real pain having...
[divverent/darkplaces.git] / fs.c
1 /*
2         Quake file system
3
4         Copyright (C) 2003 Mathieu Olivier
5         Copyright (C) 1999,2000  contributors of the QuakeForge project
6
7         This program is free software; you can redistribute it and/or
8         modify it under the terms of the GNU General Public License
9         as published by the Free Software Foundation; either version 2
10         of the License, or (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
16         See the GNU General Public License for more details.
17
18         You should have received a copy of the GNU General Public License
19         along with this program; if not, write to:
20
21                 Free Software Foundation, Inc.
22                 59 Temple Place - Suite 330
23                 Boston, MA  02111-1307, USA
24 */
25
26 #include "quakedef.h"
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <limits.h>
31 #include <fcntl.h>
32
33 #ifdef WIN32
34 # include <direct.h>
35 # include <io.h>
36 #else
37 # include <pwd.h>
38 # include <sys/stat.h>
39 # include <unistd.h>
40 #endif
41
42 #ifndef PATH_MAX
43 # define PATH_MAX 512
44 #endif
45
46 #include "fs.h"
47
48 /*
49
50 All of Quake's data access is through a hierchal file system, but the contents
51 of the file system can be transparently merged from several sources.
52
53 The "base directory" is the path to the directory holding the quake.exe and
54 all game directories.  The sys_* files pass this to host_init in
55 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
56 line parm to allow code debugging in a different directory.  The base
57 directory is only used during filesystem initialization.
58
59 The "game directory" is the first tree on the search path and directory that
60 all generated files (savegames, screenshots, demos, config files) will be
61 saved to.  This can be overridden with the "-game" command line parameter.
62 The game directory can never be changed while quake is executing.  This is a
63 precacution against having a malicious server instruct clients to write files
64 over areas they shouldn't.
65
66 */
67
68
69 /*
70 =============================================================================
71
72 TYPES
73
74 =============================================================================
75 */
76
77 // Our own file structure on top of FILE
78 typedef enum
79 {
80         FS_FLAG_NONE            = 0,
81         FS_FLAG_PACKED          = (1 << 0)      // inside a package (PAK or PK3)
82 //      FS_FLAG_COMPRESSED      = (1 << 1)  // compressed (inside a PK3 file)
83 } fs_flags_t;
84
85 struct qfile_s
86 {
87         fs_flags_t      flags;
88         FILE*           stream;
89         size_t          length;         // file size (PACKED only)
90         size_t          offset;         // offset into a package (PACKED only)
91         size_t          position;       // current position in the file (PACKED only)
92 };
93
94
95 // PAK files on disk
96 typedef struct
97 {
98         char name[56];
99         int filepos, filelen;
100 } dpackfile_t;
101
102 typedef struct
103 {
104         char id[4];
105         int dirofs;
106         int dirlen;
107 } dpackheader_t;
108
109
110 // Packages in memory
111 typedef struct
112 {
113         char name[MAX_QPATH];
114         int filepos, filelen;
115 } packfile_t;
116
117 typedef struct pack_s
118 {
119         char filename[MAX_OSPATH];
120         FILE *handle;
121         int numfiles;
122         packfile_t *files;
123         mempool_t *mempool;
124         struct pack_s *next;
125 } pack_t;
126
127
128 // Search paths for files (including packages)
129 typedef struct searchpath_s
130 {
131         // only one of filename / pack will be used
132         char filename[MAX_OSPATH];
133         pack_t *pack;
134         struct searchpath_s *next;
135 } searchpath_t;
136
137
138 /*
139 =============================================================================
140
141 VARIABLES
142
143 =============================================================================
144 */
145
146 mempool_t *fs_mempool;
147 mempool_t *pak_mempool;
148
149 int fs_filesize;
150
151 pack_t *packlist = NULL;
152
153 searchpath_t *fs_searchpaths;
154
155 // LordHavoc: was 2048, increased to 65536 and changed info[MAX_PACK_FILES] to a temporary alloc
156 #define MAX_FILES_IN_PACK       65536
157
158 char fs_gamedir[MAX_OSPATH];
159 char fs_basedir[MAX_OSPATH];
160
161 qboolean fs_modified;   // set true if using non-id files
162
163
164 /*
165 =============================================================================
166
167 PRIVATE FUNCTIONS
168
169 =============================================================================
170 */
171
172
173 /*
174 ============
175 FS_CreatePath
176
177 LordHavoc: Previously only used for CopyFile, now also used for FS_WriteFile.
178 ============
179 */
180 void FS_CreatePath (char *path)
181 {
182         char *ofs, save;
183
184         for (ofs = path+1 ; *ofs ; ofs++)
185         {
186                 if (*ofs == '/' || *ofs == '\\')
187                 {
188                         // create the directory
189                         save = *ofs;
190                         *ofs = 0;
191                         FS_mkdir (path);
192                         *ofs = save;
193                 }
194         }
195 }
196
197
198 /*
199 ============
200 FS_Path_f
201
202 ============
203 */
204 void FS_Path_f (void)
205 {
206         searchpath_t *s;
207
208         Con_Printf ("Current search path:\n");
209         for (s=fs_searchpaths ; s ; s=s->next)
210         {
211                 if (s->pack)
212                 {
213                         Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
214                 }
215                 else
216                         Con_Printf ("%s\n", s->filename);
217         }
218 }
219
220
221 /*
222 =================
223 FS_LoadPackFile
224
225 Takes an explicit (not game tree related) path to a pak file.
226
227 Loads the header and directory, adding the files at the beginning
228 of the list so they override previous pack files.
229 =================
230 */
231 pack_t *FS_LoadPackFile (const char *packfile)
232 {
233         dpackheader_t header;
234         int i, numpackfiles;
235         FILE *packhandle;
236         pack_t *pack;
237         // LordHavoc: changed from stack array to temporary alloc, allowing huge pack directories
238         dpackfile_t *info;
239
240         packhandle = fopen (packfile, "rb");
241         if (!packhandle)
242                 return NULL;
243
244         fread ((void *)&header, 1, sizeof(header), packhandle);
245         if (memcmp(header.id, "PACK", 4))
246                 Sys_Error ("%s is not a packfile", packfile);
247         header.dirofs = LittleLong (header.dirofs);
248         header.dirlen = LittleLong (header.dirlen);
249
250         if (header.dirlen % sizeof(dpackfile_t))
251                 Sys_Error ("%s has an invalid directory size", packfile);
252
253         numpackfiles = header.dirlen / sizeof(dpackfile_t);
254
255         if (numpackfiles > MAX_FILES_IN_PACK)
256                 Sys_Error ("%s has %i files", packfile, numpackfiles);
257
258         pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
259         strcpy (pack->filename, packfile);
260         pack->handle = packhandle;
261         pack->numfiles = numpackfiles;
262         pack->mempool = Mem_AllocPool(packfile);
263         pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
264         pack->next = packlist;
265         packlist = pack;
266
267         info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
268         fseek (packhandle, header.dirofs, SEEK_SET);
269         fread ((void *)info, 1, header.dirlen, packhandle);
270
271 // parse the directory
272         for (i = 0;i < numpackfiles;i++)
273         {
274                 strcpy (pack->files[i].name, info[i].name);
275                 pack->files[i].filepos = LittleLong(info[i].filepos);
276                 pack->files[i].filelen = LittleLong(info[i].filelen);
277         }
278
279         Mem_Free(info);
280
281         Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
282         return pack;
283 }
284
285
286 /*
287 ================
288 FS_AddGameDirectory
289
290 Sets fs_gamedir, adds the directory to the head of the path,
291 then loads and adds pak1.pak pak2.pak ...
292 ================
293 */
294 void FS_AddGameDirectory (char *dir)
295 {
296         stringlist_t *list, *current;
297         searchpath_t *search;
298         pack_t *pak;
299         char pakfile[MAX_OSPATH];
300
301         strcpy (fs_gamedir, dir);
302
303         // add the directory to the search path
304         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
305         strcpy (search->filename, dir);
306         search->next = fs_searchpaths;
307         fs_searchpaths = search;
308
309         // add any paks in the directory
310         list = listdirectory(dir);
311         for (current = list;current;current = current->next)
312         {
313                 if (matchpattern(current->text, "*.pak", true))
314                 {
315                         sprintf (pakfile, "%s/%s", dir, current->text);
316                         pak = FS_LoadPackFile (pakfile);
317                         if (pak)
318                         {
319                                 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
320                                 search->pack = pak;
321                                 search->next = fs_searchpaths;
322                                 fs_searchpaths = search;
323                         }
324                         else
325                                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
326                 }
327         }
328         freedirectory(list);
329 }
330
331
332 /*
333 ============
334 FS_FileExtension
335 ============
336 */
337 char *FS_FileExtension (const char *in)
338 {
339         static char exten[8];
340         int i;
341
342         while (*in && *in != '.')
343                 in++;
344         if (!*in)
345                 return "";
346         in++;
347         for (i=0 ; i<7 && *in ; i++,in++)
348                 exten[i] = *in;
349         exten[i] = 0;
350         return exten;
351 }
352
353
354 /*
355 ================
356 FS_Init
357 ================
358 */
359 void FS_Init (void)
360 {
361         int i;
362         searchpath_t *search;
363
364         fs_mempool = Mem_AllocPool("file management");
365         pak_mempool = Mem_AllocPool("paks");
366
367         Cmd_AddCommand ("path", FS_Path_f);
368
369         strcpy(fs_basedir, ".");
370
371         // -basedir <path>
372         // Overrides the system supplied base directory (under GAMENAME)
373         i = COM_CheckParm ("-basedir");
374         if (i && i < com_argc-1)
375                 strcpy (fs_basedir, com_argv[i+1]);
376
377         i = strlen (fs_basedir);
378         if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
379                 fs_basedir[i-1] = 0;
380
381         // start up with GAMENAME by default (id1)
382         strcpy(com_modname, GAMENAME);
383         FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
384         if (gamedirname[0])
385         {
386                 fs_modified = true;
387                 strcpy(com_modname, gamedirname);
388                 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
389         }
390
391         // -game <gamedir>
392         // Adds basedir/gamedir as an override game
393         i = COM_CheckParm ("-game");
394         if (i && i < com_argc-1)
395         {
396                 fs_modified = true;
397                 strcpy(com_modname, com_argv[i+1]);
398                 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i+1]));
399         }
400
401         // -path <dir or packfile> [<dir or packfile>] ...
402         // Fully specifies the exact search path, overriding the generated one
403         i = COM_CheckParm ("-path");
404         if (i)
405         {
406                 fs_modified = true;
407                 fs_searchpaths = NULL;
408                 while (++i < com_argc)
409                 {
410                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
411                                 break;
412
413                         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
414                         if ( !strcmp(FS_FileExtension(com_argv[i]), "pak") )
415                         {
416                                 search->pack = FS_LoadPackFile (com_argv[i]);
417                                 if (!search->pack)
418                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
419                         }
420                         else
421                                 strcpy (search->filename, com_argv[i]);
422                         search->next = fs_searchpaths;
423                         fs_searchpaths = search;
424                 }
425         }
426 }
427
428
429 /*
430 =============================================================================
431
432 MAIN FUNCTIONS
433
434 =============================================================================
435 */
436
437 /*
438 ====================
439 FS_Open
440
441 Internal function used to create a qfile_t and open the relevant file on disk
442 ====================
443 */
444 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
445 {
446         qfile_t* file;
447
448         file = Mem_Alloc (fs_mempool, sizeof (*file));
449         memset (file, 0, sizeof (*file));
450
451         file->stream = fopen (filepath, mode);
452         if (!file->stream)
453         {
454                 Mem_Free (file);
455                 return NULL;
456         }
457
458         return file;
459 }
460
461
462 /*
463 ===========
464 FS_OpenRead
465 ===========
466 */
467 qfile_t *FS_OpenRead (const char *path, int offs, int len)
468 {
469         qfile_t* file;
470
471         file = FS_SysOpen (path, "rb");
472         if (!file)
473         {
474                 Sys_Error ("Couldn't open %s", path);
475                 return NULL;
476         }
477
478         // Normal file
479         if (offs < 0 || len < 0)
480         {
481                 FS_Seek (file, 0, SEEK_END);
482                 len = FS_Tell (file);
483                 FS_Seek (file, 0, SEEK_SET);
484         }
485         // Packed file
486         else
487         {
488                 FS_Seek (file, offs, SEEK_SET);
489
490                 file->flags |= FS_FLAG_PACKED;
491                 file->length = len;
492                 file->offset = offs;
493                 file->position = 0;
494         }
495
496         fs_filesize = len;
497
498         return file;
499 }
500
501 /*
502 ===========
503 FS_FOpenFile
504
505 If the requested file is inside a packfile, a new qfile_t* will be opened
506 into the file.
507
508 Sets fs_filesize
509 ===========
510 */
511 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
512 {
513         searchpath_t *search;
514         char netpath[MAX_OSPATH];
515         pack_t *pak;
516         int i, filenamelen;
517
518         filenamelen = strlen (filename);
519
520         // search through the path, one element at a time
521         search = fs_searchpaths;
522
523         for ( ; search ; search = search->next)
524         {
525                 // is the element a pak file?
526                 if (search->pack)
527                 {
528                         // look through all the pak file elements
529                         pak = search->pack;
530                         for (i=0 ; i<pak->numfiles ; i++)
531                                 if (!strcmp (pak->files[i].name, filename))
532                                 {       // found it!
533                                         if (!quiet)
534                                                 Sys_Printf ("PackFile: %s : %s\n",pak->filename, pak->files[i].name);
535                                         // open a new file in the pakfile
536                                         return FS_OpenRead (pak->filename, pak->files[i].filepos, pak->files[i].filelen);
537                                 }
538                 }
539                 else
540                 {
541                         sprintf (netpath, "%s/%s",search->filename, filename);
542
543                         if (!FS_SysFileExists (netpath))
544                                 continue;
545
546                         if (!quiet)
547                                 Sys_Printf ("FindFile: %s\n",netpath);
548                         return FS_OpenRead (netpath, -1, -1);
549                 }
550         }
551
552         if (!quiet)
553                 Sys_Printf ("FindFile: can't find %s\n", filename);
554
555         fs_filesize = -1;
556         return NULL;
557 }
558
559
560 /*
561 ====================
562 FS_Open
563
564 Open a file. The syntax is the same as fopen
565 ====================
566 */
567 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
568 {
569         // If the file is opened in "write" or "append" mode
570         if (strchr (mode, 'w') || strchr (mode, 'a'))
571         {
572                 char real_path [MAX_OSPATH];
573
574                 // Open the file on disk directly
575                 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
576
577                 // Create directories up to the file
578                 FS_CreatePath (real_path);
579
580                 return FS_SysOpen (real_path, mode);
581         }
582
583         // Else, we look at the various search paths
584         return FS_FOpenFile (filepath, quiet);
585 }
586
587
588 /*
589 ====================
590 FS_Close
591
592 Close a file
593 ====================
594 */
595 int FS_Close (qfile_t* file)
596 {
597         if (fclose (file->stream))
598                 return EOF;
599
600         Mem_Free (file);
601         return 0;
602 }
603
604
605 /*
606 ====================
607 FS_Write
608
609 Write "datasize" bytes into a file
610 ====================
611 */
612 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
613 {
614         return fwrite (data, 1, datasize, file->stream);
615 }
616
617
618 /*
619 ====================
620 FS_Read
621
622 Read up to "buffersize" bytes from a file
623 ====================
624 */
625 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
626 {
627         size_t nb;
628
629         // If the file belongs to a package, we must take care
630         // to not read after the end of the file
631         if (file->flags & FS_FLAG_PACKED)
632         {
633                 size_t remain = file->length - file->position;
634                 if (buffersize > remain)
635                         buffersize = remain;
636         }
637
638         nb = fread (buffer, 1, buffersize, file->stream);
639
640         // Update the position index if the file is packed
641         if ((file->flags & FS_FLAG_PACKED) && nb > 0)
642                 file->position += nb;
643
644         return nb;
645 }
646
647
648 /*
649 ====================
650 FS_Flush
651
652 Flush the file output stream
653 ====================
654 */
655 int FS_Flush (qfile_t* file)
656 {
657         return fflush (file->stream);
658 }
659
660
661 /*
662 ====================
663 FS_Printf
664
665 Print a string into a file
666 ====================
667 */
668 int FS_Printf (qfile_t* file, const char* format, ...)
669 {
670         int result;
671         va_list args;
672
673         va_start (args, format);
674         result = vfprintf (file->stream, format, args);
675         va_end (args);
676
677         return result;
678 }
679
680
681 /*
682 ====================
683 FS_Getc
684
685 Get the next character of a file
686 ====================
687 */
688 int FS_Getc (qfile_t* file)
689 {
690         int c;
691
692         // If the file belongs to a package, we must take care
693         // to not read after the end of the file
694         if (file->flags & FS_FLAG_PACKED)
695         {
696                 if (file->position >= file->length)
697                         return EOF;
698         }
699
700         c = fgetc (file->stream);
701
702         // Update the position index if the file is packed
703         if ((file->flags & FS_FLAG_PACKED) && c != EOF)
704                 file->position++;
705
706         return c;
707 }
708
709
710 /*
711 ====================
712 FS_Seek
713
714 Move the position index in a file
715 ====================
716 */
717 int FS_Seek (qfile_t* file, long offset, int whence)
718 {
719         // Packed files receive a special treatment
720         if (file->flags & FS_FLAG_PACKED)
721         {
722                 switch (whence)
723                 {
724                         case SEEK_CUR:
725                                 offset += file->position;
726                                 // It continues on the next case (no break)
727
728                         case SEEK_SET:
729                                 if (offset < 0 || offset > file->length)
730                                         return -1;
731                                 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
732                                         return -1;
733                                 file->position = offset;
734                                 return 0;
735
736                         case SEEK_END:
737                                 if (offset > 0 || -offset > file->length)
738                                         return -1;
739                                 if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1)
740                                         return -1;
741                                 file->position = file->length + offset;
742                                 return 0;
743
744                         default:
745                                 return -1;
746                 }
747         }
748
749         return fseek (file->stream, offset, whence);
750 }
751
752
753 /*
754 ====================
755 FS_Tell
756
757 Give the current position in a file
758 ====================
759 */
760 long FS_Tell (qfile_t* file)
761 {
762         if (file->flags & FS_FLAG_PACKED)
763                 return file->position;
764
765         return ftell (file->stream);
766 }
767
768
769 /*
770 ====================
771 FS_Gets
772
773 Extract a line from a file
774 ====================
775 */
776 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
777 {
778         if (!fgets (buffer, buffersize, file->stream))
779                 return NULL;
780
781         // Check that we didn't read after the end of a packed file, and update the position
782         if (file->flags & FS_FLAG_PACKED)
783         {
784                 size_t len = strlen (buffer);
785                 size_t max = file->length - file->position;
786
787                 if (len > max)
788                 {
789                         buffer[max] = '\0';
790                         file->position = file->length;
791                 }
792                 else
793                         file->position += len;
794         }
795
796         return buffer;
797 }
798
799
800 /*
801 ==========
802 FS_Getline
803
804 Dynamic length version of fgets. DO NOT free the buffer.
805 ==========
806 */
807 char *FS_Getline (qfile_t *file)
808 {
809         static int  size = 256;
810         static char *buf = 0;
811         char        *t;
812         int         len;
813
814         if (!buf)
815                 buf = Mem_Alloc (fs_mempool, size);
816
817         if (!FS_Gets (file, buf, size))
818                 return 0;
819
820         len = strlen (buf);
821         while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
822         {
823                 t = Mem_Alloc (fs_mempool, size + 256);
824                 memcpy(t, buf, size);
825                 Mem_Free(buf);
826                 size += 256;
827                 buf = t;
828                 if (!FS_Gets (file, buf + len, size - len))
829                         break;
830                 len = strlen (buf);
831         }
832         while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
833                 buf[len - 1] = 0;
834         return buf;
835 }
836
837
838 /*
839 ====================
840 FS_Eof
841
842 Extract a line from a file
843 ====================
844 */
845 int FS_Eof (qfile_t* file)
846 {
847         if (file->flags & FS_FLAG_PACKED)
848                 return (file->position == file->length);
849         
850         return feof (file->stream);
851 }
852
853
854 /*
855 ============
856 FS_LoadFile
857
858 Filename are relative to the quake directory.
859 Always appends a 0 byte.
860 ============
861 */
862 qbyte *FS_LoadFile (const char *path, qboolean quiet)
863 {
864         qfile_t *h;
865         qbyte *buf;
866
867         // look for it in the filesystem or pack files
868         h = FS_Open (path, "rb", quiet);
869         if (!h)
870                 return NULL;
871
872         buf = Mem_Alloc(tempmempool, fs_filesize+1);
873         if (!buf)
874                 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
875
876         ((qbyte *)buf)[fs_filesize] = 0;
877
878         FS_Read (h, buf, fs_filesize);
879         FS_Close (h);
880
881         return buf;
882 }
883
884
885 /*
886 ============
887 FS_WriteFile
888
889 The filename will be prefixed by the current game directory
890 ============
891 */
892 qboolean FS_WriteFile (const char *filename, void *data, int len)
893 {
894         FILE *handle;
895         char name[MAX_OSPATH];
896
897         sprintf (name, "%s/%s", fs_gamedir, filename);
898
899         // Create directories up to the file
900         FS_CreatePath (name);
901
902         handle = fopen (name, "wb");
903         if (!handle)
904         {
905                 Con_Printf ("FS_WriteFile: failed on %s\n", name);
906                 return false;
907         }
908
909         Con_DPrintf ("FS_WriteFile: %s\n", name);
910         fwrite (data, 1, len, handle);
911         fclose (handle);
912         return true;
913 }
914
915
916 /*
917 =============================================================================
918
919 OTHERS FUNCTIONS
920
921 =============================================================================
922 */
923
924 /*
925 ============
926 FS_StripExtension
927 ============
928 */
929 void FS_StripExtension (const char *in, char *out)
930 {
931         char *last = NULL;
932         while (*in)
933         {
934                 if (*in == '.')
935                         last = out;
936                 else if (*in == '/' || *in == '\\' || *in == ':')
937                         last = NULL;
938                 *out++ = *in++;
939         }
940         if (last)
941                 *last = 0;
942         else
943                 *out = 0;
944 }
945
946
947 /*
948 ==================
949 FS_DefaultExtension
950 ==================
951 */
952 void FS_DefaultExtension (char *path, const char *extension)
953 {
954         const char *src;
955
956         // if path doesn't have a .EXT, append extension
957         // (extension should include the .)
958         src = path + strlen(path) - 1;
959
960         while (*src != '/' && src != path)
961         {
962                 if (*src == '.')
963                         return;                 // it has an extension
964                 src--;
965         }
966
967         strcat (path, extension);
968 }
969
970
971 qboolean FS_FileExists (const char *filename)
972 {
973         searchpath_t *search;
974         char netpath[MAX_OSPATH];
975         pack_t *pak;
976         int i;
977
978         for (search = fs_searchpaths;search;search = search->next)
979         {
980                 if (search->pack)
981                 {
982                         pak = search->pack;
983                         for (i = 0;i < pak->numfiles;i++)
984                                 if (!strcmp (pak->files[i].name, filename))
985                                         return true;
986                 }
987                 else
988                 {
989                         sprintf (netpath, "%s/%s",search->filename, filename);
990                         if (FS_SysFileExists (netpath))
991                                 return true;
992                 }
993         }
994
995         return false;
996 }
997
998
999 qboolean FS_SysFileExists (const char *path)
1000 {
1001 #if WIN32
1002         FILE *f;
1003
1004         f = fopen (path, "rb");
1005         if (f)
1006         {
1007                 fclose (f);
1008                 return true;
1009         }
1010
1011         return false;
1012 #else
1013         struct stat buf;
1014
1015         if (stat (path,&buf) == -1)
1016                 return false;
1017
1018         return true;
1019 #endif
1020 }
1021
1022 void FS_mkdir (const char *path)
1023 {
1024 #if WIN32
1025         _mkdir (path);
1026 #else
1027         mkdir (path, 0777);
1028 #endif
1029 }