+ 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_connected || 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)
+{
+ qboolean success;
+ stringlist_t list;
+ stringlistinit(&list);
+ listdirectory(&list, va("%s%s/", fs_basedir, gamedir));
+ success = list.numstrings > 0;
+ stringlistfreecontents(&list);
+ return success;
+}
+
+
+/*
+================
+FS_Init
+================
+*/
+void FS_Init (void)
+{
+ int i;
+
+ fs_mempool = Mem_AllocPool("file management", 0, NULL);
+
+ 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
+ strlcpy(fs_basedir, "", sizeof(fs_basedir));
+
+#ifdef MACOSX
+ // FIXME: is there a better way to find the directory outside the .app?
+ if (strstr(com_argv[0], ".app/"))
+ {
+ 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;
+ }
+#endif
+#endif
+
+ PK3_OpenLibrary ();
+
+ // -basedir <path>
+ // Overrides the system supplied base directory (under GAMENAME)
+// COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
+ i = COM_CheckParm ("-basedir");
+ if (i && i < com_argc-1)
+ {
+ strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
+ i = (int)strlen (fs_basedir);
+ if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
+ fs_basedir[i-1] = 0;
+ }
+
+ // 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));
+
+ if (!FS_CheckGameDir(gamedirname1))
+ Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
+
+ 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 && fs_numgamedirs < MAX_GAMEDIRS;i++)
+ {
+ if (!com_argv[i])
+ continue;
+ if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
+ {
+ i++;
+ 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++;
+ }
+ }
+
+ // generate the searchpath
+ FS_Rescan();
+}
+
+void FS_Init_Commands(void)
+{
+ Cvar_RegisterVariable (&scr_screenshot_name);
+ Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
+
+ 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");
+}
+
+/*
+================
+FS_Shutdown
+================
+*/
+void FS_Shutdown (void)
+{
+ Mem_FreePool (&fs_mempool);
+}
+
+/*
+====================
+FS_SysOpen
+
+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)
+{
+ qfile_t* file;
+ int mod, opt;
+ unsigned int ind;
+
+ // Parse the mode string
+ switch (mode[0])
+ {
+ case 'r':
+ mod = O_RDONLY;
+ opt = 0;
+ break;
+ case 'w':
+ mod = O_WRONLY;
+ opt = O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ mod = O_WRONLY;
+ opt = O_CREAT | O_APPEND;
+ break;
+ default:
+ Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
+ return NULL;
+ }
+ for (ind = 1; mode[ind] != '\0'; ind++)
+ {
+ switch (mode[ind])
+ {
+ case '+':
+ mod = O_RDWR;
+ break;
+ case 'b':
+ opt |= O_BINARY;
+ break;
+ default:
+ Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
+ filepath, mode, mode[ind]);
+ }
+ }
+
+ if (nonblocking)
+ opt |= O_NONBLOCK;
+
+ file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
+ memset (file, 0, sizeof (*file));
+ file->ungetc = EOF;
+
+ file->handle = open (filepath, mod | opt, 0666);
+ if (file->handle < 0)
+ {
+ Mem_Free (file);
+ return NULL;
+ }
+
+ 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)
+ file->position = file->real_length;
+ else
+ lseek (file->handle, 0, SEEK_SET);
+
+ return file;
+}
+
+
+/*
+===========
+FS_OpenPackedFile
+
+Open a packed file using its package file descriptor
+===========
+*/
+qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
+{
+ packfile_t *pfile;
+ int dup_handle;
+ qfile_t* file;
+
+ pfile = &pack->files[pack_ind];
+
+ // If we don't have the true offset, get it now
+ if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
+ if (!PK3_GetTrueFileOffset (pfile, pack))
+ return NULL;
+
+ // No Zlib DLL = no compressed files
+ if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
+ {
+ Con_Printf("WARNING: can't open the compressed file %s\n"
+ "You need the Zlib DLL to use compressed files\n",
+ pfile->name);
+ return NULL;
+ }
+
+ // 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);
+ return NULL;
+ }
+
+ dup_handle = dup (pack->handle);
+ if (dup_handle < 0)
+ {
+ Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
+ return NULL;
+ }
+
+ file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
+ memset (file, 0, sizeof (*file));
+ file->handle = dup_handle;
+ file->flags = QFILE_FLAG_PACKED;
+ file->real_length = pfile->realsize;
+ file->offset = pfile->offset;
+ file->position = 0;
+ file->ungetc = EOF;
+
+ if (pfile->flags & PACKFILE_FLAG_DEFLATED)
+ {
+ ztoolkit_t *ztk;
+
+ file->flags |= QFILE_FLAG_DEFLATED;
+
+ // We need some more variables
+ ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
+
+ ztk->comp_length = pfile->packsize;
+
+ // Initialize zlib stream
+ ztk->zstream.next_in = ztk->input;
+ ztk->zstream.avail_in = 0;
+
+ /* From Zlib's "unzip.c":
+ *
+ * windowBits is passed < 0 to tell that there is no zlib header.
+ * Note that in this case inflate *requires* an extra "dummy" byte
+ * after the compressed stream in order to complete decompression and
+ * return Z_STREAM_END.
+ * In unzip, i don't wait absolutely Z_STREAM_END because I known the
+ * size of both compressed and uncompressed data
+ */
+ if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
+ {
+ Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
+ close(dup_handle);
+ Mem_Free(file);
+ return NULL;
+ }
+
+ ztk->zstream.next_out = file->buff;
+ ztk->zstream.avail_out = sizeof (file->buff);
+
+ file->ztk = ztk;