+ // set the com_modname (reported in server info)
+ *gamedirbuf = 0;
+ 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));
+ if(i)
+ strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
+ else
+ strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
+ }
+ Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
+
+ // add back the selfpack as new first item
+ FS_AddSelfPack();
+
+ // 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((i = COM_CheckParm("-modname")) && i < com_argc - 1)
+ strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
+
+ // If "-condebug" is in the command line, remove the previous log file
+ if (COM_CheckParm ("-condebug") != 0)
+ unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
+
+ // look for the pop.lmp file and set registered to true if it is found
+ if (FS_FileExists("gfx/pop.lmp"))
+ Cvar_Set ("registered", "1");
+ switch(gamemode)
+ {
+ case GAME_NORMAL:
+ case GAME_HIPNOTIC:
+ case GAME_ROGUE:
+ if (!registered.integer)
+ {
+ 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
+ Con_Print("Playing registered version.\n");
+ break;
+ case GAME_STEELSTORM:
+ if (registered.integer)
+ Con_Print("Playing registered version.\n");
+ else
+ Con_Print("Playing shareware version.\n");
+ break;
+ default:
+ break;
+ }
+
+ // unload all wads so that future queries will return the new data
+ W_UnloadAll();
+}
+
+static void FS_Rescan_f(void)
+{
+ FS_Rescan();
+}
+
+/*
+================
+FS_ChangeGameDirs
+================
+*/
+extern qboolean vid_opened;
+qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
+{
+ int i;
+ const char *p;
+
+ 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
+ p = FS_CheckGameDir(gamedirs[i]);
+ if(!p)
+ {
+ if (complain)
+ Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
+ return false; // nasty gamedirs
+ }
+ if(p == fs_checkgamedir_missing && failmissing)
+ {
+ if (complain)
+ Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
+ return false; // missing gamedirs
+ }
+ }
+
+ Host_SaveConfig();
+
+ 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();
+
+ if (cls.demoplayback)
+ {
+ CL_Disconnect_f();
+ cls.demonum = 0;
+ }
+
+ // unload all sounds so they will be reloaded from the new files as needed
+ S_UnloadAllSounds_f();
+
+ // close down the video subsystem, it will start up again when the config finishes...
+ VID_Stop();
+ vid_opened = false;
+
+ // restart the video subsystem after the config is executed
+ Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
+
+ return true;
+}
+
+/*
+================
+FS_GameDir_f
+================
+*/
+static 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]));
+
+ if ((cls.state == ca_connected && !cls.demoplayback) || 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;
+ }
+
+ // halt demo playback to close the file
+ CL_Disconnect();
+
+ FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
+}
+
+static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
+{
+ qboolean success;
+ qfile_t *f;
+ stringlist_t list;
+ fs_offset_t n;
+ char vabuf[1024];
+
+ stringlistinit(&list);
+ listdirectory(&list, gamedir, "");
+ success = list.numstrings > 0;
+ stringlistfreecontents(&list);
+
+ if(success)
+ {
+ f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
+ if(f)
+ {
+ n = FS_Read (f, buf, buflength - 1);
+ if(n >= 0)
+ buf[n] = 0;
+ else
+ *buf = 0;
+ FS_Close(f);
+ }
+ else
+ *buf = 0;
+ return buf;
+ }
+
+ return NULL;
+}
+
+/*
+================
+FS_CheckGameDir
+================
+*/
+const char *FS_CheckGameDir(const char *gamedir)
+{
+ const char *ret;
+ char buf[8192];
+ char vabuf[1024];
+
+ if (FS_CheckNastyPath(gamedir, true))
+ return NULL;
+
+ ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
+ if(ret)
+ {
+ if(!*ret)
+ {
+ // get description from basedir
+ ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
+ if(ret)
+ return ret;
+ return "";
+ }
+ return ret;
+ }
+
+ ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
+ if(ret)
+ return ret;
+
+ return fs_checkgamedir_missing;
+}
+
+static void FS_ListGameDirs(void)
+{
+ stringlist_t list, list2;
+ int i, j;
+ const char *info;
+ char vabuf[1024];
+
+ fs_all_gamedirs_count = 0;
+ if(fs_all_gamedirs)
+ Mem_Free(fs_all_gamedirs);
+
+ stringlistinit(&list);
+ listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
+ listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
+ stringlistsort(&list, false);
+
+ stringlistinit(&list2);
+ for(i = 0; i < list.numstrings; ++i)
+ {
+ if(i)
+ if(!strcmp(list.strings[i-1], list.strings[i]))
+ continue;
+ info = FS_CheckGameDir(list.strings[i]);
+ if(!info)
+ continue;
+ if(info == fs_checkgamedir_missing)
+ continue;
+ if(!*info)
+ continue;
+ stringlistappend(&list2, list.strings[i]);
+ }
+ stringlistfreecontents(&list);
+
+ fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
+ for(i = 0; i < list2.numstrings; ++i)
+ {
+ info = FS_CheckGameDir(list2.strings[i]);
+ // all this cannot happen any more, but better be safe than sorry
+ if(!info)
+ continue;
+ if(info == fs_checkgamedir_missing)
+ continue;
+ if(!*info)
+ continue;
+ strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
+ strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
+ ++fs_all_gamedirs_count;
+ }
+}
+
+/*
+#ifdef WIN32
+#pragma comment(lib, "shell32.lib")
+#include <ShlObj.h>
+#endif
+*/
+
+/*
+================
+FS_Init_SelfPack
+================
+*/
+void FS_Init_SelfPack (void)
+{
+ PK3_OpenLibrary ();
+ fs_mempool = Mem_AllocPool("file management", 0, NULL);
+ if(com_selffd >= 0)
+ {
+ fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
+ if(fs_selfpack)
+ {
+ char *buf, *q;
+ const char *p;
+ FS_AddSelfPack();
+ buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
+ if(buf)
+ {
+ const char **new_argv;
+ int i = 0;
+ int args_left = 256;
+ new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
+ if(com_argc == 0)
+ {
+ new_argv[0] = "dummy";
+ com_argc = 1;
+ }
+ else
+ {
+ memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc);
+ }
+ p = buf;
+ while(COM_ParseToken_Console(&p))
+ {
+ size_t sz = strlen(com_token) + 1; // shut up clang
+ if(i >= args_left)
+ break;
+ q = (char *)Mem_Alloc(fs_mempool, sz);
+ strlcpy(q, com_token, sz);
+ new_argv[com_argc + i] = q;
+ ++i;
+ }
+ new_argv[i+com_argc] = NULL;
+ com_argv = new_argv;
+ com_argc = com_argc + i;
+ }
+ Mem_Free(buf);
+ }
+ }
+}
+
+static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
+{
+#if defined(__IPHONEOS__)
+ if (userdirmode == USERDIRMODE_HOME)
+ {
+ // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
+ // fs_userdir stores configurations to the Documents folder of the app
+ strlcpy(userdir, maxlength, "../Documents/");
+ return 1;
+ }
+ return -1;
+
+#elif defined(WIN32)
+ char *homedir;
+#if _MSC_VER >= 1400
+ size_t homedirlen;
+#endif
+ TCHAR mydocsdir[MAX_PATH + 1];
+ wchar_t *savedgamesdirw;
+ char savedgamesdir[MAX_OSPATH];
+ int fd;
+ char vabuf[1024];
+
+ userdir[0] = 0;
+ switch(userdirmode)
+ {
+ default:
+ return -1;
+ case USERDIRMODE_NOHOME:
+ strlcpy(userdir, fs_basedir, userdirsize);
+ break;
+ case USERDIRMODE_MYGAMES:
+ if (!shfolder_dll)
+ Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
+ mydocsdir[0] = 0;
+ if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
+ {
+ dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
+ break;
+ }
+#if _MSC_VER >= 1400
+ _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
+ if(homedir)
+ {
+ dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+ free(homedir);
+ break;
+ }
+#else
+ homedir = getenv("USERPROFILE");
+ if(homedir)
+ {
+ dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+ break;
+ }
+#endif
+ return -1;
+ case USERDIRMODE_SAVEDGAMES:
+ if (!shell32_dll)
+ Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
+ if (!ole32_dll)
+ Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
+ if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
+ {
+ savedgamesdir[0] = 0;
+ qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+/*
+#ifdef __cplusplus
+ if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
+#else
+ if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
+#endif
+*/
+ if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
+ {
+ memset(savedgamesdir, 0, sizeof(savedgamesdir));
+#if _MSC_VER >= 1400
+ wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
+#else
+ wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
+#endif
+ qCoTaskMemFree(savedgamesdirw);
+ }
+ qCoUninitialize();
+ if (savedgamesdir[0])
+ {
+ dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
+ break;
+ }
+ }
+ return -1;
+ }
+#else
+ int fd;
+ char *homedir;
+ char vabuf[1024];
+ userdir[0] = 0;
+ switch(userdirmode)
+ {
+ default:
+ return -1;
+ case USERDIRMODE_NOHOME:
+ strlcpy(userdir, fs_basedir, userdirsize);
+ break;
+ case USERDIRMODE_HOME:
+ homedir = getenv("HOME");
+ if(homedir)
+ {
+ dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
+ break;
+ }
+ return -1;
+ case USERDIRMODE_SAVEDGAMES:
+ homedir = getenv("HOME");
+ if(homedir)
+ {
+#ifdef MACOSX
+ dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
+#else
+ // the XDG say some files would need to go in:
+ // XDG_CONFIG_HOME (or ~/.config/%s/)
+ // XDG_DATA_HOME (or ~/.local/share/%s/)
+ // XDG_CACHE_HOME (or ~/.cache/%s/)
+ // and also search the following global locations if defined:
+ // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
+ // XDG_DATA_DIRS (normally /usr/share/%s/)
+ // this would be too complicated...
+ return -1;
+#endif
+ break;
+ }
+ return -1;
+ }
+#endif
+
+
+#ifdef WIN32
+ // historical behavior...
+ if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
+ return 0; // don't bother checking if the basedir folder is writable, it's annoying... unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case
+#endif
+
+ // see if we can write to this path (note: won't create path)
+#ifdef WIN32
+ // no access() here, we must try to open the file for appending
+ fd = FS_SysOpenFD(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
+ if(fd >= 0)
+ close(fd);
+#else
+ // on Unix, we don't need to ACTUALLY attempt to open the file
+ if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
+ fd = 1;
+ else
+ fd = 0;
+#endif
+ if(fd >= 0)
+ {
+ return 1; // good choice - the path exists and is writable
+ }
+ else
+ {
+ if (userdirmode == USERDIRMODE_NOHOME)
+ return -1; // path usually already exists, we lack permissions
+ else
+ return 0; // probably good - failed to write but maybe we need to create path
+ }
+}
+
+/*
+================
+FS_Init
+================
+*/
+void FS_Init (void)
+{
+ const char *p;
+ int i;
+
+ *fs_basedir = 0;
+ *fs_userdir = 0;
+ *fs_gamedir = 0;
+
+ // -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;
+ }
+ else
+ {
+// If the base directory is explicitly defined by the compilation process
+#ifdef DP_FS_BASEDIR
+ strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
+#elif defined(MACOSX)
+ // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
+ if (strstr(com_argv[0], ".app/"))
+ {
+ char *split;
+ strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
+ split = strstr(fs_basedir, ".app/");
+ if (split)
+ {
+ struct stat statresult;
+ char vabuf[1024];
+ // truncate to just after the .app/
+ split[5] = 0;
+ // see if gamedir exists in Resources
+ if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
+ {
+ // found gamedir inside Resources, use it
+ strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
+ }
+ else
+ {
+ // no gamedir found in Resources, gamedir is probably
+ // outside the .app, remove .app part of path
+ while (split > fs_basedir && *split != '/')
+ split--;
+ *split = 0;
+ }
+ }
+ }
+#endif
+ }
+
+ // make sure the appending of a path separator won't create an unterminated string
+ memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
+ // 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));
+
+ // Add the personal game directory
+ if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
+ dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
+ else if (COM_CheckParm("-nohome"))
+ *fs_userdir = 0; // user wants roaming installation, no userdir
+ else
+ {
+ int dirmode;
+ int highestuserdirmode = USERDIRMODE_COUNT - 1;
+ int preferreduserdirmode = USERDIRMODE_COUNT - 1;
+ int userdirstatus[USERDIRMODE_COUNT];
+#ifdef WIN32
+ // historical behavior...
+ if (!strcmp(gamedirname1, "id1"))
+ preferreduserdirmode = USERDIRMODE_NOHOME;
+#endif
+ // check what limitations the user wants to impose
+ if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
+ if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
+ if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
+ // gather the status of the possible userdirs
+ for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
+ {
+ userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
+ if (userdirstatus[dirmode] == 1)
+ Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
+ else if (userdirstatus[dirmode] == 0)
+ Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
+ else
+ Con_DPrintf("userdir %i (not applicable)\n", dirmode);
+ }
+ // some games may prefer writing to basedir, but if write fails we
+ // have to search for a real userdir...
+ if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
+ preferreduserdirmode = highestuserdirmode;
+ // check for an existing userdir and continue using it if possible...
+ for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
+ if (userdirstatus[dirmode] == 1)
+ break;
+ // if no existing userdir found, make a new one...
+ if (dirmode == 0 && preferreduserdirmode > 0)
+ for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
+ if (userdirstatus[dirmode] >= 0)
+ break;
+ // and finally, we picked one...
+ FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
+ Con_DPrintf("userdir %i is the winner\n", dirmode);
+ }
+
+ // if userdir equal to basedir, clear it to avoid confusion later
+ if (!strcmp(fs_basedir, fs_userdir))
+ fs_userdir[0] = 0;
+
+ FS_ListGameDirs();
+
+ p = FS_CheckGameDir(gamedirname1);
+ if(!p || p == fs_checkgamedir_missing)
+ Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
+
+ if(gamedirname2)
+ {
+ p = FS_CheckGameDir(gamedirname2);
+ if(!p || p == fs_checkgamedir_missing)
+ Con_Printf("WARNING: 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++)