]> git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/vfspak/vfs.cpp
f2bb8ac2fb96b68cc15b02dabc7411eb43b30c3e
[xonotic/netradiant.git] / plugins / vfspak / vfs.cpp
1 /*
2 Copyright (c) 2001, Loki software, inc.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
10
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
14
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
17 written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 //
32 // Rules:
33 //
34 // - Directories should be searched in the following order: ~/.q3a/baseq3,
35 //   install dir (/usr/local/games/quake3/baseq3) and cd_path (/mnt/cdrom/baseq3).
36 //
37 // - Pak files are searched first inside the directories.
38 // - Case insensitive.
39 // - Unix-style slashes (/)
40 //
41 // Leonardo Zide (leo@lokigames.com)
42 //
43
44 #include <glib.h>
45 #include <stdio.h>
46 #if defined __linux__ || defined (__APPLE__)
47         #include <dirent.h>
48         #include <unistd.h>
49         #define WINAPI
50 #else
51         #include <wtypes.h>
52         #include <io.h>
53         #define R_OK 04
54         #define S_ISDIR(mode) (mode & _S_IFDIR)
55 #endif
56
57 #include "str.h"
58 #include <stdlib.h>
59 #include <sys/stat.h>
60 #include "vfs.h"
61 #include "vfspak.h"
62
63 typedef struct
64 {
65   char magic[4];         // Name of the new WAD format ("PACK")
66   gint32 diroffset;      // Position of WAD directory from start of file
67   gint32 dirsize;        // Number of entries * 0x40 (64 char)
68 } pakheader_t;
69
70 typedef struct
71 {
72   char filename[0x38];   // Name of the file, Unix style, with extension, 50 chars, padded with '\0'.
73   gint32 offset;         // Position of the entry in PACK file
74   gint32 size;           // Size of the entry in PACK file
75 } pakentry_t;
76
77 typedef struct
78 {
79   char*   name;
80   pakentry_t entry;
81   FILE *pak;
82 } VFS_PAKFILE;
83
84 // =============================================================================
85 // Global variables
86
87 static GSList* g_unzFiles;
88 static GSList* g_pakFiles;
89 static char    g_strDirs[VFS_MAXDIRS][PATH_MAX];
90 static int     g_numDirs;
91 static bool    g_bUsePak = true;
92
93 // =============================================================================
94 // Static functions
95
96 static void vfsAddSlash (char *str)
97 {
98   int n = strlen (str);
99   if (n > 0)
100   {
101     if (str[n-1] != '\\' && str[n-1] != '/')
102       strcat (str, "/");
103   }
104 }
105
106 static void vfsFixDOSName (char *src)
107 {
108   if (src == NULL)
109     return;
110
111   while (*src)
112   {
113     if (*src == '\\')
114       *src = '/';
115     src++;
116   }
117 }
118
119 static void vfsInitPakFile (const char *filename)
120 {
121   pakheader_t header;
122   FILE *f;
123   long i;
124
125   f = fopen (filename, "rb");
126   if (f == NULL)
127     return;
128
129   // read header
130   fread (header.magic, 1, 4, f);
131   fread (&header.diroffset, 1, 4, f);
132   fread (&header.dirsize, 1, 4, f);
133
134   // fix endianess
135   header.diroffset = GINT32_FROM_LE (header.diroffset);
136   header.dirsize = GINT32_FROM_LE (header.dirsize);
137
138   // check that the magic header
139   if (strncmp (header.magic, "PACK", 4))
140   {
141     fclose (f);
142     return;
143   }
144
145   g_FuncTable.m_pfnSysPrintf("  pak file: %s\n", filename);
146
147   g_unzFiles = g_slist_append (g_unzFiles, f);
148   fseek (f, header.diroffset, SEEK_SET);
149
150   for (i = 0; i < (long)(header.dirsize/sizeof (pakentry_t)); i++)
151   {
152     VFS_PAKFILE* file;
153
154     file = (VFS_PAKFILE*)g_malloc (sizeof (VFS_PAKFILE));
155     g_pakFiles = g_slist_append (g_pakFiles, file);
156
157     fread (file->entry.filename, 1, sizeof (file->entry.filename), f);
158     fread (&file->entry.offset, 1, sizeof (file->entry.offset), f);
159     fread (&file->entry.size, 1, sizeof (file->entry.size), f);
160     file->pak = f;
161
162     // fix endianess
163     file->entry.offset = GINT32_FROM_LE (file->entry.offset);
164     file->entry.size = GINT32_FROM_LE (file->entry.size);
165
166     // fix filename
167     vfsFixDOSName (file->entry.filename);
168     g_strdown (file->entry.filename);
169     //g_FuncTable.m_pfnSysPrintf("vfs file from pak: %s\n", file->entry.filename);
170   }
171 }
172
173 static GSList* vfsGetListInternal (const char *dir, const char *ext, bool directories)
174 {
175   GSList *lst, *lst_aux, *files = NULL;
176   char dirname[NAME_MAX], extension[NAME_MAX], filename[NAME_MAX];
177   int dirlen;
178   char *ptr;
179   //struct dirent *dirlist;
180   char *dirlist;
181   struct stat st;
182   GDir *diskdir;
183   int i;
184
185   dirname[0] = '\0';
186   if (dir != NULL)
187   {
188     strcat (dirname, dir);
189         g_strdown (dirname);
190         vfsFixDOSName (dirname);
191         vfsAddSlash (dirname);
192         Sys_Printf("vfs dirname_1: %s\n", dirname);
193   }
194   //else
195   //  dirname[0] = '\0';
196   dirlen = strlen (dirname);
197
198   if (ext != NULL)
199     strcpy (extension, ext);
200   else
201     extension[0] = '\0';
202   g_strdown (extension);
203
204   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
205   {
206     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
207     gboolean found = FALSE;
208     ptr = file->entry.filename;
209
210     // check that the file name begins with dirname
211     for (i = 0; (*ptr && i < dirlen); i++, ptr++)
212       if (*ptr != dirname[i])
213         break;
214
215     if (i != dirlen)
216       continue;
217
218     if (directories)
219     {
220       char *sep = strchr (ptr, '/');
221       if (sep == NULL)
222         continue;
223
224       i = sep-ptr;
225
226       // check for duplicates
227       for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
228         if (strncmp ((char*)lst_aux->data, ptr, i) == 0)
229         {
230           found = TRUE;
231           break;
232         }
233
234       if (!found)
235       {
236         char *name = g_strndup (ptr, i+1);
237         name[i] = '\0';
238         files = g_slist_append (files, name);
239       }
240     }
241     else
242     {
243       // check extension
244       if ((ext != NULL) && (strstr (ptr, extension) == NULL))
245         continue;
246
247       // check for duplicates
248       for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
249         if (strcmp ((char*)lst_aux->data, ptr) == 0)
250         {
251           found = TRUE;
252           break;
253         }
254
255       if (!found)
256         files = g_slist_append (files, g_strdup (ptr));
257     }
258   }
259
260   for (i = 0; i < g_numDirs; i++)
261   {
262     strcpy (dirname, g_strDirs[i]);
263     strcat (dirname, dir);
264     g_strdown (dirname);
265         vfsFixDOSName (dirname);
266         vfsAddSlash (dirname);
267
268     diskdir = g_dir_open (dirname, 0, NULL);
269
270         if (diskdir != NULL)
271     {
272       while (1)
273       {
274         const char* name = g_dir_read_name(diskdir);
275         if(name == NULL)
276           break;
277
278         if (directories && (name[0] == '.'))
279           continue;
280
281         sprintf (filename, "%s%s", dirname, name);
282         stat (filename, &st);
283                 Sys_Printf("vfs FileName: %s\n", filename);
284
285         if ((S_ISDIR (st.st_mode) != 0) != directories)
286           continue;
287
288         gboolean found = FALSE;
289
290         dirlist = g_strdup(name);
291
292         g_strdown (dirlist);
293
294         char *ptr_ext = strrchr (dirlist, '.');
295         if(ext == NULL
296           || (ext != NULL && ptr_ext != NULL && ptr_ext[0] != '\0' && strcmp (ptr_ext+1, extension) == 0))
297         {
298
299           // check for duplicates
300           for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
301             if (strcmp ((char*)lst_aux->data, dirlist) == 0)
302             {
303               found = TRUE;
304               break;
305             }
306
307           if (!found)
308             files = g_slist_append (files, g_strdup (dirlist));
309         }
310
311         g_free(dirlist);
312       }
313       g_dir_close (diskdir);
314     }
315   }
316
317   return files;
318 }
319
320 /*!
321 This behaves identically to -stricmp(a,b), except that ASCII chars
322 [\]^`_ come AFTER alphabet chars instead of before. This is because
323 it effectively converts all alphabet chars to uppercase before comparison,
324 while stricmp converts them to lowercase.
325 */
326 //!\todo Analyse the code in rtcw/q3 to see how it behaves.
327 static int vfsPakSort (const void *a, const void *b)
328 {
329         char    *s1, *s2;
330         int             c1, c2;
331
332         s1 = (char*)a;
333         s2 = (char*)b;
334
335         do {
336                 c1 = *s1++;
337                 c2 = *s2++;
338
339                 if (c1 >= 'a' && c1 <= 'z')
340                 {
341                         c1 -= ('a' - 'A');
342                 }
343                 if (c2 >= 'a' && c2 <= 'z')
344                 {
345                         c2 -= ('a' - 'A');
346                 }
347
348                 if ( c1 == '\\' || c1 == ':' )
349                 {
350                         c1 = '/';
351                 }
352                 if ( c2 == '\\' || c2 == ':' )
353                 {
354                         c2 = '/';
355                 }
356
357                 // Arnout: note - sort pakfiles in reverse order. This ensures that
358                 // later pakfiles override earlier ones. This because the vfs module
359                 // returns a filehandle to the first file it can find (while it should
360                 // return the filehandle to the file in the most overriding pakfile, the
361                 // last one in the list that is).
362                 if (c1 < c2)
363                 {
364                         //return -1;            // strings not equal
365                         return 1;               // strings not equal
366                 }
367                 if (c1 > c2)
368                 {
369                         //return 1;
370                         return -1;
371                 }
372         } while (c1);
373
374         return 0;               // strings are equal
375 }
376
377 // =============================================================================
378 // Global functions
379
380 void vfsInitDirectory (const char *path)
381 {
382   char filename[PATH_MAX];
383   //struct dirent *direntry;
384   GDir *dir;
385   GSList *dirlist = NULL;
386
387   if (g_numDirs == (VFS_MAXDIRS-1))
388     return;
389
390   strcpy (g_strDirs[g_numDirs], path);
391   vfsFixDOSName (g_strDirs[g_numDirs]);
392   vfsAddSlash (g_strDirs[g_numDirs]);
393   g_numDirs++;
394
395   if (g_bUsePak)
396   {
397     dir = g_dir_open (path, 0, NULL);
398     if (dir != NULL)
399     {
400       g_FuncTable.m_pfnSysPrintf("vfs directory: %s\n", path);
401
402           for(;;)
403       {
404         const char* name = g_dir_read_name(dir);
405         if(name == NULL)
406           break;
407
408         char *ext = strrchr (name, '.');
409         if ((ext == NULL) || (strcasecmp (ext, ".pak") != 0))
410           continue;
411
412         char* direntry = g_strdup(name);
413                 dirlist = g_slist_append (dirlist, direntry);
414       }
415
416       g_dir_close (dir);
417
418
419       // sort them
420       dirlist = g_slist_sort (dirlist, vfsPakSort);
421
422       // add the entries to the vfs and free the list
423       while (dirlist)
424       {
425         GSList *cur = dirlist;
426         char* name = (char*)cur->data;
427
428         sprintf (filename, "%s/%s", path, name);
429         vfsInitPakFile (filename);
430
431         g_free (name);
432         dirlist = g_slist_remove (cur, name);
433       }
434     } else
435           g_FuncTable.m_pfnSysFPrintf(SYS_WRN, "vfs directory not found: %s\n", path);
436
437   }
438 }
439
440
441 // frees all memory that we allocated
442 void vfsShutdown ()
443 {
444   while (g_unzFiles)
445   {
446     fclose ((FILE*)g_unzFiles->data);
447     g_unzFiles = g_slist_remove (g_unzFiles, g_unzFiles->data);
448   }
449
450   while (g_pakFiles)
451   {
452     g_free (g_pakFiles->data);
453     g_pakFiles = g_slist_remove (g_pakFiles, g_pakFiles->data);
454   }
455 }
456
457 GSList* vfsGetFileList (const char *dir, const char *ext)
458 {
459   return vfsGetListInternal (dir, ext, false);
460 }
461
462 GSList* vfsGetDirList (const char *dir)
463 {
464   return vfsGetListInternal (dir, NULL, true);
465 }
466
467 void vfsClearFileDirList (GSList **lst)
468 {
469   while (*lst)
470   {
471     g_free ((*lst)->data);
472     *lst = g_slist_remove (*lst, (*lst)->data);
473   }
474 }
475
476 // return the number of files that match
477 int vfsGetFileCount (const char *filename, int flag)
478 {
479   int i, count = 0;
480   char fixed[NAME_MAX], tmp[NAME_MAX];
481   GSList *lst;
482
483   strcpy (fixed, filename);
484   vfsFixDOSName (fixed);
485   g_strdown (fixed);
486
487   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
488   {
489     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
490
491     if (strcmp (file->entry.filename, fixed) == 0)
492       count++;
493   }
494
495   for (i = 0; i < g_numDirs; i++)
496   {
497     strcpy (tmp, g_strDirs[i]);
498     strcat (tmp, fixed);
499     if (access (tmp, R_OK) == 0)
500       count++;
501   }
502
503   return count;
504 }
505
506 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
507 int vfsLoadFile (const char *filename, void **bufferptr, int index)
508 {
509   int i, count = 0;
510   char tmp[NAME_MAX], fixed[NAME_MAX];
511   GSList *lst;
512
513   *bufferptr = NULL;
514   strcpy (fixed, filename);
515   vfsFixDOSName (fixed);
516   g_strdown (fixed);
517
518   for (i = 0; i < g_numDirs; i++)
519   {
520     strcpy (tmp, g_strDirs[i]);
521     strcat (tmp, filename);
522     if (access (tmp, R_OK) == 0)
523     {
524       if (count == index)
525       {
526         long len;
527         FILE *f;
528
529         f = fopen (tmp, "rb");
530         if (f == NULL)
531           return -1;
532
533         fseek (f, 0, SEEK_END);
534         len = ftell (f);
535         rewind (f);
536
537         *bufferptr = malloc (len+1);
538         if (*bufferptr == NULL)
539           return -1;
540
541         fread (*bufferptr, 1, len, f);
542         fclose (f);
543
544         // we need to end the buffer with a 0
545         ((char*) (*bufferptr))[len] = 0;
546
547         return len;
548       }
549
550       count++;
551     }
552   }
553
554   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
555   {
556     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
557
558     if (strcmp (file->entry.filename, fixed) != 0)
559       continue;
560
561     if (count == index)
562     {
563       fseek (file->pak, file->entry.offset, SEEK_SET);
564
565       *bufferptr = malloc (file->entry.size+1);
566       // we need to end the buffer with a 0
567       ((char*) (*bufferptr))[file->entry.size] = 0;
568
569       return fread (*bufferptr, 1, file->entry.size, file->pak);
570     }
571
572     count++;
573   }
574
575   return -1;
576 }
577
578 void vfsFreeFile (void *p)
579 {
580   g_free(p);
581 }
582
583 // open a full path file
584 int vfsLoadFullPathFile (const char *filename, void **bufferptr)
585 {
586   FILE *f;
587   long len;
588
589   f = fopen (filename, "rb");
590   if (f == NULL)
591     return -1;
592
593   fseek (f, 0, SEEK_END);
594   len = ftell (f);
595   rewind (f);
596
597   *bufferptr = g_malloc (len+1);
598   if (*bufferptr == NULL)
599     return -1;
600
601   fread (*bufferptr, 1, len, f);
602   fclose (f);
603
604   // we need to end the buffer with a 0
605   ((char*) (*bufferptr))[len] = 0;
606
607   return len;
608 }
609
610 void vfsCleanFileName(char *in)
611 {
612   strlwr(in);
613   vfsFixDOSName(in);
614   int n = strlen(in);
615   if (in[n-1] == '/')
616     in[n-1] = '\0';
617 }
618
619 const char* vfsBasePromptPath()
620 {
621 #ifdef _WIN32
622   static const char* path = "C:";
623 #else
624   static const char* path = "/";
625 #endif
626   return path;
627 }
628
629 /*!
630 \param shorten will try to match against the short version
631 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144
632 recent switch back to short path names in project settings has broken some stuff
633 with shorten == true, we will convert in to short version before looking for root
634 FIXME WAAA .. the stuff below is much more simple on linux .. add appropriate #ifdef
635 */
636 char* vfsExtractRelativePath_short(const char *in, bool shorten)
637 {
638   int i;
639   char l_in[PATH_MAX];
640   char check[PATH_MAX];
641   static char out[PATH_MAX];
642   out[0] = 0;
643
644 #ifdef DBG_RLTPATH
645   Sys_Printf("vfsExtractRelativePath: %s\n", in);
646 #endif
647
648 #ifdef _WIN32
649   if (shorten)
650   {
651     // make it short
652     if (GetShortPathName(in, l_in, PATH_MAX) == 0)
653     {
654 #ifdef DBG_RLTPATH
655       Sys_Printf("GetShortPathName failed\n");
656 #endif
657       return NULL;
658     }
659   }
660   else
661   {
662     strcpy(l_in,in);
663   }
664   vfsCleanFileName(l_in);
665 #else
666   strcpy(l_in, in);
667   vfsCleanFileName(l_in);
668 #endif // ifdef WIN32
669
670
671 #ifdef DBG_RLTPATH
672   Sys_Printf("cleaned path: %s\n", l_in);
673 #endif
674
675   for (i = 0; i < g_numDirs; i++)
676   {
677     strcpy(check,g_strDirs[i]);
678     vfsCleanFileName(check);
679 #ifdef DBG_RLTPATH
680     Sys_Printf("Matching against %s\n", check);
681 #endif
682
683     // try to find a match
684     if (strstr(l_in, check))
685     {
686       strcpy(out,l_in+strlen(check)+1);
687       break;
688     }
689
690   }
691   if (out[0]!=0)
692   {
693 #ifdef DBG_RLTPATH
694     Sys_Printf("vfsExtractRelativePath: success\n");
695 #endif
696     return out;
697   }
698 #ifdef DBG_RLTPATH
699   Sys_Printf("vfsExtractRelativePath: failed\n");
700 #endif
701   return NULL;
702 }
703
704 // HYDRA: this now searches VFS/PAK files in addition to the filesystem
705 // if FLAG is unspecified then ONLY dirs are searched.
706 // PAK's are searched before DIRs to mimic engine behaviour
707 // index is ignored when searching PAK files.
708 // see ifilesystem.h
709 char* vfsGetFullPath(const char *in, int index, int flag)
710 {
711   int count = 0;
712   static char out[PATH_MAX];
713   char tmp[NAME_MAX];
714   int i;
715
716   if (flag & VFS_SEARCH_PAK)
717   {
718     char fixed[NAME_MAX];
719     GSList *lst;
720
721     strcpy (fixed, in);
722     vfsFixDOSName (fixed);
723     g_strdown (fixed);
724
725     for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
726     {
727       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
728
729       char *ptr,*lastptr;
730       lastptr = file->name;
731
732       while ((ptr = strchr(lastptr,'/')) != NULL)
733         lastptr = ptr+1;
734
735       if (strcmp (lastptr, fixed) == 0)
736       {
737         strncpy(out,file->name,PATH_MAX);
738         return out;
739       }
740     }
741
742   }
743
744   if (!flag || (flag & VFS_SEARCH_DIR))
745   {
746   for (i = 0; i < g_numDirs; i++)
747   {
748     strcpy (tmp, g_strDirs[i]);
749     strcat (tmp, in);
750     if (access (tmp, R_OK) == 0)
751     {
752       if (count == index)
753       {
754         strcpy (out, tmp);
755         return out;
756       }
757       count++;
758     }
759   }
760   }
761   return NULL;
762 }
763
764 // FIXME TTimo: this and the above should be merged at some point
765 char* vfsExtractRelativePath(const char *in)
766 {
767   static char out[PATH_MAX];
768   unsigned int i, count;
769   char *chunk, *backup = NULL; // those point to out stuff
770   char *ret = vfsExtractRelativePath_short(in, false);
771   if (!ret)
772   {
773 #ifdef DBG_RLTPATH
774     Sys_Printf("trying with a short version\n");
775 #endif
776     ret = vfsExtractRelativePath_short(in, true);
777     if (ret)
778     {
779       // ok, but we have a relative short version now
780       // hack the long relative version out of here
781       count = 0;
782       for(i=0;i<strlen(ret);i++)
783       {
784         if (ret[i]=='/')
785           count++;
786       }
787       // this is the clean, not short version
788       strcpy(out, in);
789       vfsCleanFileName(out);
790       for(i=0;i<=count;i++)
791       {
792         chunk = strrchr(out, '/');
793         if (backup)
794           backup[0] = '/';
795         chunk[0] = '\0';
796         backup = chunk;
797       }
798       return chunk+1;
799     }
800   }
801   return ret;
802 }