Boston, MA 02111-1307, USA
*/
+// on UNIX platforms we need to define this so that video saving does not cause a SIGFSZ (file size) signal when a video clip exceeds 2GB
+#define _FILE_OFFSET_BITS 64
+
#include "quakedef.h"
#include <limits.h>
int ignorecase; // PK3 ignores case
int numfiles;
packfile_t *files;
- struct pack_s *next;
} pack_t;
void FS_Dir_f(void);
void FS_Ls_f(void);
-static const char *FS_FileExtension (const char *in);
static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
fs_offset_t offset, fs_offset_t packsize,
mempool_t *fs_mempool;
-pack_t *packlist = NULL;
-
searchpath_t *fs_searchpaths = NULL;
#define MAX_FILES_IN_PACK 65536
char fs_gamedir[MAX_OSPATH];
char fs_basedir[MAX_OSPATH];
-qboolean fs_modified; // set true if using non-id files
+// list of active game directories (empty if not running a mod)
+#define MAX_GAMEDIRS 16
+int fs_numgamedirs = 0;
+char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
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)"};
pack->handle = packhandle;
pack->numfiles = eocd.nbentries;
pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
- pack->next = packlist;
- packlist = pack;
real_nb_files = PK3_BuildFileList (pack, &eocd);
if (real_nb_files < 0)
pack->handle = packhandle;
pack->numfiles = 0;
pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
- pack->next = packlist;
- packlist = pack;
// parse the directory
for (i = 0;i < numpackfiles;i++)
}
}
- // add any PK3 package in the director
+ // add any PK3 package in the directory
for (current = list;current;current = current->next)
{
if (!strcasecmp(FS_FileExtension(current->text), "pk3"))
FS_AddPack_Fullpath(pakfile, NULL, false);
}
}
+
freedirectory(list);
// Add the directory to the search path
FS_FileExtension
============
*/
-static const char *FS_FileExtension (const char *in)
+const char *FS_FileExtension (const char *in)
{
const char *separator, *backslash, *colon, *dot;
}
+/*
+============
+FS_FileWithoutPath
+============
+*/
+const char *FS_FileWithoutPath (const char *in)
+{
+ const char *separator, *backslash, *colon;
+
+ separator = strrchr(in, '/');
+ backslash = strrchr(in, '\\');
+ if (!separator || separator < backslash)
+ separator = backslash;
+ colon = strrchr(in, ':');
+ if (!separator || separator < colon)
+ separator = colon;
+ return separator ? separator + 1 : in;
+}
+
+
+/*
+================
+FS_ClearSearchPath
+================
+*/
+void FS_ClearSearchPath (void)
+{
+ while (fs_searchpaths)
+ {
+ searchpath_t *search = fs_searchpaths;
+ fs_searchpaths = search->next;
+ if (search->pack)
+ {
+ if (search->pack->files)
+ Mem_Free(search->pack->files);
+ Mem_Free(search->pack);
+ }
+ Mem_Free(search);
+ }
+}
+
+
+/*
+================
+FS_Rescan
+================
+*/
+void FS_Rescan (void)
+{
+ int i;
+ qboolean fs_modified = false;
+
+ FS_ClearSearchPath();
+
+ // 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));
+
+ // add the game-specific path, if any
+ // (only used for mission packs and the like, which should set fs_modified)
+ if (gamedirname2)
+ {
+ fs_modified = true;
+ FS_AddGameHierarchy (gamedirname2);
+ }
+
+ // -game <gamedir>
+ // Adds basedir/gamedir as an override game
+ // LordHavoc: now supports multiple -game directories
+ // set the com_modname (reported in server info)
+ 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));
+ }
+
+ // set the default screenshot name to either the mod name or the
+ // gamemode screenshot name
+ if (strcmp(com_modname, gamedirname1))
+ Cvar_SetQuick (&scr_screenshot_name, com_modname);
+ else
+ Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
+
+ // If "-condebug" is in the command line, remove the previous log file
+ if (COM_CheckParm ("-condebug") != 0)
+ unlink (va("%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_modified)
+ Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\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");
+ }
+}
+
+void FS_Rescan_f(void)
+{
+ FS_Rescan();
+}
+
+/*
+================
+FS_ChangeGameDirs
+================
+*/
+extern void Host_SaveConfig_f (void);
+extern void Host_LoadConfig_f (void);
+qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
+{
+ int i;
+
+ if (fs_numgamedirs == numgamedirs)
+ {
+ for (i = 0;i < numgamedirs;i++)
+ if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
+ break;
+ if (i == numgamedirs)
+ return true; // already using this set of gamedirs, do nothing
+ }
+
+ if (numgamedirs > MAX_GAMEDIRS)
+ {
+ if (complain)
+ Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
+ return false; // too many gamedirs
+ }
+
+ for (i = 0;i < numgamedirs;i++)
+ {
+ // if string is nasty, reject it
+ if(FS_CheckNastyPath(gamedirs[i], true))
+ {
+ if (complain)
+ Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
+ return false; // nasty gamedirs
+ }
+ }
+
+ for (i = 0;i < numgamedirs;i++)
+ {
+ if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
+ {
+ if (complain)
+ Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
+ return false; // missing gamedirs
+ }
+ }
+
+ Host_SaveConfig_f();
+
+ fs_numgamedirs = numgamedirs;
+ for (i = 0;i < fs_numgamedirs;i++)
+ strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
+
+ // reinitialize filesystem to detect the new paks
+ FS_Rescan();
+
+ // exec the new config
+ Host_LoadConfig_f();
+
+ // reinitialize the loaded sounds
+ S_Reload_f();
+
+ // reinitialize renderer (this reloads hud/console background/etc)
+ R_Modules_Restart();
+
+ return true;
+}
+
+/*
+================
+FS_GameDir_f
+================
+*/
+void FS_GameDir_f (void)
+{
+ int i;
+ int numgamedirs;
+ char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
+
+ if (Cmd_Argc() < 2)
+ {
+ Con_Printf("gamedirs active:");
+ for (i = 0;i < fs_numgamedirs;i++)
+ Con_Printf(" %s", fs_gamedirs[i]);
+ Con_Printf("\n");
+ return;
+ }
+
+ numgamedirs = Cmd_Argc() - 1;
+ if (numgamedirs > MAX_GAMEDIRS)
+ {
+ Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
+ return;
+ }
+
+ for (i = 0;i < numgamedirs;i++)
+ strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
+
+ // allow gamedir change during demo loop
+ if (cls.demoplayback)
+ CL_Disconnect();
+
+ if (cls.state != ca_disconnected || sv.active)
+ {
+ // actually, changing during game would work fine, but would be stupid
+ Con_Printf("Can not change gamedir while client is connected or server is running!\n");
+ return;
+ }
+
+ FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
+}
+
+
+/*
+================
+FS_CheckGameDir
+================
+*/
+qboolean FS_CheckGameDir(const char *gamedir)
+{
+ stringlist_t *list = listdirectory(va("%s%s/", fs_basedir, gamedir));
+ if (list)
+ freedirectory(list);
+ return list != NULL;
+}
+
+
/*
================
FS_Init
void FS_Init (void)
{
int i;
- searchpath_t *search;
fs_mempool = Mem_AllocPool("file management", 0, NULL);
- strcpy(fs_gamedir, "");
+ strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
// If the base directory is explicitly defined by the compilation process
#ifdef DP_FS_BASEDIR
- strcpy(fs_basedir, DP_FS_BASEDIR);
+ strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
#else
- strcpy(fs_basedir, "");
+ strlcpy(fs_basedir, "", sizeof(fs_basedir));
#ifdef MACOSX
// FIXME: is there a better way to find the directory outside the .app?
if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
strlcat(fs_basedir, "/", sizeof(fs_basedir));
- // -path <dir or packfile> [<dir or packfile>] ...
- // Fully specifies the exact search path, overriding the generated one
-// COMMANDLINEOPTION: Filesystem: -path <path ..> specifies the full search path manually, overriding the generated one, example: -path c:\quake\id1 c:\quake\pak0.pak c:\quake\pak1.pak (not recommended)
- i = COM_CheckParm ("-path");
- if (i)
- {
- fs_modified = true;
- while (++i < com_argc)
- {
- if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
- break;
-
- if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
- {
- search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
- strlcpy (search->filename, com_argv[i], sizeof (search->filename));
- search->next = fs_searchpaths;
- fs_searchpaths = search;
- }
- }
- return;
- }
-
- // add the game-specific paths
- // gamedirname1 (typically id1)
- FS_AddGameHierarchy (gamedirname1);
-
- // add the game-specific path, if any
- if (gamedirname2)
- {
- fs_modified = true;
- FS_AddGameHierarchy (gamedirname2);
- }
+ if (!FS_CheckGameDir(gamedirname1))
+ Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
- // set the com_modname (reported in server info)
- strlcpy(com_modname, gamedirname1, sizeof(com_modname));
+ if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
+ Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
// -game <gamedir>
// Adds basedir/gamedir as an override game
// LordHavoc: now supports multiple -game directories
- for (i = 1;i < com_argc;i++)
+ for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
{
if (!com_argv[i])
continue;
if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
{
i++;
- fs_modified = true;
- FS_AddGameHierarchy (com_argv[i]);
- // update the com_modname
- strlcpy (com_modname, com_argv[i], sizeof (com_modname));
+ if (FS_CheckNastyPath(com_argv[i], true))
+ Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
+ if (!FS_CheckGameDir(com_argv[i]))
+ Sys_Error("-game %s%s/ not found!\n", fs_basedir, com_argv[i]);
+ // add the gamedir to the list of active gamedirs
+ strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
+ fs_numgamedirs++;
}
}
- // If "-condebug" is in the command line, remove the previous log file
- if (COM_CheckParm ("-condebug") != 0)
- unlink (va("%s/qconsole.log", fs_gamedir));
+ // generate the searchpath
+ FS_Rescan();
}
void FS_Init_Commands(void)
{
Cvar_RegisterVariable (&scr_screenshot_name);
+ 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");
Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
-
- // set the default screenshot name to either the mod name or the
- // gamemode screenshot name
- if (fs_modified)
- Cvar_SetQuick (&scr_screenshot_name, com_modname);
- else
- Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
}
/*
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, pfile->offset);
+ pfile->name, pack->filename, (int) pfile->offset);
return NULL;
}
or are just not a good idea for a mod to be using.
====================
*/
-int FS_CheckNastyPath (const char *path)
+int FS_CheckNastyPath (const char *path, qboolean isgamedir)
{
+ // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
+ if (!path[0])
+ return 2;
+
// Windows: don't allow \ in filenames (windows-only), period.
// (on Windows \ is a directory separator, but / is also supported)
if (strstr(path, "\\"))
if (strstr(path, "//"))
return 1; // non-portable attempt to go to parent directory
- // all: don't allow going to current directory (./) or parent directory (../ or /../)
- if (strstr(path, "./"))
+ // all: don't allow going to parent directory (../ or /../)
+ if (strstr(path, ".."))
return 2; // attempt to go outside the game directory
// Windows and UNIXes: don't allow absolute paths
if (path[0] == '/')
return 2; // attempt to go outside the game directory
+ // all: don't allow . characters before the last slash (it should only be used in filenames, not path elements), this catches all imaginable cases of ./, ../, .../, etc
+ if (strchr(path, '.'))
+ {
+ if (isgamedir)
+ {
+ // gamedir is entirely path elements, so simply forbid . entirely
+ return 2;
+ }
+ if (strchr(path, '.') < strrchr(path, '/'))
+ return 2; // possible attempt to go outside the game directory
+ }
+
+ // all: forbid trailing slash on gamedir
+ if (isgamedir && path[strlen(path)-1] == '/')
+ return 2;
+
+ // all: forbid leading dot on any filename for any reason
+ if (strstr(path, "/."))
+ return 2; // attempt to go outside the game directory
+
// after all these checks we're pretty sure it's a / separated filename
// and won't do much if any harm
return false;
*/
qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
{
- if (FS_CheckNastyPath(filepath))
+ if (FS_CheckNastyPath(filepath, false))
{
Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
return NULL;
}
+/*
+====================
+FS_FileSize
+
+Give the total size of a file
+====================
+*/
+fs_offset_t FS_FileSize (qfile_t* file)
+{
+ return file->real_length;
+}
+
+
/*
====================
FS_Purge
void FS_StripExtension (const char *in, char *out, size_t size_out)
{
char *last = NULL;
+ char currentchar;
if (size_out == 0)
return;
- while (*in && size_out > 1)
+ while ((currentchar = *in) && size_out > 1)
{
- if (*in == '.')
+ if (currentchar == '.')
last = out;
- else if (*in == '/' || *in == '\\' || *in == ':')
+ else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
last = NULL;
- *out++ = *in++;
+ *out++ = currentchar;
+ in++;
size_out--;
}
if (last)
pak = searchpath->pack;
for (i = 0;i < pak->numfiles;i++)
{
- strcpy(temp, pak->files[i].name);
+ strlcpy(temp, pak->files[i].name, sizeof(temp));
while (temp[0])
{
if (matchpattern(temp, (char *)pattern, true))
numchars = 0;
for (listtemp = liststart;listtemp;listtemp = listtemp->next)
{
+ size_t textlen;
search->filenames[numfiles] = search->filenamesbuffer + numchars;
- strcpy(search->filenames[numfiles], listtemp->text);
+ textlen = strlen(listtemp->text) + 1;
+ memcpy(search->filenames[numfiles], listtemp->text, textlen);
numfiles++;
- numchars += (int)strlen(listtemp->text) + 1;
+ numchars += (int)textlen;
}
if (liststart)
stringlistfree(liststart);
else
return 0;
}
+
+/*
+====================
+FS_IsRegisteredQuakePack
+
+Look for a proof of purchase file file in the requested package
+
+If it is found, this file should NOT be downloaded.
+====================
+*/
+qboolean FS_IsRegisteredQuakePack(const char *name)
+{
+ searchpath_t *search;
+ pack_t *pak;
+
+ // 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))
+ {
+ int (*strcmp_funct) (const char* str1, const char* str2);
+ int left, right, middle;
+
+ pak = search->pack;
+ strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
+
+ // Look for the file (binary search)
+ left = 0;
+ right = pak->numfiles - 1;
+ while (left <= right)
+ {
+ int diff;
+
+ middle = (left + right) / 2;
+ diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
+
+ // Found it
+ if (!diff)
+ return true;
+
+ // If we're too far in the list
+ if (diff > 0)
+ right = middle - 1;
+ else
+ left = middle + 1;
+ }
+
+ // we found the requested pack but it is not registered quake
+ return false;
+ }
+ }
+
+ return false;
+}
+
+int FS_CRCFile(const char *filename, size_t *filesizepointer)
+{
+ int crc = -1;
+ unsigned char *filedata;
+ fs_offset_t filesize;
+ if (filesizepointer)
+ *filesizepointer = 0;
+ if (!filename || !*filename)
+ return crc;
+ filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
+ if (filedata)
+ {
+ if (filesizepointer)
+ *filesizepointer = filesize;
+ crc = CRC_Block(filedata, filesize);
+ Mem_Free(filedata);
+ }
+ return crc;
+}
+