Fixing Linux SCons build.
[xonotic/netradiant.git] / plugins / vfswad / 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 (/) (windows is backwards .. everyone knows that)
40 //
41 // Leonardo Zide (leo@lokigames.com)
42 //
43
44 #include <glib.h>
45 #include <stdio.h>
46
47 #if defined (__linux__) || defined (__APPLE__)
48   #include <dirent.h>
49   #include <unistd.h>
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 // TTimo: String functions
58 //   see http://www.qeradiant.com/faq/index.cgi?file=175
59 #include "str.h"
60
61 #include <stdlib.h>
62 #include <sys/stat.h>
63
64 #include "vfswad.h"
65 #include "vfs.h"
66 #include "unwad.h"
67
68 typedef struct
69 {
70   char*         name;
71   WAD3_LUMP     wadlump;
72   wadFile_t     *wadfile;
73   unsigned long filenumber;
74   unsigned long size;
75 } VFS_PAKFILE;
76
77 // =============================================================================
78 // Global variables
79
80 static GSList* g_wadFiles;
81 static GSList* g_pakFiles;
82 static char    g_strDirs[VFS_MAXDIRS][PATH_MAX];
83 static int     g_numDirs;
84
85 // =============================================================================
86 // Static functions
87
88 static void vfsAddSlash (char *str)
89 {
90   int n = strlen (str);
91   if (n > 0)
92   {
93     if (str[n-1] != '\\' && str[n-1] != '/')
94       strcat (str, "/");
95   }
96 }
97
98 static void vfsFixDOSName (char *src)
99 {
100   if (src == NULL)
101     return;
102
103   while (*src)
104   {
105     if (*src == '\\')
106       *src = '/';
107     src++;
108   }
109 }
110
111 //FIXME: STUPID short filenames.. get RID of it asap
112 // copied verbatim from qe3.cpp
113 int vfsBuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
114 {
115 #ifdef _WIN32
116   char *pFile = NULL;
117   int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile);
118   nResult = GetShortPathName(pPath, pBuffer, nBufferLen);
119   if (nResult == 0)
120     strcpy(pBuffer, pPath);                     // Use long filename
121   return nResult;
122 #endif
123
124 #if defined (__linux__) || defined (__APPLE__)
125
126   // remove /../ from directories
127   const char *scr = pPath; char *dst = pBuffer;
128   for (int i = 0; (i < nBufferLen) && (*scr != 0); i++)
129   {
130     if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.')
131     {
132       scr += 3;
133       while (dst != pBuffer && *(--dst) != '/')
134       {
135         i--;
136       }
137     }
138
139     *dst = *scr;
140
141     scr++; dst++;
142   }
143   *dst = 0;
144
145   return strlen (pBuffer);
146 #endif
147 }
148
149 static void vfsInitPakFile (const char *filename)
150 {
151   wadFile_t *wf;
152   unsigned int i;
153   int err;
154   char *wadnameptr;
155   char wadname[NAME_MAX];
156
157   wf = wadOpen (filename);
158   if (wf == NULL)
159   {
160     g_FuncTable.m_pfnSysFPrintf(SYS_WRN, "  failed to init wad file %s\n", filename);
161     return;
162   }
163   g_FuncTable.m_pfnSysPrintf("  wad file: %s\n", filename);
164
165   for (i = strlen(filename)-1 ; i >= 0 && filename[i] != '\\' && filename[i] != '/' ; i --)
166     wadnameptr = (char *)filename + i;
167
168   strcpy(wadname,wadnameptr);
169   wadname[strlen(wadname)-4] = 0; // ditch the .wad so everthing looks nice!
170
171   g_wadFiles = g_slist_append (g_wadFiles, wf); // store the wadfile handle
172
173   wadGoToFirstFile(wf);
174
175   for (i = 0; i < wf->lpHeader->numlumps; i++)
176   {
177     char filename_inwad[NAME_MAX];
178     char filename_inwadfixed[NAME_MAX];
179     unsigned long filesize;
180     VFS_PAKFILE* file;
181
182     err = wadGetCurrentFileInfo (wf, filename_inwad, sizeof(filename_inwad) - 5, &filesize); // -5 for extension + null terminator
183     if (err != 1)
184       break;
185
186     file = (VFS_PAKFILE*)g_malloc (sizeof (VFS_PAKFILE));
187     g_pakFiles = g_slist_append (g_pakFiles, file);
188
189     vfsFixDOSName (filename_inwad);
190     g_strdown (filename_inwad);
191
192     // texturenames in wad files don't have an extensions or paths, so we must add them!
193     if (wf->lpLump->type == WAD2_TYPE_MIP)
194     {
195       sprintf(filename_inwadfixed,"textures/%s/%s.mip",wadname,filename_inwad);
196     }else {
197       sprintf(filename_inwadfixed,"textures/%s/%s.hlw",wadname,filename_inwad);
198     }
199
200     //g_FuncTable.m_pfnSysFPrintf(SYS_WRN, "  scanned %s\\%s\n", filename,filename_inwad);
201
202     file->name = g_strdup (filename_inwadfixed);
203     file->size = filesize;
204     file->filenumber = wf->currentfile;
205     file->wadfile = wf;
206     memcpy(&file->wadlump, wf->lpLump, sizeof(WAD3_LUMP));
207
208     err = wadGoToNextFile(wf);
209     if (err != 1)
210       break;
211   }
212 }
213
214 static GSList* vfsGetListInternal (const char *refdir, const char *ext, bool directories)
215 {
216   GSList *lst, *lst_aux, *files = NULL;
217   char dirname[NAME_MAX], extension[NAME_MAX], filename[NAME_MAX];
218   char basedir[NAME_MAX];
219   int dirlen;
220   char *ptr;
221   struct stat st;
222   int i;
223
224   if (refdir != NULL)
225   {
226     strcpy (dirname, refdir);
227     g_strdown (dirname);
228     vfsFixDOSName (dirname);
229     vfsAddSlash (dirname);
230   } else
231     dirname[0] = '\0';
232   dirlen = strlen (dirname);
233
234   if (ext != NULL)
235     strcpy (extension, ext);
236   else
237     extension[0] = '\0';
238   g_strdown (extension);
239
240   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
241   {
242     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
243     gboolean found = FALSE;
244     ptr = file->name;
245
246     // check that the file name begins with dirname
247     for (i = 0; (*ptr && i < dirlen); i++, ptr++)
248       if (*ptr != dirname[i])
249         break;
250
251     if (i != dirlen)
252       continue;
253
254     if (directories)
255     {
256       char *sep = strchr (ptr, '/');
257       if (sep == NULL)
258         continue;
259
260       i = sep-ptr;
261
262       // check for duplicates
263       for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
264         if (strncmp ((char*)lst_aux->data, ptr, i) == 0)
265         {
266           found = TRUE;
267           break;
268         }
269
270       if (!found)
271       {
272         char *name = g_strndup (ptr, i+1);
273         name[i] = '\0';
274         files = g_slist_append (files, name);
275       }
276     } else
277     {
278       // check extension
279       char *ptr_ext = strrchr (ptr, '.');
280       if ((ext != NULL) && ((ptr_ext == NULL) || (strcmp (ptr_ext+1, extension) != 0)))
281         continue;
282
283       // check for duplicates
284       for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
285         if (strcmp ((char*)lst_aux->data, ptr) == 0)
286         {
287           found = TRUE;
288           break;
289         }
290
291       if (!found)
292         files = g_slist_append (files, g_strdup (ptr));
293     }
294   }
295
296   for (i = 0; i < g_numDirs; i++)
297   {
298     strcpy (basedir, g_strDirs[i]);
299     strcat (basedir, dirname);
300
301     GDir* dir = g_dir_open (basedir, 0, NULL);
302
303     if (dir != NULL)
304     {
305       for(;;)
306       {
307         const char* name = g_dir_read_name(dir);
308         if(name == NULL)
309           break;
310
311         if (directories && (name[0] == '.'))
312           continue;
313
314         sprintf (filename, "%s%s", basedir, name);
315         stat (filename, &st);
316
317         if ((S_ISDIR (st.st_mode) != 0) != directories)
318           continue;
319
320         gboolean found = FALSE;
321
322         char* direntry = g_strdup(name);
323
324         g_strdown (direntry);
325
326         char *ptr_ext = strrchr (direntry, '.');
327
328         if(ext == NULL
329           || (ext != NULL && ptr_ext != NULL && ptr_ext[0] != '\0' && strcmp (ptr_ext+1, extension) == 0))
330         {
331
332           // check for duplicates
333           for (lst_aux = files; lst_aux; lst_aux = g_slist_next (lst_aux))
334             if (strcmp ((char*)lst_aux->data, direntry) == 0)
335             {
336               found = TRUE;
337               break;
338             }
339
340           if (!found)
341             files = g_slist_append (files, g_strdup (direntry));
342         }
343
344         g_free(direntry);
345       }
346       g_dir_close(dir);
347     }
348   }
349
350   return files;
351 }
352
353 // =============================================================================
354 // Global functions
355
356 // reads all pak files from a dir
357 void vfsInitDirectory (const char *path)
358 {
359   char filename[PATH_MAX];
360
361   if (g_numDirs == (VFS_MAXDIRS-1))
362     return;
363
364   strcpy (g_strDirs[g_numDirs], path);
365   vfsFixDOSName (g_strDirs[g_numDirs]);
366   vfsAddSlash (g_strDirs[g_numDirs]);
367   g_numDirs++;
368
369 //  if (g_PrefsDlg.m_bPAK)
370   // TODO: can't read prefs from a module, bah..
371   if (1)
372   {
373     GDir* dir = g_dir_open (path, 0, NULL);
374     if (dir != NULL)
375     {
376       g_FuncTable.m_pfnSysPrintf("vfs directory: %s\n", path);
377       while (1)
378       {
379         const char* name = g_dir_read_name(dir);
380         if(name == NULL)
381           break;
382
383         const char *ext = strrchr (name, '.');
384         if ((ext == NULL) || (strcmp (ext, ".wad") != 0))
385           continue;
386
387         sprintf (filename, "%s/%s", path, name);
388         vfsInitPakFile (filename);
389       }
390       g_dir_close (dir);
391     } else
392       g_FuncTable.m_pfnSysFPrintf(SYS_WRN, "vfs directory not found: %s\n", path);
393   }
394 }
395
396 // frees all memory that we allocated
397 // FIXME TTimo this should be improved so that we can shutdown and restart the VFS without exiting Radiant?
398 //   (for instance when modifying the project settings)
399 void vfsShutdown ()
400 {
401   while (g_wadFiles)
402   {
403     wadCleanup((wadFile_t *)g_wadFiles->data);
404     g_wadFiles = g_slist_remove (g_wadFiles, g_wadFiles->data);
405   }
406
407   // avoid dangling pointer operation (makes BC hangry)
408   GSList *cur = g_pakFiles;
409   GSList *next = cur;
410   while (next)
411   {
412     cur = next;
413     VFS_PAKFILE* file = (VFS_PAKFILE*)cur->data;
414     g_free (file->name);
415     g_free (file);
416     next = g_slist_remove (cur, file);
417   }
418   g_pakFiles = NULL;
419 }
420
421 void vfsFreeFile (void *p)
422 {
423   g_free(p);
424 }
425
426 GSList* vfsGetFileList (const char *dir, const char *ext)
427 {
428   return vfsGetListInternal (dir, ext, false);
429 }
430
431 GSList* vfsGetDirList (const char *dir)
432 {
433   return vfsGetListInternal (dir, NULL, true);
434 }
435
436 void vfsClearFileDirList (GSList **lst)
437 {
438   while (*lst)
439   {
440     g_free ((*lst)->data);
441     *lst = g_slist_remove (*lst, (*lst)->data);
442   }
443 }
444
445 // return the number of files that match
446 int vfsGetFileCount (const char *filename, int flag)
447 {
448   int i, count = 0;
449   char fixed[NAME_MAX], tmp[NAME_MAX];
450   GSList *lst;
451
452   strcpy (fixed, filename);
453   vfsFixDOSName (fixed);
454   g_strdown (fixed);
455
456   if (!flag || (flag & VFS_SEARCH_PAK))
457   {
458     for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
459     {
460       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
461
462       if (strcmp (file->name, fixed) == 0)
463         count++;
464     }
465   }
466
467   if (!flag || (flag & VFS_SEARCH_DIR))
468   {
469     for (i = 0; i < g_numDirs; i++)
470     {
471       strcpy (tmp, g_strDirs[i]);
472       strcat (tmp, fixed);
473       if (access (tmp, R_OK) == 0)
474         count++;
475     }
476   }
477
478   return count;
479 }
480
481 // open a full path file
482 int vfsLoadFullPathFile (const char *filename, void **bufferptr)
483 {
484   FILE *f;
485   long len;
486
487   f = fopen (filename, "rb");
488   if (f == NULL)
489     return -1;
490
491   fseek (f, 0, SEEK_END);
492   len = ftell (f);
493   rewind (f);
494
495   *bufferptr = g_malloc (len+1);
496   if (*bufferptr == NULL)
497     return -1;
498
499   fread (*bufferptr, 1, len, f);
500   fclose (f);
501
502   // we need to end the buffer with a 0
503   ((char*) (*bufferptr))[len] = 0;
504
505   return len;
506 }
507
508 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
509 int vfsLoadFile (const char *filename, void **bufferptr, int index)
510 {
511   int i, count = 0;
512   char tmp[NAME_MAX], fixed[NAME_MAX];
513   GSList *lst;
514
515   *bufferptr = NULL;
516   strcpy (fixed, filename);
517   vfsFixDOSName (fixed);
518   g_strdown (fixed);
519
520   for (i = 0; i < g_numDirs; i++)
521   {
522     strcpy (tmp, g_strDirs[i]);
523     strcat (tmp, filename);
524     if (access (tmp, R_OK) == 0)
525     {
526       if (count == index)
527       {
528         return vfsLoadFullPathFile(tmp,bufferptr);
529         /*
530   long len;
531   FILE *f;
532
533   f = fopen (tmp, "rb");
534   if (f == NULL)
535     return -1;
536
537   fseek (f, 0, SEEK_END);
538   len = ftell (f);
539   rewind (f);
540
541   *bufferptr = g_malloc (len+1);
542   if (*bufferptr == NULL)
543     return -1;
544
545   fread (*bufferptr, 1, len, f);
546   fclose (f);
547
548         // we need to end the buffer with a 0
549         ((char*) (*bufferptr))[len] = 0;
550
551   return len;
552   */
553       }
554
555       count++;
556     }
557   }
558
559
560   // Textures in HalfLife wads don't have paths, but in the list of files
561   // we store the actual full paths of the files and what WAD they're in.
562   // so what we have to do is strip the paths and just compare filenames.
563
564   // Hydra: well, we did do this, but now we don't, as the map loader now
565   // fills in the correct paths for each texture.
566
567   /*
568   char *searchname;
569   char *fixedptr;
570
571   fixedptr = fixed;
572
573   for (i = strlen(fixed)-1 ; i >= 0 && fixed[i] != '\\' && fixed[i] != '/' ; i --)
574     fixedptr = (char *)fixed + i;
575   */
576   for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
577   {
578     VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
579
580
581     /*
582     searchname = file->name;
583     for (i = strlen(file->name)-1 ; i >= 0 && file->name[i] != '\\' && file->name[i] != '/' ; i --)
584       searchname = (char *)file->name + i;
585     if (strcmp (searchname, fixedptr) != 0)
586       continue;
587     */
588
589     if (strcmp (file->name, fixed) != 0)
590       continue;
591
592     if (count == index)
593     {
594       // Useful for debugging
595       //Sys_Printf("VFSWAD: reading from %s\n",file->wadfile->wadfilename);
596
597       if (wadOpenCurrentFileByNum (file->wadfile, file->filenumber) != 1)
598         return -1;
599
600       *bufferptr = g_malloc (file->size+1);
601       // we need to end the buffer with a 0
602       ((char*) (*bufferptr))[file->size] = 0;
603
604       i = wadReadCurrentFile (file->wadfile , (char *)*bufferptr, file->size);
605       wadCloseCurrentFile (file->wadfile);
606       if (i > 0)
607         return file->size;
608       else
609         return -1;
610     }
611
612     count++;
613   }
614
615   return -1;
616 }
617
618 //#ifdef _DEBUG
619 #if 0
620   #define DBG_RLTPATH
621 #endif
622
623 char* vfsExtractRelativePath(const char *in)
624 {
625   int i;
626   char l_in[PATH_MAX];
627   char check[PATH_MAX];
628   static char out[PATH_MAX];
629   out[0] = 0;
630
631 #ifdef DBG_RLTPATH
632   Sys_Printf("vfsExtractRelativePath: %s\n", in);
633 #endif
634
635   strcpy(l_in,in);
636   vfsCleanFileName(l_in);
637
638 #ifdef DBG_RLTPATH
639   Sys_Printf("cleaned path: %s\n", l_in);
640 #endif
641
642   for (i = 0; i < g_numDirs; i++)
643   {
644     strcpy(check,g_strDirs[i]);
645     vfsCleanFileName(check);
646 #ifdef DBG_RLTPATH
647     Sys_Printf("Matching against %s\n", check);
648 #endif
649
650     // try to find a match
651     if (strstr(l_in, check))
652     {
653       strcpy(out,l_in+strlen(check)+1);
654       break;
655     }
656   }
657   if (out[0]!=0)
658   {
659 #ifdef DBG_RLTPATH
660     Sys_Printf("vfsExtractRelativePath: success\n");
661 #endif
662     return out;
663   }
664 #ifdef DBG_RLTPATH
665   Sys_Printf("vfsExtractRelativePath: failed\n");
666 #endif
667   return NULL;
668 }
669
670 // removed CString usage
671 void vfsCleanFileName(char *in)
672 {
673   char str[PATH_MAX];
674   vfsBuildShortPathName (in, str, PATH_MAX);
675   strlwr(str);
676   vfsFixDOSName(str);
677   int n = strlen(str);
678   if (str[n-1] == '/')
679     str[n-1] = '\0';
680   strcpy (in, str);
681 }
682
683 // HYDRA: this now searches VFS/PAK files in addition to the filesystem
684 // if FLAG is unspecified then ONLY dirs are searched.
685 // PAK's are searched before DIRs to mimic engine behaviour
686 // index is ignored when searching PAK files.
687 // see ifilesystem.h
688 char* vfsGetFullPath(const char *in, int index, int flag)
689 {
690   int count = 0;
691   static char out[PATH_MAX];
692   char tmp[NAME_MAX];
693   int i;
694
695   if (flag & VFS_SEARCH_PAK)
696   {
697     char fixed[NAME_MAX];
698     GSList *lst;
699
700     strcpy (fixed, in);
701     vfsFixDOSName (fixed);
702     g_strdown (fixed);
703
704     for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst))
705     {
706       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
707
708       char *ptr,*lastptr;
709       lastptr = file->name;
710
711       while ((ptr = strchr(lastptr,'/')) != NULL)
712         lastptr = ptr+1;
713
714       if (strcmp (lastptr, fixed) == 0)
715       {
716         strncpy(out,file->name,PATH_MAX);
717         return out;
718       }
719     }
720
721   }
722
723   if (!flag || (flag & VFS_SEARCH_DIR))
724   {
725   for (i = 0; i < g_numDirs; i++)
726   {
727     strcpy (tmp, g_strDirs[i]);
728     strcat (tmp, in);
729     if (access (tmp, R_OK) == 0)
730     {
731       if (count == index)
732       {
733         strcpy (out, tmp);
734         return out;
735       }
736       count++;
737     }
738   }
739   }
740   return NULL;
741 }
742
743 // TODO TTimo on linux the base prompt is ~/.q3a/<fs_game>
744 // given the file dialog, we could push the strFSBasePath and ~/.q3a into the directory shortcuts
745 // FIXME TTimo is this really a VFS functionality?
746 //   actually .. this should be the decision of the core isn't it?
747 //   or .. add an API so that the base prompt can be set during VFS init
748 const char* vfsBasePromptPath()
749 {
750 #ifdef _WIN32
751   static const char* path = "C:";
752 #else
753   static const char* path = "/";
754 #endif
755   return path;
756 }
757