/*
-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:
//
#include "vfs.h"
+#include "globaldefs.h"
#include <stdio.h>
#include <stdlib.h>
-#include <glib/gslist.h>
-#include <glib/gdir.h>
-#include <glib/gstrfuncs.h>
+#include <glib.h>
#include "qerplugin.h"
#include "idatastream.h"
#include "iarchive.h"
-ArchiveModules& FileSystemQ3API_getArchiveModules();
+
+ArchiveModules &FileSystemQ3API_getArchiveModules();
+
#include "ifilesystem.h"
#include "generic/callback.h"
#include "stream/stringstream.h"
#include "os/path.h"
#include "moduleobservers.h"
+#include "filematch.h"
+#include "dpkdeps.h"
-#define VFS_MAXDIRS 64
+const int VFS_MAXDIRS = 64;
-#if defined(WIN32)
+#if GDEF_OS_WINDOWS
#define PATH_MAX 260
#endif
// =============================================================================
// Global variables
-Archive* OpenArchive(const char* name);
+Archive *OpenArchive(const char *name);
-struct archive_entry_t
-{
- CopiedString name;
- Archive* archive;
- bool is_pakfile;
+struct archive_entry_t {
+ CopiedString name;
+ Archive *archive;
+ bool is_pakfile;
};
#include <list>
+#include <map>
typedef std::list<archive_entry_t> archives_t;
static archives_t g_archives;
-static char g_strDirs[VFS_MAXDIRS][PATH_MAX+1];
-static int g_numDirs;
-static 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)
+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, "/");
+ 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)
+static void FixDOSName(char *src)
{
- if (src == 0 || strchr(src, '\\') == 0)
- return;
+ 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)
+const _QERArchiveTable *GetArchiveTable(ArchiveModules &archiveModules, const char *ext)
{
- StringOutputStream tmp(16);
- tmp << LowerCase(ext);
- return archiveModules.findModule(tmp.c_str());
+ StringOutputStream tmp(16);
+ tmp << LowerCase(ext);
+ return archiveModules.findModule(tmp.c_str());
}
-static void InitPakFile (ArchiveModules& archiveModules, const char *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;
-
- entry.archive = table->m_pfnOpenArchive(filename);
- entry.is_pakfile = true;
- g_archives.push_back(entry);
- globalOutputStream() << " pak file: " << filename << "\n";
- }
+ const _QERArchiveTable *table = GetArchiveTable(archiveModules, path_get_extension(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";
+
+ return entry.archive;
+ }
+
+ return 0;
}
-inline void pathlist_prepend_unique(GSList*& pathlist, char* 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);
- }
+ 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;
+class DirectoryListVisitor : public Archive::Visitor {
+ GSList *&m_matches;
+ const char *m_directory;
public:
- DirectoryListVisitor(GSList*& matches, const char* directory)
- : m_matches(matches), m_directory(directory)
- {}
- void visit(const char* name)
- {
- const char* subname = path_make_relative(name, m_directory);
- if(subname != name)
+ DirectoryListVisitor(GSList *&matches, const char *directory)
+ : m_matches(matches), m_directory(directory)
+ {}
+
+ void visit(const char *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);
- }
- }
+ 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;
+class FileListVisitor : public Archive::Visitor {
+ GSList *&m_matches;
+ const char *m_directory;
+ const char *m_extension;
public:
- FileListVisitor(GSList*& matches, const char* directory, const char* extension)
- : m_matches(matches), m_directory(directory), m_extension(extension)
- {}
- void visit(const char* name)
- {
- const char* subname = path_make_relative(name, m_directory);
- if(subname != name)
+ FileListVisitor(GSList *&matches, const char *directory, const char *extension)
+ : m_matches(matches), m_directory(directory), m_extension(extension)
+ {}
+
+ void visit(const char *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));
+ 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)
+
+static GSList *GetListInternal(const char *refdir, const char *ext, bool directories, std::size_t depth)
{
- GSList* files = 0;
+ GSList *files = 0;
- ASSERT_MESSAGE(refdir[strlen(refdir) - 1] == '/', "search path does not end in '/'");
+ 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);
+ 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');
- }
- return c;
+ if (c >= 'a' && c <= 'z') {
+ return c - ('a' - 'A');
+ }
+ 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)
+ 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)
- {
- return -1; // a < b
- }
- if (c1 > c2)
- {
- return 1; // a > b
- }
- if(c1 == 0)
- {
- return 0; // a == b
+ for (;;) {
+ int c1 = ascii_to_upper(*a++);
+ int c2 = ascii_to_upper(*b++);
+
+ if (c1 < c2) {
+ return -1; // a < b
+ }
+ if (c1 > c2) {
+ return 1; // a > b
+ }
+ if (c1 == 0) {
+ return 0; // a == b
+ }
}
- }
}
// Arnout: note - sort pakfiles in reverse order. This ensures that
// last one in the list that is).
//!\todo Analyse the code in rtcw/q3 to see which order it sorts pak files.
-class PakLess
-{
+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<CopiedString, PakLess> 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<CopiedString, pakfile_path_t> PakfilePathsKV;
+typedef std::map<CopiedString, pakfile_path_t> 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)
+{
+ Archive *arc;
+ ArchiveTextFile *depsFile;
+
+ if (pakname == NULL) {
+ // load DEPS from game pack
+ StringOutputStream baseDirectory(256);
+ const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
+ baseDirectory << GlobalRadiant().getGameToolsPath() << basegame << '/';
+ arc = AddDpkDir(baseDirectory.c_str());
+ depsFile = arc->openTextFile("DEPS");
+ } else {
+ 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;
+ }
+
+ 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);
+
+ depsFile = arc->openTextFile("DEPS");
+ }
+
+ if (!depsFile) {
+ return;
+ }
+
+ {
+ TextLinesInputStream<TextInputStream> istream = depsFile->getInputStream();
+
+ CopiedString line;
+ char *p_name;
+ char *p_version;
+ while (line = istream.readLine(), string_length(line.c_str())) {
+ if (!DpkReadDepsLine(line.c_str(), &p_name, &p_version)) { continue; }
+ if (!p_version) {
+ const char *p_latest = GetLatestDpkPakVersion(p_name);
+ if (p_latest) { LoadDpkPakWithDeps(p_latest); }
+ } else {
+ int len = string_length(p_name) + string_length(p_version) + 1;
+ char *p_pakname = string_new(len);
+ sprintf(p_pakname, "%s_%s", p_name, p_version);
+ LoadDpkPakWithDeps(p_pakname);
+ string_release(p_pakname, len);
+ }
+ string_release(p_name, string_length(p_name));
+ if (p_version) { string_release(p_version, string_length(p_version)); }
+ }
+ }
+
+ depsFile->release();
+}
+
+// end for Daemon DPK vfs
+
// =============================================================================
// Global functions
// reads all pak files from a dir
-void InitDirectory(const char* directory, ArchiveModules& archiveModules)
+void InitDirectory(const char *directory, ArchiveModules &archiveModules)
{
- 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";
-
- 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;
-
- 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)
- {
- continue;
- }
- if(!string_empty(override_prefix) && strncmp(name, override_prefix, strlen(override_prefix)) == 0)
- {
- archivesOverride.insert(name);
- continue;
- }
-
- archives.insert(name);
- }
-
- 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);
- }
- }
- else
+ 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++;
+
{
- globalErrorStream() << "vfs directory not found: " << path << "\n";
+ 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;
+ }
+
+ 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);
+ }
+
+ g_dir_close(dir);
+
+ // add the entries to the vfs
+ char *fullpath;
+ if (is_dpk_vfs) {
+ for (Archives::iterator i = archives.begin(); i != archives.end(); ++i) {
+ const char *name = i->c_str();
+ const char *ext = strrchr(name, '.');
+ if (!string_compare_nocase_upper(ext, ".dpk")) {
+ CopiedString name_final = CopiedString(StringRange(name, ext));
+ fullpath = string_new_concat(path, name);
+ AddDpkPak(name_final.c_str(), fullpath, true);
+ string_release(fullpath, string_length(fullpath));
+ }
+ }
+ }
+ 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";
+ }
}
- }
}
// frees all memory that we allocated
// (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();
+ 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_pakfile_paths.clear();
+ g_loaded_dpk_paks.clear();
}
-#define VFS_SEARCH_PAK 0x1
-#define VFS_SEARCH_DIR 0x2
+const int VFS_SEARCH_PAK = 0x1;
+const int VFS_SEARCH_DIR = 0x2;
-int GetFileCount (const char *filename, int flag)
+int GetFileCount(const char *filename, int flag)
{
- int count = 0;
- char fixed[PATH_MAX+1];
+ 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)
+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;
+ 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)
+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;
+ 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)
+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);
-
- 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;
-
- std::size_t length = file->getInputStream().read((InputStream::byte_type*)*bufferptr, file->size());
- file->release();
- return length;
- }
-
- *bufferptr = 0;
- return 0;
+ char fixed[PATH_MAX + 1];
+
+ 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;
+
+ 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)
+void FreeFile(void *p)
{
- free(p);
+ free(p);
}
-GSList* GetFileList (const char *dir, const char *ext, std::size_t depth)
+GSList *GetFileList(const char *dir, const char *ext, std::size_t depth)
{
- return GetListInternal (dir, ext, false, depth);
+ return GetListInternal(dir, ext, false, depth);
}
-GSList* GetDirList (const char *dir, std::size_t depth)
+GSList *GetDirList(const char *dir, std::size_t depth)
{
- return GetListInternal (dir, 0, true, depth);
+ return GetListInternal(dir, 0, true, depth);
}
-void ClearFileDirList (GSList **lst)
+void ClearFileDirList(GSList **lst)
{
- while (*lst)
- {
- g_free ((*lst)->data);
- *lst = g_slist_remove (*lst, (*lst)->data);
- }
+ while (*lst) {
+ g_free((*lst)->data);
+ *lst = g_slist_remove(*lst, (*lst)->data);
+ }
}
-
-const char* FindFile(const char* relative)
+
+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();
+ 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 *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;
+ 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;
}
-class Quake3FileSystem : public VirtualFileSystem
-{
+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))
+ void initDirectory(const char *path)
{
- callback(reinterpret_cast<const char*>((*i).data));
+ InitDirectory(path, FileSystemQ3API_getArchiveModules());
}
- ClearFileDirList(&list);
- }
- void forEachFile(const char* basedir, const char* extension, const FileNameCallback& callback, std::size_t depth)
- {
- GSList* list = GetFileList(basedir, extension, depth);
+ void initialise()
+ {
+ load();
+ globalOutputStream() << "filesystem initialised\n";
+ g_observers.realise();
+ }
- for(GSList* i = list; i != 0; i = g_slist_next(i))
+ void load()
{
- const char* name = reinterpret_cast<const char*>((*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)
+ ArchiveModules &archiveModules = FileSystemQ3API_getArchiveModules();
+ bool is_dpk_vfs = GetArchiveTable(archiveModules, "dpk");
+
+ if (is_dpk_vfs) {
+ const char *pakname;
+ g_loaded_dpk_paks.clear();
+
+ // Load DEPS from game pack
+ LoadDpkPakWithDeps(NULL);
+
+ // 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()
{
- if(pakonly && !(*i).is_pakfile)
- continue;
+ // like shutdown() but does not unrealise (keep map etc.)
+ Shutdown();
+ }
- if(path_equal((*i).name.c_str(), archiveName))
- return (*i).archive;
+ void refresh()
+ {
+ // like initialise() but does not realise (keep map etc.)
+ load();
+ globalOutputStream() << "filesystem refreshed\n";
}
- 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)
+ void shutdown()
{
- if(pakonly && !(*i).is_pakfile)
- continue;
+ g_observers.unrealise();
+ globalOutputStream() << "filesystem shutdown\n";
+ Shutdown();
+ }
- callback((*i).name.c_str());
+ int getFileCount(const char *filename, int flags)
+ {
+ return GetFileCount(filename, flags);
}
- if (reverse)
- g_archives.reverse();
- }
+ 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<const char *>((*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<const char *>((*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()
+void FileSystem_Shutdown()
{
- return g_Quake3FileSystem;
}