+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;
+ 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[fs_all_gamedirs_count].name));
+ strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].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, "../Documents/", MAX_OSPATH);
+ 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
+
+
+#if !defined(__IPHONEOS__)
+
+#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