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 ) );
193 if ( last > ( path + 1 ) ) {
194 // +1 hack: Q_strncat calls Q_strncpyz that expects a len including '\0'
195 // so that extraneous char will be rewritten by '\0', so it's ok.
196 // Also, in this case this extraneous char is always ':' or '\0', so it's ok.
197 Q_strncat( temp, sizeof( temp ), path, ( last - path + 1) );
198 Q_strcat( temp, sizeof( temp ), "/" );
200 Q_strcat( temp, sizeof( temp ), argv0 );
202 /* verify the path */
203 if ( access( temp, X_OK ) == 0 ) {
211 if ( realpath( temp, installPath ) ) {
213 if "q3map2" is "/opt/radiant/tools/q3map2",
214 installPath is "/opt/radiant"
216 *( strrchr( installPath, '/' ) ) = '\0';
217 *( strrchr( installPath, '/' ) ) = '\0';
219 #endif // !GDEF_OS_WINDOWS
226 cleans a dos path \ -> /
229 void CleanPath( char *path ){
232 if ( *path == '\\' ) {
243 gets the game_t based on a -game argument
244 returns NULL if no match found
247 game_t *GetGame( char *arg ){
252 if ( arg == NULL || arg[ 0 ] == '\0' ) {
257 if ( !Q_stricmp( arg, "quake1" ) ||
258 !Q_stricmp( arg, "quake2" ) ||
259 !Q_stricmp( arg, "unreal" ) ||
260 !Q_stricmp( arg, "ut2k3" ) ||
261 !Q_stricmp( arg, "dn3d" ) ||
262 !Q_stricmp( arg, "dnf" ) ||
263 !Q_stricmp( arg, "hl" ) ) {
264 Sys_Printf( "April fools, silly rabbit!\n" );
270 while ( games[ i ].arg != NULL )
272 if ( Q_stricmp( arg, games[ i ].arg ) == 0 ) {
278 /* no matching game */
285 AddBasePath() - ydnar
286 adds a base path to the list
289 void AddBasePath( char *path ){
291 if ( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) {
295 /* add it to the list */
296 basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 );
297 strcpy( basePaths[ numBasePaths ], path );
298 CleanPath( basePaths[ numBasePaths ] );
299 if ( EnginePath[0] == '\0' ) strcpy( EnginePath, basePaths[ numBasePaths ] );
306 AddHomeBasePath() - ydnar
307 adds a base path to the beginning of the list
310 void AddHomeBasePath( char *path ){
312 char temp[ MAX_OS_PATH ];
314 if ( homePath == NULL ) {
319 if ( path == NULL || path[ 0 ] == '\0' ) {
323 if ( strcmp( path, "." ) == 0 ) {
324 /* -fs_homebase . means that -fs_home is to be used as is */
325 strcpy( temp, homePath );
329 tempHomePath = homePath;
331 /* homePath is . on Windows if not user supplied */
335 use ${HOME}/Library/Application as ${HOME}
336 if home path is not user supplied
337 and strip the leading dot from prefix in any case
339 basically it produces
340 ${HOME}/Library/Application/unvanquished
341 /user/supplied/home/path/unvanquished
343 tempHomePath = macLibraryApplicationSupportPath;
347 on Linux, check if game uses ${XDG_DATA_HOME}/prefix instead of ${HOME}/.prefix
348 if yes and home path is not user supplied
349 use XDG_DATA_HOME instead of HOME
350 and strip the leading dot
352 basically it produces
353 ${XDG_DATA_HOME}/unvanquished
354 /user/supplied/home/path/unvanquished
358 /user/supplied/home/path/.q3a
361 sprintf( temp, "%s/%s", xdgDataHomePath, ( path + 1 ) );
362 if ( access( temp, X_OK ) == 0 ) {
363 if ( customHomePath == qfalse ) {
364 tempHomePath = xdgDataHomePath;
368 #endif // GDEF_OS_XDG
370 /* concatenate home dir and path */
371 sprintf( temp, "%s/%s", tempHomePath, path );
375 for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- )
376 basePaths[ i + 1 ] = basePaths[ i ];
378 /* add it to the list */
379 basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 );
380 strcpy( basePaths[ 0 ], temp );
381 CleanPath( basePaths[ 0 ] );
388 AddGamePath() - ydnar
389 adds a game path to the list
392 void AddGamePath( char *path ){
396 if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) {
400 /* add it to the list */
401 gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 );
402 strcpy( gamePaths[ numGamePaths ], path );
403 CleanPath( gamePaths[ numGamePaths ] );
406 /* don't add it if it's already there */
407 for ( i = 0; i < numGamePaths - 1; i++ )
409 if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) {
410 free( gamePaths[numGamePaths - 1] );
411 gamePaths[numGamePaths - 1] = NULL;
422 adds a pak path to the list
425 void AddPakPath( char *path ){
427 if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) {
431 /* add it to the list */
432 pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 );
433 strcpy( pakPaths[ numPakPaths ], path );
434 CleanPath( pakPaths[ numPakPaths ] );
442 cleaned up some of the path initialization code from bsp.c
443 will remove any arguments it uses
446 void InitPaths( int *argc, char **argv ){
447 int i, j, k, len, len2;
448 char temp[ MAX_OS_PATH ];
455 Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" );
457 /* get the install path for backup */
458 LokiInitPaths( argv[ 0 ] );
460 /* set game to default (q3a) */
465 EnginePath[0] = '\0';
467 /* parse through the arguments and extract those relevant to paths */
468 for ( i = 0; i < *argc; i++ )
471 if ( argv[ i ] == NULL ) {
476 if ( strcmp( argv[ i ], "-game" ) == 0 ) {
477 if ( ++i >= *argc || !argv[ i ] ) {
478 Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] );
480 argv[ i - 1 ] = NULL;
481 game = GetGame( argv[ i ] );
482 if ( game == NULL ) {
488 /* -fs_forbiddenpath */
489 else if ( strcmp( argv[ i ], "-fs_forbiddenpath" ) == 0 ) {
490 if ( ++i >= *argc || !argv[ i ] ) {
491 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
493 argv[ i - 1 ] = NULL;
494 if ( g_numForbiddenDirs < VFS_MAXDIRS ) {
495 strncpy( g_strForbiddenDirs[g_numForbiddenDirs], argv[i], PATH_MAX );
496 g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = 0;
497 ++g_numForbiddenDirs;
503 else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) {
505 // we don't want any basepath, neither guessed ones
510 /* -fs_nomagicpath */
511 else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) {
517 else if ( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) {
518 if ( ++i >= *argc || !argv[ i ] ) {
519 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
521 argv[ i - 1 ] = NULL;
522 AddBasePath( argv[ i ] );
527 else if ( strcmp( argv[ i ], "-fs_game" ) == 0 ) {
528 if ( ++i >= *argc || !argv[ i ] ) {
529 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
531 argv[ i - 1 ] = NULL;
532 AddGamePath( argv[ i ] );
537 else if ( strcmp( argv[ i ], "-fs_home" ) == 0 ) {
538 if ( ++i >= *argc || !argv[ i ] ) {
539 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
541 argv[ i - 1 ] = NULL;
542 customHomePath = qtrue;
548 else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) {
554 else if ( strcmp( argv[ i ], "-fs_homebase" ) == 0 ) {
555 if ( ++i >= *argc || !argv[ i ] ) {
556 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
558 argv[ i - 1 ] = NULL;
559 homeBasePath = argv[i];
563 /* -fs_homepath - sets both of them */
564 else if ( strcmp( argv[ i ], "-fs_homepath" ) == 0 ) {
565 if ( ++i >= *argc || !argv[ i ] ) {
566 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
568 argv[ i - 1 ] = NULL;
575 else if ( strcmp( argv[ i ], "-fs_pakpath" ) == 0 ) {
576 if ( ++i >= *argc || !argv[ i ] ) {
577 Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] );
579 argv[ i - 1 ] = NULL;
580 AddPakPath( argv[ i ] );
585 /* remove processed arguments */
586 for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ )
588 for ( ; j < *argc && argv[ j ] == NULL; j++ ) ;
589 argv[ i ] = argv[ j ];
590 if ( argv[ i ] != NULL ) {
596 /* add standard game path */
597 AddGamePath( game->gamePath );
599 /* if there is no base path set, figure it out unless fs_nomagicpath is set */
600 if ( numBasePaths == 0 && noBasePath == 0 && noMagicPath == 0 ) {
601 /* this is another crappy replacement for SetQdirFromPath() */
602 len2 = strlen( game->magic );
603 for ( i = 0; i < *argc && numBasePaths == 0; i++ )
605 /* extract the arg */
606 strcpy( temp, argv[ i ] );
608 len = strlen( temp );
609 Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i );
611 /* this is slow, but only done once */
612 for ( j = 0; j < ( len - len2 ); j++ )
614 /* check for the game's magic word */
615 if ( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) {
616 /* now find the next slash and nuke everything after it */
617 while ( temp[ ++j ] != '/' && temp[ j ] != '\0' ) ;
620 /* add this as a base path */
627 /* add install path */
628 if ( numBasePaths == 0 ) {
629 AddBasePath( installPath );
633 if ( numBasePaths == 0 ) {
634 Error( "Failed to find a valid base path." );
638 if ( noBasePath == 1 ) {
642 if ( noHomePath == 0 ) {
643 /* this only affects unix */
644 if ( homeBasePath ) {
645 AddHomeBasePath( homeBasePath );
648 AddHomeBasePath( game->homeBasePath );
652 /* initialize vfs paths */
653 if ( numBasePaths > MAX_BASE_PATHS ) {
654 numBasePaths = MAX_BASE_PATHS;
656 if ( numGamePaths > MAX_GAME_PATHS ) {
657 numGamePaths = MAX_GAME_PATHS;
660 /* walk the list of game paths */
661 for ( j = 0; j < numGamePaths; j++ )
663 /* walk the list of base paths */
664 for ( i = 0; i < numBasePaths; i++ )
666 /* create a full path and initialize it */
667 sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] );
668 vfsInitDirectory( temp );
672 /* initialize vfs paths */
673 if ( numPakPaths > MAX_PAK_PATHS ) {
674 numPakPaths = MAX_PAK_PATHS;
677 /* walk the list of pak paths */
678 for ( i = 0; i < numPakPaths; i++ )
680 /* initialize this pak path */
681 vfsInitDirectory( pakPaths[ i ] );