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 #elif defined(__HAIKU__)
21 #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
,
55 "social_integration" PATHSEP
,
57 static_assert(lengthof(_subdirs
) == NUM_SUBDIRS
);
60 * The search paths OpenTTD could search through.
61 * At least one of the slots has to be filled with a path.
62 * An empty string tells that there is no such path for the
63 * current operating system.
65 std::array
<std::string
, NUM_SEARCHPATHS
> _searchpaths
;
66 std::vector
<Searchpath
> _valid_searchpaths
;
67 std::array
<TarList
, NUM_SUBDIRS
> _tar_list
;
68 TarFileList _tar_filelist
[NUM_SUBDIRS
];
71 * Checks whether the given search path is a valid search path
72 * @param sp the search path to check
73 * @return true if the search path is valid
75 static bool IsValidSearchPath(Searchpath sp
)
77 return sp
< _searchpaths
.size() && !_searchpaths
[sp
].empty();
80 static void FillValidSearchPaths(bool only_local_path
)
82 _valid_searchpaths
.clear();
84 std::set
<std::string
> seen
{};
85 for (Searchpath sp
= SP_FIRST_DIR
; sp
< NUM_SEARCHPATHS
; sp
++) {
86 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
88 if (only_local_path
) {
90 case SP_WORKING_DIR
: // Can be influence by "-c" option.
91 case SP_BINARY_DIR
: // Most likely contains all the language files.
92 case SP_AUTODOWNLOAD_DIR
: // Otherwise we cannot download in-game content.
100 if (IsValidSearchPath(sp
)) {
101 if (seen
.count(_searchpaths
[sp
]) != 0) continue;
102 seen
.insert(_searchpaths
[sp
]);
103 _valid_searchpaths
.emplace_back(sp
);
107 /* The working-directory is special, as it is controlled by _do_scan_working_directory.
108 * Only add the search path if it isn't already in the set. To preserve the same order
109 * as the enum, insert it in the front. */
110 if (IsValidSearchPath(SP_WORKING_DIR
) && seen
.count(_searchpaths
[SP_WORKING_DIR
]) == 0) {
111 _valid_searchpaths
.insert(_valid_searchpaths
.begin(), SP_WORKING_DIR
);
116 * Check whether the given file exists
117 * @param filename the file to try for existence.
118 * @param subdir the subdirectory to look in
119 * @return true if and only if the file can be opened
121 bool FioCheckFileExists(const std::string
&filename
, Subdirectory subdir
)
123 auto f
= FioFOpenFile(filename
, "rb", subdir
);
124 return f
.has_value();
128 * Test whether the given filename exists.
129 * @param filename the file to test.
130 * @return true if and only if the file exists.
132 bool FileExists(const std::string
&filename
)
135 return std::filesystem::exists(OTTD2FS(filename
), ec
);
139 * Find a path to the filename in one of the search directories.
140 * @param subdir Subdirectory to try.
141 * @param filename Filename to look for.
142 * @return String containing the path if the path was found, else an empty string.
144 std::string
FioFindFullPath(Subdirectory subdir
, const std::string
&filename
)
146 assert(subdir
< NUM_SUBDIRS
);
148 for (Searchpath sp
: _valid_searchpaths
) {
149 std::string buf
= FioGetDirectory(sp
, subdir
);
151 if (FileExists(buf
)) return buf
;
153 /* Be, as opening files, aware that sometimes the filename
154 * might be in uppercase when it is in lowercase on the
155 * disk. Of course Windows doesn't care about casing. */
156 if (strtolower(buf
, _searchpaths
[sp
].size() - 1) && FileExists(buf
)) return buf
;
163 std::string
FioGetDirectory(Searchpath sp
, Subdirectory subdir
)
165 assert(subdir
< NUM_SUBDIRS
);
166 assert(sp
< NUM_SEARCHPATHS
);
168 return _searchpaths
[sp
] + _subdirs
[subdir
];
171 std::string
FioFindDirectory(Subdirectory subdir
)
173 /* Find and return the first valid directory */
174 for (Searchpath sp
: _valid_searchpaths
) {
175 std::string ret
= FioGetDirectory(sp
, subdir
);
176 if (FileExists(ret
)) return ret
;
179 /* Could not find the directory, fall back to a base path */
180 return _personal_dir
;
183 static std::optional
<FileHandle
> FioFOpenFileSp(const std::string
&filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
186 /* fopen is implemented as a define with ellipses for
187 * Unicode support (prepend an L). As we are not sending
188 * a string, but a variable, it 'renames' the variable,
189 * so make that variable to makes it compile happily */
191 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, static_cast<int>(std::size(Lmode
)));
195 if (subdir
== NO_DIRECTORY
) {
198 buf
= _searchpaths
[sp
] + _subdirs
[subdir
] + filename
;
201 auto f
= FileHandle::Open(buf
, mode
);
203 if (!f
.has_value() && strtolower(buf
, subdir
== NO_DIRECTORY
? 0 : _searchpaths
[sp
].size() - 1) ) {
204 f
= FileHandle::Open(buf
, mode
);
207 if (f
.has_value() && filesize
!= nullptr) {
208 /* Find the size of the file */
209 fseek(*f
, 0, SEEK_END
);
210 *filesize
= ftell(*f
);
211 fseek(*f
, 0, SEEK_SET
);
217 * Opens a file from inside a tar archive.
218 * @param entry The entry to open.
219 * @param[out] filesize If not \c nullptr, size of the opened file.
220 * @return File handle of the opened file, or \c nullptr if the file is not available.
221 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
223 static std::optional
<FileHandle
> FioFOpenFileTar(const TarFileListEntry
&entry
, size_t *filesize
)
225 auto f
= FileHandle::Open(entry
.tar_filename
, "rb");
226 if (!f
.has_value()) return std::nullopt
;
228 if (fseek(*f
, entry
.position
, SEEK_SET
) < 0) {
232 if (filesize
!= nullptr) *filesize
= entry
.size
;
237 * Opens a OpenTTD file somewhere in a personal or global directory.
238 * @param filename Name of the file to open.
239 * @param subdir Subdirectory to open.
240 * @return File handle of the opened file, or \c nullptr if the file is not available.
242 std::optional
<FileHandle
> FioFOpenFile(const std::string
&filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
244 std::optional
<FileHandle
> f
= std::nullopt
;
245 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
247 for (Searchpath sp
: _valid_searchpaths
) {
248 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
249 if (f
.has_value() || subdir
== NO_DIRECTORY
) break;
252 /* We can only use .tar in case of data-dir, and read-mode */
253 if (!f
.has_value() && mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
254 /* Filenames in tars are always forced to be lowercase */
255 std::string resolved_name
= filename
;
256 strtolower(resolved_name
);
259 std::istringstream
ss(resolved_name
);
260 std::vector
<std::string
> tokens
;
262 while (std::getline(ss
, token
, PATHSEPCHAR
)) {
264 if (tokens
.size() < 2) return std::nullopt
;
266 } else if (token
== ".") {
267 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
268 * This confuses our file resolver. So, act like this folder doesn't exist. */
270 tokens
.push_back(token
);
274 resolved_name
.clear();
276 for (const std::string
&token
: tokens
) {
278 resolved_name
+= PATHSEP
;
280 resolved_name
+= token
;
284 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
285 if (it
!= _tar_filelist
[subdir
].end()) {
286 f
= FioFOpenFileTar(it
->second
, filesize
);
290 /* Sometimes a full path is given. To support
291 * the 'subdirectory' must be 'removed'. */
292 if (!f
.has_value() && subdir
!= NO_DIRECTORY
) {
295 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
296 if (f
.has_value()) break;
299 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
303 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
312 * Create a directory with the given name
313 * If the parent directory does not exist, it will try to create that as well.
314 * @param name the new name of the directory
316 void FioCreateDirectory(const std::string
&name
)
318 /* Ignore directory creation errors; they'll surface later on. */
319 std::error_code error_code
;
320 std::filesystem::create_directories(OTTD2FS(name
), error_code
);
325 * @param filename Filename to remove.
326 * @return true iff the file was removed.
328 bool FioRemove(const std::string
&filename
)
330 std::filesystem::path path
= OTTD2FS(filename
);
331 std::error_code error_code
;
332 std::filesystem::remove(path
, error_code
);
334 Debug(misc
, 0, "Removing {} failed: {}", filename
, error_code
.message());
341 * Appends, if necessary, the path separator character to the end of the string.
342 * It does not add the path separator to zero-sized strings.
343 * @param buf string to append the separator to
344 * @return true iff the operation succeeded
346 void AppendPathSeparator(std::string
&buf
)
348 if (buf
.empty()) return;
350 if (buf
.back() != PATHSEPCHAR
) buf
.push_back(PATHSEPCHAR
);
354 * Simplify filenames from tars.
355 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
356 * @param name Filename to process.
358 static void SimplifyFileName(std::string
&name
)
360 for (char &c
: name
) {
361 /* Force lowercase */
363 #if (PATHSEPCHAR != '/')
364 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
365 if (c
== '/') c
= PATHSEPCHAR
;
371 * Perform the scanning of a particular subdirectory.
372 * @param sd The subdirectory to scan.
373 * @return The number of found tar files.
375 uint
TarScanner::DoScan(Subdirectory sd
)
377 _tar_filelist
[sd
].clear();
378 _tar_list
[sd
].clear();
379 uint num
= this->Scan(".tar", sd
, false);
380 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
384 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
386 Debug(misc
, 2, "Scanning for tars");
389 if (mode
& TarScanner::BASESET
) {
390 num
+= fs
.DoScan(BASESET_DIR
);
392 if (mode
& TarScanner::NEWGRF
) {
393 num
+= fs
.DoScan(NEWGRF_DIR
);
395 if (mode
& TarScanner::AI
) {
396 num
+= fs
.DoScan(AI_DIR
);
397 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
399 if (mode
& TarScanner::GAME
) {
400 num
+= fs
.DoScan(GAME_DIR
);
401 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
403 if (mode
& TarScanner::SCENARIO
) {
404 num
+= fs
.DoScan(SCENARIO_DIR
);
405 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
407 Debug(misc
, 2, "Scan complete, found {} files", num
);
412 * Add a single file to the scanned files of a tar, circumventing the scanning code.
413 * @param sd The sub directory the file is in.
414 * @param filename The name of the file to add.
415 * @return True if the additions went correctly.
417 bool TarScanner::AddFile(Subdirectory sd
, const std::string
&filename
)
420 return this->AddFile(filename
, 0);
424 * Helper to extract a string for the tar header. We must assume that the tar
425 * header contains garbage and is malicious. So, we cannot rely on the string
426 * being properly terminated.
427 * As such, do not use strlen to determine the actual length (explicitly or
428 * implictly via the std::string constructor), but pass the buffer bounds
430 * @param buffer The buffer to read from.
431 * @return The string data.
433 static std::string
ExtractString(std::span
<char> buffer
)
435 return StrMakeValid(std::string_view(buffer
.begin(), buffer
.end()));
438 bool TarScanner::AddFile(const std::string
&filename
, size_t, [[maybe_unused
]] const std::string
&tar_filename
)
440 /* No tar within tar. */
441 assert(tar_filename
.empty());
443 /* The TAR-header, repeated for every file */
445 char name
[100]; ///< Name of the file
449 char size
[12]; ///< Size of the file, in ASCII octals
460 char prefix
[155]; ///< Path of the file
465 /* Check if we already seen this file */
466 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
467 if (it
!= _tar_list
[this->subdir
].end()) return false;
469 auto of
= FileHandle::Open(filename
, "rb");
470 /* Although the file has been found there can be
471 * a number of reasons we cannot open the file.
472 * Most common case is when we simply have not
473 * been given read access. */
474 if (!of
.has_value()) return false;
477 _tar_list
[this->subdir
][filename
] = std::string
{};
479 std::string filename_base
= FS2OTTD(std::filesystem::path(OTTD2FS(filename
)).filename());
480 SimplifyFileName(filename_base
);
483 size_t num
= 0, pos
= 0;
485 /* Make a char of 512 empty bytes */
487 memset(&empty
[0], 0, sizeof(empty
));
489 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
490 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
491 if (num_bytes_read
!= 512) break;
492 pos
+= num_bytes_read
;
494 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
495 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
496 /* If we have only zeros in the block, it can be an end-of-file indicator */
497 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
499 Debug(misc
, 0, "The file '{}' isn't a valid tar-file", filename
);
505 /* The prefix contains the directory-name */
506 if (th
.prefix
[0] != '\0') {
507 name
= ExtractString(th
.prefix
);
511 /* Copy the name of the file in a safe way at the end of 'name' */
512 name
+= ExtractString(th
.name
);
514 /* The size of the file, for some strange reason, this is stored as a string in octals. */
515 std::string size
= ExtractString(th
.size
);
518 StrTrimInPlace(size
);
519 auto [_
, err
] = std::from_chars(size
.data(), size
.data() + size
.size(), skip
, 8);
520 if (err
!= std::errc()) {
521 Debug(misc
, 0, "The file '{}' has an invalid size for '{}'", filename
, name
);
527 switch (th
.typeflag
) {
529 case '0': { // regular file
530 if (name
.empty()) break;
532 /* Store this entry in the list */
533 TarFileListEntry entry
;
534 entry
.tar_filename
= filename
;
536 entry
.position
= pos
;
538 /* Convert to lowercase and our PATHSEPCHAR */
539 SimplifyFileName(name
);
541 Debug(misc
, 6, "Found file in tar: {} ({} bytes, {} offset)", name
, skip
, pos
);
542 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(filename_base
+ PATHSEPCHAR
+ name
, entry
)).second
) num
++;
547 case '1': // hard links
548 case '2': { // symbolic links
549 std::string link
= ExtractString(th
.linkname
);
551 Debug(misc
, 5, "Ignoring link in tar: {} -> {}", name
, link
);
555 case '5': // directory
556 /* Convert to lowercase and our PATHSEPCHAR */
557 SimplifyFileName(name
);
559 /* Store the first directory name we detect */
560 Debug(misc
, 6, "Found dir in tar: {}", name
);
561 if (_tar_list
[this->subdir
][filename
].empty()) _tar_list
[this->subdir
][filename
] = name
;
565 /* Ignore other types */
569 /* Skip to the next block.. */
570 skip
= Align(skip
, 512);
571 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
572 Debug(misc
, 0, "The file '{}' can't be read as a valid tar-file", filename
);
578 Debug(misc
, 4, "Found tar '{}' with {} new files", filename
, num
);
584 * Extract the tar with the given filename in the directory
585 * where the tar resides.
586 * @param tar_filename the name of the tar to extract.
587 * @param subdir The sub directory the tar is in.
588 * @return false on failure.
590 bool ExtractTar(const std::string
&tar_filename
, Subdirectory subdir
)
592 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
593 /* We don't know the file. */
594 if (it
== _tar_list
[subdir
].end()) return false;
596 const auto &dirname
= (*it
).second
;
598 /* The file doesn't have a sub directory! */
599 if (dirname
.empty()) {
600 Debug(misc
, 3, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename
);
604 std::string filename
= tar_filename
;
605 auto p
= filename
.find_last_of(PATHSEPCHAR
);
606 /* The file's path does not have a separator? */
607 if (p
== std::string::npos
) return false;
609 filename
.replace(p
+ 1, std::string::npos
, dirname
);
610 Debug(misc
, 8, "Extracting {} to directory {}", tar_filename
, filename
);
611 FioCreateDirectory(filename
);
613 for (auto &it2
: _tar_filelist
[subdir
]) {
614 if (tar_filename
!= it2
.second
.tar_filename
) continue;
616 /* it2.first is tarball + PATHSEPCHAR + name. */
617 std::string_view name
= it2
.first
;
618 name
.remove_prefix(name
.find_first_of(PATHSEPCHAR
) + 1);
619 filename
.replace(p
+ 1, std::string::npos
, name
);
621 Debug(misc
, 9, " extracting {}", filename
);
623 /* First open the file in the .tar. */
625 auto in
= FioFOpenFileTar(it2
.second
, &to_copy
);
626 if (!in
.has_value()) {
627 Debug(misc
, 6, "Extracting {} failed; could not open {}", filename
, tar_filename
);
631 /* Now open the 'output' file. */
632 auto out
= FileHandle::Open(filename
, "wb");
633 if (!out
.has_value()) {
634 Debug(misc
, 6, "Extracting {} failed; could not open {}", filename
, filename
);
638 /* Now read from the tar and write it into the file. */
641 for (; to_copy
!= 0; to_copy
-= read
) {
642 read
= fread(buffer
, 1, std::min(to_copy
, lengthof(buffer
)), *in
);
643 if (read
<= 0 || fwrite(buffer
, 1, read
, *out
) != read
) break;
647 Debug(misc
, 6, "Extracting {} failed; still {} bytes to copy", filename
, to_copy
);
652 Debug(misc
, 9, " extraction successful");
658 * Determine the base (personal dir and game data dir) paths
659 * @param exe the path from the current path to the executable
660 * @note defined in the OS related files (win32.cpp, unix.cpp etc)
662 extern void DetermineBasePaths(const char *exe
);
663 #else /* defined(_WIN32) */
666 * Changes the working directory to the path of the give executable.
667 * For OSX application bundles '.app' is the required extension of the bundle,
668 * so when we crop the path to there, when can remove the name of the bundle
669 * in the same way we remove the name from the executable name.
670 * @param exe the path to the executable
672 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
674 std::string path
= exe
;
677 for (size_t pos
= path
.find_first_of('.'); pos
!= std::string::npos
; pos
= path
.find_first_of('.', pos
+ 1)) {
678 if (StrEqualsIgnoreCase(path
.substr(pos
, 4), ".app")) {
683 #endif /* WITH_COCOA */
685 size_t pos
= path
.find_last_of(PATHSEPCHAR
);
686 if (pos
== std::string::npos
) return false;
690 if (chdir(path
.c_str()) != 0) {
691 Debug(misc
, 0, "Directory with the binary does not exist?");
699 * Whether we should scan the working directory.
700 * It should not be scanned if it's the root or
701 * the home directory as in both cases a big data
702 * directory can cause huge amounts of unrelated
703 * files scanned. Furthermore there are nearly no
704 * use cases for the home/root directory to have
705 * OpenTTD directories.
706 * @return true if it should be scanned.
708 bool DoScanWorkingDirectory()
710 /* No working directory, so nothing to do. */
711 if (_searchpaths
[SP_WORKING_DIR
].empty()) return false;
713 /* Working directory is root, so do nothing. */
714 if (_searchpaths
[SP_WORKING_DIR
] == PATHSEP
) return false;
716 /* No personal/home directory, so the working directory won't be that. */
717 if (_searchpaths
[SP_PERSONAL_DIR
].empty()) return true;
719 std::string tmp
= _searchpaths
[SP_WORKING_DIR
] + PERSONAL_DIR
;
720 AppendPathSeparator(tmp
);
722 return _searchpaths
[SP_PERSONAL_DIR
] != tmp
;
726 * Gets the home directory of the user.
727 * May return an empty string in the unlikely scenario that the home directory cannot be found.
728 * @return User's home directory
730 static std::string
GetHomeDir()
734 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
735 return std::string(path
.Path());
737 const char *home_env
= std::getenv("HOME"); // Stack var, shouldn't be freed
738 if (home_env
!= nullptr) return std::string(home_env
);
740 const struct passwd
*pw
= getpwuid(getuid());
741 if (pw
!= nullptr) return std::string(pw
->pw_dir
);
747 * Determine the base (personal dir and game data dir) paths
748 * @param exe the path to the executable
750 void DetermineBasePaths(const char *exe
)
753 const std::string homedir
= GetHomeDir();
755 const char *xdg_data_home
= std::getenv("XDG_DATA_HOME");
756 if (xdg_data_home
!= nullptr) {
759 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
760 AppendPathSeparator(tmp
);
761 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
763 tmp
+= "content_download";
764 AppendPathSeparator(tmp
);
765 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
766 } else if (!homedir
.empty()) {
768 tmp
+= PATHSEP
".local" PATHSEP
"share" PATHSEP
;
769 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
770 AppendPathSeparator(tmp
);
771 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
773 tmp
+= "content_download";
774 AppendPathSeparator(tmp
);
775 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
777 _searchpaths
[SP_PERSONAL_DIR_XDG
].clear();
778 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
].clear();
782 #if !defined(WITH_PERSONAL_DIR)
783 _searchpaths
[SP_PERSONAL_DIR
].clear();
785 if (!homedir
.empty()) {
789 AppendPathSeparator(tmp
);
790 _searchpaths
[SP_PERSONAL_DIR
] = tmp
;
792 tmp
+= "content_download";
793 AppendPathSeparator(tmp
);
794 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
] = tmp
;
796 _searchpaths
[SP_PERSONAL_DIR
].clear();
797 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
].clear();
801 #if defined(WITH_SHARED_DIR)
803 AppendPathSeparator(tmp
);
804 _searchpaths
[SP_SHARED_DIR
] = tmp
;
806 _searchpaths
[SP_SHARED_DIR
].clear();
810 if (getcwd(cwd
, MAX_PATH
) == nullptr) *cwd
= '\0';
812 if (_config_file
.empty()) {
813 /* Get the path to working directory of OpenTTD. */
815 AppendPathSeparator(tmp
);
816 _searchpaths
[SP_WORKING_DIR
] = tmp
;
818 _do_scan_working_directory
= DoScanWorkingDirectory();
820 /* Use the folder of the config file as working directory. */
821 size_t end
= _config_file
.find_last_of(PATHSEPCHAR
);
822 if (end
== std::string::npos
) {
823 /* _config_file is not in a folder, so use current directory. */
826 tmp
= FS2OTTD(std::filesystem::weakly_canonical(std::filesystem::path(OTTD2FS(_config_file
))).parent_path());
828 AppendPathSeparator(tmp
);
829 _searchpaths
[SP_WORKING_DIR
] = tmp
;
832 /* Change the working directory to that one of the executable */
833 if (ChangeWorkingDirectoryToExecutable(exe
)) {
835 if (getcwd(buf
, lengthof(buf
)) == nullptr) {
840 AppendPathSeparator(tmp
);
841 _searchpaths
[SP_BINARY_DIR
] = tmp
;
843 _searchpaths
[SP_BINARY_DIR
].clear();
846 if (cwd
[0] != '\0') {
847 /* Go back to the current working directory. */
848 if (chdir(cwd
) != 0) {
849 Debug(misc
, 0, "Failed to return to working directory!");
853 #if !defined(GLOBAL_DATA_DIR)
854 _searchpaths
[SP_INSTALLATION_DIR
].clear();
856 tmp
= GLOBAL_DATA_DIR
;
857 AppendPathSeparator(tmp
);
858 _searchpaths
[SP_INSTALLATION_DIR
] = tmp
;
861 extern void CocoaSetApplicationBundleDir();
862 CocoaSetApplicationBundleDir();
864 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
].clear();
867 #endif /* defined(_WIN32) */
869 std::string _personal_dir
;
872 * Acquire the base paths (personal dir and game data dir),
873 * fill all other paths (save dir, autosave dir etc) and
874 * make the save and scenario directories.
875 * @param exe the path from the current path to the executable
876 * @param only_local_path Whether we shouldn't fill searchpaths with global folders.
878 void DeterminePaths(const char *exe
, bool only_local_path
)
880 DetermineBasePaths(exe
);
881 FillValidSearchPaths(only_local_path
);
884 std::string config_home
;
885 const std::string homedir
= GetHomeDir();
886 const char *xdg_config_home
= std::getenv("XDG_CONFIG_HOME");
887 if (xdg_config_home
!= nullptr) {
888 config_home
= xdg_config_home
;
889 config_home
+= PATHSEP
;
890 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
891 } else if (!homedir
.empty()) {
892 /* Defaults to ~/.config */
893 config_home
= homedir
;
894 config_home
+= PATHSEP
".config" PATHSEP
;
895 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
897 AppendPathSeparator(config_home
);
900 for (Searchpath sp
: _valid_searchpaths
) {
901 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
902 Debug(misc
, 3, "{} added as search path", _searchpaths
[sp
]);
905 std::string config_dir
;
906 if (!_config_file
.empty()) {
907 config_dir
= _searchpaths
[SP_WORKING_DIR
];
909 std::string personal_dir
= FioFindFullPath(BASE_DIR
, "openttd.cfg");
910 if (!personal_dir
.empty()) {
911 auto end
= personal_dir
.find_last_of(PATHSEPCHAR
);
912 if (end
!= std::string::npos
) personal_dir
.erase(end
+ 1);
913 config_dir
= personal_dir
;
916 /* No previous configuration file found. Use the configuration folder from XDG. */
917 config_dir
= config_home
;
919 static const Searchpath new_openttd_cfg_order
[] = {
920 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
924 for (const auto &searchpath
: new_openttd_cfg_order
) {
925 if (IsValidSearchPath(searchpath
)) {
926 config_dir
= _searchpaths
[searchpath
];
932 _config_file
= config_dir
+ "openttd.cfg";
935 Debug(misc
, 1, "{} found as config directory", config_dir
);
937 _highscore_file
= config_dir
+ "hs.dat";
938 extern std::string _hotkeys_file
;
939 _hotkeys_file
= config_dir
+ "hotkeys.cfg";
940 extern std::string _windows_file
;
941 _windows_file
= config_dir
+ "windows.cfg";
942 extern std::string _private_file
;
943 _private_file
= config_dir
+ "private.cfg";
944 extern std::string _secrets_file
;
945 _secrets_file
= config_dir
+ "secrets.cfg";
946 extern std::string _favs_file
;
947 _favs_file
= config_dir
+ "favs.cfg";
950 if (config_dir
== config_home
) {
951 /* We are using the XDG configuration home for the config file,
952 * then store the rest in the XDG data home folder. */
953 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
954 if (only_local_path
) {
955 /* In case of XDG and we only want local paths and we detected that
956 * the user either manually indicated the XDG path or didn't use
957 * "-c" option, we change the working-dir to the XDG personal-dir,
958 * as this is most likely what the user is expecting. */
959 _searchpaths
[SP_WORKING_DIR
] = _searchpaths
[SP_PERSONAL_DIR_XDG
];
964 _personal_dir
= config_dir
;
967 /* Make the necessary folders */
968 FioCreateDirectory(config_dir
);
969 #if defined(WITH_PERSONAL_DIR)
970 FioCreateDirectory(_personal_dir
);
973 Debug(misc
, 1, "{} found as personal directory", _personal_dir
);
975 static const Subdirectory default_subdirs
[] = {
976 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
, SOCIAL_INTEGRATION_DIR
979 for (const auto &default_subdir
: default_subdirs
) {
980 FioCreateDirectory(_personal_dir
+ _subdirs
[default_subdir
]);
983 /* If we have network we make a directory for the autodownloading of content */
984 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = _personal_dir
+ "content_download" PATHSEP
;
985 Debug(misc
, 3, "{} added as search path", _searchpaths
[SP_AUTODOWNLOAD_DIR
]);
986 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
987 FillValidSearchPaths(only_local_path
);
989 /* Create the directory for each of the types of content */
990 const Subdirectory subdirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SOCIAL_INTEGRATION_DIR
};
991 for (const auto &subdir
: subdirs
) {
992 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR
, subdir
));
995 extern std::string _log_file
;
996 _log_file
= _personal_dir
+ "openttd.log";
1000 * Sanitizes a filename, i.e. removes all illegal characters from it.
1001 * @param filename the filename
1003 void SanitizeFilename(std::string
&filename
)
1005 for (auto &c
: filename
) {
1007 /* The following characters are not allowed in filenames
1008 * on at least one of the supported operating systems: */
1009 case ':': case '\\': case '*': case '?': case '/':
1010 case '<': case '>': case '|': case '"':
1018 * Load a file into memory.
1019 * @param filename Name of the file to load.
1020 * @param[out] lenp Length of loaded data.
1021 * @param maxsize Maximum size to load.
1022 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1023 * @note If \a maxsize less than the length of the file, loading fails.
1025 std::unique_ptr
<char[]> ReadFileToMem(const std::string
&filename
, size_t &lenp
, size_t maxsize
)
1027 auto in
= FileHandle::Open(filename
, "rb");
1028 if (!in
.has_value()) return nullptr;
1030 fseek(*in
, 0, SEEK_END
);
1031 size_t len
= ftell(*in
);
1032 fseek(*in
, 0, SEEK_SET
);
1033 if (len
> maxsize
) return nullptr;
1035 std::unique_ptr
<char[]> mem
= std::make_unique
<char[]>(len
+ 1);
1038 if (fread(mem
.get(), len
, 1, *in
) != 1) return nullptr;
1045 * Helper to see whether a given filename matches the extension.
1046 * @param extension The extension to look for.
1047 * @param filename The filename to look in for the extension.
1048 * @return True iff the extension is nullptr, or the filename ends with it.
1050 static bool MatchesExtension(std::string_view extension
, const std::string
&filename
)
1052 if (extension
.empty()) return true;
1053 if (filename
.length() < extension
.length()) return false;
1055 std::string_view filename_sv
= filename
; // String view to avoid making another copy of the substring.
1056 return StrCompareIgnoreCase(extension
, filename_sv
.substr(filename_sv
.length() - extension
.length())) == 0;
1060 * Scan a single directory (and recursively its children) and add
1061 * any graphics sets that are found.
1062 * @param fs the file scanner to add the files to
1063 * @param extension the extension of files to search for.
1064 * @param path full path we're currently at
1065 * @param basepath_length from where in the path are we 'based' on the search path
1066 * @param recursive whether to recursively search the sub directories
1068 static uint
ScanPath(FileScanner
*fs
, std::string_view extension
, const std::filesystem::path
&path
, size_t basepath_length
, bool recursive
)
1072 std::error_code error_code
;
1073 for (const auto &dir_entry
: std::filesystem::directory_iterator(path
, error_code
)) {
1074 if (dir_entry
.is_directory()) {
1075 if (!recursive
) continue;
1076 num
+= ScanPath(fs
, extension
, dir_entry
.path(), basepath_length
, recursive
);
1077 } else if (dir_entry
.is_regular_file()) {
1078 std::string file
= FS2OTTD(dir_entry
.path());
1079 if (!MatchesExtension(extension
, file
)) continue;
1080 if (fs
->AddFile(file
, basepath_length
, {})) num
++;
1084 Debug(misc
, 9, "Unable to read directory {}: {}", path
.string(), error_code
.message());
1091 * Scan the given tar and add graphics sets when it finds one.
1092 * @param fs the file scanner to scan for
1093 * @param extension the extension of files to search for.
1094 * @param tar the tar to search in.
1096 static uint
ScanTar(FileScanner
*fs
, std::string_view extension
, const TarFileList::value_type
&tar
)
1100 if (MatchesExtension(extension
, tar
.first
) && fs
->AddFile(tar
.first
, 0, tar
.second
.tar_filename
)) num
++;
1106 * Scan for files with the given extension in the given search path.
1107 * @param extension the extension of files to search for.
1108 * @param sd the sub directory to search in.
1109 * @param tars whether to search in the tars too.
1110 * @param recursive whether to search recursively
1111 * @return the number of found files, i.e. the number of times that
1112 * AddFile returned true.
1114 uint
FileScanner::Scan(std::string_view extension
, Subdirectory sd
, bool tars
, bool recursive
)
1120 for (Searchpath sp
: _valid_searchpaths
) {
1121 /* Don't search in the working directory */
1122 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1124 std::string path
= FioGetDirectory(sp
, sd
);
1125 num
+= ScanPath(this, extension
, OTTD2FS(path
), path
.size(), recursive
);
1128 if (tars
&& sd
!= NO_DIRECTORY
) {
1129 for (const auto &tar
: _tar_filelist
[sd
]) {
1130 num
+= ScanTar(this, extension
, tar
);
1136 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1139 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1149 * Scan for files with the given extension in the given search path.
1150 * @param extension the extension of files to search for.
1151 * @param directory the sub directory to search in.
1152 * @param recursive whether to search recursively
1153 * @return the number of found files, i.e. the number of times that
1154 * AddFile returned true.
1156 uint
FileScanner::Scan(const std::string_view extension
, const std::string
&directory
, bool recursive
)
1158 std::string
path(directory
);
1159 AppendPathSeparator(path
);
1160 return ScanPath(this, extension
, OTTD2FS(path
), path
.size(), recursive
);
1164 * Open an RAII file handle if possible.
1165 * The canonical RAII-way is for FileHandle to open the file and throw an exception on failure, but we don't want that.
1166 * @param filename UTF-8 encoded filename to open.
1167 * @param mode Mode to open file.
1168 * @return FileHandle, or std::nullopt on failure.
1170 std::optional
<FileHandle
> FileHandle::Open(const std::string
&filename
, const std::string
&mode
)
1173 /* Windows also requires mode to be wchar_t. */
1174 auto f
= _wfopen(OTTD2FS(filename
).c_str(), OTTD2FS(mode
).c_str());
1176 auto f
= fopen(filename
.c_str(), mode
.c_str());
1179 if (f
== nullptr) return std::nullopt
;
1180 return FileHandle(f
);