]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
build: disallow use of VLAs
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index 269490744ab7aacb7fdc3e6a250701f2546de8bb..2b51ec910905be9261e3f7c7ad5d743569f59c01 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
@@ -321,6 +385,7 @@ typedef struct pack_s
        int ignorecase;  ///< PK3 ignores case
        int numfiles;
        qbool vpack;
+       qbool dlcache;
        packfile_t *files;
 } pack_t;
 //@}
@@ -384,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)"};
 
 
@@ -437,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 [] =
@@ -450,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
@@ -495,7 +561,7 @@ Unload the Zlib DLL
 static void PK3_CloseLibrary (void)
 {
 #ifndef LINK_TO_ZLIB
-       Sys_UnloadLibrary (&zlib_dll);
+       Sys_FreeLibrary (&zlib_dll);
 #endif
 }
 
@@ -535,7 +601,7 @@ static qbool PK3_OpenLibrary (void)
                return true;
 
        // Load the DLL
-       return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
+       return Sys_LoadDependency (dllnames, &zlib_dll, zlibfuncs);
 #endif
 }
 
@@ -682,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
@@ -699,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;
@@ -743,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;
        }
@@ -925,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
@@ -944,7 +1026,6 @@ static void FS_mkdir (const char *path)
        }
 }
 
-
 /*
 ============
 FS_CreatePath
@@ -1118,7 +1199,7 @@ FS_AddPack_Fullpath
  * plain directories.
  *
  */
-static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbool *already_loaded, qbool 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 +1219,11 @@ static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbo
        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);
@@ -1194,18 +1275,19 @@ static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbo
                        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 +1314,7 @@ FS_AddPack
  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
  * plain directories.
  */
-qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_dirs)
+qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
 {
        char fullpath[MAX_OSPATH];
        int index;
@@ -1251,7 +1333,7 @@ qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_di
 
        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);
 }
 
 
@@ -1280,16 +1362,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);
                }
        }
 
@@ -1329,6 +1412,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 +1424,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 +1481,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)
@@ -1467,13 +1596,16 @@ void FS_Rescan (void)
        // 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));
 
@@ -1563,7 +1695,7 @@ qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool compl
                }
        }
 
-       Host_SaveConfig();
+       Host_SaveConfig(CONFIGFILENAME);
 
        fs_numgamedirs = numgamedirs;
        for (i = 0;i < fs_numgamedirs;i++)
@@ -1574,15 +1706,15 @@ qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool compl
 
        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");
+       Cbuf_InsertText(cmd_local, "\nloadconfig\nvid_restart\n\n");
 
        return true;
 }
@@ -1694,7 +1826,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 +1859,7 @@ static void FS_ListGameDirs(void)
                        continue;
                if(!*info)
                        continue;
-               stringlistappend(&list2, list.strings[i]); 
+               stringlistappend(&list2, list.strings[i]);
        }
        stringlistfreecontents(&list);
 
@@ -1803,13 +1935,15 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
        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];
 
@@ -1823,24 +1957,27 @@ static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t use
                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 +1986,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 +2002,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();
@@ -1965,6 +2097,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)");
@@ -2218,9 +2351,9 @@ 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)
@@ -2233,6 +2366,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])
@@ -2284,10 +2420,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);
@@ -2507,6 +2644,20 @@ int FS_CheckNastyPath (const char *path, qbool 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 = '/';
+}
 
 /*
 ====================
@@ -2654,7 +2805,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;
@@ -3532,8 +3683,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;
@@ -3886,7 +4039,7 @@ 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);
        if (!sp) {
@@ -4039,7 +4192,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");
@@ -4066,7 +4219,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;
 }
 
@@ -4133,7 +4286,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;
@@ -4149,7 +4302,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)
                {
@@ -4177,6 +4330,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;
 }