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