diff --git a/README.md b/README.md index a434cd34..d1412d3d 100644 --- a/README.md +++ b/README.md @@ -162,10 +162,8 @@ is searched at: - The directory given with the *+set basedir /path/to/quake2_installation/* commandline argument. -Yamagi Quake II has support for an alternative startup config. It may be a good -idea to install it, since it sets some global options to sane defaults. Copy -yq2.cfg to the baseq2/ subdirectory in your gamedata directory. Usually yq2.cfg -can be found somewhere in /usr/share or /usr/local/share. +If you're a package maintainer, please look at our documentation at +[stuff/packaging.md](stuff/packaging.md). ### Compiling from source diff --git a/src/backends/generic/misc.c b/src/backends/generic/misc.c index 61c3ccc3..c41198f7 100644 --- a/src/backends/generic/misc.c +++ b/src/backends/generic/misc.c @@ -29,6 +29,8 @@ #if defined(__linux) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include // readlink(), amongst others +#include "../../common/header/shared.h" + #endif #ifdef __FreeBSD__ @@ -64,11 +66,12 @@ static void SetExecutablePath(char* exePath) exePath[0] = '\0'; } -#elif defined(__linux) || defined(__NetBSD__) || defined(__OpenBSD__) +#elif defined(__linux) || defined(__NetBSD__) // all the platforms that have /proc/$pid/exe or similar that symlink the // real executable - basiscally Linux and the BSDs except for FreeBSD which - // doesn't enable proc by default and has a sysctl() for this + // doesn't enable proc by default and has a sysctl() for this. OpenBSD once + // had /proc but removed it for security reasons. char buf[PATH_MAX] = {0}; #ifdef __linux snprintf(buf, sizeof(buf), "/proc/%d/exe", getpid()); @@ -115,7 +118,13 @@ static void SetExecutablePath(char* exePath) #else -#error "Unsupported Platform!" // feel free to add implementation for your platform and send me a patch + // Several platforms (for example OpenBSD) donn't provide a + // reliable way to determine the executable path. Just return + // an empty string. + exePath[0] = '\0'; + +// feel free to add implementation for your platform and send a pull request. +#warning "SetExecutablePath() is unimplemented on this platform" #endif } @@ -124,18 +133,25 @@ const char *Sys_GetBinaryDir(void) { static char exeDir[PATH_MAX] = {0}; - if(exeDir[0] != '\0') return exeDir; + if(exeDir[0] != '\0') { + return exeDir; + } SetExecutablePath(exeDir); - // cut off executable name - char* lastSlash = strrchr(exeDir, '/'); + if (exeDir[0] == '\0') { + Com_Printf("Couldn't determine executable path. Using ./ instead.\n"); + Q_strlcpy(exeDir, "./", sizeof(exeDir)); + } else { + // cut off executable name + char *lastSlash = strrchr(exeDir, '/'); #ifdef _WIN32 - char* lastBackSlash = strrchr(exeDir, '\\'); - if(lastSlash == NULL || lastBackSlash > lastSlash) lastSlash = lastBackSlash; + char* lastBackSlash = strrchr(exeDir, '\\'); + if(lastSlash == NULL || lastBackSlash > lastSlash) lastSlash = lastBackSlash; #endif // _WIN32 - if(lastSlash != NULL) lastSlash[1] = '\0'; // cut off after last (back)slash + if (lastSlash != NULL) lastSlash[1] = '\0'; // cut off after last (back)slash + } return exeDir; } diff --git a/src/backends/unix/main.c b/src/backends/unix/main.c index af8b2653..ab742309 100644 --- a/src/backends/unix/main.c +++ b/src/backends/unix/main.c @@ -33,6 +33,8 @@ #include "../../common/header/common.h" #include "header/unix.h" +qboolean is_portable; + int main(int argc, char **argv) { @@ -47,6 +49,13 @@ main(int argc, char **argv) /* register signal handler */ registerHandler(); + /* Are we portable? */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "-portable") == 0) { + is_portable = true; + } + } + /* Prevent running Quake II as root. Only very mad minded or stupid people even think about it. :) */ if (getuid() == 0) diff --git a/src/backends/windows/system.c b/src/backends/windows/system.c index 4302bc68..7a0141c5 100644 --- a/src/backends/windows/system.c +++ b/src/backends/windows/system.c @@ -65,6 +65,8 @@ int findhandle; int argc; char *argv[MAX_NUM_ARGVS]; +qboolean is_portable; + /* ================================================================ */ void @@ -647,31 +649,33 @@ void Sys_RedirectStdout(void) { char *cur; - char *home; char *old; + char dir[MAX_OSPATH]; char path_stdout[MAX_OSPATH]; char path_stderr[MAX_OSPATH]; + const char *tmp; - if (portable->value) + if (is_portable) { + tmp = Sys_GetBinaryDir(); + Q_strlcpy(dir, tmp, sizeof(dir)); + } else { + tmp = Sys_GetHomeDir(); + Q_strlcpy(dir, tmp, sizeof(dir)); + } + + if (dir == NULL) { return; } - home = Sys_GetHomeDir(); - - if (home == NULL) - { - return; - } - - cur = old = home; + cur = old = dir; while (cur != NULL) { if ((cur - old) > 1) { *cur = '\0'; - Sys_Mkdir(home); + Sys_Mkdir(dir); *cur = '/'; } @@ -679,8 +683,8 @@ Sys_RedirectStdout(void) cur = strchr(old + 1, '/'); } - snprintf(path_stdout, sizeof(path_stdout), "%s/%s", home, "stdout.txt"); - snprintf(path_stderr, sizeof(path_stderr), "%s/%s", home, "stderr.txt"); + snprintf(path_stdout, sizeof(path_stdout), "%s/%s", dir, "stdout.txt"); + snprintf(path_stderr, sizeof(path_stderr), "%s/%s", dir, "stderr.txt"); freopen(path_stdout, "w", stdout); freopen(path_stderr, "w", stderr); @@ -755,7 +759,21 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, /* Force DPI awareness */ Sys_SetHighDPIMode(); - /* FIXME: No one can see this! */ + /* Parse the command line arguments */ + ParseCommandLine(lpCmdLine); + + /* Are we portable? */ + for (int i = 0; i < argc; i++) { + if (strcmp(argv[i], "-portable") == 0) { + is_portable = true; + } + } + + /* Need to redirect stdout before anything happens. */ +#ifndef DEDICATED_ONLY + Sys_RedirectStdout(); +#endif + printf("Yamagi Quake II v%s\n", YQ2VERSION); printf("=====================\n\n"); @@ -791,12 +809,10 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, printf("Platform: %s\n", YQ2OSTYPE); printf("Architecture: %s\n", YQ2ARCH); + /* Seed PRNG */ randk_seed(); - /* Parse the command line arguments */ - ParseCommandLine(lpCmdLine); - /* Call the initialization code */ Qcommon_Init(argc, argv); diff --git a/src/client/cl_keyboard.c b/src/client/cl_keyboard.c index 42be42e5..2b3c26c1 100644 --- a/src/client/cl_keyboard.c +++ b/src/client/cl_keyboard.c @@ -785,7 +785,7 @@ Key_WriteConsoleHistory() int i; char path[MAX_OSPATH]; - if (portable->value) + if (is_portable) { Com_sprintf(path, sizeof(path), "%sconsole_history.txt", Sys_GetBinaryDir()); } @@ -832,7 +832,7 @@ Key_ReadConsoleHistory() char path[MAX_OSPATH]; - if (portable->value) + if (is_portable) { Com_sprintf(path, sizeof(path), "%sconsole_history.txt", Sys_GetBinaryDir()); } diff --git a/src/common/clientserver.c b/src/common/clientserver.c index 17c72de6..d8389954 100644 --- a/src/common/clientserver.c +++ b/src/common/clientserver.c @@ -136,7 +136,7 @@ Com_VPrintf(int print_level, const char *fmt, va_list argptr) /* logfile */ if (logfile_active && logfile_active->value) { - char name[MAX_QPATH]; + char name[MAX_OSPATH]; if (!logfile) { diff --git a/src/common/cvar.c b/src/common/cvar.c index 7aa08ef4..2f59bf46 100644 --- a/src/common/cvar.c +++ b/src/common/cvar.c @@ -252,7 +252,7 @@ Cvar_Set2(char *var_name, char *value, qboolean force) if (!strcmp(var->name, "game")) { - FS_SetGamedir(var->string); + FS_BuildGameSpecificSearchPath(var->string); } } @@ -370,7 +370,7 @@ Cvar_GetLatchedVars(void) if (!strcmp(var->name, "game")) { - FS_SetGamedir(var->string); + FS_BuildGameSpecificSearchPath(var->string); } } } diff --git a/src/common/filesystem.c b/src/common/filesystem.c index 6466e2ee..9aa0cc2f 100644 --- a/src/common/filesystem.c +++ b/src/common/filesystem.c @@ -114,26 +114,28 @@ fsPackTypes_t fs_packtypes[] = { }; char fs_gamedir[MAX_OSPATH]; -static char fs_currentGame[MAX_QPATH]; +qboolean file_from_pak; -static char fs_fileInPath[MAX_OSPATH]; -static qboolean fs_fileInPack; - -/* Set by FS_FOpenFile. */ -int file_from_pak = 0; -#ifdef ZIP -int file_from_pk3 = 0; -char file_from_pk3_name[MAX_QPATH]; -#endif - -cvar_t *fs_homepath; cvar_t *fs_basedir; cvar_t *fs_cddir; cvar_t *fs_gamedirvar; cvar_t *fs_debug; fsHandle_t *FS_GetFileByHandle(fileHandle_t f); -char *Sys_GetCurrentDirectory(void); + +// -------- + +// Raw search path, the actual search +// bath is build from this one. +typedef struct fsRawPath_s { + char path[MAX_OSPATH]; + qboolean create; + struct fsRawPath_s *next; +} fsRawPath_t; + +fsRawPath_t *fs_rawPath; + +// -------- /* * All of Quake's data access is through a hierchal file system, but the @@ -343,11 +345,7 @@ FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only) fsSearchPath_t *search; int i; - file_from_pak = 0; -#ifdef ZIP - file_from_pk3 = 0; -#endif - + file_from_pak = false; handle = FS_HandleForFile(name, f); Q_strlcpy(handle->name, name, sizeof(handle->name)); handle->mode = FS_READ; @@ -381,19 +379,16 @@ FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only) if (Q_stricmp(pack->files[i].name, handle->name) == 0) { /* Found it! */ - Com_FilePath(pack->name, fs_fileInPath, sizeof(fs_fileInPath)); - fs_fileInPack = true; - if (fs_debug->value) { Com_Printf("FS_FOpenFile: '%s' (found in '%s').\n", - handle->name, pack->name); + handle->name, pack->name); } if (pack->pak) { /* PAK */ - file_from_pak = 1; + file_from_pak = true; handle->file = fopen(pack->name, "rb"); if (handle->file) @@ -406,8 +401,7 @@ FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only) else if (pack->pk3) { /* PK3 */ - file_from_pk3 = 1; - Q_strlcpy(file_from_pk3_name, strrchr(pack->name, '/') + 1, sizeof(file_from_pk3_name)); + file_from_pak = true; handle->zip = unzOpen(pack->name); if (handle->zip) @@ -449,10 +443,6 @@ FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only) if (handle->file) { - /* Found it! */ - Q_strlcpy(fs_fileInPath, search->path, sizeof(fs_fileInPath)); - fs_fileInPack = false; - if (fs_debug->value) { Com_Printf("FS_FOpenFile: '%s' (found in '%s').\n", @@ -463,11 +453,6 @@ FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only) } } } - - /* Not found! */ - fs_fileInPath[0] = 0; - fs_fileInPack = false; - if (fs_debug->value) { Com_Printf("FS_FOpenFile: couldn't find '%s'.\n", handle->name); @@ -809,171 +794,6 @@ FS_LoadPK3(const char *packPath) } #endif -/* - * Adds the directory to the head of the path, then loads and adds pak1.pak - * pak2.pak ... - * - * Extended all functionality to include Quake III .pk3 - */ -void -FS_AddGameDirectory(const char *dir) -{ - char **list; /* File list. */ - char path[MAX_OSPATH]; /* Path to PAK / PK3. */ - int i, j; /* Loop counters. */ - int nfiles; /* Number of files in list. */ - fsSearchPath_t *search; /* Search path. */ - fsPack_t *pack; /* PAK / PK3 file. */ - - pack = NULL; - - /* Set game directory. */ - Q_strlcpy(fs_gamedir, dir, sizeof(fs_gamedir)); - - /* Create directory if it does not exist. */ - FS_CreatePath(fs_gamedir); - - /* Add the directory to the search path. */ - search = Z_Malloc(sizeof(fsSearchPath_t)); - Q_strlcpy(search->path, dir, sizeof(search->path)); - search->next = fs_searchPaths; - fs_searchPaths = search; - - /* Add numbered pack files in sequence. */ - for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) - { - for (j = 0; j < MAX_PAKS; j++) - { - Com_sprintf(path, sizeof(path), "%s/pak%d.%s", - dir, j, fs_packtypes[i].suffix); - - switch (fs_packtypes[i].format) - { - case PAK: - pack = FS_LoadPAK(path); - break; -#ifdef ZIP - case PK3: - pack = FS_LoadPK3(path); - break; -#endif - } - - if (pack == NULL) - { - continue; - } - - search = Z_Malloc(sizeof(fsSearchPath_t)); - search->pack = pack; - search->next = fs_searchPaths; - fs_searchPaths = search; - } - } - - /* Add not numbered pack files. */ - for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) - { - Com_sprintf(path, sizeof(path), "%s/*.%s", dir, fs_packtypes[i].suffix); - - if ((list = FS_ListFiles(path, &nfiles, 0, SFF_SUBDIR)) == NULL) - { - continue; - } - - Com_sprintf(path, sizeof(path), "%s/pak*.%s", - dir, fs_packtypes[i].suffix); - - for (j = 0; j < nfiles - 1; j++) - { - /* Skip all packs starting with "pak" */ - if (glob_match(path, list[j])) - { - continue; - } - - switch (fs_packtypes[i].format) - { - case PAK: - pack = FS_LoadPAK(list[j]); - break; -#ifdef ZIP - case PK3: - pack = FS_LoadPK3(list[j]); - break; -#endif - } - - if (pack == NULL) - { - continue; - } - - search = Z_Malloc(sizeof(fsSearchPath_t)); - search->pack = pack; - search->next = fs_searchPaths; - fs_searchPaths = search; - } - - FS_FreeList(list, nfiles); - } -} - -/* - * Use ~/.quake2/dir as fs_gamedir. - */ -void -FS_AddHomeAsGameDirectory(char *dir) -{ - char *home; - char gdir[MAX_OSPATH]; - size_t len; - - if (portable->value) - { - return; - } - - home = Sys_GetHomeDir(); - - if (home == NULL) - { - return; - } - - len = snprintf(gdir, sizeof(gdir), "%s%s/", home, dir); - FS_CreatePath(gdir); - - if ((len > 0) && (len < sizeof(gdir)) && (gdir[len - 1] == '/')) - { - gdir[len - 1] = 0; - } - - Q_strlcpy(fs_gamedir, gdir, sizeof(fs_gamedir)); - - FS_AddGameDirectory(gdir); -} - -void FS_AddBinaryDirAsGameDirectory(const char* dir) -{ - char gdir[MAX_OSPATH]; - const char *datadir = Sys_GetBinaryDir(); - if(datadir[0] == '\0') - { - return; - } - int len = snprintf(gdir, sizeof(gdir), "%s%s/", datadir, dir); - - printf("Using binary dir %s to fetch paks\n", gdir); - - if ((len > 0) && (len < sizeof(gdir)) && (gdir[len - 1] == '/')) - { - gdir[len - 1] = 0; - } - - FS_AddGameDirectory(gdir); -} - /* * Allows enumerating all of the directories in the search path. */ @@ -1060,97 +880,6 @@ FS_Path_f(void) #endif } -/* - * Sets the gamedir and path to a different directory. - */ -void -FS_SetGamedir(char *dir) -{ - int i; - fsSearchPath_t *next; - - if (!*dir || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/")) - { - Com_Printf("Gamedir should be a single filename, not a path.\n"); - return; - } - - /* Do not set BASEDIR as gamedir. It was already set by FS_InitFilesystem() - * and setting it again f*cks the search pathes up. Yes, this a hack. */ - if(!Q_stricmp(dir, BASEDIRNAME)) - { - 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); - } - -#ifdef ZIP - if (fs_searchPaths->pack->pk3) - { - unzClose(fs_searchPaths->pack->pk3); - } -#endif - - Z_Free(fs_searchPaths->pack->files); - Z_Free(fs_searchPaths->pack); - } - - next = fs_searchPaths->next; - Z_Free(fs_searchPaths); - fs_searchPaths = next; - } - - /* Close open files for game dir. */ - for (i = 0; i < MAX_HANDLES; i++) - { - if (strstr(fs_handles[i].name, dir) && - ((fs_handles[i].file != NULL) -#ifdef ZIP - || (fs_handles[i].zip != NULL) -#endif - )) - { - FS_FCloseFile(i); - } - } - - /* Flush all data, so it will be forced to reload. */ - if ((dedicated != NULL) && (dedicated->value != 1)) - { - Cbuf_AddText("vid_restart\nsnd_restart\n"); - } - - Com_sprintf(fs_gamedir, sizeof(fs_gamedir), "%s/%s", - fs_basedir->string, dir); - - if ((strcmp(dir, BASEDIRNAME) == 0) || (*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] == '\0') - { - FS_AddGameDirectory(va("%s/%s", fs_cddir->string, dir)); - } - - FS_AddGameDirectory(va("%s/%s", fs_basedir->string, dir)); - FS_AddBinaryDirAsGameDirectory(dir); - FS_AddHomeAsGameDirectory(dir); - } -} - /* * Creates a filelink_t. */ @@ -1523,67 +1252,327 @@ FS_Dir_f(void) } } +// -------- + +void +FS_AddDirToSearchPath(char *dir, qboolean create) { + char **list; + char path[MAX_OSPATH]; + int i, j; + int nfiles; + fsPack_t *pack = NULL; + fsSearchPath_t *search; + size_t len = strlen(dir); + + // The directory must not end with an /. It would + // f*ck up the logic in other parts of the game... + if (dir[len - 1] == '/') + { + dir[len - 1] = '\0'; + } + else if (dir[len - 1] == '\\') { + dir[len - 1] = '\0'; + } + + // Set the current directory as game directory. This + // is somewhat fragile since the game directory MUST + // be the last directory added to the search path. + Q_strlcpy(fs_gamedir, dir, sizeof(fs_gamedir)); + + if (create) { + FS_CreatePath(fs_gamedir); + } + + // Add the directory itself. + search = Z_Malloc(sizeof(fsSearchPath_t)); + Q_strlcpy(search->path, dir, sizeof(search->path)); + search->next = fs_searchPaths; + fs_searchPaths = search; + + // We need to add numbered paks in te directory in + // sequence and all other paks after them. Otherwise + // the gamedata may break. + for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) { + for (j = 0; j < MAX_PAKS; j++) { + Com_sprintf(path, sizeof(path), "%s/pak%d.%s", dir, j, fs_packtypes[i].suffix); + + switch (fs_packtypes[i].format) + { + case PAK: + pack = FS_LoadPAK(path); + break; +#ifdef ZIP + case PK3: + pack = FS_LoadPK3(path); + break; +#endif + } + + if (pack == NULL) + { + continue; + } + + search = Z_Malloc(sizeof(fsSearchPath_t)); + search->pack = pack; + search->next = fs_searchPaths; + fs_searchPaths = search; + } + } + + // And as said above all other pak files. + for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) { + Com_sprintf(path, sizeof(path), "%s/*.%s", dir, fs_packtypes[i].suffix); + + // Nothing here, next pak type please. + if ((list = FS_ListFiles(path, &nfiles, 0, SFF_SUBDIR)) == NULL) + { + continue; + } + + Com_sprintf(path, sizeof(path), "%s/pak*.%s", dir, fs_packtypes[i].suffix); + + for (j = 0; j < nfiles - 1; j++) + { + // If the pak starts with the string 'pak' it's ignored. + // This is somewhat stupid, it would be better to ignore + // just pak%d... + if (glob_match(path, list[j])) + { + continue; + } + + switch (fs_packtypes[i].format) + { + case PAK: + pack = FS_LoadPAK(list[j]); + break; +#ifdef ZIP + case PK3: + pack = FS_LoadPK3(list[j]); + break; +#endif + } + + if (pack == NULL) + { + continue; + } + + search = Z_Malloc(sizeof(fsSearchPath_t)); + search->pack = pack; + search->next = fs_searchPaths; + fs_searchPaths = search; + } + + FS_FreeList(list, nfiles); + } +} + +void FS_BuildGenericSearchPath(void) { + // We may not use the va() function from shared.c + // since it's buffersize is 1024 while most OS have + // a maximum path size of 4096... + char path[MAX_OSPATH]; + + fsRawPath_t *search = fs_rawPath; + + while (search != NULL) { + Com_sprintf(path, sizeof(path), "%s/%s", search->path, BASEDIRNAME); + FS_AddDirToSearchPath(path, search->create); + + search = search->next; + } + + // Until here we've added the generic directories to the + // search path. Save the current head node so we can + // distinguish generic and specialized directories. + fs_baseSearchPaths = fs_searchPaths; + + // We need to create the screenshot directory since the + // render dll doesn't link the filesystem stuff. + Com_sprintf(path, sizeof(path), "%s/scrnshot", fs_gamedir); + Sys_Mkdir(path); +} + +void +FS_BuildGameSpecificSearchPath(char *dir) +{ + // We may not use the va() function from shared.c + // since it's buffersize is 1024 while most OS have + // a maximum path size of 4096... + char path[MAX_OSPATH]; + int i; + fsRawPath_t *search; + fsSearchPath_t *next; + + // This is against PEBCAK. The user may give us paths like + // xatrix/ or even /home/stupid/quake2/xatrix. + if (!*dir || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\")) + { + Com_Printf("Gamedir should be a single filename, not a path.\n"); + return; + } + + // BASEDIR is already added as a generic directory. Adding it + // again as a specialised directory breaks the logic in other + // parts of the code. This may happen if the user does something + // like ./quake2 +set game baseq2 + if(!Q_stricmp(dir, BASEDIRNAME)) + { + return; + } + + // We may already have specialised directories in our search + // path. This can happen if the server changes the mod. Let's + // remove them. + while (fs_searchPaths != fs_baseSearchPaths) + { + if (fs_searchPaths->pack) + { + if (fs_searchPaths->pack->pak) + { + fclose(fs_searchPaths->pack->pak); + } + +#ifdef ZIP + if (fs_searchPaths->pack->pk3) + { + unzClose(fs_searchPaths->pack->pk3); + } +#endif + + Z_Free(fs_searchPaths->pack->files); + Z_Free(fs_searchPaths->pack); + } + + next = fs_searchPaths->next; + Z_Free(fs_searchPaths); + fs_searchPaths = next; + } + + /* Close open files for game dir. */ + for (i = 0; i < MAX_HANDLES; i++) + { + if (strstr(fs_handles[i].name, dir) && + ((fs_handles[i].file != NULL) +#ifdef ZIP + || (fs_handles[i].zip != NULL) +#endif + )) + { + FS_FCloseFile(i); + } + } + + // Enforce a renderer and sound backend restart to + // purge all internal caches. This is rather hacky + // but Quake II doesn't have a better mechanism... + if ((dedicated != NULL) && (dedicated->value != 1)) + { + Cbuf_AddText("vid_restart\nsnd_restart\n"); + } + + // The game was reset to baseq2. Nothing to do here. + if ((Q_stricmp(dir, BASEDIRNAME) == 0) || (*dir == 0)) { + Cvar_FullSet("gamedir", "", CVAR_SERVERINFO | CVAR_NOSET); + Cvar_FullSet("game", "", CVAR_LATCH | CVAR_SERVERINFO); + + // fs_gamedir must be reset to the last + // dir of the generic search path. + Q_strlcpy(fs_gamedir, fs_baseSearchPaths->path, sizeof(fs_gamedir)); + } else { + Cvar_FullSet("gamedir", dir, CVAR_SERVERINFO | CVAR_NOSET); + search = fs_rawPath; + + while (search != NULL) { + Com_sprintf(path, sizeof(path), "%s/%s", search->path, dir); + FS_AddDirToSearchPath(path, search->create); + + search = search->next; + } + } + + // We need to create the screenshot directory since the + // render dll doesn't link the filesystem stuff. + Com_sprintf(path, sizeof(path), "%s/scrnshot", fs_gamedir); + Sys_Mkdir(path); +} + +// -------- + +void FS_AddDirToRawPath (const char *dir, qboolean create) { + fsRawPath_t *search; + + // Add the directory + search = Z_Malloc(sizeof(fsRawPath_t)); + Q_strlcpy(search->path, dir, sizeof(search->path)); + search->create = create; + search->next = fs_rawPath; + fs_rawPath = search; +} + + +void FS_BuildRawPath(void) { + // Add $HOME/.yq2 (MUST be the last dir!) + if (!is_portable) { + const char *homedir = Sys_GetHomeDir(); + + if (homedir != NULL) { + FS_AddDirToRawPath(homedir, true); + } + } + + // Add $binarydir + const char *binarydir = Sys_GetBinaryDir(); + + if(binarydir[0] != '\0') + { + FS_AddDirToRawPath(binarydir, false); + } + + // Add $basedir/ + FS_AddDirToRawPath(fs_basedir->string, false); + + // Add SYSTEMDIR +#ifdef SYSTEMWIDE + FS_AddDirToRawPath(SYSTEMDIR, false); +#endif + + // The CD must be the last directory of the path, + // otherwise we cannot be sure that the game won't + // stream the videos from the CD. + if (fs_cddir->string[0] != '\0') { + FS_AddDirToRawPath(fs_cddir->string, false); + } +} + +// -------- + void FS_InitFilesystem(void) { - char scrnshotdir[MAX_OSPATH]; - - /* Register FS commands. */ + // Register FS commands. Cmd_AddCommand("path", FS_Path_f); Cmd_AddCommand("link", FS_Link_f); Cmd_AddCommand("dir", FS_Dir_f); - /* basedir Allows the game to run from outside the data tree. */ - fs_basedir = Cvar_Get("basedir", -#ifdef SYSTEMWIDE - SYSTEMDIR, -#else - ".", -#endif - CVAR_NOSET); - - /* cddir Logically concatenates the cddir after the basedir to - allow the game to run from outside the data tree. */ + // Register cvars + fs_basedir = Cvar_Get("basedir", ".", CVAR_NOSET); fs_cddir = Cvar_Get("cddir", "", CVAR_NOSET); - - if (fs_cddir->string[0] != '\0') - { - FS_AddGameDirectory(va("%s/" BASEDIRNAME, fs_cddir->string)); - } - - /* Debug flag. */ + fs_gamedirvar = Cvar_Get("game", "", CVAR_LATCH | CVAR_SERVERINFO); fs_debug = Cvar_Get("fs_debug", "0", 0); - /* Game directory. */ - fs_gamedirvar = Cvar_Get("game", "", CVAR_LATCH | CVAR_SERVERINFO); + // Build search path + FS_BuildRawPath(); + FS_BuildGenericSearchPath(); - /* Current directory. */ - fs_homepath = Cvar_Get("homepath", Sys_GetCurrentDirectory(), CVAR_NOSET); - - /* Add baseq2 to search path. */ - FS_AddGameDirectory(va("%s/" BASEDIRNAME, fs_basedir->string)); - FS_AddBinaryDirAsGameDirectory(BASEDIRNAME); - FS_AddHomeAsGameDirectory(BASEDIRNAME); - - /* Any set gamedirs will be freed up to here. */ - fs_baseSearchPaths = fs_searchPaths; - Q_strlcpy(fs_currentGame, BASEDIRNAME, sizeof(fs_currentGame)); - - /* Check for game override. */ if (fs_gamedirvar->string[0] != '\0') { - FS_SetGamedir(fs_gamedirvar->string); + FS_BuildGameSpecificSearchPath(fs_gamedirvar->string); } - /* Create directory if it does not exist. */ - FS_CreatePath(fs_gamedir); - - /* create the scrnshots directory if it doesn't exist - * (do it here instead of in ref_gl so ref_gl doesn't need mkdir) - */ - Com_sprintf(scrnshotdir, sizeof(scrnshotdir), "%s/scrnshot", FS_Gamedir()); - Sys_Mkdir(scrnshotdir); - + // Debug output Com_Printf("Using '%s' for writing.\n", fs_gamedir); } diff --git a/src/common/header/common.h b/src/common/header/common.h index 48c6ae3f..7e329180 100644 --- a/src/common/header/common.h +++ b/src/common/header/common.h @@ -643,7 +643,7 @@ void Pmove(pmove_t *pmove); #define SFF_INPACK 0x20 -extern int file_from_pak; +extern qboolean file_from_pak; typedef int fileHandle_t; @@ -680,7 +680,7 @@ char **FS_ListFiles2(char *findname, int *numfiles, void FS_FreeList(char **list, int nfiles); void FS_InitFilesystem(void); -void FS_SetGamedir(char *dir); +void FS_BuildGameSpecificSearchPath(char *dir); char *FS_Gamedir(void); char *FS_NextPath(char *prevpath); int FS_LoadFile(char *path, void **buffer); @@ -726,7 +726,9 @@ extern cvar_t *modder; extern cvar_t *dedicated; extern cvar_t *host_speeds; extern cvar_t *log_stats; -extern cvar_t *portable; + +/* Hack for portable client */ +extern qboolean is_portable; extern FILE *log_stats_file; diff --git a/src/common/misc.c b/src/common/misc.c index a5714c99..4cc2adb4 100644 --- a/src/common/misc.c +++ b/src/common/misc.c @@ -213,14 +213,6 @@ Qcommon_Init(int argc, char **argv) Cbuf_AddEarlyCommands(false); Cbuf_Execute(); - /* Be portable, don't add HOME to the search path - * This is needed by Sys_RedirectStdout() on Windows*/ - portable = Cvar_Get("portable", "0", 0); - -#ifndef DEDICATED_ONLY - Sys_RedirectStdout(); -#endif - FS_InitFilesystem(); Cbuf_AddText("exec default.cfg\n"); diff --git a/src/server/sv_user.c b/src/server/sv_user.c index 791ad3d3..b3f21716 100644 --- a/src/server/sv_user.c +++ b/src/server/sv_user.c @@ -296,7 +296,7 @@ SV_BeginDownload_f(void) extern cvar_t *allow_download_models; extern cvar_t *allow_download_sounds; extern cvar_t *allow_download_maps; - extern int file_from_pak; + extern qboolean file_from_pak; int offset = 0; name = Cmd_Argv(1); diff --git a/stuff/packaging.md b/stuff/packaging.md new file mode 100644 index 00000000..093bf127 --- /dev/null +++ b/stuff/packaging.md @@ -0,0 +1,66 @@ +# Notes for Package Maintainers + +Our 7.00 release caused some trouble for package maintainers +(see https://github.com/yquake2/yquake2/issues/214), so we decided to finally +properly document how we think it should be done and what assumptions +Yamagi Quake II makes regarding binary locations etc. + +## Where you should put the executables + +Yamagi Quake II expects all binaries (executables and libs) to be in the same directory +(or, in the case of game.so/.dll/dylib, in the mod-specific subdirectory). + +So the binary directory should look somehow like this *(on Unix-like systems; +on Windows and OSX it's very similar but with different extensions: .dll or +.dylib instead of .so, and the executables have .exe file extension on Windows of course)*: + +* /path/to/yamagi-quake2/ + - quake2 + - q2ded + - ref_gl1.so + - ref_gl3.so + - baseq2/ + * game.so + - xatrix/ + * game.so + - ... *(the same for other addons/mods)* + +Yamagi Quake2 will get the directory the `quake2` executable is in from the system +and then look in that directory (and nowhere else!) for `ref_*.so`. +It will look for `game.so` there first, but if it's not found in the binary directory, +it will look for it in all directories that are also searched for game +data (SYSTEMDIR, basedir, $HOME/.yq2/). This is for better compatibility with mods that +might ship their own game.so. + +You can **just symlink the executables to a directory in your $PATH**, like /usr/bin/. +(*Except on OpenBSD, which does not provide a way to get the executable directory, + there you'll need a shellscript that first does a `cd /path/to/yamagi-quake2/` and + then executes `./quake2`*) + +We want all binaries to be in the same directory to ensure that people don't accidentally +update only parts of their Yamagi Quake II installtion, so they'd end up with a new +quake2 executable and old render libraries (`ref_*.so`) and report weird bugs. + +## The SYSTEMWIDE and SYSTEMDIR options + +The Makefile allows you to enable the `SYSTEMWIDE` feature (`WITH_SYSTEMWIDE=yes`) and +lets you specify the directory that will be used (`SYSTEMDIR`, `WITH_SYSTEMDIR=/your/custom/path/`). +If you don't set SYSTEMDIR, it defaults to `/usr/share/games/quake2`, which is what debian uses. + +The `SYSTEMDIR` was meant to contain just the game data, *not* the binaries, and allows +several Quake2 source ports to share the same game data. +Unfortunately, we didn't document this assumption, so some packages used it for both binaries +and data, just binaries or - which causes most trouble - only for the game libs, but not the +executables. The latter case doesn't work anymore since we (re)introduced the render libs, +as they need to be located next to the executable, and if the executable is in /usr/bin/ you +don't want to put libs next to it. + +Anyway: If you use `SYSTEMWIDE`/`SYSTEMDIR`, please use it for game data. +You *can* also put the binaries in there, but in that case please put all of them +(including executables) in there, as explained above. + +## Alternative startup config + +Yamagi Quake II has support for an alternative startup config. +It may be a good idea to install it, since it sets some global options to sane defaults. +Copy yq2.cfg to the baseq2/ subdirectory in the gamedata (`SYSTEMDIR`) directory.