X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=fs.c;h=12dfc317ccdb46d6ef591b0101ed2f3281077eb9;hp=3e3820ed1c249705f3ad516fb86f4601de076b5f;hb=ef80c7198405dac0c7e6971ea65df2ead0015240;hpb=cada7970d2eaae14a23a2530b6e34a2db671d432 diff --git a/fs.c b/fs.c index 3e3820ed..12dfc317 100644 --- a/fs.c +++ b/fs.c @@ -55,6 +55,17 @@ # define lseek _lseeki64 #endif +#if _MSC_VER >= 1400 +// suppress deprecated warnings +# include +# include +# 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); @@ -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) @@ -2633,11 +2830,11 @@ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet) // 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))); @@ -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; }