X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fnetradiant.git;a=blobdiff_plain;f=plugins%2Fvfspk3%2Fvfs.cpp;h=9ec85fd818a292d44ca172fc6f1777fcf51f09da;hp=b47942ba9149e2394fdaafc35cc2783b47c26902;hb=bbe9ab19c2012a84d1e0080e2e1bb8f8a402f13c;hpb=0a784ea43961c5edddb23ac109948e682d1b8eb5 diff --git a/plugins/vfspk3/vfs.cpp b/plugins/vfspk3/vfs.cpp index b47942ba..9ec85fd8 100644 --- a/plugins/vfspk3/vfs.cpp +++ b/plugins/vfspk3/vfs.cpp @@ -1,32 +1,32 @@ /* -Copyright (c) 2001, Loki software, inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of Loki software nor the names of its contributors may be used -to endorse or promote products derived from this software without specific prior -written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY -DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + Copyright (c) 2001, Loki software, inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Loki software nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ // // Rules: @@ -45,9 +45,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include -#include -#include +#include #include "qerplugin.h" #include "idatastream.h" @@ -60,11 +58,12 @@ ArchiveModules& FileSystemQ3API_getArchiveModules(); #include "stream/stringstream.h" #include "os/path.h" #include "moduleobservers.h" +#include "filematch.h" #define VFS_MAXDIRS 64 -#if defined(WIN32) +#if defined( WIN32 ) #define PATH_MAX 260 #endif @@ -75,204 +74,194 @@ ArchiveModules& FileSystemQ3API_getArchiveModules(); // ============================================================================= // Global variables -Archive* OpenArchive(const char* name); +Archive* OpenArchive( const char* name ); struct archive_entry_t { - CopiedString name; - Archive* archive; - bool is_pakfile; + CopiedString name; + Archive* archive; + bool is_pakfile; }; #include +#include typedef std::list 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; +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 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 FixDOSName (char *src) -{ - if (src == 0 || strchr(src, '\\') == 0) - return; +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 FixDOSName( char *src ){ + if ( src == 0 || strchr( src, '\\' ) == 0 ) { + return; + } - globalErrorStream() << "WARNING: invalid path separator '\\': " << src << "\n"; + globalErrorStream() << "WARNING: invalid path separator '\\': " << src << "\n"; - while (*src) - { - if (*src == '\\') - *src = '/'; - src++; - } + while ( *src ) + { + if ( *src == '\\' ) { + *src = '/'; + } + src++; + } } -const _QERArchiveTable* GetArchiveTable(ArchiveModules& archiveModules, const char* ext) -{ - StringOutputStream tmp(16); - tmp << LowerCase(ext); - return archiveModules.findModule(tmp.c_str()); +const _QERArchiveTable* GetArchiveTable( ArchiveModules& archiveModules, const char* ext ){ + StringOutputStream tmp( 16 ); + tmp << LowerCase( ext ); + return archiveModules.findModule( tmp.c_str() ); } -static void InitPakFile (ArchiveModules& archiveModules, const char *filename) -{ - const _QERArchiveTable* table = GetArchiveTable(archiveModules, path_get_extension(filename)); +static Archive* InitPakFile( ArchiveModules& archiveModules, const char *filename ){ + const _QERArchiveTable* table = GetArchiveTable( archiveModules, path_get_extension( filename ) ); - if(table != 0) - { - archive_entry_t entry; - entry.name = filename; + if ( table != 0 ) { + archive_entry_t entry; + entry.name = filename; - entry.archive = table->m_pfnOpenArchive(filename); - entry.is_pakfile = true; - g_archives.push_back(entry); - globalOutputStream() << " pak file: " << filename << "\n"; - } + entry.archive = table->m_pfnOpenArchive( filename ); + entry.is_pakfile = true; + g_archives.push_back( entry ); + globalOutputStream() << " pak file: " << filename << "\n"; + + return entry.archive; + } + + 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 - { - g_free(path); - } +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 + { + g_free( path ); + } } class DirectoryListVisitor : public Archive::Visitor { - GSList*& m_matches; - const char* m_directory; +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); - } - } +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 ); + } +} }; class FileListVisitor : public Archive::Visitor { - GSList*& m_matches; - const char* m_directory; - const char* m_extension; +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)); - } - } +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; - ASSERT_MESSAGE(refdir[strlen(refdir) - 1] == '/', "search path does not end in '/'"); +static GSList* GetListInternal( const char *refdir, const char *ext, bool directories, std::size_t depth ){ + GSList* files = 0; - 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 - { - 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); - } - } + ASSERT_MESSAGE( refdir[strlen( refdir ) - 1] == '/', "search path does not end in '/'" ); + + 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 + { + 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 ); + } + } - files = g_slist_reverse(files); + files = g_slist_reverse( files ); - return files; + return files; } -inline int ascii_to_upper(int c) -{ - if (c >= 'a' && c <= 'z') - { - return c - ('a' - 'A'); +inline int ascii_to_upper( int c ){ + if ( c >= 'a' && c <= 'z' ) { + return c - ( 'a' - 'A' ); } - return c; + return c; } /*! -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(;;) - { - int c1 = ascii_to_upper(*a++); - int c2 = ascii_to_upper(*b++); + 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 (;; ) + { + int c1 = ascii_to_upper( *a++ ); + int c2 = ascii_to_upper( *b++ ); - if (c1 < c2) - { + if ( c1 < c2 ) { return -1; // a < b } - if (c1 > c2) - { + if ( c1 > c2 ) { return 1; // a > b } - if(c1 == 0) - { - return 0; // a == b - } - } + if ( c1 == 0 ) { + return 0; // a == b + } + } } // Arnout: note - sort pakfiles in reverse order. This ensures that @@ -285,458 +274,687 @@ static int string_compare_nocase_upper(const char* a, const char* b) class PakLess { public: - bool operator()(const CopiedString& self, const CopiedString& other) const - { - return string_compare_nocase_upper(self.c_str(), other.c_str()) > 0; - } +bool operator()( const CopiedString& self, const CopiedString& other ) const { + return string_compare_nocase_upper( self.c_str(), other.c_str() ) > 0; +} }; typedef std::set Archives; +Archive* AddPk3Dir( const char* fullpath ){ + if ( g_numDirs == VFS_MAXDIRS ) return 0; + + strncpy( g_strDirs[g_numDirs], fullpath, PATH_MAX ); + g_strDirs[g_numDirs][PATH_MAX] = '\0'; + g_numDirs++; + + { + archive_entry_t entry; + entry.name = fullpath; + entry.archive = OpenArchive( fullpath ); + entry.is_pakfile = false; + g_archives.push_back( entry ); + + return entry.archive; + } +} + +// for unvanquished + +bool IsUnvanquished(){ + return strncmp( GlobalRadiant().getGameFile(), "unvanquished", 12 ) == 0; +} + +struct pakfile_path_t +{ + CopiedString fullpath; // full pak dir or pk3dir name + bool is_pakfile; // defines is it .pk3dir or .pk3 file +}; + +typedef std::pair PakfilePathsKV; +typedef std::map PakfilePaths; // key must have no extension, only name + +static PakfilePaths g_pakfile_paths; + +void AddUnvPak( 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 ) ); +} + +// Comparaison function for version numbers +// Implementation is based on dpkg's version comparison code (verrevcmp() and order()) +// http://anonscm.debian.org/gitweb/?p=dpkg/dpkg.git;a=blob;f=lib/dpkg/version.c;hb=74946af470550a3295e00cf57eca1747215b9311 +static int char_weight(char c){ + if (std::isdigit(c)) + return 0; + else if (std::isalpha(c)) + return c; + else if (c == '~') + return -1; + else if (c) + return c + 256; + else + return 0; +} + +static int VersionCmp(const char* a, const char* b){ + while (*a || *b) { + int firstDiff = 0; + + while ((*a && !std::isdigit(*a)) || (*b && !std::isdigit(*b))) { + int ac = char_weight(*a); + int bc = char_weight(*b); + + if (ac != bc) + return ac - bc; + + a++; + b++; + } + + while (*a == '0') + a++; + while (*b == '0') + b++; + + while (std::isdigit(*a) && std::isdigit(*b)) { + if (firstDiff == 0) + firstDiff = *a - *b; + a++; + b++; + } + + if (std::isdigit(*a)) + return 1; + if (std::isdigit(*b)) + return -1; + if (firstDiff) + return firstDiff; + } + + return false; +} + +// takes name without ext, returns without ext +static const char* GetLatestVersionOfUnvPak( const char* name ){ + const char* maxversion = 0; + const char* result = 0; + const char* pakname; + const char* pakversion; + int namelen = string_length( name ); + + 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 || VersionCmp( pakversion, maxversion ) > 0 ){ + maxversion = pakversion; + result = pakname; + } + } + return result; +} + +// release string after using +static char* GetCurrentMapPakName(){ + 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, ".pk3dir/maps/", 13 ) == 0 ) { + *mapdir = '\0'; + mapdir = strrchr( mapname, '/' ); + if ( mapdir ) mapdir++; + else mapdir = mapname; + result = string_clone( mapdir ); + } + } + + string_release( mapname, mapnamelen ); + return result; + +} + +// prevent loading duplicates or circular references +static Archives g_loaded_unv_paks; + +// actual pak adding on initialise, deferred from InitDirectory +// Unvanquished filesystem doesn't need load all paks it finds +static void LoadPakWithDeps( const char* pakname ){ + const char* und = strrchr( pakname, '_' ); + if ( !und ) pakname = GetLatestVersionOfUnvPak( pakname ); + if ( !pakname || g_loaded_unv_paks.find( pakname ) != g_loaded_unv_paks.end() ) return; + + PakfilePaths::iterator i = g_pakfile_paths.find( pakname ); + if ( i == g_pakfile_paths.end() ) return; + + Archive* arc; + if ( i->second.is_pakfile ){ + arc = InitPakFile( FileSystemQ3API_getArchiveModules(), i->second.fullpath.c_str() ); + } else { + arc = AddPk3Dir( i->second.fullpath.c_str() ); + } + g_loaded_unv_paks.insert( pakname ); + + ArchiveTextFile* depsFile = arc->openTextFile( "DEPS" ); + if ( !depsFile ) return; + + { + TextLinesInputStream istream = depsFile->getInputStream(); + + CopiedString line; + const char* c; + const char* p_name; + const char* p_name_end; + const char* p_version; + const char* p_version_end; + while ( line = istream.readLine(), string_length( line.c_str() ) ) { + c = line.c_str(); + while ( std::isspace( *c ) && *c != '\0' ) ++c; + p_name = c; + while ( !std::isspace( *c ) && *c != '\0' ) ++c; + p_name_end = c; + while ( std::isspace( *c ) && *c != '\0' ) ++c; + p_version = c; + while ( !std::isspace( *c ) && *c != '\0' ) ++c; + p_version_end = c; + + if ( p_name_end - p_name == 0 ) continue; + if ( p_version_end - p_version == 0 ) { + const char* p_pakname; + CopiedString name_final = CopiedString( StringRange( p_name, p_name_end ) ); + p_pakname = GetLatestVersionOfUnvPak( name_final.c_str() ); + if ( !p_pakname ) continue; + LoadPakWithDeps( p_pakname ); + } else { + int len = ( p_name_end - p_name ) + ( p_version_end - p_version ) + 1; + char* p_pakname = string_new( len ); + strncpy( p_pakname, p_name, p_name_end - p_name ); + p_pakname[ p_name_end - p_name ] = '\0'; + strcat( p_pakname, "_" ); + strncat( p_pakname, p_version, p_version_end - p_version ); + LoadPakWithDeps( p_pakname ); + string_release( p_pakname, len ); + } + } + } + + depsFile->release(); +} + +// end for unvanquished + // ============================================================================= // Global functions // reads all pak files from a dir -void InitDirectory(const char* directory, ArchiveModules& archiveModules) -{ - int j; - - 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; - } - - for(j = 0; j < g_numForbiddenDirs; ++j) - { - if(!string_compare_nocase_upper(directory, g_strForbiddenDirs[j]) - || (string_length(directory) > string_length(g_strForbiddenDirs[j]) && directory[string_length(directory) - string_length(g_strForbiddenDirs[j]) - 1] == '/' && !string_compare_nocase_upper(directory + string_length(directory) - string_length(g_strForbiddenDirs[j]), g_strForbiddenDirs[j]))) - break; - } - if(j < g_numForbiddenDirs) - { - printf("Directory %s matched by forbidden dirs, removed\n", directory); - return; - } - - 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) - { - GDir* dir = g_dir_open (path, 0, 0); - - if (dir != 0) - { - globalOutputStream() << "vfs directory: " << path << "\n"; +void InitDirectory( const char* directory, ArchiveModules& archiveModules ){ + int j; + + 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; + } + + for ( j = 0; j < g_numForbiddenDirs; ++j ) + { + char* dbuf = g_strdup( directory ); + if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) { + dbuf[strlen( dbuf ) - 1] = 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 ); + } + if ( j < g_numForbiddenDirs ) { + printf( "Directory %s matched by forbidden dirs, removed\n", directory ); + return; + } + + 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++; - const char* ignore_prefix = ""; - const char* override_prefix = ""; - - { - // 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_"; - } - } - - Archives archives; - Archives archivesOverride; - for(;;) - { - const char* name = g_dir_read_name(dir); - if(name == 0) - break; - - for(j = 0; j < g_numForbiddenDirs; ++j) - if(!string_compare_nocase_upper(name, g_strForbiddenDirs[j])) - break; - if(j < g_numForbiddenDirs) - continue; - - const char *ext = strrchr (name, '.'); - - if(ext && !string_compare_nocase_upper(ext, ".pk3dir")) { - if (g_numDirs == VFS_MAXDIRS) - continue; - snprintf(g_strDirs[g_numDirs], PATH_MAX, "%s%s/", path, name); - g_strDirs[g_numDirs][PATH_MAX] = '\0'; - FixDOSName (g_strDirs[g_numDirs]); - AddSlash (g_strDirs[g_numDirs]); - g_numDirs++; - - { - archive_entry_t entry; - entry.name = g_strDirs[g_numDirs-1]; - entry.archive = OpenArchive(g_strDirs[g_numDirs-1]); - entry.is_pakfile = false; - g_archives.push_back(entry); - } - } - - if ((ext == 0) || *(++ext) == '\0' || GetArchiveTable(archiveModules, ext) == 0) - continue; - - // using the same kludge as in engine to ensure consistency - if(!string_empty(ignore_prefix) && strncmp(name, ignore_prefix, strlen(ignore_prefix)) == 0) + archive_entry_t entry; + entry.name = path; + entry.archive = OpenArchive( path ); + entry.is_pakfile = false; + g_archives.push_back( entry ); + } + + if ( g_bUsePak ) { + + GDir* dir = g_dir_open( path, 0, 0 ); + + if ( dir != 0 ) { + globalOutputStream() << "vfs directory: " << path << "\n"; + + bool unv; + unv = IsUnvanquished(); + + const char* ignore_prefix = ""; + const char* override_prefix = ""; + + if ( !unv ) { + // 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_"; + } + } + + Archives archives; + Archives archivesOverride; + for (;; ) + { + const char* name = g_dir_read_name( dir ); + if ( name == 0 ) { + break; + } + + 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; + } + + const char *ext = strrchr( name, '.' ); + char tmppath[PATH_MAX]; + + if ( ext && !string_compare_nocase_upper( ext, ".pk3dir" ) ) { + + snprintf( tmppath, PATH_MAX, "%s%s/", path, name ); + tmppath[PATH_MAX] = '\0'; + FixDOSName( tmppath ); + AddSlash( tmppath ); + + if ( unv ) { + AddUnvPak( CopiedString( StringRange( name, ext ) ).c_str(), tmppath, false ); + } else { + AddPk3Dir( tmppath ); + } + } + + if ( ( ext == 0 ) || *( ++ext ) == '\0' || GetArchiveTable( archiveModules, ext ) == 0 ) { continue; } - if(!string_empty(override_prefix) && strncmp(name, override_prefix, strlen(override_prefix)) == 0) - { - archivesOverride.insert(name); + + // using the same kludge as in engine to ensure consistency + 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 ( unv ) { + archives.insert( name ); + } else { + archivesOverride.insert( name ); + } continue; - } + } - archives.insert(name); - } + archives.insert( name ); + } - g_dir_close (dir); + g_dir_close( dir ); // add the entries to the vfs - for(Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i) - { - char filename[PATH_MAX]; - strcpy(filename, path); - strcat(filename, (*i).c_str()); - InitPakFile(archiveModules, filename); - } - for(Archives::iterator i = archives.begin(); i != archives.end(); ++i) - { - char filename[PATH_MAX]; - strcpy(filename, path); - strcat(filename, (*i).c_str()); - InitPakFile(archiveModules, filename); + char* fullpath; + if ( unv ) { + for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i ) { + const char* name = i->c_str(); + const char* ext = strrchr( name, '.' ); + CopiedString name_final = CopiedString( StringRange( name, ext ) ); + fullpath = string_new_concat( path, name ); + AddUnvPak( name_final.c_str(), fullpath, true ); + string_release( fullpath, string_length( fullpath ) ); + } + } else { + for ( Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i ) + { + fullpath = string_new_concat( path, i->c_str() ); + InitPakFile( archiveModules, fullpath ); + string_release( fullpath, string_length( fullpath ) ); + } + for ( Archives::iterator i = archives.begin(); i != archives.end(); ++i ) + { + fullpath = string_new_concat( path, i->c_str() ); + InitPakFile( archiveModules, fullpath ); + string_release( fullpath, string_length( fullpath ) ); + } } - } - else - { - globalErrorStream() << "vfs directory not found: " << path << "\n"; - } - } + } + else + { + globalErrorStream() << "vfs directory not found: " << path << "\n"; + } + } } // 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 Shutdown() -{ - for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) - { - (*i).archive->release(); - } - g_archives.clear(); +void Shutdown(){ + for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i ) + { + ( *i ).archive->release(); + } + g_archives.clear(); - g_numDirs = 0; - g_numForbiddenDirs = 0; + g_numDirs = 0; + g_numForbiddenDirs = 0; + + g_pakfile_paths.clear(); + g_loaded_unv_paks.clear(); } #define VFS_SEARCH_PAK 0x1 #define VFS_SEARCH_DIR 0x2 -int GetFileCount (const char *filename, int flag) -{ - int count = 0; - char fixed[PATH_MAX+1]; +int GetFileCount( const char *filename, int flag ){ + int count = 0; + char fixed[PATH_MAX + 1]; - strncpy(fixed, filename, PATH_MAX); - fixed[PATH_MAX] = '\0'; - FixDOSName (fixed); + strncpy( fixed, filename, PATH_MAX ); + fixed[PATH_MAX] = '\0'; + FixDOSName( fixed ); - if(!flag) - flag = VFS_SEARCH_PAK | VFS_SEARCH_DIR; + if ( !flag ) { + flag = VFS_SEARCH_PAK | VFS_SEARCH_DIR; + } - 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; - } - } + 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; + } + } + } - return count; + return 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; - } - } +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; + } + } - return 0; + 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; - } - } +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 0; + return 0; } // 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]; +std::size_t LoadFile( const char *filename, void **bufferptr, int index ){ + char fixed[PATH_MAX + 1]; - strncpy (fixed, filename, PATH_MAX); - fixed[PATH_MAX] = '\0'; - FixDOSName (fixed); + strncpy( fixed, filename, PATH_MAX ); + fixed[PATH_MAX] = '\0'; + FixDOSName( fixed ); - ArchiveFile* file = OpenFile(fixed); - - if(file != 0) - { - *bufferptr = malloc (file->size()+1); - // we need to end the buffer with a 0 - ((char*) (*bufferptr))[file->size()] = 0; + ArchiveFile* file = OpenFile( fixed ); - std::size_t length = file->getInputStream().read((InputStream::byte_type*)*bufferptr, file->size()); - file->release(); - return length; - } + if ( file != 0 ) { + *bufferptr = malloc( file->size() + 1 ); + // we need to end the buffer with a 0 + ( (char*) ( *bufferptr ) )[file->size()] = 0; - *bufferptr = 0; - return 0; + std::size_t length = file->getInputStream().read( (InputStream::byte_type*)*bufferptr, file->size() ); + file->release(); + return length; + } + + *bufferptr = 0; + return 0; } -void FreeFile (void *p) -{ - free(p); +void FreeFile( void *p ){ + free( p ); } -GSList* GetFileList (const char *dir, const char *ext, std::size_t depth) -{ - return GetListInternal (dir, ext, false, depth); +GSList* GetFileList( const char *dir, const char *ext, std::size_t depth ){ + return GetListInternal( dir, ext, false, depth ); } -GSList* GetDirList (const char *dir, std::size_t depth) -{ - return GetListInternal (dir, 0, true, depth); +GSList* GetDirList( const char *dir, std::size_t depth ){ + return GetListInternal( dir, 0, true, depth ); } -void ClearFileDirList (GSList **lst) -{ - while (*lst) - { - g_free ((*lst)->data); - *lst = g_slist_remove (*lst, (*lst)->data); - } -} - -const char* FindFile(const char* relative) -{ - for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) - { - if((*i).archive->containsFile(relative)) - { - return (*i).name.c_str(); - } - } +void ClearFileDirList( GSList **lst ){ + while ( *lst ) + { + g_free( ( *lst )->data ); + *lst = g_slist_remove( *lst, ( *lst )->data ); + } +} + +const char* FindFile( const char* relative ){ + for ( archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i ) + { + if ( ( *i ).archive->containsFile( relative ) ) { + return ( *i ).name.c_str(); + } + } - return ""; + return ""; } -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(); - } +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(); + } + } + } - return best; + return best; } class Quake3FileSystem : public VirtualFileSystem { public: - void initDirectory(const char *path) - { - InitDirectory(path, FileSystemQ3API_getArchiveModules()); - } - void initialise() - { - globalOutputStream() << "filesystem initialised\n"; - g_observers.realise(); - } - void shutdown() - { - g_observers.unrealise(); - globalOutputStream() << "filesystem shutdown\n"; - Shutdown(); - } - - 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); - } - - void forEachDirectory(const char* basedir, const FileNameCallback& callback, std::size_t depth) - { - GSList* list = GetDirList(basedir, depth); - - for(GSList* i = list; i != 0; i = g_slist_next(i)) - { - callback(reinterpret_cast((*i).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((*i).data); - if(extension_equal(path_get_extension(name), extension)) - { - callback(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); - } - - void attach(ModuleObserver& observer) - { - g_observers.attach(observer); - } - void detach(ModuleObserver& observer) - { - g_observers.detach(observer); - } - - 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(); - - 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(); - } +void initDirectory( const char *path ){ + InitDirectory( path, FileSystemQ3API_getArchiveModules() ); +} +void initialise(){ + if ( IsUnvanquished() ) { + const char* pakname; + g_loaded_unv_paks.clear(); + + pakname = GetLatestVersionOfUnvPak( "tex-common" ); + if ( pakname ) LoadPakWithDeps( pakname ); + + pakname = GetLatestVersionOfUnvPak( "radiant" ); + if ( pakname ) LoadPakWithDeps( pakname ); + + pakname = GetCurrentMapPakName(); + if ( pakname && !string_empty( pakname ) ) { + LoadPakWithDeps( pakname ); + } + + g_pakfile_paths.clear(); + g_loaded_unv_paks.clear(); + } + + globalOutputStream() << "filesystem initialised\n"; + g_observers.realise(); +} +void shutdown(){ + g_observers.unrealise(); + globalOutputStream() << "filesystem shutdown\n"; + Shutdown(); +} + +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 ); +} + +void forEachDirectory( const char* basedir, const FileNameCallback& callback, std::size_t depth ){ + GSList* list = GetDirList( basedir, depth ); + + for ( GSList* i = list; i != 0; i = g_slist_next( i ) ) + { + callback( reinterpret_cast( ( *i ).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( ( *i ).data ); + if ( extension_equal( path_get_extension( name ), extension ) ) { + callback( 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 ); +} + +void attach( ModuleObserver& observer ){ + g_observers.attach( observer ); +} +void detach( ModuleObserver& observer ){ + g_observers.detach( observer ); +} + +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(); + } + + 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(); + } +} }; + Quake3FileSystem g_Quake3FileSystem; -void FileSystem_Init() -{ +VirtualFileSystem& GetFileSystem(){ + return g_Quake3FileSystem; } -void FileSystem_Shutdown() -{ +void FileSystem_Init(){ } -VirtualFileSystem& GetFileSystem() -{ - return g_Quake3FileSystem; +void FileSystem_Shutdown(){ }