OpenTTD Source 20260218-master-g2123fca5ea
fileio.cpp
Go to the documentation of this file.
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
12#include "fileio_func.h"
14#include "debug.h"
15#include "fios.h"
16#include "string_func.h"
17#include "tar_type.h"
18#ifdef _WIN32
19#include <windows.h>
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 <filesystem>
29
30#include "safeguards.h"
31
33static bool _do_scan_working_directory = true;
34
35extern std::string _config_file;
36extern std::string _highscore_file;
37
38static const std::string_view _subdirs[] = {
39 "",
40 "save" PATHSEP,
41 "save" PATHSEP "autosave" PATHSEP,
42 "scenario" PATHSEP,
43 "scenario" PATHSEP "heightmap" PATHSEP,
44 "gm" PATHSEP,
45 "data" PATHSEP,
46 "baseset" PATHSEP,
47 "newgrf" PATHSEP,
48 "lang" PATHSEP,
49 "ai" PATHSEP,
50 "ai" PATHSEP "library" PATHSEP,
51 "game" PATHSEP,
52 "game" PATHSEP "library" PATHSEP,
53 "screenshot" PATHSEP,
54 "social_integration" PATHSEP,
55 "docs" PATHSEP,
56};
57static_assert(lengthof(_subdirs) == NUM_SUBDIRS);
58
65std::array<std::string, NUM_SEARCHPATHS> _searchpaths;
66std::vector<Searchpath> _valid_searchpaths;
67std::array<TarList, NUM_SUBDIRS> _tar_list;
68TarFileList _tar_filelist[NUM_SUBDIRS];
69
76{
77 return sp < _searchpaths.size() && !_searchpaths[sp].empty();
78}
79
80static void FillValidSearchPaths(bool only_local_path)
81{
82 _valid_searchpaths.clear();
83
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;
87
88 if (only_local_path) {
89 switch (sp) {
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.
93 break;
94
95 default:
96 continue;
97 }
98 }
99
100 if (IsValidSearchPath(sp)) {
101 if (seen.count(_searchpaths[sp]) != 0) continue;
102 seen.insert(_searchpaths[sp]);
103 _valid_searchpaths.emplace_back(sp);
104 }
105 }
106
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. */
111 _valid_searchpaths.insert(_valid_searchpaths.begin(), SP_WORKING_DIR);
112 }
113}
114
121bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
122{
123 auto f = FioFOpenFile(filename, "rb", subdir);
124 return f.has_value();
125}
126
132bool FileExists(std::string_view filename)
133{
134 std::error_code ec;
135 return std::filesystem::exists(OTTD2FS(filename), ec);
136}
137
144std::string FioFindFullPath(Subdirectory subdir, std::string_view filename)
145{
146 assert(subdir < NUM_SUBDIRS);
147
148 for (Searchpath sp : _valid_searchpaths) {
149 std::string buf = FioGetDirectory(sp, subdir);
150 buf += filename;
151 if (FileExists(buf)) return buf;
152#if !defined(_WIN32)
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;
157#endif
158 }
159
160 return {};
161}
162
163std::string FioGetDirectory(Searchpath sp, Subdirectory subdir)
164{
165 assert(subdir < NUM_SUBDIRS);
166 assert(sp < NUM_SEARCHPATHS);
167
168 return fmt::format("{}{}", _searchpaths[sp], _subdirs[subdir]);
169}
170
171std::string FioFindDirectory(Subdirectory subdir)
172{
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;
177 }
178
179 /* Could not find the directory, fall back to a base path */
180 return _personal_dir;
181}
182
183static std::optional<FileHandle> FioFOpenFileSp(std::string_view filename, std::string_view mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
184{
185#if defined(_WIN32)
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 */
190 wchar_t Lmode[5];
191 MultiByteToWideChar(CP_ACP, 0, mode.data(), static_cast<int>(std::size(mode)), Lmode, static_cast<int>(std::size(Lmode)));
192#endif
193 std::string buf;
194
195 if (subdir == NO_DIRECTORY) {
196 buf = filename;
197 } else {
198 buf = fmt::format("{}{}{}", _searchpaths[sp], _subdirs[subdir], filename);
199 }
200
201 auto f = FileHandle::Open(buf, mode);
202#if !defined(_WIN32)
203 if (!f.has_value() && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
204 f = FileHandle::Open(buf, mode);
205 }
206#endif
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);
212 }
213 return f;
214}
215
223static std::optional<FileHandle> FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
224{
225 auto f = FileHandle::Open(entry.tar_filename, "rb");
226 if (!f.has_value()) return std::nullopt;
227
228 if (fseek(*f, entry.position, SEEK_SET) < 0) {
229 return std::nullopt;
230 }
231
232 if (filesize != nullptr) *filesize = entry.size;
233 return f;
234}
235
244std::optional<FileHandle> FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
245{
246 std::optional<FileHandle> f = std::nullopt;
247 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
248
249 for (Searchpath sp : _valid_searchpaths) {
250 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
251 if (f.has_value() || subdir == NO_DIRECTORY) break;
252 }
253
254 /* We can only use .tar in case of data-dir, and read-mode */
255 if (!f.has_value() && mode[0] == 'r' && subdir != NO_DIRECTORY) {
256 /* Filenames in tars are always forced to be lowercase */
257 std::string resolved_name{filename};
258 strtolower(resolved_name);
259
260 /* Resolve ".." */
261 std::istringstream ss(resolved_name);
262 std::vector<std::string> tokens;
263 for (std::string token; std::getline(ss, token, PATHSEPCHAR); /* nothing */) {
264 if (token == "..") {
265 if (tokens.size() < 2) return std::nullopt;
266 tokens.pop_back();
267 } else if (token == ".") {
268 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
269 * This confuses our file resolver. So, act like this folder doesn't exist. */
270 } else {
271 tokens.push_back(token);
272 }
273 }
274
275 resolved_name.clear();
276 bool first = true;
277 for (const std::string &token : tokens) {
278 if (!first) {
279 resolved_name += PATHSEP;
280 }
281 resolved_name += token;
282 first = false;
283 }
284
285 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
286 if (it != _tar_filelist[subdir].end()) {
287 f = FioFOpenFileTar(it->second, filesize);
288 }
289 }
290
291 /* Sometimes a full path is given. To support
292 * the 'subdirectory' must be 'removed'. */
293 if (!f.has_value() && subdir != NO_DIRECTORY) {
294 switch (subdir) {
295 case BASESET_DIR:
296 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
297 if (f.has_value()) break;
298 [[fallthrough]];
299 case NEWGRF_DIR:
300 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
301 break;
302
303 default:
304 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
305 break;
306 }
307 }
308
309 return f;
310}
311
317void FioCreateDirectory(const std::string &name)
318{
319 /* Ignore directory creation errors; they'll surface later on. */
320 std::error_code error_code;
321 std::filesystem::create_directories(OTTD2FS(name), error_code);
322}
323
329bool FioRemove(const std::string &filename)
330{
331 std::filesystem::path path = OTTD2FS(filename);
332 std::error_code error_code;
333 if (!std::filesystem::remove(path, error_code)) {
334 if (error_code) {
335 Debug(misc, 0, "Removing {} failed: {}", filename, error_code.message());
336 } else {
337 Debug(misc, 0, "Removing {} failed: file does not exist", filename);
338 }
339 return false;
340 }
341 return true;
342}
343
349void AppendPathSeparator(std::string &buf)
350{
351 if (buf.empty()) return;
352
353 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
354}
355
361static void SimplifyFileName(std::string &name)
362{
363 for (char &c : name) {
364 /* Force lowercase */
365 c = std::tolower(c);
366#if (PATHSEPCHAR != '/')
367 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
368 if (c == '/') c = PATHSEPCHAR;
369#endif
370 }
371}
372
379{
380 _tar_filelist[sd].clear();
381 _tar_list[sd].clear();
382 uint num = this->Scan(".tar", sd, false);
383 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
384 return num;
385}
386
392/* static */ uint TarScanner::DoScan(TarScanner::Modes modes)
393{
394 Debug(misc, 2, "Scanning for tars");
395 TarScanner fs;
396 uint num = 0;
397 if (modes.Test(TarScanner::Mode::Baseset)) {
398 num += fs.DoScan(BASESET_DIR);
399 }
400 if (modes.Test(TarScanner::Mode::NewGRF)) {
401 num += fs.DoScan(NEWGRF_DIR);
402 }
403 if (modes.Test(TarScanner::Mode::AI)) {
404 num += fs.DoScan(AI_DIR);
405 num += fs.DoScan(AI_LIBRARY_DIR);
406 }
407 if (modes.Test(TarScanner::Mode::Game)) {
408 num += fs.DoScan(GAME_DIR);
409 num += fs.DoScan(GAME_LIBRARY_DIR);
410 }
411 if (modes.Test(TarScanner::Mode::Scenario)) {
412 num += fs.DoScan(SCENARIO_DIR);
413 num += fs.DoScan(HEIGHTMAP_DIR);
414 }
415 Debug(misc, 2, "Scan complete, found {} files", num);
416 return num;
417}
418
425bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
426{
427 this->subdir = sd;
428 return this->AddFile(filename, 0);
429}
430
441static std::string ExtractString(std::span<char> buffer)
442{
443 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
444}
445
446bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] const std::string &tar_filename)
447{
448 /* No tar within tar. */
449 assert(tar_filename.empty());
450
451 /* The TAR-header, repeated for every file */
452 struct TarHeader {
453 char name[100];
454 char mode[8];
455 char uid[8];
456 char gid[8];
457 char size[12];
458 char mtime[12];
459 char chksum[8];
460 char typeflag;
461 char linkname[100];
462 char magic[6];
463 char version[2];
464 char uname[32];
465 char gname[32];
466 char devmajor[8];
467 char devminor[8];
468 char prefix[155];
469
470 char unused[12];
471 };
472 static_assert(sizeof(TarHeader) == 512);
473
474 /* Check if we already seen this file */
475 TarList::iterator it = _tar_list[this->subdir].find(filename);
476 if (it != _tar_list[this->subdir].end()) return false;
477
478 auto of = FileHandle::Open(filename, "rb");
479 /* Although the file has been found there can be
480 * a number of reasons we cannot open the file.
481 * Most common case is when we simply have not
482 * been given read access. */
483 if (!of.has_value()) return false;
484 auto &f = *of;
485
486 _tar_list[this->subdir][filename] = std::string{};
487
488 std::string filename_base = FS2OTTD(std::filesystem::path(OTTD2FS(filename)).filename().native());
489 SimplifyFileName(filename_base);
490
491 TarHeader th;
492 size_t num = 0, pos = 0;
493
494 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
495 size_t num_bytes_read = fread(&th, 1, sizeof(TarHeader), f);
496 if (num_bytes_read != sizeof(TarHeader)) break;
497 pos += num_bytes_read;
498
499 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
500 auto last_of_th = &th.unused[std::size(th.unused)];
501 if (std::string_view{th.magic, 5} != "ustar" && std::any_of(th.magic, last_of_th, [](auto c) { return c != 0; })) {
502 /* If we have only zeros in the block, it can be an end-of-file indicator */
503 if (std::all_of(th.name, last_of_th, [](auto c) { return c == 0; })) continue;
504
505 Debug(misc, 0, "The file '{}' isn't a valid tar-file", filename);
506 return false;
507 }
508
509 std::string name;
510
511 /* The prefix contains the directory-name */
512 if (th.prefix[0] != '\0') {
513 name = ExtractString(th.prefix);
514 name += PATHSEP;
515 }
516
517 /* Copy the name of the file in a safe way at the end of 'name' */
518 name += ExtractString(th.name);
519
520 /* The size of the file, for some strange reason, this is stored as a string in octals. */
521 std::string size = ExtractString(th.size);
522 size_t skip = 0;
523 if (!size.empty()) {
524 auto value = ParseInteger<size_t>(size, 8);
525 if (!value.has_value()) {
526 Debug(misc, 0, "The file '{}' has an invalid size for '{}'", filename, name);
527 fclose(f);
528 return false;
529 }
530 skip = *value;
531 }
532
533 switch (th.typeflag) {
534 case '\0':
535 case '0': { // regular file
536 if (name.empty()) break;
537
538 /* Store this entry in the list */
539 TarFileListEntry entry;
540 entry.tar_filename = filename;
541 entry.size = skip;
542 entry.position = pos;
543
544 /* Convert to lowercase and our PATHSEPCHAR */
545 SimplifyFileName(name);
546
547 Debug(misc, 6, "Found file in tar: {} ({} bytes, {} offset)", name, skip, pos);
548 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(filename_base + PATHSEPCHAR + name, entry)).second) num++;
549
550 break;
551 }
552
553 case '1': // hard links
554 case '2': { // symbolic links
555 std::string link = ExtractString(th.linkname);
556
557 Debug(misc, 5, "Ignoring link in tar: {} -> {}", name, link);
558 break;
559 }
560
561 case '5': // directory
562 /* Convert to lowercase and our PATHSEPCHAR */
563 SimplifyFileName(name);
564
565 /* Store the first directory name we detect */
566 Debug(misc, 6, "Found dir in tar: {}", name);
567 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = std::move(name);
568 break;
569
570 default:
571 /* Ignore other types */
572 break;
573 }
574
575 /* Skip to the next block.. */
576 skip = Align(skip, 512);
577 if (fseek(f, skip, SEEK_CUR) < 0) {
578 Debug(misc, 0, "The file '{}' can't be read as a valid tar-file", filename);
579 return false;
580 }
581 pos += skip;
582 }
583
584 Debug(misc, 4, "Found tar '{}' with {} new files", filename, num);
585
586 return true;
587}
588
596bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
597{
598 TarList::iterator it = _tar_list[subdir].find(tar_filename);
599 /* We don't know the file. */
600 if (it == _tar_list[subdir].end()) return false;
601
602 const auto &dirname = it->second;
603
604 /* The file doesn't have a sub directory! */
605 if (dirname.empty()) {
606 Debug(misc, 3, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
607 return false;
608 }
609
610 std::string filename = tar_filename;
611 auto p = filename.find_last_of(PATHSEPCHAR);
612 /* The file's path does not have a separator? */
613 if (p == std::string::npos) return false;
614
615 filename.replace(p + 1, std::string::npos, dirname);
616 Debug(misc, 8, "Extracting {} to directory {}", tar_filename, filename);
617 FioCreateDirectory(filename);
618
619 for (auto &it2 : _tar_filelist[subdir]) {
620 if (tar_filename != it2.second.tar_filename) continue;
621
622 /* it2.first is tarball + PATHSEPCHAR + name. */
623 std::string_view name = it2.first;
624 name.remove_prefix(name.find_first_of(PATHSEPCHAR) + 1);
625 filename.replace(p + 1, std::string::npos, name);
626
627 Debug(misc, 9, " extracting {}", filename);
628
629 /* First open the file in the .tar. */
630 size_t to_copy = 0;
631 auto in = FioFOpenFileTar(it2.second, &to_copy);
632 if (!in.has_value()) {
633 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, tar_filename);
634 return false;
635 }
636
637 /* Now open the 'output' file. */
638 auto out = FileHandle::Open(filename, "wb");
639 if (!out.has_value()) {
640 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, filename);
641 return false;
642 }
643
644 /* Now read from the tar and write it into the file. */
645 char buffer[4096];
646 size_t read;
647 for (; to_copy != 0; to_copy -= read) {
648 read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), *in);
649 if (read <= 0 || fwrite(buffer, 1, read, *out) != read) break;
650 }
651
652 if (to_copy != 0) {
653 Debug(misc, 6, "Extracting {} failed; still {} bytes to copy", filename, to_copy);
654 return false;
655 }
656 }
657
658 Debug(misc, 9, " extraction successful");
659 return true;
660}
661
662#if defined(_WIN32)
668extern void DetermineBasePaths(std::string_view exe);
669
671char *getcwd(char *buf, size_t size);
672#else /* defined(_WIN32) */
673
682static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
683{
684 std::string path{exe};
685
686#ifdef WITH_COCOA
687 for (size_t pos = path.find_first_of('.'); pos != std::string::npos; pos = path.find_first_of('.', pos + 1)) {
688 if (StrEqualsIgnoreCase(path.substr(pos, 4), ".app")) {
689 path.erase(pos);
690 break;
691 }
692 }
693#endif /* WITH_COCOA */
694
695 size_t pos = path.find_last_of(PATHSEPCHAR);
696 if (pos == std::string::npos) return false;
697
698 path.erase(pos);
699
700 if (chdir(path.c_str()) != 0) {
701 Debug(misc, 0, "Directory with the binary does not exist?");
702 return false;
703 }
704
705 return true;
706}
707
719{
720 /* No working directory, so nothing to do. */
721 if (_searchpaths[SP_WORKING_DIR].empty()) return false;
722
723 /* Working directory is root, so do nothing. */
724 if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
725
726 /* No personal/home directory, so the working directory won't be that. */
727 if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
728
729 std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
731
732 return _searchpaths[SP_PERSONAL_DIR] != tmp;
733}
734
740static std::string GetHomeDir()
741{
742#ifdef __HAIKU__
743 BPath path;
744 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
745 return std::string(path.Path());
746#else
747 auto home_env = GetEnv("HOME"); // Stack var, shouldn't be freed
748 if (home_env.has_value()) return std::string(*home_env);
749
750 const struct passwd *pw = getpwuid(getuid());
751 if (pw != nullptr) return std::string(pw->pw_dir);
752#endif
753 return {};
754}
755
760void DetermineBasePaths(std::string_view exe)
761{
762 std::string tmp;
763 const std::string homedir = GetHomeDir();
764#ifdef USE_XDG
765 if (auto xdg_data_home = GetEnv("XDG_DATA_HOME"); xdg_data_home.has_value()) {
766 tmp = *xdg_data_home;
767 tmp += PATHSEP;
768 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
770 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
771
772 tmp += "content_download";
775 } else if (!homedir.empty()) {
776 tmp = homedir;
777 tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
778 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
780 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
781
782 tmp += "content_download";
785 } else {
786 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
788 }
789#endif
790
791#if !defined(WITH_PERSONAL_DIR)
793#else
794 if (!homedir.empty()) {
795 tmp = std::move(homedir);
796 tmp += PATHSEP;
797 tmp += PERSONAL_DIR;
800
801 tmp += "content_download";
804 } else {
807 }
808#endif
809
810#if defined(WITH_SHARED_DIR)
811 tmp = SHARED_DIR;
814#else
816#endif
817
818 char cwd[MAX_PATH];
819 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
820
821 if (_config_file.empty()) {
822 /* Get the path to working directory of OpenTTD. */
823 tmp = cwd;
826
828 } else {
829 /* Use the folder of the config file as working directory. */
830 size_t end = _config_file.find_last_of(PATHSEPCHAR);
831 if (end == std::string::npos) {
832 /* _config_file is not in a folder, so use current directory. */
833 tmp = cwd;
834 } else {
835 tmp = FS2OTTD(std::filesystem::weakly_canonical(std::filesystem::path(OTTD2FS(_config_file))).parent_path().native());
836 }
839 }
840
841 /* Change the working directory to that one of the executable */
843 char buf[MAX_PATH];
844 if (getcwd(buf, lengthof(buf)) == nullptr) {
845 tmp.clear();
846 } else {
847 tmp = buf;
848 }
851 } else {
853 }
854
855 if (cwd[0] != '\0') {
856 /* Go back to the current working directory. */
857 if (chdir(cwd) != 0) {
858 Debug(misc, 0, "Failed to return to working directory!");
859 }
860 }
861
862#if !defined(GLOBAL_DATA_DIR)
864#else
865 tmp = GLOBAL_DATA_DIR;
867 _searchpaths[SP_INSTALLATION_DIR] = std::move(tmp);
868#endif
869#ifdef WITH_COCOA
870extern void CocoaSetApplicationBundleDir();
871 CocoaSetApplicationBundleDir();
872#else
874#endif
875}
876#endif /* defined(_WIN32) */
877
878std::string _personal_dir;
879
887void DeterminePaths(std::string_view exe, bool only_local_path)
888{
890 FillValidSearchPaths(only_local_path);
891
892#ifdef USE_XDG
893 std::string config_home;
894 std::string homedir = GetHomeDir();
895 if (auto xdg_config_home = GetEnv("XDG_CONFIG_HOME"); xdg_config_home.has_value()) {
896 config_home = *xdg_config_home;
897 config_home += PATHSEP;
898 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
899 } else if (!homedir.empty()) {
900 /* Defaults to ~/.config */
901 config_home = std::move(homedir);
902 config_home += PATHSEP ".config" PATHSEP;
903 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
904 }
905 AppendPathSeparator(config_home);
906#endif
907
908 for (Searchpath sp : _valid_searchpaths) {
909 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
910 Debug(misc, 3, "{} added as search path", _searchpaths[sp]);
911 }
912
913 std::string config_dir;
914 if (!_config_file.empty()) {
915 config_dir = _searchpaths[SP_WORKING_DIR];
916 } else {
917 std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
918 if (!personal_dir.empty()) {
919 auto end = personal_dir.find_last_of(PATHSEPCHAR);
920 if (end != std::string::npos) personal_dir.erase(end + 1);
921 config_dir = std::move(personal_dir);
922 } else {
923#ifdef USE_XDG
924 /* No previous configuration file found. Use the configuration folder from XDG. */
925 config_dir = config_home;
926#else
927 static const Searchpath new_openttd_cfg_order[] = {
929 };
930
931 config_dir.clear();
932 for (const auto &searchpath : new_openttd_cfg_order) {
933 if (IsValidSearchPath(searchpath)) {
934 config_dir = _searchpaths[searchpath];
935 break;
936 }
937 }
938#endif
939 }
940 _config_file = config_dir + "openttd.cfg";
941 }
942
943 Debug(misc, 1, "{} found as config directory", config_dir);
944
945 _highscore_file = config_dir + "hs.dat";
946 extern std::string _hotkeys_file;
947 _hotkeys_file = config_dir + "hotkeys.cfg";
948 extern std::string _windows_file;
949 _windows_file = config_dir + "windows.cfg";
950 extern std::string _private_file;
951 _private_file = config_dir + "private.cfg";
952 extern std::string _secrets_file;
953 _secrets_file = config_dir + "secrets.cfg";
954 extern std::string _favs_file;
955 _favs_file = config_dir + "favs.cfg";
956
957#ifdef USE_XDG
958 if (config_dir == config_home) {
959 /* We are using the XDG configuration home for the config file,
960 * then store the rest in the XDG data home folder. */
961 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
962 if (only_local_path) {
963 /* In case of XDG and we only want local paths and we detected that
964 * the user either manually indicated the XDG path or didn't use
965 * "-c" option, we change the working-dir to the XDG personal-dir,
966 * as this is most likely what the user is expecting. */
967 _searchpaths[SP_WORKING_DIR] = _searchpaths[SP_PERSONAL_DIR_XDG];
968 }
969 } else
970#endif
971 {
972 _personal_dir = config_dir;
973 }
974
975 /* Make the necessary folders */
976 FioCreateDirectory(config_dir);
977#if defined(WITH_PERSONAL_DIR)
979#endif
980
981 Debug(misc, 1, "{} found as personal directory", _personal_dir);
982
983 static const Subdirectory default_subdirs[] = {
985 };
986
987 for (const auto &default_subdir : default_subdirs) {
988 FioCreateDirectory(fmt::format("{}{}", _personal_dir, _subdirs[default_subdir]));
989 }
990
991 /* If we have network we make a directory for the autodownloading of content */
992 _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
993 Debug(misc, 3, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
995 FillValidSearchPaths(only_local_path);
996
997 /* Create the directory for each of the types of content */
999 for (const auto &subdir : subdirs) {
1000 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, subdir));
1001 }
1002
1003 extern std::string _log_file;
1004 _log_file = _personal_dir + "openttd.log";
1005}
1006
1011void SanitizeFilename(std::string &filename)
1012{
1013 for (auto &c : filename) {
1014 switch (c) {
1015 /* The following characters are not allowed in filenames
1016 * on at least one of the supported operating systems: */
1017 case ':': case '\\': case '*': case '?': case '/':
1018 case '<': case '>': case '|': case '"':
1019 c = '_';
1020 break;
1021 }
1022 }
1023}
1024
1033std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1034{
1035 auto in = FileHandle::Open(filename, "rb");
1036 if (!in.has_value()) return nullptr;
1037
1038 fseek(*in, 0, SEEK_END);
1039 size_t len = ftell(*in);
1040 fseek(*in, 0, SEEK_SET);
1041 if (len > maxsize) return nullptr;
1042
1043 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1044
1045 mem.get()[len] = 0;
1046 if (fread(mem.get(), len, 1, *in) != 1) return nullptr;
1047
1048 lenp = len;
1049 return mem;
1050}
1051
1058static bool MatchesExtension(std::string_view extension, const std::string &filename)
1059{
1060 if (extension.empty()) return true;
1061 if (filename.length() < extension.length()) return false;
1062
1063 std::string_view filename_sv = filename; // String view to avoid making another copy of the substring.
1064 return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1065}
1066
1077static uint ScanPath(FileScanner *fs, std::string_view extension, const std::filesystem::path &path, size_t basepath_length, bool recursive)
1078{
1079 uint num = 0;
1080
1081 std::error_code error_code;
1082 for (const auto &dir_entry : std::filesystem::directory_iterator(path, error_code)) {
1083 if (dir_entry.is_directory()) {
1084 if (!recursive) continue;
1085 num += ScanPath(fs, extension, dir_entry.path(), basepath_length, recursive);
1086 } else if (dir_entry.is_regular_file()) {
1087 std::string file = FS2OTTD(dir_entry.path().native());
1088 if (!MatchesExtension(extension, file)) continue;
1089 if (fs->AddFile(file, basepath_length, {})) num++;
1090 }
1091 }
1092 if (error_code) {
1093 Debug(misc, 9, "Unable to read directory {}: {}", path.string(), error_code.message());
1094 }
1095
1096 return num;
1097}
1098
1106static uint ScanTar(FileScanner *fs, std::string_view extension, const TarFileList::value_type &tar)
1107{
1108 uint num = 0;
1109
1110 if (MatchesExtension(extension, tar.first) && fs->AddFile(tar.first, 0, tar.second.tar_filename)) num++;
1111
1112 return num;
1113}
1114
1124uint FileScanner::Scan(std::string_view extension, Subdirectory sd, bool tars, bool recursive)
1125{
1126 this->subdir = sd;
1127
1128 uint num = 0;
1129
1130 for (Searchpath sp : _valid_searchpaths) {
1131 /* Don't search in the working directory */
1132 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1133
1134 std::string path = FioGetDirectory(sp, sd);
1135 num += ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1136 }
1137
1138 if (tars && sd != NO_DIRECTORY) {
1139 for (const auto &tar : _tar_filelist[sd]) {
1140 num += ScanTar(this, extension, tar);
1141 }
1142 }
1143
1144 switch (sd) {
1145 case BASESET_DIR:
1146 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1147 [[fallthrough]];
1148 case NEWGRF_DIR:
1149 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1150 break;
1151
1152 default: break;
1153 }
1154
1155 return num;
1156}
1157
1166uint FileScanner::Scan(std::string_view extension, const std::string &directory, bool recursive)
1167{
1168 std::string path(directory);
1169 AppendPathSeparator(path);
1170 return ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1171}
1172
1173std::optional<FileHandle> FileHandle::Open(const std::string &filename, std::string_view mode)
1174{
1175#if defined(_WIN32)
1176 /* Windows also requires mode to be wchar_t. */
1177 auto f = _wfopen(OTTD2FS(filename).c_str(), OTTD2FS(mode).c_str());
1178#else
1179 auto f = fopen(filename.c_str(), std::string{mode}.c_str());
1180#endif /* _WIN32 */
1181
1182 if (f == nullptr) return std::nullopt;
1183 return FileHandle(f);
1184}
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1173
Helper for scanning for files with a given name.
Definition fileio_func.h:37
uint Scan(std::string_view extension, Subdirectory sd, bool tars=true, bool recursive=true)
Scan for files with the given extension in the given search path.
Definition fileio.cpp:1124
virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)=0
Add a file with the given filename.
Helper for scanning for files with tar as extension.
Definition fileio_func.h:59
@ AI
Scan for AIs and its libraries.
Definition fileio_func.h:66
@ Scenario
Scan for scenarios and heightmaps.
Definition fileio_func.h:67
@ Game
Scan for game scripts.
Definition fileio_func.h:68
@ Baseset
Scan for base sets.
Definition fileio_func.h:64
@ NewGRF
Scan for non-base sets.
Definition fileio_func.h:65
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition fileio.cpp:378
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename={}) override
Add a file with the given filename.
Definition fileio.cpp:446
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
std::string _log_file
Filename to reroute output of a forked OpenTTD to.
Definition dedicated.cpp:14
static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
Changes the working directory to the path of the give executable.
Definition fileio.cpp:682
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
Extract the tar with the given filename in the directory where the tar resides.
Definition fileio.cpp:596
void DeterminePaths(std::string_view exe, bool only_local_path)
Acquire the base paths (personal dir and game data dir), fill all other paths (save dir,...
Definition fileio.cpp:887
static bool IsValidSearchPath(Searchpath sp)
Checks whether the given search path is a valid search path.
Definition fileio.cpp:75
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:329
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
Definition fileio.cpp:1011
static uint ScanPath(FileScanner *fs, std::string_view extension, const std::filesystem::path &path, size_t basepath_length, bool recursive)
Scan a single directory (and recursively its children) and add any graphics sets that are found.
Definition fileio.cpp:1077
void DetermineBasePaths(std::string_view exe)
Determine the base (personal dir and game data dir) paths.
Definition fileio.cpp:760
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:349
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:244
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition fileio.cpp:878
static bool _do_scan_working_directory
Whether the working directory should be scanned.
Definition fileio.cpp:33
static std::string GetHomeDir()
Gets the home directory of the user.
Definition fileio.cpp:740
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
Definition fileio.cpp:361
static bool MatchesExtension(std::string_view extension, const std::string &filename)
Helper to see whether a given filename matches the extension.
Definition fileio.cpp:1058
void FioCreateDirectory(const std::string &name)
Create a directory with the given name If the parent directory does not exist, it will try to create ...
Definition fileio.cpp:317
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
static uint ScanTar(FileScanner *fs, std::string_view extension, const TarFileList::value_type &tar)
Scan the given tar and add graphics sets when it finds one.
Definition fileio.cpp:1106
std::array< std::string, NUM_SEARCHPATHS > _searchpaths
The search paths OpenTTD could search through.
Definition fileio.cpp:65
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
static std::optional< FileHandle > FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
Opens a file from inside a tar archive.
Definition fileio.cpp:223
static std::string ExtractString(std::span< char > buffer)
Helper to extract a string for the tar header.
Definition fileio.cpp:441
std::string _highscore_file
The file to store the highscore data in.
Definition highscore.cpp:25
bool DoScanWorkingDirectory()
Whether we should scan the working directory.
Definition fileio.cpp:718
std::string _config_file
Configuration file of OpenTTD.
Definition settings.cpp:64
std::string FioFindFullPath(Subdirectory subdir, std::string_view filename)
Find a path to the filename in one of the search directories.
Definition fileio.cpp:144
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
Definition fileio.cpp:1033
Functions for standard in/out file operations.
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:349
Searchpath
Types of searchpaths OpenTTD might use.
@ SP_SHARED_DIR
Search in the shared directory, like 'Shared Files' under Windows.
@ SP_INSTALLATION_DIR
Search in the installation directory.
@ SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
Search within the autodownload directory located in the personal directory (XDG variant).
@ SP_BINARY_DIR
Search in the directory where the binary resides.
@ SP_AUTODOWNLOAD_PERSONAL_DIR
Search within the autodownload directory located in the personal directory.
@ SP_PERSONAL_DIR
Search in the personal directory.
@ SP_WORKING_DIR
Search in the working directory.
@ SP_APPLICATION_BUNDLE_DIR
Search within the application bundle.
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ OLD_DATA_DIR
Old subdirectory for the data.
Definition fileio_type.h:95
@ NO_DIRECTORY
A path without any base directory.
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
@ SOCIAL_INTEGRATION_DIR
Subdirectory for all social integration plugins.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
Definition fileio_type.h:99
@ OLD_GM_DIR
Old subdirectory for the music.
Definition fileio_type.h:94
@ SCENARIO_DIR
Base directory for all scenarios.
Definition fileio_type.h:92
@ BASE_DIR
Base directory for all subdirectories.
Definition fileio_type.h:89
@ SAVE_DIR
Base directory for all savegames.
Definition fileio_type.h:90
@ NUM_SUBDIRS
Number of subdirectories.
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
Definition fileio_type.h:93
@ NEWGRF_DIR
Subdirectory for all NewGRFs.
Definition fileio_type.h:97
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
Definition fileio_type.h:91
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game).
Definition fileio_type.h:96
@ GAME_DIR
Subdirectory for all game scripts.
Declarations for savegames operations.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
A number of safeguards to prevent using unsafe methods.
std::string _secrets_file
Secrets configuration file of OpenTTD.
Definition settings.cpp:66
std::string _favs_file
Picker favourites configuration file of OpenTTD.
Definition settings.cpp:67
std::string _private_file
Private configuration file of OpenTTD.
Definition settings.cpp:65
Base for loading sprites.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
std::optional< std::string_view > GetEnv(const char *variable)
Get the environment variable using std::getenv and when it is an empty string (or nullptr),...
Definition string.cpp:854
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:323
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
int StrCompareIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
Definition string.cpp:310
Parse strings.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
Functions related to low-level strings.
Structs, typedefs and macros used for TAR file handling.
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
Definition win32.cpp:356
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:340
std::string _windows_file
Config file to store WindowDesc.
Definition window.cpp:105