2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file fileio.cpp Standard In/Out file operations */
11 #include "fileio_func.h"
12 #include "spriteloader/spriteloader.hpp"
15 #include "string_func.h"
19 # define access _taccess
20 #elif defined(__HAIKU__)
22 #include <storage/FindDirectory.h>
31 #include "safeguards.h"
33 /** Whether the working directory should be scanned. */
34 static bool _do_scan_working_directory
= true;
36 extern std::string _config_file
;
37 extern std::string _highscore_file
;
39 static const char * const _subdirs
[] = {
42 "save" PATHSEP
"autosave" PATHSEP
,
44 "scenario" PATHSEP
"heightmap" PATHSEP
,
51 "ai" PATHSEP
"library" PATHSEP
,
53 "game" PATHSEP
"library" PATHSEP
,
56 static_assert(lengthof(_subdirs
) == NUM_SUBDIRS
);
59 * The search paths OpenTTD could search through.
60 * At least one of the slots has to be filled with a path.
61 * An empty string tells that there is no such path for the
62 * current operating system.
64 std::array
<std::string
, NUM_SEARCHPATHS
> _searchpaths
;
65 std::vector
<Searchpath
> _valid_searchpaths
;
66 std::array
<TarList
, NUM_SUBDIRS
> _tar_list
;
67 TarFileList _tar_filelist
[NUM_SUBDIRS
];
69 typedef std::map
<std::string
, std::string
> TarLinkList
;
70 static TarLinkList _tar_linklist
[NUM_SUBDIRS
]; ///< List of directory links
73 * Checks whether the given search path is a valid search path
74 * @param sp the search path to check
75 * @return true if the search path is valid
77 static bool IsValidSearchPath(Searchpath sp
)
79 return sp
< _searchpaths
.size() && !_searchpaths
[sp
].empty();
82 static void FillValidSearchPaths(bool only_local_path
)
84 _valid_searchpaths
.clear();
85 for (Searchpath sp
= SP_FIRST_DIR
; sp
< NUM_SEARCHPATHS
; sp
++) {
86 if (only_local_path
) {
88 case SP_WORKING_DIR
: // Can be influence by "-c" option.
89 case SP_BINARY_DIR
: // Most likely contains all the language files.
90 case SP_AUTODOWNLOAD_DIR
: // Otherwise we cannot download in-game content.
98 if (IsValidSearchPath(sp
)) _valid_searchpaths
.emplace_back(sp
);
103 * Check whether the given file exists
104 * @param filename the file to try for existence.
105 * @param subdir the subdirectory to look in
106 * @return true if and only if the file can be opened
108 bool FioCheckFileExists(const std::string
&filename
, Subdirectory subdir
)
110 FILE *f
= FioFOpenFile(filename
, "rb", subdir
);
111 if (f
== nullptr) return false;
118 * Test whether the given filename exists.
119 * @param filename the file to test.
120 * @return true if and only if the file exists.
122 bool FileExists(const std::string
&filename
)
124 return access(OTTD2FS(filename
).c_str(), 0) == 0;
128 * Close a file in a safe way.
130 void FioFCloseFile(FILE *f
)
136 * Find a path to the filename in one of the search directories.
137 * @param subdir Subdirectory to try.
138 * @param filename Filename to look for.
139 * @return String containing the path if the path was found, else an empty string.
141 std::string
FioFindFullPath(Subdirectory subdir
, const char *filename
)
143 assert(subdir
< NUM_SUBDIRS
);
145 for (Searchpath sp
: _valid_searchpaths
) {
146 std::string buf
= FioGetDirectory(sp
, subdir
);
148 if (FileExists(buf
)) return buf
;
150 /* Be, as opening files, aware that sometimes the filename
151 * might be in uppercase when it is in lowercase on the
152 * disk. Of course Windows doesn't care about casing. */
153 if (strtolower(buf
, _searchpaths
[sp
].size() - 1) && FileExists(buf
)) return buf
;
160 std::string
FioGetDirectory(Searchpath sp
, Subdirectory subdir
)
162 assert(subdir
< NUM_SUBDIRS
);
163 assert(sp
< NUM_SEARCHPATHS
);
165 return _searchpaths
[sp
] + _subdirs
[subdir
];
168 std::string
FioFindDirectory(Subdirectory subdir
)
170 /* Find and return the first valid directory */
171 for (Searchpath sp
: _valid_searchpaths
) {
172 std::string ret
= FioGetDirectory(sp
, subdir
);
173 if (FileExists(ret
)) return ret
;
176 /* Could not find the directory, fall back to a base path */
177 return _personal_dir
;
180 static FILE *FioFOpenFileSp(const std::string
&filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
183 /* fopen is implemented as a define with ellipses for
184 * Unicode support (prepend an L). As we are not sending
185 * a string, but a variable, it 'renames' the variable,
186 * so make that variable to makes it compile happily */
188 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
193 if (subdir
== NO_DIRECTORY
) {
196 buf
= _searchpaths
[sp
] + _subdirs
[subdir
] + filename
;
200 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
).c_str()) == INVALID_FILE_ATTRIBUTES
) return nullptr;
203 f
= fopen(buf
.c_str(), mode
);
205 if (f
== nullptr && strtolower(buf
, subdir
== NO_DIRECTORY
? 0 : _searchpaths
[sp
].size() - 1) ) {
206 f
= fopen(buf
.c_str(), mode
);
209 if (f
!= nullptr && filesize
!= nullptr) {
210 /* Find the size of the file */
211 fseek(f
, 0, SEEK_END
);
212 *filesize
= ftell(f
);
213 fseek(f
, 0, SEEK_SET
);
219 * Opens a file from inside a tar archive.
220 * @param entry The entry to open.
221 * @param[out] filesize If not \c nullptr, size of the opened file.
222 * @return File handle of the opened file, or \c nullptr if the file is not available.
223 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
225 FILE *FioFOpenFileTar(const TarFileListEntry
&entry
, size_t *filesize
)
227 FILE *f
= fopen(entry
.tar_filename
.c_str(), "rb");
228 if (f
== nullptr) return f
;
230 if (fseek(f
, entry
.position
, SEEK_SET
) < 0) {
235 if (filesize
!= nullptr) *filesize
= entry
.size
;
240 * Opens a OpenTTD file somewhere in a personal or global directory.
241 * @param filename Name of the file to open.
242 * @param subdir Subdirectory to open.
243 * @return File handle of the opened file, or \c nullptr if the file is not available.
245 FILE *FioFOpenFile(const std::string
&filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
249 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
251 for (Searchpath sp
: _valid_searchpaths
) {
252 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
253 if (f
!= nullptr || subdir
== NO_DIRECTORY
) break;
256 /* We can only use .tar in case of data-dir, and read-mode */
257 if (f
== nullptr && mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
258 /* Filenames in tars are always forced to be lowercase */
259 std::string resolved_name
= filename
;
260 strtolower(resolved_name
);
263 std::istringstream
ss(resolved_name
);
264 std::vector
<std::string
> tokens
;
266 while (std::getline(ss
, token
, PATHSEPCHAR
)) {
268 if (tokens
.size() < 2) return nullptr;
270 } else if (token
== ".") {
271 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
272 * This confuses our file resolver. So, act like this folder doesn't exist. */
274 tokens
.push_back(token
);
278 resolved_name
.clear();
280 for (const std::string
&token
: tokens
) {
282 resolved_name
+= PATHSEP
;
284 resolved_name
+= token
;
288 /* Resolve ONE directory link */
289 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
290 const std::string
&src
= link
->first
;
291 size_t len
= src
.length();
292 if (resolved_name
.length() >= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& src
.compare(0, len
, resolved_name
, 0, len
) == 0) {
294 resolved_name
.replace(0, len
, link
->second
);
295 break; // Only resolve one level
299 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
300 if (it
!= _tar_filelist
[subdir
].end()) {
301 f
= FioFOpenFileTar(it
->second
, filesize
);
305 /* Sometimes a full path is given. To support
306 * the 'subdirectory' must be 'removed'. */
307 if (f
== nullptr && subdir
!= NO_DIRECTORY
) {
310 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
311 if (f
!= nullptr) break;
314 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
318 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
327 * Create a directory with the given name
328 * If the parent directory does not exist, it will try to create that as well.
329 * @param name the new name of the directory
331 void FioCreateDirectory(const std::string
&name
)
333 auto p
= name
.find_last_of(PATHSEPCHAR
);
334 if (p
!= std::string::npos
) {
335 std::string dirname
= name
.substr(0, p
);
336 DIR *dir
= ttd_opendir(dirname
.c_str());
337 if (dir
== nullptr) {
338 FioCreateDirectory(dirname
); // Try creating the parent directory, if we couldn't open it
344 /* Ignore directory creation errors; they'll surface later on, and most
345 * of the time they are 'directory already exists' errors anyhow. */
347 CreateDirectory(OTTD2FS(name
).c_str(), nullptr);
348 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
349 mkdir(OTTD2FS(name
).c_str());
351 mkdir(OTTD2FS(name
).c_str(), 0755);
356 * Appends, if necessary, the path separator character to the end of the string.
357 * It does not add the path separator to zero-sized strings.
358 * @param buf string to append the separator to
359 * @return true iff the operation succeeded
361 void AppendPathSeparator(std::string
&buf
)
363 if (buf
.empty()) return;
365 if (buf
.back() != PATHSEPCHAR
) buf
.push_back(PATHSEPCHAR
);
368 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
370 std::string src
= srcParam
;
371 std::string dest
= destParam
;
372 /* Tar internals assume lowercase */
373 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
374 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
376 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
377 if (dest_file
!= _tar_filelist
[subdir
].end()) {
378 /* Link to file. Process the link like the destination file. */
379 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
381 /* Destination file not found. Assume 'link to directory'
382 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
383 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
384 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
385 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
390 * Simplify filenames from tars.
391 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
392 * @param name Filename to process.
394 static void SimplifyFileName(char *name
)
396 /* Force lowercase */
399 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
400 #if (PATHSEPCHAR != '/')
401 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
406 * Perform the scanning of a particular subdirectory.
407 * @param sd The subdirectory to scan.
408 * @return The number of found tar files.
410 uint
TarScanner::DoScan(Subdirectory sd
)
412 _tar_filelist
[sd
].clear();
413 _tar_list
[sd
].clear();
414 uint num
= this->Scan(".tar", sd
, false);
415 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
419 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
421 Debug(misc
, 1, "Scanning for tars");
424 if (mode
& TarScanner::BASESET
) {
425 num
+= fs
.DoScan(BASESET_DIR
);
427 if (mode
& TarScanner::NEWGRF
) {
428 num
+= fs
.DoScan(NEWGRF_DIR
);
430 if (mode
& TarScanner::AI
) {
431 num
+= fs
.DoScan(AI_DIR
);
432 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
434 if (mode
& TarScanner::GAME
) {
435 num
+= fs
.DoScan(GAME_DIR
);
436 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
438 if (mode
& TarScanner::SCENARIO
) {
439 num
+= fs
.DoScan(SCENARIO_DIR
);
440 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
442 Debug(misc
, 1, "Scan complete, found {} files", num
);
447 * Add a single file to the scanned files of a tar, circumventing the scanning code.
448 * @param sd The sub directory the file is in.
449 * @param filename The name of the file to add.
450 * @return True if the additions went correctly.
452 bool TarScanner::AddFile(Subdirectory sd
, const std::string
&filename
)
455 return this->AddFile(filename
, 0);
458 bool TarScanner::AddFile(const std::string
&filename
, size_t basepath_length
, const std::string
&tar_filename
)
460 /* No tar within tar. */
461 assert(tar_filename
.empty());
463 /* The TAR-header, repeated for every file */
465 char name
[100]; ///< Name of the file
469 char size
[12]; ///< Size of the file, in ASCII
480 char prefix
[155]; ///< Path of the file
485 /* Check if we already seen this file */
486 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
487 if (it
!= _tar_list
[this->subdir
].end()) return false;
489 FILE *f
= fopen(filename
.c_str(), "rb");
490 /* Although the file has been found there can be
491 * a number of reasons we cannot open the file.
492 * Most common case is when we simply have not
493 * been given read access. */
494 if (f
== nullptr) return false;
496 _tar_list
[this->subdir
][filename
] = std::string
{};
498 TarLinkList links
; ///< Temporary list to collect links
501 char buf
[sizeof(th
.name
) + 1], *end
;
502 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
503 char link
[sizeof(th
.linkname
) + 1];
504 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
505 size_t num
= 0, pos
= 0;
507 /* Make a char of 512 empty bytes */
509 memset(&empty
[0], 0, sizeof(empty
));
511 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
512 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
513 if (num_bytes_read
!= 512) break;
514 pos
+= num_bytes_read
;
516 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
517 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
518 /* If we have only zeros in the block, it can be an end-of-file indicator */
519 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
521 Debug(misc
, 0, "The file '{}' isn't a valid tar-file", filename
);
528 /* The prefix contains the directory-name */
529 if (th
.prefix
[0] != '\0') {
530 strecpy(name
, th
.prefix
, lastof(name
));
531 strecat(name
, PATHSEP
, lastof(name
));
534 /* Copy the name of the file in a safe way at the end of 'name' */
535 strecat(name
, th
.name
, lastof(name
));
537 /* Calculate the size of the file.. for some strange reason this is stored as a string */
538 strecpy(buf
, th
.size
, lastof(buf
));
539 size_t skip
= strtoul(buf
, &end
, 8);
541 switch (th
.typeflag
) {
543 case '0': { // regular file
544 if (strlen(name
) == 0) break;
546 /* Store this entry in the list */
547 TarFileListEntry entry
;
548 entry
.tar_filename
= filename
;
550 entry
.position
= pos
;
552 /* Convert to lowercase and our PATHSEPCHAR */
553 SimplifyFileName(name
);
555 Debug(misc
, 6, "Found file in tar: {} ({} bytes, {} offset)", name
, skip
, pos
);
556 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
561 case '1': // hard links
562 case '2': { // symbolic links
563 /* Copy the destination of the link in a safe way at the end of 'linkname' */
564 strecpy(link
, th
.linkname
, lastof(link
));
566 if (strlen(name
) == 0 || strlen(link
) == 0) break;
568 /* Convert to lowercase and our PATHSEPCHAR */
569 SimplifyFileName(name
);
570 SimplifyFileName(link
);
572 /* Only allow relative links */
573 if (link
[0] == PATHSEPCHAR
) {
574 Debug(misc
, 1, "Ignoring absolute link in tar: {} -> {}", name
, link
);
578 /* Process relative path.
579 * Note: The destination of links must not contain any directory-links. */
580 strecpy(dest
, name
, lastof(dest
));
581 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
582 if (destpos
== nullptr) destpos
= dest
;
586 while (*pos
!= '\0') {
587 char *next
= strchr(pos
, PATHSEPCHAR
);
588 if (next
== nullptr) {
589 next
= pos
+ strlen(pos
);
591 /* Terminate the substring up to the path separator character. */
595 if (strcmp(pos
, ".") == 0) {
596 /* Skip '.' (current dir) */
597 } else if (strcmp(pos
, "..") == 0) {
599 if (dest
[0] == '\0') {
600 Debug(misc
, 1, "Ignoring link pointing outside of data directory: {} -> {}", name
, link
);
604 /* Truncate 'dest' after last PATHSEPCHAR.
605 * This assumes that the truncated part is a real directory and not a link. */
606 destpos
= strrchr(dest
, PATHSEPCHAR
);
607 if (destpos
== nullptr) destpos
= dest
;
610 /* Append at end of 'dest' */
611 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
612 destpos
= strecpy(destpos
, pos
, lastof(dest
));
615 if (destpos
>= lastof(dest
)) {
616 Debug(misc
, 0, "The length of a link in tar-file '{}' is too large (malformed?)", filename
);
624 /* Store links in temporary list */
625 Debug(misc
, 6, "Found link in tar: {} -> {}", name
, dest
);
626 links
.insert(TarLinkList::value_type(name
, dest
));
631 case '5': // directory
632 /* Convert to lowercase and our PATHSEPCHAR */
633 SimplifyFileName(name
);
635 /* Store the first directory name we detect */
636 Debug(misc
, 6, "Found dir in tar: {}", name
);
637 if (_tar_list
[this->subdir
][filename
].empty()) _tar_list
[this->subdir
][filename
] = name
;
641 /* Ignore other types */
645 /* Skip to the next block.. */
646 skip
= Align(skip
, 512);
647 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
648 Debug(misc
, 0, "The file '{}' can't be read as a valid tar-file", filename
);
655 Debug(misc
, 1, "Found tar '{}' with {} new files", filename
, num
);
658 /* Resolve file links and store directory links.
659 * We restrict usage of links to two cases:
660 * 1) Links to directories:
661 * Both the source path and the destination path must NOT contain any further links.
662 * When resolving files at most one directory link is resolved.
664 * The destination path must NOT contain any links.
665 * The source path may contain one directory link.
667 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
668 const std::string
&src
= link
->first
;
669 const std::string
&dest
= link
->second
;
670 TarAddLink(src
, dest
, this->subdir
);
677 * Extract the tar with the given filename in the directory
678 * where the tar resides.
679 * @param tar_filename the name of the tar to extract.
680 * @param subdir The sub directory the tar is in.
681 * @return false on failure.
683 bool ExtractTar(const std::string
&tar_filename
, Subdirectory subdir
)
685 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
686 /* We don't know the file. */
687 if (it
== _tar_list
[subdir
].end()) return false;
689 const auto &dirname
= (*it
).second
;
691 /* The file doesn't have a sub directory! */
692 if (dirname
.empty()) {
693 Debug(misc
, 1, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename
);
697 std::string filename
= tar_filename
;
698 auto p
= filename
.find_last_of(PATHSEPCHAR
);
699 /* The file's path does not have a separator? */
700 if (p
== std::string::npos
) return false;
702 filename
.replace(p
+ 1, std::string::npos
, dirname
);
703 Debug(misc
, 8, "Extracting {} to directory {}", tar_filename
, filename
);
704 FioCreateDirectory(filename
);
706 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
707 if (tar_filename
!= it2
->second
.tar_filename
) continue;
709 filename
.replace(p
+ 1, std::string::npos
, it2
->first
);
711 Debug(misc
, 9, " extracting {}", filename
);
713 /* First open the file in the .tar. */
715 std::unique_ptr
<FILE, FileDeleter
> in(FioFOpenFileTar(it2
->second
, &to_copy
));
717 Debug(misc
, 6, "Extracting {} failed; could not open {}", filename
, tar_filename
);
721 /* Now open the 'output' file. */
722 std::unique_ptr
<FILE, FileDeleter
> out(fopen(filename
.c_str(), "wb"));
724 Debug(misc
, 6, "Extracting {} failed; could not open {}", filename
, filename
);
728 /* Now read from the tar and write it into the file. */
731 for (; to_copy
!= 0; to_copy
-= read
) {
732 read
= fread(buffer
, 1, std::min(to_copy
, lengthof(buffer
)), in
.get());
733 if (read
<= 0 || fwrite(buffer
, 1, read
, out
.get()) != read
) break;
737 Debug(misc
, 6, "Extracting {} failed; still {} bytes to copy", filename
, to_copy
);
742 Debug(misc
, 9, " extraction successful");
748 * Determine the base (personal dir and game data dir) paths
749 * @param exe the path from the current path to the executable
750 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
752 extern void DetermineBasePaths(const char *exe
);
753 #else /* defined(_WIN32) */
756 * Changes the working directory to the path of the give executable.
757 * For OSX application bundles '.app' is the required extension of the bundle,
758 * so when we crop the path to there, when can remove the name of the bundle
759 * in the same way we remove the name from the executable name.
760 * @param exe the path to the executable
762 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
765 strecpy(tmp
, exe
, lastof(tmp
));
767 bool success
= false;
769 char *app_bundle
= strchr(tmp
, '.');
770 while (app_bundle
!= nullptr && strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
772 if (app_bundle
!= nullptr) *app_bundle
= '\0';
773 #endif /* WITH_COCOA */
774 char *s
= strrchr(tmp
, PATHSEPCHAR
);
777 if (chdir(tmp
) != 0) {
778 Debug(misc
, 0, "Directory with the binary does not exist?");
787 * Whether we should scan the working directory.
788 * It should not be scanned if it's the root or
789 * the home directory as in both cases a big data
790 * directory can cause huge amounts of unrelated
791 * files scanned. Furthermore there are nearly no
792 * use cases for the home/root directory to have
793 * OpenTTD directories.
794 * @return true if it should be scanned.
796 bool DoScanWorkingDirectory()
798 /* No working directory, so nothing to do. */
799 if (_searchpaths
[SP_WORKING_DIR
].empty()) return false;
801 /* Working directory is root, so do nothing. */
802 if (_searchpaths
[SP_WORKING_DIR
] == PATHSEP
) return false;
804 /* No personal/home directory, so the working directory won't be that. */
805 if (_searchpaths
[SP_PERSONAL_DIR
].empty()) return true;
807 std::string tmp
= _searchpaths
[SP_WORKING_DIR
] + PERSONAL_DIR
;
808 AppendPathSeparator(tmp
);
810 return _searchpaths
[SP_PERSONAL_DIR
] != tmp
;
814 * Gets the home directory of the user.
815 * May return an empty string in the unlikely scenario that the home directory cannot be found.
816 * @return User's home directory
818 static std::string
GetHomeDir()
822 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
823 return std::string(path
.Path());
825 const char *home_env
= std::getenv("HOME"); // Stack var, shouldn't be freed
826 if (home_env
!= nullptr) return std::string(home_env
);
828 const struct passwd
*pw
= getpwuid(getuid());
829 if (pw
!= nullptr) return std::string(pw
->pw_dir
);
835 * Determine the base (personal dir and game data dir) paths
836 * @param exe the path to the executable
838 void DetermineBasePaths(const char *exe
)
841 const std::string homedir
= GetHomeDir();
843 const char *xdg_data_home
= std::getenv("XDG_DATA_HOME");
844 if (xdg_data_home
!= nullptr) {
847 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
848 AppendPathSeparator(tmp
);
849 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
851 tmp
+= "content_download";
852 AppendPathSeparator(tmp
);
853 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
854 } else if (!homedir
.empty()) {
856 tmp
+= PATHSEP
".local" PATHSEP
"share" PATHSEP
;
857 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
858 AppendPathSeparator(tmp
);
859 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
861 tmp
+= "content_download";
862 AppendPathSeparator(tmp
);
863 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
865 _searchpaths
[SP_PERSONAL_DIR_XDG
].clear();
866 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
].clear();
870 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
871 _searchpaths
[SP_PERSONAL_DIR
].clear();
873 if (!homedir
.empty()) {
877 AppendPathSeparator(tmp
);
878 _searchpaths
[SP_PERSONAL_DIR
] = tmp
;
880 tmp
+= "content_download";
881 AppendPathSeparator(tmp
);
882 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
] = tmp
;
884 _searchpaths
[SP_PERSONAL_DIR
].clear();
885 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
].clear();
889 #if defined(WITH_SHARED_DIR)
891 AppendPathSeparator(tmp
);
892 _searchpaths
[SP_SHARED_DIR
] = tmp
;
894 _searchpaths
[SP_SHARED_DIR
].clear();
898 if (getcwd(cwd
, MAX_PATH
) == nullptr) *cwd
= '\0';
900 if (_config_file
.empty()) {
901 /* Get the path to working directory of OpenTTD. */
903 AppendPathSeparator(tmp
);
904 _searchpaths
[SP_WORKING_DIR
] = tmp
;
906 _do_scan_working_directory
= DoScanWorkingDirectory();
908 /* Use the folder of the config file as working directory. */
909 size_t end
= _config_file
.find_last_of(PATHSEPCHAR
);
910 if (end
== std::string::npos
) {
911 /* _config_file is not in a folder, so use current directory. */
913 AppendPathSeparator(tmp
);
914 _searchpaths
[SP_WORKING_DIR
] = tmp
;
916 _searchpaths
[SP_WORKING_DIR
] = _config_file
.substr(0, end
+ 1);
920 /* Change the working directory to that one of the executable */
921 if (ChangeWorkingDirectoryToExecutable(exe
)) {
923 if (getcwd(buf
, lengthof(buf
)) == nullptr) {
928 AppendPathSeparator(tmp
);
929 _searchpaths
[SP_BINARY_DIR
] = tmp
;
931 _searchpaths
[SP_BINARY_DIR
].clear();
934 if (cwd
[0] != '\0') {
935 /* Go back to the current working directory. */
936 if (chdir(cwd
) != 0) {
937 Debug(misc
, 0, "Failed to return to working directory!");
941 #if !defined(GLOBAL_DATA_DIR)
942 _searchpaths
[SP_INSTALLATION_DIR
].clear();
944 tmp
= GLOBAL_DATA_DIR
;
945 AppendPathSeparator(tmp
);
946 _searchpaths
[SP_INSTALLATION_DIR
] = tmp
;
949 extern void CocoaSetApplicationBundleDir();
950 CocoaSetApplicationBundleDir();
952 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
].clear();
955 #endif /* defined(_WIN32) */
957 std::string _personal_dir
;
960 * Acquire the base paths (personal dir and game data dir),
961 * fill all other paths (save dir, autosave dir etc) and
962 * make the save and scenario directories.
963 * @param exe the path from the current path to the executable
964 * @param only_local_path Whether we shouldn't fill searchpaths with global folders.
966 void DeterminePaths(const char *exe
, bool only_local_path
)
968 DetermineBasePaths(exe
);
969 FillValidSearchPaths(only_local_path
);
972 std::string config_home
;
973 const std::string homedir
= GetHomeDir();
974 const char *xdg_config_home
= std::getenv("XDG_CONFIG_HOME");
975 if (xdg_config_home
!= nullptr) {
976 config_home
= xdg_config_home
;
977 config_home
+= PATHSEP
;
978 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
979 } else if (!homedir
.empty()) {
980 /* Defaults to ~/.config */
981 config_home
= homedir
;
982 config_home
+= PATHSEP
".config" PATHSEP
;
983 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
985 AppendPathSeparator(config_home
);
988 for (Searchpath sp
: _valid_searchpaths
) {
989 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
990 Debug(misc
, 4, "{} added as search path", _searchpaths
[sp
]);
993 std::string config_dir
;
994 if (!_config_file
.empty()) {
995 config_dir
= _searchpaths
[SP_WORKING_DIR
];
997 std::string personal_dir
= FioFindFullPath(BASE_DIR
, "openttd.cfg");
998 if (!personal_dir
.empty()) {
999 auto end
= personal_dir
.find_last_of(PATHSEPCHAR
);
1000 if (end
!= std::string::npos
) personal_dir
.erase(end
+ 1);
1001 config_dir
= personal_dir
;
1004 /* No previous configuration file found. Use the configuration folder from XDG. */
1005 config_dir
= config_home
;
1007 static const Searchpath new_openttd_cfg_order
[] = {
1008 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1012 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1013 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1014 config_dir
= _searchpaths
[new_openttd_cfg_order
[i
]];
1020 _config_file
= config_dir
+ "openttd.cfg";
1023 Debug(misc
, 3, "{} found as config directory", config_dir
);
1025 _highscore_file
= config_dir
+ "hs.dat";
1026 extern std::string _hotkeys_file
;
1027 _hotkeys_file
= config_dir
+ "hotkeys.cfg";
1028 extern std::string _windows_file
;
1029 _windows_file
= config_dir
+ "windows.cfg";
1030 extern std::string _private_file
;
1031 _private_file
= config_dir
+ "private.cfg";
1032 extern std::string _secrets_file
;
1033 _secrets_file
= config_dir
+ "secrets.cfg";
1036 if (config_dir
== config_home
) {
1037 /* We are using the XDG configuration home for the config file,
1038 * then store the rest in the XDG data home folder. */
1039 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1040 if (only_local_path
) {
1041 /* In case of XDG and we only want local paths and we detected that
1042 * the user either manually indicated the XDG path or didn't use
1043 * "-c" option, we change the working-dir to the XDG personal-dir,
1044 * as this is most likely what the user is expecting. */
1045 _searchpaths
[SP_WORKING_DIR
] = _searchpaths
[SP_PERSONAL_DIR_XDG
];
1050 _personal_dir
= config_dir
;
1053 /* Make the necessary folders */
1054 FioCreateDirectory(config_dir
);
1055 #if defined(WITH_PERSONAL_DIR)
1056 FioCreateDirectory(_personal_dir
);
1059 Debug(misc
, 3, "{} found as personal directory", _personal_dir
);
1061 static const Subdirectory default_subdirs
[] = {
1062 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1065 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1066 FioCreateDirectory(_personal_dir
+ _subdirs
[default_subdirs
[i
]]);
1069 /* If we have network we make a directory for the autodownloading of content */
1070 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = _personal_dir
+ "content_download" PATHSEP
;
1071 Debug(misc
, 4, "{} added as search path", _searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1072 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1073 FillValidSearchPaths(only_local_path
);
1075 /* Create the directory for each of the types of content */
1076 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1077 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1078 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR
, dirs
[i
]));
1081 extern std::string _log_file
;
1082 _log_file
= _personal_dir
+ "openttd.log";
1086 * Sanitizes a filename, i.e. removes all illegal characters from it.
1087 * @param filename the "\0" terminated filename
1089 void SanitizeFilename(char *filename
)
1091 for (; *filename
!= '\0'; filename
++) {
1092 switch (*filename
) {
1093 /* The following characters are not allowed in filenames
1094 * on at least one of the supported operating systems: */
1095 case ':': case '\\': case '*': case '?': case '/':
1096 case '<': case '>': case '|': case '"':
1104 * Load a file into memory.
1105 * @param filename Name of the file to load.
1106 * @param[out] lenp Length of loaded data.
1107 * @param maxsize Maximum size to load.
1108 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1109 * @note If \a maxsize less than the length of the file, loading fails.
1111 std::unique_ptr
<char[]> ReadFileToMem(const std::string
&filename
, size_t &lenp
, size_t maxsize
)
1113 FILE *in
= fopen(filename
.c_str(), "rb");
1114 if (in
== nullptr) return nullptr;
1118 fseek(in
, 0, SEEK_END
);
1119 size_t len
= ftell(in
);
1120 fseek(in
, 0, SEEK_SET
);
1121 if (len
> maxsize
) return nullptr;
1123 std::unique_ptr
<char[]> mem
= std::make_unique
<char[]>(len
+ 1);
1126 if (fread(mem
.get(), len
, 1, in
) != 1) return nullptr;
1133 * Helper to see whether a given filename matches the extension.
1134 * @param extension The extension to look for.
1135 * @param filename The filename to look in for the extension.
1136 * @return True iff the extension is nullptr, or the filename ends with it.
1138 static bool MatchesExtension(const char *extension
, const char *filename
)
1140 if (extension
== nullptr) return true;
1142 const char *ext
= strrchr(filename
, extension
[0]);
1143 return ext
!= nullptr && strcasecmp(ext
, extension
) == 0;
1147 * Scan a single directory (and recursively its children) and add
1148 * any graphics sets that are found.
1149 * @param fs the file scanner to add the files to
1150 * @param extension the extension of files to search for.
1151 * @param path full path we're currently at
1152 * @param basepath_length from where in the path are we 'based' on the search path
1153 * @param recursive whether to recursively search the sub directories
1155 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1157 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1161 struct dirent
*dirent
;
1164 if (path
== nullptr || (dir
= ttd_opendir(path
)) == nullptr) return 0;
1166 while ((dirent
= readdir(dir
)) != nullptr) {
1167 std::string d_name
= FS2OTTD(dirent
->d_name
);
1169 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1171 std::string
filename(path
);
1174 if (S_ISDIR(sb
.st_mode
)) {
1176 if (!recursive
) continue;
1177 if (d_name
== "." || d_name
== "..") continue;
1178 AppendPathSeparator(filename
);
1179 num
+= ScanPath(fs
, extension
, filename
.c_str(), basepath_length
, recursive
);
1180 } else if (S_ISREG(sb
.st_mode
)) {
1182 if (MatchesExtension(extension
, filename
.c_str()) && fs
->AddFile(filename
, basepath_length
, {})) num
++;
1192 * Scan the given tar and add graphics sets when it finds one.
1193 * @param fs the file scanner to scan for
1194 * @param extension the extension of files to search for.
1195 * @param tar the tar to search in.
1197 static uint
ScanTar(FileScanner
*fs
, const char *extension
, const TarFileList::value_type
&tar
)
1200 const auto &filename
= tar
.first
;
1202 if (MatchesExtension(extension
, filename
.c_str()) && fs
->AddFile(filename
, 0, tar
.second
.tar_filename
)) num
++;
1208 * Scan for files with the given extension in the given search path.
1209 * @param extension the extension of files to search for.
1210 * @param sd the sub directory to search in.
1211 * @param tars whether to search in the tars too.
1212 * @param recursive whether to search recursively
1213 * @return the number of found files, i.e. the number of times that
1214 * AddFile returned true.
1216 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1222 for (Searchpath sp
: _valid_searchpaths
) {
1223 /* Don't search in the working directory */
1224 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1226 std::string path
= FioGetDirectory(sp
, sd
);
1227 num
+= ScanPath(this, extension
, path
.c_str(), path
.size(), recursive
);
1230 if (tars
&& sd
!= NO_DIRECTORY
) {
1231 for (const auto &tar
: _tar_filelist
[sd
]) {
1232 num
+= ScanTar(this, extension
, tar
);
1238 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1241 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1251 * Scan for files with the given extension in the given search path.
1252 * @param extension the extension of files to search for.
1253 * @param directory the sub directory to search in.
1254 * @param recursive whether to search recursively
1255 * @return the number of found files, i.e. the number of times that
1256 * AddFile returned true.
1258 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1260 std::string
path(directory
);
1261 AppendPathSeparator(path
);
1262 return ScanPath(this, extension
, path
.c_str(), path
.size(), recursive
);