]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/vfspk3/vfs.cpp
fix unzip code
[xonotic/netradiant.git] / plugins / vfspk3 / 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 "vfspk3.h"
65 #include "vfs.h"
66 #include "unzip-vfspk3.h"
67
68 typedef struct
69 {
70         char*   name;
71         unz_s zipinfo;
72         unzFile zipfile;
73         guint32 size;
74 } VFS_PAKFILE;
75
76 // =============================================================================
77 // Global variables
78
79 static GSList* g_unzFiles;
80 static GSList* g_pakFiles;
81 static char g_strDirs[VFS_MAXDIRS][PATH_MAX];
82 static int g_numDirs;
83 static bool g_bUsePak = true;
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 static void vfsInitPakFile( const char *filename ){
112         unz_global_info gi;
113         unzFile uf;
114         guint32 i;
115         int err;
116
117         uf = unzOpen( filename );
118         if ( uf == NULL ) {
119                 g_FuncTable.m_pfnSysFPrintf( SYS_WRN, "  failed to init pak file %s\n", filename );
120                 return;
121         }
122         g_FuncTable.m_pfnSysPrintf( "  pak file: %s\n", filename );
123
124         g_unzFiles = g_slist_append( g_unzFiles, uf );
125
126         err = unzGetGlobalInfo( uf,&gi );
127         if ( err != UNZ_OK ) {
128                 return;
129         }
130         unzGoToFirstFile( uf );
131
132         for ( i = 0; i < gi.number_entry; i++ )
133         {
134                 char filename_inzip[NAME_MAX];
135                 unz_file_info file_info;
136                 VFS_PAKFILE* file;
137
138                 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
139                 if ( err != UNZ_OK ) {
140                         break;
141                 }
142
143                 file = (VFS_PAKFILE*)g_malloc( sizeof( VFS_PAKFILE ) );
144                 g_pakFiles = g_slist_append( g_pakFiles, file );
145
146                 vfsFixDOSName( filename_inzip );
147                 g_strdown( filename_inzip );
148
149                 file->name = g_strdup( filename_inzip );
150                 file->size = file_info.uncompressed_size;
151                 file->zipfile = uf;
152                 memcpy( &file->zipinfo, uf, sizeof( unz_s ) );
153
154                 if ( ( i + 1 ) < gi.number_entry ) {
155                         err = unzGoToNextFile( uf );
156                         if ( err != UNZ_OK ) {
157                                 break;
158                         }
159                 }
160         }
161 }
162
163 static GSList* vfsGetListInternal( const char *refdir, const char *ext, bool directories ){
164         GSList *lst, *lst_aux, *files = NULL;
165         char dirname[NAME_MAX], extension[NAME_MAX], filename[NAME_MAX];
166         char basedir[NAME_MAX];
167         int dirlen;
168         char *ptr;
169         char *dirlist;
170         struct stat st;
171         GDir *diskdir;
172         int i;
173
174         if ( refdir != NULL ) {
175                 strcpy( dirname, refdir );
176                 g_strdown( dirname );
177                 vfsFixDOSName( dirname );
178                 vfsAddSlash( dirname );
179         }
180         else{
181                 dirname[0] = '\0';
182         }
183         dirlen = strlen( dirname );
184
185         if ( ext != NULL ) {
186                 strcpy( extension, ext );
187         }
188         else{
189                 extension[0] = '\0';
190         }
191         g_strdown( extension );
192
193         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
194         {
195                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
196                 gboolean found = FALSE;
197                 ptr = file->name;
198
199                 // check that the file name begins with dirname
200                 for ( i = 0; ( *ptr && i < dirlen ); i++, ptr++ )
201                         if ( *ptr != dirname[i] ) {
202                                 break;
203                         }
204
205                 if ( i != dirlen ) {
206                         continue;
207                 }
208
209                 if ( directories ) {
210                         char *sep = strchr( ptr, '/' );
211                         if ( sep == NULL ) {
212                                 continue;
213                         }
214
215                         i = sep - ptr;
216
217                         // check for duplicates
218                         for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
219                                 if ( strncmp( (char*)lst_aux->data, ptr, i ) == 0 ) {
220                                         found = TRUE;
221                                         break;
222                                 }
223
224                         if ( !found ) {
225                                 char *name = g_strndup( ptr, i + 1 );
226                                 name[i] = '\0';
227                                 files = g_slist_append( files, name );
228                         }
229                 }
230                 else
231                 {
232                         // check extension
233                         char *ptr_ext = strrchr( ptr, '.' );
234                         if ( ( ext != NULL ) && ( ( ptr_ext == NULL ) || ( strcmp( ptr_ext + 1, extension ) != 0 ) ) ) {
235                                 continue;
236                         }
237
238                         // check for duplicates
239                         for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
240                                 if ( strcmp( (char*)lst_aux->data, ptr ) == 0 ) {
241                                         found = TRUE;
242                                         break;
243                                 }
244
245                         if ( !found ) {
246                                 files = g_slist_append( files, g_strdup( ptr ) );
247                         }
248                 }
249         }
250
251         for ( i = 0; i < g_numDirs; i++ )
252         {
253                 strcpy( basedir, g_strDirs[i] );
254                 strcat( basedir, dirname );
255
256                 diskdir = g_dir_open( basedir, 0, NULL );
257
258                 if ( diskdir != NULL ) {
259                         while ( 1 )
260                         {
261                                 const char* name = g_dir_read_name( diskdir );
262                                 if ( name == NULL ) {
263                                         break;
264                                 }
265
266                                 if ( directories && ( name[0] == '.' ) ) {
267                                         continue;
268                                 }
269
270                                 sprintf( filename, "%s%s", basedir, name );
271                                 stat( filename, &st );
272
273                                 if ( ( S_ISDIR( st.st_mode ) != 0 ) != directories ) {
274                                         continue;
275                                 }
276
277                                 gboolean found = FALSE;
278
279                                 dirlist = g_strdup( name );
280
281                                 g_strdown( dirlist );
282
283                                 char *ptr_ext = strrchr( dirlist, '.' );
284                                 if ( ext == NULL
285                                          || ( ext != NULL && ptr_ext != NULL && ptr_ext[0] != '\0' && strcmp( ptr_ext + 1, extension ) == 0 ) ) {
286
287                                         // check for duplicates
288                                         for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
289                                                 if ( strcmp( (char*)lst_aux->data, dirlist ) == 0 ) {
290                                                         found = TRUE;
291                                                         break;
292                                                 }
293
294                                         if ( !found ) {
295                                                 files = g_slist_append( files, g_strdup( dirlist ) );
296                                         }
297                                 }
298
299                                 g_free( dirlist );
300                         }
301                         g_dir_close( diskdir );
302                 }
303         }
304
305         return files;
306 }
307
308 /*!
309    This behaves identically to -stricmp(a,b), except that ASCII chars
310    [\]^`_ come AFTER alphabet chars instead of before. This is because
311    it effectively converts all alphabet chars to uppercase before comparison,
312    while stricmp converts them to lowercase.
313  */
314 //!\todo Analyse the code in rtcw/q3 to see how it behaves.
315 static int vfsPakSort( const void *a, const void *b ){
316         char    *s1, *s2;
317         int c1, c2;
318
319         s1 = (char*)a;
320         s2 = (char*)b;
321
322         do {
323                 c1 = *s1++;
324                 c2 = *s2++;
325
326                 if ( c1 >= 'a' && c1 <= 'z' ) {
327                         c1 -= ( 'a' - 'A' );
328                 }
329                 if ( c2 >= 'a' && c2 <= 'z' ) {
330                         c2 -= ( 'a' - 'A' );
331                 }
332
333                 if ( c1 == '\\' || c1 == ':' ) {
334                         c1 = '/';
335                 }
336                 if ( c2 == '\\' || c2 == ':' ) {
337                         c2 = '/';
338                 }
339
340                 // Arnout: note - sort pakfiles in reverse order. This ensures that
341                 // later pakfiles override earlier ones. This because the vfs module
342                 // returns a filehandle to the first file it can find (while it should
343                 // return the filehandle to the file in the most overriding pakfile, the
344                 // last one in the list that is).
345                 if ( c1 < c2 ) {
346                         //return -1;            // strings not equal
347                         return 1;       // strings not equal
348                 }
349                 if ( c1 > c2 ) {
350                         //return 1;
351                         return -1;
352                 }
353         } while ( c1 );
354
355         return 0;       // strings are equal
356 }
357
358 // =============================================================================
359 // Global functions
360
361 // reads all pak files from a dir
362 /*!
363    The gamemode hacks in here will do undefined things with files called zz_*.
364    This is simple to fix by cleaning up the hacks, but may be better left alone
365    if the engine code does the same thing.
366  */
367 void vfsInitDirectory( const char *path ){
368         char filename[PATH_MAX];
369         GDir *dir;
370         GSList *dirlist = NULL;
371         int iGameMode; // 0: no filtering 1: SP 2: MP
372
373         if ( g_numDirs == ( VFS_MAXDIRS - 1 ) ) {
374                 return;
375         }
376
377         // See if we are in "sp" or "mp" mapping mode
378         const char* gamemode = g_FuncTable.m_pfnReadProjectKey( "gamemode" );
379
380         if ( gamemode ) {
381                 if ( strcmp( gamemode, "sp" ) == 0 ) {
382                         iGameMode = 1;
383                 }
384                 else if ( strcmp( gamemode, "mp" ) == 0 ) {
385                         iGameMode = 2;
386                 }
387                 else{
388                         iGameMode = 0;
389                 }
390         }
391         else{
392                 iGameMode = 0;
393         }
394
395         strcpy( g_strDirs[g_numDirs], path );
396         vfsFixDOSName( g_strDirs[g_numDirs] );
397         vfsAddSlash( g_strDirs[g_numDirs] );
398         g_numDirs++;
399
400         if ( g_bUsePak ) {
401                 dir = g_dir_open( path, 0, NULL );
402
403                 if ( dir != NULL ) {
404                         g_FuncTable.m_pfnSysPrintf( "vfs directory: %s\n", path );
405
406                         for (;; )
407                         {
408                                 const char* name = g_dir_read_name( dir );
409                                 if ( name == NULL ) {
410                                         break;
411                                 }
412
413                                 char *ext = (char*)strrchr( name, '.' );
414                                 if ( ( ext == NULL ) || ( strcasecmp( ext, ".pk3" ) != 0 ) ) {
415                                         continue;
416                                 }
417
418                                 char* direntry = g_strdup( name );
419
420                                 // using the same kludge as in engine to ensure consistency
421                                 switch ( iGameMode )
422                                 {
423                                 case 1: // SP
424                                         if ( strncmp( direntry,"sp_",3 ) == 0 ) {
425                                                 memcpy( direntry,"zz",2 );
426                                         }
427                                         break;
428                                 case 2: // MP
429                                         if ( strncmp( direntry,"mp_",3 ) == 0 ) {
430                                                 memcpy( direntry,"zz",2 );
431                                         }
432                                         break;
433                                 }
434
435                                 dirlist = g_slist_append( dirlist, direntry );
436                         }
437
438                         g_dir_close( dir );
439
440                         // sort them
441                         dirlist = g_slist_sort( dirlist, vfsPakSort );
442
443                         // add the entries to the vfs and free the list
444                         while ( dirlist )
445                         {
446                                 GSList *cur = dirlist;
447                                 char* name = (char*)cur->data;
448
449                                 switch ( iGameMode )
450                                 {
451                                 case 1: // SP
452                                         if ( strncmp( name,"mp_",3 ) == 0 ) {
453                                                 g_free( name );
454                                                 dirlist = g_slist_remove( cur, name );
455                                                 continue;
456                                         }
457                                         else if ( strncmp( name,"zz_",3 ) == 0 ) {
458                                                 memcpy( name,"sp",2 );
459                                         }
460                                         break;
461                                 case 2: // MP
462                                         if ( strncmp( name,"sp_",3 ) == 0 ) {
463                                                 g_free( name );
464                                                 dirlist = g_slist_remove( cur, name );
465                                                 continue;
466                                         }
467                                         else if ( strncmp( name,"zz_",3 ) == 0 ) {
468                                                 memcpy( name,"mp",2 );
469                                         }
470                                         break;
471                                 }
472
473                                 sprintf( filename, "%s/%s", path, name );
474                                 vfsInitPakFile( filename );
475
476                                 g_free( name );
477                                 dirlist = g_slist_remove( cur, name );
478                         }
479                 }
480                 else{
481                         g_FuncTable.m_pfnSysFPrintf( SYS_WRN, "vfs directory not found: %s\n", path );
482                 }
483         }
484 }
485
486 // frees all memory that we allocated
487 // FIXME TTimo this should be improved so that we can shutdown and restart the VFS without exiting Radiant?
488 //   (for instance when modifying the project settings)
489 void vfsShutdown(){
490         while ( g_unzFiles )
491         {
492                 unzClose( (unzFile)g_unzFiles->data );
493                 g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
494         }
495
496         // avoid dangling pointer operation (makes BC hangry)
497         GSList *cur = g_pakFiles;
498         GSList *next = cur;
499         while ( next )
500         {
501                 cur = next;
502                 VFS_PAKFILE* file = (VFS_PAKFILE*)cur->data;
503                 g_free( file->name );
504                 g_free( file );
505                 next = g_slist_remove( cur, file );
506         }
507         g_pakFiles = NULL;
508 }
509
510 void vfsFreeFile( void *p ){
511         g_free( p );
512 }
513
514 GSList* vfsGetFileList( const char *dir, const char *ext ){
515         return vfsGetListInternal( dir, ext, false );
516 }
517
518 GSList* vfsGetDirList( const char *dir ){
519         return vfsGetListInternal( dir, NULL, true );
520 }
521
522 void vfsClearFileDirList( GSList **lst ){
523         while ( *lst )
524         {
525                 g_free( ( *lst )->data );
526                 *lst = g_slist_remove( *lst, ( *lst )->data );
527         }
528 }
529
530 int vfsGetFileCount( const char *filename, int flag ){
531         int i, count = 0;
532         char fixed[NAME_MAX], tmp[NAME_MAX];
533         GSList *lst;
534
535         strcpy( fixed, filename );
536         vfsFixDOSName( fixed );
537         g_strdown( fixed );
538
539         if ( !flag || ( flag & VFS_SEARCH_PAK ) ) {
540                 for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
541                 {
542                         VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
543
544                         if ( strcmp( file->name, fixed ) == 0 ) {
545                                 count++;
546                         }
547                 }
548         }
549
550         if ( !flag || ( flag & VFS_SEARCH_DIR ) ) {
551                 for ( i = 0; i < g_numDirs; i++ )
552                 {
553                         strcpy( tmp, g_strDirs[i] );
554                         strcat( tmp, fixed );
555                         if ( access( tmp, R_OK ) == 0 ) {
556                                 count++;
557                         }
558                 }
559         }
560
561         return count;
562 }
563
564 // open a full path file
565 int vfsLoadFullPathFile( const char *filename, void **bufferptr ){
566         FILE *f;
567         long len;
568
569         f = fopen( filename, "rb" );
570         if ( f == NULL ) {
571                 return -1;
572         }
573
574         fseek( f, 0, SEEK_END );
575         len = ftell( f );
576         rewind( f );
577
578         *bufferptr = g_malloc( len + 1 );
579         if ( *bufferptr == NULL ) {
580                 return -1;
581         }
582
583         fread( *bufferptr, 1, len, f );
584         fclose( f );
585
586         // we need to end the buffer with a 0
587         ( (char*) ( *bufferptr ) )[len] = 0;
588
589         return len;
590 }
591
592 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
593 int vfsLoadFile( const char *filename, void **bufferptr, int index ){
594         int i, count = 0;
595         char tmp[NAME_MAX], fixed[NAME_MAX];
596         GSList *lst;
597
598         *bufferptr = NULL;
599         strcpy( fixed, filename );
600         vfsFixDOSName( fixed );
601         g_strdown( fixed );
602
603         for ( i = 0; i < g_numDirs; i++ )
604         {
605                 strcpy( tmp, g_strDirs[i] );
606                 strcat( tmp, filename );
607                 if ( access( tmp, R_OK ) == 0 ) {
608                         if ( count == index ) {
609                                 return vfsLoadFullPathFile( tmp,bufferptr );
610                         }
611
612                         count++;
613                 }
614         }
615
616         for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
617         {
618                 VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
619
620                 if ( strcmp( file->name, fixed ) != 0 ) {
621                         continue;
622                 }
623
624                 if ( count == index ) {
625                         memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
626
627                         if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
628                                 return -1;
629                         }
630
631                         *bufferptr = g_malloc( file->size + 1 );
632                         // we need to end the buffer with a 0
633                         ( (char*) ( *bufferptr ) )[file->size] = 0;
634
635                         i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
636                         unzCloseCurrentFile( file->zipfile );
637                         if ( i > 0 ) {
638                                 return file->size;
639                         }
640                         else{
641                                 return -1;
642                         }
643                 }
644
645                 count++;
646         }
647
648         return -1;
649 }
650
651 //#ifdef _DEBUG
652 #if 1
653   #define DBG_RLTPATH
654 #endif
655
656 /*!
657    \param shorten will try to match against the short version
658    recent switch back to short path names in project settings has broken some stuff
659    with shorten == true, we will convert in to short version before looking for root
660    FIXME WAAA .. the stuff below is much more simple on linux .. add appropriate #ifdef
661  */
662 char* vfsExtractRelativePath_short( const char *in, bool shorten ){
663         int i;
664         char l_in[PATH_MAX];
665         char check[PATH_MAX];
666         static char out[PATH_MAX];
667         out[0] = 0;
668
669 #ifdef DBG_RLTPATH
670         Sys_Printf( "vfsExtractRelativePath: %s\n", in );
671 #endif
672
673 #ifdef _WIN32
674         if ( shorten ) {
675                 // make it short
676                 if ( GetShortPathName( in, l_in, PATH_MAX ) == 0 ) {
677 #ifdef DBG_RLTPATH
678                         Sys_Printf( "GetShortPathName failed\n" );
679 #endif
680                         return NULL;
681                 }
682         }
683         else
684         {
685                 strcpy( l_in,in );
686         }
687         vfsCleanFileName( l_in );
688 #else
689         strcpy( l_in, in );
690         vfsCleanFileName( l_in );
691 #endif // ifdef WIN32
692
693
694 #ifdef DBG_RLTPATH
695         Sys_Printf( "cleaned path: %s\n", l_in );
696 #endif
697
698         for ( i = 0; i < g_numDirs; i++ )
699         {
700                 strcpy( check,g_strDirs[i] );
701                 vfsCleanFileName( check );
702 #ifdef DBG_RLTPATH
703                 Sys_Printf( "Matching against %s\n", check );
704 #endif
705
706                 // try to find a match
707                 if ( strstr( l_in, check ) ) {
708                         strcpy( out,l_in + strlen( check ) + 1 );
709                         break;
710                 }
711
712         }
713         if ( out[0] != 0 ) {
714 #ifdef DBG_RLTPATH
715                 Sys_Printf( "vfsExtractRelativePath: success\n" );
716 #endif
717                 return out;
718         }
719 #ifdef DBG_RLTPATH
720         Sys_Printf( "vfsExtractRelativePath: failed\n" );
721 #endif
722         return NULL;
723 }
724
725
726 // FIXME TTimo: this and the above should be merged at some point
727 char* vfsExtractRelativePath( const char *in ){
728         static char out[PATH_MAX];
729         unsigned int i, count;
730         char *chunk, *backup = NULL; // those point to out stuff
731         char *ret = vfsExtractRelativePath_short( in, false );
732         if ( !ret ) {
733 #ifdef DBG_RLTPATH
734                 Sys_Printf( "trying with a short version\n" );
735 #endif
736                 ret = vfsExtractRelativePath_short( in, true );
737                 if ( ret ) {
738                         // ok, but we have a relative short version now
739                         // hack the long relative version out of here
740                         count = 0;
741                         for ( i = 0; i < strlen( ret ); i++ )
742                         {
743                                 if ( ret[i] == '/' ) {
744                                         count++;
745                                 }
746                         }
747                         // this is the clean, not short version
748                         strcpy( out, in );
749                         vfsCleanFileName( out );
750                         for ( i = 0; i <= count; i++ )
751                         {
752                                 chunk = strrchr( out, '/' );
753                                 if ( backup ) {
754                                         backup[0] = '/';
755                                 }
756                                 chunk[0] = '\0';
757                                 backup = chunk;
758                         }
759                         return chunk + 1;
760                 }
761         }
762         return ret;
763 }
764
765 void vfsCleanFileName( char *in ){
766         strlwr( in );
767         vfsFixDOSName( in );
768         int n = strlen( in );
769         if ( in[n - 1] == '/' ) {
770                 in[n - 1] = '\0';
771         }
772 }
773
774 // HYDRA: this now searches VFS/PAK files in addition to the filesystem
775 // if FLAG is unspecified then ONLY dirs are searched.
776 // PAK's are searched before DIRs to mimic engine behaviour
777 // index is ignored when searching PAK files.
778 // see ifilesystem.h
779 char* vfsGetFullPath( const char *in, int index, int flag ){
780         int count = 0;
781         static char out[PATH_MAX];
782         char tmp[NAME_MAX];
783         int i;
784
785         if ( flag & VFS_SEARCH_PAK ) {
786                 char fixed[NAME_MAX];
787                 GSList *lst;
788
789                 strcpy( fixed, in );
790                 vfsFixDOSName( fixed );
791                 g_strdown( fixed );
792
793                 for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
794                 {
795                         VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
796
797                         char *ptr,*lastptr;
798                         lastptr = file->name;
799
800                         while ( ( ptr = strchr( lastptr,'/' ) ) != NULL )
801                                 lastptr = ptr + 1;
802
803                         if ( strcmp( lastptr, fixed ) == 0 ) {
804                                 strncpy( out,file->name,PATH_MAX );
805                                 return out;
806                         }
807                 }
808
809         }
810
811         if ( !flag || ( flag & VFS_SEARCH_DIR ) ) {
812                 for ( i = 0; i < g_numDirs; i++ )
813                 {
814                         strcpy( tmp, g_strDirs[i] );
815                         strcat( tmp, in );
816                         if ( access( tmp, R_OK ) == 0 ) {
817                                 if ( count == index ) {
818                                         strcpy( out, tmp );
819                                         return out;
820                                 }
821                                 count++;
822                         }
823                 }
824         }
825         return NULL;
826 }
827
828
829 // TODO TTimo on linux the base prompt is ~/.q3a/<fs_game>
830 // given the file dialog, we could push the strFSBasePath and ~/.q3a into the directory shortcuts
831 // FIXME TTimo is this really a VFS functionality?
832 //   actually .. this should be the decision of the core isn't it?
833 //   or .. add an API so that the base prompt can be set during VFS init
834 const char* vfsBasePromptPath(){
835 #ifdef _WIN32
836         static const char* path = "C:";
837 #else
838         static const char* path = "/";
839 #endif
840         return path;
841 }