]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3map2/path_init.c
q3map2: check Linux prefix against XDG_DATA_HOME before HOME
[xonotic/netradiant.git] / tools / quake3 / q3map2 / path_init.c
1 /* -------------------------------------------------------------------------------
2
3    Copyright (C) 1999-2007 id Software, Inc. and contributors.
4    For a list of contributors, see the accompanying CONTRIBUTORS file.
5
6    This file is part of GtkRadiant.
7
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.
12
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.
17
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
21
22    ----------------------------------------------------------------------------------
23
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."
26
27    ------------------------------------------------------------------------------- */
28
29
30
31 /* marker */
32 #define PATH_INIT_C
33
34
35
36 /* dependencies */
37 #include "q3map2.h"
38
39
40
41 /* path support */
42 #define MAX_BASE_PATHS  10
43 #define MAX_GAME_PATHS  10
44 #define MAX_PAK_PATHS  200
45
46 qboolean                                customHomePath = qfalse;
47 char                    *homePath;
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 ];
52
53 int numBasePaths;
54 char                    *basePaths[ MAX_BASE_PATHS ];
55 int numGamePaths;
56 char                    *gamePaths[ MAX_GAME_PATHS ];
57 int numPakPaths;
58 char                    *pakPaths[ MAX_PAK_PATHS ];
59 char                    *homeBasePath = NULL;
60
61
62 /*
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.
65  */
66
67 /*
68    PathLokiGetHomeDir()
69    gets the user's home dir (for ~/.q3a)
70  */
71
72 char *LokiGetHomeDir( void ){
73         #ifndef Q_UNIX
74         return NULL;
75         #else
76         static char     buf[ 4096 ];
77         struct passwd   pw, *pwp;
78         char            *home;
79
80         /* get the home environment variable */
81         home = getenv( "HOME" );
82
83         /* look up home dir in password database */
84         if( home == NULL )
85         {
86                 if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 ) {
87                         return pw.pw_dir;
88                 }
89         }
90
91         /* return it */
92         return home;
93         #endif
94 }
95
96
97
98 /*
99    PathLokiInitPaths()
100    initializes some paths on linux/os x
101  */
102
103 void LokiInitPaths( char *argv0 ){
104         char *home;
105
106         if ( homePath == NULL ) {
107                 /* get home dir */
108                 home = LokiGetHomeDir();
109                 if ( home == NULL ) {
110                         home = ".";
111                 }
112
113                 /* set home path */
114                 homePath = home;
115         }
116         else {
117                 home = homePath;
118         }
119
120         #if (GDEF_OS_POSIX && !GDEF_OS_MACOS)
121         xdgDataHomePath = getenv( "XDG_DATA_HOME" );
122
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 );
127         }
128         #endif // (GDEF_OS_POSIX && !GDEF_OS_MACOS)
129
130         #ifndef Q_UNIX
131         /* this is kinda crap, but hey */
132         strcpy( installPath, "../" );
133         #else
134
135         char temp[ MAX_OS_PATH ];
136         char *path;
137         char *last;
138         qboolean found;
139
140
141         path = getenv( "PATH" );
142
143         /* do some path divining */
144         Q_strncpyz( temp, argv0, sizeof( temp ) );
145         if ( strrchr( temp, '/' ) ) {
146                 argv0 = strrchr( argv0, '/' ) + 1;
147         }
148         else if ( path != NULL ) {
149
150                 /*
151                    This code has a special behavior when q3map2 is a symbolic link.
152
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.
156
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.
160
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.
166                 */
167
168                 found = qfalse;
169                 last = path;
170
171                 /* go through each : segment of path */
172                 while ( last[ 0 ] != '\0' && found == qfalse )
173                 {
174                         /* null out temp */
175                         temp[ 0 ] = '\0';
176
177                         /* find next chunk */
178                         last = strchr( path, ':' );
179                         if ( last == NULL ) {
180                                 last = path + strlen( path );
181                         }
182
183                         /* found home dir candidate */
184                         if ( *path == '~' ) {
185                                 Q_strncpyz( temp, home, sizeof( temp ) );
186                                 path++;
187                         }
188
189                         /* concatenate */
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 ), "/" );
196                         }
197                         Q_strcat( temp, sizeof( temp ), argv0 );
198
199                         /* verify the path */
200                         if ( access( temp, X_OK ) == 0 ) {
201                                 found = qtrue;
202                         }
203                         path = last + 1;
204                 }
205         }
206
207         /* flake */
208         if ( realpath( temp, installPath ) ) {
209                 /*
210                    if "q3map2" is "/opt/radiant/tools/q3map2",
211                    installPath is "/opt/radiant"
212                 */
213                 *( strrchr( installPath, '/' ) ) = '\0';
214                 *( strrchr( installPath, '/' ) ) = '\0';
215         }
216         #endif
217 }
218
219
220
221 /*
222    CleanPath() - ydnar
223    cleans a dos path \ -> /
224  */
225
226 void CleanPath( char *path ){
227         while ( *path )
228         {
229                 if ( *path == '\\' ) {
230                         *path = '/';
231                 }
232                 path++;
233         }
234 }
235
236
237
238 /*
239    GetGame() - ydnar
240    gets the game_t based on a -game argument
241    returns NULL if no match found
242  */
243
244 game_t *GetGame( char *arg ){
245         int i;
246
247
248         /* dummy check */
249         if ( arg == NULL || arg[ 0 ] == '\0' ) {
250                 return NULL;
251         }
252
253         /* joke */
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" );
262                 exit( 0 );
263         }
264
265         /* test it */
266         i = 0;
267         while ( games[ i ].arg != NULL )
268         {
269                 if ( Q_stricmp( arg, games[ i ].arg ) == 0 ) {
270                         return &games[ i ];
271                 }
272                 i++;
273         }
274
275         /* no matching game */
276         return NULL;
277 }
278
279
280
281 /*
282    AddBasePath() - ydnar
283    adds a base path to the list
284  */
285
286 void AddBasePath( char *path ){
287         /* dummy check */
288         if ( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) {
289                 return;
290         }
291
292         /* add it to the list */
293         basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 );
294         strcpy( basePaths[ numBasePaths ], path );
295         CleanPath( basePaths[ numBasePaths ] );
296         numBasePaths++;
297 }
298
299
300
301 /*
302    AddHomeBasePath() - ydnar
303    adds a base path to the beginning of the list
304  */
305
306 void AddHomeBasePath( char *path ){
307         int i;
308         char temp[ MAX_OS_PATH ];
309
310         if ( homePath == NULL ) {
311                 return;
312         }
313
314         /* dummy check */
315         if ( path == NULL || path[ 0 ] == '\0' ) {
316                 return;
317         }
318
319         if ( strcmp( path, "." ) == 0 ) {
320                 /* -fs_homebase . means that -fs_home is to be used as is */
321                 strcpy( temp, homePath );
322         }
323         else {
324                 char *tempHomePath;
325                 tempHomePath = homePath;
326
327                 #if (GDEF_OS_POSIX && !GDEF_OS_MACOS)
328                 /*
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
333                   
334                    basically it produces
335                    ${XDG_DATA_HOME}/unvanquished
336                    /user/supplied/home/path/unvanquished
337                    
338                    or
339                    ${HOME}/.q3a
340                    /user/supplied/home/path/.q3a
341                  */
342
343                 sprintf( temp, "%s/%s", xdgDataHomePath, ( path + 1 ) );
344                 if ( access( temp, X_OK ) == 0 ) {
345                         if ( customHomePath == qfalse ) {
346                                 tempHomePath = xdgDataHomePath;
347                         }
348                         path = path + 1;
349                 }
350                 #endif // (GDEF_OS_POSIX && !GDEF_OS_MACOS)
351
352                 /* concatenate home dir and path */
353                 sprintf( temp, "%s/%s", tempHomePath, path );
354         }
355
356         /* make a hole */
357         for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- )
358                 basePaths[ i + 1 ] = basePaths[ i ];
359
360         /* add it to the list */
361         basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 );
362         strcpy( basePaths[ 0 ], temp );
363         CleanPath( basePaths[ 0 ] );
364         numBasePaths++;
365 }
366
367
368
369 /*
370    AddGamePath() - ydnar
371    adds a game path to the list
372  */
373
374 void AddGamePath( char *path ){
375         int i;
376
377         /* dummy check */
378         if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) {
379                 return;
380         }
381
382         /* add it to the list */
383         gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 );
384         strcpy( gamePaths[ numGamePaths ], path );
385         CleanPath( gamePaths[ numGamePaths ] );
386         numGamePaths++;
387
388         /* don't add it if it's already there */
389         for ( i = 0; i < numGamePaths - 1; i++ )
390         {
391                 if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) {
392                         free( gamePaths[numGamePaths - 1] );
393                         gamePaths[numGamePaths - 1] = NULL;
394                         numGamePaths--;
395                         break;
396                 }
397         }
398
399 }
400
401
402 /*
403    AddPakPath()
404    adds a pak path to the list
405  */
406
407 void AddPakPath( char *path ){
408         /* dummy check */
409         if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) {
410                 return;
411         }
412
413         /* add it to the list */
414         pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 );
415         strcpy( pakPaths[ numPakPaths ], path );
416         CleanPath( pakPaths[ numPakPaths ] );
417         numPakPaths++;
418 }
419
420
421
422 /*
423    InitPaths() - ydnar
424    cleaned up some of the path initialization code from bsp.c
425    will remove any arguments it uses
426  */
427
428 void InitPaths( int *argc, char **argv ){
429         int i, j, k, len, len2;
430         char temp[ MAX_OS_PATH ];
431
432         int noBasePath = 0;
433         int noHomePath = 0;
434         int noMagicPath = 0;
435
436         /* note it */
437         Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" );
438
439         /* get the install path for backup */
440         LokiInitPaths( argv[ 0 ] );
441
442         /* set game to default (q3a) */
443         game = &games[ 0 ];
444         numBasePaths = 0;
445         numGamePaths = 0;
446
447         /* parse through the arguments and extract those relevant to paths */
448         for ( i = 0; i < *argc; i++ )
449         {
450                 /* check for null */
451                 if ( argv[ i ] == NULL ) {
452                         continue;
453                 }
454
455                 /* -game */
456                 if ( strcmp( argv[ i ], "-game" ) == 0 ) {
457                         if ( ++i >= *argc ) {
458                                 Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] );
459                         }
460                         argv[ i - 1 ] = NULL;
461                         game = GetGame( argv[ i ] );
462                         if ( game == NULL ) {
463                                 game = &games[ 0 ];
464                         }
465                         argv[ i ] = NULL;
466                 }
467
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 ] );
472                         }
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;
478                         }
479                         argv[ i ] = NULL;
480                 }
481
482                 /* -fs_nobasepath */
483                 else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) {
484                         noBasePath = 1;
485                         // we don't want any basepath, neither guessed ones
486                         noMagicPath = 1;
487                         argv[ i ] = NULL;
488                 }               
489
490                 /* -fs_nomagicpath */
491                 else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) {
492                         noMagicPath = 1;
493                         argv[ i ] = NULL;
494                 }
495
496                 /* -fs_basepath */
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 ] );
500                         }
501                         argv[ i - 1 ] = NULL;
502                         AddBasePath( argv[ i ] );
503                         argv[ i ] = NULL;
504                 }
505
506                 /* -fs_game */
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 ] );
510                         }
511                         argv[ i - 1 ] = NULL;
512                         AddGamePath( argv[ i ] );
513                         argv[ i ] = NULL;
514                 }
515
516                 /* -fs_home */
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 ] );
520                         }
521                         argv[ i - 1 ] = NULL;
522                         customHomePath = qtrue;
523                         homePath = argv[i];
524                         argv[ i ] = NULL;
525                 }
526
527                 /* -fs_nohomepath */
528                 else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) {
529                         noHomePath = 1;
530                         argv[ i ] = NULL;
531                 }               
532
533                 /* -fs_homebase */
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 ] );
537                         }
538                         argv[ i - 1 ] = NULL;
539                         homeBasePath = argv[i];
540                         argv[ i ] = NULL;
541                 }
542
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 ] );
547                         }
548                         argv[ i - 1 ] = NULL;
549                         homePath = argv[i];
550                         homeBasePath = ".";
551                         argv[ i ] = NULL;
552                 }
553
554                 /* -fs_pakpath */
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 ] );
558                         }
559                         argv[ i - 1 ] = NULL;
560                         AddPakPath( argv[ i ] );
561                         argv[ i ] = NULL;
562                 }
563         }
564
565         /* remove processed arguments */
566         for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ )
567         {
568                 for ( ; j < *argc && argv[ j ] == NULL; j++ ) ;
569                 argv[ i ] = argv[ j ];
570                 if ( argv[ i ] != NULL ) {
571                         k++;
572                 }
573         }
574         *argc = k;
575
576         /* add standard game path */
577         AddGamePath( game->gamePath );
578
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++ )
584                 {
585                         /* extract the arg */
586                         strcpy( temp, argv[ i ] );
587                         CleanPath( temp );
588                         len = strlen( temp );
589                         Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i );
590
591                         /* this is slow, but only done once */
592                         for ( j = 0; j < ( len - len2 ); j++ )
593                         {
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' ) ;
598                                         temp[ j ] = '\0';
599
600                                         /* add this as a base path */
601                                         AddBasePath( temp );
602                                         break;
603                                 }
604                         }
605                 }
606
607                 /* add install path */
608                 if ( numBasePaths == 0 ) {
609                         AddBasePath( installPath );
610                 }
611
612                 /* check again */
613                 if ( numBasePaths == 0 ) {
614                         Error( "Failed to find a valid base path." );
615                 }
616         }
617
618         if ( noBasePath == 1 ) {
619                 numBasePaths = 0;
620         }
621
622         if ( noHomePath == 0 ) {
623                 /* this only affects unix */
624                 if ( homeBasePath ) {
625                         AddHomeBasePath( homeBasePath );
626                 }
627                 else{
628                         AddHomeBasePath( game->homeBasePath );
629                 }
630         }
631
632         /* initialize vfs paths */
633         if ( numBasePaths > MAX_BASE_PATHS ) {
634                 numBasePaths = MAX_BASE_PATHS;
635         }
636         if ( numGamePaths > MAX_GAME_PATHS ) {
637                 numGamePaths = MAX_GAME_PATHS;
638         }
639
640         /* walk the list of game paths */
641         for ( j = 0; j < numGamePaths; j++ )
642         {
643                 /* walk the list of base paths */
644                 for ( i = 0; i < numBasePaths; i++ )
645                 {
646                         /* create a full path and initialize it */
647                         sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] );
648                         vfsInitDirectory( temp );
649                 }
650         }
651
652         /* initialize vfs paths */
653         if ( numPakPaths > MAX_PAK_PATHS ) {
654                 numPakPaths = MAX_PAK_PATHS;
655         }
656
657         /* walk the list of pak paths */
658         for ( i = 0; i < numPakPaths; i++ )
659         {
660                 /* initialize this pak path */
661                 vfsInitDirectory( pakPaths[ i ] );
662         }
663
664         /* done */
665         Sys_Printf( "\n" );
666 }