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