1 /*
2
3 *************************************************************************
4
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000 Manuel Moos (manuel@moosnet.de)
7 Copyright (C) 2004 Armagetron Advanced Team (http://sourceforge.net/projects/armagetronad/)
8
9 **************************************************************************
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25 ***************************************************************************
26
27 */
28
29 #include "config.h"
30
31 #include <errno.h>
32 #include <sys/types.h>
33 #ifndef WIN32
34 #include <sys/stat.h>
35 #endif
36 #include <stdio.h>
37 #include <stdlib.h>
38
39 #include "tLocale.h"
40 #include "tDirectories.h"
41 #include "tString.h"
42 #include "tConfiguration.h"
43 #include "tCommandLine.h"
44 #include "tMemManager.h"
45
46 // include definition for top source directory
47 #ifndef MACOSX
48 #ifdef TOP_SOURCE_DIR
49 static const char * s_topSourceDir = TOP_SOURCE_DIR;
50 #else
51 static const char * s_topSourceDir = ".";
52 #endif
53 #endif
54
55 // program name definition
56 #ifndef PROGNAME
57 #ifdef DEDICATED
58 #define PROGNAME "armagetronad-dedicated"
59 #else
60 #define PROGNAME "armagetronad"
61 #endif
62 #endif
63
64 #ifndef PROGNAMEBASE
65 #define PROGNAMEBASE "armagetronad"
66 #endif
67
68 #ifndef PROGDIR_SUFFIX
69 #define PROGDIR_SUFFIX ""
70 #endif
71
72 #ifdef TOP_SOURCE_DIR
73 // #include "tPaths.h"
74 #include "tUniversalVariables.h"
75 #endif
76
77 #ifndef PREFIX
78 #define PREFIX "/usr/local"
79 #endif
80
81 #ifndef BINDIR
82 #define BINDIR "/usr/local/bin"
83 #endif
84
85 // move variables out of binrelocs macros way
86 static tString st_prefixCompiled(PREFIX);
87 static tString st_bindirCompiled(BINDIR);
88
89 // Paths from root to binaries, data and config ( so we can do search&replace to get
90 // from the binary path to the data and config )
91 #ifndef SYSBINDIR
92 #define SYSBINDIR "/does/not/exist"
93 #endif
94
95 #ifndef DATASUFFIX
96 #define DATASUFFIX ""
97 #endif
98
99 #ifndef CONFIGSUFFIX
100 #define CONFIGSUFFIX ""
101 #endif
102
103 #define PROGDIR PROGNAME PROGDIR_SUFFIX
104
105 #ifdef WIN32
106 // activate used function from shlobj.h (z-man does not know if this is a bad hack)
107 #ifdef __MINGW32__
108 #define _WIN32_IE 0x400
109 #endif
110
111 #include <direct.h>
112 #include <windows.h>
113 #include <shlobj.h>
114 #define mkdir(x, y) _mkdir(x)
115 #ifndef strdup
116 #define strdup _strdup
117 #endif
118 #ifndef _stat
119 #define _stat stat
120 #endif
121 #else
122 #include <pwd.h>
123 #endif
124
125 #ifdef DEBUG
126 // define this if you want file search debug output
127 //#define PRINTSEARCH
128 //#define DEBUG_PATH
129 #endif
130
131 #include <dirent.h>
132 #include <sys/types.h>
133 #include <sys/stat.h>
134
135 #ifdef ENABLE_BINRELOC
136 #include "thirdparty/binreloc/prefix.h"
137 #endif
138
139 tString expand_home(const tString & pathname);
140
141 #define expand_home_c(c) expand_home(tString(c))
142
143 #ifdef DATA_DIR
144 static tString st_DataDir(expand_home_c(DATA_DIR)); // directory for game data
145 #else
146 static tString st_DataDir("."); // directory for game data
147 #endif
148
149 // #define USER_DATA_DIR "${APPDATA}/Armagetron"
150
151 #ifdef USER_DATA_DIR
152 static tString st_UserDataDir(expand_home_c(USER_DATA_DIR)); // directory for game data
153 #else
154 static tString st_UserDataDir(expand_home_c("~/." PROGDIR)); // directory for game data
155 #endif
156
157 #ifdef CONFIG_DIR
158 static tString st_ConfigDir(expand_home_c(CONFIG_DIR)); // directory for static configuration files
159 #else
160 static tString st_ConfigDir(""); // directory for static configuration files
161 #endif
162
163 #ifdef USER_CONFIG_DIR
164 static tString st_UserConfigDir(expand_home_c(USER_CONFIG_DIR)); // directory for static configuration files
165 #else
166 static tString st_UserConfigDir(""); // directory for static configuration files
167 #endif
168
169 #ifdef VAR_DIR
170 static tString st_VarDir(expand_home_c(VAR_DIR)); // directory for dynamic logs and highscores
171 #else
172 static tString st_VarDir(""); // directory for dynamic logs and highscores
173 #endif
174
175 #ifdef RESOURCE_DIR
176 static tString st_ResourceDir(expand_home_c(RESOURCE_DIR));
177 #else
178 static tString st_ResourceDir("");
179 #endif
180
181 #ifdef AUTORESOURCE_DIR
182 static tString st_AutoResourceDir(expand_home_c(AUTORESOURCE_DIR));
183 #else
184 static tString st_AutoResourceDir("");
185 #endif
186
187 #ifdef INCLUDEDRESOURCE_DIR
188 static tString st_IncludedResourceDir(expand_home_c(INCLUDEDRESOURCE_DIR));
189 #else
190 static tString st_IncludedResourceDir("");
191 #endif
192
193 #ifdef SCREENSHOT_DIR
194 static tString st_ScreenshotDir(expand_home_c(SCREENSHOT_DIR));
195 #else
196 static tString st_ScreenshotDir("");
197 #endif
198
199 // checks whether a character is a path delimiter
st_IsPathDelimiter(char c)200 static bool st_IsPathDelimiter( char c )
201 {
202 return ( c == '/' || c == '\\' );
203 }
204
205 /*
206 Recursively creates directories.
207 Parameters:
208 pathname: The path to create. Must have aat least one / and last directory must be suffix'd with a /
209 safe_path: The number of characters to assume safe for usage. Checking for .* is disabled for these.
210 Returns true if successful. Returns false if unsuccessful or in hidden-file circumstances!
211 */
mkdir_recurse(char * pathname,size_t safe_path)212 static bool mkdir_recurse(char *pathname, size_t safe_path) {
213 // method: strip path segments from the end, try to make the directory up to there.
214 // once this succeeded, rebuild the path, making all the subdirectories as you go.
215
216 size_t i;
217 bool fs = false; // forward search flag. if set, start rebuilding the path
218 bool e = false; // error flag.
219
220 size_t len = strlen(pathname); // the total length of the path
221
222 for (i = len - 1; i < len && !e; fs ? ++i : --i) {
223 if ( st_IsPathDelimiter( pathname[i] ) ) {
224 if (pathname[i + 1] == '.' && i + 2 > safe_path)
225 return false; // abort; we have a hidden file/dir!
226 char delimiter = pathname[i];
227 pathname[i] = '\0';
228
229 #ifdef DEBUG_PATH
230 static bool first = true;
231 if (fs || first )
232 {
233 first = false;
234 con << "Making directory " << pathname << "\n";
235 }
236 #endif
237
238 if (!mkdir(pathname, 0777) || errno == EEXIST)
239 fs = true;
240 else if (fs)
241 e = true;
242 pathname[i] = delimiter;
243 }
244 if (i == 0 && !fs)
245 e = true;
246 }
247 return !e;
248 }
249
250 static bool st_checkPathAbsolute = true; // check for absolute paths
251 static bool st_checkPathRelative = true; // check for relative paths
252 static bool st_checkPathHidden = true; // check for hidden files/directories in paths
253
254 // *******************************************************************************************
255 // *
256 // * IsValidPath
257 // *
258 // *******************************************************************************************
259 //!
260 //! checks whether a user given path, to be prepended with one of the AA directories, is
261 //! a valid path (does not use dirty filename tricks to access portions of the file system
262 //! it isn't supposed to access). It is called by all path finding functions of tPath
263 //! automatically, you should only call it from outside of tDirectories.cpp if you want
264 //! to avoid redundant error messages if you try several path resolutions.
265 //!
266 //! @todo make this throw exceptions instead, right now nobody is there to catch them
267 //!
268 //! @param filename the user given filename to check
269 //! @return true if no security issues were found
270 //!
271 // *******************************************************************************************
272
IsValidPath(char const * filename)273 bool tPath::IsValidPath( char const * filename )
274 {
275 // always check for empty paths
276 if ( !filename || filename[0] == 0 )
277 {
278 con << tOutput( "$directory_path_null" );
279 return false;
280 }
281
282 // check for absolute paths (the system would not make them absolute, so no real
283 // danger comes from them, but we check anyway)
284 if ( st_checkPathAbsolute && ( st_IsPathDelimiter( filename[0] ) || strstr(filename,":") ) )
285 {
286 con << tOutput( "$directory_path_absolute", filename );
287 return false;
288 }
289
290 // check for relative paths. Those are the real danger. Search for .. and see
291 // if it surrounded by path delimiters or string ends.
292 if ( st_checkPathRelative )
293 {
294 // traverse through occurences of ..
295 char const * run = filename;
296 while ( run && *run )
297 {
298 // find next ..
299 run = strstr(run,"..");
300
301 // check if before and after that come no path delimiters
302 if ( run )
303 {
304 if ( ( run == filename || st_IsPathDelimiter( run[-1] ) ) &&
305 ( run[2] == 0 || st_IsPathDelimiter( run[2] ) ) )
306 {
307 con << tOutput( "$directory_path_relative", filename );
308 return false;
309 }
310
311 // go on searching
312 run ++;
313 }
314 }
315 }
316
317 // hidden paths are a path delimiter followed by a ., but not ..<delimiter>.
318 if ( st_checkPathHidden )
319 {
320 // iterate path segments
321 char const * run = filename;
322 while ( run && *run )
323 {
324 // check if it is a hidden file
325 if ( run[0] == '.' )
326 {
327 // don't give false alarm for relative paths
328 if ( run[1] != '.' || ( !st_IsPathDelimiter( run[2] ) && run[2] != 0 ) )
329 {
330 con << tOutput( "$directory_path_hidden", filename );
331 return false;
332 }
333 }
334
335 // proceed to next path segments: find next path delimiter
336 while ( *run && !st_IsPathDelimiter( *run ) )
337 ++run;
338
339 // go to next character after that
340 if ( *run )
341 ++run;
342 }
343 }
344
345 return true;
346 }
347
348 /*
349 Parses "~", "~username", "${HOME}", "${HOME:username}", etc
350 */
eh_getdir(const char * da,size_t * len)351 char *eh_getdir(const char *da, size_t *len) {
352 const char *s, *r, *d;
353 char *type = 0, *user = 0, *ret = 0;
354 size_t l;
355
356 // Step 1: Extract type, user, and *len
357 if (da[0] == '~') {
358 type = strdup("HOME");
359 {
360 // find first occurence of slash or backslash
361 char const * slash = strchr(da, '/');
362 char const * backslash = strchr(da, '\\');
363 if ( slash && backslash )
364 d = slash < backslash ? slash : backslash;
365 else
366 d = slash ? slash : backslash;
367 }
368 l = ((d ? d : strchr(da, '\0')) - da - 1) * sizeof(char);
369 user = (char *)memcpy(malloc(l + sizeof(char)), da + 1, l); user[l] = '\0';
370 *len = l + 1;
371 } else if (!strncmp(da, "${", 2)) {
372 s = strchr(da, '}');
373 if (!s) {
374 // Invalid structure
375 return 0;
376 }
377 if ((r = strchr(da, ':'))) {
378 l = (s - r - 1) * sizeof(char);
379 user = (char *)memcpy(malloc(l + sizeof(char)), r + 1, l); user[l] = '\0';
380 } else {
381 r = s;
382 user = (char *)malloc(sizeof(char));
383 user[0] = '\0';
384 }
385 l = (r - da - 2) * sizeof(char);
386 type = (char *)memcpy(malloc(l + sizeof(char)), da + 2, l); type[l] = '\0';
387 *len = s - da + 1;
388 }
389
390 // Step 2: Resolve type/user into ret
391 #ifdef WIN32
392 if (!strcmp(type, "HOME")) {
393 free(type);
394 type = strdup("HOMEPATH");
395 }
396 #endif
397 if ((ret = getenv(type)))
398 ret = strdup(ret);
399 else {
400 # ifdef WIN32
401 {
402 char path[MAX_PATH];
403 int ssf = 0;
404
405 // Bug! This assumes user == current user! (who cares, but... yeah)
406 if (!strcmp(type, "HOME")) ssf = 0x28;
407 if (!strcmp(type, "APPDATA")) ssf = 0x1a;
408 if (!strcmp(type, "COMMONAPPDATA")) ssf = 0x23;
409 if (!strcmp(type, "DESKTOP")) ssf = 0x10;
410 if (!strcmp(type, "LOCALAPPDATA")) ssf = 0x1c;
411 if (!strcmp(type, "MYPICTURES")) ssf = 0x27;
412 if (!strcmp(type, "PERSONAL")) ssf = 0x05;
413 if (!strcmp(type, "PROFILE")) ssf = 0x28;
414 if (!strcmp(type, "SYSTEM")) ssf = 0x25;
415 if (!strcmp(type, "WINDOWS")) ssf = 0x24;
416
417 //if (ssf && SUCCEEDED(SHGetFolderPath(NULL, ssf, NULL, 0, path)))
418 if (ssf && SHGetSpecialFolderPath(NULL, path, ssf, 1))
419 ret = strdup(path);
420 }
421 # else
422 // fall back to default for HOME
423 if (!strcmp(type, "HOME")) {
424 struct passwd *pw;
425
426 if (user[0] == '\0') // Current user
427 pw = getpwuid(getuid());
428 else
429 pw = getpwnam(user);
430 if (pw)
431 ret = strdup(pw->pw_dir);
432 // struct passwd seems to do some really freaky stuff and doesn't like freeing? can someone confirm this?
433 }
434 # endif
435 // TODO: fall back to hardcoded stuff in some cases?
436 }
437
438 #ifdef DEBUG_PATH
439 std::cout << "Changing " << type << " to " << ret << "\n";
440 #endif
441
442 // Step 3: Cleanup
443 free(type);
444 free(user);
445 if (!ret && da[0] != '~') {
446 // Valid, but undefined
447 free(ret);
448 ret = (char *)malloc(sizeof(char));
449 ret[0] = '\0';
450 }
451
452 return ret;
453 }
454
expand_home(tString const & pathname)455 tString expand_home(tString const & pathname) {
456 const char *pn = (const char *)pathname;
457 char *s;
458 size_t len;
459 tString r;
460
461 if ((pn[0] == '~' || !strncmp(pn, "${", 2)) && (s = eh_getdir(pn, &len))) {
462 r = tString(s) << (pn + len);
463 free(s);
464 }
465 else
466 r = pathname;
467
468 #ifdef DEBUG
469 printf("changed %s to %s\n", (const char *)pathname, (const char *)r);
470 #endif
471 return r;
472 }
473
474 class tPathConfig: public tPath
475 {
476 public:
tPathConfig()477 tPathConfig() {}
478 private:
Paths(tArray<tString> & paths) const479 void Paths ( tArray< tString >& paths ) const
480 {
481 paths.SetLen( 0 );
482 int pos = 0;
483
484 paths[ pos++ ] = st_DataDir + "/config";
485
486 if ( st_ConfigDir.Len() > 1 )
487 {
488 paths[ pos++ ] = st_ConfigDir;
489 }
490
491 if ( st_UserDataDir.Len() > 1 )
492 {
493 paths[ pos++ ] = st_UserDataDir + "/config";
494 }
495
496 if ( st_UserConfigDir.Len() > 1 )
497 {
498 paths[ pos++ ] = st_UserConfigDir;
499 }
500 }
501 };
502
503 static const tPathConfig st_Config;
504
505
506 class tPathData: public tPath
507 {
508 public:
tPathData()509 tPathData() {}
510 private:
Paths(tArray<tString> & paths) const511 void Paths ( tArray< tString >& paths ) const
512 {
513 paths.SetLen( 0 );
514 int pos = 0;
515
516 paths[ pos++ ] = st_DataDir;
517
518 if ( st_UserDataDir.Len() > 1 )
519 {
520 paths[ pos++ ] = st_UserDataDir;
521 }
522 }
523 };
524
525 static const tPathData st_Data;
526
527
528 class tPathVar: public tPath
529 {
530 public:
tPathVar()531 tPathVar() {}
532 private:
Paths(tArray<tString> & paths) const533 void Paths ( tArray< tString >& paths ) const
534 {
535 paths.SetLen( 0 );
536 int pos = 0;
537
538 paths[ pos++ ] = st_DataDir + "/var";
539
540 if ( st_UserDataDir.Len() > 1 )
541 {
542 paths[ pos++ ] = st_UserDataDir + "/var";
543 }
544
545 if ( st_VarDir.Len() > 1 )
546 {
547 paths[ pos++ ] = st_VarDir;
548 }
549 }
550 };
551
552 static const tPathVar st_Var;
553
554
555 class tPathScreenshot: public tPath
556 {
557 public:
tPathScreenshot()558 tPathScreenshot() {}
559 private:
Paths(tArray<tString> & paths) const560 void Paths ( tArray< tString >& paths ) const
561 {
562 paths.SetLen( 0 );
563 int pos = 0;
564
565 paths[ pos++ ] = st_DataDir + "/screenshot";
566
567 if ( st_UserDataDir.Len() > 1 )
568 paths[ pos++ ] = st_UserDataDir + "/screenshot";
569
570 if ( st_ScreenshotDir.Len() > 1 )
571 paths[ pos++ ] = st_ScreenshotDir;
572 }
573 };
574
575 static const tPathScreenshot st_Screenshot;
576
577
GetWritePath(const char * filename) const578 tString tPathResource::GetWritePath(const char *filename) const {
579 if ( !tPath::IsValidPath( filename ) )
580 return tString();
581
582 tArray< tString > paths;
583 Paths( paths );
584
585 tString fullname;
586 fullname << paths(0) << "/" << filename;
587
588 {
589 bool s;
590
591 char *fpmr = strdup( static_cast< char const * >( fullname ) );
592 s = mkdir_recurse(fpmr, paths(0).Len());
593 free(fpmr);
594
595 if (!s)
596 {
597 tERR_WARN( tOutput( "$directory_path_nonwritable", fullname ) );
598 return tString();
599 }
600 }
601
602 return fullname;
603 }
604
GetIncluded() const605 tString tPathResource::GetIncluded() const {
606 if ( st_IncludedResourceDir.Len() > 2 )
607 return st_IncludedResourceDir;
608 else
609 return st_DataDir + "/resource/included";
610 }
611
Paths(tArray<tString> & paths) const612 void tPathResource::Paths(tArray< tString >& paths) const {
613 paths.SetLen( 0 );
614 int pos = 0;
615
616 if ( st_AutoResourceDir.Len() > 1 )
617 paths[ pos++ ] = st_AutoResourceDir;
618 else if ( st_ResourceDir.Len() > 1 )
619 paths[ pos++ ] = st_ResourceDir + "/automatic";
620 else if ( st_UserDataDir.Len() > 1 )
621 paths[ pos++ ] = st_UserDataDir + "/resource/automatic";
622 else
623 paths[ pos++ ] = st_DataDir + "/resource/automatic";
624
625 paths[ pos++ ] = st_DataDir + "/resource/included";
626 if ( st_IncludedResourceDir.Len() > 1 )
627 paths[ pos++ ] = st_IncludedResourceDir;
628
629 paths[ pos++ ] = st_DataDir + "/resource";
630 if ( st_UserDataDir.Len() > 1 )
631 paths[ pos++ ] = st_UserDataDir + "/resource";
632 if ( st_ResourceDir.Len() > 1 )
633 paths[ pos++ ] = st_ResourceDir;
634 }
635
636
637 static const tPathResource st_Resource;
638
639
640
641
642
643
Open(std::ifstream & f,const char * filename) const644 bool tPath::Open ( std::ifstream& f,
645 const char* filename ) const
646 {
647 if ( !tPath::IsValidPath( filename ) )
648 return false;
649
650 tArray< tString > paths;
651 Paths( paths );
652
653 for ( int prio = paths.Len() - 1; prio>=0; --prio )
654 {
655 // std::ifstream test;
656
657 tString fullname;
658 fullname << paths( prio ) << "/" << filename;
659
660 #ifdef PRINTSEARCH
661 #endif
662
663 // test.open( fullname );
664 f.clear();
665 f.open( fullname );
666
667 // if ( test )
668 if ( f && f.good() )
669 {
670 #ifdef PRINTSEARCH
671 std::cout << "Trying to open " << fullname << " succeeded.";
672 #endif
673 // f.open( fullname );
674
675 // return f;
676 return true;
677 }
678
679 #ifdef PRINTSEARCH
680 std::cout << "Trying to open " << fullname << " succeeded.";
681 #endif
682 }
683
684 return false;
685 }
686
687 static bool st_protectFiles = true;
688 tSettingItem<bool> st_protectFilesConf("PROTECT_SENSITIVE_FILES", st_protectFiles);
689
Open(std::ofstream & f,const char * filename,std::ios::openmode mode,bool sensitive) const690 bool tPath::Open ( std::ofstream& f,
691 const char* filename,
692 std::ios::openmode mode,
693 bool sensitive) const
694 {
695 if ( !tPath::IsValidPath( filename ) )
696 return false;
697
698 // tArray< tString > paths;
699 // Paths( paths );
700
701 tString fullname = GetWritePath(filename);
702
703 #ifndef WIN32
704 mode_t oldmask=0;
705 if(sensitive && st_protectFiles)
706 {
707 oldmask = umask(0600);
708 }
709 #endif
710 f.open( fullname, mode );
711 #ifndef WIN32
712 if(sensitive && st_protectFiles)
713 {
714 chmod( &fullname(0), 0600 );
715 umask(oldmask);
716 }
717 #endif
718
719 return ( f && f.good() );
720 }
721
GetReadPath(const char * filename) const722 tString tPath::GetReadPath ( const char* filename ) const
723 {
724 if ( !tPath::IsValidPath( filename ) )
725 return tString();
726
727 tArray< tString > paths;
728 Paths( paths );
729
730 for ( int prio = paths.Len() - 1; prio>=0; --prio )
731 {
732 tString fullname;
733 fullname << paths( prio ) << "/" << filename;
734 std::ifstream f;
735
736 //if (fullname != "./moviepack/sky.png")
737 #ifdef PRINTSEARCH
738 printf("Searching %s...", (const char *)fullname);
739 #endif
740 f.open( fullname );
741
742 if ( f && f.good() )
743 {
744 //if (fullname != "./moviepack/sky.png")
745 #ifdef PRINTSEARCH
746 printf("OK\n");
747 #endif
748 return fullname;
749 }
750 //if (fullname != "./moviepack/sky.png")
751 #ifdef PRINTSEARCH
752 printf("nope\n");
753 #endif
754 }
755
756 return tString();
757 }
758
GetWritePath(const char * filename) const759 tString tPath::GetWritePath ( const char* filename ) const
760 {
761 if ( !tPath::IsValidPath( filename ) )
762 return tString();
763
764 tArray< tString > paths;
765 Paths( paths );
766
767 tString fullname;
768 fullname << paths( paths.Len() -1 ) << "/" << filename;
769
770 {
771 bool s;
772
773 char *fpmr = strdup( static_cast< char const * > ( fullname ) );
774 s = mkdir_recurse(fpmr, paths( paths.Len() -1 ).Len());
775 free(fpmr);
776
777 if (!s)
778 {
779 tERR_WARN( "Could not create path to " << fullname << ". Check your user's rights." );
780 return tString();
781 }
782 }
783
784 return fullname;
785 }
786
Data()787 const tPath& tDirectories::Data()
788 {
789 return st_Data;
790 }
791
Config()792 const tPath& tDirectories::Config()
793 {
794 return st_Config;
795 }
796
Var()797 const tPath& tDirectories::Var()
798 {
799 return st_Var;
800 }
801
Screenshot()802 const tPath& tDirectories::Screenshot()
803 {
804 return st_Screenshot;
805 }
806
Resource()807 const tPathResource& tDirectories::Resource()
808 {
809 return st_Resource;
810 }
811
812 // set location of data directory
SetData(const tString & dir)813 void tDirectories::SetData( const tString& dir )
814 {
815 st_DataDir = dir;
816 }
817
818 // set location of user data directory
SetUserData(const tString & dir)819 void tDirectories::SetUserData( const tString& dir )
820 {
821 st_UserDataDir = dir;
822 }
823
824 // set location of config directory
SetConfig(const tString & dir)825 void tDirectories::SetConfig( const tString& dir )
826 {
827 st_ConfigDir = dir;
828 }
829
830 // set location of user config directory
SetUserConfig(const tString & dir)831 void tDirectories::SetUserConfig( const tString& dir )
832 {
833 st_UserConfigDir = dir;
834 }
835
836 // set location of var directory
SetVar(const tString & dir)837 void tDirectories::SetVar( const tString& dir )
838 {
839 st_VarDir = dir;
840 }
841
842 // set location of screenshot directory
SetScreenshot(const tString & dir)843 void tDirectories::SetScreenshot( const tString& dir ) {
844 st_ScreenshotDir = dir;
845 }
846
SetResource(const tString & dir)847 void tDirectories::SetResource( const tString& dir ) {
848 st_ResourceDir = dir;
849 }
850
SetAutoResource(const tString & dir)851 void tDirectories::SetAutoResource( const tString& dir ) {
852 st_AutoResourceDir = dir;
853 }
854
SetIncludedResource(const tString & dir)855 void tDirectories::SetIncludedResource( const tString& dir ) {
856 st_IncludedResourceDir = dir;
857 }
858
859 /*
860 * robust glob pattern matcher
861 * ozan s. yigit/dec 1994
862 * public domain
863 * http://www.cs.yorku.ca/~oz/glob.bun
864 *
865 * glob patterns:
866 * * matches zero or more characters
867 * ? matches any single character
868 *
869 * char matches itself except where char is '*' or '?' or '['
870 * \char matches char, including any pattern character
871 *
872 * examples:
873 * a*c ac abc abbc ...
874 * a?c acc abc aXc ...
875 *
876 * Revision 1.5 2004/08/24 12:24:23 k
877 * added case sensitive/insensitive option
878 *
879 * Revision 1.4 2004/08/17 12:24:23 k
880 * removed [a-z] checking to match Windows wildcard parsing
881 */
882 // check if a file name matches a wildcard (* and ? are valid wild cards)
FileMatchesWildcard(const char * str,const char * pattern,bool ignoreCase)883 bool tDirectories::FileMatchesWildcard(const char *str, const char *pattern,
884 bool ignoreCase /* = true */)
885 {
886 int c;
887
888 while (*pattern)
889 {
890 if (!*str && *pattern != '*')
891 return false;
892
893 switch (c = *pattern++)
894 {
895
896 case '*':
897 while (*pattern == '*')
898 pattern++;
899
900 if (!*pattern)
901 return true;
902
903 if (*pattern != '?' && *pattern != '\\')
904 {
905 if (ignoreCase)
906 {
907 while (*str && tolower(*pattern) != tolower(*str) )
908 str++;
909 }
910 else
911 {
912 while (*str && *pattern != *str )
913 str++;
914 }
915 }
916
917 while (*str)
918 {
919 if (FileMatchesWildcard(str, pattern, ignoreCase))
920 return true;
921 str++;
922 }
923 return false;
924
925 case '?':
926 if (*str)
927 break;
928 return false;
929
930 case '\\':
931 if (*pattern)
932 c = *pattern++;
933
934 default:
935 if (ignoreCase)
936 {
937 if (tolower(c) != tolower(*str))
938 return false;
939 }
940 else
941 {
942 if (c != *str)
943 return false;
944 }
945 break;
946
947 }
948 str++;
949 }
950
951 return !*str;
952 }
953
954 // get a list of files for a directory
955 // flag: 0=files+dirs, 1=files, 2=dirs
GetFiles(const tString & dir,const tString & fileSpec,tArray<tString> & files,int flag)956 void tDirectories::GetFiles( const tString& dir, const tString& fileSpec,
957 tArray< tString >& files, int flag /* = eGetFilesAllFiles */ )
958 {
959 tArray<tString> specList;
960 long pos = 0;
961 struct stat statbuf;
962 tString temp;
963 bool bDir = false;
964
965 files.SetLen( 0 );
966
967 // Check for multiple file specs
968 GetSpecList( fileSpec, specList );
969
970 DIR *dirp;
971 struct dirent *entry;
972
973 dirp = opendir( dir );
974
975 if ( dirp != NULL )
976 {
977 while ( ( entry = readdir( dirp ) ) != NULL )
978 {
979 // Ignore "." and ".." entries
980 if ( entry->d_name[0] != '.' )
981 {
982 // Build path. Make sure dir ends with a '/' or '\'.
983 temp = dir;
984 if ( ( dir.Len() > 1 ) && ( dir[ dir.Len() - 2 ] != '/' && dir[ dir.Len() - 2 ] != '\\' ) )
985 {
986 temp += "/";
987 }
988 temp += entry->d_name;
989
990 // Is the entry a directory?
991 bDir = false;
992 if ( stat( temp, &statbuf ) == 0 )
993 {
994 if( statbuf.st_mode & S_IFDIR )
995 {
996 bDir = true;
997 // Don't add directories when flag = 1
998 if ( flag == eGetFilesFilesOnly )
999 continue;
1000 }
1001 else
1002 {
1003 // Don't add files when flag = 2
1004 if ( flag == eGetFilesDirsOnly )
1005 continue;
1006 }
1007 }
1008
1009 // If the entry matches a file spec add it to the list
1010 for ( int i = 0; i < specList.Len(); i++ )
1011 {
1012 if ( FileMatchesWildcard( entry->d_name, specList( i ), true ) )
1013 {
1014 files[ pos ] = entry->d_name;
1015 if ( bDir )
1016 files[ pos ] += "/";
1017 pos++;
1018 break;
1019 }
1020 }
1021 }
1022 }
1023
1024 closedir( dirp );
1025 }
1026
1027 // Sort the list of files
1028 SortFiles( files );
1029 }
1030
1031 // Convert a file name to a menu name (strip extension, replace '_' with ' ')
FileNameToMenuName(const char * fileName,tString & menuName)1032 tString& tDirectories::FileNameToMenuName(const char* fileName, tString& menuName)
1033 {
1034 char szBuf[256];
1035 int i = 0;
1036
1037 // copy string to buffer for manipulation
1038 memset( szBuf, 0, sizeof( szBuf ) );
1039 strncpy( szBuf, fileName, sizeof( szBuf ) - 1 );
1040
1041 // skip translation strings
1042 if ( szBuf[0] != '$' )
1043 {
1044 // strip extension
1045 for ( i = strlen(szBuf); i >= 0; i-- )
1046 {
1047 if ( szBuf[i] == '.' )
1048 {
1049 szBuf[i] = '\0';
1050 }
1051 }
1052
1053 // replace underscores with spaces
1054 for ( i = 0; (unsigned)i < strlen(szBuf); i++ )
1055 {
1056 if ( szBuf[i] == '_' )
1057 {
1058 szBuf[i] = ' ';
1059 }
1060 }
1061 }
1062
1063 menuName = szBuf;
1064
1065 return menuName;
1066 }
1067
1068 // split the file spec into a list
GetSpecList(const tString & fileSpec,tArray<tString> & specList)1069 void tDirectories::GetSpecList( const tString& fileSpec, tArray< tString >& specList )
1070 {
1071 char szBuf[256];
1072 char szSep[] = ";";
1073 char *token = NULL;
1074 long pos = 0;
1075
1076 specList.SetLen( 0 );
1077
1078 // Check for multiple file specs
1079 memset( szBuf, 0, sizeof( szBuf ) );
1080 strncpy( szBuf, (const char *) fileSpec, sizeof( szBuf ) - 1 );
1081 token = strtok( szBuf, szSep );
1082 while( token != NULL )
1083 {
1084 specList[ pos++ ] = token;
1085 token = strtok( NULL, szSep );
1086 }
1087 }
1088
1089 // Helper function to see if tString s1 is less than s2 ignoring case
tStringLessThan(const tString & s1,const tString & s2)1090 static bool tStringLessThan(const tString &s1, const tString &s2)
1091 {
1092 for (int i = 0; i < s1.Len() - 1; i++)
1093 {
1094 if ( tolower( s2( i ) ) >= tolower( s1( i ) ) )
1095 {
1096 return false;
1097 }
1098 }
1099 return true;
1100 }
1101
1102 // Sort the list of files
SortFiles(tArray<tString> & files)1103 void tDirectories::SortFiles( tArray< tString >& files )
1104 {
1105 tString temp;
1106 long pos = 0;
1107
1108 // bubble sort for now
1109 for ( pos = 0; pos < files.Len() - 1; pos ++ )
1110 {
1111 for ( long pos2 = pos + 1; pos2 < files.Len(); pos2++ )
1112 {
1113 //if ( strcmp( (const char *) files( pos2 ), (const char *) files( pos ) ) < 0 )
1114 if ( tStringLessThan( files( pos2 ), files( pos ) ) )
1115 {
1116 temp = files( pos );
1117 files( pos ) = files( pos2 );
1118 files( pos2 ) = temp;
1119 }
1120 }
1121 }
1122 }
1123
1124 /*
1125 static void quitWithMessage( const char* message )
1126 {
1127 #ifdef WIN32
1128 #ifndef DEDICATED
1129 #define USEBOX
1130 #endif
1131 #endif
1132
1133 #ifdef USEBOX
1134 int result = MessageBox (NULL, message , "Message", MB_OK);
1135 #else
1136 std::cout << message;
1137 #endif
1138
1139 tLocale::Clear();
1140 }
1141 */
1142
1143 //#define QUIT(x) { std::ostringstream s; s << x; quitWithMessage(s.str().c_str()); name_.Clear(); } exit(0)
1144 #define QUIT(x) { std::ostringstream s; s << x; quitWithMessage(s.str().c_str()); name_.Clear(); } return false
1145
1146 /*
1147 // reads a command line option
1148 bool ReadOption( int& i, int argc, char **argv, const char* argument, tString& target )
1149 {
1150 if ( !strcmp(argv[i],argument ) )
1151 {
1152 if ( i < argc - 1 )
1153 {
1154 i++;
1155 target = argv[i];
1156
1157 return true;
1158 }
1159 else
1160 {
1161 tString name_;
1162 QUIT( "\n\n" << argument << " needs another argument.\n\n" );
1163 }
1164 }
1165
1166 return false;
1167 }
1168 */
1169
ReplacePath(tString & path,char const * replacement)1170 void ReplacePath( tString & path, char const * replacement )
1171 {
1172 // don't do a thing if the path is already set
1173 if ( path.Len() < 3 )
1174 {
1175 path = replacement;
1176 }
1177 }
1178
1179 // tests whether <file> can be found in path <path>
TestPath(char const * path,char const * file)1180 bool TestPath( char const * path, char const * file )
1181 {
1182 std::string testData( path );
1183 testData += "/";
1184 testData += file;
1185 std::ifstream f(testData.c_str());
1186
1187
1188 #ifdef DEBUG_PATH
1189 con << "Testing existence of file " << testData << ( f.good() ? " : good.\n" : " : bad!\n" );
1190 #endif
1191
1192 return ( f.good() );
1193 }
1194
1195 // tests whether the given path is a valid configuration path
TestConfigurationPath(char const * path)1196 bool TestConfigurationPath( char const * path )
1197 {
1198 if ( TestPath( path, "settings.cfg" ) )
1199 {
1200 #ifdef DEBUG_PATH
1201 con << "Configuration path " << path << " is good.\n";
1202 #endif
1203 // replace data paths
1204 ReplacePath( st_ConfigDir, path );
1205
1206 return true;
1207 }
1208
1209 #ifdef DEBUG_PATH
1210 con << "Configuration path " << path << " is bad!\n";
1211 #endif
1212
1213 return false;
1214 }
1215
1216 // tests whether the given path is a valid data path
TestDataPath(char const * path)1217 bool TestDataPath( char const * path )
1218 {
1219 if ( TestPath( path, "language/english_base.txt") )
1220 {
1221 #ifdef DEBUG_PATH
1222 con << "Data path " << path << " is good.\n";
1223 #endif
1224 // replace data paths
1225 ReplacePath( st_DataDir, path );
1226
1227 return true;
1228 }
1229
1230 #ifdef DEBUG_PATH
1231 con << "Data path " << path << " is bad!\n";
1232 #endif
1233
1234 return false;
1235 }
1236
1237 // generates parent directories of the passed path
GetParent(char const * child,int levels)1238 static tString GetParent( char const * child, int levels )
1239 {
1240 tString ret( child );
1241
1242 // strip last two path segments
1243 int toStrip = levels;
1244 int stripCurrent = ret.Len()-1;
1245 while (stripCurrent >= 0 && toStrip > 0)
1246 {
1247 char & c = ret[ stripCurrent ];
1248 // count separators
1249 if (c == '/' || c == '\\' || c == ':')
1250 --toStrip;
1251 c=0;
1252 --stripCurrent;
1253 }
1254
1255 #ifdef DEBUG_PATH
1256 std::cout << "Parent: " << ret << "\n";
1257 #endif
1258
1259 return tString(static_cast<char const *>(ret));
1260 }
1261
1262 //! Class holding our best guess of the path to the binary executable
1263 class tPathToExecutable
1264 {
1265 public:
1266 //! sets a default path, for when we have no better idea
Set(char const * defaultPath)1267 void Set( char const * defaultPath )
1268 {
1269 #ifndef WIN32
1270 #ifdef ENABLE_BINRELOC
1271 // get path of executable
1272 char const * bestGuess = SELFPATH;
1273 #else // binreloc
1274 char const * bestGuess = BINDIR "/" PROGNAME;
1275 #endif// binreloc
1276 #else // win32
1277 char const * bestGuess = "./" PROGNAME;
1278 #endif// win32
1279
1280 #ifndef ENABLE_BINRELOC
1281 // if the passed default path is a real path, let it override the best guess
1282 if ( strstr( defaultPath, "/" ) || strstr( defaultPath, "\\" ) )
1283 bestGuess = defaultPath;
1284 // bestGuess = "./armagetronad-dedicated";
1285 #endif
1286
1287 path_ = bestGuess;
1288
1289 #ifdef DEBUG_PATH
1290 con << "path to executable: " << path_ << "\n";
1291 #endif
1292 }
1293
1294 // returns the best bet
Get() const1295 char const * Get() const
1296 {
1297 tASSERT( path_.Len() > 2 );
1298 return path_;
1299 }
1300 private:
1301 tString path_;
1302 };
1303
1304 static tPathToExecutable st_pathToExecutable;
1305
GenerateParentOfExecutable(int levels=2)1306 static tString GenerateParentOfExecutable( int levels = 2 )
1307 {
1308 return GetParent( st_pathToExecutable.Get(), levels );
1309 }
1310
1311 // exception to throw if the game is running from the build directory
1312 struct tRunningInBuildDirectory
1313 {
1314 };
1315
1316 // generates real prefix from executable position and compiled in prefix and
1317 // binary path
GeneratePrefix()1318 static tString GeneratePrefix()
1319 {
1320 // fetch prefix as it was compiled in
1321 tString const & prefixCompiled = st_prefixCompiled;
1322 // the binary path as it was compiled in
1323 tString const & bindirCompiled = st_bindirCompiled;
1324 // and the current binary path
1325 tString bindirNow(GenerateParentOfExecutable(1));
1326
1327 // the length of the bindir suffix, the part that is added below prefix
1328 int bindirSuffixLength=bindirCompiled.Len() - prefixCompiled.Len();
1329
1330 // the end of the prefix part in binDirNow according to that
1331 int bindirNowPrefixEnd=bindirNow.Len() - 1 - bindirSuffixLength;
1332 if ( bindirNowPrefixEnd < 0 )
1333 bindirNowPrefixEnd = 0;
1334
1335 // check that the binary path now ends the same way
1336 tString suffixNow = bindirNow.SubStr( bindirNowPrefixEnd + 1 );
1337 tString suffixCompiled = bindirCompiled.SubStr( prefixCompiled.Len() );
1338
1339 #ifdef DEBUG_PATH
1340 con << "suffices: " << suffixNow << ", " << suffixCompiled << "\n";
1341 #endif
1342
1343 if ( suffixNow != suffixCompiled )
1344 {
1345 // may we be running inside the build directory?
1346 int bindirEndStart = bindirNow.Len() - 5;
1347 if ( bindirEndStart < 0 )
1348 bindirEndStart = 0;
1349
1350 tString bindirEnd = bindirNow.SubStr( bindirEndStart );
1351
1352 #ifdef DEBUG_PATH
1353 con << "bindirEnd: " << bindirEnd << "\n";
1354 #endif
1355
1356 if ( TestPath( bindirNow, "Makefile" ) )
1357 throw tRunningInBuildDirectory();
1358
1359 tERR_ERROR("Relocation error. The binary was supposed to be installed into " << bindirCompiled << " and found itself in " << bindirNow << " and could not find out what this means for the prefix " << prefixCompiled << "." );
1360 }
1361
1362 // generate prefix
1363 tString prefixNow = bindirNow.SubStr( 0, bindirNowPrefixEnd );
1364
1365 #ifdef DEBUG_PATH
1366 con << "prefix: " << prefixNow << "\n";
1367 #endif
1368
1369 return prefixNow;
1370 }
1371
1372 // returns the complete prefix the game was installed in (defaults to /usr/local)
GetPrefix()1373 static char const * GetPrefix()
1374 {
1375 static tString ret( GeneratePrefix() );
1376 return ret;
1377 }
1378
1379 /*
1380 // appends the given suffix to the prefix and returns the result
1381 static tString AddPrefix( const char * suffix )
1382 {
1383 tString ret(GetPrefix());
1384 ret += suffix;
1385
1386 return ret;
1387 }
1388 */
1389
st_RelocatePath(tString const & original)1390 static tString st_RelocatePath( tString const & original )
1391 {
1392 // fetch prefix as it was compiled in
1393 tString const & prefixCompiled = st_prefixCompiled;
1394 // and as it is now
1395 static tString prefixNow(GetPrefix());
1396
1397 // see if the passed string starts with it
1398 if ( original.StartsWith( prefixCompiled ) )
1399 {
1400 // replace the prefix with the real prefix and return the result
1401 return prefixNow + original.SubStr( prefixCompiled.Len()-1 );
1402 }
1403 else
1404 {
1405 // don't relocate and hope it works
1406 return original;
1407 }
1408 }
1409
1410 // tries to find the path to the data files, given the location of the executable
FindDataPath()1411 static void FindDataPath()
1412 {
1413 #ifndef MACOSX
1414 #ifdef WIN32
1415 // look for data in the same directory as the executable
1416 if ( TestDataPath(GetParent(st_pathToExecutable.Get(), 1) ) ) return;
1417 #else
1418 // try to use path substitution
1419 if ( TestDataPath( st_RelocatePath( tString(AA_DATADIR ) ) ) ) return;
1420 // if ( TestDataPath( AddPrefix( DATASUFFIX ) ) ) return;
1421 #endif
1422
1423 #ifdef DEBUG_PATH
1424 con << "Data sarch failed, trying debug fallback.\n";
1425 #endif
1426
1427 #ifdef DEBUG_PATH
1428 tERR_MESSAGE("Could not determine path to data files. Using defaults or command line arguments.\n");
1429 #endif
1430 #endif // !MACOSX
1431 }
1432
1433 // tries to find the path to the configuration files, given the location of the executable
FindConfigurationPath()1434 static void FindConfigurationPath()
1435 {
1436 #ifndef MACOSX
1437 #ifndef WIN32
1438 if ( TestConfigurationPath( st_RelocatePath( tString( AA_SYSCONFDIR ) ) ) ) return;
1439 #endif
1440
1441 // look for configuration where the data is
1442 if ( TestConfigurationPath(st_DataDir + "/config") ) return;
1443
1444 tERR_WARN("Could not determine path to configuration files. Using defaults or command line arguments.\n");
1445 #endif // !MACOSX
1446 }
1447
1448 // tries to read a direcory type argument from the command line parser; result is written
1449 // into target, argument is the required switch ("--userdatadir")
ReadDir(tCommandLineParser & parser,tString & target,const char * argument)1450 static bool ReadDir( tCommandLineParser & parser, tString & target, const char* argument )
1451 {
1452 if ( parser.GetOption( target, argument ) )
1453 {
1454 target = expand_home_c( target );
1455
1456 return true;
1457 }
1458
1459 return false;
1460 }
1461
1462 class tDirectoriesCommandLineAnalyzer: public tCommandLineAnalyzer
1463 {
1464 private:
DoInitialize(tCommandLineParser & parser)1465 virtual void DoInitialize( tCommandLineParser & parser )
1466 {
1467 // Puts the data files in the executable's bundle
1468 #ifndef MACOSX
1469 try
1470 {
1471 st_pathToExecutable.Set( parser.Executable() );
1472 FindDataPath();
1473 FindConfigurationPath();
1474 }
1475 catch( tRunningInBuildDirectory )
1476 {
1477 // last fallback for debugging (activated only if there is data in the current directory)
1478 if ( TestPath( ".", "language/languages.txt") && TestDataPath(s_topSourceDir) && TestConfigurationPath(st_DataDir + "/config") )
1479 {
1480 // we must be running the game in debug mode; set user data dir to current directory.
1481 st_UserDataDir = ".";
1482
1483 // the included resources are scrambled and put into the current directory as well.
1484 st_IncludedResourceDir = "./resource/included";
1485 return;
1486 }
1487 }
1488 #endif
1489
1490
1491 }
1492
DoAnalyze(tCommandLineParser & parser)1493 virtual bool DoAnalyze( tCommandLineParser & parser )
1494 {
1495 if( ReadDir( parser, st_DataDir, "--datadir" ) ) return true;
1496 if( ReadDir( parser, st_UserDataDir, "--userdatadir" ) ) return true;
1497 if( ReadDir( parser, st_ConfigDir, "--configdir" ) ) return true;
1498 if( ReadDir( parser, st_UserConfigDir, "--userconfigdir" ) ) return true;
1499 if( ReadDir( parser, st_VarDir, "--vardir" ) ) return true;
1500 if( ReadDir( parser, st_ResourceDir, "--resourcedir" ) ) return true;
1501 if( ReadDir( parser, st_AutoResourceDir, "--autoresourcedir" ) ) return true;
1502
1503 if ( parser.GetSwitch( "--path-no-absolutecheck" ) )
1504 {
1505 st_checkPathAbsolute = false;
1506 return true;
1507 }
1508
1509 if ( parser.GetSwitch( "--path-no-relativecheck" ) )
1510 {
1511 st_checkPathRelative = false;
1512 return true;
1513 }
1514
1515 if ( parser.GetSwitch( "--path-no-hiddencheck" ) )
1516 {
1517 st_checkPathHidden = false;
1518 return true;
1519 }
1520
1521 if ( parser.GetSwitch( "--prefix" ) )
1522 {
1523 std::cout << GetPrefix() << '\n';
1524 throw 1;
1525 return true;
1526 }
1527
1528 return false;
1529 }
1530
DoHelp(std::ostream & s)1531 virtual void DoHelp( std::ostream & s )
1532 { //
1533 s << "--datadir <Directory> : read game data (textures, sounds and texts)\n"
1534 << " from this directory\n";
1535 s << "--userdatadir <Directory> : read customized game data from this directory\n";
1536 s << "--configdir <Directory> : read game configuration (.cfg-files)\n"
1537 << " from this directory\n";
1538 s << "--userconfigdir <Directory> : read user game configuration from this directory\n";
1539 s << "--vardir <Directory> : save game logs and highscores in this directory\n\n";
1540 s << "--resourcedir <Directory> : look for resources in this directory\n\n";
1541 s << "--autoresourcedir <Directory>: download missing resources into this directory\n\n";
1542 s << "--path-no-absolutecheck : disables security check against absolute paths\n";
1543 s << "--path-no-hiddencheck : disables security check against hidden paths\n";
1544 s << "--path-no-relativecheck : disables security check against relative paths.\n"
1545 << " Not recommended, this check is really important.\n\n";
1546 s << "--prefix : prints the prefix the game was installed to\n";
1547 }
1548 };
1549
1550 static tDirectoriesCommandLineAnalyzer analyzer;
1551
GetPaths(void) const1552 tString tPath::GetPaths(void) const {
1553 tString ret;
1554 tArray<tString> paths;
1555 Paths(paths);
1556 for (int i = 0; i < paths.Len(); ++i) {
1557 if(i > 0 && paths[i - 1] == paths[i]) continue;
1558 ret << " - " << paths[i] << "\n";
1559 }
1560 return ret;
1561 }
1562
st_PrintPathInfo(tOutput & buf)1563 void st_PrintPathInfo(tOutput &buf) {
1564 tString const hcol("0xff8888");
1565 buf << hcol << "$path_info_user_cfg" << "0xRESETT\n " << tDirectories::Var().GetReadPath("user.cfg") << "\n"
1566 << hcol << "$path_info_config" << "0xRESETT\n" << tDirectories::Config().GetPaths()
1567 << hcol << "$path_info_resource" << "0xRESETT\n" << tDirectories::Resource().GetPaths()
1568 << hcol << "$path_info_data" << "0xRESETT\n" << tDirectories::Data().GetPaths()
1569 << hcol << "$path_info_screenshot" << "0xRESETT\n" << tDirectories::Screenshot().GetPaths()
1570 << hcol << "$path_info_var" << "0xRESETT\n" << tDirectories::Var().GetPaths();
1571 }
1572