4 Copyright (C) 2003 Mathieu Olivier
5 Copyright (C) 1999,2000 contributors of the QuakeForge project
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.
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.
16 See the GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to:
21 Free Software Foundation, Inc.
22 59 Temple Place - Suite 330
23 Boston, MA 02111-1307, USA
38 # include <sys/stat.h>
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.
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.
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.
70 =============================================================================
74 =============================================================================
77 // Our own file structure on top of FILE
81 FS_FLAG_PACKED = (1 << 0) // inside a package (PAK or PK3)
82 // FS_FLAG_COMPRESSED = (1 << 1) // compressed (inside a PK3 file)
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)
110 // Packages in memory
113 char name[MAX_QPATH];
114 int filepos, filelen;
117 typedef struct pack_s
119 char filename[MAX_OSPATH];
128 // Search paths for files (including packages)
129 typedef struct searchpath_s
131 // only one of filename / pack will be used
132 char filename[MAX_OSPATH];
134 struct searchpath_s *next;
139 =============================================================================
143 =============================================================================
146 mempool_t *fs_mempool;
147 mempool_t *pak_mempool;
151 pack_t *packlist = NULL;
153 searchpath_t *fs_searchpaths;
155 // LordHavoc: was 2048, increased to 65536 and changed info[MAX_PACK_FILES] to a temporary alloc
156 #define MAX_FILES_IN_PACK 65536
158 char fs_gamedir[MAX_OSPATH];
159 char fs_basedir[MAX_OSPATH];
161 qboolean fs_modified; // set true if using non-id files
165 =============================================================================
169 =============================================================================
177 LordHavoc: Previously only used for CopyFile, now also used for FS_WriteFile.
180 void FS_CreatePath (char *path)
184 for (ofs = path+1 ; *ofs ; ofs++)
186 if (*ofs == '/' || *ofs == '\\')
188 // create the directory
204 void FS_Path_f (void)
208 Con_Printf ("Current search path:\n");
209 for (s=fs_searchpaths ; s ; s=s->next)
213 Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
216 Con_Printf ("%s\n", s->filename);
225 Takes an explicit (not game tree related) path to a pak file.
227 Loads the header and directory, adding the files at the beginning
228 of the list so they override previous pack files.
231 pack_t *FS_LoadPackFile (const char *packfile)
233 dpackheader_t header;
237 // LordHavoc: changed from stack array to temporary alloc, allowing huge pack directories
240 packhandle = fopen (packfile, "rb");
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);
250 if (header.dirlen % sizeof(dpackfile_t))
251 Sys_Error ("%s has an invalid directory size", packfile);
253 numpackfiles = header.dirlen / sizeof(dpackfile_t);
255 if (numpackfiles > MAX_FILES_IN_PACK)
256 Sys_Error ("%s has %i files", packfile, numpackfiles);
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;
267 info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
268 fseek (packhandle, header.dirofs, SEEK_SET);
269 fread ((void *)info, 1, header.dirlen, packhandle);
271 // parse the directory
272 for (i = 0;i < numpackfiles;i++)
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);
281 Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
290 Sets fs_gamedir, adds the directory to the head of the path,
291 then loads and adds pak1.pak pak2.pak ...
294 void FS_AddGameDirectory (char *dir)
296 stringlist_t *list, *current;
297 searchpath_t *search;
299 char pakfile[MAX_OSPATH];
301 strcpy (fs_gamedir, dir);
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;
309 // add any paks in the directory
310 list = listdirectory(dir);
311 for (current = list;current;current = current->next)
313 if (matchpattern(current->text, "*.pak", true))
315 sprintf (pakfile, "%s/%s", dir, current->text);
316 pak = FS_LoadPackFile (pakfile);
319 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
321 search->next = fs_searchpaths;
322 fs_searchpaths = search;
325 Con_Printf("unable to load pak \"%s\"\n", pakfile);
337 char *FS_FileExtension (const char *in)
339 static char exten[8];
342 while (*in && *in != '.')
347 for (i=0 ; i<7 && *in ; i++,in++)
362 searchpath_t *search;
364 fs_mempool = Mem_AllocPool("file management");
365 pak_mempool = Mem_AllocPool("paks");
367 Cmd_AddCommand ("path", FS_Path_f);
369 strcpy(fs_basedir, ".");
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]);
377 i = strlen (fs_basedir);
378 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
381 // start up with GAMENAME by default (id1)
382 strcpy(com_modname, GAMENAME);
383 FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
387 strcpy(com_modname, gamedirname);
388 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
392 // Adds basedir/gamedir as an override game
393 i = COM_CheckParm ("-game");
394 if (i && i < com_argc-1)
397 strcpy(com_modname, com_argv[i+1]);
398 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i+1]));
401 // -path <dir or packfile> [<dir or packfile>] ...
402 // Fully specifies the exact search path, overriding the generated one
403 i = COM_CheckParm ("-path");
407 fs_searchpaths = NULL;
408 while (++i < com_argc)
410 if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
413 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
414 if ( !strcmp(FS_FileExtension(com_argv[i]), "pak") )
416 search->pack = FS_LoadPackFile (com_argv[i]);
418 Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
421 strcpy (search->filename, com_argv[i]);
422 search->next = fs_searchpaths;
423 fs_searchpaths = search;
430 =============================================================================
434 =============================================================================
441 Internal function used to create a qfile_t and open the relevant file on disk
444 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
448 file = Mem_Alloc (fs_mempool, sizeof (*file));
449 memset (file, 0, sizeof (*file));
451 file->stream = fopen (filepath, mode);
467 qfile_t *FS_OpenRead (const char *path, int offs, int len)
471 file = FS_SysOpen (path, "rb");
474 Sys_Error ("Couldn't open %s", path);
479 if (offs < 0 || len < 0)
481 FS_Seek (file, 0, SEEK_END);
482 len = FS_Tell (file);
483 FS_Seek (file, 0, SEEK_SET);
488 FS_Seek (file, offs, SEEK_SET);
490 file->flags |= FS_FLAG_PACKED;
505 If the requested file is inside a packfile, a new qfile_t* will be opened
511 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
513 searchpath_t *search;
514 char netpath[MAX_OSPATH];
518 filenamelen = strlen (filename);
520 // search through the path, one element at a time
521 search = fs_searchpaths;
523 for ( ; search ; search = search->next)
525 // is the element a pak file?
528 // look through all the pak file elements
530 for (i=0 ; i<pak->numfiles ; i++)
531 if (!strcmp (pak->files[i].name, filename))
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);
541 sprintf (netpath, "%s/%s",search->filename, filename);
543 if (!FS_SysFileExists (netpath))
547 Sys_Printf ("FindFile: %s\n",netpath);
548 return FS_OpenRead (netpath, -1, -1);
553 Sys_Printf ("FindFile: can't find %s\n", filename);
564 Open a file. The syntax is the same as fopen
567 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
569 // If the file is opened in "write" or "append" mode
570 if (strchr (mode, 'w') || strchr (mode, 'a'))
572 char real_path [MAX_OSPATH];
574 // Open the file on disk directly
575 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
577 // Create directories up to the file
578 FS_CreatePath (real_path);
580 return FS_SysOpen (real_path, mode);
583 // Else, we look at the various search paths
584 return FS_FOpenFile (filepath, quiet);
595 int FS_Close (qfile_t* file)
597 if (fclose (file->stream))
609 Write "datasize" bytes into a file
612 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
614 return fwrite (data, 1, datasize, file->stream);
622 Read up to "buffersize" bytes from a file
625 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
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)
633 size_t remain = file->length - file->position;
634 if (buffersize > remain)
638 nb = fread (buffer, 1, buffersize, file->stream);
640 // Update the position index if the file is packed
641 if ((file->flags & FS_FLAG_PACKED) && nb > 0)
642 file->position += nb;
652 Flush the file output stream
655 int FS_Flush (qfile_t* file)
657 return fflush (file->stream);
665 Print a string into a file
668 int FS_Printf (qfile_t* file, const char* format, ...)
673 va_start (args, format);
674 result = vfprintf (file->stream, format, args);
685 Get the next character of a file
688 int FS_Getc (qfile_t* file)
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)
696 if (file->position >= file->length)
700 c = fgetc (file->stream);
702 // Update the position index if the file is packed
703 if ((file->flags & FS_FLAG_PACKED) && c != EOF)
714 Move the position index in a file
717 int FS_Seek (qfile_t* file, long offset, int whence)
719 // Packed files receive a special treatment
720 if (file->flags & FS_FLAG_PACKED)
725 offset += file->position;
726 // It continues on the next case (no break)
729 if (offset < 0 || offset > file->length)
731 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
733 file->position = offset;
737 if (offset > 0 || -offset > file->length)
739 if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1)
741 file->position = file->length + offset;
749 return fseek (file->stream, offset, whence);
757 Give the current position in a file
760 long FS_Tell (qfile_t* file)
762 if (file->flags & FS_FLAG_PACKED)
763 return file->position;
765 return ftell (file->stream);
773 Extract a line from a file
776 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
778 if (!fgets (buffer, buffersize, file->stream))
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)
784 size_t len = strlen (buffer);
785 size_t max = file->length - file->position;
790 file->position = file->length;
793 file->position += len;
804 Dynamic length version of fgets. DO NOT free the buffer.
807 char *FS_Getline (qfile_t *file)
809 static int size = 256;
810 static char *buf = 0;
815 buf = Mem_Alloc (fs_mempool, size);
817 if (!FS_Gets (file, buf, size))
821 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
823 t = Mem_Alloc (fs_mempool, size + 256);
824 memcpy(t, buf, size);
828 if (!FS_Gets (file, buf + len, size - len))
832 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
842 Extract a line from a file
845 int FS_Eof (qfile_t* file)
847 if (file->flags & FS_FLAG_PACKED)
848 return (file->position == file->length);
850 return feof (file->stream);
858 Filename are relative to the quake directory.
859 Always appends a 0 byte.
862 qbyte *FS_LoadFile (const char *path, qboolean quiet)
867 // look for it in the filesystem or pack files
868 h = FS_Open (path, "rb", quiet);
872 buf = Mem_Alloc(tempmempool, fs_filesize+1);
874 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
876 ((qbyte *)buf)[fs_filesize] = 0;
878 FS_Read (h, buf, fs_filesize);
889 The filename will be prefixed by the current game directory
892 qboolean FS_WriteFile (const char *filename, void *data, int len)
895 char name[MAX_OSPATH];
897 sprintf (name, "%s/%s", fs_gamedir, filename);
899 // Create directories up to the file
900 FS_CreatePath (name);
902 handle = fopen (name, "wb");
905 Con_Printf ("FS_WriteFile: failed on %s\n", name);
909 Con_DPrintf ("FS_WriteFile: %s\n", name);
910 fwrite (data, 1, len, handle);
917 =============================================================================
921 =============================================================================
929 void FS_StripExtension (const char *in, char *out)
936 else if (*in == '/' || *in == '\\' || *in == ':')
952 void FS_DefaultExtension (char *path, const char *extension)
956 // if path doesn't have a .EXT, append extension
957 // (extension should include the .)
958 src = path + strlen(path) - 1;
960 while (*src != '/' && src != path)
963 return; // it has an extension
967 strcat (path, extension);
971 qboolean FS_FileExists (const char *filename)
973 searchpath_t *search;
974 char netpath[MAX_OSPATH];
978 for (search = fs_searchpaths;search;search = search->next)
983 for (i = 0;i < pak->numfiles;i++)
984 if (!strcmp (pak->files[i].name, filename))
989 sprintf (netpath, "%s/%s",search->filename, filename);
990 if (FS_SysFileExists (netpath))
999 qboolean FS_SysFileExists (const char *path)
1004 f = fopen (path, "rb");
1015 if (stat (path,&buf) == -1)
1022 void FS_mkdir (const char *path)