Change: Let AI developers edit non-editable AI/Game Script Parameters (#8895)
[openttd-github.git] / src / fileio.cpp
blob23562cf6a59d4b83f7934ad6b1738dab473a8258
1 /*
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/>.
6 */
8 /** @file fileio.cpp Standard In/Out file operations */
10 #include "stdafx.h"
11 #include "fileio_func.h"
12 #include "spriteloader/spriteloader.hpp"
13 #include "debug.h"
14 #include "fios.h"
15 #include "string_func.h"
16 #include "tar_type.h"
17 #ifdef _WIN32
18 #include <windows.h>
19 # define access _taccess
20 #elif defined(__HAIKU__)
21 #include <Path.h>
22 #include <storage/FindDirectory.h>
23 #else
24 #include <unistd.h>
25 #include <pwd.h>
26 #endif
27 #include <sys/stat.h>
28 #include <array>
29 #include <sstream>
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[] = {
40 "",
41 "save" PATHSEP,
42 "save" PATHSEP "autosave" PATHSEP,
43 "scenario" PATHSEP,
44 "scenario" PATHSEP "heightmap" PATHSEP,
45 "gm" PATHSEP,
46 "data" PATHSEP,
47 "baseset" PATHSEP,
48 "newgrf" PATHSEP,
49 "lang" PATHSEP,
50 "ai" PATHSEP,
51 "ai" PATHSEP "library" PATHSEP,
52 "game" PATHSEP,
53 "game" PATHSEP "library" PATHSEP,
54 "screenshot" PATHSEP,
56 static_assert(lengthof(_subdirs) == NUM_SUBDIRS);
58 /**
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
72 /**
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) {
87 switch (sp) {
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.
91 break;
93 default:
94 continue;
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;
113 FioFCloseFile(f);
114 return true;
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)
132 fclose(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);
147 buf += filename;
148 if (FileExists(buf)) return buf;
149 #if !defined(_WIN32)
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;
154 #endif
157 return {};
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)
182 #if defined(_WIN32)
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 */
187 wchar_t Lmode[5];
188 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
189 #endif
190 FILE *f = nullptr;
191 std::string buf;
193 if (subdir == NO_DIRECTORY) {
194 buf = filename;
195 } else {
196 buf = _searchpaths[sp] + _subdirs[subdir] + filename;
199 #if defined(_WIN32)
200 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf).c_str()) == INVALID_FILE_ATTRIBUTES) return nullptr;
201 #endif
203 f = fopen(buf.c_str(), mode);
204 #if !defined(_WIN32)
205 if (f == nullptr && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
206 f = fopen(buf.c_str(), mode);
208 #endif
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);
215 return f;
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) {
231 fclose(f);
232 return nullptr;
235 if (filesize != nullptr) *filesize = entry.size;
236 return f;
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)
247 FILE *f = nullptr;
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);
262 /* Resolve ".." */
263 std::istringstream ss(resolved_name);
264 std::vector<std::string> tokens;
265 std::string token;
266 while (std::getline(ss, token, PATHSEPCHAR)) {
267 if (token == "..") {
268 if (tokens.size() < 2) return nullptr;
269 tokens.pop_back();
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. */
273 } else {
274 tokens.push_back(token);
278 resolved_name.clear();
279 bool first = true;
280 for (const std::string &token : tokens) {
281 if (!first) {
282 resolved_name += PATHSEP;
284 resolved_name += token;
285 first = false;
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) {
293 /* Apply link */
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) {
308 switch (subdir) {
309 case BASESET_DIR:
310 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
311 if (f != nullptr) break;
312 FALLTHROUGH;
313 case NEWGRF_DIR:
314 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
315 break;
317 default:
318 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
319 break;
323 return f;
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
339 } else {
340 closedir(dir);
344 /* Ignore directory creation errors; they'll surface later on, and most
345 * of the time they are 'directory already exists' errors anyhow. */
346 #if defined(_WIN32)
347 CreateDirectory(OTTD2FS(name).c_str(), nullptr);
348 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
349 mkdir(OTTD2FS(name).c_str());
350 #else
351 mkdir(OTTD2FS(name).c_str(), 0755);
352 #endif
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));
380 } else {
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 */
397 strtolower(name);
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;
402 #endif
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);
416 return num;
419 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
421 Debug(misc, 1, "Scanning for tars");
422 TarScanner fs;
423 uint num = 0;
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);
443 return 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)
454 this->subdir = sd;
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 */
464 struct TarHeader {
465 char name[100]; ///< Name of the file
466 char mode[8];
467 char uid[8];
468 char gid[8];
469 char size[12]; ///< Size of the file, in ASCII
470 char mtime[12];
471 char chksum[8];
472 char typeflag;
473 char linkname[100];
474 char magic[6];
475 char version[2];
476 char uname[32];
477 char gname[32];
478 char devmajor[8];
479 char devminor[8];
480 char prefix[155]; ///< Path of the file
482 char unused[12];
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
500 TarHeader th;
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 */
508 char empty[512];
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);
522 fclose(f);
523 return false;
526 name[0] = '\0';
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) {
542 case '\0':
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;
549 entry.size = skip;
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++;
558 break;
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);
575 break;
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;
583 *destpos = '\0';
585 char *pos = link;
586 while (*pos != '\0') {
587 char *next = strchr(pos, PATHSEPCHAR);
588 if (next == nullptr) {
589 next = pos + strlen(pos);
590 } else {
591 /* Terminate the substring up to the path separator character. */
592 *next++= '\0';
595 if (strcmp(pos, ".") == 0) {
596 /* Skip '.' (current dir) */
597 } else if (strcmp(pos, "..") == 0) {
598 /* level up */
599 if (dest[0] == '\0') {
600 Debug(misc, 1, "Ignoring link pointing outside of data directory: {} -> {}", name, link);
601 break;
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;
608 *destpos = '\0';
609 } else {
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);
617 fclose(f);
618 return false;
621 pos = next;
624 /* Store links in temporary list */
625 Debug(misc, 6, "Found link in tar: {} -> {}", name, dest);
626 links.insert(TarLinkList::value_type(name, dest));
628 break;
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;
638 break;
640 default:
641 /* Ignore other types */
642 break;
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);
649 fclose(f);
650 return false;
652 pos += skip;
655 Debug(misc, 1, "Found tar '{}' with {} new files", filename, num);
656 fclose(f);
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.
663 * 2) Links to files:
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);
673 return true;
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);
694 return false;
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. */
714 size_t to_copy = 0;
715 std::unique_ptr<FILE, FileDeleter> in(FioFOpenFileTar(it2->second, &to_copy));
716 if (!in) {
717 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, tar_filename);
718 return false;
721 /* Now open the 'output' file. */
722 std::unique_ptr<FILE, FileDeleter> out(fopen(filename.c_str(), "wb"));
723 if (!out) {
724 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, filename);
725 return false;
728 /* Now read from the tar and write it into the file. */
729 char buffer[4096];
730 size_t read;
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;
736 if (to_copy != 0) {
737 Debug(misc, 6, "Extracting {} failed; still {} bytes to copy", filename, to_copy);
738 return false;
742 Debug(misc, 9, " extraction successful");
743 return true;
746 #if defined(_WIN32)
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)
764 char tmp[MAX_PATH];
765 strecpy(tmp, exe, lastof(tmp));
767 bool success = false;
768 #ifdef WITH_COCOA
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);
775 if (s != nullptr) {
776 *s = '\0';
777 if (chdir(tmp) != 0) {
778 Debug(misc, 0, "Directory with the binary does not exist?");
779 } else {
780 success = true;
783 return success;
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()
820 #ifdef __HAIKU__
821 BPath path;
822 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
823 return std::string(path.Path());
824 #else
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);
830 #endif
831 return {};
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)
840 std::string tmp;
841 const std::string homedir = GetHomeDir();
842 #ifdef USE_XDG
843 const char *xdg_data_home = std::getenv("XDG_DATA_HOME");
844 if (xdg_data_home != nullptr) {
845 tmp = xdg_data_home;
846 tmp += PATHSEP;
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()) {
855 tmp = homedir;
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;
864 } else {
865 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
866 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG].clear();
868 #endif
870 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
871 _searchpaths[SP_PERSONAL_DIR].clear();
872 #else
873 if (!homedir.empty()) {
874 tmp = homedir;
875 tmp += PATHSEP;
876 tmp += PERSONAL_DIR;
877 AppendPathSeparator(tmp);
878 _searchpaths[SP_PERSONAL_DIR] = tmp;
880 tmp += "content_download";
881 AppendPathSeparator(tmp);
882 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR] = tmp;
883 } else {
884 _searchpaths[SP_PERSONAL_DIR].clear();
885 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR].clear();
887 #endif
889 #if defined(WITH_SHARED_DIR)
890 tmp = SHARED_DIR;
891 AppendPathSeparator(tmp);
892 _searchpaths[SP_SHARED_DIR] = tmp;
893 #else
894 _searchpaths[SP_SHARED_DIR].clear();
895 #endif
897 char cwd[MAX_PATH];
898 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
900 if (_config_file.empty()) {
901 /* Get the path to working directory of OpenTTD. */
902 tmp = cwd;
903 AppendPathSeparator(tmp);
904 _searchpaths[SP_WORKING_DIR] = tmp;
906 _do_scan_working_directory = DoScanWorkingDirectory();
907 } else {
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. */
912 tmp = cwd;
913 AppendPathSeparator(tmp);
914 _searchpaths[SP_WORKING_DIR] = tmp;
915 } else {
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)) {
922 char buf[MAX_PATH];
923 if (getcwd(buf, lengthof(buf)) == nullptr) {
924 tmp.clear();
925 } else {
926 tmp = buf;
928 AppendPathSeparator(tmp);
929 _searchpaths[SP_BINARY_DIR] = tmp;
930 } else {
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();
943 #else
944 tmp = GLOBAL_DATA_DIR;
945 AppendPathSeparator(tmp);
946 _searchpaths[SP_INSTALLATION_DIR] = tmp;
947 #endif
948 #ifdef WITH_COCOA
949 extern void CocoaSetApplicationBundleDir();
950 CocoaSetApplicationBundleDir();
951 #else
952 _searchpaths[SP_APPLICATION_BUNDLE_DIR].clear();
953 #endif
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);
971 #ifdef USE_XDG
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);
986 #endif
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];
996 } else {
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;
1002 } else {
1003 #ifdef USE_XDG
1004 /* No previous configuration file found. Use the configuration folder from XDG. */
1005 config_dir = config_home;
1006 #else
1007 static const Searchpath new_openttd_cfg_order[] = {
1008 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1011 config_dir.clear();
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]];
1015 break;
1018 #endif
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";
1035 #ifdef USE_XDG
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];
1047 } else
1048 #endif
1050 _personal_dir = config_dir;
1053 /* Make the necessary folders */
1054 FioCreateDirectory(config_dir);
1055 #if defined(WITH_PERSONAL_DIR)
1056 FioCreateDirectory(_personal_dir);
1057 #endif
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 '"':
1097 *filename = '_';
1098 break;
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;
1116 FileCloser fc(in);
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);
1125 mem.get()[len] = 0;
1126 if (fread(mem.get(), len, 1, in) != 1) return nullptr;
1128 lenp = len;
1129 return mem;
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);
1159 uint num = 0;
1160 struct stat sb;
1161 struct dirent *dirent;
1162 DIR *dir;
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);
1172 filename += d_name;
1174 if (S_ISDIR(sb.st_mode)) {
1175 /* Directory */
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)) {
1181 /* File */
1182 if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, basepath_length, {})) num++;
1186 closedir(dir);
1188 return 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)
1199 uint num = 0;
1200 const auto &filename = tar.first;
1202 if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, 0, tar.second.tar_filename)) num++;
1204 return 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)
1218 this->subdir = sd;
1220 uint num = 0;
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);
1236 switch (sd) {
1237 case BASESET_DIR:
1238 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1239 FALLTHROUGH;
1240 case NEWGRF_DIR:
1241 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1242 break;
1244 default: break;
1247 return num;
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);