]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - plugins/vfspk3/vfs.cpp
CMake: Make dll bundling optional
[xonotic/netradiant.git] / plugins / vfspk3 / vfs.cpp
index cf6e36db355111b77b76c255451a5e3fe4e8484e..bb3b5dbf4fc7c8cc237efdf54da6671272aa6872 100644 (file)
 // Leonardo Zide (leo@lokigames.com)
 //
 
-#include <glib.h>
+#include "vfs.h"
+
 #include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
 
-#if defined ( __linux__ ) || defined ( __APPLE__ )
-  #include <dirent.h>
-  #include <unistd.h>
-#else
-  #include <wtypes.h>
-  #include <io.h>
-  #define R_OK 04
-  #define S_ISDIR( mode ) ( mode & _S_IFDIR )
-#endif
+#include "qerplugin.h"
+#include "idatastream.h"
+#include "iarchive.h"
+ArchiveModules& FileSystemQ3API_getArchiveModules();
+#include "ifilesystem.h"
 
-// TTimo: String functions
-//   see http://www.qeradiant.com/faq/index.cgi?file=175
-#include "str.h"
+#include "generic/callback.h"
+#include "string/string.h"
+#include "stream/stringstream.h"
+#include "os/path.h"
+#include "moduleobservers.h"
+#include "filematch.h"
+#include "dpkdeps.h"
 
-#include <stdlib.h>
-#include <sys/stat.h>
 
-#include "vfspk3.h"
-#include "vfs.h"
-#include "unzip-vfspk3.h"
+#define VFS_MAXDIRS 64
+
+#if defined( WIN32 )
+#define PATH_MAX 260
+#endif
+
+#define gamemode_get GlobalRadiant().getGameMode
+
 
-typedef struct
-{
-       char*   name;
-       unz_s zipinfo;
-       unzFile zipfile;
-       guint32 size;
-} VFS_PAKFILE;
 
 // =============================================================================
 // Global variables
 
-static GSList* g_unzFiles;
-static GSList* g_pakFiles;
-static char g_strDirs[VFS_MAXDIRS][PATH_MAX];
+Archive* OpenArchive( const char* name );
+
+struct archive_entry_t
+{
+       CopiedString name;
+       Archive* archive;
+       bool is_pakfile;
+};
+
+#include <list>
+#include <map>
+
+typedef std::list<archive_entry_t> archives_t;
+
+static archives_t g_archives;
+static char g_strDirs[VFS_MAXDIRS][PATH_MAX + 1];
 static int g_numDirs;
+static char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
+static int g_numForbiddenDirs = 0;
 static bool g_bUsePak = true;
 
+ModuleObservers g_observers;
+
 // =============================================================================
 // Static functions
 
-static void vfsAddSlash( char *str ){
-       int n = strlen( str );
+static void AddSlash( char *str ){
+       std::size_t n = strlen( str );
        if ( n > 0 ) {
                if ( str[n - 1] != '\\' && str[n - 1] != '/' ) {
+                       globalErrorStream() << "WARNING: directory path does not end with separator: " << str << "\n";
                        strcat( str, "/" );
                }
        }
 }
 
-static void vfsFixDOSName( char *src ){
-       if ( src == NULL ) {
+static void FixDOSName( char *src ){
+       if ( src == 0 || strchr( src, '\\' ) == 0 ) {
                return;
        }
 
+       globalErrorStream() << "WARNING: invalid path separator '\\': " << src << "\n";
+
        while ( *src )
        {
                if ( *src == '\\' ) {
@@ -108,377 +127,504 @@ static void vfsFixDOSName( char *src ){
        }
 }
 
-static void vfsInitPakFile( const char *filename ){
-       unz_global_info gi;
-       unzFile uf;
-       guint32 i;
-       int err;
+const _QERArchiveTable* GetArchiveTable( ArchiveModules& archiveModules, const char* ext ){
+       StringOutputStream tmp( 16 );
+       tmp << LowerCase( ext );
+       return archiveModules.findModule( tmp.c_str() );
+}
 
-       uf = unzOpen( filename );
-       if ( uf == NULL ) {
-               g_FuncTable.m_pfnSysFPrintf( SYS_WRN, "  failed to init pak file %s\n", filename );
-               return;
-       }
-       g_FuncTable.m_pfnSysPrintf( "  pak file: %s\n", filename );
+static Archive* InitPakFile( ArchiveModules& archiveModules, const char *filename ){
+       const _QERArchiveTable* table = GetArchiveTable( archiveModules, path_get_extension( filename ) );
 
-       g_unzFiles = g_slist_append( g_unzFiles, uf );
+       if ( table != 0 ) {
+               archive_entry_t entry;
+               entry.name = filename;
 
-       err = unzGetGlobalInfo( uf,&gi );
-       if ( err != UNZ_OK ) {
-               return;
+               entry.archive = table->m_pfnOpenArchive( filename );
+               entry.is_pakfile = true;
+               g_archives.push_back( entry );
+               globalOutputStream() << "  pak file: " << filename << "\n";
+
+               return entry.archive;
        }
-       unzGoToFirstFile( uf );
 
-       for ( i = 0; i < gi.number_entry; i++ )
+       return 0;
+}
+
+inline void pathlist_prepend_unique( GSList*& pathlist, char* path ){
+       if ( g_slist_find_custom( pathlist, path, (GCompareFunc)path_compare ) == 0 ) {
+               pathlist = g_slist_prepend( pathlist, path );
+       }
+       else
        {
-               char filename_inzip[NAME_MAX];
-               unz_file_info file_info;
-               VFS_PAKFILE* file;
+               g_free( path );
+       }
+}
 
-               err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
-               if ( err != UNZ_OK ) {
-                       break;
+class DirectoryListVisitor : public Archive::Visitor
+{
+GSList*& m_matches;
+const char* m_directory;
+public:
+DirectoryListVisitor( GSList*& matches, const char* directory )
+       : m_matches( matches ), m_directory( directory )
+{}
+void visit( const char* name ){
+       const char* subname = path_make_relative( name, m_directory );
+       if ( subname != name ) {
+               if ( subname[0] == '/' ) {
+                       ++subname;
                }
+               char* dir = g_strdup( subname );
+               char* last_char = dir + strlen( dir );
+               if ( last_char != dir && *( --last_char ) == '/' ) {
+                       *last_char = '\0';
+               }
+               pathlist_prepend_unique( m_matches, dir );
+       }
+}
+};
 
-               file = (VFS_PAKFILE*)g_malloc( sizeof( VFS_PAKFILE ) );
-               g_pakFiles = g_slist_append( g_pakFiles, file );
-
-               vfsFixDOSName( filename_inzip );
-               g_strdown( filename_inzip );
-
-               file->name = g_strdup( filename_inzip );
-               file->size = file_info.uncompressed_size;
-               file->zipfile = uf;
-               memcpy( &file->zipinfo, uf, sizeof( unz_s ) );
-
-               if ( ( i + 1 ) < gi.number_entry ) {
-                       err = unzGoToNextFile( uf );
-                       if ( err != UNZ_OK ) {
-                               break;
-                       }
+class FileListVisitor : public Archive::Visitor
+{
+GSList*& m_matches;
+const char* m_directory;
+const char* m_extension;
+public:
+FileListVisitor( GSList*& matches, const char* directory, const char* extension )
+       : m_matches( matches ), m_directory( directory ), m_extension( extension )
+{}
+void visit( const char* name ){
+       const char* subname = path_make_relative( name, m_directory );
+       if ( subname != name ) {
+               if ( subname[0] == '/' ) {
+                       ++subname;
+               }
+               if ( m_extension[0] == '*' || extension_equal( path_get_extension( subname ), m_extension ) ) {
+                       pathlist_prepend_unique( m_matches, g_strdup( subname ) );
                }
        }
 }
+};
+
+static GSList* GetListInternal( const char *refdir, const char *ext, bool directories, std::size_t depth ){
+       GSList* files = 0;
 
-static GSList* vfsGetListInternal( const char *refdir, const char *ext, bool directories ){
-       GSList *lst, *lst_aux, *files = NULL;
-       char dirname[NAME_MAX], extension[NAME_MAX], filename[NAME_MAX];
-       char basedir[NAME_MAX];
-       int dirlen;
-       char *ptr;
-       char *dirlist;
-       struct stat st;
-       GDir *diskdir;
-       int i;
+       ASSERT_MESSAGE( refdir[strlen( refdir ) - 1] == '/', "search path does not end in '/'" );
 
-       if ( refdir != NULL ) {
-               strcpy( dirname, refdir );
-               g_strdown( dirname );
-               vfsFixDOSName( dirname );
-               vfsAddSlash( dirname );
+       if ( directories ) {
+               for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+               {
+                       DirectoryListVisitor visitor( files, refdir );
+                       ( *i ).archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eDirectories, depth ), refdir );
+               }
        }
-       else{
-               dirname[0] = '\0';
+       else
+       {
+               for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+               {
+                       FileListVisitor visitor( files, refdir, ext );
+                       ( *i ).archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, depth ), refdir );
+               }
        }
-       dirlen = strlen( dirname );
 
-       if ( ext != NULL ) {
-               strcpy( extension, ext );
-       }
-       else{
-               extension[0] = '\0';
+       files = g_slist_reverse( files );
+
+       return files;
+}
+
+inline int ascii_to_upper( int c ){
+       if ( c >= 'a' && c <= 'z' ) {
+               return c - ( 'a' - 'A' );
        }
-       g_strdown( extension );
+       return c;
+}
 
-       for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
+/*!
+   This behaves identically to stricmp(a,b), except that ASCII chars
+   [\]^`_ come AFTER alphabet chars instead of before. This is because
+   it converts all alphabet chars to uppercase before comparison,
+   while stricmp converts them to lowercase.
+ */
+static int string_compare_nocase_upper( const char* a, const char* b ){
+       for (;; )
        {
-               VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
-               gboolean found = FALSE;
-               ptr = file->name;
-
-               // check that the file name begins with dirname
-               for ( i = 0; ( *ptr && i < dirlen ); i++, ptr++ )
-                       if ( *ptr != dirname[i] ) {
-                               break;
-                       }
+               int c1 = ascii_to_upper( *a++ );
+               int c2 = ascii_to_upper( *b++ );
 
-               if ( i != dirlen ) {
-                       continue;
+               if ( c1 < c2 ) {
+                       return -1; // a < b
                }
+               if ( c1 > c2 ) {
+                       return 1; // a > b
+               }
+               if ( c1 == 0 ) {
+                       return 0; // a == b
+               }
+       }
+}
 
-               if ( directories ) {
-                       char *sep = strchr( ptr, '/' );
-                       if ( sep == NULL ) {
-                               continue;
-                       }
-
-                       i = sep - ptr;
+// Arnout: note - sort pakfiles in reverse order. This ensures that
+// later pakfiles override earlier ones. This because the vfs module
+// returns a filehandle to the first file it can find (while it should
+// return the filehandle to the file in the most overriding pakfile, the
+// last one in the list that is).
 
-                       // check for duplicates
-                       for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
-                               if ( strncmp( (char*)lst_aux->data, ptr, i ) == 0 ) {
-                                       found = TRUE;
-                                       break;
-                               }
+//!\todo Analyse the code in rtcw/q3 to see which order it sorts pak files.
+class PakLess
+{
+public:
+bool operator()( const CopiedString& self, const CopiedString& other ) const {
+       return string_compare_nocase_upper( self.c_str(), other.c_str() ) > 0;
+}
+};
 
-                       if ( !found ) {
-                               char *name = g_strndup( ptr, i + 1 );
-                               name[i] = '\0';
-                               files = g_slist_append( files, name );
-                       }
-               }
-               else
-               {
-                       // check extension
-                       char *ptr_ext = strrchr( ptr, '.' );
-                       if ( ( ext != NULL ) && ( ( ptr_ext == NULL ) || ( strcmp( ptr_ext + 1, extension ) != 0 ) ) ) {
-                               continue;
-                       }
+typedef std::set<CopiedString, PakLess> Archives;
 
-                       // check for duplicates
-                       for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
-                               if ( strcmp( (char*)lst_aux->data, ptr ) == 0 ) {
-                                       found = TRUE;
-                                       break;
-                               }
+Archive* AddPk3Dir( const char* fullpath ){
+       if ( g_numDirs == VFS_MAXDIRS ) return 0;
 
-                       if ( !found ) {
-                               files = g_slist_append( files, g_strdup( ptr ) );
-                       }
-               }
-       }
+       strncpy( g_strDirs[g_numDirs], fullpath, PATH_MAX );
+       g_strDirs[g_numDirs][PATH_MAX] = '\0';
+       g_numDirs++;
 
-       for ( i = 0; i < g_numDirs; i++ )
        {
-               strcpy( basedir, g_strDirs[i] );
-               strcat( basedir, dirname );
-
-               diskdir = g_dir_open( basedir, 0, NULL );
-
-               if ( diskdir != NULL ) {
-                       while ( 1 )
-                       {
-                               const char* name = g_dir_read_name( diskdir );
-                               if ( name == NULL ) {
-                                       break;
-                               }
+               archive_entry_t entry;
+               entry.name = fullpath;
+               entry.archive = OpenArchive( fullpath );
+               entry.is_pakfile = false;
+               g_archives.push_back( entry );
 
-                               if ( directories && ( name[0] == '.' ) ) {
-                                       continue;
-                               }
+               return entry.archive;
+       }
+}
 
-                               sprintf( filename, "%s%s", basedir, name );
-                               stat( filename, &st );
+// for Daemon DPK vfs
 
-                               if ( ( S_ISDIR( st.st_mode ) != 0 ) != directories ) {
-                                       continue;
-                               }
+Archive* AddDpkDir( const char* fullpath ){
+       return AddPk3Dir( fullpath );
+}
 
-                               gboolean found = FALSE;
+struct pakfile_path_t
+{
+       CopiedString fullpath;  // full pak dir or pk3dir name
+       bool is_pakfile;  // defines is it .pk3dir or .pk3 file
+};
 
-                               dirlist = g_strdup( name );
+typedef std::pair<CopiedString, pakfile_path_t> PakfilePathsKV;
+typedef std::map<CopiedString, pakfile_path_t> PakfilePaths;  // key must have no extension, only name
 
-                               g_strdown( dirlist );
+static PakfilePaths g_pakfile_paths;
 
-                               char *ptr_ext = strrchr( dirlist, '.' );
-                               if ( ext == NULL
-                                        || ( ext != NULL && ptr_ext != NULL && ptr_ext[0] != '\0' && strcmp( ptr_ext + 1, extension ) == 0 ) ) {
+void AddDpkPak( const char* name, const char* fullpath, bool is_pakfile ){
+       pakfile_path_t pakfile_path;
+       pakfile_path.fullpath = fullpath;
+       pakfile_path.is_pakfile = is_pakfile;
+       g_pakfile_paths.insert( PakfilePathsKV( name, pakfile_path ) );
+}
 
-                                       // check for duplicates
-                                       for ( lst_aux = files; lst_aux; lst_aux = g_slist_next( lst_aux ) )
-                                               if ( strcmp( (char*)lst_aux->data, dirlist ) == 0 ) {
-                                                       found = TRUE;
-                                                       break;
-                                               }
+// takes name without ext, returns without ext
+static const char* GetLatestDpkPakVersion( const char* name ){
+       const char* maxversion = 0;
+       const char* result = 0;
+       const char* pakname;
+       const char* pakversion;
+       int namelen = string_length( name );
 
-                                       if ( !found ) {
-                                               files = g_slist_append( files, g_strdup( dirlist ) );
-                                       }
-                               }
+       for ( PakfilePaths::iterator i = g_pakfile_paths.begin(); i != g_pakfile_paths.end(); ++i )
+       {
+               pakname = i->first.c_str();
+               if ( strncmp( pakname, name, namelen ) != 0 || pakname[namelen] != '_' ) continue;
+               pakversion = pakname + (namelen + 1);
+               if ( maxversion == 0 || DpkPakVersionCmp( pakversion, maxversion ) > 0 ){
+                       maxversion = pakversion;
+                       result = pakname;
+               }
+       }
+       return result;
+}
 
-                               g_free( dirlist );
-                       }
-                       g_dir_close( diskdir );
+// release string after using
+static char* GetCurrentMapDpkPakName(){
+       char* mapdir;
+       char* mapname;
+       int mapnamelen;
+       char* result = 0;
+
+       mapname = string_clone( GlobalRadiant().getMapName() );
+       mapnamelen = string_length( mapname );
+
+       mapdir = strrchr( mapname, '/' );
+       if ( mapdir ) {
+               mapdir -= 12;
+               if ( strncmp( mapdir, ".dpkdir/maps/", 13 ) == 0 ) {
+                       *mapdir = '\0';
+                       mapdir = strrchr( mapname, '/' );
+                       if ( mapdir ) mapdir++;
+                       else mapdir = mapname;
+                       result = string_clone( mapdir );
                }
        }
 
-       return files;
+       string_release( mapname, mapnamelen );
+       return result;
+
 }
 
-/*!
-   This behaves identically to -stricmp(a,b), except that ASCII chars
-   [\]^`_ come AFTER alphabet chars instead of before. This is because
-   it effectively converts all alphabet chars to uppercase before comparison,
-   while stricmp converts them to lowercase.
- */
-//!\todo Analyse the code in rtcw/q3 to see how it behaves.
-static int vfsPakSort( const void *a, const void *b ){
-       char    *s1, *s2;
-       int c1, c2;
+// prevent loading duplicates or circular references
+static Archives g_loaded_dpk_paks;
 
-       s1 = (char*)a;
-       s2 = (char*)b;
+// actual pak adding on initialise, deferred from InitDirectory
+// Daemon DPK filesystem doesn't need load all paks it finds
+static void LoadDpkPakWithDeps( const char* pakname ){
+       const char* und = strrchr( pakname, '_' );
+       if ( !und ) pakname = GetLatestDpkPakVersion( pakname );
+       if ( !pakname || g_loaded_dpk_paks.find( pakname ) != g_loaded_dpk_paks.end() ) return;
 
-       do {
-               c1 = *s1++;
-               c2 = *s2++;
+       PakfilePaths::iterator i = g_pakfile_paths.find( pakname );
+       if ( i == g_pakfile_paths.end() ) return;
 
-               if ( c1 >= 'a' && c1 <= 'z' ) {
-                       c1 -= ( 'a' - 'A' );
-               }
-               if ( c2 >= 'a' && c2 <= 'z' ) {
-                       c2 -= ( 'a' - 'A' );
-               }
+       Archive* arc;
+       if ( i->second.is_pakfile ){
+               arc = InitPakFile( FileSystemQ3API_getArchiveModules(), i->second.fullpath.c_str() );
+       } else {
+               arc = AddDpkDir( i->second.fullpath.c_str() );
+       }
+       g_loaded_dpk_paks.insert( pakname );
 
-               if ( c1 == '\\' || c1 == ':' ) {
-                       c1 = '/';
-               }
-               if ( c2 == '\\' || c2 == ':' ) {
-                       c2 = '/';
-               }
+       ArchiveTextFile* depsFile = arc->openTextFile( "DEPS" );
+       if ( !depsFile ) return;
 
-               // Arnout: note - sort pakfiles in reverse order. This ensures that
-               // later pakfiles override earlier ones. This because the vfs module
-               // returns a filehandle to the first file it can find (while it should
-               // return the filehandle to the file in the most overriding pakfile, the
-               // last one in the list that is).
-               if ( c1 < c2 ) {
-                       //return -1;            // strings not equal
-                       return 1;       // strings not equal
-               }
-               if ( c1 > c2 ) {
-                       //return 1;
-                       return -1;
+       {
+               TextLinesInputStream<TextInputStream> istream = depsFile->getInputStream();
+
+               CopiedString line;
+               char *p_name;
+               char *p_version;
+               while ( line = istream.readLine(), string_length( line.c_str() ) ) {
+                       if ( !DpkReadDepsLine( line.c_str(), &p_name, &p_version ) ) continue;
+                       if ( !p_version ) {
+                               const char* p_latest = GetLatestDpkPakVersion( p_name );
+                               if ( p_latest ) LoadDpkPakWithDeps( p_latest );
+                       } else {
+                               int len = string_length( p_name ) + string_length( p_version ) + 1;
+                               char* p_pakname = string_new( len );
+                               sprintf( p_pakname, "%s_%s", p_name, p_version );
+                               LoadDpkPakWithDeps( p_pakname );
+                               string_release( p_pakname, len );
+                       }
+                       string_release( p_name, string_length( p_name ) );
+                       if ( p_version ) string_release( p_version, string_length( p_version ) );
                }
-       } while ( c1 );
+       }
 
-       return 0;       // strings are equal
+       depsFile->release();
 }
 
+// end for Daemon DPK vfs
+
 // =============================================================================
 // Global functions
 
 // reads all pak files from a dir
-/*!
-   The gamemode hacks in here will do undefined things with files called zz_*.
-   This is simple to fix by cleaning up the hacks, but may be better left alone
-   if the engine code does the same thing.
- */
-void vfsInitDirectory( const char *path ){
-       char filename[PATH_MAX];
-       GDir *dir;
-       GSList *dirlist = NULL;
-       int iGameMode; // 0: no filtering 1: SP 2: MP
+void InitDirectory( const char* directory, ArchiveModules& archiveModules ){
+       int j;
 
-       if ( g_numDirs == ( VFS_MAXDIRS - 1 ) ) {
-               return;
+       g_numForbiddenDirs = 0;
+       StringTokeniser st( GlobalRadiant().getGameDescriptionKeyValue( "forbidden_paths" ), " " );
+       for ( j = 0; j < VFS_MAXDIRS; ++j )
+       {
+               const char *t = st.getToken();
+               if ( string_empty( t ) ) {
+                       break;
+               }
+               strncpy( g_strForbiddenDirs[g_numForbiddenDirs], t, PATH_MAX );
+               g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = '\0';
+               ++g_numForbiddenDirs;
        }
 
-       // See if we are in "sp" or "mp" mapping mode
-       const char* gamemode = g_FuncTable.m_pfnReadProjectKey( "gamemode" );
-
-       if ( gamemode ) {
-               if ( strcmp( gamemode, "sp" ) == 0 ) {
-                       iGameMode = 1;
-               }
-               else if ( strcmp( gamemode, "mp" ) == 0 ) {
-                       iGameMode = 2;
+       for ( j = 0; j < g_numForbiddenDirs; ++j )
+       {
+               char* dbuf = g_strdup( directory );
+               if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) {
+                       dbuf[strlen( dbuf ) - 1] = 0;
                }
-               else{
-                       iGameMode = 0;
+               const char *p = strrchr( dbuf, '/' );
+               p = ( p ? ( p + 1 ) : dbuf );
+               if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
+                       g_free( dbuf );
+                       break;
                }
+               g_free( dbuf );
        }
-       else{
-               iGameMode = 0;
+       if ( j < g_numForbiddenDirs ) {
+               printf( "Directory %s matched by forbidden dirs, removed\n", directory );
+               return;
        }
 
-       strcpy( g_strDirs[g_numDirs], path );
-       vfsFixDOSName( g_strDirs[g_numDirs] );
-       vfsAddSlash( g_strDirs[g_numDirs] );
+       if ( g_numDirs == VFS_MAXDIRS ) {
+               return;
+       }
+
+       strncpy( g_strDirs[g_numDirs], directory, PATH_MAX );
+       g_strDirs[g_numDirs][PATH_MAX] = '\0';
+       FixDOSName( g_strDirs[g_numDirs] );
+       AddSlash( g_strDirs[g_numDirs] );
+
+       const char* path = g_strDirs[g_numDirs];
+
        g_numDirs++;
 
+       {
+               archive_entry_t entry;
+               entry.name = path;
+               entry.archive = OpenArchive( path );
+               entry.is_pakfile = false;
+               g_archives.push_back( entry );
+       }
+
        if ( g_bUsePak ) {
-               dir = g_dir_open( path, 0, NULL );
 
-               if ( dir != NULL ) {
-                       g_FuncTable.m_pfnSysPrintf( "vfs directory: %s\n", path );
+               GDir* dir = g_dir_open( path, 0, 0 );
+
+               if ( dir != 0 ) {
+                       globalOutputStream() << "vfs directory: " << path << "\n";
+
+                       Archives archives;
+                       Archives archivesOverride;
+                       const char* ignore_prefix = "";
+                       const char* override_prefix = "";
+                       bool is_pk3_vfs, is_pk4_vfs, is_dpk_vfs;
+
+                       is_pk3_vfs = GetArchiveTable( archiveModules, "pk3" );
+                       is_pk4_vfs = GetArchiveTable( archiveModules, "pk4" );
+                       is_dpk_vfs = GetArchiveTable( archiveModules, "dpk" );
+
+                       if ( !is_dpk_vfs ) {
+                               // See if we are in "sp" or "mp" mapping mode
+                               const char* gamemode = gamemode_get();
+
+                               if ( strcmp( gamemode, "sp" ) == 0 ) {
+                                       ignore_prefix = "mp_";
+                                       override_prefix = "sp_";
+                               }
+                               else if ( strcmp( gamemode, "mp" ) == 0 ) {
+                                       ignore_prefix = "sp_";
+                                       override_prefix = "mp_";
+                               }
+                       }
 
                        for (;; )
                        {
                                const char* name = g_dir_read_name( dir );
-                               if ( name == NULL ) {
+                               if ( name == 0 ) {
                                        break;
                                }
 
-                               char *ext = (char*)strrchr( name, '.' );
-                               if ( ( ext == NULL ) || ( strcasecmp( ext, ".pk3" ) != 0 ) ) {
+                               for ( j = 0; j < g_numForbiddenDirs; ++j )
+                               {
+                                       const char *p = strrchr( name, '/' );
+                                       p = ( p ? ( p + 1 ) : name );
+                                       if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
+                                               break;
+                                       }
+                               }
+                               if ( j < g_numForbiddenDirs ) {
                                        continue;
                                }
 
-                               char* direntry = g_strdup( name );
+                               const char *ext = strrchr( name, '.' );
+                               char tmppath[PATH_MAX];
+
+                               if ( is_dpk_vfs ) {
+                                       if ( !!ext && !string_compare_nocase_upper( ext, ".dpkdir" ) ) {
+                                               snprintf( tmppath, PATH_MAX, "%s%s/", path, name );
+                                               tmppath[PATH_MAX] = '\0';
+                                               FixDOSName( tmppath );
+                                               AddSlash( tmppath );
+                                               AddDpkPak( CopiedString( StringRange( name, ext ) ).c_str(), tmppath, false );
+                                       }
+                               }
+
+                               if ( is_pk3_vfs || is_pk4_vfs ) {
+                                       if ( !!ext && ( !string_compare_nocase_upper( ext, ".pk3dir" )
+                                               || !string_compare_nocase_upper( ext, ".pk4dir" ) ) ) {
+                                               snprintf( tmppath, PATH_MAX, "%s%s/", path, name );
+                                               tmppath[PATH_MAX] = '\0';
+                                               FixDOSName( tmppath );
+                                               AddSlash( tmppath );
+                                               AddPk3Dir( tmppath );
+                                       }
+                               }
+
+                               // GetArchiveTable() needs "pk3" if ext is ".pk3"
+                               if ( ( ext == 0 ) || *( ext + 1 ) == '\0' || GetArchiveTable( archiveModules, ext + 1 ) == 0 ) {
+                                       continue;
+                               }
 
                                // using the same kludge as in engine to ensure consistency
-                               switch ( iGameMode )
-                               {
-                               case 1: // SP
-                                       if ( strncmp( direntry,"sp_",3 ) == 0 ) {
-                                               memcpy( direntry,"zz",2 );
+                               if ( !string_empty( ignore_prefix ) && strncmp( name, ignore_prefix, strlen( ignore_prefix ) ) == 0 ) {
+                                       continue;
+                               }
+                               if ( !string_empty( override_prefix ) && strncmp( name, override_prefix, strlen( override_prefix ) ) == 0 ) {
+                                       if ( !string_compare_nocase_upper( ext, ".dpk" ) ) {
+                                               if ( is_dpk_vfs ) {
+                                                       archives.insert( name );
+                                               }
                                        }
-                                       break;
-                               case 2: // MP
-                                       if ( strncmp( direntry,"mp_",3 ) == 0 ) {
-                                               memcpy( direntry,"zz",2 );
+                                       else {
+                                               archivesOverride.insert( name );
                                        }
-                                       break;
+                                       continue;
                                }
 
-                               dirlist = g_slist_append( dirlist, direntry );
+                               archives.insert( name );
                        }
 
                        g_dir_close( dir );
 
-                       // sort them
-                       dirlist = g_slist_sort( dirlist, vfsPakSort );
-
-                       // add the entries to the vfs and free the list
-                       while ( dirlist )
-                       {
-                               GSList *cur = dirlist;
-                               char* name = (char*)cur->data;
-
-                               switch ( iGameMode )
-                               {
-                               case 1: // SP
-                                       if ( strncmp( name,"mp_",3 ) == 0 ) {
-                                               g_free( name );
-                                               dirlist = g_slist_remove( cur, name );
-                                               continue;
-                                       }
-                                       else if ( strncmp( name,"zz_",3 ) == 0 ) {
-                                               memcpy( name,"sp",2 );
+                       // add the entries to the vfs
+                       char* fullpath;
+                       if ( is_dpk_vfs ) {
+                               for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i ) {
+                                       const char* name = i->c_str();
+                                       const char* ext = strrchr( name, '.' );
+                                       if ( !string_compare_nocase_upper( ext, ".dpk" ) ) {
+                                               CopiedString name_final = CopiedString( StringRange( name, ext ) );
+                                               fullpath = string_new_concat( path, name );
+                                               AddDpkPak( name_final.c_str(), fullpath, true );
+                                               string_release( fullpath, string_length( fullpath ) );
                                        }
-                                       break;
-                               case 2: // MP
-                                       if ( strncmp( name,"sp_",3 ) == 0 ) {
-                                               g_free( name );
-                                               dirlist = g_slist_remove( cur, name );
-                                               continue;
+                               }
+                       }
+                       if ( is_pk3_vfs || is_pk4_vfs ) {
+                               for ( Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i )
+                               {
+                                       const char* name = i->c_str();
+                                       const char* ext = strrchr( name, '.' );
+                                       if ( !string_compare_nocase_upper( ext, ".pk3" )
+                                               || !string_compare_nocase_upper( ext, ".pk4" ) ) {
+                                               fullpath = string_new_concat( path, i->c_str() );
+                                               InitPakFile( archiveModules, fullpath );
+                                               string_release( fullpath, string_length( fullpath ) );
                                        }
-                                       else if ( strncmp( name,"zz_",3 ) == 0 ) {
-                                               memcpy( name,"mp",2 );
+                               }
+                               for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i )
+                               {
+                                       const char* name = i->c_str();
+                                       const char* ext = strrchr( name, '.' );
+                                       if ( !string_compare_nocase_upper( ext, ".pk3" )
+                                               || !string_compare_nocase_upper( ext, ".pk4" ) ) {
+                                               fullpath = string_new_concat( path, i->c_str() );
+                                               InitPakFile( archiveModules, fullpath );
+                                               string_release( fullpath, string_length( fullpath ) );
                                        }
-                                       break;
                                }
-
-                               sprintf( filename, "%s/%s", path, name );
-                               vfsInitPakFile( filename );
-
-                               g_free( name );
-                               dirlist = g_slist_remove( cur, name );
                        }
                }
-               else{
-                       g_FuncTable.m_pfnSysFPrintf( SYS_WRN, "vfs directory not found: %s\n", path );
+               else
+               {
+                       globalErrorStream() << "vfs directory not found: " << path << "\n";
                }
        }
 }
@@ -486,356 +632,306 @@ void vfsInitDirectory( const char *path ){
 // frees all memory that we allocated
 // FIXME TTimo this should be improved so that we can shutdown and restart the VFS without exiting Radiant?
 //   (for instance when modifying the project settings)
-void vfsShutdown(){
-       while ( g_unzFiles )
+void Shutdown(){
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
        {
-               unzClose( (unzFile)g_unzFiles->data );
-               g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data );
+               ( *i ).archive->release();
        }
+       g_archives.clear();
 
-       // avoid dangling pointer operation (makes BC hangry)
-       GSList *cur = g_pakFiles;
-       GSList *next = cur;
-       while ( next )
-       {
-               cur = next;
-               VFS_PAKFILE* file = (VFS_PAKFILE*)cur->data;
-               g_free( file->name );
-               g_free( file );
-               next = g_slist_remove( cur, file );
-       }
-       g_pakFiles = NULL;
-}
+       g_numDirs = 0;
+       g_numForbiddenDirs = 0;
 
-void vfsFreeFile( void *p ){
-       g_free( p );
+       g_pakfile_paths.clear();
+       g_loaded_dpk_paks.clear();
 }
 
-GSList* vfsGetFileList( const char *dir, const char *ext ){
-       return vfsGetListInternal( dir, ext, false );
-}
+#define VFS_SEARCH_PAK 0x1
+#define VFS_SEARCH_DIR 0x2
 
-GSList* vfsGetDirList( const char *dir ){
-       return vfsGetListInternal( dir, NULL, true );
-}
+int GetFileCount( const char *filename, int flag ){
+       int count = 0;
+       char fixed[PATH_MAX + 1];
 
-void vfsClearFileDirList( GSList **lst ){
-       while ( *lst )
-       {
-               g_free( ( *lst )->data );
-               *lst = g_slist_remove( *lst, ( *lst )->data );
-       }
-}
+       strncpy( fixed, filename, PATH_MAX );
+       fixed[PATH_MAX] = '\0';
+       FixDOSName( fixed );
 
-int vfsGetFileCount( const char *filename, int flag ){
-       int i, count = 0;
-       char fixed[NAME_MAX], tmp[NAME_MAX];
-       GSList *lst;
+       if ( !flag ) {
+               flag = VFS_SEARCH_PAK | VFS_SEARCH_DIR;
+       }
 
-       strcpy( fixed, filename );
-       vfsFixDOSName( fixed );
-       g_strdown( fixed );
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               if ( ( *i ).is_pakfile && ( flag & VFS_SEARCH_PAK ) != 0
+                        || !( *i ).is_pakfile && ( flag & VFS_SEARCH_DIR ) != 0 ) {
+                       if ( ( *i ).archive->containsFile( fixed ) ) {
+                               ++count;
+                       }
+               }
+       }
 
-       if ( !flag || ( flag & VFS_SEARCH_PAK ) ) {
-               for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
-               {
-                       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
+       return count;
+}
 
-                       if ( strcmp( file->name, fixed ) == 0 ) {
-                               count++;
-                       }
+ArchiveFile* OpenFile( const char* filename ){
+       ASSERT_MESSAGE( strchr( filename, '\\' ) == 0, "path contains invalid separator '\\': \"" << filename << "\"" );
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               ArchiveFile* file = ( *i ).archive->openFile( filename );
+               if ( file != 0 ) {
+                       return file;
                }
        }
 
-       if ( !flag || ( flag & VFS_SEARCH_DIR ) ) {
-               for ( i = 0; i < g_numDirs; i++ )
-               {
-                       strcpy( tmp, g_strDirs[i] );
-                       strcat( tmp, fixed );
-                       if ( access( tmp, R_OK ) == 0 ) {
-                               count++;
-                       }
+       return 0;
+}
+
+ArchiveTextFile* OpenTextFile( const char* filename ){
+       ASSERT_MESSAGE( strchr( filename, '\\' ) == 0, "path contains invalid separator '\\': \"" << filename << "\"" );
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               ArchiveTextFile* file = ( *i ).archive->openTextFile( filename );
+               if ( file != 0 ) {
+                       return file;
                }
        }
 
-       return count;
+       return 0;
 }
 
-// open a full path file
-int vfsLoadFullPathFile( const char *filename, void **bufferptr ){
-       FILE *f;
-       long len;
+// NOTE: when loading a file, you have to allocate one extra byte and set it to \0
+std::size_t LoadFile( const char *filename, void **bufferptr, int index ){
+       char fixed[PATH_MAX + 1];
 
-       f = fopen( filename, "rb" );
-       if ( f == NULL ) {
-               return -1;
-       }
+       strncpy( fixed, filename, PATH_MAX );
+       fixed[PATH_MAX] = '\0';
+       FixDOSName( fixed );
 
-       fseek( f, 0, SEEK_END );
-       len = ftell( f );
-       rewind( f );
+       ArchiveFile* file = OpenFile( fixed );
 
-       *bufferptr = g_malloc( len + 1 );
-       if ( *bufferptr == NULL ) {
-               return -1;
-       }
+       if ( file != 0 ) {
+               *bufferptr = malloc( file->size() + 1 );
+               // we need to end the buffer with a 0
+               ( (char*) ( *bufferptr ) )[file->size()] = 0;
 
-       fread( *bufferptr, 1, len, f );
-       fclose( f );
+               std::size_t length = file->getInputStream().read( (InputStream::byte_type*)*bufferptr, file->size() );
+               file->release();
+               return length;
+       }
 
-       // we need to end the buffer with a 0
-       ( (char*) ( *bufferptr ) )[len] = 0;
+       *bufferptr = 0;
+       return 0;
+}
 
-       return len;
+void FreeFile( void *p ){
+       free( p );
 }
 
-// NOTE: when loading a file, you have to allocate one extra byte and set it to \0
-int vfsLoadFile( const char *filename, void **bufferptr, int index ){
-       int i, count = 0;
-       char tmp[NAME_MAX], fixed[NAME_MAX];
-       GSList *lst;
+GSList* GetFileList( const char *dir, const char *ext, std::size_t depth ){
+       return GetListInternal( dir, ext, false, depth );
+}
 
-       *bufferptr = NULL;
-       strcpy( fixed, filename );
-       vfsFixDOSName( fixed );
-       g_strdown( fixed );
+GSList* GetDirList( const char *dir, std::size_t depth ){
+       return GetListInternal( dir, 0, true, depth );
+}
 
-       for ( i = 0; i < g_numDirs; i++ )
+void ClearFileDirList( GSList **lst ){
+       while ( *lst )
        {
-               strcpy( tmp, g_strDirs[i] );
-               strcat( tmp, filename );
-               if ( access( tmp, R_OK ) == 0 ) {
-                       if ( count == index ) {
-                               return vfsLoadFullPathFile( tmp,bufferptr );
-                       }
-
-                       count++;
-               }
+               g_free( ( *lst )->data );
+               *lst = g_slist_remove( *lst, ( *lst )->data );
        }
+}
 
-       for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
+const char* FindFile( const char* relative ){
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
        {
-               VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
-
-               if ( strcmp( file->name, fixed ) != 0 ) {
-                       continue;
+               if ( ( *i ).archive->containsFile( relative ) ) {
+                       return ( *i ).name.c_str();
                }
+       }
 
-               if ( count == index ) {
-                       memcpy( file->zipfile, &file->zipinfo, sizeof( unz_s ) );
-
-                       if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) {
-                               return -1;
-                       }
-
-                       *bufferptr = g_malloc( file->size + 1 );
-                       // we need to end the buffer with a 0
-                       ( (char*) ( *bufferptr ) )[file->size] = 0;
+       return "";
+}
 
-                       i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size );
-                       unzCloseCurrentFile( file->zipfile );
-                       if ( i > 0 ) {
-                               return file->size;
-                       }
-                       else{
-                               return -1;
+const char* FindPath( const char* absolute ){
+       const char *best = "";
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               if ( string_length( ( *i ).name.c_str() ) > string_length( best ) ) {
+                       if ( path_equal_n( absolute, ( *i ).name.c_str(), string_length( ( *i ).name.c_str() ) ) ) {
+                               best = ( *i ).name.c_str();
                        }
                }
-
-               count++;
        }
 
-       return -1;
+       return best;
 }
 
-//#ifdef _DEBUG
-#if 1
-  #define DBG_RLTPATH
-#endif
-
-/*!
-   \param shorten will try to match against the short version
-   recent switch back to short path names in project settings has broken some stuff
-   with shorten == true, we will convert in to short version before looking for root
-   FIXME WAAA .. the stuff below is much more simple on linux .. add appropriate #ifdef
- */
-char* vfsExtractRelativePath_short( const char *in, bool shorten ){
-       int i;
-       char l_in[PATH_MAX];
-       char check[PATH_MAX];
-       static char out[PATH_MAX];
-       out[0] = 0;
-
-#ifdef DBG_RLTPATH
-       Sys_Printf( "vfsExtractRelativePath: %s\n", in );
-#endif
 
-#ifdef _WIN32
-       if ( shorten ) {
-               // make it short
-               if ( GetShortPathName( in, l_in, PATH_MAX ) == 0 ) {
-#ifdef DBG_RLTPATH
-                       Sys_Printf( "GetShortPathName failed\n" );
-#endif
-                       return NULL;
-               }
-       }
-       else
-       {
-               strcpy( l_in,in );
-       }
-       vfsCleanFileName( l_in );
-#else
-       strcpy( l_in, in );
-       vfsCleanFileName( l_in );
-#endif // ifdef WIN32
+class Quake3FileSystem : public VirtualFileSystem
+{
+public:
+void initDirectory( const char *path ){
+       InitDirectory( path, FileSystemQ3API_getArchiveModules() );
+}
+void initialise(){
+       load();
+       globalOutputStream() << "filesystem initialised\n";
+       g_observers.realise();
+}
 
+void load(){
+       ArchiveModules& archiveModules = FileSystemQ3API_getArchiveModules();
+       bool is_dpk_vfs = GetArchiveTable( archiveModules, "dpk" );
 
-#ifdef DBG_RLTPATH
-       Sys_Printf( "cleaned path: %s\n", l_in );
-#endif
+       if ( is_dpk_vfs ) {
+               const char* pakname;
+               g_loaded_dpk_paks.clear();
 
-       for ( i = 0; i < g_numDirs; i++ )
-       {
-               strcpy( check,g_strDirs[i] );
-               vfsCleanFileName( check );
-#ifdef DBG_RLTPATH
-               Sys_Printf( "Matching against %s\n", check );
-#endif
+               pakname = GetLatestDpkPakVersion( "tex-common" );
+               if (pakname != NULL) {
+                       LoadDpkPakWithDeps( pakname );
+               }
 
-               // try to find a match
-               if ( strstr( l_in, check ) ) {
-                       strcpy( out,l_in + strlen( check ) + 1 );
-                       break;
+               // prevent VFS double start, for MapName="" and MapName="unnamed.map"
+               if ( string_length( GlobalRadiant().getMapName() ) ){
+                       // load map's paks from DEPS
+                       char* mappakname = GetCurrentMapDpkPakName();
+                       if ( mappakname != NULL ) {
+                               LoadDpkPakWithDeps( mappakname );
+                               string_release( mappakname, string_length( mappakname ) );
+                       }
                }
 
+               g_pakfile_paths.clear();
+               g_loaded_dpk_paks.clear();
        }
-       if ( out[0] != 0 ) {
-#ifdef DBG_RLTPATH
-               Sys_Printf( "vfsExtractRelativePath: success\n" );
-#endif
-               return out;
-       }
-#ifdef DBG_RLTPATH
-       Sys_Printf( "vfsExtractRelativePath: failed\n" );
-#endif
-       return NULL;
 }
 
+void clear() {
+       // like shutdown() but does not unrealise (keep map etc.)
+       Shutdown();
+}
 
-// FIXME TTimo: this and the above should be merged at some point
-char* vfsExtractRelativePath( const char *in ){
-       static char out[PATH_MAX];
-       unsigned int i, count;
-       char *chunk, *backup = NULL; // those point to out stuff
-       char *ret = vfsExtractRelativePath_short( in, false );
-       if ( !ret ) {
-#ifdef DBG_RLTPATH
-               Sys_Printf( "trying with a short version\n" );
-#endif
-               ret = vfsExtractRelativePath_short( in, true );
-               if ( ret ) {
-                       // ok, but we have a relative short version now
-                       // hack the long relative version out of here
-                       count = 0;
-                       for ( i = 0; i < strlen( ret ); i++ )
-                       {
-                               if ( ret[i] == '/' ) {
-                                       count++;
-                               }
-                       }
-                       // this is the clean, not short version
-                       strcpy( out, in );
-                       vfsCleanFileName( out );
-                       for ( i = 0; i <= count; i++ )
-                       {
-                               chunk = strrchr( out, '/' );
-                               if ( backup ) {
-                                       backup[0] = '/';
-                               }
-                               chunk[0] = '\0';
-                               backup = chunk;
-                       }
-                       return chunk + 1;
-               }
-       }
-       return ret;
+void refresh(){
+       // like initialise() but does not realise (keep map etc.)
+       load();
+       globalOutputStream() << "filesystem refreshed\n";
 }
 
-void vfsCleanFileName( char *in ){
-       strlwr( in );
-       vfsFixDOSName( in );
-       int n = strlen( in );
-       if ( in[n - 1] == '/' ) {
-               in[n - 1] = '\0';
-       }
+void shutdown(){
+       g_observers.unrealise();
+       globalOutputStream() << "filesystem shutdown\n";
+       Shutdown();
 }
 
-// HYDRA: this now searches VFS/PAK files in addition to the filesystem
-// if FLAG is unspecified then ONLY dirs are searched.
-// PAK's are searched before DIRs to mimic engine behaviour
-// index is ignored when searching PAK files.
-// see ifilesystem.h
-char* vfsGetFullPath( const char *in, int index, int flag ){
-       int count = 0;
-       static char out[PATH_MAX];
-       char tmp[NAME_MAX];
-       int i;
+int getFileCount( const char *filename, int flags ){
+       return GetFileCount( filename, flags );
+}
+ArchiveFile* openFile( const char* filename ){
+       return OpenFile( filename );
+}
+ArchiveTextFile* openTextFile( const char* filename ){
+       return OpenTextFile( filename );
+}
+std::size_t loadFile( const char *filename, void **buffer ){
+       return LoadFile( filename, buffer, 0 );
+}
+void freeFile( void *p ){
+       FreeFile( p );
+}
 
-       if ( flag & VFS_SEARCH_PAK ) {
-               char fixed[NAME_MAX];
-               GSList *lst;
+void forEachDirectory( const char* basedir, const FileNameCallback& callback, std::size_t depth ){
+       GSList* list = GetDirList( basedir, depth );
 
-               strcpy( fixed, in );
-               vfsFixDOSName( fixed );
-               g_strdown( fixed );
+       for ( GSList* i = list; i != 0; i = g_slist_next( i ) )
+       {
+               callback( reinterpret_cast<const char*>( ( *i ).data ) );
+       }
 
-               for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) )
-               {
-                       VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data;
+       ClearFileDirList( &list );
+}
+void forEachFile( const char* basedir, const char* extension, const FileNameCallback& callback, std::size_t depth ){
+       GSList* list = GetFileList( basedir, extension, depth );
+
+       for ( GSList* i = list; i != 0; i = g_slist_next( i ) )
+       {
+               const char* name = reinterpret_cast<const char*>( ( *i ).data );
+               if ( extension_equal( path_get_extension( name ), extension ) ) {
+                       callback( name );
+               }
+       }
 
-                       char *ptr,*lastptr;
-                       lastptr = file->name;
+       ClearFileDirList( &list );
+}
+GSList* getDirList( const char *basedir ){
+       return GetDirList( basedir, 1 );
+}
+GSList* getFileList( const char *basedir, const char *extension ){
+       return GetFileList( basedir, extension, 1 );
+}
+void clearFileDirList( GSList **lst ){
+       ClearFileDirList( lst );
+}
+
+const char* findFile( const char *name ){
+       return FindFile( name );
+}
+const char* findRoot( const char *name ){
+       return FindPath( name );
+}
 
-                       while ( ( ptr = strchr( lastptr,'/' ) ) != NULL )
-                               lastptr = ptr + 1;
+void attach( ModuleObserver& observer ){
+       g_observers.attach( observer );
+}
+void detach( ModuleObserver& observer ){
+       g_observers.detach( observer );
+}
 
-                       if ( strcmp( lastptr, fixed ) == 0 ) {
-                               strncpy( out,file->name,PATH_MAX );
-                               return out;
-                       }
+Archive* getArchive( const char* archiveName, bool pakonly ){
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               if ( pakonly && !( *i ).is_pakfile ) {
+                       continue;
                }
 
+               if ( path_equal( ( *i ).name.c_str(), archiveName ) ) {
+                       return ( *i ).archive;
+               }
+       }
+       return 0;
+}
+void forEachArchive( const ArchiveNameCallback& callback, bool pakonly, bool reverse ){
+       if ( reverse ) {
+               g_archives.reverse();
        }
 
-       if ( !flag || ( flag & VFS_SEARCH_DIR ) ) {
-               for ( i = 0; i < g_numDirs; i++ )
-               {
-                       strcpy( tmp, g_strDirs[i] );
-                       strcat( tmp, in );
-                       if ( access( tmp, R_OK ) == 0 ) {
-                               if ( count == index ) {
-                                       strcpy( out, tmp );
-                                       return out;
-                               }
-                               count++;
-                       }
+       for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
+       {
+               if ( pakonly && !( *i ).is_pakfile ) {
+                       continue;
                }
+
+               callback( ( *i ).name.c_str() );
+       }
+
+       if ( reverse ) {
+               g_archives.reverse();
        }
-       return NULL;
 }
+};
 
 
-// TODO TTimo on linux the base prompt is ~/.q3a/<fs_game>
-// given the file dialog, we could push the strFSBasePath and ~/.q3a into the directory shortcuts
-// FIXME TTimo is this really a VFS functionality?
-//   actually .. this should be the decision of the core isn't it?
-//   or .. add an API so that the base prompt can be set during VFS init
-const char* vfsBasePromptPath(){
-#ifdef _WIN32
-       static const char* path = "C:";
-#else
-       static const char* path = "/";
-#endif
-       return path;
+Quake3FileSystem g_Quake3FileSystem;
+
+VirtualFileSystem& GetFileSystem(){
+       return g_Quake3FileSystem;
+}
+
+void FileSystem_Init(){
+}
+
+void FileSystem_Shutdown(){
 }