]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
Support file symlinks inside pk3 files. Directory symlinks, as well as symlinks with...
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index 2454bea34ec69d05ed01f0ba705b2de3fa23500a..58a0c33463ffb0fa371858497698ac80037b8443 100644 (file)
--- a/fs.c
+++ b/fs.c
 # define lseek _lseeki64
 #endif
 
+#if _MSC_VER >= 1400
+// suppress deprecated warnings
+# include <sys/stat.h>
+# include <share.h>
+# define read _read
+# define write _write
+# define close _close
+# define unlink _unlink
+# define dup _dup
+#endif
+
 /*
 
 All of Quake's data access is through a hierchal file system, but the contents
@@ -211,6 +222,8 @@ typedef struct dpackheader_s
 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
 // file compressed using the deflate algorithm
 #define PACKFILE_FLAG_DEFLATED (1 << 1)
+// file is a symbolic link
+#define PACKFILE_FLAG_SYMLINK (1 << 2)
 
 typedef struct packfile_s
 {
@@ -318,6 +331,15 @@ static dllfunction_t zlibfuncs[] =
 // Handle for Zlib DLL
 static dllhandle_t zlib_dll = NULL;
 
+#ifdef WIN32
+static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
+static dllfunction_t shfolderfuncs[] =
+{
+       {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
+       {NULL, NULL}
+};
+static dllhandle_t shfolder_dll = NULL;
+#endif
 
 /*
 ====================
@@ -366,14 +388,7 @@ qboolean PK3_OpenLibrary (void)
                return true;
 
        // Load the DLL
-       if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
-       {
-               Con_Printf ("Compressed files support disabled\n");
-               return false;
-       }
-
-       Con_Printf ("Compressed files support enabled\n");
-       return true;
+       return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
 }
 
 
@@ -488,9 +503,16 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                // Check encryption, compression, and attributes
                // 1st uint8  : general purpose bit flag
                //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
+               //
+               // LordHavoc: bit 3 would be a problem if we were scanning the archive
+               // but is not a problem in the central directory where the values are
+               // always real.
+               //
+               // bit 3 seems to always be set by the standard Mac OSX zip maker
+               //
                // 2nd uint8 : external file attributes
                //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
-               if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
+               if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
                {
                        // Still enough bytes for the name?
                        if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
@@ -518,6 +540,18 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                                offset = BuffLittleLong (&ptr[42]);
                                packsize = BuffLittleLong (&ptr[20]);
                                realsize = BuffLittleLong (&ptr[24]);
+
+                               switch(ptr[5]) // C_VERSION_MADE_BY_1
+                               {
+                                       case 3: // UNIX_
+                                       case 2: // VMS_
+                                       case 16: // BEOS_
+                                               if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
+                                                       // can't use S_ISLNK here, as this has to compile on non-UNIX too
+                                                       flags |= PACKFILE_FLAG_SYMLINK;
+                                               break;
+                               }
+
                                FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
                        }
                }
@@ -551,7 +585,11 @@ pack_t *FS_LoadPackPK3 (const char *packfile)
        pack_t *pack;
        int real_nb_files;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        packhandle = open (packfile, O_RDONLY | O_BINARY);
+#endif
        if (packhandle < 0)
                return NULL;
 
@@ -762,7 +800,11 @@ pack_t *FS_LoadPackPAK (const char *packfile)
        pack_t *pack;
        dpackfile_t *info;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        packhandle = open (packfile, O_RDONLY | O_BINARY);
+#endif
        if (packhandle < 0)
                return NULL;
        read (packhandle, (void *)&header, sizeof(header));
@@ -967,12 +1009,11 @@ void FS_AddGameDirectory (const char *dir)
        int i;
        stringlist_t list;
        searchpath_t *search;
-       char pakfile[MAX_OSPATH];
 
        strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
 
        stringlistinit(&list);
-       listdirectory(&list, dir);
+       listdirectory(&list, "", dir);
        stringlistsort(&list);
 
        // add any PAK package in the directory
@@ -980,8 +1021,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
                {
-                       dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, list.strings[i]);
-                       FS_AddPack_Fullpath(pakfile, NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
                }
        }
 
@@ -990,8 +1030,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
                {
-                       dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, list.strings[i]);
-                       FS_AddPack_Fullpath(pakfile, NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
                }
        }
 
@@ -1016,10 +1055,12 @@ void FS_AddGameHierarchy (const char *dir)
        int i;
        char userdir[MAX_QPATH];
 #ifdef WIN32
-       TCHAR appdata[MAX_PATH + 1];
-#else
-       const char *homedir;
+       TCHAR mydocsdir[MAX_PATH + 1];
+#if _MSC_VER >= 1400
+       size_t homedirlen;
 #endif
+#endif
+       char *homedir;
 
        // Add the common game directory
        FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
@@ -1028,18 +1069,53 @@ void FS_AddGameHierarchy (const char *dir)
 
        // Add the personal game directory
 #ifdef WIN32
-       if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, appdata) == S_OK)
-               dpsnprintf(userdir, sizeof(userdir), "%s/%s/", appdata, gameuserdirname);
+       if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
+       {
+               dpsnprintf(userdir, sizeof(userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
+               Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", userdir);
+       }
+       else
+       {
+               // use the environment
+#if _MSC_VER >= 1400
+               _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
+#else
+               homedir = getenv("USERPROFILE");
+#endif
+
+               if(homedir)
+               {
+                       dpsnprintf(userdir, sizeof(userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
+#if _MSC_VER >= 1400
+                       free(homedir);
+#endif
+                       Con_DPrintf("Obtained personal directory %s from environment\n", userdir);
+               }
+               else
+                       *userdir = 0; // just to make sure it hasn't been written to by SHGetFolderPath returning failure
+       }
+
+       if(!*userdir)
+               Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
 #else
        homedir = getenv ("HOME");
        if(homedir)
                dpsnprintf(userdir, sizeof(userdir), "%s/.%s/", homedir, gameuserdirname);
+
+       if(!*userdir)
+               Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
 #endif
 
+
 #ifdef WIN32
-       if(!COM_CheckParm("-appdata"))
+       if(!COM_CheckParm("-mygames"))
        {
-               int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT | O_APPEND, 0666);
+#if _MSC_VER >= 1400
+               int fd;
+               _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
+#else
+               int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
+#endif
                if(fd >= 0)
                {
                        close(fd);
@@ -1050,7 +1126,7 @@ void FS_AddGameHierarchy (const char *dir)
 
        if(COM_CheckParm("-nohome"))
                *userdir = 0;
-       
+
        if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
                dpsnprintf(userdir, sizeof(userdir), "%s/", com_argv[i+1]);
 
@@ -1253,9 +1329,6 @@ qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean
                }
        }
 
-       // halt demo playback to close the file
-       CL_Disconnect();
-
        Host_SaveConfig();
 
        fs_numgamedirs = numgamedirs;
@@ -1314,6 +1387,9 @@ void FS_GameDir_f (void)
                return;
        }
 
+       // halt demo playback to close the file
+       CL_Disconnect();
+
        FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
 }
 
@@ -1328,7 +1404,7 @@ qboolean FS_CheckGameDir(const char *gamedir)
        qboolean success;
        stringlist_t list;
        stringlistinit(&list);
-       listdirectory(&list, va("%s%s/", fs_basedir, gamedir));
+       listdirectory(&list, va("%s%s/", fs_basedir, gamedir), "");
        success = list.numstrings > 0;
        stringlistfreecontents(&list);
        return success;
@@ -1344,6 +1420,16 @@ void FS_Init (void)
 {
        int i;
 
+#ifdef WIN32
+       const char* dllnames [] =
+       {
+               "shfolder.dll",  // IE 4, or Win NT and higher
+               NULL
+       };
+       Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
+       // don't care for the result; if it fails, %USERPROFILE% will be used instead
+#endif
+
        fs_mempool = Mem_AllocPool("file management", 0, NULL);
 
        strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
@@ -1388,10 +1474,10 @@ void FS_Init (void)
                strlcat(fs_basedir, "/", sizeof(fs_basedir));
 
        if (!FS_CheckGameDir(gamedirname1))
-               Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
+               Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
 
        if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
-               Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
+               Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
 
        // -game <gamedir>
        // Adds basedir/gamedir as an override game
@@ -1406,7 +1492,7 @@ void FS_Init (void)
                        if (FS_CheckNastyPath(com_argv[i], true))
                                Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
                        if (!FS_CheckGameDir(com_argv[i]))
-                               Sys_Error("-game %s%s/ not found!\n", fs_basedir, com_argv[i]);
+                               Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
                        // add the gamedir to the list of active gamedirs
                        strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
                        fs_numgamedirs++;
@@ -1441,6 +1527,10 @@ void FS_Shutdown (void)
        //  by the OS anyway)
        FS_ClearSearchPath();
        Mem_FreePool (&fs_mempool);
+
+#ifdef WIN32
+       Sys_UnloadLibrary (&shfolder_dll);
+#endif
 }
 
 /*
@@ -1498,7 +1588,11 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean non
        memset (file, 0, sizeof (*file));
        file->ungetc = EOF;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        file->handle = open (filepath, mod | opt, 0666);
+#endif
        if (file->handle < 0)
        {
                Mem_Free (file);
@@ -1777,7 +1871,7 @@ FS_OpenReadFile
 Look for a file in the search paths and open it in read-only mode
 ===========
 */
-qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
+qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
 {
        searchpath_t *search;
        int pack_ind;
@@ -1797,6 +1891,80 @@ qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonbloc
        }
 
        // So, we found it in a package...
+
+       // Is it a PK3 symlink?
+       // TODO also handle directory symlinks by parsing the whole structure...
+       // but heck, file symlinks are good enough for now
+       if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
+       {
+               if(symlinkLevels <= 0)
+               {
+                       Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
+                       return NULL;
+               }
+               else
+               {
+                       char linkbuf[MAX_QPATH];
+                       fs_offset_t count;
+                       qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
+                       const char *mergeslash;
+                       char *mergestart;
+
+                       if(!linkfile)
+                               return NULL;
+                       count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
+                       FS_Close(linkfile);
+                       if(count < 0)
+                               return NULL;
+                       linkbuf[count] = 0;
+                       
+                       // Now combine the paths...
+                       mergeslash = strrchr(filename, '/');
+                       mergestart = linkbuf;
+                       if(!mergeslash)
+                               mergeslash = filename;
+                       while(!strncmp(mergestart, "../", 3))
+                       {
+                               mergestart += 3;
+                               while(mergeslash > filename)
+                               {
+                                       --mergeslash;
+                                       if(*mergeslash == '/')
+                                               break;
+                               }
+                       }
+                       // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
+                       if(mergeslash == filename)
+                       {
+                               // Either mergeslash == filename, then we just replace the name (done below)
+                       }
+                       else
+                       {
+                               // Or, we append the name after mergeslash;
+                               // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
+                               int spaceNeeded = mergeslash - filename + 1;
+                               int spaceRemoved = mergestart - linkbuf;
+                               if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
+                               {
+                                       Con_DPrintf("symlink: too long path rejected\n");
+                                       return NULL;
+                               }
+                               memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
+                               memcpy(linkbuf, filename, spaceNeeded);
+                               linkbuf[count - spaceRemoved + spaceNeeded] = 0;
+                               mergestart = linkbuf;
+                       }
+                       if (!quiet && developer_loading.integer)
+                               Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
+                       if(FS_CheckNastyPath (mergestart, false))
+                       {
+                               Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
+                               return NULL;
+                       }
+                       return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
+               }
+       }
+
        return FS_OpenPackedFile (search->pack, pack_ind);
 }
 
@@ -1839,7 +2007,7 @@ qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboole
        }
        // Else, we look at the various search paths and open the file in read-only mode
        else
-               return FS_OpenReadFile (filepath, quiet, nonblocking);
+               return FS_OpenReadFile (filepath, quiet, nonblocking, 16);
 }
 
 
@@ -2328,6 +2496,8 @@ unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, f
                buf[filesize] = '\0';
                FS_Read (file, buf, filesize);
                FS_Close (file);
+               if (developer_loadfile.integer)
+                       Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
        }
 
        if (filesizepointer)
@@ -2354,7 +2524,7 @@ qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
                return false;
        }
 
-       Con_DPrintf("FS_WriteFile: %s\n", filename);
+       Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
        FS_Write (file, data, len);
        FS_Close (file);
        return true;
@@ -2423,6 +2593,30 @@ void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
 }
 
 
+/*
+==================
+FS_FileType
+
+Look for a file in the packages and in the filesystem
+==================
+*/
+int FS_FileType (const char *filename)
+{
+       searchpath_t *search;
+       char fullpath[MAX_QPATH];
+
+       search = FS_FindFile (filename, NULL, true);
+       if(!search)
+               return FS_FILETYPE_NONE;
+
+       if(search->pack)
+               return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
+
+       dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
+       return FS_SysFileType(fullpath);
+}
+
+
 /*
 ==================
 FS_FileExists
@@ -2443,28 +2637,41 @@ FS_SysFileExists
 Look for a file in the filesystem only
 ==================
 */
-qboolean FS_SysFileExists (const char *path)
+int FS_SysFileType (const char *path)
 {
 #if WIN32
-       int desc;
+// Sajt - some older sdks are missing this define
+# ifndef INVALID_FILE_ATTRIBUTES
+#  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+# endif
 
-       // TODO: use another function instead, to avoid opening the file
-       desc = open (path, O_RDONLY | O_BINARY);
-       if (desc < 0)
-               return false;
+       DWORD result = GetFileAttributes(path);
 
-       close (desc);
-       return true;
+       if(result == INVALID_FILE_ATTRIBUTES)
+               return FS_FILETYPE_NONE;
+
+       if(result & FILE_ATTRIBUTE_DIRECTORY)
+               return FS_FILETYPE_DIRECTORY;
+
+       return FS_FILETYPE_FILE;
 #else
        struct stat buf;
 
        if (stat (path,&buf) == -1)
-               return false;
+               return FS_FILETYPE_NONE;
 
-       return true;
+       if(S_ISDIR(buf.st_mode))
+               return FS_FILETYPE_DIRECTORY;
+
+       return FS_FILETYPE_FILE;
 #endif
 }
 
+qboolean FS_SysFileExists (const char *path)
+{
+       return FS_SysFileType (path) != FS_FILETYPE_NONE;
+}
+
 void FS_mkdir (const char *path)
 {
 #if WIN32
@@ -2491,7 +2698,6 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
        stringlist_t dirlist;
        const char *slash, *backslash, *colon, *separator;
        char *basepath;
-       char netpath[MAX_OSPATH];
        char temp[MAX_OSPATH];
 
        for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
@@ -2538,8 +2744,8 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                                                if (resultlistindex == resultlist.numstrings)
                                                {
                                                        stringlistappend(&resultlist, temp);
-                                                       if (!quiet)
-                                                               Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
+                                                       if (!quiet && developer_loading.integer)
+                                                               Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
                                                }
                                        }
                                        // strip off one path element at a time until empty
@@ -2560,13 +2766,80 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                }
                else
                {
-                       // get a directory listing and look at each name
-                       dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
-                       stringlistinit(&dirlist);
-                       listdirectory(&dirlist, netpath);
-                       for (dirlistindex = 0;dirlistindex < dirlist.numstrings;dirlistindex++)
+                       stringlist_t matchedSet, foundSet;
+                       const char *start = pattern;
+
+                       stringlistinit(&matchedSet);
+                       stringlistinit(&foundSet);
+                       // add a first entry to the set
+                       stringlistappend(&matchedSet, "");
+                       // iterate through pattern's path
+                       while (*start)
+                       {
+                               const char *asterisk, *wildcard, *nextseparator, *prevseparator;
+                               char subpath[MAX_OSPATH];
+                               char subpattern[MAX_OSPATH];
+
+                               // find the next wildcard
+                               wildcard = strchr(start, '?');
+                               asterisk = strchr(start, '*');
+                               if (asterisk && (!wildcard || asterisk < wildcard))
+                               {
+                                       wildcard = asterisk;
+                               }
+
+                               if (wildcard)
+                               {
+                                       nextseparator = strchr( wildcard, '/' );
+                               }
+                               else
+                               {
+                                       nextseparator = NULL;
+                               }
+
+                               if( !nextseparator ) {
+                                       nextseparator = start + strlen( start );
+                               }
+
+                               // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
+                               // copy everything up except nextseperator
+                               strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
+                               // find the last '/' before the wildcard
+                               prevseparator = strrchr( subpattern, '/' );
+                               if (!prevseparator)
+                                       prevseparator = subpattern;
+                               else
+                                       prevseparator++;
+                               // copy everything from start to the previous including the '/' (before the wildcard)
+                               // everything up to start is already included in the path of matchedSet's entries
+                               strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
+
+                               // for each entry in matchedSet try to open the subdirectories specified in subpath
+                               for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
+                                       strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
+                                       strlcat( temp, subpath, sizeof(temp) );
+                                       listdirectory( &foundSet, searchpath->filename, temp );
+                               }
+                               if( dirlistindex == 0 ) {
+                                       break;
+                               }
+                               // reset the current result set
+                               stringlistfreecontents( &matchedSet );
+                               // match against the pattern
+                               for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
+                                       const char *direntry = foundSet.strings[ dirlistindex ];
+                                       if (matchpattern(direntry, subpattern, true)) {
+                                               stringlistappend( &matchedSet, direntry );
+                                       }
+                               }
+                               stringlistfreecontents( &foundSet );
+
+                               start = nextseparator;
+                       }
+
+                       for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
                        {
-                               dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirlist.strings[dirlistindex]);
+                               const char *temp = matchedSet.strings[dirlistindex];
                                if (matchpattern(temp, (char *)pattern, true))
                                {
                                        for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
@@ -2575,12 +2848,12 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                                        if (resultlistindex == resultlist.numstrings)
                                        {
                                                stringlistappend(&resultlist, temp);
-                                               if (!quiet)
-                                                       Con_DPrintf("SearchDirFile: %s\n", temp);
+                                               if (!quiet && developer_loading.integer)
+                                                       Con_Printf("SearchDirFile: %s\n", temp);
                                        }
                                }
                        }
-                       stringlistfreecontents(&dirlist);
+                       stringlistfreecontents( &matchedSet );
                }
        }