]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
This is a patch from Elric greatly cleaning up the filesystem portions of the engine...
[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                 return FS_SysOpen (real_path, mode);
577         }
578
579         // Else, we look at the various search paths
580         return FS_FOpenFile (filepath, quiet);
581 }
582
583
584 /*
585 ====================
586 FS_Close
587
588 Close a file
589 ====================
590 */
591 int FS_Close (qfile_t* file)
592 {
593         if (fclose (file->stream))
594                 return EOF;
595
596         Mem_Free (file);
597         return 0;
598 }
599
600
601 /*
602 ====================
603 FS_Write
604
605 Write "datasize" bytes into a file
606 ====================
607 */
608 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
609 {
610         return fwrite (data, 1, datasize, file->stream);
611 }
612
613
614 /*
615 ====================
616 FS_Read
617
618 Read up to "buffersize" bytes from a file
619 ====================
620 */
621 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
622 {
623         size_t nb;
624
625         // If the file belongs to a package, we must take care
626         // to not read after the end of the file
627         if (file->flags & FS_FLAG_PACKED)
628         {
629                 size_t remain = file->length - file->position;
630                 if (buffersize > remain)
631                         buffersize = remain;
632         }
633
634         nb = fread (buffer, 1, buffersize, file->stream);
635
636         // Update the position index if the file is packed
637         if ((file->flags & FS_FLAG_PACKED) && nb > 0)
638                 file->position += nb;
639
640         return nb;
641 }
642
643
644 /*
645 ====================
646 FS_Flush
647
648 Flush the file output stream
649 ====================
650 */
651 int FS_Flush (qfile_t* file)
652 {
653         return fflush (file->stream);
654 }
655
656
657 /*
658 ====================
659 FS_Printf
660
661 Print a string into a file
662 ====================
663 */
664 int FS_Printf (qfile_t* file, const char* format, ...)
665 {
666         int result;
667         va_list args;
668
669         va_start (args, format);
670         result = vfprintf (file->stream, format, args);
671         va_end (args);
672
673         return result;
674 }
675
676
677 /*
678 ====================
679 FS_Getc
680
681 Get the next character of a file
682 ====================
683 */
684 int FS_Getc (qfile_t* file)
685 {
686         int c;
687
688         // If the file belongs to a package, we must take care
689         // to not read after the end of the file
690         if (file->flags & FS_FLAG_PACKED)
691         {
692                 if (file->position >= file->length)
693                         return EOF;
694         }
695
696         c = fgetc (file->stream);
697
698         // Update the position index if the file is packed
699         if ((file->flags & FS_FLAG_PACKED) && c != EOF)
700                 file->position++;
701
702         return c;
703 }
704
705
706 /*
707 ====================
708 FS_Seek
709
710 Move the position index in a file
711 ====================
712 */
713 int FS_Seek (qfile_t* file, long offset, int whence)
714 {
715         // Packed files receive a special treatment
716         if (file->flags & FS_FLAG_PACKED)
717         {
718                 switch (whence)
719                 {
720                         case SEEK_CUR:
721                                 offset += file->position;
722                                 // It continues on the next case (no break)
723
724                         case SEEK_SET:
725                                 if (offset < 0 || offset > file->length)
726                                         return -1;
727                                 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
728                                         return -1;
729                                 file->position = offset;
730                                 return 0;
731
732                         case SEEK_END:
733                                 if (offset > 0 || -offset > file->length)
734                                         return -1;
735                                 if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1)
736                                         return -1;
737                                 file->position = file->length + offset;
738                                 return 0;
739
740                         default:
741                                 return -1;
742                 }
743         }
744
745         return fseek (file->stream, offset, whence);
746 }
747
748
749 /*
750 ====================
751 FS_Tell
752
753 Give the current position in a file
754 ====================
755 */
756 long FS_Tell (qfile_t* file)
757 {
758         if (file->flags & FS_FLAG_PACKED)
759                 return file->position;
760
761         return ftell (file->stream);
762 }
763
764
765 /*
766 ====================
767 FS_Gets
768
769 Extract a line from a file
770 ====================
771 */
772 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
773 {
774         if (!fgets (buffer, buffersize, file->stream))
775                 return NULL;
776
777         // Check that we didn't read after the end of a packed file, and update the position
778         if (file->flags & FS_FLAG_PACKED)
779         {
780                 size_t len = strlen (buffer);
781                 size_t max = file->length - file->position;
782
783                 if (len > max)
784                 {
785                         buffer[max] = '\0';
786                         file->position = file->length;
787                 }
788                 else
789                         file->position += len;
790         }
791
792         return buffer;
793 }
794
795
796 /*
797 ==========
798 FS_Getline
799
800 Dynamic length version of fgets. DO NOT free the buffer.
801 ==========
802 */
803 char *FS_Getline (qfile_t *file)
804 {
805         static int  size = 256;
806         static char *buf = 0;
807         char        *t;
808         int         len;
809
810         if (!buf)
811                 buf = Mem_Alloc (fs_mempool, size);
812
813         if (!FS_Gets (file, buf, size))
814                 return 0;
815
816         len = strlen (buf);
817         while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
818         {
819                 t = Mem_Alloc (fs_mempool, size + 256);
820                 memcpy(t, buf, size);
821                 Mem_Free(buf);
822                 size += 256;
823                 buf = t;
824                 if (!FS_Gets (file, buf + len, size - len))
825                         break;
826                 len = strlen (buf);
827         }
828         while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
829                 buf[len - 1] = 0;
830         return buf;
831 }
832
833
834 /*
835 ====================
836 FS_Eof
837
838 Extract a line from a file
839 ====================
840 */
841 int FS_Eof (qfile_t* file)
842 {
843         if (file->flags & FS_FLAG_PACKED)
844                 return (file->position == file->length);
845         
846         return feof (file->stream);
847 }
848
849
850 /*
851 ============
852 FS_LoadFile
853
854 Filename are relative to the quake directory.
855 Always appends a 0 byte.
856 ============
857 */
858 qbyte *FS_LoadFile (const char *path, qboolean quiet)
859 {
860         qfile_t *h;
861         qbyte *buf;
862
863         // look for it in the filesystem or pack files
864         h = FS_Open (path, "rb", quiet);
865         if (!h)
866                 return NULL;
867
868         buf = Mem_Alloc(tempmempool, fs_filesize+1);
869         if (!buf)
870                 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
871
872         ((qbyte *)buf)[fs_filesize] = 0;
873
874         FS_Read (h, buf, fs_filesize);
875         FS_Close (h);
876
877         return buf;
878 }
879
880
881 /*
882 ============
883 FS_WriteFile
884
885 The filename will be prefixed by the current game directory
886 ============
887 */
888 qboolean FS_WriteFile (const char *filename, void *data, int len)
889 {
890         FILE *handle;
891         char name[MAX_OSPATH];
892
893         sprintf (name, "%s/%s", fs_gamedir, filename);
894
895         // Create directories up to the file
896         FS_CreatePath (name);
897
898         handle = fopen (name, "wb");
899         if (!handle)
900         {
901                 Con_Printf ("FS_WriteFile: failed on %s\n", name);
902                 return false;
903         }
904
905         Con_DPrintf ("FS_WriteFile: %s\n", name);
906         fwrite (data, 1, len, handle);
907         fclose (handle);
908         return true;
909 }
910
911
912 /*
913 =============================================================================
914
915 OTHERS FUNCTIONS
916
917 =============================================================================
918 */
919
920 /*
921 ============
922 FS_StripExtension
923 ============
924 */
925 void FS_StripExtension (const char *in, char *out)
926 {
927         char *last = NULL;
928         while (*in)
929         {
930                 if (*in == '.')
931                         last = out;
932                 else if (*in == '/' || *in == '\\' || *in == ':')
933                         last = NULL;
934                 *out++ = *in++;
935         }
936         if (last)
937                 *last = 0;
938         else
939                 *out = 0;
940 }
941
942
943 /*
944 ==================
945 FS_DefaultExtension
946 ==================
947 */
948 void FS_DefaultExtension (char *path, const char *extension)
949 {
950         const char *src;
951
952         // if path doesn't have a .EXT, append extension
953         // (extension should include the .)
954         src = path + strlen(path) - 1;
955
956         while (*src != '/' && src != path)
957         {
958                 if (*src == '.')
959                         return;                 // it has an extension
960                 src--;
961         }
962
963         strcat (path, extension);
964 }
965
966
967 qboolean FS_FileExists (const char *filename)
968 {
969         searchpath_t *search;
970         char netpath[MAX_OSPATH];
971         pack_t *pak;
972         int i;
973
974         for (search = fs_searchpaths;search;search = search->next)
975         {
976                 if (search->pack)
977                 {
978                         pak = search->pack;
979                         for (i = 0;i < pak->numfiles;i++)
980                                 if (!strcmp (pak->files[i].name, filename))
981                                         return true;
982                 }
983                 else
984                 {
985                         sprintf (netpath, "%s/%s",search->filename, filename);
986                         if (FS_SysFileExists (netpath))
987                                 return true;
988                 }
989         }
990
991         return false;
992 }
993
994
995 qboolean FS_SysFileExists (const char *path)
996 {
997 #if WIN32
998         FILE *f;
999
1000         f = fopen (path, "rb");
1001         if (f)
1002         {
1003                 fclose (f);
1004                 return true;
1005         }
1006
1007         return false;
1008 #else
1009         struct stat buf;
1010
1011         if (stat (path,&buf) == -1)
1012                 return false;
1013
1014         return true;
1015 #endif
1016 }
1017
1018 void FS_mkdir (const char *path)
1019 {
1020 #if WIN32
1021         _mkdir (path);
1022 #else
1023         mkdir (path, 0777);
1024 #endif
1025 }