]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
physics: fix and refactor unsticking
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index 4b98e32ea4b96d4e4158d333b5270a949380e667..e2afcf052ca4b89b8227b7fd1f4a30b277aefea3 100644 (file)
--- a/fs.c
+++ b/fs.c
 #include "fs.h"
 #include "wad.h"
 
+#ifdef WIN32
+#include "utf8lib.h"
+#endif
+
 // Win32 requires us to add O_BINARY, but the other OSes don't have it
 #ifndef O_BINARY
 # define O_BINARY 0
@@ -105,9 +109,69 @@ static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
 }
 #endif
 
+
+/* This code seems to have originally been written with the assumption that
+ * read(..., n) returns n on success. This is not the case (refer to
+ * <https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html>).
+ * Ditto for write.
+ */
+
+/*
+====================
+ReadAll
+
+Read exactly length bytes from fd into buf. If end of file is reached,
+the number of bytes read is returned. If an error occurred, that error
+is returned. Note that if an error is returned, any previously read
+data is lost.
+====================
+*/
+static fs_offset_t ReadAll(const filedesc_t fd, void *const buf, const size_t length)
+{
+       char *const p = (char *)buf;
+       size_t cursor = 0;
+       do
+       {
+               const fs_offset_t result = FILEDESC_READ(fd, p + cursor, length - cursor);
+               if (result < 0) // Error
+                       return result;
+               if (result == 0) // EOF
+                       break;
+               cursor += result;
+       } while (cursor < length);
+       return cursor;
+}
+
+/*
+====================
+WriteAll
+
+Write exactly length bytes to fd from buf.
+If an error occurred, that error is returned.
+====================
+*/
+static fs_offset_t WriteAll(const filedesc_t fd, const void *const buf, const size_t length)
+{
+       const char *const p = (const char *)buf;
+       size_t cursor = 0;
+       do
+       {
+               const fs_offset_t result = FILEDESC_WRITE(fd, p + cursor, length - cursor);
+               if (result < 0) // Error
+                       return result;
+               cursor += result;
+       } while (cursor < length);
+       return cursor;
+}
+
+#undef FILEDESC_READ
+#define FILEDESC_READ ReadAll
+#undef FILEDESC_WRITE
+#define FILEDESC_WRITE WriteAll
+
 /** \page fs File System
 
-All of Quake's data access is through a hierchal file system, but the contents
+All of Quake's data access is through a hierarchical file system, the contents
 of the file system can be transparently merged from several sources.
 
 The "base directory" is the path to the directory holding the quake.exe and
@@ -119,9 +183,8 @@ directory is only used during filesystem initialization.
 The "game directory" is the first tree on the search path and directory that
 all generated files (savegames, screenshots, demos, config files) will be
 saved to.  This can be overridden with the "-game" command line parameter.
-The game directory can never be changed while quake is executing.  This is a
-precaution against having a malicious server instruct clients to write files
-over areas they shouldn't.
+If multiple "-game <gamedir>" args are passed the last one is the "primary"
+and files will be saved there, the rest are read-only.
 
 */
 
@@ -318,9 +381,10 @@ typedef struct pack_s
        char filename [MAX_OSPATH];
        char shortname [MAX_QPATH];
        filedesc_t handle;
-       int ignorecase;  ///< PK3 ignores case
+       qbool ignorecase;  ///< PK3 ignores case
        int numfiles;
-       qboolean vpack;
+       qbool vpack;
+       qbool dlcache;
        packfile_t *files;
 } pack_t;
 //@}
@@ -347,7 +411,7 @@ void FS_Dir_f(cmd_state_t *cmd);
 void FS_Ls_f(cmd_state_t *cmd);
 void FS_Which_f(cmd_state_t *cmd);
 
-static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
+static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet);
 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
                                                                        fs_offset_t offset, fs_offset_t packsize,
                                                                        fs_offset_t realsize, int flags);
@@ -382,9 +446,10 @@ char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
 gamedir_t *fs_all_gamedirs = NULL;
 int fs_all_gamedirs_count = 0;
 
-cvar_t scr_screenshot_name = {CVAR_CLIENT | CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
-cvar_t fs_empty_files_in_pack_mark_deletions = {CVAR_CLIENT | CVAR_SERVER, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
-cvar_t cvar_fs_gamedir = {CVAR_CLIENT | CVAR_SERVER | CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
+cvar_t scr_screenshot_name = {CF_CLIENT | CF_PERSISTENT, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
+cvar_t fs_empty_files_in_pack_mark_deletions = {CF_CLIENT | CF_SERVER, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
+cvar_t fs_unload_dlcache = {CF_CLIENT, "fs_unload_dlcache", "1", "if enabled, unload dlcache's loaded pak/pk3 files when changing server and/or map WARNING: disabling unloading can cause servers to override assets of other servers, \"memory leaking\" by dlcache assets never unloading and many more issues"};
+cvar_t cvar_fs_gamedir = {CF_CLIENT | CF_SERVER | CF_READONLY | CF_PERSISTENT, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
 
 
 /*
@@ -437,10 +502,10 @@ static dllhandle_t zlib_dll = NULL;
 #endif
 
 #ifdef WIN32
-static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
+static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath);
 static dllfunction_t shfolderfuncs[] =
 {
-       {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
+       {"SHGetFolderPathW", (void **) &qSHGetFolderPath},
        {NULL, NULL}
 };
 static const char* shfolderdllnames [] =
@@ -450,7 +515,7 @@ static const char* shfolderdllnames [] =
 };
 static dllhandle_t shfolder_dll = NULL;
 
-const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; 
+const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
 #define qREFKNOWNFOLDERID const GUID *
 #define qKF_FLAG_CREATE 0x8000
 #define qKF_FLAG_NO_ALIAS 0x1000
@@ -495,7 +560,7 @@ Unload the Zlib DLL
 static void PK3_CloseLibrary (void)
 {
 #ifndef LINK_TO_ZLIB
-       Sys_UnloadLibrary (&zlib_dll);
+       Sys_FreeLibrary (&zlib_dll);
 #endif
 }
 
@@ -507,7 +572,7 @@ PK3_OpenLibrary
 Try to load the Zlib DLL
 ====================
 */
-static qboolean PK3_OpenLibrary (void)
+static qbool PK3_OpenLibrary (void)
 {
 #ifdef LINK_TO_ZLIB
        return true;
@@ -535,7 +600,7 @@ static qboolean PK3_OpenLibrary (void)
                return true;
 
        // Load the DLL
-       return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
+       return Sys_LoadDependency (dllnames, &zlib_dll, zlibfuncs);
 #endif
 }
 
@@ -546,7 +611,7 @@ FS_HasZlib
 See if zlib is available
 ====================
 */
-qboolean FS_HasZlib(void)
+qbool FS_HasZlib(void)
 {
 #ifdef LINK_TO_ZLIB
        return true;
@@ -563,7 +628,7 @@ PK3_GetEndOfCentralDir
 Extract the end of the central directory from a PK3 package
 ====================
 */
-static qboolean PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
+static qbool PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
 {
        fs_offset_t filesize, maxsize;
        unsigned char *buffer, *ptr;
@@ -682,7 +747,7 @@ static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                        return -1;
                }
 
-               namesize = BuffLittleShort (&ptr[28]);  // filename length
+               namesize = (unsigned short)BuffLittleShort (&ptr[28]);  // filename length
 
                // Check encryption, compression, and attributes
                // 1st uint8  : general purpose bit flag
@@ -699,7 +764,7 @@ static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
                {
                        // Still enough bytes for the name?
-                       if (namesize < 0 || remaining < namesize || namesize >= (int)sizeof (*pack->files))
+                       if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
                        {
                                Mem_Free (central_dir);
                                return -1;
@@ -743,7 +808,7 @@ static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                // Skip the name, additionnal field, and comment
                // 1er uint16 : extra field length
                // 2eme uint16 : file comment length
-               count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
+               count = namesize + (unsigned short)BuffLittleShort (&ptr[30]) + (unsigned short)BuffLittleShort (&ptr[32]);
                ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
                remaining -= count;
        }
@@ -762,7 +827,7 @@ FS_LoadPackPK3
 Create a package entry associated with a PK3 file
 ====================
 */
-static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qboolean silent)
+static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qbool silent)
 {
        pk3_endOfCentralDir_t eocd;
        pack_t *pack;
@@ -798,7 +863,7 @@ static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle
        // Create a package structure in memory
        pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
        pack->ignorecase = true; // PK3 ignores case
-       strlcpy (pack->filename, packfile, sizeof (pack->filename));
+       dp_strlcpy (pack->filename, packfile, sizeof (pack->filename));
        pack->handle = packhandle;
        pack->numfiles = eocd.nbentries;
        pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
@@ -816,7 +881,7 @@ static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle
        return pack;
 }
 
-static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking);
+static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking);
 static pack_t *FS_LoadPackPK3 (const char *packfile)
 {
        filedesc_t packhandle;
@@ -834,7 +899,7 @@ PK3_GetTrueFileOffset
 Find where the true file data offset is
 ====================
 */
-static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
+static qbool PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
 {
        unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
        fs_offset_t count;
@@ -916,7 +981,7 @@ static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
        memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
        pack->numfiles++;
 
-       strlcpy (pfile->name, name, sizeof (pfile->name));
+       dp_strlcpy (pfile->name, name, sizeof (pfile->name));
        pfile->offset = offset;
        pfile->packsize = packsize;
        pfile->realsize = realsize;
@@ -925,14 +990,30 @@ static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
        return pfile;
 }
 
+#if WIN32
+#define WSTRBUF 4096
+static inline int wstrlen(wchar *wstr)
+{
+       int len = 0;
+       while (wstr[len] != 0 && len < WSTRBUF)
+               ++len;
+       return len;
+}
+#define widen(str, wstr) fromwtf8(str, strlen(str), wstr, WSTRBUF)
+#define narrow(wstr, str) towtf8(wstr, wstrlen(wstr), str, WSTRBUF)
+#endif
 
 static void FS_mkdir (const char *path)
 {
-       if(COM_CheckParm("-readonly"))
+#if WIN32
+       wchar pathw[WSTRBUF] = {0};
+#endif
+       if(Sys_CheckParm("-readonly"))
                return;
 
 #if WIN32
-       if (_mkdir (path) == -1)
+       widen(path, pathw);
+       if (_wmkdir (pathw) == -1)
 #else
        if (mkdir (path, 0777) == -1)
 #endif
@@ -944,7 +1025,6 @@ static void FS_mkdir (const char *path)
        }
 }
 
-
 /*
 ============
 FS_CreatePath
@@ -1059,7 +1139,7 @@ static pack_t *FS_LoadPackPAK (const char *packfile)
 
        pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
        pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
-       strlcpy (pack->filename, packfile, sizeof (pack->filename));
+       dp_strlcpy (pack->filename, packfile, sizeof (pack->filename));
        pack->handle = packhandle;
        pack->numfiles = 0;
        pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
@@ -1095,7 +1175,7 @@ static pack_t *FS_LoadPackVirtual (const char *dirname)
        pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
        pack->vpack = true;
        pack->ignorecase = false;
-       strlcpy (pack->filename, dirname, sizeof(pack->filename));
+       dp_strlcpy (pack->filename, dirname, sizeof(pack->filename));
        pack->handle = FILEDESC_INVALID;
        pack->numfiles = -1;
        pack->files = NULL;
@@ -1118,7 +1198,7 @@ FS_AddPack_Fullpath
  * plain directories.
  *
  */
-static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
+static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
 {
        searchpath_t *search;
        pack_t *pak = NULL;
@@ -1138,11 +1218,11 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
        if(already_loaded)
                *already_loaded = false;
 
-       if(!strcasecmp(ext, "pk3dir"))
+       if(!strcasecmp(ext, "pk3dir") || !strcasecmp(ext, "dpkdir"))
                pak = FS_LoadPackVirtual (pakfile);
        else if(!strcasecmp(ext, "pak"))
                pak = FS_LoadPackPAK (pakfile);
-       else if(!strcasecmp(ext, "pk3"))
+       else if(!strcasecmp(ext, "pk3") || !strcasecmp(ext, "dpk"))
                pak = FS_LoadPackPK3 (pakfile);
        else if(!strcasecmp(ext, "obb")) // android apk expansion
                pak = FS_LoadPackPK3 (pakfile);
@@ -1151,7 +1231,7 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
 
        if(pak)
        {
-               strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
+               dp_strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
 
                //Con_DPrintf("  Registered pack with short name %s\n", shortname);
                if(keep_plain_dirs)
@@ -1194,18 +1274,19 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
                        fs_searchpaths = search;
                }
                search->pack = pak;
+               search->pack->dlcache = dlcache;
                if(pak->vpack)
                {
                        dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
-                       // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
+                       // if shortname ends with "pk3dir" or "dpkdir", strip that suffix to make it just "pk3" or "dpk"
                        // same goes for the name inside the pack structure
                        l = strlen(pak->shortname);
                        if(l >= 7)
-                               if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
+                               if(!strcasecmp(pak->shortname + l - 7, ".pk3dir") || !strcasecmp(pak->shortname + l - 7, ".dpkdir"))
                                        pak->shortname[l - 3] = 0;
                        l = strlen(pak->filename);
                        if(l >= 7)
-                               if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
+                               if(!strcasecmp(pak->filename + l - 7, ".pk3dir") || !strcasecmp(pak->filename + l - 7, ".dpkdir"))
                                        pak->filename[l - 3] = 0;
                }
                return true;
@@ -1232,7 +1313,7 @@ FS_AddPack
  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
  * plain directories.
  */
-qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
+qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
 {
        char fullpath[MAX_OSPATH];
        int index;
@@ -1242,7 +1323,7 @@ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep
                *already_loaded = false;
 
        // then find the real name...
-       search = FS_FindFile(pakfile, &index, true);
+       search = FS_FindFile(pakfile, &index, NULL, true);
        if(!search || search->pack)
        {
                Con_Printf("could not find pak \"%s\"\n", pakfile);
@@ -1251,7 +1332,7 @@ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep
 
        dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
 
-       return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
+       return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs, dlcache);
 }
 
 
@@ -1269,7 +1350,7 @@ static void FS_AddGameDirectory (const char *dir)
        stringlist_t list;
        searchpath_t *search;
 
-       strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
+       dp_strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
 
        stringlistinit(&list);
        listdirectory(&list, "", dir);
@@ -1280,16 +1361,17 @@ static void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
                }
        }
 
        // add any PK3 package in the directory
        for (i = 0;i < list.numstrings;i++)
        {
-               if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
+               if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")
+                       || !strcasecmp(FS_FileExtension(list.strings[i]), "dpk") || !strcasecmp(FS_FileExtension(list.strings[i]), "dpkdir"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
                }
        }
 
@@ -1298,7 +1380,7 @@ static void FS_AddGameDirectory (const char *dir)
        // Add the directory to the search path
        // (unpacked files have the priority over packed files)
        search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
-       strlcpy (search->filename, dir, sizeof (search->filename));
+       dp_strlcpy (search->filename, dir, sizeof (search->filename));
        search->next = fs_searchpaths;
        fs_searchpaths = search;
 }
@@ -1329,6 +1411,10 @@ const char *FS_FileExtension (const char *in)
 {
        const char *separator, *backslash, *colon, *dot;
 
+       dot = strrchr(in, '.');
+       if (dot == NULL)
+               return "";
+
        separator = strrchr(in, '/');
        backslash = strrchr(in, '\\');
        if (!separator || separator < backslash)
@@ -1337,8 +1423,7 @@ const char *FS_FileExtension (const char *in)
        if (!separator || separator < colon)
                separator = colon;
 
-       dot = strrchr(in, '.');
-       if (dot == NULL || (separator && (dot < separator)))
+       if (separator && (dot < separator))
                return "";
 
        return dot + 1;
@@ -1395,6 +1480,49 @@ static void FS_ClearSearchPath (void)
        }
 }
 
+/*
+================
+FS_UnloadPacks_dlcache
+
+Like FS_ClearSearchPath() but unloads only the packs loaded from dlcache
+so we don't need to use a full FS_Rescan() to prevent
+content from the previous server and/or map from interfering with the next
+================
+*/
+void FS_UnloadPacks_dlcache(void)
+{
+       searchpath_t *search = fs_searchpaths, *searchprev = fs_searchpaths, *searchnext;
+
+       if (!fs_unload_dlcache.integer)
+               return;
+
+       while (search)
+       {
+               searchnext = search->next;
+               if (search->pack && search->pack->dlcache)
+               {
+                       Con_DPrintf("Unloading pack: %s\n", search->pack->shortname);
+
+                       // remove it from the search path list
+                       if (search == fs_searchpaths)
+                               fs_searchpaths = search->next;
+                       else
+                               searchprev->next = search->next;
+
+                       // close the file
+                       FILEDESC_CLOSE(search->pack->handle);
+                       // free any memory associated with it
+                       if (search->pack->files)
+                               Mem_Free(search->pack->files);
+                       Mem_Free(search->pack);
+                       Mem_Free(search);
+               }
+               else
+                       searchprev = search;
+               search = searchnext;
+       }
+}
+
 static void FS_AddSelfPack(void)
 {
        if(fs_selfpack)
@@ -1416,8 +1544,8 @@ FS_Rescan
 void FS_Rescan (void)
 {
        int i;
-       qboolean fs_modified = false;
-       qboolean reset = false;
+       qbool fs_modified = false;
+       qbool reset = false;
        char gamedirbuf[MAX_INPUTLINE];
        char vabuf[1024];
 
@@ -1434,9 +1562,9 @@ void FS_Rescan (void)
        FS_AddGameHierarchy (gamedirname1);
        // update the com_modname (used for server info)
        if (gamedirname2 && gamedirname2[0])
-               strlcpy(com_modname, gamedirname2, sizeof(com_modname));
+               dp_strlcpy(com_modname, gamedirname2, sizeof(com_modname));
        else
-               strlcpy(com_modname, gamedirname1, sizeof(com_modname));
+               dp_strlcpy(com_modname, gamedirname1, sizeof(com_modname));
 
        // add the game-specific path, if any
        // (only used for mission packs and the like, which should set fs_modified)
@@ -1456,29 +1584,32 @@ void FS_Rescan (void)
                fs_modified = true;
                FS_AddGameHierarchy (fs_gamedirs[i]);
                // update the com_modname (used server info)
-               strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
+               dp_strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
                if(i)
-                       strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
+                       dp_strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
                else
-                       strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
+                       dp_strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
        }
        Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
 
        // add back the selfpack as new first item
        FS_AddSelfPack();
 
-       // set the default screenshot name to either the mod name or the
-       // gamemode screenshot name
-       if (strcmp(com_modname, gamedirname1))
-               Cvar_SetQuick (&scr_screenshot_name, com_modname);
-       else
-               Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
-       
-       if((i = COM_CheckParm("-modname")) && i < sys.argc - 1)
-               strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
+       if (cls.state != ca_dedicated)
+       {
+               // set the default screenshot name to either the mod name or the
+               // gamemode screenshot name
+               if (strcmp(com_modname, gamedirname1))
+                       Cvar_SetQuick (&scr_screenshot_name, com_modname);
+               else
+                       Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
+       }
+
+       if((i = Sys_CheckParm("-modname")) && i < sys.argc - 1)
+               dp_strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
 
        // If "-condebug" is in the command line, remove the previous log file
-       if (COM_CheckParm ("-condebug") != 0)
+       if (Sys_CheckParm ("-condebug") != 0)
                unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
 
        // look for the pop.lmp file and set registered to true if it is found
@@ -1523,8 +1654,8 @@ static void FS_Rescan_f(cmd_state_t *cmd)
 FS_ChangeGameDirs
 ================
 */
-extern qboolean vid_opened;
-qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
+extern qbool vid_opened;
+qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool complain, qbool failmissing)
 {
        int i;
        const char *p;
@@ -1563,26 +1694,26 @@ qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean
                }
        }
 
-       Host_SaveConfig();
+       Host_SaveConfig(CONFIGFILENAME);
 
        fs_numgamedirs = numgamedirs;
        for (i = 0;i < fs_numgamedirs;i++)
-               strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
+               dp_strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
 
        // reinitialize filesystem to detect the new paks
        FS_Rescan();
 
        if (cls.demoplayback)
        {
-               CL_Disconnect_f(&cmd_client);
+               CL_Disconnect();
                cls.demonum = 0;
        }
 
        // unload all sounds so they will be reloaded from the new files as needed
-       S_UnloadAllSounds_f(&cmd_client);
+       S_UnloadAllSounds_f(cmd_local);
 
-       // restart the video subsystem after the config is executed
-       Cbuf_InsertText(&cmd_client, "\nloadconfig\nvid_restart\n\n");
+       // reset everything that can be and reload configs
+       Cbuf_InsertText(cmd_local, "\nloadconfig\n");
 
        return true;
 }
@@ -1615,7 +1746,7 @@ static void FS_GameDir_f(cmd_state_t *cmd)
        }
 
        for (i = 0;i < numgamedirs;i++)
-               strlcpy(gamedirs[i], Cmd_Argv(cmd, i+1), sizeof(gamedirs[i]));
+               dp_strlcpy(gamedirs[i], Cmd_Argv(cmd, i+1), sizeof(gamedirs[i]));
 
        if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
        {
@@ -1632,7 +1763,7 @@ static void FS_GameDir_f(cmd_state_t *cmd)
 
 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
 {
-       qboolean success;
+       qbool success;
        qfile_t *f;
        stringlist_t list;
        fs_offset_t n;
@@ -1694,7 +1825,7 @@ const char *FS_CheckGameDir(const char *gamedir)
        ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
        if(ret)
                return ret;
-       
+
        return fs_checkgamedir_missing;
 }
 
@@ -1727,7 +1858,7 @@ static void FS_ListGameDirs(void)
                        continue;
                if(!*info)
                        continue;
-               stringlistappend(&list2, list.strings[i]); 
+               stringlistappend(&list2, list.strings[i]);
        }
        stringlistfreecontents(&list);
 
@@ -1742,8 +1873,8 @@ static void FS_ListGameDirs(void)
                        continue;
                if(!*info)
                        continue;
-               strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
-               strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
+               dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
+               dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
                ++fs_all_gamedirs_count;
        }
 }
@@ -1774,7 +1905,7 @@ static void COM_InsertFlags(const char *buf) {
                if(i > args_left)
                        break;
                q = (char *)Mem_Alloc(fs_mempool, sz);
-               strlcpy(q, com_token, sz);
+               dp_strlcpy(q, com_token, sz);
                new_argv[i] = q;
                ++i;
        }
@@ -1797,19 +1928,21 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
        {
                // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
                // fs_userdir stores configurations to the Documents folder of the app
-               strlcpy(userdir, "../Documents/", MAX_OSPATH);
+               dp_strlcpy(userdir, "../Documents/", MAX_OSPATH);
                return 1;
        }
        return -1;
 
 #elif defined(WIN32)
-       char *homedir;
+       char homedir[WSTRBUF];
+       wchar *homedirw;
 #if _MSC_VER >= 1400
-       size_t homedirlen;
+       size_t homedirwlen;
 #endif
-       TCHAR mydocsdir[MAX_PATH + 1];
+       wchar_t mydocsdirw[WSTRBUF];
+       char mydocsdir[WSTRBUF];
        wchar_t *savedgamesdirw;
-       char savedgamesdir[MAX_OSPATH];
+       char savedgamesdir[WSTRBUF] = {0};
        int fd;
        char vabuf[1024];
 
@@ -1819,28 +1952,31 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
        default:
                return -1;
        case USERDIRMODE_NOHOME:
-               strlcpy(userdir, fs_basedir, userdirsize);
+               dp_strlcpy(userdir, fs_basedir, userdirsize);
                break;
        case USERDIRMODE_MYGAMES:
                if (!shfolder_dll)
-                       Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
+                       Sys_LoadDependency(shfolderdllnames, &shfolder_dll, shfolderfuncs);
                mydocsdir[0] = 0;
-               if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
+               if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdirw) == S_OK)
                {
+                       narrow(mydocsdirw, mydocsdir);
                        dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
                        break;
                }
 #if _MSC_VER >= 1400
-               _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
-               if(homedir)
+               _wdupenv_s(&homedirw, &homedirwlen, L"USERPROFILE");
+               narrow(homedirw, homedir);
+               if(homedir[0])
                {
                        dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
-                       free(homedir);
+                       free(homedirw);
                        break;
                }
 #else
-               homedir = getenv("USERPROFILE");
-               if(homedir)
+               homedirw = _wgetenv(L"USERPROFILE");
+               narrow(homedirw, homedir);
+               if(homedir[0])
                {
                        dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
                        break;
@@ -1849,9 +1985,9 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
                return -1;
        case USERDIRMODE_SAVEDGAMES:
                if (!shell32_dll)
-                       Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
+                       Sys_LoadDependency(shell32dllnames, &shell32_dll, shell32funcs);
                if (!ole32_dll)
-                       Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
+                       Sys_LoadDependency(ole32dllnames, &ole32_dll, ole32funcs);
                if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
                {
                        savedgamesdir[0] = 0;
@@ -1865,12 +2001,7 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
 */
                        if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
                        {
-                               memset(savedgamesdir, 0, sizeof(savedgamesdir));
-#if _MSC_VER >= 1400
-                               wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
-#else
-                               wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
-#endif
+                               narrow(savedgamesdirw, savedgamesdir);
                                qCoTaskMemFree(savedgamesdirw);
                        }
                        qCoUninitialize();
@@ -1892,7 +2023,7 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
        default:
                return -1;
        case USERDIRMODE_NOHOME:
-               strlcpy(userdir, fs_basedir, userdirsize);
+               dp_strlcpy(userdir, fs_basedir, userdirsize);
                break;
        case USERDIRMODE_HOME:
                homedir = getenv("HOME");
@@ -1965,14 +2096,15 @@ void FS_Init_Commands(void)
 {
        Cvar_RegisterVariable (&scr_screenshot_name);
        Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
+       Cvar_RegisterVariable (&fs_unload_dlcache);
        Cvar_RegisterVariable (&cvar_fs_gamedir);
 
-       Cmd_AddCommand(CMD_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
-       Cmd_AddCommand(CMD_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
-       Cmd_AddCommand(CMD_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
-       Cmd_AddCommand(CMD_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
-       Cmd_AddCommand(CMD_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
-       Cmd_AddCommand(CMD_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
+       Cmd_AddCommand(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list, can take multiple arguments which shouldn't include the base directory, the last gamedir is the \"primary\" and files will be saved there (example usage: gamedir ctf id1)");
+       Cmd_AddCommand(CF_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
+       Cmd_AddCommand(CF_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
+       Cmd_AddCommand(CF_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
+       Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
+       Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
 }
 
 static void FS_Init_Dir (void)
@@ -1987,10 +2119,10 @@ static void FS_Init_Dir (void)
        // -basedir <path>
        // Overrides the system supplied base directory (under GAMENAME)
 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
-       i = COM_CheckParm ("-basedir");
+       i = Sys_CheckParm ("-basedir");
        if (i && i < sys.argc-1)
        {
-               strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
+               dp_strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
                i = (int)strlen (fs_basedir);
                if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
                        fs_basedir[i-1] = 0;
@@ -1999,7 +2131,7 @@ static void FS_Init_Dir (void)
        {
 // If the base directory is explicitly defined by the compilation process
 #ifdef DP_FS_BASEDIR
-               strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
+               dp_strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
 #elif defined(__ANDROID__)
                dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
 #elif defined(MACOSX)
@@ -2007,7 +2139,7 @@ static void FS_Init_Dir (void)
                if (strstr(sys.argv[0], ".app/"))
                {
                        char *split;
-                       strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
+                       dp_strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
                        split = strstr(fs_basedir, ".app/");
                        if (split)
                        {
@@ -2019,7 +2151,7 @@ static void FS_Init_Dir (void)
                                if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
                                {
                                        // found gamedir inside Resources, use it
-                                       strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
+                                       dp_strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
                                }
                                else
                                {
@@ -2031,6 +2163,9 @@ static void FS_Init_Dir (void)
                                }
                        }
                }
+#else
+               // use the working directory
+               getcwd(fs_basedir, sizeof(fs_basedir));
 #endif
        }
 
@@ -2038,17 +2173,17 @@ static void FS_Init_Dir (void)
        memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
        // add a path separator to the end of the basedir if it lacks one
        if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
-               strlcat(fs_basedir, "/", sizeof(fs_basedir));
+               dp_strlcat(fs_basedir, "/", sizeof(fs_basedir));
 
        // Add the personal game directory
-       if((i = COM_CheckParm("-userdir")) && i < sys.argc - 1)
+       if((i = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
                dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", sys.argv[i+1]);
-       else if (COM_CheckParm("-nohome"))
+       else if (Sys_CheckParm("-nohome"))
                *fs_userdir = 0; // user wants roaming installation, no userdir
        else
        {
 #ifdef DP_FS_USERDIR
-               strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
+               dp_strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
 #else
                int dirmode;
                int highestuserdirmode = USERDIRMODE_COUNT - 1;
@@ -2060,9 +2195,9 @@ static void FS_Init_Dir (void)
                        preferreduserdirmode = USERDIRMODE_NOHOME;
 # endif
                // check what limitations the user wants to impose
-               if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
-               if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
-               if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
+               if (Sys_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
+               if (Sys_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
+               if (Sys_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
                // gather the status of the possible userdirs
                for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
                {
@@ -2126,7 +2261,7 @@ static void FS_Init_Dir (void)
                        if(p == fs_checkgamedir_missing)
                                Con_Printf(CON_WARN "WARNING: -game %s%s/ not found!\n", fs_basedir, sys.argv[i]);
                        // add the gamedir to the list of active gamedirs
-                       strlcpy (fs_gamedirs[fs_numgamedirs], sys.argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
+                       dp_strlcpy (fs_gamedirs[fs_numgamedirs], sys.argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
                        fs_numgamedirs++;
                }
        }
@@ -2148,7 +2283,7 @@ void FS_Init_SelfPack (void)
        char *buf;
 
        // Load darkplaces.opt from the FS.
-       if (!COM_CheckParm("-noopt"))
+       if (!Sys_CheckParm("-noopt"))
        {
                buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
                if(buf)
@@ -2160,13 +2295,13 @@ void FS_Init_SelfPack (void)
 
 #ifndef USE_RWOPS
        // Provide the SelfPack.
-       if (!COM_CheckParm("-noselfpack") && sys.selffd >= 0)
+       if (!Sys_CheckParm("-noselfpack") && sys.selffd >= 0)
        {
                fs_selfpack = FS_LoadPackPK3FromFD(sys.argv[0], sys.selffd, true);
                if(fs_selfpack)
                {
                        FS_AddSelfPack();
-                       if (!COM_CheckParm("-noopt"))
+                       if (!Sys_CheckParm("-noopt"))
                        {
                                buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
                                if(buf)
@@ -2218,21 +2353,24 @@ void FS_Shutdown (void)
        PK3_CloseLibrary ();
 
 #ifdef WIN32
-       Sys_UnloadLibrary (&shfolder_dll);
-       Sys_UnloadLibrary (&shell32_dll);
-       Sys_UnloadLibrary (&ole32_dll);
+       Sys_FreeLibrary (&shfolder_dll);
+       Sys_FreeLibrary (&shell32_dll);
+       Sys_FreeLibrary (&ole32_dll);
 #endif
 
        if (fs_mutex)
                Thread_DestroyMutex(fs_mutex);
 }
 
-static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking)
+static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking)
 {
        filedesc_t handle = FILEDESC_INVALID;
        int mod, opt;
        unsigned int ind;
-       qboolean dolock = false;
+       qbool dolock = false;
+       #ifdef WIN32
+       wchar filepathw[WSTRBUF] = {0};
+       #endif
 
        // Parse the mode string
        switch (mode[0])
@@ -2275,7 +2413,7 @@ static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbo
        if (nonblocking)
                opt |= O_NONBLOCK;
 
-       if(COM_CheckParm("-readonly") && mod != O_RDONLY)
+       if(Sys_CheckParm("-readonly") && mod != O_RDONLY)
                return FILEDESC_INVALID;
 
 #if USE_RWOPS
@@ -2284,10 +2422,11 @@ static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbo
        handle = SDL_RWFromFile(filepath, mode);
 #else
 # ifdef WIN32
+       widen(filepath, filepathw);
 #  if _MSC_VER >= 1400
-       _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
+       _wsopen_s(&handle, filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
 #  else
-       handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
+       handle = _wsopen (filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
 #  endif
 # else
        handle = open (filepath, mod | opt, 0666);
@@ -2310,7 +2449,7 @@ static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbo
        return handle;
 }
 
-int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
+int FS_SysOpenFD(const char *filepath, const char *mode, qbool nonblocking)
 {
 #ifdef USE_RWOPS
        return -1;
@@ -2326,7 +2465,7 @@ FS_SysOpen
 Internal function used to create a qfile_t and open the relevant non-packed file on disk
 ====================
 */
-qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
+qfile_t* FS_SysOpen (const char* filepath, const char* mode, qbool nonblocking)
 {
        qfile_t* file;
 
@@ -2460,7 +2599,7 @@ Return true if the path should be rejected due to one of the following:
    or are just not a good idea for a mod to be using.
 ====================
 */
-int FS_CheckNastyPath (const char *path, qboolean isgamedir)
+int FS_CheckNastyPath (const char *path, qbool isgamedir)
 {
        // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
        if (!path[0])
@@ -2507,6 +2646,20 @@ int FS_CheckNastyPath (const char *path, qboolean isgamedir)
        return false;
 }
 
+/*
+====================
+FS_SanitizePath
+
+Sanitize path (replace non-portable characters
+with portable ones in-place, etc)
+====================
+*/
+void FS_SanitizePath(char *path)
+{
+       for (; *path; path++)
+               if (*path == '\\')
+                       *path = '/';
+}
 
 /*
 ====================
@@ -2518,7 +2671,7 @@ Return the searchpath where the file was found (or NULL)
 and the file index in the package if relevant
 ====================
 */
-static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
+static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet)
 {
        searchpath_t *search;
        pack_t *pak;
@@ -2556,15 +2709,18 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
 
                                                if (index != NULL)
                                                        *index = -1;
+                                               if (canonicalname)
+                                                       *canonicalname = NULL;
                                                return NULL;
                                        }
 
                                        if (!quiet && developer_extra.integer)
-                                               Con_DPrintf("FS_FindFile: %s in %s\n",
-                                                                       pak->files[middle].name, pak->filename);
+                                               Con_DPrintf("FS_FindFile: %s in %s\n", pak->files[middle].name, pak->filename);
 
                                        if (index != NULL)
                                                *index = middle;
+                                       if (canonicalname)
+                                               *canonicalname = pak->files[middle].name;
                                        return search;
                                }
 
@@ -2586,6 +2742,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
 
                                if (index != NULL)
                                        *index = -1;
+                               if (canonicalname)
+                                       *canonicalname = name;
                                return search;
                        }
                }
@@ -2596,6 +2754,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
 
        if (index != NULL)
                *index = -1;
+       if (canonicalname)
+               *canonicalname = NULL;
        return NULL;
 }
 
@@ -2607,12 +2767,12 @@ FS_OpenReadFile
 Look for a file in the search paths and open it in read-only mode
 ===========
 */
-static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
+static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblocking, int symlinkLevels)
 {
        searchpath_t *search;
        int pack_ind;
 
-       search = FS_FindFile (filename, &pack_ind, quiet);
+       search = FS_FindFile (filename, &pack_ind, NULL, quiet);
 
        // Not found?
        if (search == NULL)
@@ -2654,7 +2814,7 @@ static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean
                        if(count < 0)
                                return NULL;
                        linkbuf[count] = 0;
-                       
+
                        // Now combine the paths...
                        mergeslash = strrchr(filename, '/');
                        mergestart = linkbuf;
@@ -2722,7 +2882,7 @@ Open a file in the userpath. The syntax is the same as fopen
 Used for savegame scanning in menu, and all file writing.
 ====================
 */
-qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
+qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qbool quiet)
 {
        char real_path [MAX_OSPATH];
 
@@ -2749,7 +2909,7 @@ FS_OpenVirtualFile
 Open a file. The syntax is the same as fopen
 ====================
 */
-qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
+qfile_t* FS_OpenVirtualFile (const char* filepath, qbool quiet)
 {
        qfile_t *result = NULL;
        if (FS_CheckNastyPath(filepath, false))
@@ -2772,7 +2932,7 @@ FS_FileFromData
 Open a file. The syntax is the same as fopen
 ====================
 */
-qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
+qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qbool quiet)
 {
        qfile_t* file;
        file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
@@ -3324,7 +3484,7 @@ Loads full content of a qfile_t and closes it.
 Always appends a 0 byte.
 ============
 */
-static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
+static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
 {
        unsigned char *buf = NULL;
        fs_offset_t filesize = 0;
@@ -3361,7 +3521,7 @@ Filename are relative to the quake directory.
 Always appends a 0 byte.
 ============
 */
-unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
+unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
 {
        qfile_t *file = FS_OpenVirtualFile(path, quiet);
        return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
@@ -3376,7 +3536,7 @@ Filename are OS paths.
 Always appends a 0 byte.
 ============
 */
-unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
+unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
 {
        qfile_t *file = FS_SysOpen(path, "rb", false);
        return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
@@ -3390,7 +3550,7 @@ FS_WriteFile
 The filename will be prefixed by the current game directory
 ============
 */
-qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
+qbool FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
 {
        qfile_t *file;
        size_t i;
@@ -3413,7 +3573,7 @@ qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, co
        return true;
 }
 
-qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
+qbool FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
 {
        return FS_WriteFileInBlocks(filename, &data, &len, 1);
 }
@@ -3477,7 +3637,7 @@ void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
                src--;
        }
 
-       strlcat (path, extension, size_path);
+       dp_strlcat (path, extension, size_path);
 }
 
 
@@ -3493,7 +3653,7 @@ int FS_FileType (const char *filename)
        searchpath_t *search;
        char fullpath[MAX_OSPATH];
 
-       search = FS_FindFile (filename, NULL, true);
+       search = FS_FindFile (filename, NULL, NULL, true);
        if(!search)
                return FS_FILETYPE_NONE;
 
@@ -3510,11 +3670,15 @@ int FS_FileType (const char *filename)
 FS_FileExists
 
 Look for a file in the packages and in the filesystem
+Returns its canonical name (VFS path with correct capitalisation) if found, else NULL.
+If the file is found outside a pak, this will be the same pointer as passed in.
 ==================
 */
-qboolean FS_FileExists (const char *filename)
+const char *FS_FileExists (const char *filename)
 {
-       return (FS_FindFile (filename, NULL, true) != NULL);
+       const char *canonicalname;
+
+       return FS_FindFile(filename, NULL, &canonicalname, true) ? canonicalname : NULL;
 }
 
 
@@ -3532,8 +3696,10 @@ int FS_SysFileType (const char *path)
 # ifndef INVALID_FILE_ATTRIBUTES
 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
 # endif
-
-       DWORD result = GetFileAttributes(path);
+       wchar pathw[WSTRBUF] = {0};
+       DWORD result;
+       widen(path, pathw);
+       result = GetFileAttributesW(pathw);
 
        if(result == INVALID_FILE_ATTRIBUTES)
                return FS_FILETYPE_NONE;
@@ -3558,7 +3724,7 @@ int FS_SysFileType (const char *path)
 #endif
 }
 
-qboolean FS_SysFileExists (const char *path)
+qbool FS_SysFileExists (const char *path)
 {
        return FS_SysFileType (path) != FS_FILETYPE_NONE;
 }
@@ -3570,7 +3736,7 @@ FS_Search
 Allocate and fill a search structure with information on matching filenames.
 ===========
 */
-fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
+fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const char *packfile)
 {
        fssearch_t *search;
        searchpath_t *searchpath;
@@ -3578,7 +3744,8 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
        int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
        stringlist_t resultlist;
        stringlist_t dirlist;
-       const char *slash, *backslash, *colon, *separator;
+       stringlist_t matchedSet, foundSet;
+       const char *start, *slash, *backslash, *colon, *separator;
        char *basepath;
 
        for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
@@ -3612,10 +3779,15 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                {
                        // look through all the pak file elements
                        pak = searchpath->pack;
+                       if(packfile)
+                       {
+                               if(strcmp(packfile, pak->shortname))
+                                       continue;
+                       }
                        for (i = 0;i < pak->numfiles;i++)
                        {
                                char temp[MAX_OSPATH];
-                               strlcpy(temp, pak->files[i].name, sizeof(temp));
+                               dp_strlcpy(temp, pak->files[i].name, sizeof(temp));
                                while (temp[0])
                                {
                                        if (matchpattern(temp, (char *)pattern, true))
@@ -3648,8 +3820,10 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                }
                else
                {
-                       stringlist_t matchedSet, foundSet;
-                       const char *start = pattern;
+                       if(packfile)
+                               continue;
+
+                       start = pattern;
 
                        stringlistinit(&matchedSet);
                        stringlistinit(&foundSet);
@@ -3685,7 +3859,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
 
                                // 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)));
+                               dp_ustr2stp(subpattern, sizeof(subpattern), pattern, nextseparator - pattern);
                                // find the last '/' before the wildcard
                                prevseparator = strrchr( subpattern, '/' );
                                if (!prevseparator)
@@ -3694,13 +3868,13 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                                        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)));
+                               dp_ustr2stp(subpath, sizeof(subpath), start, (prevseparator - subpattern) - (start - pattern));
 
                                // for each entry in matchedSet try to open the subdirectories specified in subpath
                                for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
                                        char temp[MAX_OSPATH];
-                                       strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
-                                       strlcat( temp, subpath, sizeof(temp) );
+                                       dp_strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
+                                       dp_strlcat( temp, subpath, sizeof(temp) );
                                        listdirectory( &foundSet, searchpath->filename, temp );
                                }
                                if( dirlistindex == 0 ) {
@@ -3786,7 +3960,7 @@ static int FS_ListDirectory(const char *pattern, int oneperline)
        const char *name;
        char linebuf[MAX_INPUTLINE];
        fssearch_t *search;
-       search = FS_Search(pattern, true, true);
+       search = FS_Search(pattern, true, true, NULL);
        if (!search)
                return 0;
        numfiles = search->numfilenames;
@@ -3878,9 +4052,9 @@ void FS_Which_f(cmd_state_t *cmd)
        {
                Con_Printf("usage:\n%s <file>\n", Cmd_Argv(cmd, 0));
                return;
-       }  
+       }
        filename = Cmd_Argv(cmd, 1);
-       sp = FS_FindFile(filename, &index, true);
+       sp = FS_FindFile(filename, &index, NULL, true);
        if (!sp) {
                Con_Printf("%s isn't anywhere\n", filename);
                return;
@@ -3900,7 +4074,7 @@ void FS_Which_f(cmd_state_t *cmd)
 const char *FS_WhichPack(const char *filename)
 {
        int index;
-       searchpath_t *sp = FS_FindFile(filename, &index, true);
+       searchpath_t *sp = FS_FindFile(filename, &index, NULL, true);
        if(sp && sp->pack)
                return sp->pack->shortname;
        else if(sp)
@@ -3918,7 +4092,7 @@ Look for a proof of purchase file file in the requested package
 If it is found, this file should NOT be downloaded.
 ====================
 */
-qboolean FS_IsRegisteredQuakePack(const char *name)
+qbool FS_IsRegisteredQuakePack(const char *name)
 {
        searchpath_t *search;
        pack_t *pak;
@@ -4031,7 +4205,7 @@ unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflat
                Mem_Free(tmp);
                return NULL;
        }
-       
+
        if(qz_deflateEnd(&strm) != Z_OK)
        {
                Con_Printf("FS_Deflate: deflateEnd failed\n");
@@ -4058,7 +4232,7 @@ unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflat
 
        memcpy(out, tmp, strm.total_out);
        Mem_Free(tmp);
-       
+
        return out;
 }
 
@@ -4125,7 +4299,7 @@ unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflat
                        case Z_STREAM_END:
                        case Z_OK:
                                break;
-                               
+
                        case Z_STREAM_ERROR:
                                Con_Print("FS_Inflate: stream error!\n");
                                break;
@@ -4141,7 +4315,7 @@ unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflat
                        default:
                                Con_Print("FS_Inflate: unknown error!\n");
                                break;
-                               
+
                }
                if(ret != Z_OK && ret != Z_STREAM_END)
                {
@@ -4169,6 +4343,6 @@ unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflat
        Mem_Free(outbuf.data);
 
        *inflated_size = (size_t)outbuf.cursize;
-       
+
        return out;
 }