]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
server: always use correct case for map names
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index 7c5ce33c2e99163691ad8f08704496e142b65f23..bea6cbcd586ed9432ab0ebec6a8201a37254b4c5 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,6 +109,66 @@ 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
@@ -318,7 +382,7 @@ 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;
        qbool vpack;
        qbool dlcache;
@@ -348,7 +412,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, qbool 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);
@@ -385,6 +449,7 @@ int fs_all_gamedirs_count = 0;
 
 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)"};
 
 
@@ -438,10 +503,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 [] =
@@ -451,7 +516,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
@@ -683,7 +748,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
@@ -700,7 +765,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;
@@ -744,7 +809,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;
        }
@@ -799,7 +864,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));
@@ -917,7 +982,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;
@@ -926,14 +991,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 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
@@ -945,7 +1026,6 @@ static void FS_mkdir (const char *path)
        }
 }
 
-
 /*
 ============
 FS_CreatePath
@@ -1060,7 +1140,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));
@@ -1096,7 +1176,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;
@@ -1152,7 +1232,7 @@ static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbo
 
        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)
@@ -1244,7 +1324,7 @@ qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_di
                *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);
@@ -1265,13 +1345,14 @@ Sets fs_gamedir, adds the directory to the head of the path,
 then loads and adds pak1.pak pak2.pak ...
 ================
 */
-static void FS_AddGameDirectory (const char *dir)
+static void FS_AddGameDirectory (const char *dir, qbool set_fs_gamedir)
 {
        int i;
        stringlist_t list;
        searchpath_t *search;
 
-       strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
+       if (set_fs_gamedir)
+               dp_strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
 
        stringlistinit(&list);
        listdirectory(&list, "", dir);
@@ -1301,7 +1382,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;
 }
@@ -1312,14 +1393,14 @@ static void FS_AddGameDirectory (const char *dir)
 FS_AddGameHierarchy
 ================
 */
-static void FS_AddGameHierarchy (const char *dir)
+static void FS_AddGameHierarchy (const char *dir, qbool set_fs_gamedir)
 {
        char vabuf[1024];
        // Add the common game directory
-       FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
+       FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir), set_fs_gamedir);
 
        if (*fs_userdir)
-               FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
+               FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir), set_fs_gamedir);
 }
 
 
@@ -1414,6 +1495,9 @@ 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;
@@ -1477,51 +1561,56 @@ void FS_Rescan (void)
 
        // add the game-specific paths
        // gamedirname1 (typically id1)
-       FS_AddGameHierarchy (gamedirname1);
+       FS_AddGameHierarchy (gamedirname1, true);
        // 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
+       // add the secondary game-specific path, if any
        // (only used for mission packs and the like, which should set fs_modified)
        if (gamedirname2 && gamedirname2[0])
        {
                fs_modified = true;
-               FS_AddGameHierarchy (gamedirname2);
+               FS_AddGameHierarchy (gamedirname2, true);
        }
 
        // -game <gamedir>
        // Adds basedir/gamedir as an override game
        // LadyHavoc: now supports multiple -game directories
        // set the com_modname (reported in server info)
+       // bones_was_here: does NOT set fs_gamedir in FS_AddGameHierarchy()
+       // so that we still save files in the right place.
        *gamedirbuf = 0;
        for (i = 0;i < fs_numgamedirs;i++)
        {
                fs_modified = true;
-               FS_AddGameHierarchy (fs_gamedirs[i]);
+               FS_AddGameHierarchy (fs_gamedirs[i], false);
                // 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 (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)
-               strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
+               dp_strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
 
        // If "-condebug" is in the command line, remove the previous log file
        if (Sys_CheckParm ("-condebug") != 0)
@@ -1613,7 +1702,7 @@ qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool compl
 
        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();
@@ -1627,8 +1716,8 @@ qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool compl
        // unload all sounds so they will be reloaded from the new files as needed
        S_UnloadAllSounds_f(cmd_local);
 
-       // restart the video subsystem after the config is executed
-       Cbuf_InsertText(cmd_local, "\nloadconfig\nvid_restart\n\n");
+       // reset everything that can be and reload configs
+       Cbuf_InsertText(cmd_local, "\nloadconfig\n");
 
        return true;
 }
@@ -1661,7 +1750,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)
        {
@@ -1740,7 +1829,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;
 }
 
@@ -1773,7 +1862,7 @@ static void FS_ListGameDirs(void)
                        continue;
                if(!*info)
                        continue;
-               stringlistappend(&list2, list.strings[i]); 
+               stringlistappend(&list2, list.strings[i]);
        }
        stringlistfreecontents(&list);
 
@@ -1788,8 +1877,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;
        }
 }
@@ -1820,7 +1909,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;
        }
@@ -1843,19 +1932,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];
 
@@ -1865,28 +1956,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_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;
@@ -1911,12 +2005,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();
@@ -1938,7 +2027,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");
@@ -2011,6 +2100,7 @@ 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(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
@@ -2036,7 +2126,7 @@ static void FS_Init_Dir (void)
        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;
@@ -2045,7 +2135,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)
@@ -2053,7 +2143,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)
                        {
@@ -2065,7 +2155,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
                                {
@@ -2084,7 +2174,7 @@ 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 = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
@@ -2094,7 +2184,7 @@ static void FS_Init_Dir (void)
        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;
@@ -2172,7 +2262,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++;
                }
        }
@@ -2279,6 +2369,9 @@ static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbo
        int mod, opt;
        unsigned int ind;
        qbool dolock = false;
+       #ifdef WIN32
+       wchar filepathw[WSTRBUF] = {0};
+       #endif
 
        // Parse the mode string
        switch (mode[0])
@@ -2330,10 +2423,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);
@@ -2557,7 +2651,7 @@ int FS_CheckNastyPath (const char *path, qbool isgamedir)
 ====================
 FS_SanitizePath
 
-Sanitize path (replace non-portable characters 
+Sanitize path (replace non-portable characters
 with portable ones in-place, etc)
 ====================
 */
@@ -2578,7 +2672,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, qbool quiet)
+static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet)
 {
        searchpath_t *search;
        pack_t *pak;
@@ -2616,15 +2710,18 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qbool 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;
                                }
 
@@ -2646,6 +2743,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet)
 
                                if (index != NULL)
                                        *index = -1;
+                               if (canonicalname)
+                                       *canonicalname = name;
                                return search;
                        }
                }
@@ -2656,6 +2755,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet)
 
        if (index != NULL)
                *index = -1;
+       if (canonicalname)
+               *canonicalname = NULL;
        return NULL;
 }
 
@@ -2672,7 +2773,7 @@ static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblo
        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)
@@ -2714,7 +2815,7 @@ static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblo
                        if(count < 0)
                                return NULL;
                        linkbuf[count] = 0;
-                       
+
                        // Now combine the paths...
                        mergeslash = strrchr(filename, '/');
                        mergestart = linkbuf;
@@ -3537,7 +3638,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);
 }
 
 
@@ -3553,7 +3654,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;
 
@@ -3570,11 +3671,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.
 ==================
 */
-qbool 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;
 }
 
 
@@ -3592,8 +3697,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;
@@ -3681,7 +3788,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const
                        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))
@@ -3753,7 +3860,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const
 
                                // 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)
@@ -3762,13 +3869,13 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const
                                        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 ) {
@@ -3946,9 +4053,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;
@@ -3968,7 +4075,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)
@@ -4099,7 +4206,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");
@@ -4126,7 +4233,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;
 }
 
@@ -4193,7 +4300,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;
@@ -4209,7 +4316,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)
                {
@@ -4237,6 +4344,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;
 }