]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
win32: add two includes to make sure the constants for file mode and sharing are...
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index c7721ef2efe75b8c1278ec335a5b77d0a3ece7a5..93768046d58dbe73e3b2049bb121e55ae920d893 100644 (file)
--- a/fs.c
+++ b/fs.c
                Boston, MA  02111-1307, USA
 */
 
-#include "quakedef.h"
+#ifdef __APPLE__
+// include SDL for IPHONEOS code
+# include <TargetConditionals.h>
+# if TARGET_OS_IPHONE
+#  include <SDL.h>
+# endif
+#endif
 
 #include <limits.h>
 #include <fcntl.h>
 # include <direct.h>
 # include <io.h>
 # include <shlobj.h>
+# include <sys/stat.h>
+# include <share.h>
 #else
 # include <pwd.h>
 # include <sys/stat.h>
 # include <unistd.h>
 #endif
 
+#include "quakedef.h"
+#include "thread.h"
+
 #include "fs.h"
 #include "wad.h"
 
 
 // largefile support for Win32
 #ifdef WIN32
+#undef lseek
 # define lseek _lseeki64
 #endif
 
-#if _MSC_VER >= 1400
 // suppress deprecated warnings
-# include <sys/stat.h>
-# include <share.h>
+#if _MSC_VER >= 1400
 # define read _read
 # define write _write
 # define close _close
@@ -106,6 +116,19 @@ CONSTANTS
 #define ZIP_CDIR_CHUNK_BASE_SIZE       46
 #define ZIP_LOCAL_CHUNK_BASE_SIZE      30
 
+#ifdef LINK_TO_ZLIB
+#include <zlib.h>
+
+#define qz_inflate inflate
+#define qz_inflateEnd inflateEnd
+#define qz_inflateInit2_ inflateInit2_
+#define qz_inflateReset inflateReset
+#define qz_deflateInit2_ deflateInit2_
+#define qz_deflateEnd deflateEnd
+#define qz_deflate deflate
+#define Z_MEMLEVEL_DEFAULT 8
+#else
+
 // Zlib constants (from zlib.h)
 #define Z_SYNC_FLUSH   2
 #define MAX_WBITS              15
@@ -166,6 +189,7 @@ typedef struct
        unsigned long   adler;          ///< adler32 value of the uncompressed data
        unsigned long   reserved;       ///< reserved for future use
 } z_stream;
+#endif
 
 
 /// inside a package (PAK or PK3)
@@ -174,6 +198,8 @@ typedef struct
 #define QFILE_FLAG_DEFLATED (1 << 1)
 /// file is actually already loaded data
 #define QFILE_FLAG_DATA (1 << 2)
+/// real file will be removed on close
+#define QFILE_FLAG_REMOVE (1 << 3)
 
 #define FILE_BUFF_SIZE 2048
 typedef struct
@@ -201,6 +227,8 @@ struct qfile_s
        ztoolkit_t*             ztk;    ///< For zipped files.
 
        const unsigned char *data;      ///< For data files.
+
+       const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
 };
 
 
@@ -218,6 +246,7 @@ typedef struct pk3_endOfCentralDir_s
        unsigned int cdir_size;                 ///< size of the central directory
        unsigned int cdir_offset;               ///< with respect to the starting disk number
        unsigned short comment_size;
+       fs_offset_t prepended_garbage;
 } pk3_endOfCentralDir_t;
 
 
@@ -262,6 +291,7 @@ typedef struct pack_s
        int handle;
        int ignorecase;  ///< PK3 ignores case
        int numfiles;
+       qboolean vpack;
        packfile_t *files;
 } pack_t;
 //@}
@@ -303,6 +333,7 @@ VARIABLES
 */
 
 mempool_t *fs_mempool;
+void *fs_mutex = NULL;
 
 searchpath_t *fs_searchpaths = NULL;
 const char *const fs_checkgamedir_missing = "missing";
@@ -312,6 +343,7 @@ const char *const fs_checkgamedir_missing = "missing";
 char fs_userdir[MAX_OSPATH];
 char fs_gamedir[MAX_OSPATH];
 char fs_basedir[MAX_OSPATH];
+static pack_t *fs_selfpack = NULL;
 
 // list of active game directories (empty if not running a mod)
 int fs_numgamedirs = 0;
@@ -321,8 +353,9 @@ char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
 gamedir_t *fs_all_gamedirs = NULL;
 int fs_all_gamedirs_count = 0;
 
-cvar_t scr_screenshot_name = {0, "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 scr_screenshot_name = {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 = {0, "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_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
 
 
 /*
@@ -333,6 +366,7 @@ PRIVATE FUNCTIONS - PK3 HANDLING
 =============================================================================
 */
 
+#ifndef LINK_TO_ZLIB
 // Functions exported from zlib
 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
 # define ZEXPORT WINAPI
@@ -347,12 +381,14 @@ static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
+#endif
 
 #define qz_inflateInit2(strm, windowBits) \
         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
         qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
 
+#ifndef LINK_TO_ZLIB
 //        qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
 
 static dllfunction_t zlibfuncs[] =
@@ -369,6 +405,7 @@ static dllfunction_t zlibfuncs[] =
 
 /// Handle for Zlib DLL
 static dllhandle_t zlib_dll = NULL;
+#endif
 
 #ifdef WIN32
 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
@@ -377,7 +414,46 @@ static dllfunction_t shfolderfuncs[] =
        {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
        {NULL, NULL}
 };
+static const char* shfolderdllnames [] =
+{
+       "shfolder.dll",  // IE 4, or Win NT and higher
+       NULL
+};
 static dllhandle_t shfolder_dll = NULL;
+
+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
+static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
+static dllfunction_t shell32funcs[] =
+{
+       {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
+       {NULL, NULL}
+};
+static const char* shell32dllnames [] =
+{
+       "shell32.dll",  // Vista and higher
+       NULL
+};
+static dllhandle_t shell32_dll = NULL;
+
+static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
+static void (WINAPI *qCoUninitialize)(void);
+static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
+static dllfunction_t ole32funcs[] =
+{
+       {"CoInitializeEx", (void **) &qCoInitializeEx},
+       {"CoUninitialize", (void **) &qCoUninitialize},
+       {"CoTaskMemFree", (void **) &qCoTaskMemFree},
+       {NULL, NULL}
+};
+static const char* ole32dllnames [] =
+{
+       "ole32.dll", // 2000 and higher
+       NULL
+};
+static dllhandle_t ole32_dll = NULL;
 #endif
 
 /*
@@ -387,9 +463,11 @@ PK3_CloseLibrary
 Unload the Zlib DLL
 ====================
 */
-void PK3_CloseLibrary (void)
+static void PK3_CloseLibrary (void)
 {
+#ifndef LINK_TO_ZLIB
        Sys_UnloadLibrary (&zlib_dll);
+#endif
 }
 
 
@@ -400,13 +478,14 @@ PK3_OpenLibrary
 Try to load the Zlib DLL
 ====================
 */
-qboolean PK3_OpenLibrary (void)
+static qboolean PK3_OpenLibrary (void)
 {
+#ifdef LINK_TO_ZLIB
+       return true;
+#else
        const char* dllnames [] =
        {
-#if defined(WIN64)
-               "zlib64.dll",
-#elif defined(WIN32)
+#if defined(WIN32)
 # ifdef ZLIB_USES_WINAPI
                "zlibwapi.dll",
                "zlib.dll",
@@ -428,6 +507,7 @@ qboolean PK3_OpenLibrary (void)
 
        // Load the DLL
        return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
+#endif
 }
 
 /*
@@ -439,8 +519,12 @@ See if zlib is available
 */
 qboolean FS_HasZlib(void)
 {
+#ifdef LINK_TO_ZLIB
+       return true;
+#else
        PK3_OpenLibrary(); // to be safe
        return (zlib_dll != 0);
+#endif
 }
 
 /*
@@ -450,7 +534,7 @@ PK3_GetEndOfCentralDir
 Extract the end of the central directory from a PK3 package
 ====================
 */
-qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
+static qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
 {
        fs_offset_t filesize, maxsize;
        unsigned char *buffer, *ptr;
@@ -499,6 +583,8 @@ qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOf
        eocd->cdir_size = LittleLong (eocd->cdir_size);
        eocd->cdir_offset = LittleLong (eocd->cdir_offset);
        eocd->comment_size = LittleShort (eocd->comment_size);
+       eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
+       eocd->cdir_offset += eocd->prepended_garbage;
 
        Mem_Free (buffer);
 
@@ -513,7 +599,7 @@ PK3_BuildFileList
 Extract the file list from a PK3 file
 ====================
 */
-int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
+static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
 {
        unsigned char *central_dir, *ptr;
        unsigned int ind;
@@ -522,7 +608,7 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
        // Load the central directory in memory
        central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
        lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
-       if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
+       if(read (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
        {
                Mem_Free (central_dir);
                return -1;
@@ -592,9 +678,9 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                                        flags = PACKFILE_FLAG_DEFLATED;
                                else
                                        flags = 0;
-                               offset = BuffLittleLong (&ptr[42]);
-                               packsize = BuffLittleLong (&ptr[20]);
-                               realsize = BuffLittleLong (&ptr[24]);
+                               offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
+                               packsize = (unsigned int)BuffLittleLong (&ptr[20]);
+                               realsize = (unsigned int)BuffLittleLong (&ptr[24]);
 
                                switch(ptr[5]) // C_VERSION_MADE_BY_1
                                {
@@ -633,24 +719,16 @@ FS_LoadPackPK3
 Create a package entry associated with a PK3 file
 ====================
 */
-pack_t *FS_LoadPackPK3 (const char *packfile)
+static pack_t *FS_LoadPackPK3FromFD (const char *packfile, int packhandle, qboolean silent)
 {
-       int packhandle;
        pk3_endOfCentralDir_t eocd;
        pack_t *pack;
        int real_nb_files;
 
-#if _MSC_VER >= 1400
-       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
-#else
-       packhandle = open (packfile, O_RDONLY | O_BINARY);
-#endif
-       if (packhandle < 0)
-               return NULL;
-
        if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
        {
-               Con_Printf ("%s is not a PK3 file\n", packfile);
+               if(!silent)
+                       Con_Printf ("%s is not a PK3 file\n", packfile);
                close(packhandle);
                return NULL;
        }
@@ -691,9 +769,17 @@ pack_t *FS_LoadPackPK3 (const char *packfile)
                return NULL;
        }
 
-       Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
+       Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
        return pack;
 }
+static pack_t *FS_LoadPackPK3 (const char *packfile)
+{
+       int packhandle;
+       packhandle = FS_SysOpenFD (packfile, "rb", false);
+       if (packhandle < 0)
+               return NULL;
+       return FS_LoadPackPK3FromFD(packfile, packhandle, false);
+}
 
 
 /*
@@ -703,7 +789,7 @@ PK3_GetTrueFileOffset
 Find where the true file data offset is
 ====================
 */
-qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
+static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
 {
        unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
        fs_offset_t count;
@@ -822,7 +908,7 @@ FS_Path_f
 
 ============
 */
-void FS_Path_f (void)
+static void FS_Path_f (void)
 {
        searchpath_t *s;
 
@@ -830,7 +916,12 @@ void FS_Path_f (void)
        for (s=fs_searchpaths ; s ; s=s->next)
        {
                if (s->pack)
-                       Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
+               {
+                       if(s->pack->vpack)
+                               Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
+                       else
+                               Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
+               }
                else
                        Con_Printf("%s\n", s->filename);
        }
@@ -846,7 +937,7 @@ FS_LoadPackPAK
  *Loads the header and directory, adding the files at the beginning
  *of the list so they override previous pack files.
  */
-pack_t *FS_LoadPackPAK (const char *packfile)
+static pack_t *FS_LoadPackPAK (const char *packfile)
 {
        dpackheader_t header;
        int i, numpackfiles;
@@ -854,11 +945,7 @@ pack_t *FS_LoadPackPAK (const char *packfile)
        pack_t *pack;
        dpackfile_t *info;
 
-#if _MSC_VER >= 1400
-       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
-#else
-       packhandle = open (packfile, O_RDONLY | O_BINARY);
-#endif
+       packhandle = FS_SysOpenFD(packfile, "rb", false);
        if (packhandle < 0)
                return NULL;
        if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
@@ -912,15 +999,36 @@ pack_t *FS_LoadPackPAK (const char *packfile)
        // parse the directory
        for (i = 0;i < numpackfiles;i++)
        {
-               fs_offset_t offset = LittleLong (info[i].filepos);
-               fs_offset_t size = LittleLong (info[i].filelen);
+               fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
+               fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
 
                FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
        }
 
        Mem_Free(info);
 
-       Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
+       Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
+       return pack;
+}
+
+/*
+====================
+FS_LoadPackVirtual
+
+Create a package entry associated with a directory file
+====================
+*/
+static pack_t *FS_LoadPackVirtual (const char *dirname)
+{
+       pack_t *pack;
+       pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
+       pack->vpack = true;
+       pack->ignorecase = false;
+       strlcpy (pack->filename, dirname, sizeof(pack->filename));
+       pack->handle = -1;
+       pack->numfiles = -1;
+       pack->files = NULL;
+       Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
        return pack;
 }
 
@@ -944,6 +1052,7 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
        searchpath_t *search;
        pack_t *pak = NULL;
        const char *ext = FS_FileExtension(pakfile);
+       size_t l;
 
        for(search = fs_searchpaths; search; search = search->next)
        {
@@ -958,16 +1067,19 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
        if(already_loaded)
                *already_loaded = false;
 
-       if(!strcasecmp(ext, "pak"))
+       if(!strcasecmp(ext, "pk3dir"))
+               pak = FS_LoadPackVirtual (pakfile);
+       else if(!strcasecmp(ext, "pak"))
                pak = FS_LoadPackPAK (pakfile);
        else if(!strcasecmp(ext, "pk3"))
                pak = FS_LoadPackPK3 (pakfile);
        else
                Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
 
-       if (pak)
+       if(pak)
        {
                strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
+
                //Con_DPrintf("  Registered pack with short name %s\n", shortname);
                if(keep_plain_dirs)
                {
@@ -991,7 +1103,6 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
                        if(!insertion_point)
                        {
                                search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
-                               search->pack = pak;
                                search->next = fs_searchpaths;
                                fs_searchpaths = search;
                        }
@@ -999,7 +1110,6 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
                        // otherwise we want to append directly after insertion_point.
                        {
                                search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
-                               search->pack = pak;
                                search->next = insertion_point->next;
                                insertion_point->next = search;
                        }
@@ -1007,10 +1117,24 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname,
                else
                {
                        search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
-                       search->pack = pak;
                        search->next = fs_searchpaths;
                        fs_searchpaths = search;
                }
+               search->pack = pak;
+               if(pak->vpack)
+               {
+                       dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
+                       // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
+                       // same goes for the name inside the pack structure
+                       l = strlen(pak->shortname);
+                       if(l >= 7)
+                               if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
+                                       pak->shortname[l - 3] = 0;
+                       l = strlen(pak->filename);
+                       if(l >= 7)
+                               if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
+                                       pak->filename[l - 3] = 0;
+               }
                return true;
        }
        else
@@ -1037,7 +1161,7 @@ FS_AddPack
  */
 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
 {
-       char fullpath[MAX_QPATH];
+       char fullpath[MAX_OSPATH];
        int index;
        searchpath_t *search;
 
@@ -1066,7 +1190,7 @@ Sets fs_gamedir, adds the directory to the head of the path,
 then loads and adds pak1.pak pak2.pak ...
 ================
 */
-void FS_AddGameDirectory (const char *dir)
+static void FS_AddGameDirectory (const char *dir)
 {
        int i;
        stringlist_t list;
@@ -1076,7 +1200,7 @@ void FS_AddGameDirectory (const char *dir)
 
        stringlistinit(&list);
        listdirectory(&list, "", dir);
-       stringlistsort(&list);
+       stringlistsort(&list, false);
 
        // add any PAK package in the directory
        for (i = 0;i < list.numstrings;i++)
@@ -1090,7 +1214,7 @@ void FS_AddGameDirectory (const char *dir)
        // add any PK3 package in the directory
        for (i = 0;i < list.numstrings;i++)
        {
-               if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
+               if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
                {
                        FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
                }
@@ -1112,13 +1236,14 @@ void FS_AddGameDirectory (const char *dir)
 FS_AddGameHierarchy
 ================
 */
-void FS_AddGameHierarchy (const char *dir)
+static void FS_AddGameHierarchy (const char *dir)
 {
+       char vabuf[1024];
        // Add the common game directory
-       FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
+       FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
 
        if (*fs_userdir)
-               FS_AddGameDirectory(va("%s%s/", fs_userdir, dir));
+               FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
 }
 
 
@@ -1172,7 +1297,7 @@ const char *FS_FileWithoutPath (const char *in)
 FS_ClearSearchPath
 ================
 */
-void FS_ClearSearchPath (void)
+static void FS_ClearSearchPath (void)
 {
        // unload all packs and directory information, close all pack files
        // (if a qfile is still reading a pack it won't be harmed because it used
@@ -1181,19 +1306,34 @@ void FS_ClearSearchPath (void)
        {
                searchpath_t *search = fs_searchpaths;
                fs_searchpaths = search->next;
-               if (search->pack)
+               if (search->pack && search->pack != fs_selfpack)
                {
-                       // close the file
-                       close(search->pack->handle);
-                       // free any memory associated with it
-                       if (search->pack->files)
-                               Mem_Free(search->pack->files);
+                       if(!search->pack->vpack)
+                       {
+                               // close the file
+                               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);
        }
 }
 
+static void FS_AddSelfPack(void)
+{
+       if(fs_selfpack)
+       {
+               searchpath_t *search;
+               search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
+               search->next = fs_searchpaths;
+               search->pack = fs_selfpack;
+               fs_searchpaths = search;
+       }
+}
+
 
 /*
 ================
@@ -1204,18 +1344,30 @@ void FS_Rescan (void)
 {
        int i;
        qboolean fs_modified = false;
+       qboolean reset = false;
+       char gamedirbuf[MAX_INPUTLINE];
+       char vabuf[1024];
 
+       if (fs_searchpaths)
+               reset = true;
        FS_ClearSearchPath();
 
+       // automatically activate gamemode for the gamedirs specified
+       if (reset)
+               COM_ChangeGameTypeForGameDirs();
+
        // add the game-specific paths
        // gamedirname1 (typically id1)
        FS_AddGameHierarchy (gamedirname1);
        // update the com_modname (used for server info)
-       strlcpy(com_modname, gamedirname1, sizeof(com_modname));
+       if (gamedirname2 && gamedirname2[0])
+               strlcpy(com_modname, gamedirname2, sizeof(com_modname));
+       else
+               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)
-       if (gamedirname2)
+       if (gamedirname2 && gamedirname2[0])
        {
                fs_modified = true;
                FS_AddGameHierarchy (gamedirname2);
@@ -1225,13 +1377,22 @@ void FS_Rescan (void)
        // Adds basedir/gamedir as an override game
        // LordHavoc: now supports multiple -game directories
        // set the com_modname (reported in server info)
+       *gamedirbuf = 0;
        for (i = 0;i < fs_numgamedirs;i++)
        {
                fs_modified = true;
                FS_AddGameHierarchy (fs_gamedirs[i]);
                // update the com_modname (used server info)
                strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
+               if(i)
+                       strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
+               else
+                       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
@@ -1239,31 +1400,47 @@ void FS_Rescan (void)
                Cvar_SetQuick (&scr_screenshot_name, com_modname);
        else
                Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
+       
+       if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
+               strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
 
        // If "-condebug" is in the command line, remove the previous log file
        if (COM_CheckParm ("-condebug") != 0)
-               unlink (va("%s/qconsole.log", fs_gamedir));
+               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
-       if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
+       if (FS_FileExists("gfx/pop.lmp"))
+               Cvar_Set ("registered", "1");
+       switch(gamemode)
        {
-               if (fs_modified)
-                       Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
+       case GAME_NORMAL:
+       case GAME_HIPNOTIC:
+       case GAME_ROGUE:
+               if (!registered.integer)
+               {
+                       if (fs_modified)
+                               Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
+                       else
+                               Con_Print("Playing shareware version.\n");
+               }
                else
-                       Con_Print("Playing shareware version.\n");
-       }
-       else
-       {
-               Cvar_Set ("registered", "1");
-               if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
                        Con_Print("Playing registered version.\n");
+               break;
+       case GAME_STEELSTORM:
+               if (registered.integer)
+                       Con_Print("Playing registered version.\n");
+               else
+                       Con_Print("Playing shareware version.\n");
+               break;
+       default:
+               break;
        }
 
        // unload all wads so that future queries will return the new data
        W_UnloadAll();
 }
 
-void FS_Rescan_f(void)
+static void FS_Rescan_f(void)
 {
        FS_Rescan();
 }
@@ -1273,8 +1450,7 @@ void FS_Rescan_f(void)
 FS_ChangeGameDirs
 ================
 */
-extern void Host_SaveConfig (void);
-extern void Host_LoadConfig_f (void);
+extern qboolean vid_opened;
 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
 {
        int i;
@@ -1323,14 +1499,21 @@ qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean
        // reinitialize filesystem to detect the new paks
        FS_Rescan();
 
-       // exec the new config
-       Host_LoadConfig_f();
+       if (cls.demoplayback)
+       {
+               CL_Disconnect_f();
+               cls.demonum = 0;
+       }
 
        // unload all sounds so they will be reloaded from the new files as needed
        S_UnloadAllSounds_f();
 
-       // reinitialize renderer (this reloads hud/console background/etc)
-       R_Modules_Restart();
+       // close down the video subsystem, it will start up again when the config finishes...
+       VID_Stop();
+       vid_opened = false;
+
+       // restart the video subsystem after the config is executed
+       Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
 
        return true;
 }
@@ -1340,7 +1523,7 @@ qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean
 FS_GameDir_f
 ================
 */
-void FS_GameDir_f (void)
+static void FS_GameDir_f (void)
 {
        int i;
        int numgamedirs;
@@ -1378,14 +1561,13 @@ void FS_GameDir_f (void)
        FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
 }
 
-static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking);
-static const char *FS_SysCheckGameDir(const char *gamedir)
+static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
 {
-       static char buf[8192];
        qboolean success;
        qfile_t *f;
        stringlist_t list;
        fs_offset_t n;
+       char vabuf[1024];
 
        stringlistinit(&list);
        listdirectory(&list, gamedir, "");
@@ -1394,10 +1576,10 @@ static const char *FS_SysCheckGameDir(const char *gamedir)
 
        if(success)
        {
-               f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
+               f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
                if(f)
                {
-                       n = FS_Read (f, buf, sizeof(buf) - 1);
+                       n = FS_Read (f, buf, buflength - 1);
                        if(n >= 0)
                                buf[n] = 0;
                        else
@@ -1420,17 +1602,19 @@ FS_CheckGameDir
 const char *FS_CheckGameDir(const char *gamedir)
 {
        const char *ret;
+       char buf[8192];
+       char vabuf[1024];
 
        if (FS_CheckNastyPath(gamedir, true))
                return NULL;
 
-       ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
+       ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
        if(ret)
        {
                if(!*ret)
                {
                        // get description from basedir
-                       ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
+                       ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
                        if(ret)
                                return ret;
                        return "";
@@ -1438,27 +1622,28 @@ const char *FS_CheckGameDir(const char *gamedir)
                return ret;
        }
 
-       ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
+       ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
        if(ret)
                return ret;
        
        return fs_checkgamedir_missing;
 }
 
-static void FS_ListGameDirs()
+static void FS_ListGameDirs(void)
 {
        stringlist_t list, list2;
        int i, j;
        const char *info;
+       char vabuf[1024];
 
        fs_all_gamedirs_count = 0;
        if(fs_all_gamedirs)
                Mem_Free(fs_all_gamedirs);
 
        stringlistinit(&list);
-       listdirectory(&list, va("%s/", fs_basedir), "");
-       listdirectory(&list, va("%s/", fs_userdir), "");
-       stringlistsort(&list);
+       listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
+       listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
+       stringlistsort(&list, false);
 
        stringlistinit(&list2);
        for(i = 0; i < list.numstrings; ++i)
@@ -1477,7 +1662,7 @@ static void FS_ListGameDirs()
        }
        stringlistfreecontents(&list);
 
-       fs_all_gamedirs = Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
+       fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
        for(i = 0; i < list2.numstrings; ++i)
        {
                info = FS_CheckGameDir(list2.strings[i]);
@@ -1494,124 +1679,246 @@ static void FS_ListGameDirs()
        }
 }
 
+/*
+#ifdef WIN32
+#pragma comment(lib, "shell32.lib")
+#include <ShlObj.h>
+#endif
+*/
+
 /*
 ================
-FS_Init
+FS_Init_SelfPack
 ================
 */
-void FS_Init (void)
+void FS_Init_SelfPack (void)
 {
-       const char *p;
-       int i;
-#ifdef WIN32
-       TCHAR mydocsdir[MAX_PATH + 1];
-#if _MSC_VER >= 1400
-       size_t homedirlen;
-#endif
-#endif
-       char *homedir;
-
-#ifdef WIN32
-       const char* dllnames [] =
-       {
-               "shfolder.dll",  // IE 4, or Win NT and higher
-               NULL
-       };
-       Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
-       // don't care for the result; if it fails, %USERPROFILE% will be used instead
-#endif
-
+       PK3_OpenLibrary ();
        fs_mempool = Mem_AllocPool("file management", 0, NULL);
-
-       // Add the personal game directory
-       if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
+       if(com_selffd >= 0)
        {
-               dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
+               fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
+               if(fs_selfpack)
+               {
+                       char *buf, *q;
+                       const char *p;
+                       FS_AddSelfPack();
+                       buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
+                       if(buf)
+                       {
+                               const char **new_argv;
+                               int i = 0;
+                               int args_left = 256;
+                               new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
+                               if(com_argc == 0)
+                               {
+                                       new_argv[0] = "dummy";
+                                       com_argc = 1;
+                               }
+                               else
+                               {
+                                       memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc);
+                               }
+                               p = buf;
+                               while(COM_ParseToken_Console(&p))
+                               {
+                                       if(i >= args_left)
+                                               break;
+                                       q = (char *)Mem_Alloc(fs_mempool, strlen(com_token) + 1);
+                                       strlcpy(q, com_token, strlen(com_token) + 1);
+                                       new_argv[com_argc + i] = q;
+                                       ++i;
+                               }
+                               new_argv[i+com_argc] = NULL;
+                               com_argv = new_argv;
+                               com_argc = com_argc + i;
+                       }
+                       Mem_Free(buf);
+               }
        }
-       else if(COM_CheckParm("-nohome"))
+}
+
+static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
+{
+#if defined(__IPHONEOS__)
+       if (userdirmode == USERDIRMODE_HOME)
        {
-               *fs_userdir = 0;
+               // 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, maxlength, "../Documents/");
+               return 1;
        }
-       else
+       return -1;
+
+#elif defined(WIN32)
+       char *homedir;
+#if _MSC_VER >= 1400
+       size_t homedirlen;
+#endif
+       TCHAR mydocsdir[MAX_PATH + 1];
+       wchar_t *savedgamesdirw;
+       char savedgamesdir[MAX_OSPATH];
+       int fd;
+       char vabuf[1024];
+
+       userdir[0] = 0;
+       switch(userdirmode)
        {
-#ifdef WIN32
-               if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
+       default:
+               return -1;
+       case USERDIRMODE_NOHOME:
+               strlcpy(userdir, fs_basedir, userdirsize);
+               break;
+       case USERDIRMODE_MYGAMES:
+               if (!shfolder_dll)
+                       Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
+               mydocsdir[0] = 0;
+               if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
                {
-                       dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
-                       Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
+                       dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
+                       break;
                }
-               else
-               {
-                       // use the environment
 #if _MSC_VER >= 1400
-                       _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
+               _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
+               if(homedir)
+               {
+                       dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+                       free(homedir);
+                       break;
+               }
 #else
-                       homedir = getenv("USERPROFILE");
+               homedir = getenv("USERPROFILE");
+               if(homedir)
+               {
+                       dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+                       break;
+               }
 #endif
-
-                       if(homedir)
+               return -1;
+       case USERDIRMODE_SAVEDGAMES:
+               if (!shell32_dll)
+                       Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
+               if (!ole32_dll)
+                       Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
+               if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
+               {
+                       savedgamesdir[0] = 0;
+                       qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+/*
+#ifdef __cplusplus
+                       if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
+#else
+                       if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
+#endif
+*/
+                       if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
                        {
-                               dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
+                               memset(savedgamesdir, 0, sizeof(savedgamesdir));
 #if _MSC_VER >= 1400
-                               free(homedir);
+                               wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
+#else
+                               wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
 #endif
-                               Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
+                               qCoTaskMemFree(savedgamesdirw);
+                       }
+                       qCoUninitialize();
+                       if (savedgamesdir[0])
+                       {
+                               dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
+                               break;
                        }
                }
-
-               if(!*fs_userdir)
-                       Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
+               return -1;
+       }
 #else
-               homedir = getenv ("HOME");
+       int fd;
+       char *homedir;
+       char vabuf[1024];
+       userdir[0] = 0;
+       switch(userdirmode)
+       {
+       default:
+               return -1;
+       case USERDIRMODE_NOHOME:
+               strlcpy(userdir, fs_basedir, userdirsize);
+               break;
+       case USERDIRMODE_HOME:
+               homedir = getenv("HOME");
                if(homedir)
-                       dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
-
-               if(!*fs_userdir)
-                       Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
-#endif
-
-#ifdef WIN32
-               if(!COM_CheckParm("-mygames"))
                {
-#if _MSC_VER >= 1400
-                       int fd;
-                       _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
+                       dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+                       break;
+               }
+               return -1;
+       case USERDIRMODE_SAVEDGAMES:
+               homedir = getenv("HOME");
+               if(homedir)
+               {
+#ifdef MACOSX
+                       dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
 #else
-                       int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
+                       // the XDG say some files would need to go in:
+                       // XDG_CONFIG_HOME (or ~/.config/%s/)
+                       // XDG_DATA_HOME (or ~/.local/share/%s/)
+                       // XDG_CACHE_HOME (or ~/.cache/%s/)
+                       // and also search the following global locations if defined:
+                       // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
+                       // XDG_DATA_DIRS (normally /usr/share/%s/)
+                       // this would be too complicated...
+                       return -1;
 #endif
-                       if(fd >= 0)
-                       {
-                               close(fd);
-                               *fs_userdir = 0; // we have write access to the game dir, so let's use it
-                       }
+                       break;
                }
-#endif
+               return -1;
        }
+#endif
 
-       strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
 
-// If the base directory is explicitly defined by the compilation process
-#ifdef DP_FS_BASEDIR
-       strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
-#else
-       *fs_basedir = 0;
+#ifdef WIN32
+       // historical behavior...
+       if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
+               return 0; // don't bother checking if the basedir folder is writable, it's annoying...  unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case
+#endif
 
-#ifdef MACOSX
-       // FIXME: is there a better way to find the directory outside the .app?
-       if (strstr(com_argv[0], ".app/"))
+       // see if we can write to this path (note: won't create path)
+#ifdef WIN32
+       // no access() here, we must try to open the file for appending
+       fd = FS_SysOpenFD(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
+       if(fd >= 0)
+               close(fd);
+#else
+       // on Unix, we don't need to ACTUALLY attempt to open the file
+       if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
+               fd = 1;
+       else
+               fd = 0;
+#endif
+       if(fd >= 0)
        {
-               char *split;
-
-               split = strstr(com_argv[0], ".app/");
-               while (split > com_argv[0] && *split != '/')
-                       split--;
-               strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
-               fs_basedir[split - com_argv[0]] = 0;
+               return 1; // good choice - the path exists and is writable
        }
-#endif
-#endif
+       else
+       {
+               if (userdirmode == USERDIRMODE_NOHOME)
+                       return -1; // path usually already exists, we lack permissions
+               else
+                       return 0; // probably good - failed to write but maybe we need to create path
+       }
+}
 
-       PK3_OpenLibrary ();
+/*
+================
+FS_Init
+================
+*/
+void FS_Init (void)
+{
+       const char *p;
+       int i;
+
+       *fs_basedir = 0;
+       *fs_userdir = 0;
+       *fs_gamedir = 0;
 
        // -basedir <path>
        // Overrides the system supplied base directory (under GAMENAME)
@@ -1624,11 +1931,102 @@ void FS_Init (void)
                if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
                        fs_basedir[i-1] = 0;
        }
+       else
+       {
+// If the base directory is explicitly defined by the compilation process
+#ifdef DP_FS_BASEDIR
+               strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
+#elif defined(MACOSX)
+               // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
+               if (strstr(com_argv[0], ".app/"))
+               {
+                       char *split;
+                       strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
+                       split = strstr(fs_basedir, ".app/");
+                       if (split)
+                       {
+                               struct stat statresult;
+                               char vabuf[1024];
+                               // truncate to just after the .app/
+                               split[5] = 0;
+                               // see if gamedir exists in Resources
+                               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));
+                               }
+                               else
+                               {
+                                       // no gamedir found in Resources, gamedir is probably
+                                       // outside the .app, remove .app part of path
+                                       while (split > fs_basedir && *split != '/')
+                                               split--;
+                                       *split = 0;
+                               }
+                       }
+               }
+#endif
+       }
 
+       // make sure the appending of a path separator won't create an unterminated string
+       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));
 
+       // Add the personal game directory
+       if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
+               dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
+       else if (COM_CheckParm("-nohome"))
+               *fs_userdir = 0; // user wants roaming installation, no userdir
+       else
+       {
+               int dirmode;
+               int highestuserdirmode = USERDIRMODE_COUNT - 1;
+               int preferreduserdirmode = USERDIRMODE_COUNT - 1;
+               int userdirstatus[USERDIRMODE_COUNT];
+#ifdef WIN32
+               // historical behavior...
+               if (!strcmp(gamedirname1, "id1"))
+                       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;
+               // gather the status of the possible userdirs
+               for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
+               {
+                       userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
+                       if (userdirstatus[dirmode] == 1)
+                               Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
+                       else if (userdirstatus[dirmode] == 0)
+                               Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
+                       else
+                               Con_DPrintf("userdir %i (not applicable)\n", dirmode);
+               }
+               // some games may prefer writing to basedir, but if write fails we
+               // have to search for a real userdir...
+               if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
+                       preferreduserdirmode = highestuserdirmode;
+               // check for an existing userdir and continue using it if possible...
+               for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
+                       if (userdirstatus[dirmode] == 1)
+                               break;
+               // if no existing userdir found, make a new one...
+               if (dirmode == 0 && preferreduserdirmode > 0)
+                       for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
+                               if (userdirstatus[dirmode] >= 0)
+                                       break;
+               // and finally, we picked one...
+               FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
+               Con_DPrintf("userdir %i is the winner\n", dirmode);
+       }
+
+       // if userdir equal to basedir, clear it to avoid confusion later
+       if (!strcmp(fs_basedir, fs_userdir))
+               fs_userdir[0] = 0;
+
        FS_ListGameDirs();
 
        p = FS_CheckGameDir(gamedirname1);
@@ -1665,12 +2063,16 @@ void FS_Init (void)
 
        // generate the searchpath
        FS_Rescan();
+
+       if (Thread_HasThreads())
+               fs_mutex = Thread_CreateMutex();
 }
 
 void FS_Init_Commands(void)
 {
        Cvar_RegisterVariable (&scr_screenshot_name);
        Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
+       Cvar_RegisterVariable (&cvar_fs_gamedir);
 
        Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
        Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
@@ -1692,24 +2094,24 @@ void FS_Shutdown (void)
        //  by the OS anyway)
        FS_ClearSearchPath();
        Mem_FreePool (&fs_mempool);
+       PK3_CloseLibrary ();
 
 #ifdef WIN32
        Sys_UnloadLibrary (&shfolder_dll);
+       Sys_UnloadLibrary (&shell32_dll);
+       Sys_UnloadLibrary (&ole32_dll);
 #endif
-}
 
-/*
-====================
-FS_SysOpen
+       if (fs_mutex)
+               Thread_DestroyMutex(fs_mutex);
+}
 
-Internal function used to create a qfile_t and open the relevant non-packed file on disk
-====================
-*/
-static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
+int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
 {
-       qfile_t* file;
+       int handle = -1;
        int mod, opt;
        unsigned int ind;
+       qboolean dolock = false;
 
        // Parse the mode string
        switch (mode[0])
@@ -1728,7 +2130,7 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean non
                        break;
                default:
                        Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
-                       return NULL;
+                       return -1;
        }
        for (ind = 1; mode[ind] != '\0'; ind++)
        {
@@ -1740,6 +2142,9 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean non
                        case 'b':
                                opt |= O_BINARY;
                                break;
+                       case 'l':
+                               dolock = true;
+                               break;
                        default:
                                Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
                                                        filepath, mode, mode[ind]);
@@ -1749,25 +2154,58 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean non
        if (nonblocking)
                opt |= O_NONBLOCK;
 
-       file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
-       memset (file, 0, sizeof (*file));
-       file->ungetc = EOF;
-
-#if _MSC_VER >= 1400
-       _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#ifdef WIN32
+# if _MSC_VER >= 1400
+       _sopen_s(&handle, filepath, 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);
+# endif
 #else
-       file->handle = open (filepath, mod | opt, 0666);
+       handle = open (filepath, mod | opt, 0666);
+       if(handle >= 0 && dolock)
+       {
+               struct flock l;
+               l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
+               l.l_whence = SEEK_SET;
+               l.l_start = 0;
+               l.l_len = 0;
+               if(fcntl(handle, F_SETLK, &l) == -1)
+               {
+                       close(handle);
+                       handle = -1;
+               }
+       }
 #endif
+
+       return handle;
+}
+
+/*
+====================
+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* file;
+
+       file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
+       file->ungetc = EOF;
+       file->handle = FS_SysOpenFD(filepath, mode, nonblocking);
        if (file->handle < 0)
        {
                Mem_Free (file);
                return NULL;
        }
 
+       file->filename = Mem_strdup(fs_mempool, filepath);
+
        file->real_length = lseek (file->handle, 0, SEEK_END);
 
        // For files opened in append mode, we start at the end of the file
-       if (mod & O_APPEND)
+       if (mode[0] == 'a')
                file->position = file->real_length;
        else
                lseek (file->handle, 0, SEEK_SET);
@@ -1783,7 +2221,7 @@ FS_OpenPackedFile
 Open a packed file using its package file descriptor
 ===========
 */
-qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
+static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
 {
        packfile_t *pfile;
        int dup_handle;
@@ -1796,6 +2234,7 @@ qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
                if (!PK3_GetTrueFileOffset (pfile, pack))
                        return NULL;
 
+#ifndef LINK_TO_ZLIB
        // No Zlib DLL = no compressed files
        if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
        {
@@ -1804,13 +2243,14 @@ qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
                                        pfile->name);
                return NULL;
        }
+#endif
 
        // LordHavoc: lseek affects all duplicates of a handle so we do it before
        // the dup() call to avoid having to close the dup_handle on error here
        if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
        {
-               Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
-                                       pfile->name, pack->filename, (int) pfile->offset);
+               Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
+                                       pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
                return NULL;
        }
 
@@ -1956,7 +2396,7 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
        for (search = fs_searchpaths;search;search = search->next)
        {
                // is the element a pak file?
-               if (search->pack)
+               if (search->pack && !search->pack->vpack)
                {
                        int (*strcmp_funct) (const char* str1, const char* str2);
                        int left, right, middle;
@@ -1980,16 +2420,16 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
                                        if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
                                        {
                                                // yes, but the first one is empty so we treat it as not being there
-                                               if (!quiet && developer.integer >= 10)
-                                                       Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
+                                               if (!quiet && developer_extra.integer)
+                                                       Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
 
                                                if (index != NULL)
                                                        *index = -1;
                                                return NULL;
                                        }
 
-                                       if (!quiet && developer.integer >= 10)
-                                               Con_Printf("FS_FindFile: %s in %s\n",
+                                       if (!quiet && developer_extra.integer)
+                                               Con_DPrintf("FS_FindFile: %s in %s\n",
                                                                        pak->files[middle].name, pak->filename);
 
                                        if (index != NULL)
@@ -2010,8 +2450,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
                        dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
                        if (FS_SysFileExists (netpath))
                        {
-                               if (!quiet && developer.integer >= 10)
-                                       Con_Printf("FS_FindFile: %s\n", netpath);
+                               if (!quiet && developer_extra.integer)
+                                       Con_DPrintf("FS_FindFile: %s\n", netpath);
 
                                if (index != NULL)
                                        *index = -1;
@@ -2020,8 +2460,8 @@ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
                }
        }
 
-       if (!quiet && developer.integer >= 10)
-               Con_Printf("FS_FindFile: can't find %s\n", name);
+       if (!quiet && developer_extra.integer)
+               Con_DPrintf("FS_FindFile: can't find %s\n", name);
 
        if (index != NULL)
                *index = -1;
@@ -2036,7 +2476,7 @@ FS_OpenReadFile
 Look for a file in the search paths and open it in read-only mode
 ===========
 */
-qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
+static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
 {
        searchpath_t *search;
        int pack_ind;
@@ -2050,6 +2490,7 @@ qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonbloc
        // Found in the filesystem?
        if (pack_ind < 0)
        {
+               // this works with vpacks, so we are fine
                char path [MAX_OSPATH];
                dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
                return FS_SysOpen (path, "rb", nonblocking);
@@ -2160,7 +2601,7 @@ qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet
                return NULL;
        }
 
-       dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
+       dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
 
        // If the file is opened in "write", "append", or "read/write" mode,
        // create directories up to the file.
@@ -2179,13 +2620,17 @@ Open a file. The syntax is the same as fopen
 */
 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
 {
+       qfile_t *result = NULL;
        if (FS_CheckNastyPath(filepath, false))
        {
                Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
                return NULL;
        }
 
-       return FS_OpenReadFile (filepath, quiet, false, 16);
+       if (fs_mutex) Thread_LockMutex(fs_mutex);
+       result = FS_OpenReadFile (filepath, quiet, false, 16);
+       if (fs_mutex) Thread_UnlockMutex(fs_mutex);
+       return result;
 }
 
 
@@ -2226,6 +2671,14 @@ int FS_Close (qfile_t* file)
        if (close (file->handle))
                return EOF;
 
+       if (file->filename)
+       {
+               if (file->flags & QFILE_FLAG_REMOVE)
+                       remove(file->filename);
+
+               Mem_Free((void *) file->filename);
+       }
+
        if (file->ztk)
        {
                qz_inflateEnd (&file->ztk->zstream);
@@ -2236,6 +2689,10 @@ int FS_Close (qfile_t* file)
        return 0;
 }
 
+void FS_RemoveOnClose(qfile_t* file)
+{
+       file->flags |= QFILE_FLAG_REMOVE;
+}
 
 /*
 ====================
@@ -2739,9 +3196,11 @@ FS_WriteFile
 The filename will be prefixed by the current game directory
 ============
 */
-qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
+qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
 {
        qfile_t *file;
+       size_t i;
+       fs_offset_t lentotal;
 
        file = FS_OpenRealFile(filename, "wb", false);
        if (!file)
@@ -2750,12 +3209,21 @@ qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
                return false;
        }
 
-       Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
-       FS_Write (file, data, len);
+       lentotal = 0;
+       for(i = 0; i < count; ++i)
+               lentotal += len[i];
+       Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
+       for(i = 0; i < count; ++i)
+               FS_Write (file, data[i], len[i]);
        FS_Close (file);
        return true;
 }
 
+qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
+{
+       return FS_WriteFileInBlocks(filename, &data, &len, 1);
+}
+
 
 /*
 =============================================================================
@@ -2829,13 +3297,13 @@ Look for a file in the packages and in the filesystem
 int FS_FileType (const char *filename)
 {
        searchpath_t *search;
-       char fullpath[MAX_QPATH];
+       char fullpath[MAX_OSPATH];
 
        search = FS_FindFile (filename, NULL, true);
        if(!search)
                return FS_FILETYPE_NONE;
 
-       if(search->pack)
+       if(search->pack && !search->pack->vpack)
                return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
 
        dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
@@ -2886,6 +3354,9 @@ int FS_SysFileType (const char *path)
        if (stat (path,&buf) == -1)
                return FS_FILETYPE_NONE;
 
+#ifndef S_ISDIR
+#define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
+#endif
        if(S_ISDIR(buf.st_mode))
                return FS_FILETYPE_DIRECTORY;
 
@@ -2953,7 +3424,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
        for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
        {
                // is the element a pak file?
-               if (searchpath->pack)
+               if (searchpath->pack && !searchpath->pack->vpack)
                {
                        // look through all the pak file elements
                        pak = searchpath->pack;
@@ -3085,7 +3556,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
 
        if (resultlist.numstrings)
        {
-               stringlistsort(&resultlist);
+               stringlistsort(&resultlist, true);
                numfiles = resultlist.numstrings;
                numchars = 0;
                for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
@@ -3118,7 +3589,7 @@ void FS_FreeSearch(fssearch_t *search)
 }
 
 extern int con_linewidth;
-int FS_ListDirectory(const char *pattern, int oneperline)
+static int FS_ListDirectory(const char *pattern, int oneperline)
 {
        int numfiles;
        int numcolumns;
@@ -3189,7 +3660,7 @@ int FS_ListDirectory(const char *pattern, int oneperline)
 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
 {
        const char *pattern;
-       if (Cmd_Argc() > 3)
+       if (Cmd_Argc() >= 3)
        {
                Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
                return;
@@ -3229,7 +3700,12 @@ void FS_Which_f(void)
                return;
        }
        if (sp->pack)
-               Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
+       {
+               if(sp->pack->vpack)
+                       Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
+               else
+                       Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
+       }
        else
                Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
 }
@@ -3262,7 +3738,8 @@ qboolean FS_IsRegisteredQuakePack(const char *name)
        // search through the path, one element at a time
        for (search = fs_searchpaths;search;search = search->next)
        {
-               if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
+               if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
+                       // TODO do we want to support vpacks in here too?
                {
                        int (*strcmp_funct) (const char* str1, const char* str2);
                        int left, right, middle;
@@ -3325,6 +3802,12 @@ unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflat
        unsigned char *out = NULL;
        unsigned char *tmp;
 
+       *deflated_size = 0;
+#ifndef LINK_TO_ZLIB
+       if(!zlib_dll)
+               return NULL;
+#endif
+
        memset(&strm, 0, sizeof(strm));
        strm.zalloc = Z_NULL;
        strm.zfree = Z_NULL;
@@ -3418,6 +3901,12 @@ unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflat
        unsigned int have;
        sizebuf_t outbuf;
 
+       *inflated_size = 0;
+#ifndef LINK_TO_ZLIB
+       if(!zlib_dll)
+               return NULL;
+#endif
+
        memset(&outbuf, 0, sizeof(outbuf));
        outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
        outbuf.maxsize = sizeof(tmp);