X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fnetradiant.git;a=blobdiff_plain;f=plugins%2Fvfspk3%2Fvfs.cpp;h=bb3b5dbf4fc7c8cc237efdf54da6671272aa6872;hp=86a2ac85f4cead9909bd9a033ac73e255fb1f4ed;hb=826fce3cdc0bce78d15c04a2eb0a373dc1b251a3;hpb=bfc8a12a6b315ae261101a34db8ba1b682c67bb7 diff --git a/plugins/vfspk3/vfs.cpp b/plugins/vfspk3/vfs.cpp index 86a2ac85..bb3b5dbf 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" @@ -61,11 +59,12 @@ ArchiveModules& FileSystemQ3API_getArchiveModules(); #include "os/path.h" #include "moduleobservers.h" #include "filematch.h" +#include "dpkdeps.h" #define VFS_MAXDIRS 64 -#if defined(WIN32) +#if defined( WIN32 ) #define PATH_MAX 260 #endif @@ -76,204 +75,193 @@ 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() ); } +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; -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)); + entry.archive = table->m_pfnOpenArchive( filename ); + entry.is_pakfile = true; + g_archives.push_back( entry ); + globalOutputStream() << " pak file: " << filename << "\n"; - if(table != 0) - { - archive_entry_t entry; - entry.name = filename; + return entry.archive; + } - entry.archive = table->m_pfnOpenArchive(filename); - entry.is_pakfile = true; - g_archives.push_back(entry); - globalOutputStream() << " pak file: " << filename << "\n"; - } + 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 '/'" ); - files = g_slist_reverse(files); + 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 ); + } + } - return files; + files = g_slist_reverse( 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 @@ -286,470 +274,664 @@ 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 Daemon DPK vfs + +Archive* AddDpkDir( const char* fullpath ){ + return AddPk3Dir( fullpath ); +} + +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 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 ) ); +} + +// 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 ); + + 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; +} + +// 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 ); + } + } + + string_release( mapname, mapnamelen ); + return result; + +} + +// prevent loading duplicates or circular references +static Archives g_loaded_dpk_paks; + +// 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; + + 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 = AddDpkDir( i->second.fullpath.c_str() ); + } + g_loaded_dpk_paks.insert( pakname ); + + ArchiveTextFile* depsFile = arc->openTextFile( "DEPS" ); + if ( !depsFile ) return; + + { + TextLinesInputStream 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 ) ); + } + } + + depsFile->release(); +} + +// end for Daemon DPK vfs + // ============================================================================= // 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) - { - 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++; - - { - 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) - { - 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, '.'); - - 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"; + + 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 == 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; } - if(!string_empty(override_prefix) && strncmp(name, override_prefix, strlen(override_prefix)) == 0) - { - archivesOverride.insert(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 + 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 ); + } + } + 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); + 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 ) ); + } + } } - 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); + 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 ) ); + } + } + 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 ) ); + } + } } - } - 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_dpk_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 ); - 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; + if ( file != 0 ) { + *bufferptr = malloc( file->size() + 1 ); + // we need to end the buffer with a 0 + ( (char*) ( *bufferptr ) )[file->size()] = 0; - std::size_t length = file->getInputStream().read((InputStream::byte_type*)*bufferptr, file->size()); - file->release(); - return length; - } + std::size_t length = file->getInputStream().read( (InputStream::byte_type*)*bufferptr, file->size() ); + file->release(); + return length; + } - *bufferptr = 0; - return 0; + *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(){ + load(); + globalOutputStream() << "filesystem initialised\n"; + g_observers.realise(); +} + +void load(){ + ArchiveModules& archiveModules = FileSystemQ3API_getArchiveModules(); + bool is_dpk_vfs = GetArchiveTable( archiveModules, "dpk" ); + + if ( is_dpk_vfs ) { + const char* pakname; + g_loaded_dpk_paks.clear(); + + pakname = GetLatestDpkPakVersion( "tex-common" ); + if (pakname != NULL) { + LoadDpkPakWithDeps( pakname ); + } + + // 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(); + } +} + +void clear() { + // like shutdown() but does not unrealise (keep map etc.) + Shutdown(); +} + +void refresh(){ + // like initialise() but does not realise (keep map etc.) + load(); + globalOutputStream() << "filesystem refreshed\n"; +} + +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(){ }