Fixing Linux SCons build.
[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         const 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 recent switch back to short path names in project settings has broken some stuff
632 with shorten == true, we will convert in to short version before looking for root
633 FIXME WAAA .. the stuff below is much more simple on linux .. add appropriate #ifdef
634 */
635 char* vfsExtractRelativePath_short(const char *in, bool shorten)
636 {
637   int i;
638   char l_in[PATH_MAX];
639   char check[PATH_MAX];
640   static char out[PATH_MAX];
641   out[0] = 0;
642
643 #ifdef DBG_RLTPATH
644   Sys_Printf("vfsExtractRelativePath: %s\n", in);
645 #endif
646
647 #ifdef _WIN32
648   if (shorten)
649   {
650     // make it short
651     if (GetShortPathName(in, l_in, PATH_MAX) == 0)
652     {
653 #ifdef DBG_RLTPATH
654       Sys_Printf("GetShortPathName failed\n");
655 #endif
656       return NULL;
657     }
658   }
659   else
660   {
661     strcpy(l_in,in);
662   }
663   vfsCleanFileName(l_in);
664 #else
665   strcpy(l_in, in);
666   vfsCleanFileName(l_in);
667 #endif // ifdef WIN32
668
669
670 #ifdef DBG_RLTPATH
671   Sys_Printf("cleaned path: %s\n", l_in);
672 #endif
673
674   for (i = 0; i < g_numDirs; i++)
675   {
676     strcpy(check,g_strDirs[i]);
677     vfsCleanFileName(check);
678 #ifdef DBG_RLTPATH
679     Sys_Printf("Matching against %s\n", check);
680 #endif
681
682     // try to find a match
683     if (strstr(l_in, check))
684     {
685       strcpy(out,l_in+strlen(check)+1);
686       break;
687     }
688
689   }
690   if (out[0]!=0)
691   {
692 #ifdef DBG_RLTPATH
693     Sys_Printf("vfsExtractRelativePath: success\n");
694 #endif
695     return out;
696   }
697 #ifdef DBG_RLTPATH
698   Sys_Printf("vfsExtractRelativePath: failed\n");
699 #endif
700   return NULL;
701 }
702
703 // HYDRA: this now searches VFS/PAK files in addition to the filesystem
704 // if FLAG is unspecified then ONLY dirs are searched.
705 // PAK's are searched before DIRs to mimic engine behaviour
706 // index is ignored when searching PAK files.
707 // see ifilesystem.h
708 char* vfsGetFullPath(const char *in, int index, int flag)
709 {
710   int count = 0;
711   static char out[PATH_MAX];
712   char tmp[NAME_MAX];
713   int i;
714
715   if (flag & VFS_SEARCH_PAK)
716   {
717     char fixed[NAME_MAX];
718     GSList *lst;
719
720     strcpy (fixed, in);
721     vfsFixDOSName (fixed);
722     g_strdown (fixed);
723
724     for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
725     {
726       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
727
728       char *ptr,*lastptr;
729       lastptr = file->name;
730
731       while ((ptr = strchr(lastptr,'/')) != NULL)
732         lastptr = ptr+1;
733
734       if (strcmp (lastptr, fixed) == 0)
735       {
736         strncpy(out,file->name,PATH_MAX);
737         return out;
738       }
739     }
740
741   }
742
743   if (!flag || (flag & VFS_SEARCH_DIR))
744   {
745   for (i = 0; i < g_numDirs; i++)
746   {
747     strcpy (tmp, g_strDirs[i]);
748     strcat (tmp, in);
749     if (access (tmp, R_OK) == 0)
750     {
751       if (count == index)
752       {
753         strcpy (out, tmp);
754         return out;
755       }
756       count++;
757     }
758   }
759   }
760   return NULL;
761 }
762
763 // FIXME TTimo: this and the above should be merged at some point
764 char* vfsExtractRelativePath(const char *in)
765 {
766   static char out[PATH_MAX];
767   unsigned int i, count;
768   char *chunk, *backup = NULL; // those point to out stuff
769   char *ret = vfsExtractRelativePath_short(in, false);
770   if (!ret)
771   {
772 #ifdef DBG_RLTPATH
773     Sys_Printf("trying with a short version\n");
774 #endif
775     ret = vfsExtractRelativePath_short(in, true);
776     if (ret)
777     {
778       // ok, but we have a relative short version now
779       // hack the long relative version out of here
780       count = 0;
781       for(i=0;i<strlen(ret);i++)
782       {
783         if (ret[i]=='/')
784           count++;
785       }
786       // this is the clean, not short version
787       strcpy(out, in);
788       vfsCleanFileName(out);
789       for(i=0;i<=count;i++)
790       {
791         chunk = strrchr(out, '/');
792         if (backup)
793           backup[0] = '/';
794         chunk[0] = '\0';
795         backup = chunk;
796       }
797       return chunk+1;
798     }
799   }
800   return ret;
801 }