2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
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.
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
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.
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).
37 // - Pak files are searched first inside the directories.
38 // - Case insensitive.
39 // - Unix-style slashes (/) (windows is backwards .. everyone knows that)
41 // Leonardo Zide (leo@lokigames.com)
50 #include "qerplugin.h"
51 #include "idatastream.h"
53 ArchiveModules& FileSystemQ3API_getArchiveModules();
54 #include "ifilesystem.h"
56 #include "generic/callback.h"
57 #include "string/string.h"
58 #include "stream/stringstream.h"
60 #include "moduleobservers.h"
61 #include "filematch.h"
64 #define VFS_MAXDIRS 64
70 #define gamemode_get GlobalRadiant().getGameMode
74 // =============================================================================
77 Archive* OpenArchive( const char* name );
79 struct archive_entry_t
89 typedef std::list<archive_entry_t> archives_t;
91 static archives_t g_archives;
92 static char g_strDirs[VFS_MAXDIRS][PATH_MAX + 1];
94 static char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1];
95 static int g_numForbiddenDirs = 0;
96 static bool g_bUsePak = true;
98 ModuleObservers g_observers;
100 // =============================================================================
103 static void AddSlash( char *str ){
104 std::size_t n = strlen( str );
106 if ( str[n - 1] != '\\' && str[n - 1] != '/' ) {
107 globalErrorStream() << "WARNING: directory path does not end with separator: " << str << "\n";
113 static void FixDOSName( char *src ){
114 if ( src == 0 || strchr( src, '\\' ) == 0 ) {
118 globalErrorStream() << "WARNING: invalid path separator '\\': " << src << "\n";
122 if ( *src == '\\' ) {
129 const _QERArchiveTable* GetArchiveTable( ArchiveModules& archiveModules, const char* ext ){
130 StringOutputStream tmp( 16 );
131 tmp << LowerCase( ext );
132 return archiveModules.findModule( tmp.c_str() );
135 static Archive* InitPakFile( ArchiveModules& archiveModules, const char *filename ){
136 const _QERArchiveTable* table = GetArchiveTable( archiveModules, path_get_extension( filename ) );
139 archive_entry_t entry;
140 entry.name = filename;
142 entry.archive = table->m_pfnOpenArchive( filename );
143 entry.is_pakfile = true;
144 g_archives.push_back( entry );
145 globalOutputStream() << " pak file: " << filename << "\n";
147 return entry.archive;
153 inline void pathlist_prepend_unique( GSList*& pathlist, char* path ){
154 if ( g_slist_find_custom( pathlist, path, (GCompareFunc)path_compare ) == 0 ) {
155 pathlist = g_slist_prepend( pathlist, path );
163 class DirectoryListVisitor : public Archive::Visitor
166 const char* m_directory;
168 DirectoryListVisitor( GSList*& matches, const char* directory )
169 : m_matches( matches ), m_directory( directory )
171 void visit( const char* name ){
172 const char* subname = path_make_relative( name, m_directory );
173 if ( subname != name ) {
174 if ( subname[0] == '/' ) {
177 char* dir = g_strdup( subname );
178 char* last_char = dir + strlen( dir );
179 if ( last_char != dir && *( --last_char ) == '/' ) {
182 pathlist_prepend_unique( m_matches, dir );
187 class FileListVisitor : public Archive::Visitor
190 const char* m_directory;
191 const char* m_extension;
193 FileListVisitor( GSList*& matches, const char* directory, const char* extension )
194 : m_matches( matches ), m_directory( directory ), m_extension( extension )
196 void visit( const char* name ){
197 const char* subname = path_make_relative( name, m_directory );
198 if ( subname != name ) {
199 if ( subname[0] == '/' ) {
202 if ( m_extension[0] == '*' || extension_equal( path_get_extension( subname ), m_extension ) ) {
203 pathlist_prepend_unique( m_matches, g_strdup( subname ) );
209 static GSList* GetListInternal( const char *refdir, const char *ext, bool directories, std::size_t depth ){
212 ASSERT_MESSAGE( refdir[strlen( refdir ) - 1] == '/', "search path does not end in '/'" );
215 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
217 DirectoryListVisitor visitor( files, refdir );
218 ( *i ).archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eDirectories, depth ), refdir );
223 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
225 FileListVisitor visitor( files, refdir, ext );
226 ( *i ).archive->forEachFile( Archive::VisitorFunc( visitor, Archive::eFiles, depth ), refdir );
230 files = g_slist_reverse( files );
235 inline int ascii_to_upper( int c ){
236 if ( c >= 'a' && c <= 'z' ) {
237 return c - ( 'a' - 'A' );
243 This behaves identically to stricmp(a,b), except that ASCII chars
244 [\]^`_ come AFTER alphabet chars instead of before. This is because
245 it converts all alphabet chars to uppercase before comparison,
246 while stricmp converts them to lowercase.
248 static int string_compare_nocase_upper( const char* a, const char* b ){
251 int c1 = ascii_to_upper( *a++ );
252 int c2 = ascii_to_upper( *b++ );
266 // Arnout: note - sort pakfiles in reverse order. This ensures that
267 // later pakfiles override earlier ones. This because the vfs module
268 // returns a filehandle to the first file it can find (while it should
269 // return the filehandle to the file in the most overriding pakfile, the
270 // last one in the list that is).
272 //!\todo Analyse the code in rtcw/q3 to see which order it sorts pak files.
276 bool operator()( const CopiedString& self, const CopiedString& other ) const {
277 return string_compare_nocase_upper( self.c_str(), other.c_str() ) > 0;
281 typedef std::set<CopiedString, PakLess> Archives;
283 Archive* AddPk3Dir( const char* fullpath ){
284 if ( g_numDirs == VFS_MAXDIRS ) return 0;
286 strncpy( g_strDirs[g_numDirs], fullpath, PATH_MAX );
287 g_strDirs[g_numDirs][PATH_MAX] = '\0';
291 archive_entry_t entry;
292 entry.name = fullpath;
293 entry.archive = OpenArchive( fullpath );
294 entry.is_pakfile = false;
295 g_archives.push_back( entry );
297 return entry.archive;
301 // for Daemon DPK vfs
303 Archive* AddDpkDir( const char* fullpath ){
304 return AddPk3Dir( fullpath );
307 struct pakfile_path_t
309 CopiedString fullpath; // full pak dir or pk3dir name
310 bool is_pakfile; // defines is it .pk3dir or .pk3 file
313 typedef std::pair<CopiedString, pakfile_path_t> PakfilePathsKV;
314 typedef std::map<CopiedString, pakfile_path_t> PakfilePaths; // key must have no extension, only name
316 static PakfilePaths g_pakfile_paths;
318 void AddDpkPak( const char* name, const char* fullpath, bool is_pakfile ){
319 pakfile_path_t pakfile_path;
320 pakfile_path.fullpath = fullpath;
321 pakfile_path.is_pakfile = is_pakfile;
322 g_pakfile_paths.insert( PakfilePathsKV( name, pakfile_path ) );
325 // Comparaison function for version numbers
326 // Implementation is based on dpkg's version comparison code (verrevcmp() and order())
327 // http://anonscm.debian.org/gitweb/?p=dpkg/dpkg.git;a=blob;f=lib/dpkg/version.c;hb=74946af470550a3295e00cf57eca1747215b9311
328 static int char_weight(char c){
331 else if (std::isalpha(c))
341 static int VersionCmp(const char* a, const char* b){
345 while ((*a && !std::isdigit(*a)) || (*b && !std::isdigit(*b))) {
346 int ac = char_weight(*a);
347 int bc = char_weight(*b);
361 while (std::isdigit(*a) && std::isdigit(*b)) {
368 if (std::isdigit(*a))
370 if (std::isdigit(*b))
379 // takes name without ext, returns without ext
380 static const char* GetLatestDpkPakVersion( const char* name ){
381 const char* maxversion = 0;
382 const char* result = 0;
384 const char* pakversion;
385 int namelen = string_length( name );
387 for ( PakfilePaths::iterator i = g_pakfile_paths.begin(); i != g_pakfile_paths.end(); ++i )
389 pakname = i->first.c_str();
390 if ( strncmp( pakname, name, namelen ) != 0 || pakname[namelen] != '_' ) continue;
391 pakversion = pakname + (namelen + 1);
392 if ( maxversion == 0 || VersionCmp( pakversion, maxversion ) > 0 ){
393 maxversion = pakversion;
400 // release string after using
401 static char* GetCurrentMapDpkPakName(){
407 mapname = string_clone( GlobalRadiant().getMapName() );
408 mapnamelen = string_length( mapname );
410 mapdir = strrchr( mapname, '/' );
413 if ( strncmp( mapdir, ".dpkdir/maps/", 13 ) == 0 ) {
415 mapdir = strrchr( mapname, '/' );
416 if ( mapdir ) mapdir++;
417 else mapdir = mapname;
418 result = string_clone( mapdir );
422 string_release( mapname, mapnamelen );
427 // prevent loading duplicates or circular references
428 static Archives g_loaded_dpk_paks;
430 // actual pak adding on initialise, deferred from InitDirectory
431 // Daemon DPK filesystem doesn't need load all paks it finds
432 static void LoadDpkPakWithDeps( const char* pakname ){
433 const char* und = strrchr( pakname, '_' );
434 if ( !und ) pakname = GetLatestDpkPakVersion( pakname );
435 if ( !pakname || g_loaded_dpk_paks.find( pakname ) != g_loaded_dpk_paks.end() ) return;
437 PakfilePaths::iterator i = g_pakfile_paths.find( pakname );
438 if ( i == g_pakfile_paths.end() ) return;
441 if ( i->second.is_pakfile ){
442 arc = InitPakFile( FileSystemQ3API_getArchiveModules(), i->second.fullpath.c_str() );
444 arc = AddDpkDir( i->second.fullpath.c_str() );
446 g_loaded_dpk_paks.insert( pakname );
448 ArchiveTextFile* depsFile = arc->openTextFile( "DEPS" );
449 if ( !depsFile ) return;
452 TextLinesInputStream<TextInputStream> istream = depsFile->getInputStream();
457 const char* p_name_end;
458 const char* p_version;
459 const char* p_version_end;
460 while ( line = istream.readLine(), string_length( line.c_str() ) ) {
462 while ( std::isspace( *c ) && *c != '\0' ) ++c;
464 while ( !std::isspace( *c ) && *c != '\0' ) ++c;
466 while ( std::isspace( *c ) && *c != '\0' ) ++c;
468 while ( !std::isspace( *c ) && *c != '\0' ) ++c;
471 if ( p_name_end - p_name == 0 ) continue;
472 if ( p_version_end - p_version == 0 ) {
473 const char* p_pakname;
474 CopiedString name_final = CopiedString( StringRange( p_name, p_name_end ) );
475 p_pakname = GetLatestDpkPakVersion( name_final.c_str() );
476 if ( p_pakname != NULL ) {
477 LoadDpkPakWithDeps( p_pakname );
480 int len = ( p_name_end - p_name ) + ( p_version_end - p_version ) + 1;
481 char* p_pakname = string_new( len );
482 strncpy( p_pakname, p_name, p_name_end - p_name );
483 p_pakname[ p_name_end - p_name ] = '\0';
484 strcat( p_pakname, "_" );
485 strncat( p_pakname, p_version, p_version_end - p_version );
486 LoadDpkPakWithDeps( p_pakname );
487 string_release( p_pakname, len );
495 // end for Daemon DPK vfs
497 // =============================================================================
500 // reads all pak files from a dir
501 void InitDirectory( const char* directory, ArchiveModules& archiveModules ){
504 g_numForbiddenDirs = 0;
505 StringTokeniser st( GlobalRadiant().getGameDescriptionKeyValue( "forbidden_paths" ), " " );
506 for ( j = 0; j < VFS_MAXDIRS; ++j )
508 const char *t = st.getToken();
509 if ( string_empty( t ) ) {
512 strncpy( g_strForbiddenDirs[g_numForbiddenDirs], t, PATH_MAX );
513 g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = '\0';
514 ++g_numForbiddenDirs;
517 for ( j = 0; j < g_numForbiddenDirs; ++j )
519 char* dbuf = g_strdup( directory );
520 if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) {
521 dbuf[strlen( dbuf ) - 1] = 0;
523 const char *p = strrchr( dbuf, '/' );
524 p = ( p ? ( p + 1 ) : dbuf );
525 if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
531 if ( j < g_numForbiddenDirs ) {
532 printf( "Directory %s matched by forbidden dirs, removed\n", directory );
536 if ( g_numDirs == VFS_MAXDIRS ) {
540 strncpy( g_strDirs[g_numDirs], directory, PATH_MAX );
541 g_strDirs[g_numDirs][PATH_MAX] = '\0';
542 FixDOSName( g_strDirs[g_numDirs] );
543 AddSlash( g_strDirs[g_numDirs] );
545 const char* path = g_strDirs[g_numDirs];
550 archive_entry_t entry;
552 entry.archive = OpenArchive( path );
553 entry.is_pakfile = false;
554 g_archives.push_back( entry );
559 GDir* dir = g_dir_open( path, 0, 0 );
562 globalOutputStream() << "vfs directory: " << path << "\n";
565 Archives archivesOverride;
566 const char* ignore_prefix = "";
567 const char* override_prefix = "";
568 bool is_pk3_vfs, is_pk4_vfs, is_dpk_vfs;
570 is_pk3_vfs = GetArchiveTable( archiveModules, "pk3" );
571 is_pk4_vfs = GetArchiveTable( archiveModules, "pk4" );
572 is_dpk_vfs = GetArchiveTable( archiveModules, "dpk" );
575 // See if we are in "sp" or "mp" mapping mode
576 const char* gamemode = gamemode_get();
578 if ( strcmp( gamemode, "sp" ) == 0 ) {
579 ignore_prefix = "mp_";
580 override_prefix = "sp_";
582 else if ( strcmp( gamemode, "mp" ) == 0 ) {
583 ignore_prefix = "sp_";
584 override_prefix = "mp_";
590 const char* name = g_dir_read_name( dir );
595 for ( j = 0; j < g_numForbiddenDirs; ++j )
597 const char *p = strrchr( name, '/' );
598 p = ( p ? ( p + 1 ) : name );
599 if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) {
603 if ( j < g_numForbiddenDirs ) {
607 const char *ext = strrchr( name, '.' );
608 char tmppath[PATH_MAX];
611 if ( !!ext && !string_compare_nocase_upper( ext, ".dpkdir" ) ) {
612 snprintf( tmppath, PATH_MAX, "%s%s/", path, name );
613 tmppath[PATH_MAX] = '\0';
614 FixDOSName( tmppath );
616 AddDpkPak( CopiedString( StringRange( name, ext ) ).c_str(), tmppath, false );
620 if ( is_pk3_vfs || is_pk4_vfs ) {
621 if ( !!ext && ( !string_compare_nocase_upper( ext, ".pk3dir" )
622 || !string_compare_nocase_upper( ext, ".pk4dir" ) ) ) {
623 snprintf( tmppath, PATH_MAX, "%s%s/", path, name );
624 tmppath[PATH_MAX] = '\0';
625 FixDOSName( tmppath );
627 AddPk3Dir( tmppath );
631 // GetArchiveTable() needs "pk3" if ext is ".pk3"
632 if ( ( ext == 0 ) || *( ext + 1 ) == '\0' || GetArchiveTable( archiveModules, ext + 1 ) == 0 ) {
636 // using the same kludge as in engine to ensure consistency
637 if ( !string_empty( ignore_prefix ) && strncmp( name, ignore_prefix, strlen( ignore_prefix ) ) == 0 ) {
640 if ( !string_empty( override_prefix ) && strncmp( name, override_prefix, strlen( override_prefix ) ) == 0 ) {
641 if ( !string_compare_nocase_upper( ext, ".dpk" ) ) {
643 archives.insert( name );
647 archivesOverride.insert( name );
652 archives.insert( name );
657 // add the entries to the vfs
660 for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i ) {
661 const char* name = i->c_str();
662 const char* ext = strrchr( name, '.' );
663 if ( !string_compare_nocase_upper( ext, "dpk" ) ) {
664 CopiedString name_final = CopiedString( StringRange( name, ext ) );
665 fullpath = string_new_concat( path, name );
666 AddDpkPak( name_final.c_str(), fullpath, true );
667 string_release( fullpath, string_length( fullpath ) );
671 if ( is_pk3_vfs || is_pk4_vfs ) {
672 for ( Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i )
674 const char* name = i->c_str();
675 const char* ext = strrchr( name, '.' );
676 if ( !string_compare_nocase_upper( ext, "pk3" )
677 || !string_compare_nocase_upper( ext, "pk4" ) ) {
678 fullpath = string_new_concat( path, i->c_str() );
679 InitPakFile( archiveModules, fullpath );
680 string_release( fullpath, string_length( fullpath ) );
683 for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i )
685 const char* name = i->c_str();
686 const char* ext = strrchr( name, '.' );
687 if ( !string_compare_nocase_upper( ext, "pk3" )
688 || !string_compare_nocase_upper( ext, "pk4" ) ) {
689 fullpath = string_new_concat( path, i->c_str() );
690 InitPakFile( archiveModules, fullpath );
691 string_release( fullpath, string_length( fullpath ) );
698 globalErrorStream() << "vfs directory not found: " << path << "\n";
703 // frees all memory that we allocated
704 // FIXME TTimo this should be improved so that we can shutdown and restart the VFS without exiting Radiant?
705 // (for instance when modifying the project settings)
707 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
709 ( *i ).archive->release();
714 g_numForbiddenDirs = 0;
716 g_pakfile_paths.clear();
717 g_loaded_dpk_paks.clear();
720 #define VFS_SEARCH_PAK 0x1
721 #define VFS_SEARCH_DIR 0x2
723 int GetFileCount( const char *filename, int flag ){
725 char fixed[PATH_MAX + 1];
727 strncpy( fixed, filename, PATH_MAX );
728 fixed[PATH_MAX] = '\0';
732 flag = VFS_SEARCH_PAK | VFS_SEARCH_DIR;
735 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
737 if ( ( *i ).is_pakfile && ( flag & VFS_SEARCH_PAK ) != 0
738 || !( *i ).is_pakfile && ( flag & VFS_SEARCH_DIR ) != 0 ) {
739 if ( ( *i ).archive->containsFile( fixed ) ) {
748 ArchiveFile* OpenFile( const char* filename ){
749 ASSERT_MESSAGE( strchr( filename, '\\' ) == 0, "path contains invalid separator '\\': \"" << filename << "\"" );
750 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
752 ArchiveFile* file = ( *i ).archive->openFile( filename );
761 ArchiveTextFile* OpenTextFile( const char* filename ){
762 ASSERT_MESSAGE( strchr( filename, '\\' ) == 0, "path contains invalid separator '\\': \"" << filename << "\"" );
763 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
765 ArchiveTextFile* file = ( *i ).archive->openTextFile( filename );
774 // NOTE: when loading a file, you have to allocate one extra byte and set it to \0
775 std::size_t LoadFile( const char *filename, void **bufferptr, int index ){
776 char fixed[PATH_MAX + 1];
778 strncpy( fixed, filename, PATH_MAX );
779 fixed[PATH_MAX] = '\0';
782 ArchiveFile* file = OpenFile( fixed );
785 *bufferptr = malloc( file->size() + 1 );
786 // we need to end the buffer with a 0
787 ( (char*) ( *bufferptr ) )[file->size()] = 0;
789 std::size_t length = file->getInputStream().read( (InputStream::byte_type*)*bufferptr, file->size() );
798 void FreeFile( void *p ){
802 GSList* GetFileList( const char *dir, const char *ext, std::size_t depth ){
803 return GetListInternal( dir, ext, false, depth );
806 GSList* GetDirList( const char *dir, std::size_t depth ){
807 return GetListInternal( dir, 0, true, depth );
810 void ClearFileDirList( GSList **lst ){
813 g_free( ( *lst )->data );
814 *lst = g_slist_remove( *lst, ( *lst )->data );
818 const char* FindFile( const char* relative ){
819 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
821 if ( ( *i ).archive->containsFile( relative ) ) {
822 return ( *i ).name.c_str();
829 const char* FindPath( const char* absolute ){
830 const char *best = "";
831 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
833 if ( string_length( ( *i ).name.c_str() ) > string_length( best ) ) {
834 if ( path_equal_n( absolute, ( *i ).name.c_str(), string_length( ( *i ).name.c_str() ) ) ) {
835 best = ( *i ).name.c_str();
844 class Quake3FileSystem : public VirtualFileSystem
847 void initDirectory( const char *path ){
848 InitDirectory( path, FileSystemQ3API_getArchiveModules() );
851 ArchiveModules& archiveModules = FileSystemQ3API_getArchiveModules();
852 bool is_dpk_vfs = GetArchiveTable( archiveModules, "dpk" );
856 g_loaded_dpk_paks.clear();
858 pakname = GetLatestDpkPakVersion( "tex-common" );
859 if (pakname != NULL) {
860 LoadDpkPakWithDeps( pakname );
863 // prevent VFS double start, for MapName="" and MapName="unnamed.map"
864 if ( string_length( GlobalRadiant().getMapName() ) ){
865 // load map's paks from DEPS
866 char* mappakname = GetCurrentMapDpkPakName();
867 if ( mappakname != NULL ) {
868 LoadDpkPakWithDeps( mappakname );
869 string_release( mappakname, string_length( mappakname ) );
873 g_pakfile_paths.clear();
874 g_loaded_dpk_paks.clear();
877 globalOutputStream() << "filesystem initialised\n";
878 g_observers.realise();
881 g_observers.unrealise();
882 globalOutputStream() << "filesystem shutdown\n";
886 int getFileCount( const char *filename, int flags ){
887 return GetFileCount( filename, flags );
889 ArchiveFile* openFile( const char* filename ){
890 return OpenFile( filename );
892 ArchiveTextFile* openTextFile( const char* filename ){
893 return OpenTextFile( filename );
895 std::size_t loadFile( const char *filename, void **buffer ){
896 return LoadFile( filename, buffer, 0 );
898 void freeFile( void *p ){
902 void forEachDirectory( const char* basedir, const FileNameCallback& callback, std::size_t depth ){
903 GSList* list = GetDirList( basedir, depth );
905 for ( GSList* i = list; i != 0; i = g_slist_next( i ) )
907 callback( reinterpret_cast<const char*>( ( *i ).data ) );
910 ClearFileDirList( &list );
912 void forEachFile( const char* basedir, const char* extension, const FileNameCallback& callback, std::size_t depth ){
913 GSList* list = GetFileList( basedir, extension, depth );
915 for ( GSList* i = list; i != 0; i = g_slist_next( i ) )
917 const char* name = reinterpret_cast<const char*>( ( *i ).data );
918 if ( extension_equal( path_get_extension( name ), extension ) ) {
923 ClearFileDirList( &list );
925 GSList* getDirList( const char *basedir ){
926 return GetDirList( basedir, 1 );
928 GSList* getFileList( const char *basedir, const char *extension ){
929 return GetFileList( basedir, extension, 1 );
931 void clearFileDirList( GSList **lst ){
932 ClearFileDirList( lst );
935 const char* findFile( const char *name ){
936 return FindFile( name );
938 const char* findRoot( const char *name ){
939 return FindPath( name );
942 void attach( ModuleObserver& observer ){
943 g_observers.attach( observer );
945 void detach( ModuleObserver& observer ){
946 g_observers.detach( observer );
949 Archive* getArchive( const char* archiveName, bool pakonly ){
950 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
952 if ( pakonly && !( *i ).is_pakfile ) {
956 if ( path_equal( ( *i ).name.c_str(), archiveName ) ) {
957 return ( *i ).archive;
962 void forEachArchive( const ArchiveNameCallback& callback, bool pakonly, bool reverse ){
964 g_archives.reverse();
967 for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i )
969 if ( pakonly && !( *i ).is_pakfile ) {
973 callback( ( *i ).name.c_str() );
977 g_archives.reverse();
983 Quake3FileSystem g_Quake3FileSystem;
985 VirtualFileSystem& GetFileSystem(){
986 return g_Quake3FileSystem;
989 void FileSystem_Init(){
992 void FileSystem_Shutdown(){