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);
576 return FS_SysOpen (real_path, mode);
579 // Else, we look at the various search paths
580 return FS_FOpenFile (filepath, quiet);
591 int FS_Close (qfile_t* file)
593 if (fclose (file->stream))
605 Write "datasize" bytes into a file
608 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
610 return fwrite (data, 1, datasize, file->stream);
618 Read up to "buffersize" bytes from a file
621 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
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)
629 size_t remain = file->length - file->position;
630 if (buffersize > remain)
634 nb = fread (buffer, 1, buffersize, file->stream);
636 // Update the position index if the file is packed
637 if ((file->flags & FS_FLAG_PACKED) && nb > 0)
638 file->position += nb;
648 Flush the file output stream
651 int FS_Flush (qfile_t* file)
653 return fflush (file->stream);
661 Print a string into a file
664 int FS_Printf (qfile_t* file, const char* format, ...)
669 va_start (args, format);
670 result = vfprintf (file->stream, format, args);
681 Get the next character of a file
684 int FS_Getc (qfile_t* file)
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)
692 if (file->position >= file->length)
696 c = fgetc (file->stream);
698 // Update the position index if the file is packed
699 if ((file->flags & FS_FLAG_PACKED) && c != EOF)
710 Move the position index in a file
713 int FS_Seek (qfile_t* file, long offset, int whence)
715 // Packed files receive a special treatment
716 if (file->flags & FS_FLAG_PACKED)
721 offset += file->position;
722 // It continues on the next case (no break)
725 if (offset < 0 || offset > file->length)
727 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
729 file->position = offset;
733 if (offset > 0 || -offset > file->length)
735 if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1)
737 file->position = file->length + offset;
745 return fseek (file->stream, offset, whence);
753 Give the current position in a file
756 long FS_Tell (qfile_t* file)
758 if (file->flags & FS_FLAG_PACKED)
759 return file->position;
761 return ftell (file->stream);
769 Extract a line from a file
772 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
774 if (!fgets (buffer, buffersize, file->stream))
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)
780 size_t len = strlen (buffer);
781 size_t max = file->length - file->position;
786 file->position = file->length;
789 file->position += len;
800 Dynamic length version of fgets. DO NOT free the buffer.
803 char *FS_Getline (qfile_t *file)
805 static int size = 256;
806 static char *buf = 0;
811 buf = Mem_Alloc (fs_mempool, size);
813 if (!FS_Gets (file, buf, size))
817 while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
819 t = Mem_Alloc (fs_mempool, size + 256);
820 memcpy(t, buf, size);
824 if (!FS_Gets (file, buf + len, size - len))
828 while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
838 Extract a line from a file
841 int FS_Eof (qfile_t* file)
843 if (file->flags & FS_FLAG_PACKED)
844 return (file->position == file->length);
846 return feof (file->stream);
854 Filename are relative to the quake directory.
855 Always appends a 0 byte.
858 qbyte *FS_LoadFile (const char *path, qboolean quiet)
863 // look for it in the filesystem or pack files
864 h = FS_Open (path, "rb", quiet);
868 buf = Mem_Alloc(tempmempool, fs_filesize+1);
870 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
872 ((qbyte *)buf)[fs_filesize] = 0;
874 FS_Read (h, buf, fs_filesize);
885 The filename will be prefixed by the current game directory
888 qboolean FS_WriteFile (const char *filename, void *data, int len)
891 char name[MAX_OSPATH];
893 sprintf (name, "%s/%s", fs_gamedir, filename);
895 // Create directories up to the file
896 FS_CreatePath (name);
898 handle = fopen (name, "wb");
901 Con_Printf ("FS_WriteFile: failed on %s\n", name);
905 Con_DPrintf ("FS_WriteFile: %s\n", name);
906 fwrite (data, 1, len, handle);
913 =============================================================================
917 =============================================================================
925 void FS_StripExtension (const char *in, char *out)
932 else if (*in == '/' || *in == '\\' || *in == ':')
948 void FS_DefaultExtension (char *path, const char *extension)
952 // if path doesn't have a .EXT, append extension
953 // (extension should include the .)
954 src = path + strlen(path) - 1;
956 while (*src != '/' && src != path)
959 return; // it has an extension
963 strcat (path, extension);
967 qboolean FS_FileExists (const char *filename)
969 searchpath_t *search;
970 char netpath[MAX_OSPATH];
974 for (search = fs_searchpaths;search;search = search->next)
979 for (i = 0;i < pak->numfiles;i++)
980 if (!strcmp (pak->files[i].name, filename))
985 sprintf (netpath, "%s/%s",search->filename, filename);
986 if (FS_SysFileExists (netpath))
995 qboolean FS_SysFileExists (const char *path)
1000 f = fopen (path, "rb");
1011 if (stat (path,&buf) == -1)
1018 void FS_mkdir (const char *path)