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 ] );
304 AddHomeBasePath() - ydnar
305 adds a base path to the beginning of the list
308 void AddHomeBasePath( char *path ){
310 char temp[ MAX_OS_PATH ];
312 if ( homePath == NULL ) {
317 if ( path == NULL || path[ 0 ] == '\0' ) {
321 if ( strcmp( path, "." ) == 0 ) {
322 /* -fs_homebase . means that -fs_home is to be used as is */
323 strcpy( temp, homePath );
327 tempHomePath = homePath;
329 /* homePath is . on Windows if not user supplied */
333 use ${HOME}/Library/Application as ${HOME}
334 if home path is not user supplied
335 and strip the leading dot from prefix in any case
337 basically it produces
338 ${HOME}/Library/Application/unvanquished
339 /user/supplied/home/path/unvanquished
341 tempHomePath = macLibraryApplicationSupportPath;
345 on Linux, check if game uses ${XDG_DATA_HOME}/prefix instead of ${HOME}/.prefix
346 if yes and home path is not user supplied
347 use XDG_DATA_HOME instead of HOME
348 and strip the leading dot
350 basically it produces
351 ${XDG_DATA_HOME}/unvanquished
352 /user/supplied/home/path/unvanquished
356 /user/supplied/home/path/.q3a
359 sprintf( temp, "%s/%s", xdgDataHomePath, ( path + 1 ) );
360 if ( access( temp, X_OK ) == 0 ) {
361 if ( customHomePath == qfalse ) {
362 tempHomePath = xdgDataHomePath;
366 #endif // GDEF_OS_XDG
368 /* concatenate home dir and path */
369 sprintf( temp, "%s/%s", tempHomePath, path );
373 for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- )
374 basePaths[ i + 1 ] = basePaths[ i ];
376 /* add it to the list */
377 basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 );
378 strcpy( basePaths[ 0 ], temp );
379 CleanPath( basePaths[ 0 ] );
386 AddGamePath() - ydnar
387 adds a game path to the list
390 void AddGamePath( char *path ){
394 if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) {
398 /* add it to the list */
399 gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 );
400 strcpy( gamePaths[ numGamePaths ], path );
401 CleanPath( gamePaths[ numGamePaths ] );
404 /* don't add it if it's already there */
405 for ( i = 0; i < numGamePaths - 1; i++ )
407 if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) {
408 free( gamePaths[numGamePaths - 1] );
409 gamePaths[numGamePaths - 1] = NULL;
420 adds a pak path to the list
423 void AddPakPath( char *path ){
425 if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) {
429 /* add it to the list */
430 pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 );
431 strcpy( pakPaths[ numPakPaths ], path );
432 CleanPath( pakPaths[ numPakPaths ] );
440 cleaned up some of the path initialization code from bsp.c
441 will remove any arguments it uses
444 void InitPaths( int *argc, char **argv ){
445 int i, j, k, len, len2;
446 char temp[ MAX_OS_PATH ];
453 Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" );
455 /* get the install path for backup */
456 LokiInitPaths( argv[ 0 ] );
458 /* set game to default (q3a) */
463 /* parse through the arguments and extract those relevant to paths */
464 for ( i = 0; i < *argc; i++ )
467 if ( argv[ i ] == NULL ) {
472 if ( strcmp( argv[ i ], "-game" ) == 0 ) {
473 if ( ++i >= *argc || !argv[ i ] ) {
474 Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] );
476 argv[ i - 1 ] = NULL;
477 game = GetGame( argv[ i ] );
478 if ( game == NULL ) {
484 /* -fs_forbiddenpath */
485 else if ( strcmp( argv[ i ], "-fs_forbiddenpath" ) == 0 ) {
486 if ( ++i >= *argc || !argv[ i ] ) {
487 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
489 argv[ i - 1 ] = NULL;
490 if ( g_numForbiddenDirs < VFS_MAXDIRS ) {
491 strncpy( g_strForbiddenDirs[g_numForbiddenDirs], argv[i], PATH_MAX );
492 g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = 0;
493 ++g_numForbiddenDirs;
499 else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) {
501 // we don't want any basepath, neither guessed ones
506 /* -fs_nomagicpath */
507 else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) {
513 else if ( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) {
514 if ( ++i >= *argc || !argv[ i ] ) {
515 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
517 argv[ i - 1 ] = NULL;
518 AddBasePath( argv[ i ] );
523 else if ( strcmp( argv[ i ], "-fs_game" ) == 0 ) {
524 if ( ++i >= *argc || !argv[ i ] ) {
525 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
527 argv[ i - 1 ] = NULL;
528 AddGamePath( argv[ i ] );
533 else if ( strcmp( argv[ i ], "-fs_home" ) == 0 ) {
534 if ( ++i >= *argc || !argv[ i ] ) {
535 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
537 argv[ i - 1 ] = NULL;
538 customHomePath = qtrue;
544 else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) {
550 else if ( strcmp( argv[ i ], "-fs_homebase" ) == 0 ) {
551 if ( ++i >= *argc || !argv[ i ] ) {
552 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
554 argv[ i - 1 ] = NULL;
555 homeBasePath = argv[i];
559 /* -fs_homepath - sets both of them */
560 else if ( strcmp( argv[ i ], "-fs_homepath" ) == 0 ) {
561 if ( ++i >= *argc || !argv[ i ] ) {
562 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
564 argv[ i - 1 ] = NULL;
571 else if ( strcmp( argv[ i ], "-fs_pakpath" ) == 0 ) {
572 if ( ++i >= *argc || !argv[ i ] ) {
573 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
575 argv[ i - 1 ] = NULL;
576 AddPakPath( argv[ i ] );
581 /* remove processed arguments */
582 for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ )
584 for ( ; j < *argc && argv[ j ] == NULL; j++ ) ;
585 argv[ i ] = argv[ j ];
586 if ( argv[ i ] != NULL ) {
592 /* add standard game path */
593 AddGamePath( game->gamePath );
595 /* if there is no base path set, figure it out unless fs_nomagicpath is set */
596 if ( numBasePaths == 0 && noBasePath == 0 && noMagicPath == 0 ) {
597 /* this is another crappy replacement for SetQdirFromPath() */
598 len2 = strlen( game->magic );
599 for ( i = 0; i < *argc && numBasePaths == 0; i++ )
601 /* extract the arg */
602 strcpy( temp, argv[ i ] );
604 len = strlen( temp );
605 Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i );
607 /* this is slow, but only done once */
608 for ( j = 0; j < ( len - len2 ); j++ )
610 /* check for the game's magic word */
611 if ( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) {
612 /* now find the next slash and nuke everything after it */
613 while ( temp[ ++j ] != '/' && temp[ j ] != '\0' ) ;
616 /* add this as a base path */
623 /* add install path */
624 if ( numBasePaths == 0 ) {
625 AddBasePath( installPath );
629 if ( numBasePaths == 0 ) {
630 Error( "Failed to find a valid base path." );
634 if ( noBasePath == 1 ) {
638 if ( noHomePath == 0 ) {
639 /* this only affects unix */
640 if ( homeBasePath ) {
641 AddHomeBasePath( homeBasePath );
644 AddHomeBasePath( game->homeBasePath );
648 /* initialize vfs paths */
649 if ( numBasePaths > MAX_BASE_PATHS ) {
650 numBasePaths = MAX_BASE_PATHS;
652 if ( numGamePaths > MAX_GAME_PATHS ) {
653 numGamePaths = MAX_GAME_PATHS;
656 /* walk the list of game paths */
657 for ( j = 0; j < numGamePaths; j++ )
659 /* walk the list of base paths */
660 for ( i = 0; i < numBasePaths; i++ )
662 /* create a full path and initialize it */
663 sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] );
664 vfsInitDirectory( temp );
668 /* initialize vfs paths */
669 if ( numPakPaths > MAX_PAK_PATHS ) {
670 numPakPaths = MAX_PAK_PATHS;
673 /* walk the list of pak paths */
674 for ( i = 0; i < numPakPaths; i++ )
676 /* initialize this pak path */
677 vfsInitDirectory( pakPaths[ i ] );