Elric's pk3 support (no support for compressed files yet, that's still to come)
[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 CONSTANTS
73
74 =============================================================================
75 */
76
77 // Magic numbers of a ZIP file (big-endian format)
78 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
79 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
80 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
81
82 // Other constants for ZIP files
83 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
84 #define ZIP_END_CDIR_SIZE                       22
85 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
86 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
87
88
89 /*
90 =============================================================================
91
92 TYPES
93
94 =============================================================================
95 */
96
97 // Our own file structure on top of FILE
98 typedef enum
99 {
100         FS_FLAG_NONE            = 0,
101         FS_FLAG_PACKED          = (1 << 0)      // inside a package (PAK or PK3)
102 } fs_flags_t;
103
104 struct qfile_s
105 {
106         fs_flags_t      flags;
107         FILE*           stream;
108         size_t          length;         // file size (PACKED only)
109         size_t          offset;         // offset into a package (PACKED only)
110         size_t          position;       // current position in the file (PACKED only)
111 };
112
113
114 // ------ PK3 files on disk ------ //
115
116 // You can get the complete ZIP format description from PKWARE website
117
118 typedef struct
119 {
120         unsigned int signature;
121         unsigned short disknum;
122         unsigned short cdir_disknum;    // number of the disk with the start of the central directory
123         unsigned short localentries;    // number of entries in the central directory on this disk
124         unsigned short nbentries;               // total number of entries in the central directory on this disk
125         unsigned int cdir_size;                 // size of the central directory
126         unsigned int cdir_offset;               // with respect to the starting disk number
127         unsigned short comment_size;
128 } pk3_endOfCentralDir_t;
129
130
131 // ------ PAK files on disk ------ //
132 typedef struct
133 {
134         char name[56];
135         int filepos, filelen;
136 } dpackfile_t;
137
138 typedef struct
139 {
140         char id[4];
141         int dirofs;
142         int dirlen;
143 } dpackheader_t;
144
145
146 // Packages in memory
147 typedef enum 
148 {
149         FILE_FLAG_NONE          = 0,
150         FILE_FLAG_TRUEOFFS      = (1 << 0),     // the offset in packfile_t is the true contents offset
151         FILE_FLAG_DEFLATED      = (1 << 1)      // file compressed using the deflate algorithm
152 } file_flags_t;
153
154 typedef struct
155 {
156         char name [MAX_QPATH];
157         file_flags_t flags;
158         size_t offset;
159         size_t packsize;        // size in the package
160         size_t realsize;        // real file size (uncompressed)
161 } packfile_t;
162
163 typedef struct pack_s
164 {
165         char filename [MAX_OSPATH];
166         FILE *handle;
167         int numfiles;
168         packfile_t *files;
169         mempool_t *mempool;
170         struct pack_s *next;
171 } pack_t;
172
173
174 // Search paths for files (including packages)
175 typedef struct searchpath_s
176 {
177         // only one of filename / pack will be used
178         char filename[MAX_OSPATH];
179         pack_t *pack;
180         struct searchpath_s *next;
181 } searchpath_t;
182
183
184 /*
185 =============================================================================
186
187 VARIABLES
188
189 =============================================================================
190 */
191
192 mempool_t *fs_mempool;
193 mempool_t *pak_mempool;
194
195 int fs_filesize;
196
197 pack_t *packlist = NULL;
198
199 searchpath_t *fs_searchpaths;
200
201 // LordHavoc: was 2048, increased to 65536 and changed info[MAX_PACK_FILES] to a temporary alloc
202 #define MAX_FILES_IN_PACK       65536
203
204 char fs_gamedir[MAX_OSPATH];
205 char fs_basedir[MAX_OSPATH];
206
207 qboolean fs_modified;   // set true if using non-id files
208
209
210 /*
211 =============================================================================
212
213 PRIVATE FUNCTIONS - PK3 HANDLING
214
215 =============================================================================
216 */
217
218 /*
219 ====================
220 PK3_GetEndOfCentralDir
221
222 Extract the end of the central directory from a PK3 package
223 ====================
224 */
225 qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
226 {
227         long filesize, maxsize;
228         qbyte *buffer, *ptr;
229         int ind;
230
231         // Get the package size
232         fseek (packhandle, 0, SEEK_END);
233         filesize = ftell (packhandle);
234         if (filesize < ZIP_END_CDIR_SIZE)
235                 return false;
236
237         // Load the end of the file in memory
238         if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
239                 maxsize = filesize;
240         else
241                 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
242         buffer = Mem_Alloc (tempmempool, maxsize);
243         fseek (packhandle, filesize - maxsize, SEEK_SET);
244         if (fread (buffer, 1, maxsize, packhandle) != maxsize)
245         {
246                 Mem_Free (buffer);
247                 return false;
248         }
249
250         // Look for the end of central dir signature around the end of the file
251         maxsize -= ZIP_END_CDIR_SIZE;
252         ptr = &buffer[maxsize];
253         ind = 0;
254         while (BuffBigLong (ptr) != ZIP_END_HEADER)
255         {
256                 if (ind == maxsize)
257                 {
258                         Mem_Free (buffer);
259                         return false;
260                 }
261
262                 ind++;
263                 ptr--;
264         }
265
266         memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
267         eocd->signature = LittleLong (eocd->signature);
268         eocd->disknum = LittleShort (eocd->disknum);
269         eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
270         eocd->localentries = LittleShort (eocd->localentries);
271         eocd->nbentries = LittleShort (eocd->nbentries);
272         eocd->cdir_size = LittleLong (eocd->cdir_size);
273         eocd->cdir_offset = LittleLong (eocd->cdir_offset);
274         eocd->comment_size = LittleShort (eocd->comment_size);
275
276         Mem_Free (buffer);
277
278         return true;
279 }
280
281
282 /*
283 ====================
284 PK3_BuildFileList
285
286 Extract the file list from a PK3 file
287 ====================
288 */
289 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
290 {
291         qbyte *central_dir, *ptr;
292         unsigned int ind;
293         int remaining;
294
295         // Load the central directory in memory
296         central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
297         fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
298         fread (central_dir, 1, eocd->cdir_size, pack->handle);
299
300         // Extract the files properties
301         // The parsing is done "by hand" because some fields have variable sizes and
302         // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
303         remaining = eocd->cdir_size;
304         pack->numfiles = 0;
305         ptr = central_dir;
306         for (ind = 0; ind < eocd->nbentries; ind++)
307         {
308                 size_t namesize, count;
309                 packfile_t *file;
310
311                 // Checking the remaining size
312                 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
313                 {
314                         Mem_Free (central_dir);
315                         return -1;
316                 }
317                 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
318
319                 // Check header
320                 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
321                 {
322                         Mem_Free (central_dir);
323                         return -1;
324                 }
325
326                 namesize = BuffLittleShort (&ptr[28]);  // filename length
327
328                 // Check encryption, compression, and attributes
329                 // 1st uint8  : general purpose bit flag
330                 //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
331                 // 2nd uint8 : external file attributes
332                 //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
333                 if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
334                 {
335                         // Still enough bytes for the name?
336                         if (remaining < namesize || namesize >= sizeof (*pack->files))
337                         {
338                                 Mem_Free (central_dir);
339                                 return -1;
340                         }
341
342                         // WinZip doesn't use the "directory" attribute, so we need to check the name directly
343                         if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
344                         {
345                                 // Extract the name
346                                 file = &pack->files[pack->numfiles];
347                                 memcpy (file->name, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
348                                 file->name[namesize] = '\0';
349
350                                 // Compression, sizes and offset
351                                 if (BuffLittleShort (&ptr[10]))
352                                         file->flags = FILE_FLAG_DEFLATED;
353                                 file->packsize = BuffLittleLong (&ptr[20]);
354                                 file->realsize = BuffLittleLong (&ptr[24]);
355                                 file->offset = BuffLittleLong (&ptr[42]);
356
357                                 pack->numfiles++;
358                         }
359                 }
360
361                 // Skip the name, additionnal field, and comment
362                 // 1er uint16 : extra field length
363                 // 2eme uint16 : file comment length
364                 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
365                 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
366                 remaining -= count;
367         }
368
369         Mem_Free (central_dir);
370         return pack->numfiles;
371 }
372
373
374 /*
375 ====================
376 FS_LoadPackPK3
377
378 Create a package entry associated with a PK3 file
379 ====================
380 */
381 pack_t *FS_LoadPackPK3 (const char *packfile)
382 {
383         FILE *packhandle;
384         pk3_endOfCentralDir_t eocd;
385         pack_t *pack;
386         int real_nb_files;
387
388         packhandle = fopen (packfile, "rb");
389         if (!packhandle)
390                 return NULL;
391
392         if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
393                 Sys_Error ("%s is not a PK3 file", packfile);
394
395         // Multi-volume ZIP archives are NOT allowed
396         if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
397                 Sys_Error ("%s is a multi-volume ZIP archive", packfile);
398
399         if (eocd.nbentries > MAX_FILES_IN_PACK)
400                 Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
401
402         // Create a package structure in memory
403         pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
404         strcpy (pack->filename, packfile);
405         pack->handle = packhandle;
406         pack->numfiles = eocd.nbentries;
407         pack->mempool = Mem_AllocPool (packfile);
408         pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
409         pack->next = packlist;
410         packlist = pack;
411
412         real_nb_files = PK3_BuildFileList (pack, &eocd);
413         if (real_nb_files <= 0)
414                 Sys_Error ("%s is not a valid PK3 file", packfile);
415
416         Con_Printf ("Added packfile %s (%i files)\n", packfile, real_nb_files);
417         return pack;
418 }
419
420
421 /*
422 ====================
423 PK3_GetTrueFileOffset
424
425 Find where the true file data offset is
426 ====================
427 */
428 void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
429 {
430         qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
431         size_t count;
432
433         // Already found?
434         if (file->flags & FILE_FLAG_TRUEOFFS)
435                 return;
436
437         // Load the local file description
438         fseek (pack->handle, file->offset, SEEK_SET);
439         count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
440         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
441                 Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
442
443         // Skip name and extra field
444         file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
445
446         file->flags |= FILE_FLAG_TRUEOFFS;
447 }
448
449
450 /*
451 =============================================================================
452
453 OTHER PRIVATE FUNCTIONS
454
455 =============================================================================
456 */
457
458
459 /*
460 ============
461 FS_CreatePath
462
463 Only used for FS_WriteFile.
464 ============
465 */
466 void FS_CreatePath (char *path)
467 {
468         char *ofs, save;
469
470         for (ofs = path+1 ; *ofs ; ofs++)
471         {
472                 if (*ofs == '/' || *ofs == '\\')
473                 {
474                         // create the directory
475                         save = *ofs;
476                         *ofs = 0;
477                         FS_mkdir (path);
478                         *ofs = save;
479                 }
480         }
481 }
482
483
484 /*
485 ============
486 FS_Path_f
487
488 ============
489 */
490 void FS_Path_f (void)
491 {
492         searchpath_t *s;
493
494         Con_Printf ("Current search path:\n");
495         for (s=fs_searchpaths ; s ; s=s->next)
496         {
497                 if (s->pack)
498                 {
499                         Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
500                 }
501                 else
502                         Con_Printf ("%s\n", s->filename);
503         }
504 }
505
506
507 /*
508 =================
509 FS_LoadPackPAK
510
511 Takes an explicit (not game tree related) path to a pak file.
512
513 Loads the header and directory, adding the files at the beginning
514 of the list so they override previous pack files.
515 =================
516 */
517 pack_t *FS_LoadPackPAK (const char *packfile)
518 {
519         dpackheader_t header;
520         int i, numpackfiles;
521         FILE *packhandle;
522         pack_t *pack;
523         dpackfile_t *info;      // temporary alloc, allowing huge pack directories
524
525         packhandle = fopen (packfile, "rb");
526         if (!packhandle)
527                 return NULL;
528
529         fread ((void *)&header, 1, sizeof(header), packhandle);
530         if (memcmp(header.id, "PACK", 4))
531                 Sys_Error ("%s is not a packfile", packfile);
532         header.dirofs = LittleLong (header.dirofs);
533         header.dirlen = LittleLong (header.dirlen);
534
535         if (header.dirlen % sizeof(dpackfile_t))
536                 Sys_Error ("%s has an invalid directory size", packfile);
537
538         numpackfiles = header.dirlen / sizeof(dpackfile_t);
539
540         if (numpackfiles > MAX_FILES_IN_PACK)
541                 Sys_Error ("%s has %i files", packfile, numpackfiles);
542
543         pack = Mem_Alloc(pak_mempool, sizeof (pack_t));
544         strcpy (pack->filename, packfile);
545         pack->handle = packhandle;
546         pack->numfiles = numpackfiles;
547         pack->mempool = Mem_AllocPool(packfile);
548         pack->files = Mem_Alloc(pack->mempool, numpackfiles * sizeof(packfile_t));
549         pack->next = packlist;
550         packlist = pack;
551
552         info = Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
553         fseek (packhandle, header.dirofs, SEEK_SET);
554         fread ((void *)info, 1, header.dirlen, packhandle);
555
556         // parse the directory
557         for (i = 0;i < numpackfiles;i++)
558         {
559                 size_t size;
560                 packfile_t *file = &pack->files[i];
561
562                 strcpy (file->name, info[i].name);
563                 file->offset = LittleLong(info[i].filepos);
564                 size = LittleLong (info[i].filelen);
565                 file->packsize = size;
566                 file->realsize = size;
567                 file->flags = FILE_FLAG_TRUEOFFS;
568         }
569
570         Mem_Free(info);
571
572         Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
573         return pack;
574 }
575
576
577 /*
578 ================
579 FS_AddGameDirectory
580
581 Sets fs_gamedir, adds the directory to the head of the path,
582 then loads and adds pak1.pak pak2.pak ...
583 ================
584 */
585 void FS_AddGameDirectory (char *dir)
586 {
587         stringlist_t *list, *current;
588         searchpath_t *search;
589         pack_t *pak;
590         char pakfile[MAX_OSPATH];
591
592         strcpy (fs_gamedir, dir);
593
594         // add the directory to the search path
595         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
596         strcpy (search->filename, dir);
597         search->next = fs_searchpaths;
598         fs_searchpaths = search;
599
600         list = listdirectory(dir);
601
602         // add any PAK package in the directory
603         for (current = list;current;current = current->next)
604         {
605                 if (matchpattern(current->text, "*.pak", true))
606                 {
607                         sprintf (pakfile, "%s/%s", dir, current->text);
608                         pak = FS_LoadPackPAK (pakfile);
609                         if (pak)
610                         {
611                                 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
612                                 search->pack = pak;
613                                 search->next = fs_searchpaths;
614                                 fs_searchpaths = search;
615                         }
616                         else
617                                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
618                 }
619         }
620
621         // add any PK3 package in the director
622         for (current = list;current;current = current->next)
623         {
624                 if (matchpattern(current->text, "*.pk3", true))
625                 {
626                         sprintf (pakfile, "%s/%s", dir, current->text);
627                         pak = FS_LoadPackPK3 (pakfile);
628                         if (pak)
629                         {
630                                 search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
631                                 search->pack = pak;
632                                 search->next = fs_searchpaths;
633                                 fs_searchpaths = search;
634                         }
635                         else
636                                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
637                 }
638         }
639         freedirectory(list);
640 }
641
642
643 /*
644 ============
645 FS_FileExtension
646 ============
647 */
648 char *FS_FileExtension (const char *in)
649 {
650         static char exten[8];
651         int i;
652
653         while (*in && *in != '.')
654                 in++;
655         if (!*in)
656                 return "";
657         in++;
658         for (i=0 ; i<7 && *in ; i++,in++)
659                 exten[i] = *in;
660         exten[i] = 0;
661         return exten;
662 }
663
664
665 /*
666 ================
667 FS_Init
668 ================
669 */
670 void FS_Init (void)
671 {
672         int i;
673         searchpath_t *search;
674
675         fs_mempool = Mem_AllocPool("file management");
676         pak_mempool = Mem_AllocPool("paks");
677
678         Cmd_AddCommand ("path", FS_Path_f);
679
680         strcpy(fs_basedir, ".");
681
682         // -basedir <path>
683         // Overrides the system supplied base directory (under GAMENAME)
684         i = COM_CheckParm ("-basedir");
685         if (i && i < com_argc-1)
686                 strcpy (fs_basedir, com_argv[i+1]);
687
688         i = strlen (fs_basedir);
689         if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
690                 fs_basedir[i-1] = 0;
691
692         // start up with GAMENAME by default (id1)
693         strcpy(com_modname, GAMENAME);
694         FS_AddGameDirectory (va("%s/"GAMENAME, fs_basedir));
695         if (gamedirname[0])
696         {
697                 fs_modified = true;
698                 strcpy(com_modname, gamedirname);
699                 FS_AddGameDirectory (va("%s/%s", fs_basedir, gamedirname));
700         }
701
702         // -game <gamedir>
703         // Adds basedir/gamedir as an override game
704         i = COM_CheckParm ("-game");
705         if (i && i < com_argc-1)
706         {
707                 fs_modified = true;
708                 strcpy(com_modname, com_argv[i+1]);
709                 FS_AddGameDirectory (va("%s/%s", fs_basedir, com_argv[i+1]));
710         }
711
712         // -path <dir or packfile> [<dir or packfile>] ...
713         // Fully specifies the exact search path, overriding the generated one
714         i = COM_CheckParm ("-path");
715         if (i)
716         {
717                 fs_modified = true;
718                 fs_searchpaths = NULL;
719                 while (++i < com_argc)
720                 {
721                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
722                                 break;
723
724                         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
725                         if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
726                         {
727                                 search->pack = FS_LoadPackPAK (com_argv[i]);
728                                 if (!search->pack)
729                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
730                         }
731                         else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
732                         {
733                                 search->pack = FS_LoadPackPK3 (com_argv[i]);
734                                 if (!search->pack)
735                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
736                         }
737                         else
738                                 strcpy (search->filename, com_argv[i]);
739                         search->next = fs_searchpaths;
740                         fs_searchpaths = search;
741                 }
742         }
743 }
744
745
746 /*
747 =============================================================================
748
749 MAIN PUBLIC FUNCTIONS
750
751 =============================================================================
752 */
753
754 /*
755 ====================
756 FS_Open
757
758 Internal function used to create a qfile_t and open the relevant file on disk
759 ====================
760 */
761 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
762 {
763         qfile_t* file;
764
765         file = Mem_Alloc (fs_mempool, sizeof (*file));
766         memset (file, 0, sizeof (*file));
767
768         file->stream = fopen (filepath, mode);
769         if (!file->stream)
770         {
771                 Mem_Free (file);
772                 return NULL;
773         }
774
775         return file;
776 }
777
778
779 /*
780 ===========
781 FS_OpenRead
782 ===========
783 */
784 qfile_t *FS_OpenRead (const char *path, int offs, int len)
785 {
786         qfile_t* file;
787
788         file = FS_SysOpen (path, "rb");
789         if (!file)
790         {
791                 Sys_Error ("Couldn't open %s", path);
792                 return NULL;
793         }
794
795         // Normal file
796         if (offs < 0 || len < 0)
797         {
798                 FS_Seek (file, 0, SEEK_END);
799                 len = FS_Tell (file);
800                 FS_Seek (file, 0, SEEK_SET);
801         }
802         // Packed file
803         else
804         {
805                 FS_Seek (file, offs, SEEK_SET);
806
807                 file->flags |= FS_FLAG_PACKED;
808                 file->length = len;
809                 file->offset = offs;
810                 file->position = 0;
811         }
812
813         fs_filesize = len;
814
815         return file;
816 }
817
818 /*
819 ===========
820 FS_FOpenFile
821
822 If the requested file is inside a packfile, a new qfile_t* will be opened
823 into the file.
824
825 Sets fs_filesize
826 ===========
827 */
828 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
829 {
830         searchpath_t *search;
831         char netpath[MAX_OSPATH];
832         pack_t *pak;
833         int i, filenamelen;
834
835         filenamelen = strlen (filename);
836
837         // search through the path, one element at a time
838         search = fs_searchpaths;
839
840         for ( ; search ; search = search->next)
841         {
842                 // is the element a pak file?
843                 if (search->pack)
844                 {
845                         // look through all the pak file elements
846                         pak = search->pack;
847                         for (i=0 ; i<pak->numfiles ; i++)
848                                 if (!strcmp (pak->files[i].name, filename))  // found it?
849                                 {
850                                         // TODO: compressed files are NOT supported yet
851                                         if (pak->files[i].flags & FILE_FLAG_DEFLATED)
852                                         {
853                                                 Con_Printf ("WARNING: %s is a compressed file and so cannot be opened\n");
854                                                 fs_filesize = -1;
855                                                 return NULL;
856                                         }
857
858                                         if (!quiet)
859                                                 Sys_Printf ("PackFile: %s : %s\n",pak->filename, pak->files[i].name);
860
861                                         // If we don't have the true offset, get it now
862                                         if (! (pak->files[i].flags & FILE_FLAG_TRUEOFFS))
863                                                 PK3_GetTrueFileOffset (&pak->files[i], pak);
864
865                                         // open a new file in the pakfile
866                                         return FS_OpenRead (pak->filename, pak->files[i].offset, pak->files[i].packsize);
867                                 }
868                 }
869                 else
870                 {
871                         sprintf (netpath, "%s/%s",search->filename, filename);
872
873                         if (!FS_SysFileExists (netpath))
874                                 continue;
875
876                         if (!quiet)
877                                 Sys_Printf ("FindFile: %s\n",netpath);
878                         return FS_OpenRead (netpath, -1, -1);
879                 }
880         }
881
882         if (!quiet)
883                 Sys_Printf ("FindFile: can't find %s\n", filename);
884
885         fs_filesize = -1;
886         return NULL;
887 }
888
889
890 /*
891 ====================
892 FS_Open
893
894 Open a file. The syntax is the same as fopen
895 ====================
896 */
897 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
898 {
899         // If the file is opened in "write" or "append" mode
900         if (strchr (mode, 'w') || strchr (mode, 'a'))
901         {
902                 char real_path [MAX_OSPATH];
903
904                 // Open the file on disk directly
905                 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
906
907                 // Create directories up to the file
908                 FS_CreatePath (real_path);
909
910                 return FS_SysOpen (real_path, mode);
911         }
912
913         // Else, we look at the various search paths
914         return FS_FOpenFile (filepath, quiet);
915 }
916
917
918 /*
919 ====================
920 FS_Close
921
922 Close a file
923 ====================
924 */
925 int FS_Close (qfile_t* file)
926 {
927         if (fclose (file->stream))
928                 return EOF;
929
930         Mem_Free (file);
931         return 0;
932 }
933
934
935 /*
936 ====================
937 FS_Write
938
939 Write "datasize" bytes into a file
940 ====================
941 */
942 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
943 {
944         return fwrite (data, 1, datasize, file->stream);
945 }
946
947
948 /*
949 ====================
950 FS_Read
951
952 Read up to "buffersize" bytes from a file
953 ====================
954 */
955 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
956 {
957         size_t nb;
958
959         // If the file belongs to a package, we must take care
960         // to not read after the end of the file
961         if (file->flags & FS_FLAG_PACKED)
962         {
963                 size_t remain = file->length - file->position;
964                 if (buffersize > remain)
965                         buffersize = remain;
966         }
967
968         nb = fread (buffer, 1, buffersize, file->stream);
969
970         // Update the position index if the file is packed
971         if ((file->flags & FS_FLAG_PACKED) && nb > 0)
972                 file->position += nb;
973
974         return nb;
975 }
976
977
978 /*
979 ====================
980 FS_Flush
981
982 Flush the file output stream
983 ====================
984 */
985 int FS_Flush (qfile_t* file)
986 {
987         return fflush (file->stream);
988 }
989
990
991 /*
992 ====================
993 FS_Printf
994
995 Print a string into a file
996 ====================
997 */
998 int FS_Printf (qfile_t* file, const char* format, ...)
999 {
1000         int result;
1001         va_list args;
1002
1003         va_start (args, format);
1004         result = vfprintf (file->stream, format, args);
1005         va_end (args);
1006
1007         return result;
1008 }
1009
1010
1011 /*
1012 ====================
1013 FS_Getc
1014
1015 Get the next character of a file
1016 ====================
1017 */
1018 int FS_Getc (qfile_t* file)
1019 {
1020         int c;
1021
1022         // If the file belongs to a package, we must take care
1023         // to not read after the end of the file
1024         if (file->flags & FS_FLAG_PACKED)
1025         {
1026                 if (file->position >= file->length)
1027                         return EOF;
1028         }
1029
1030         c = fgetc (file->stream);
1031
1032         // Update the position index if the file is packed
1033         if ((file->flags & FS_FLAG_PACKED) && c != EOF)
1034                 file->position++;
1035
1036         return c;
1037 }
1038
1039
1040 /*
1041 ====================
1042 FS_Seek
1043
1044 Move the position index in a file
1045 ====================
1046 */
1047 int FS_Seek (qfile_t* file, long offset, int whence)
1048 {
1049         // Packed files receive a special treatment
1050         if (file->flags & FS_FLAG_PACKED)
1051         {
1052                 switch (whence)
1053                 {
1054                         case SEEK_CUR:
1055                                 offset += file->position;
1056                                 // It continues on the next case (no break)
1057
1058                         case SEEK_SET:
1059                                 if (offset < 0 || offset > file->length)
1060                                         return -1;
1061                                 if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1062                                         return -1;
1063                                 file->position = offset;
1064                                 return 0;
1065
1066                         case SEEK_END:
1067                                 if (offset > 0 || -offset > file->length)
1068                                         return -1;
1069                                 if (fseek (file->stream, file->offset + file->length + offset, SEEK_SET) == -1)
1070                                         return -1;
1071                                 file->position = file->length + offset;
1072                                 return 0;
1073
1074                         default:
1075                                 return -1;
1076                 }
1077         }
1078
1079         return fseek (file->stream, offset, whence);
1080 }
1081
1082
1083 /*
1084 ====================
1085 FS_Tell
1086
1087 Give the current position in a file
1088 ====================
1089 */
1090 long FS_Tell (qfile_t* file)
1091 {
1092         if (file->flags & FS_FLAG_PACKED)
1093                 return file->position;
1094
1095         return ftell (file->stream);
1096 }
1097
1098
1099 /*
1100 ====================
1101 FS_Gets
1102
1103 Extract a line from a file
1104 ====================
1105 */
1106 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1107 {
1108         if (!fgets (buffer, buffersize, file->stream))
1109                 return NULL;
1110
1111         // Check that we didn't read after the end of a packed file, and update the position
1112         if (file->flags & FS_FLAG_PACKED)
1113         {
1114                 size_t len = strlen (buffer);
1115                 size_t max = file->length - file->position;
1116
1117                 if (len > max)
1118                 {
1119                         buffer[max] = '\0';
1120                         file->position = file->length;
1121                 }
1122                 else
1123                         file->position += len;
1124         }
1125
1126         return buffer;
1127 }
1128
1129
1130 /*
1131 ==========
1132 FS_Getline
1133
1134 Dynamic length version of fgets. DO NOT free the buffer.
1135 ==========
1136 */
1137 char *FS_Getline (qfile_t *file)
1138 {
1139         static int  size = 256;
1140         static char *buf = 0;
1141         char        *t;
1142         int         len;
1143
1144         if (!buf)
1145                 buf = Mem_Alloc (fs_mempool, size);
1146
1147         if (!FS_Gets (file, buf, size))
1148                 return 0;
1149
1150         len = strlen (buf);
1151         while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1152         {
1153                 t = Mem_Alloc (fs_mempool, size + 256);
1154                 memcpy(t, buf, size);
1155                 Mem_Free(buf);
1156                 size += 256;
1157                 buf = t;
1158                 if (!FS_Gets (file, buf + len, size - len))
1159                         break;
1160                 len = strlen (buf);
1161         }
1162         while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1163                 buf[len - 1] = 0;
1164         return buf;
1165 }
1166
1167
1168 /*
1169 ====================
1170 FS_Eof
1171
1172 Extract a line from a file
1173 ====================
1174 */
1175 int FS_Eof (qfile_t* file)
1176 {
1177         if (file->flags & FS_FLAG_PACKED)
1178                 return (file->position == file->length);
1179         
1180         return feof (file->stream);
1181 }
1182
1183
1184 /*
1185 ============
1186 FS_LoadFile
1187
1188 Filename are relative to the quake directory.
1189 Always appends a 0 byte.
1190 ============
1191 */
1192 qbyte *FS_LoadFile (const char *path, qboolean quiet)
1193 {
1194         qfile_t *h;
1195         qbyte *buf;
1196
1197         // look for it in the filesystem or pack files
1198         h = FS_Open (path, "rb", quiet);
1199         if (!h)
1200                 return NULL;
1201
1202         buf = Mem_Alloc(tempmempool, fs_filesize+1);
1203         if (!buf)
1204                 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1205
1206         ((qbyte *)buf)[fs_filesize] = 0;
1207
1208         FS_Read (h, buf, fs_filesize);
1209         FS_Close (h);
1210
1211         return buf;
1212 }
1213
1214
1215 /*
1216 ============
1217 FS_WriteFile
1218
1219 The filename will be prefixed by the current game directory
1220 ============
1221 */
1222 qboolean FS_WriteFile (const char *filename, void *data, int len)
1223 {
1224         FILE *handle;
1225         char name[MAX_OSPATH];
1226
1227         sprintf (name, "%s/%s", fs_gamedir, filename);
1228
1229         // Create directories up to the file
1230         FS_CreatePath (name);
1231
1232         handle = fopen (name, "wb");
1233         if (!handle)
1234         {
1235                 Con_Printf ("FS_WriteFile: failed on %s\n", name);
1236                 return false;
1237         }
1238
1239         Con_DPrintf ("FS_WriteFile: %s\n", name);
1240         fwrite (data, 1, len, handle);
1241         fclose (handle);
1242         return true;
1243 }
1244
1245
1246 /*
1247 =============================================================================
1248
1249 OTHERS PUBLIC FUNCTIONS
1250
1251 =============================================================================
1252 */
1253
1254 /*
1255 ============
1256 FS_StripExtension
1257 ============
1258 */
1259 void FS_StripExtension (const char *in, char *out)
1260 {
1261         char *last = NULL;
1262         while (*in)
1263         {
1264                 if (*in == '.')
1265                         last = out;
1266                 else if (*in == '/' || *in == '\\' || *in == ':')
1267                         last = NULL;
1268                 *out++ = *in++;
1269         }
1270         if (last)
1271                 *last = 0;
1272         else
1273                 *out = 0;
1274 }
1275
1276
1277 /*
1278 ==================
1279 FS_DefaultExtension
1280 ==================
1281 */
1282 void FS_DefaultExtension (char *path, const char *extension)
1283 {
1284         const char *src;
1285
1286         // if path doesn't have a .EXT, append extension
1287         // (extension should include the .)
1288         src = path + strlen(path) - 1;
1289
1290         while (*src != '/' && src != path)
1291         {
1292                 if (*src == '.')
1293                         return;                 // it has an extension
1294                 src--;
1295         }
1296
1297         strcat (path, extension);
1298 }
1299
1300
1301 qboolean FS_FileExists (const char *filename)
1302 {
1303         searchpath_t *search;
1304         char netpath[MAX_OSPATH];
1305         pack_t *pak;
1306         int i;
1307
1308         for (search = fs_searchpaths;search;search = search->next)
1309         {
1310                 if (search->pack)
1311                 {
1312                         pak = search->pack;
1313                         for (i = 0;i < pak->numfiles;i++)
1314                                 if (!strcmp (pak->files[i].name, filename))
1315                                         return true;
1316                 }
1317                 else
1318                 {
1319                         sprintf (netpath, "%s/%s",search->filename, filename);
1320                         if (FS_SysFileExists (netpath))
1321                                 return true;
1322                 }
1323         }
1324
1325         return false;
1326 }
1327
1328
1329 qboolean FS_SysFileExists (const char *path)
1330 {
1331 #if WIN32
1332         FILE *f;
1333
1334         f = fopen (path, "rb");
1335         if (f)
1336         {
1337                 fclose (f);
1338                 return true;
1339         }
1340
1341         return false;
1342 #else
1343         struct stat buf;
1344
1345         if (stat (path,&buf) == -1)
1346                 return false;
1347
1348         return true;
1349 #endif
1350 }
1351
1352 void FS_mkdir (const char *path)
1353 {
1354 #if WIN32
1355         _mkdir (path);
1356 #else
1357         mkdir (path, 0777);
1358 #endif
1359 }