1 /* -------------------------------------------------------------------------------
3 Copyright (C) 1999-2007 id Software, Inc. and contributors.
4 For a list of contributors, see the accompanying CONTRIBUTORS file.
6 This file is part of GtkRadiant.
8 GtkRadiant is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 GtkRadiant is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GtkRadiant; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 ----------------------------------------------------------------------------------
24 This code has been altered significantly from its original form, to support
25 several games based on the Quake III Arena engine, in the form of "Q3Map2."
27 ------------------------------------------------------------------------------- */
42 #define MAX_BASE_PATHS 10
43 #define MAX_GAME_PATHS 10
44 #define MAX_PAK_PATHS 200
46 qboolean customHomePath = qfalse;
48 #if (GDEF_OS_POSIX && !GDEF_OS_MACOS)
49 char *xdgDataHomePath;
50 #endif // (GDEF_OS_POSIX && !GDEF_OS_MACOS)
51 char installPath[ MAX_OS_PATH ];
54 char *basePaths[ MAX_BASE_PATHS ];
56 char *gamePaths[ MAX_GAME_PATHS ];
58 char *pakPaths[ MAX_PAK_PATHS ];
59 char *homeBasePath = NULL;
63 some of this code is based off the original q3map port from loki
64 and finds various paths. moved here from bsp.c for clarity.
69 gets the user's home dir (for ~/.q3a)
72 char *LokiGetHomeDir( void ){
75 #else // !GDEF_OS_WINDOWS
76 static char buf[ 4096 ];
77 struct passwd pw, *pwp;
80 /* get the home environment variable */
81 home = getenv( "HOME" );
83 /* look up home dir in password database */
86 if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 ) {
100 initializes some paths on linux/os x
103 void LokiInitPaths( char *argv0 ){
106 if ( homePath == NULL ) {
108 home = LokiGetHomeDir();
109 if ( home == NULL ) {
120 #if (GDEF_OS_POSIX && !GDEF_OS_MACOS)
121 xdgDataHomePath = getenv( "XDG_DATA_HOME" );
123 if ( xdgDataHomePath == NULL ) {
124 char *subPath = "/.local/share";
125 xdgDataHomePath = safe_malloc( sizeof( char ) * ( strlen( home ) + strlen( subPath ) ) );
126 sprintf( xdgDataHomePath, "%s%s", home, subPath );
128 #endif // (GDEF_OS_POSIX && !GDEF_OS_MACOS)
131 /* this is kinda crap, but hey */
132 strcpy( installPath, "../" );
133 #else // !GDEF_OS_WINDOWS
135 char temp[ MAX_OS_PATH ];
141 path = getenv( "PATH" );
143 /* do some path divining */
144 Q_strncpyz( temp, argv0, sizeof( temp ) );
145 if ( strrchr( temp, '/' ) ) {
146 argv0 = strrchr( argv0, '/' ) + 1;
148 else if ( path != NULL ) {
151 This code has a special behavior when q3map2 is a symbolic link.
153 For each dir in ${PATH} (example: "/usr/bin", "/usr/local/bin" if ${PATH} == "/usr/bin:/usr/local/bin"),
154 it looks for "${dir}/q3map2" (file exists and is executable),
155 then it uses "dirname(realpath("${dir}/q3map2"))/../" as installPath.
157 So, if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2",
158 it will find the installPath "/usr/share/radiant/",
159 so q3map2 will look for "/opt/radiant/baseq3" to find paks.
161 More precisely, it looks for "${dir}/${argv[0]}",
162 so if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2",
163 and if "/opt/radiant/tools/q3ma2" is a symbolic link to "/opt/radiant/tools/q3map2.x86_64",
164 it will use "dirname("/opt/radiant/tools/q3map2.x86_64")/../" as path,
165 so it will use "/opt/radiant/" as installPath, which will be expanded later to "/opt/radiant/baseq3" to find paks.
171 /* go through each : segment of path */
172 while ( last[ 0 ] != '\0' && found == qfalse )
177 /* find next chunk */
178 last = strchr( path, ':' );
179 if ( last == NULL ) {
180 last = path + strlen( path );
183 /* found home dir candidate */
184 if ( *path == '~' ) {
185 Q_strncpyz( temp, home, sizeof( temp ) );
190 if ( last > ( path + 1 ) ) {
191 // +1 hack: Q_strncat calls Q_strncpyz that expects a len including '\0'
192 // so that extraneous char will be rewritten by '\0', so it's ok.
193 // Also, in this case this extraneous char is always ':' or '\0', so it's ok.
194 Q_strncat( temp, sizeof( temp ), path, ( last - path + 1) );
195 Q_strcat( temp, sizeof( temp ), "/" );
197 Q_strcat( temp, sizeof( temp ), argv0 );
199 /* verify the path */
200 if ( access( temp, X_OK ) == 0 ) {
208 if ( realpath( temp, installPath ) ) {
210 if "q3map2" is "/opt/radiant/tools/q3map2",
211 installPath is "/opt/radiant"
213 *( strrchr( installPath, '/' ) ) = '\0';
214 *( strrchr( installPath, '/' ) ) = '\0';
216 #endif // !GDEF_OS_WINDOWS
223 cleans a dos path \ -> /
226 void CleanPath( char *path ){
229 if ( *path == '\\' ) {
240 gets the game_t based on a -game argument
241 returns NULL if no match found
244 game_t *GetGame( char *arg ){
249 if ( arg == NULL || arg[ 0 ] == '\0' ) {
254 if ( !Q_stricmp( arg, "quake1" ) ||
255 !Q_stricmp( arg, "quake2" ) ||
256 !Q_stricmp( arg, "unreal" ) ||
257 !Q_stricmp( arg, "ut2k3" ) ||
258 !Q_stricmp( arg, "dn3d" ) ||
259 !Q_stricmp( arg, "dnf" ) ||
260 !Q_stricmp( arg, "hl" ) ) {
261 Sys_Printf( "April fools, silly rabbit!\n" );
267 while ( games[ i ].arg != NULL )
269 if ( Q_stricmp( arg, games[ i ].arg ) == 0 ) {
275 /* no matching game */
282 AddBasePath() - ydnar
283 adds a base path to the list
286 void AddBasePath( char *path ){
288 if ( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) {
292 /* add it to the list */
293 basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 );
294 strcpy( basePaths[ numBasePaths ], path );
295 CleanPath( basePaths[ numBasePaths ] );
302 AddHomeBasePath() - ydnar
303 adds a base path to the beginning of the list
306 void AddHomeBasePath( char *path ){
308 char temp[ MAX_OS_PATH ];
310 if ( homePath == NULL ) {
315 if ( path == NULL || path[ 0 ] == '\0' ) {
319 if ( strcmp( path, "." ) == 0 ) {
320 /* -fs_homebase . means that -fs_home is to be used as is */
321 strcpy( temp, homePath );
325 tempHomePath = homePath;
327 #if (GDEF_OS_POSIX && !GDEF_OS_MACOS)
329 on Linux, check if game uses ${XDG_DATA_HOME}/prefix instead of ${HOME}/.prefix
330 if yes and home path is not user supplied
331 use XDG_DATA_HOME instead of HOME
332 and strip the leading dot
334 basically it produces
335 ${XDG_DATA_HOME}/unvanquished
336 /user/supplied/home/path/unvanquished
340 /user/supplied/home/path/.q3a
343 sprintf( temp, "%s/%s", xdgDataHomePath, ( path + 1 ) );
344 if ( access( temp, X_OK ) == 0 ) {
345 if ( customHomePath == qfalse ) {
346 tempHomePath = xdgDataHomePath;
350 #endif // (GDEF_OS_POSIX && !GDEF_OS_MACOS)
352 /* concatenate home dir and path */
353 sprintf( temp, "%s/%s", tempHomePath, path );
357 for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- )
358 basePaths[ i + 1 ] = basePaths[ i ];
360 /* add it to the list */
361 basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 );
362 strcpy( basePaths[ 0 ], temp );
363 CleanPath( basePaths[ 0 ] );
370 AddGamePath() - ydnar
371 adds a game path to the list
374 void AddGamePath( char *path ){
378 if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) {
382 /* add it to the list */
383 gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 );
384 strcpy( gamePaths[ numGamePaths ], path );
385 CleanPath( gamePaths[ numGamePaths ] );
388 /* don't add it if it's already there */
389 for ( i = 0; i < numGamePaths - 1; i++ )
391 if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) {
392 free( gamePaths[numGamePaths - 1] );
393 gamePaths[numGamePaths - 1] = NULL;
404 adds a pak path to the list
407 void AddPakPath( char *path ){
409 if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) {
413 /* add it to the list */
414 pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 );
415 strcpy( pakPaths[ numPakPaths ], path );
416 CleanPath( pakPaths[ numPakPaths ] );
424 cleaned up some of the path initialization code from bsp.c
425 will remove any arguments it uses
428 void InitPaths( int *argc, char **argv ){
429 int i, j, k, len, len2;
430 char temp[ MAX_OS_PATH ];
437 Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" );
439 /* get the install path for backup */
440 LokiInitPaths( argv[ 0 ] );
442 /* set game to default (q3a) */
447 /* parse through the arguments and extract those relevant to paths */
448 for ( i = 0; i < *argc; i++ )
451 if ( argv[ i ] == NULL ) {
456 if ( strcmp( argv[ i ], "-game" ) == 0 ) {
457 if ( ++i >= *argc ) {
458 Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] );
460 argv[ i - 1 ] = NULL;
461 game = GetGame( argv[ i ] );
462 if ( game == NULL ) {
468 /* -fs_forbiddenpath */
469 else if ( strcmp( argv[ i ], "-fs_forbiddenpath" ) == 0 ) {
470 if ( ++i >= *argc ) {
471 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
473 argv[ i - 1 ] = NULL;
474 if ( g_numForbiddenDirs < VFS_MAXDIRS ) {
475 strncpy( g_strForbiddenDirs[g_numForbiddenDirs], argv[i], PATH_MAX );
476 g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = 0;
477 ++g_numForbiddenDirs;
483 else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) {
485 // we don't want any basepath, neither guessed ones
490 /* -fs_nomagicpath */
491 else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) {
497 else if ( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) {
498 if ( ++i >= *argc ) {
499 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
501 argv[ i - 1 ] = NULL;
502 AddBasePath( argv[ i ] );
507 else if ( strcmp( argv[ i ], "-fs_game" ) == 0 ) {
508 if ( ++i >= *argc ) {
509 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
511 argv[ i - 1 ] = NULL;
512 AddGamePath( argv[ i ] );
517 else if ( strcmp( argv[ i ], "-fs_home" ) == 0 ) {
518 if ( ++i >= *argc ) {
519 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
521 argv[ i - 1 ] = NULL;
522 customHomePath = qtrue;
528 else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) {
534 else if ( strcmp( argv[ i ], "-fs_homebase" ) == 0 ) {
535 if ( ++i >= *argc ) {
536 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
538 argv[ i - 1 ] = NULL;
539 homeBasePath = argv[i];
543 /* -fs_homepath - sets both of them */
544 else if ( strcmp( argv[ i ], "-fs_homepath" ) == 0 ) {
545 if ( ++i >= *argc ) {
546 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
548 argv[ i - 1 ] = NULL;
555 else if ( strcmp( argv[ i ], "-fs_pakpath" ) == 0 ) {
556 if ( ++i >= *argc ) {
557 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
559 argv[ i - 1 ] = NULL;
560 AddPakPath( argv[ i ] );
565 /* remove processed arguments */
566 for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ )
568 for ( ; j < *argc && argv[ j ] == NULL; j++ ) ;
569 argv[ i ] = argv[ j ];
570 if ( argv[ i ] != NULL ) {
576 /* add standard game path */
577 AddGamePath( game->gamePath );
579 /* if there is no base path set, figure it out unless fs_nomagicpath is set */
580 if ( numBasePaths == 0 && noBasePath == 0 && noMagicPath == 0 ) {
581 /* this is another crappy replacement for SetQdirFromPath() */
582 len2 = strlen( game->magic );
583 for ( i = 0; i < *argc && numBasePaths == 0; i++ )
585 /* extract the arg */
586 strcpy( temp, argv[ i ] );
588 len = strlen( temp );
589 Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i );
591 /* this is slow, but only done once */
592 for ( j = 0; j < ( len - len2 ); j++ )
594 /* check for the game's magic word */
595 if ( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) {
596 /* now find the next slash and nuke everything after it */
597 while ( temp[ ++j ] != '/' && temp[ j ] != '\0' ) ;
600 /* add this as a base path */
607 /* add install path */
608 if ( numBasePaths == 0 ) {
609 AddBasePath( installPath );
613 if ( numBasePaths == 0 ) {
614 Error( "Failed to find a valid base path." );
618 if ( noBasePath == 1 ) {
622 if ( noHomePath == 0 ) {
623 /* this only affects unix */
624 if ( homeBasePath ) {
625 AddHomeBasePath( homeBasePath );
628 AddHomeBasePath( game->homeBasePath );
632 /* initialize vfs paths */
633 if ( numBasePaths > MAX_BASE_PATHS ) {
634 numBasePaths = MAX_BASE_PATHS;
636 if ( numGamePaths > MAX_GAME_PATHS ) {
637 numGamePaths = MAX_GAME_PATHS;
640 /* walk the list of game paths */
641 for ( j = 0; j < numGamePaths; j++ )
643 /* walk the list of base paths */
644 for ( i = 0; i < numBasePaths; i++ )
646 /* create a full path and initialize it */
647 sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] );
648 vfsInitDirectory( temp );
652 /* initialize vfs paths */
653 if ( numPakPaths > MAX_PAK_PATHS ) {
654 numPakPaths = MAX_PAK_PATHS;
657 /* walk the list of pak paths */
658 for ( i = 0; i < numPakPaths; i++ )
660 /* initialize this pak path */
661 vfsInitDirectory( pakPaths[ i ] );