/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "qcommon.h" #include "include/unzip.h" #pragma warning (disable : 4715) /* ============================================================================= QUAKE FILESYSTEM ============================================================================= */ /* All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources. The "base directory" is the path to the directory holding the quake.exe and all game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. This can be overridden with the "+set game" command line parm to allow code debugging in a different directory. The base directory is only used during filesystem initialization. The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to. This can be overridden with the "-game" command line parameter. The game directory can never be changed while quake is executing. This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't. */ #define BASEDIRNAME "baseq2" #define MAX_HANDLES 32 #define MAX_READ 0x10000 #define MAX_WRITE 0x10000 #define MAX_FIND_FILES 0x04000 // // in memory // // // Berserk's pk3 file support // typedef struct { char name[MAX_QPATH]; fsMode_t mode; FILE *file; // Only one of file or unzFile *zip; // zip will be used } fsHandle_t; typedef struct fsLink_s { char *from; int length; char *to; struct fsLink_s *next; } fsLink_t; typedef struct { char name[MAX_QPATH]; int size; int offset; // This is ignored in PK3 files } fsPackFile_t; typedef struct { char name[MAX_OSPATH]; FILE *pak; unzFile *pk3; int numFiles; fsPackFile_t *files; } fsPack_t; typedef struct fsSearchPath_s { char path[MAX_OSPATH]; // Only one of path or fsPack_t *pack; // pack will be used struct fsSearchPath_s *next; } fsSearchPath_t; fsHandle_t fs_handles[MAX_HANDLES]; fsLink_t *fs_links; fsSearchPath_t *fs_searchPaths; fsSearchPath_t *fs_baseSearchPaths; char fs_gamedir[MAX_OSPATH]; static char fs_currentGame[MAX_QPATH]; static char fs_fileInPath[MAX_OSPATH]; static qboolean fs_fileInPack; int file_from_pak = 0; // This is set by FS_FOpenFile int file_from_pk3 = 0; // This is set by FS_FOpenFile char file_from_pk3_name[MAX_QPATH]; // This is set by FS_FOpenFile cvar_t *fs_homepath; cvar_t *fs_basedir; cvar_t *fs_cddir; cvar_t *fs_gamedirvar; cvar_t *fs_debug; void CDAudio_Stop (void); /* ================= Q_strncpyz Safe strncpy that ensures a trailing zero ================= */ void Q_strncpyz (char *dst, const char *src, int dstSize){ if (!dst) Com_Error(ERR_FATAL, "Q_strncpyz: NULL dst"); if (!src) Com_Error(ERR_FATAL, "Q_strncpyz: NULL src"); if (dstSize < 1) Com_Error(ERR_FATAL, "Q_strncpyz: dstSize < 1"); strncpy(dst, src, dstSize-1); dst[dstSize-1] = 0; } /* ================= Com_FilePath Returns the path up to, but not including the last / ================= */ void Com_FilePath (const char *path, char *dst, int dstSize){ const char *s, *last; s = last = path + strlen(path); while (*s != '/' && *s != '\\' && s != path){ last = s-1; s--; } Q_strncpyz(dst, path, dstSize); if (last-path < dstSize) dst[last-path] = 0; } /* ================ FS_filelength ================ */ int FS_filelength (FILE *f) { int pos; int end; pos = ftell (f); fseek (f, 0, SEEK_END); end = ftell (f); fseek (f, pos, SEEK_SET); return end; } /* ============ FS_CreatePath Creates any directories needed to store the given filename ============ */ void FS_CreatePath (char *path) { char *ofs; FS_DPrintf("FS_CreatePath( %s )\n", path); if (strstr(path, "..") || strstr(path, "::") || strstr(path, "\\\\") || strstr(path, "//")) { Com_Printf(S_COLOR_YELLOW"WARNING: refusing to create relative path '%s'\n", path); return; } for (ofs = path+1 ; *ofs ; ofs++) { if (*ofs == '/' || *ofs == '\\') // Q2E changed //if (*ofs == '/') { // create the directory *ofs = 0; Sys_Mkdir (path); *ofs = '/'; } } } //Knightmare- Psychospaz's mod detector qboolean modType (char *name) { fsSearchPath_t *search; for (search = fs_searchPaths ; search ; search = search->next) { if (strstr (search->path, name)) return true; } return (0); } //Knightmare- this enables Rogue menu options for Q2MP4 qboolean roguepath () { if (modType("rogue") || modType("q2mp4")) return true; return false; } //end Knightmare /* ================= FS_DPrintf ================= */ void FS_DPrintf (const char *format, ...) { char msg[1024]; va_list argPtr; if (!fs_debug->value) return; va_start(argPtr, format); vsprintf(msg, format, argPtr); va_end(argPtr); Com_Printf("%s", msg); } /* ================= FS_GameDir Called to find where to write a file (demos, savegames, etc...) ================= */ char *FS_GameDir (void) { return fs_gamedir; } char *FS_Gamedir (void) { return fs_gamedir; } /* ================= FS_DeletePath TODO: delete tree contents ================= */ void FS_DeletePath (char *path) { FS_DPrintf("FS_DeletePath( %s )\n", path); Sys_Rmdir(path); } /* ================= FS_FileForHandle Returns a FILE * for a fileHandle_t ================= */ fsHandle_t *FS_GetFileByHandle (fileHandle_t f); FILE *FS_FileForHandle (fileHandle_t f) { fsHandle_t *handle; handle = FS_GetFileByHandle(f); if (handle->zip) Com_Error(ERR_DROP, "FS_FileForHandle: can't get FILE on zip file"); if (!handle->file) Com_Error(ERR_DROP, "FS_FileForHandle: NULL"); return handle->file; } /* ================= FS_HandleForFile Finds a free fileHandle_t ================= */ fsHandle_t *FS_HandleForFile (const char *path, fileHandle_t *f) { fsHandle_t *handle; int i; handle = fs_handles; for (i = 0; i < MAX_HANDLES; i++, handle++) { if (!handle->file && !handle->zip) { strcpy(handle->name, path); *f = i + 1; return handle; } } // Failed Com_Error(ERR_DROP, "FS_HandleForFile: none free"); } /* ================= FS_GetFileByHandle Returns a fsHandle_t * for the given fileHandle_t ================= */ fsHandle_t *FS_GetFileByHandle (fileHandle_t f) { if (f <= 0 || f > MAX_HANDLES) Com_Error(ERR_DROP, "FS_GetFileByHandle: out of range"); return &fs_handles[f-1]; } /* ================= FS_FileLength ================= */ int FS_FileLength (FILE *f) { int cur, end; cur = ftell(f); fseek(f, 0, SEEK_END); end = ftell(f); fseek(f, cur, SEEK_SET); return end; } /* ================= FS_FOpenFileAppend Returns file size or -1 on error ================= */ int FS_FOpenFileAppend (fsHandle_t *handle) { char path[MAX_OSPATH]; FS_CreatePath(handle->name); Com_sprintf(path, sizeof(path), "%s/%s", fs_gamedir, handle->name); handle->file = fopen(path, "ab"); if (handle->file) { if (fs_debug->value) Com_Printf("FS_FOpenFileAppend: %s\n", path); return FS_FileLength(handle->file); } if (fs_debug->value) Com_Printf("FS_FOpenFileAppend: couldn't open %s\n", path); return -1; } /* ================= FS_FOpenFileWrite Always returns 0 or -1 on error ================= */ int FS_FOpenFileWrite (fsHandle_t *handle) { char path[MAX_OSPATH]; FS_CreatePath(handle->name); Com_sprintf(path, sizeof(path), "%s/%s", fs_gamedir, handle->name); handle->file = fopen(path, "wb"); if (handle->file) { if (fs_debug->value) Com_Printf("FS_FOpenFileWrite: %s\n", path); return 0; } if (fs_debug->value) Com_Printf("FS_FOpenFileWrite: couldn't open %s\n", path); return -1; } /* ================= FS_FOpenFileRead Returns file size or -1 if not found. Can open separate files as well as files inside pack files (both PAK and PK3). ================= */ int FS_FOpenFileRead (fsHandle_t *handle) { fsSearchPath_t *search; fsPack_t *pack; char path[MAX_OSPATH]; int i; // Knightmare- hack global vars for autodownloads file_from_pak = 0; file_from_pk3 = 0; // Search through the path, one element at a time for (search = fs_searchPaths; search; search = search->next) { if (search->pack) { // Search inside a pack file pack = search->pack; for (i = 0; i < pack->numFiles; i++) { if (!Q_stricmp(pack->files[i].name, handle->name)) { // Found it! Com_FilePath(pack->name, fs_fileInPath, sizeof(fs_fileInPath)); fs_fileInPack = true; if (fs_debug->value) Com_Printf("FS_FOpenFileRead: %s (found in %s)\n", handle->name, pack->name); if (pack->pak) { // PAK file_from_pak = 1; // Knightmare added handle->file = fopen(pack->name, "rb"); if (handle->file) { fseek(handle->file, pack->files[i].offset, SEEK_SET); return pack->files[i].size; } } else if (pack->pk3) { // PK3 file_from_pk3 = 1; // Knightmare added sprintf(file_from_pk3_name, strrchr(pack->name, '/')+1); // Knightmare added handle->zip = unzOpen(pack->name); if (handle->zip) { if (unzLocateFile(handle->zip, handle->name, 2) == UNZ_OK) { if (unzOpenCurrentFile(handle->zip) == UNZ_OK) return pack->files[i].size; } unzClose(handle->zip); } } Com_Error(ERR_FATAL, "Couldn't reopen %s", pack->name); } } } else { // Search in a directory tree Com_sprintf(path, sizeof(path), "%s/%s", search->path, handle->name); handle->file = fopen(path, "rb"); if (handle->file) { // Found it! Q_strncpyz(fs_fileInPath, search->path, sizeof(fs_fileInPath)); fs_fileInPack = false; if (fs_debug->value) Com_Printf("FS_FOpenFileRead: %s (found in %s)\n", handle->name, search->path); return FS_FileLength(handle->file); } } } // Not found! fs_fileInPath[0] = 0; fs_fileInPack = false; if (fs_debug->value) Com_Printf("FS_FOpenFileRead: couldn't find %s\n", handle->name); return -1; } /* ================= FS_FOpenFile Opens a file for "mode". Returns file size or -1 if an error occurs/not found. Can open separate files as well as files inside pack files (both PAK and PK3). ================= */ int FS_FOpenFile (const char *name, fileHandle_t *f, fsMode_t mode) { fsHandle_t *handle; int size; handle = FS_HandleForFile(name, f); Q_strncpyz(handle->name, name, sizeof(handle->name)); handle->mode = mode; switch (mode) { case FS_READ: size = FS_FOpenFileRead(handle); break; case FS_WRITE: size = FS_FOpenFileWrite(handle); break; case FS_APPEND: size = FS_FOpenFileAppend(handle); break; default: Com_Error(ERR_FATAL, "FS_FOpenFile: bad mode (%i)", mode); } if (size != -1) return size; // Couldn't open, so free the handle memset(handle, 0, sizeof(*handle)); *f = 0; return -1; } /* ================= FS_FCloseFile ================= */ void FS_FCloseFile (fileHandle_t f) { fsHandle_t *handle; handle = FS_GetFileByHandle(f); if (handle->file) fclose(handle->file); else if (handle->zip) { unzCloseCurrentFile(handle->zip); unzClose(handle->zip); } memset(handle, 0, sizeof(*handle)); } /* ================= FS_Read Properly handles partial reads ================= */ int FS_Read (void *buffer, int size, fileHandle_t f) { fsHandle_t *handle; int remaining, r; byte *buf; qboolean tried = false; handle = FS_GetFileByHandle(f); // Read remaining = size; buf = (byte *)buffer; while (remaining) { if (handle->file) r = fread(buf, 1, remaining, handle->file); else if (handle->zip) r = unzReadCurrentFile(handle->zip, buf, remaining); else return 0; if (r == 0) { if (!tried) { // We might have been trying to read from a CD CDAudio_Stop(); tried = true; } else { // Already tried once //Com_Error(ERR_FATAL, va("FS_Read: 0 bytes read from %s", handle->name)); //bc hide this error message. todo: make it display during developer2 mode. //Com_DPrintf(S_COLOR_RED"FS_Read: 0 bytes read from %s\n", handle->name); return size - remaining; } } else if (r == -1) Com_Error(ERR_FATAL, "FS_Read: -1 bytes read from %s", handle->name); remaining -= r; buf += r; } return size; } /* ================= FS_FRead Properly handles partial reads of size up to count times No error if it can't read ================= */ int FS_FRead (void *buffer, int size, int count, fileHandle_t f) { fsHandle_t *handle; int loops, remaining, r; byte *buf; qboolean tried = false; handle = FS_GetFileByHandle(f); // Read loops = count; //remaining = size; buf = (byte *)buffer; while (loops) { // Read in chunks remaining = size; while (remaining) { if (handle->file) r = fread(buf, 1, remaining, handle->file); else if (handle->zip) r = unzReadCurrentFile(handle->zip, buf, remaining); else return 0; if (r == 0) { if (!tried) { // We might have been trying to read from a CD CDAudio_Stop(); tried = true; } else { //Com_Printf(S_COLOR_RED"FS_FRead: 0 bytes read from %s\n", handle->name); return size - remaining; } } else if (r == -1) Com_Error(ERR_FATAL, "FS_FRead: -1 bytes read from %s", handle->name); remaining -= r; buf += r; } loops--; } return size; } /* ================= FS_Write Properly handles partial writes ================= */ int FS_Write (const void *buffer, int size, fileHandle_t f){ fsHandle_t *handle; int remaining, w; byte *buf; handle = FS_GetFileByHandle(f); // Write remaining = size; buf = (byte *)buffer; while (remaining) { if (handle->file) w = fwrite(buf, 1, remaining, handle->file); else if (handle->zip) Com_Error(ERR_FATAL, "FS_Write: can't write to zip file %s", handle->name); else return 0; if (w == 0) { Com_Printf(S_COLOR_RED"FS_Write: 0 bytes written to %s\n", handle->name); return size - remaining; } else if (w == -1) Com_Error(ERR_FATAL, "FS_Write: -1 bytes written to %s", handle->name); remaining -= w; buf += w; } return size; } /* ================= FS_FTell ================= */ int FS_FTell (fileHandle_t f) { fsHandle_t *handle; handle = FS_GetFileByHandle(f); if (handle->file) return ftell(handle->file); else if (handle->zip) return unztell(handle->zip); return 0; } /* ================= FS_ListPak Generates a listing of the contents of a pak file ================= */ char **FS_ListPak (char *find, int *num) { fsSearchPath_t *search; //char netpath[MAX_OSPATH]; fsPack_t *pak; int nfiles = 0, nfound = 0; char **list = 0; int i; // now check pak files for (search = fs_searchPaths; search; search = search->next) { if (!search->pack) continue; pak = search->pack; //now find and build list for (i=0 ; inumFiles ; i++) nfiles++; } list = malloc( sizeof( char * ) * nfiles ); memset( list, 0, sizeof( char * ) * nfiles ); for (search = fs_searchPaths; search; search = search->next) { if (!search->pack) continue; pak = search->pack; //now find and build list for (i=0 ; inumFiles ; i++) { if (strstr(pak->files[i].name, find)) { list[nfound] = strdup(pak->files[i].name); nfound++; } } } *num = nfound; return list; } /* ================= FS_Seek ================= */ void FS_Seek (fileHandle_t f, int offset, fsOrigin_t origin) { fsHandle_t *handle; unz_file_info info; int remaining, r, len; byte dummy[0x8000]; handle = FS_GetFileByHandle(f); if (handle->file) { switch (origin) { case FS_SEEK_SET: fseek(handle->file, offset, SEEK_SET); break; case FS_SEEK_CUR: fseek(handle->file, offset, SEEK_CUR); break; case FS_SEEK_END: fseek(handle->file, offset, SEEK_END); break; default: Com_Error(ERR_FATAL, "FS_Seek: bad origin (%i)", origin); } } else if (handle->zip) { switch (origin) { case FS_SEEK_SET: remaining = offset; break; case FS_SEEK_CUR: remaining = offset + unztell(handle->zip); break; case FS_SEEK_END: unzGetCurrentFileInfo(handle->zip, &info, NULL, 0, NULL, 0, NULL, 0); remaining = offset + info.uncompressed_size; break; default: Com_Error(ERR_FATAL, "FS_Seek: bad origin (%i)", origin); } // Reopen the file unzCloseCurrentFile(handle->zip); unzOpenCurrentFile(handle->zip); // Skip until the desired offset is reached while (remaining) { len = remaining; if (len > sizeof(dummy)) len = sizeof(dummy); r = unzReadCurrentFile(handle->zip, dummy, len); if (r <= 0) break; remaining -= r; } } } /* ================= FS_Tell Returns -1 if an error occurs ================= */ int FS_Tell (fileHandle_t f) { fsHandle_t *handle; handle = FS_GetFileByHandle(f); if (handle->file) return ftell(handle->file); else if (handle->zip) return unztell(handle->zip); } /* ================= FS_FileExists ================ */ qboolean FS_FileExists (char *path) { fileHandle_t f; FS_FOpenFile(path, &f, FS_READ); if (f) { FS_FCloseFile(f); return true; } return false; } /* ================= FS_RenameFile ================= */ void FS_RenameFile (const char *oldPath, const char *newPath) { FS_DPrintf("FS_RenameFile( %s, %s )\n", oldPath, newPath); if (rename(oldPath, newPath)) FS_DPrintf("FS_RenameFile: failed to rename %s to %s\n", oldPath, newPath); } /* ================= FS_DeleteFile ================= */ void FS_DeleteFile (const char *path) { FS_DPrintf("FS_DeleteFile( %s )\n", path); if (remove(path)) FS_DPrintf("FS_DeleteFile: failed to delete %s\n", path); } /* ================= FS_LoadFile "path" is relative to the Quake search path. Returns file size or -1 if the file is not found. A NULL buffer will just return the file size without loading. ================= */ int FS_LoadFile (char *path, void **buffer) { fileHandle_t f; byte *buf; int size; buf = NULL; size = FS_FOpenFile(path, &f, FS_READ); if (size == -1 || size == 0) { if (buffer) *buffer = NULL; return size; } if (!buffer) { FS_FCloseFile(f); return size; } buf = Z_Malloc(size); *buffer = buf; FS_Read(buf, size, f); FS_FCloseFile(f); return size; } /* ================= FS_FreeFile ================= */ void FS_FreeFile (void *buffer) { if (!buffer) { FS_DPrintf("FS_FreeFile: NULL buffer\n"); return; } Z_Free(buffer); } /* ================= FS_LoadPAK Takes an explicit (not game tree related) path to a pack file. Loads the header and directory, adding the files at the beginning of the list so they override previous pack files. ================= */ fsPack_t *FS_LoadPAK (const char *packPath) { int numFiles, i; fsPackFile_t *files; fsPack_t *pack; FILE *handle; dpackheader_t header; dpackfile_t info[MAX_FILES_IN_PACK]; handle = fopen(packPath, "rb"); if (!handle) return NULL; fread(&header, 1, sizeof(dpackheader_t), handle); if (LittleLong(header.ident) != IDPAKHEADER) { fclose(handle); Com_Error(ERR_FATAL, "FS_LoadPAK: %s is not a pack file", packPath); } header.dirofs = LittleLong(header.dirofs); header.dirlen = LittleLong(header.dirlen); numFiles = header.dirlen / sizeof(dpackfile_t); if (numFiles > MAX_FILES_IN_PACK || numFiles == 0) { fclose(handle); Com_Error(ERR_FATAL, "FS_LoadPAK: %s has %i files", packPath, numFiles); } files = Z_Malloc(numFiles * sizeof(fsPackFile_t)); fseek(handle, header.dirofs, SEEK_SET); fread(info, 1, header.dirlen, handle); // Parse the directory for (i = 0; i < numFiles; i++) { strcpy(files[i].name, info[i].name); files[i].offset = LittleLong(info[i].filepos); files[i].size = LittleLong(info[i].filelen); } pack = Z_Malloc(sizeof(fsPack_t)); strcpy(pack->name, packPath); pack->pak = handle; pack->pk3 = NULL; pack->numFiles = numFiles; pack->files = files; return pack; } /* ================= FS_LoadPK3 Takes an explicit (not game tree related) path to a pack file. Loads the header and directory, adding the files at the beginning of the list so they override previous pack files. ================= */ fsPack_t *FS_LoadPK3 (const char *packPath) { int numFiles, i = 0; fsPackFile_t *files; fsPack_t *pack; unzFile *handle; unz_global_info global; unz_file_info info; int status; char fileName[MAX_QPATH]; handle = unzOpen(packPath); if (!handle) return NULL; if (unzGetGlobalInfo(handle, &global) != UNZ_OK) { unzClose(handle); Com_Error(ERR_FATAL, "FS_LoadPK3: %s is not a pack file", packPath); } numFiles = global.number_entry; if (numFiles > MAX_FILES_IN_PACK || numFiles == 0) { unzClose(handle); Com_Error(ERR_FATAL, "FS_LoadPK3: %s has %i files", packPath, numFiles); } files = Z_Malloc(numFiles * sizeof(fsPackFile_t)); // Parse the directory status = unzGoToFirstFile(handle); while (status == UNZ_OK) { fileName[0] = 0; unzGetCurrentFileInfo(handle, &info, fileName, MAX_QPATH, NULL, 0, NULL, 0); strcpy(files[i].name, fileName); files[i].offset = -1; // Not used in ZIP files files[i].size = info.uncompressed_size; i++; status = unzGoToNextFile(handle); } pack = Z_Malloc(sizeof(fsPack_t)); strcpy(pack->name, packPath); pack->pak = NULL; pack->pk3 = handle; pack->numFiles = numFiles; pack->files = files; return pack; } /* ================= FS_AddGameDirectory Sets fs_gameDir, adds the directory to the head of the path, then loads and adds all the pack files found (in alphabetical order). PK3 files are loaded later so they override PAK files. NOTE: PAK files can be restricted by setting fs_pakFiles to avoid some problems ================= */ void FS_AddGameDirectory (const char *dir) { fsSearchPath_t *search; fsPack_t *pack; char packPath[MAX_OSPATH]; int i; //VoiD -S- *.pak support char *path = NULL; char findname[1024]; char **dirnames; int ndirs; char *tmp; //VoiD -E- *.pak support strcpy(fs_gamedir, dir); // // Add the directory to the search path // search = Z_Malloc(sizeof(fsSearchPath_t)); strcpy(search->path, dir); search->path[sizeof(search->path)-1] = 0; search->next = fs_searchPaths; fs_searchPaths = search; // // add any pak files in the format pak0.pak pak1.pak, ... // for (i=0; i<100; i++) // Pooy - paks can now go up to 100 { Com_sprintf (packPath, sizeof(packPath), "%s/pak%i.pak", dir, i); pack = FS_LoadPAK (packPath); if (!pack) continue; search = Z_Malloc (sizeof(fsSearchPath_t)); search->pack = pack; search->next = fs_searchPaths; fs_searchPaths = search; } // // NeVo - pak3's! // add any pk3 files in the format pak0.pk3 pak1.pk3, ... // for (i=0; i<100; i++) // Pooy - paks can now go up to 100 { Com_sprintf (packPath, sizeof(packPath), "%s/pak%i.pk3", dir, i); pack = FS_LoadPK3 (packPath); if (!pack) continue; search = Z_Malloc (sizeof(fsSearchPath_t)); search->pack = pack; search->next = fs_searchPaths; fs_searchPaths = search; } for (i=0; i<2; i++) { // NeVo - Set filetype switch (i) { case 0: // Standard Quake II pack file '.pak' Com_sprintf( findname, sizeof(findname), "%s/%s", dir, "*.pak" ); break; case 1: // Quake III pack file '.pk3' Com_sprintf( findname, sizeof(findname), "%s/%s", dir, "*.pk3" ); break; default: // Is this reachable? // Standard Quake II pack file '.pak' Com_sprintf( findname, sizeof(findname), "%s/%s", dir, "*.pak" ); break; } //VoiD -S- *.pack support tmp = findname; while ( *tmp != 0 ) { if ( *tmp == '\\' ) *tmp = '/'; tmp++; } if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 ) { int j; for ( j=0; j < ndirs-1; j++ ) { // don't reload numbered pak files int k; char buf[16]; char buf2[16]; qboolean numberedpak = false; for (k=0; k<100; k++) { Com_sprintf( buf, sizeof(buf), "/pak%i.pak", k); Com_sprintf( buf2, sizeof(buf2), "/pak%i.pk3", k); if ( strstr(dirnames[j], buf) || strstr(dirnames[j], buf2)) { numberedpak = true; break; } } if (numberedpak) continue; // end Knightmare if ( strrchr( dirnames[j], '/' ) ) { if (i==1) pack = FS_LoadPK3 (dirnames[j]); else pack = FS_LoadPAK (dirnames[j]); if (!pack) continue; search = Z_Malloc (sizeof(fsSearchPath_t)); search->pack = pack; search->next = fs_searchPaths; fs_searchPaths = search; } free( dirnames[j] ); } free( dirnames ); } //VoiD -E- *.pack support } } /* ================= FS_NextPath Allows enumerating all of the directories in the search path ================= */ char *FS_NextPath (char *prevPath) { fsSearchPath_t *search; char *prev; if (!prevPath) return fs_gamedir; prev = fs_gamedir; for (search = fs_searchPaths; search; search = search->next) { if (search->pack) continue; if (prevPath == prev) return search->path; prev = search->path; } return NULL; } /* ================= FS_Path_f ================= */ void FS_Path_f (void) { fsSearchPath_t *search; fsHandle_t *handle; fsLink_t *link; int totalFiles = 0, i; Com_Printf("Current search path:\n"); for (search = fs_searchPaths; search; search = search->next) { if (search->pack) { Com_Printf("%s (%i files)\n", search->pack->name, search->pack->numFiles); totalFiles += search->pack->numFiles; } else Com_Printf("%s\n", search->path); } //Com_Printf("\n"); for (i = 0, handle = fs_handles; i < MAX_HANDLES; i++, handle++) { if (handle->file || handle->zip) Com_Printf("Handle %i: %s\n", i + 1, handle->name); } for (i = 0, link = fs_links; link; i++, link = link->next) Com_Printf("Link %i: %s -> %s\n", i, link->from, link->to); Com_Printf("-------------------------------------\n"); Com_Printf("%i files in PAK/PK3 files\n\n", totalFiles); } /* ================= FS_Startup TODO: close open files for game dir ================= */ void FS_Startup (void) { if (strstr(fs_gamedirvar->string, "..") || strstr(fs_gamedirvar->string, ".") || strstr(fs_gamedirvar->string, "/") || strstr(fs_gamedirvar->string, "\\") || strstr(fs_gamedirvar->string, ":") || !fs_gamedirvar->string[0]) { //Com_Printf("Invalid game directory\n"); Cvar_ForceSet("game", BASEDIRNAME); } // Check for game override if (stricmp(fs_gamedirvar->string, fs_currentGame)) { fsSearchPath_t *next; fsPack_t *pack; // Free up any current game dir info while (fs_searchPaths != fs_baseSearchPaths) { if (fs_searchPaths->pack) { pack = fs_searchPaths->pack; if (pack->pak) fclose(pack->pak); if (pack->pk3) unzClose(pack->pk3); Z_Free(pack->files); Z_Free(pack); } next = fs_searchPaths->next; Z_Free(fs_searchPaths); fs_searchPaths = next; } if (!stricmp(fs_gamedirvar->string, BASEDIRNAME)) // Don't add baseq2 again strcpy(fs_gamedir, fs_basedir->string); else { // Add the directories FS_AddGameDirectory(va("%s/%s", fs_homepath->string, fs_gamedirvar->string)); } } strcpy(fs_currentGame, fs_gamedirvar->string); FS_Path_f(); } /* ================= FS_Init ================= */ void FS_Dir_f (void); void FS_Link_f (void); char *Sys_GetCurrentDirectory (void); void FS_InitFilesystem (void) { // Register our commands and cvars Cmd_AddCommand("path", FS_Path_f); Cmd_AddCommand("link", FS_Link_f); Cmd_AddCommand("dir", FS_Dir_f); Com_Printf("\n----- Filesystem Initialization -----\n"); // basedir // allows the game to run from outside the data tree fs_basedir = Cvar_Get ("basedir", ".", CVAR_NOSET); // cddir // Logically concatenates the cddir after the basedir for // allows the game to run from outside the data tree fs_cddir = Cvar_Get("cddir", "", CVAR_NOSET); if (fs_cddir->string[0]) FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_cddir->string) ); // start up with baseq2 by default FS_AddGameDirectory (va("%s/"BASEDIRNAME, fs_basedir->string) ); // any set gamedirs will be freed up to here fs_baseSearchPaths = fs_searchPaths; strcpy(fs_currentGame, BASEDIRNAME); // check for game override fs_homepath = Cvar_Get("homepath", Sys_GetCurrentDirectory(), CVAR_NOSET); fs_debug = Cvar_Get("fs_debug", "0", 0); fs_gamedirvar = Cvar_Get ("game", "", CVAR_LATCH|CVAR_SERVERINFO); if (fs_gamedirvar->string[0]) FS_SetGamedir (fs_gamedirvar->string); FS_Path_f(); // output path data } /* ================= FS_Shutdown ================= */ void FS_Shutdown (void) { fsHandle_t *handle; fsSearchPath_t *next; fsPack_t *pack; int i; Cmd_RemoveCommand("dir"); //Cmd_RemoveCommand("fdir"); Cmd_RemoveCommand("link"); Cmd_RemoveCommand("path"); // Close all files for (i = 0, handle = fs_handles; i < MAX_HANDLES; i++, handle++) { if (handle->file) fclose(handle->file); if (handle->zip) { unzCloseCurrentFile(handle->zip); unzClose(handle->zip); } } // Free the search paths while (fs_searchPaths) { if (fs_searchPaths->pack) { pack = fs_searchPaths->pack; if (pack->pak) fclose(pack->pak); if (pack->pk3) unzClose(pack->pk3); Z_Free(pack->files); Z_Free(pack); } next = fs_searchPaths->next; Z_Free(fs_searchPaths); fs_searchPaths = next; } } /* ================ FS_SetGamedir Sets the gamedir and path to a different directory. ================ */ void FS_SetGamedir (char *dir) { fsSearchPath_t *next; if (strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\") || strstr(dir, ":") ) { Com_Printf ("Gamedir should be a single filename, not a path\n"); return; } // // free up any current game dir info // while (fs_searchPaths != fs_baseSearchPaths) { if (fs_searchPaths->pack) { if (fs_searchPaths->pack->pak) fclose(fs_searchPaths->pack->pak); if (fs_searchPaths->pack->pk3) unzClose(fs_searchPaths->pack->pk3); Z_Free (fs_searchPaths->pack->files); Z_Free (fs_searchPaths->pack); } next = fs_searchPaths->next; Z_Free (fs_searchPaths); fs_searchPaths = next; } // // flush all data, so it will be forced to reload // if (dedicated && !dedicated->value) Cbuf_AddText ("vid_restart\nsnd_restart\n"); Com_sprintf (fs_gamedir, sizeof(fs_gamedir), "%s/%s", fs_basedir->string, dir); if (!strcmp(dir,BASEDIRNAME) || (*dir == 0)) { Cvar_FullSet ("gamedir", "", CVAR_SERVERINFO|CVAR_NOSET); Cvar_FullSet ("game", "", CVAR_LATCH|CVAR_SERVERINFO); } else { Cvar_FullSet ("gamedir", dir, CVAR_SERVERINFO|CVAR_NOSET); if (fs_cddir->string[0]) FS_AddGameDirectory (va("%s/%s", fs_cddir->string, dir) ); FS_AddGameDirectory (va("%s/%s", fs_basedir->string, dir) ); } } /* ================ FS_Link_f Creates a filelink_t ================ */ void FS_Link_f (void) { fsLink_t *l, **prev; if (Cmd_Argc() != 3) { Com_Printf ("USAGE: link \n"); return; } // see if the link already exists prev = &fs_links; for (l=fs_links ; l ; l=l->next) { if (!strcmp (l->from, Cmd_Argv(1))) { Z_Free (l->to); if (!strlen(Cmd_Argv(2))) { // delete it *prev = l->next; Z_Free (l->from); Z_Free (l); return; } l->to = CopyString (Cmd_Argv(2)); return; } prev = &l->next; } // create a new link l = Z_Malloc(sizeof(*l)); l->next = fs_links; fs_links = l; l->from = CopyString(Cmd_Argv(1)); l->length = strlen(l->from); l->to = CopyString(Cmd_Argv(2)); } /* ============= FS_ExecAutoexec ============= */ void FS_ExecAutoexec (void) { char *dir; char name [MAX_QPATH]; dir = Cvar_VariableString("gamedir"); if (*dir) Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, dir); else Com_sprintf(name, sizeof(name), "%s/%s/autoexec.cfg", fs_basedir->string, BASEDIRNAME); if (Sys_FindFirst(name, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM)) Cbuf_AddText ("exec autoexec.cfg\n"); Sys_FindClose(); } /* ================ FS_ListFiles ================ */ char **FS_ListFiles( char *findname, int *numfiles, unsigned musthave, unsigned canthave ) { char *s; int nfiles = 0; char **list = 0; s = Sys_FindFirst( findname, musthave, canthave ); while ( s ) { if ( s[strlen(s)-1] != '.' ) nfiles++; s = Sys_FindNext( musthave, canthave ); } Sys_FindClose (); if ( !nfiles ) return NULL; nfiles++; // add space for a guard *numfiles = nfiles; list = malloc( sizeof( char * ) * nfiles ); memset( list, 0, sizeof( char * ) * nfiles ); s = Sys_FindFirst( findname, musthave, canthave ); nfiles = 0; while ( s ) { if ( s[strlen(s)-1] != '.' ) { list[nfiles] = strdup( s ); #ifdef _WIN32 strlwr( list[nfiles] ); #endif nfiles++; } s = Sys_FindNext( musthave, canthave ); } Sys_FindClose (); return list; } /* ================ FS_Dir_f ================ */ void FS_Dir_f( void ) { char *path = NULL; char findname[1024]; char wildcard[1024] = "*.*"; char **dirnames; int ndirs; if ( Cmd_Argc() != 1 ) { strcpy( wildcard, Cmd_Argv( 1 ) ); } while ( ( path = FS_NextPath( path ) ) != NULL ) { char *tmp = findname; Com_sprintf( findname, sizeof(findname), "%s/%s", path, wildcard ); while ( *tmp != 0 ) { if ( *tmp == '\\' ) *tmp = '/'; tmp++; } Com_Printf( "Directory of %s\n", findname ); Com_Printf( "----\n" ); if ( ( dirnames = FS_ListFiles( findname, &ndirs, 0, 0 ) ) != 0 ) { int i; for ( i = 0; i < ndirs-1; i++ ) { if ( strrchr( dirnames[i], '/' ) ) Com_Printf( "%s\n", strrchr( dirnames[i], '/' ) + 1 ); else Com_Printf( "%s\n", dirnames[i] ); free( dirnames[i] ); } free( dirnames ); } Com_Printf( "\n" ); }; }