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 ------------------------------------------------------------------------------- */
36 #define MAX_BASE_PATHS 10
37 #define MAX_GAME_PATHS 10
38 #define MAX_PAK_PATHS 200
40 qboolean customHomePath = qfalse;
44 char *macLibraryApplicationSupportPath;
46 char *xdgDataHomePath;
49 char installPath[ MAX_OS_PATH ];
52 char *basePaths[ MAX_BASE_PATHS ];
54 char *gamePaths[ MAX_GAME_PATHS ];
56 char *pakPaths[ MAX_PAK_PATHS ];
57 char *homeBasePath = NULL;
61 some of this code is based off the original q3map port from loki
62 and finds various paths. moved here from bsp.c for clarity.
67 gets the user's home dir (for ~/.q3a)
70 char *LokiGetHomeDir( void ){
73 #else // !GDEF_OS_WINDOWS
74 static char buf[ 4096 ];
75 struct passwd pw, *pwp;
78 /* get the home environment variable */
79 home = getenv( "HOME" );
81 /* look up home dir in password database */
84 if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 ) {
91 #endif // !GDEF_OS_WINDOWS
98 initializes some paths on linux/os x
101 void LokiInitPaths( char *argv0 ){
104 if ( homePath == NULL ) {
106 home = LokiGetHomeDir();
107 if ( home == NULL ) {
119 char *subPath = "/Library/Application Support";
120 macLibraryApplicationSupportPath = safe_malloc( sizeof( char ) * ( strlen( home ) + strlen( subPath ) ) + 1 );
121 sprintf( macLibraryApplicationSupportPath, "%s%s", home, subPath );
123 xdgDataHomePath = getenv( "XDG_DATA_HOME" );
125 if ( xdgDataHomePath == NULL ) {
126 char *subPath = "/.local/share";
127 xdgDataHomePath = safe_malloc( sizeof( char ) * ( strlen( home ) + strlen( subPath ) ) + 1 );
128 sprintf( xdgDataHomePath, "%s%s", home, subPath );
130 #endif // GDEF_OS_XDG
133 /* this is kinda crap, but hey */
134 strcpy( installPath, "../" );
135 #else // !GDEF_OS_WINDOWS
137 char temp[ MAX_OS_PATH ];
143 path = getenv( "PATH" );
145 /* do some path divining */
146 Q_strncpyz( temp, argv0, sizeof( temp ) );
147 if ( strrchr( temp, '/' ) ) {
148 argv0 = strrchr( argv0, '/' ) + 1;
150 else if ( path != NULL ) {
153 This code has a special behavior when q3map2 is a symbolic link.
155 For each dir in ${PATH} (example: "/usr/bin", "/usr/local/bin" if ${PATH} == "/usr/bin:/usr/local/bin"),
156 it looks for "${dir}/q3map2" (file exists and is executable),
157 then it uses "dirname(realpath("${dir}/q3map2"))/../" as installPath.
159 So, if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2",
160 it will find the installPath "/usr/share/radiant/",
161 so q3map2 will look for "/opt/radiant/baseq3" to find paks.
163 More precisely, it looks for "${dir}/${argv[0]}",
164 so if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2",
165 and if "/opt/radiant/tools/q3ma2" is a symbolic link to "/opt/radiant/tools/q3map2.x86_64",
166 it will use "dirname("/opt/radiant/tools/q3map2.x86_64")/../" as path,
167 so it will use "/opt/radiant/" as installPath, which will be expanded later to "/opt/radiant/baseq3" to find paks.
173 /* go through each : segment of path */
174 while ( last[ 0 ] != '\0' && found == qfalse )
179 /* find next chunk */
180 last = strchr( path, ':' );
181 if ( last == NULL ) {
182 last = path + strlen( path );
185 /* found home dir candidate */
186 if ( *path == '~' ) {
187 Q_strncpyz( temp, home, sizeof( temp ) );
192 if ( last > ( path + 1 ) ) {
193 // +1 hack: Q_strncat calls Q_strncpyz that expects a len including '\0'
194 // so that extraneous char will be rewritten by '\0', so it's ok.
195 // Also, in this case this extraneous char is always ':' or '\0', so it's ok.
196 Q_strncat( temp, sizeof( temp ), path, ( last - path + 1) );
197 Q_strcat( temp, sizeof( temp ), "/" );
199 Q_strcat( temp, sizeof( temp ), argv0 );
201 /* verify the path */
202 if ( access( temp, X_OK ) == 0 ) {
210 if ( realpath( temp, installPath ) ) {
212 if "q3map2" is "/opt/radiant/tools/q3map2",
213 installPath is "/opt/radiant"
215 *( strrchr( installPath, '/' ) ) = '\0';
216 *( strrchr( installPath, '/' ) ) = '\0';
218 #endif // !GDEF_OS_WINDOWS
225 cleans a dos path \ -> /
228 void CleanPath( char *path ){
231 if ( *path == '\\' ) {
242 gets the game_t based on a -game argument
243 returns NULL if no match found
246 game_t *GetGame( char *arg ){
251 if ( arg == NULL || arg[ 0 ] == '\0' ) {
256 if ( !Q_stricmp( arg, "quake1" ) ||
257 !Q_stricmp( arg, "quake2" ) ||
258 !Q_stricmp( arg, "unreal" ) ||
259 !Q_stricmp( arg, "ut2k3" ) ||
260 !Q_stricmp( arg, "dn3d" ) ||
261 !Q_stricmp( arg, "dnf" ) ||
262 !Q_stricmp( arg, "hl" ) ) {
263 Sys_Printf( "April fools, silly rabbit!\n" );
269 while ( games[ i ].arg != NULL )
271 if ( Q_stricmp( arg, games[ i ].arg ) == 0 ) {
277 /* no matching game */
284 AddBasePath() - ydnar
285 adds a base path to the list
288 void AddBasePath( char *path ){
290 if ( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) {
294 /* add it to the list */
295 basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 );
296 strcpy( basePaths[ numBasePaths ], path );
297 CleanPath( basePaths[ numBasePaths ] );
298 if ( EnginePath[0] == '\0' ) strcpy( EnginePath, basePaths[ numBasePaths ] );
305 AddHomeBasePath() - ydnar
306 adds a base path to the beginning of the list
309 void AddHomeBasePath( char *path ){
311 char temp[ MAX_OS_PATH ];
313 if ( homePath == NULL ) {
318 if ( path == NULL || path[ 0 ] == '\0' ) {
322 if ( strcmp( path, "." ) == 0 ) {
323 /* -fs_homebase . means that -fs_home is to be used as is */
324 strcpy( temp, homePath );
328 tempHomePath = homePath;
330 /* homePath is . on Windows if not user supplied */
334 use ${HOME}/Library/Application as ${HOME}
335 if home path is not user supplied
336 and strip the leading dot from prefix in any case
338 basically it produces
339 ${HOME}/Library/Application/unvanquished
340 /user/supplied/home/path/unvanquished
342 tempHomePath = macLibraryApplicationSupportPath;
346 on Linux, check if game uses ${XDG_DATA_HOME}/prefix instead of ${HOME}/.prefix
347 if yes and home path is not user supplied
348 use XDG_DATA_HOME instead of HOME
349 and strip the leading dot
351 basically it produces
352 ${XDG_DATA_HOME}/unvanquished
353 /user/supplied/home/path/unvanquished
357 /user/supplied/home/path/.q3a
360 sprintf( temp, "%s/%s", xdgDataHomePath, ( path + 1 ) );
361 if ( access( temp, X_OK ) == 0 ) {
362 if ( customHomePath == qfalse ) {
363 tempHomePath = xdgDataHomePath;
367 #endif // GDEF_OS_XDG
369 /* concatenate home dir and path */
370 sprintf( temp, "%s/%s", tempHomePath, path );
374 for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- )
375 basePaths[ i + 1 ] = basePaths[ i ];
377 /* add it to the list */
378 basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 );
379 strcpy( basePaths[ 0 ], temp );
380 CleanPath( basePaths[ 0 ] );
387 AddGamePath() - ydnar
388 adds a game path to the list
391 void AddGamePath( char *path ){
395 if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) {
399 /* add it to the list */
400 gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 );
401 strcpy( gamePaths[ numGamePaths ], path );
402 CleanPath( gamePaths[ numGamePaths ] );
405 /* don't add it if it's already there */
406 for ( i = 0; i < numGamePaths - 1; i++ )
408 if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) {
409 free( gamePaths[numGamePaths - 1] );
410 gamePaths[numGamePaths - 1] = NULL;
421 adds a pak path to the list
424 void AddPakPath( char *path ){
426 if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) {
430 /* add it to the list */
431 pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 );
432 strcpy( pakPaths[ numPakPaths ], path );
433 CleanPath( pakPaths[ numPakPaths ] );
441 cleaned up some of the path initialization code from bsp.c
442 will remove any arguments it uses
445 void InitPaths( int *argc, char **argv ){
446 int i, j, k, len, len2;
447 char temp[ MAX_OS_PATH ];
454 Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" );
456 /* get the install path for backup */
457 LokiInitPaths( argv[ 0 ] );
459 /* set game to default (q3a) */
464 EnginePath[0] = '\0';
466 /* parse through the arguments and extract those relevant to paths */
467 for ( i = 0; i < *argc; i++ )
470 if ( argv[ i ] == NULL ) {
475 if ( strcmp( argv[ i ], "-game" ) == 0 ) {
476 if ( ++i >= *argc || !argv[ i ] ) {
477 Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] );
479 argv[ i - 1 ] = NULL;
480 game = GetGame( argv[ i ] );
481 if ( game == NULL ) {
487 /* -fs_forbiddenpath */
488 else if ( strcmp( argv[ i ], "-fs_forbiddenpath" ) == 0 ) {
489 if ( ++i >= *argc || !argv[ i ] ) {
490 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
492 argv[ i - 1 ] = NULL;
493 if ( g_numForbiddenDirs < VFS_MAXDIRS ) {
494 strncpy( g_strForbiddenDirs[g_numForbiddenDirs], argv[i], PATH_MAX );
495 g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = 0;
496 ++g_numForbiddenDirs;
502 else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) {
504 // we don't want any basepath, neither guessed ones
509 /* -fs_nomagicpath */
510 else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) {
516 else if ( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) {
517 if ( ++i >= *argc || !argv[ i ] ) {
518 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
520 argv[ i - 1 ] = NULL;
521 AddBasePath( argv[ i ] );
526 else if ( strcmp( argv[ i ], "-fs_game" ) == 0 ) {
527 if ( ++i >= *argc || !argv[ i ] ) {
528 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
530 argv[ i - 1 ] = NULL;
531 AddGamePath( argv[ i ] );
536 else if ( strcmp( argv[ i ], "-fs_home" ) == 0 ) {
537 if ( ++i >= *argc || !argv[ i ] ) {
538 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
540 argv[ i - 1 ] = NULL;
541 customHomePath = qtrue;
547 else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) {
553 else if ( strcmp( argv[ i ], "-fs_homebase" ) == 0 ) {
554 if ( ++i >= *argc || !argv[ i ] ) {
555 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
557 argv[ i - 1 ] = NULL;
558 homeBasePath = argv[i];
562 /* -fs_homepath - sets both of them */
563 else if ( strcmp( argv[ i ], "-fs_homepath" ) == 0 ) {
564 if ( ++i >= *argc || !argv[ i ] ) {
565 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
567 argv[ i - 1 ] = NULL;
574 else if ( strcmp( argv[ i ], "-fs_pakpath" ) == 0 ) {
575 if ( ++i >= *argc || !argv[ i ] ) {
576 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
578 argv[ i - 1 ] = NULL;
579 AddPakPath( argv[ i ] );
584 /* remove processed arguments */
585 for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ )
587 for ( ; j < *argc && argv[ j ] == NULL; j++ ) ;
588 argv[ i ] = argv[ j ];
589 if ( argv[ i ] != NULL ) {
595 /* add standard game path */
596 AddGamePath( game->gamePath );
598 /* if there is no base path set, figure it out unless fs_nomagicpath is set */
599 if ( numBasePaths == 0 && noBasePath == 0 && noMagicPath == 0 ) {
600 /* this is another crappy replacement for SetQdirFromPath() */
601 len2 = strlen( game->magic );
602 for ( i = 0; i < *argc && numBasePaths == 0; i++ )
604 /* extract the arg */
605 strcpy( temp, argv[ i ] );
607 len = strlen( temp );
608 Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i );
610 /* this is slow, but only done once */
611 for ( j = 0; j < ( len - len2 ); j++ )
613 /* check for the game's magic word */
614 if ( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) {
615 /* now find the next slash and nuke everything after it */
616 while ( temp[ ++j ] != '/' && temp[ j ] != '\0' ) ;
619 /* add this as a base path */
626 /* add install path */
627 if ( numBasePaths == 0 ) {
628 AddBasePath( installPath );
632 if ( numBasePaths == 0 ) {
633 Error( "Failed to find a valid base path." );
637 if ( noBasePath == 1 ) {
641 if ( noHomePath == 0 ) {
642 /* this only affects unix */
643 if ( homeBasePath ) {
644 AddHomeBasePath( homeBasePath );
647 AddHomeBasePath( game->homeBasePath );
651 /* initialize vfs paths */
652 if ( numBasePaths > MAX_BASE_PATHS ) {
653 numBasePaths = MAX_BASE_PATHS;
655 if ( numGamePaths > MAX_GAME_PATHS ) {
656 numGamePaths = MAX_GAME_PATHS;
659 /* walk the list of game paths */
660 for ( j = 0; j < numGamePaths; j++ )
662 /* walk the list of base paths */
663 for ( i = 0; i < numBasePaths; i++ )
665 /* create a full path and initialize it */
666 sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] );
667 vfsInitDirectory( temp );
671 /* initialize vfs paths */
672 if ( numPakPaths > MAX_PAK_PATHS ) {
673 numPakPaths = MAX_PAK_PATHS;
676 /* walk the list of pak paths */
677 for ( i = 0; i < numPakPaths; i++ )
679 /* initialize this pak path */
680 vfsInitDirectory( pakPaths[ i ] );