]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - fs.c
remove warning about signed/unsigned
[xonotic/darkplaces.git] / fs.c
diff --git a/fs.c b/fs.c
index dc75fdceac08d04e0e6de91a72b15c91032ebfe7..12dfc317ccdb46d6ef591b0101ed2f3281077eb9 100644 (file)
--- a/fs.c
+++ b/fs.c
 # define lseek _lseeki64
 #endif
 
+#if _MSC_VER >= 1400
+// suppress deprecated warnings
+# include <sys/stat.h>
+# include <share.h>
+# define read _read
+# define write _write
+# define close _close
+# define unlink _unlink
+# define dup _dup
+#endif
+
 /*
 
 All of Quake's data access is through a hierchal file system, but the contents
@@ -211,6 +222,8 @@ typedef struct dpackheader_s
 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
 // file compressed using the deflate algorithm
 #define PACKFILE_FLAG_DEFLATED (1 << 1)
+// file is a symbolic link
+#define PACKFILE_FLAG_SYMLINK (1 << 2)
 
 typedef struct packfile_s
 {
@@ -224,6 +237,7 @@ typedef struct packfile_s
 typedef struct pack_s
 {
        char filename [MAX_OSPATH];
+       char shortname [MAX_QPATH];
        int handle;
        int ignorecase;  // PK3 ignores case
        int numfiles;
@@ -318,6 +332,15 @@ static dllfunction_t zlibfuncs[] =
 // Handle for Zlib DLL
 static dllhandle_t zlib_dll = NULL;
 
+#ifdef WIN32
+static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
+static dllfunction_t shfolderfuncs[] =
+{
+       {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
+       {NULL, NULL}
+};
+static dllhandle_t shfolder_dll = NULL;
+#endif
 
 /*
 ====================
@@ -449,7 +472,11 @@ 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);
-       read (pack->handle, central_dir, eocd->cdir_size);
+       if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
+       {
+               Mem_Free (central_dir);
+               return -1;
+       }
 
        // Extract the files properties
        // The parsing is done "by hand" because some fields have variable sizes and
@@ -481,9 +508,16 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                // Check encryption, compression, and attributes
                // 1st uint8  : general purpose bit flag
                //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
+               //
+               // LordHavoc: bit 3 would be a problem if we were scanning the archive
+               // but is not a problem in the central directory where the values are
+               // always real.
+               //
+               // bit 3 seems to always be set by the standard Mac OSX zip maker
+               //
                // 2nd uint8 : external file attributes
                //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
-               if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
+               if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
                {
                        // Still enough bytes for the name?
                        if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
@@ -511,6 +545,18 @@ int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
                                offset = BuffLittleLong (&ptr[42]);
                                packsize = BuffLittleLong (&ptr[20]);
                                realsize = BuffLittleLong (&ptr[24]);
+
+                               switch(ptr[5]) // C_VERSION_MADE_BY_1
+                               {
+                                       case 3: // UNIX_
+                                       case 2: // VMS_
+                                       case 16: // BEOS_
+                                               if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
+                                                       // can't use S_ISLNK here, as this has to compile on non-UNIX too
+                                                       flags |= PACKFILE_FLAG_SYMLINK;
+                                               break;
+                               }
+
                                FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
                        }
                }
@@ -544,7 +590,11 @@ pack_t *FS_LoadPackPK3 (const char *packfile)
        pack_t *pack;
        int real_nb_files;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        packhandle = open (packfile, O_RDONLY | O_BINARY);
+#endif
        if (packhandle < 0)
                return NULL;
 
@@ -695,7 +745,7 @@ static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
 ============
 FS_CreatePath
 
-Only used for FS_Open.
+Only used for FS_OpenRealFile.
 ============
 */
 void FS_CreatePath (char *path)
@@ -755,10 +805,19 @@ pack_t *FS_LoadPackPAK (const char *packfile)
        pack_t *pack;
        dpackfile_t *info;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        packhandle = open (packfile, O_RDONLY | O_BINARY);
+#endif
        if (packhandle < 0)
                return NULL;
-       read (packhandle, (void *)&header, sizeof(header));
+       if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
+       {
+               Con_Printf ("%s is not a packfile\n", packfile);
+               close(packhandle);
+               return NULL;
+       }
        if (memcmp(header.id, "PACK", 4))
        {
                Con_Printf ("%s is not a packfile\n", packfile);
@@ -830,7 +889,7 @@ If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
 plain directories.
 ================
 */
-static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
+static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
 {
        searchpath_t *search;
        pack_t *pak = NULL;
@@ -858,6 +917,8 @@ static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loade
 
        if (pak)
        {
+               strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
+               //Con_DPrintf("  Registered pack with short name %s\n", shortname);
                if(keep_plain_dirs)
                {
                        // find the first item whose next one is a pack or NULL
@@ -943,7 +1004,7 @@ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep
 
        dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
 
-       return FS_AddPack_Fullpath(fullpath, already_loaded, keep_plain_dirs);
+       return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
 }
 
 
@@ -972,7 +1033,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
                }
        }
 
@@ -981,7 +1042,7 @@ void FS_AddGameDirectory (const char *dir)
        {
                if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
                {
-                       FS_AddPack_Fullpath(list.strings[i], NULL, false);
+                       FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
                }
        }
 
@@ -1007,9 +1068,11 @@ void FS_AddGameHierarchy (const char *dir)
        char userdir[MAX_QPATH];
 #ifdef WIN32
        TCHAR mydocsdir[MAX_PATH + 1];
-#else
-       const char *homedir;
+#if _MSC_VER >= 1400
+       size_t homedirlen;
+#endif
 #endif
+       char *homedir;
 
        // Add the common game directory
        FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
@@ -1018,18 +1081,53 @@ void FS_AddGameHierarchy (const char *dir)
 
        // Add the personal game directory
 #ifdef WIN32
-       if(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
+       if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
+       {
                dpsnprintf(userdir, sizeof(userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
+               Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", userdir);
+       }
+       else
+       {
+               // use the environment
+#if _MSC_VER >= 1400
+               _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
+#else
+               homedir = getenv("USERPROFILE");
+#endif
+
+               if(homedir)
+               {
+                       dpsnprintf(userdir, sizeof(userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
+#if _MSC_VER >= 1400
+                       free(homedir);
+#endif
+                       Con_DPrintf("Obtained personal directory %s from environment\n", userdir);
+               }
+               else
+                       *userdir = 0; // just to make sure it hasn't been written to by SHGetFolderPath returning failure
+       }
+
+       if(!*userdir)
+               Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
 #else
        homedir = getenv ("HOME");
        if(homedir)
                dpsnprintf(userdir, sizeof(userdir), "%s/.%s/", homedir, gameuserdirname);
+
+       if(!*userdir)
+               Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
 #endif
 
+
 #ifdef WIN32
        if(!COM_CheckParm("-mygames"))
        {
+#if _MSC_VER >= 1400
+               int fd;
+               _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
+#else
                int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
+#endif
                if(fd >= 0)
                {
                        close(fd);
@@ -1243,9 +1341,6 @@ qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean
                }
        }
 
-       // halt demo playback to close the file
-       CL_Disconnect();
-
        Host_SaveConfig();
 
        fs_numgamedirs = numgamedirs;
@@ -1304,6 +1399,9 @@ void FS_GameDir_f (void)
                return;
        }
 
+       // halt demo playback to close the file
+       CL_Disconnect();
+
        FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
 }
 
@@ -1334,6 +1432,16 @@ void FS_Init (void)
 {
        int i;
 
+#ifdef WIN32
+       const char* dllnames [] =
+       {
+               "shfolder.dll",  // IE 4, or Win NT and higher
+               NULL
+       };
+       Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
+       // don't care for the result; if it fails, %USERPROFILE% will be used instead
+#endif
+
        fs_mempool = Mem_AllocPool("file management", 0, NULL);
 
        strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
@@ -1431,6 +1539,10 @@ void FS_Shutdown (void)
        //  by the OS anyway)
        FS_ClearSearchPath();
        Mem_FreePool (&fs_mempool);
+
+#ifdef WIN32
+       Sys_UnloadLibrary (&shfolder_dll);
+#endif
 }
 
 /*
@@ -1488,7 +1600,11 @@ static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean non
        memset (file, 0, sizeof (*file));
        file->ungetc = EOF;
 
+#if _MSC_VER >= 1400
+       _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
+#else
        file->handle = open (filepath, mod | opt, 0666);
+#endif
        if (file->handle < 0)
        {
                Mem_Free (file);
@@ -1767,7 +1883,7 @@ FS_OpenReadFile
 Look for a file in the search paths and open it in read-only mode
 ===========
 */
-qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
+qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
 {
        searchpath_t *search;
        int pack_ind;
@@ -1787,6 +1903,80 @@ qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonbloc
        }
 
        // So, we found it in a package...
+
+       // Is it a PK3 symlink?
+       // TODO also handle directory symlinks by parsing the whole structure...
+       // but heck, file symlinks are good enough for now
+       if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
+       {
+               if(symlinkLevels <= 0)
+               {
+                       Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
+                       return NULL;
+               }
+               else
+               {
+                       char linkbuf[MAX_QPATH];
+                       fs_offset_t count;
+                       qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
+                       const char *mergeslash;
+                       char *mergestart;
+
+                       if(!linkfile)
+                               return NULL;
+                       count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
+                       FS_Close(linkfile);
+                       if(count < 0)
+                               return NULL;
+                       linkbuf[count] = 0;
+                       
+                       // Now combine the paths...
+                       mergeslash = strrchr(filename, '/');
+                       mergestart = linkbuf;
+                       if(!mergeslash)
+                               mergeslash = filename;
+                       while(!strncmp(mergestart, "../", 3))
+                       {
+                               mergestart += 3;
+                               while(mergeslash > filename)
+                               {
+                                       --mergeslash;
+                                       if(*mergeslash == '/')
+                                               break;
+                               }
+                       }
+                       // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
+                       if(mergeslash == filename)
+                       {
+                               // Either mergeslash == filename, then we just replace the name (done below)
+                       }
+                       else
+                       {
+                               // Or, we append the name after mergeslash;
+                               // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
+                               int spaceNeeded = mergeslash - filename + 1;
+                               int spaceRemoved = mergestart - linkbuf;
+                               if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
+                               {
+                                       Con_DPrintf("symlink: too long path rejected\n");
+                                       return NULL;
+                               }
+                               memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
+                               memcpy(linkbuf, filename, spaceNeeded);
+                               linkbuf[count - spaceRemoved + spaceNeeded] = 0;
+                               mergestart = linkbuf;
+                       }
+                       if (!quiet && developer_loading.integer)
+                               Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
+                       if(FS_CheckNastyPath (mergestart, false))
+                       {
+                               Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
+                               return NULL;
+                       }
+                       return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
+               }
+       }
+
        return FS_OpenPackedFile (search->pack, pack_ind);
 }
 
@@ -1801,46 +1991,48 @@ MAIN PUBLIC FUNCTIONS
 
 /*
 ====================
-FS_Open
+FS_OpenRealFile
 
-Open a file. The syntax is the same as fopen
+Open a file in the userpath. The syntax is the same as fopen
+Used for savegame scanning in menu, and all file writing.
 ====================
 */
-qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
-{
-#ifdef FS_FIX_PATHS
-       char fixedFileName[MAX_QPATH];
-       char *d;
-       strlcpy( fixedFileName, filepath, MAX_QPATH );
-       // try to fix common mistakes (\ instead of /)
-       for( d = fixedFileName ; *d ; d++ )
-               if( *d == '\\' )
-                       *d = '/';
-       filepath = fixedFileName;
-#endif
+qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
+{
+       char real_path [MAX_OSPATH];
 
        if (FS_CheckNastyPath(filepath, false))
        {
-               Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
+               Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
                return NULL;
        }
 
-       // If the file is opened in "write", "append", or "read/write" mode
+       dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
+
+       // If the file is opened in "write", "append", or "read/write" mode,
+       // create directories up to the file.
        if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
-       {
-               char real_path [MAX_OSPATH];
+               FS_CreatePath (real_path);
+       return FS_SysOpen (real_path, mode, false);
+}
 
-               // Open the file on disk directly
-               dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
 
-               // Create directories up to the file
-               FS_CreatePath (real_path);
+/*
+====================
+FS_OpenVirtualFile
 
-               return FS_SysOpen (real_path, mode, nonblocking);
+Open a file. The syntax is the same as fopen
+====================
+*/
+qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
+{
+       if (FS_CheckNastyPath(filepath, false))
+       {
+               Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
+               return NULL;
        }
-       // Else, we look at the various search paths and open the file in read-only mode
-       else
-               return FS_OpenReadFile (filepath, quiet, nonblocking);
+
+       return FS_OpenReadFile (filepath, quiet, false, 16);
 }
 
 
@@ -2321,7 +2513,7 @@ unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, f
        unsigned char *buf = NULL;
        fs_offset_t filesize = 0;
 
-       file = FS_Open (path, "rb", quiet, false);
+       file = FS_OpenVirtualFile(path, quiet);
        if (file)
        {
                filesize = file->real_length;
@@ -2350,7 +2542,7 @@ qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
 {
        qfile_t *file;
 
-       file = FS_Open (filename, "wb", false, false);
+       file = FS_OpenRealFile(filename, "wb", false);
        if (!file)
        {
                Con_Printf("FS_WriteFile: failed on %s\n", filename);
@@ -2473,6 +2665,11 @@ Look for a file in the filesystem only
 int FS_SysFileType (const char *path)
 {
 #if WIN32
+// Sajt - some older sdks are missing this define
+# ifndef INVALID_FILE_ATTRIBUTES
+#  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
+# endif
+
        DWORD result = GetFileAttributes(path);
 
        if(result == INVALID_FILE_ATTRIBUTES)
@@ -2628,25 +2825,25 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
                                if( !nextseparator ) {
                                        nextseparator = start + strlen( start );
                                }
-                               
+
                                // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
                                // copy everything up except nextseperator
                                strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
                                // find the last '/' before the wildcard
-                               prevseparator = strrchr( subpattern, '/' ) + 1;
+                               prevseparator = strrchr( subpattern, '/' );
                                if (!prevseparator)
-                               {
                                        prevseparator = subpattern;
-                               }
+                               else
+                                       prevseparator++;
                                // copy everything from start to the previous including the '/' (before the wildcard)
                                // everything up to start is already included in the path of matchedSet's entries
                                strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
-                               
+
                                // for each entry in matchedSet try to open the subdirectories specified in subpath
                                for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
                                        strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
                                        strlcat( temp, subpath, sizeof(temp) );
-                                       listdirectory( &foundSet, searchpath->filename, temp );                         
+                                       listdirectory( &foundSet, searchpath->filename, temp );
                                }
                                if( dirlistindex == 0 ) {
                                        break;
@@ -2664,7 +2861,7 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
 
                                start = nextseparator;
                        }
-                       
+
                        for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
                        {
                                const char *temp = matchedSet.strings[dirlistindex];
@@ -2819,7 +3016,7 @@ const char *FS_WhichPack(const char *filename)
        int index;
        searchpath_t *sp = FS_FindFile(filename, &index, true);
        if(sp && sp->pack)
-               return sp->pack->filename;
+               return sp->pack->shortname;
        else
                return 0;
 }