OpenTTD Source 20260206-master-g4d4e37dbf1
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
350void AppendPathSeparator(std::string &buf)
351{
352 if (buf.empty()) return;
353
354 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
355}
356
362static void SimplifyFileName(std::string &name)
363{
364 for (char &c : name) {
365 /* Force lowercase */
366 c = std::tolower(c);
367#if (PATHSEPCHAR != '/')
368 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
369 if (c == '/') c = PATHSEPCHAR;
370#endif
371 }
372}
373
380{
381 _tar_filelist[sd].clear();
382 _tar_list[sd].clear();
383 uint num = this->Scan(".tar", sd, false);
384 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
385 return num;
386}
387
388/* static */ uint TarScanner::DoScan(TarScanner::Modes modes)
389{
390 Debug(misc, 2, "Scanning for tars");
391 TarScanner fs;
392 uint num = 0;
393 if (modes.Test(TarScanner::Mode::Baseset)) {
394 num += fs.DoScan(BASESET_DIR);
395 }
396 if (modes.Test(TarScanner::Mode::NewGRF)) {
397 num += fs.DoScan(NEWGRF_DIR);
398 }
399 if (modes.Test(TarScanner::Mode::AI)) {
400 num += fs.DoScan(AI_DIR);
401 num += fs.DoScan(AI_LIBRARY_DIR);
402 }
403 if (modes.Test(TarScanner::Mode::Game)) {
404 num += fs.DoScan(GAME_DIR);
405 num += fs.DoScan(GAME_LIBRARY_DIR);
406 }
407 if (modes.Test(TarScanner::Mode::Scenario)) {
408 num += fs.DoScan(SCENARIO_DIR);
409 num += fs.DoScan(HEIGHTMAP_DIR);
410 }
411 Debug(misc, 2, "Scan complete, found {} files", num);
412 return num;
413}
414
421bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
422{
423 this->subdir = sd;
424 return this->AddFile(filename, 0);
425}
426
437static std::string ExtractString(std::span<char> buffer)
438{
439 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
440}
441
442bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] const std::string &tar_filename)
443{
444 /* No tar within tar. */
445 assert(tar_filename.empty());
446
447 /* The TAR-header, repeated for every file */
448 struct TarHeader {
449 char name[100];
450 char mode[8];
451 char uid[8];
452 char gid[8];
453 char size[12];
454 char mtime[12];
455 char chksum[8];
456 char typeflag;
457 char linkname[100];
458 char magic[6];
459 char version[2];
460 char uname[32];
461 char gname[32];
462 char devmajor[8];
463 char devminor[8];
464 char prefix[155];
465
466 char unused[12];
467 };
468 static_assert(sizeof(TarHeader) == 512);
469
470 /* Check if we already seen this file */
471 TarList::iterator it = _tar_list[this->subdir].find(filename);
472 if (it != _tar_list[this->subdir].end()) return false;
473
474 auto of = FileHandle::Open(filename, "rb");
475 /* Although the file has been found there can be
476 * a number of reasons we cannot open the file.
477 * Most common case is when we simply have not
478 * been given read access. */
479 if (!of.has_value()) return false;
480 auto &f = *of;
481
482 _tar_list[this->subdir][filename] = std::string{};
483
484 std::string filename_base = FS2OTTD(std::filesystem::path(OTTD2FS(filename)).filename().native());
485 SimplifyFileName(filename_base);
486
487 TarHeader th;
488 size_t num = 0, pos = 0;
489
490 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
491 size_t num_bytes_read = fread(&th, 1, sizeof(TarHeader), f);
492 if (num_bytes_read != sizeof(TarHeader)) break;
493 pos += num_bytes_read;
494
495 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
496 auto last_of_th = &th.unused[std::size(th.unused)];
497 if (std::string_view{th.magic, 5} != "ustar" && std::any_of(th.magic, last_of_th, [](auto c) { return c != 0; })) {
498 /* If we have only zeros in the block, it can be an end-of-file indicator */
499 if (std::all_of(th.name, last_of_th, [](auto c) { return c == 0; })) continue;
500
501 Debug(misc, 0, "The file '{}' isn't a valid tar-file", filename);
502 return false;
503 }
504
505 std::string name;
506
507 /* The prefix contains the directory-name */
508 if (th.prefix[0] != '\0') {
509 name = ExtractString(th.prefix);
510 name += PATHSEP;
511 }
512
513 /* Copy the name of the file in a safe way at the end of 'name' */
514 name += ExtractString(th.name);
515
516 /* The size of the file, for some strange reason, this is stored as a string in octals. */
517 std::string size = ExtractString(th.size);
518 size_t skip = 0;
519 if (!size.empty()) {
520 auto value = ParseInteger<size_t>(size, 8);
521 if (!value.has_value()) {
522 Debug(misc, 0, "The file '{}' has an invalid size for '{}'", filename, name);
523 fclose(f);
524 return false;
525 }
526 skip = *value;
527 }
528
529 switch (th.typeflag) {
530 case '\0':
531 case '0': { // regular file
532 if (name.empty()) break;
533
534 /* Store this entry in the list */
535 TarFileListEntry entry;
536 entry.tar_filename = filename;
537 entry.size = skip;
538 entry.position = pos;
539
540 /* Convert to lowercase and our PATHSEPCHAR */
541 SimplifyFileName(name);
542
543 Debug(misc, 6, "Found file in tar: {} ({} bytes, {} offset)", name, skip, pos);
544 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(filename_base + PATHSEPCHAR + name, entry)).second) num++;
545
546 break;
547 }
548
549 case '1': // hard links
550 case '2': { // symbolic links
551 std::string link = ExtractString(th.linkname);
552
553 Debug(misc, 5, "Ignoring link in tar: {} -> {}", name, link);
554 break;
555 }
556
557 case '5': // directory
558 /* Convert to lowercase and our PATHSEPCHAR */
559 SimplifyFileName(name);
560
561 /* Store the first directory name we detect */
562 Debug(misc, 6, "Found dir in tar: {}", name);
563 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = std::move(name);
564 break;
565
566 default:
567 /* Ignore other types */
568 break;
569 }
570
571 /* Skip to the next block.. */
572 skip = Align(skip, 512);
573 if (fseek(f, skip, SEEK_CUR) < 0) {
574 Debug(misc, 0, "The file '{}' can't be read as a valid tar-file", filename);
575 return false;
576 }
577 pos += skip;
578 }
579
580 Debug(misc, 4, "Found tar '{}' with {} new files", filename, num);
581
582 return true;
583}
584
592bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
593{
594 TarList::iterator it = _tar_list[subdir].find(tar_filename);
595 /* We don't know the file. */
596 if (it == _tar_list[subdir].end()) return false;
597
598 const auto &dirname = it->second;
599
600 /* The file doesn't have a sub directory! */
601 if (dirname.empty()) {
602 Debug(misc, 3, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
603 return false;
604 }
605
606 std::string filename = tar_filename;
607 auto p = filename.find_last_of(PATHSEPCHAR);
608 /* The file's path does not have a separator? */
609 if (p == std::string::npos) return false;
610
611 filename.replace(p + 1, std::string::npos, dirname);
612 Debug(misc, 8, "Extracting {} to directory {}", tar_filename, filename);
613 FioCreateDirectory(filename);
614
615 for (auto &it2 : _tar_filelist[subdir]) {
616 if (tar_filename != it2.second.tar_filename) continue;
617
618 /* it2.first is tarball + PATHSEPCHAR + name. */
619 std::string_view name = it2.first;
620 name.remove_prefix(name.find_first_of(PATHSEPCHAR) + 1);
621 filename.replace(p + 1, std::string::npos, name);
622
623 Debug(misc, 9, " extracting {}", filename);
624
625 /* First open the file in the .tar. */
626 size_t to_copy = 0;
627 auto in = FioFOpenFileTar(it2.second, &to_copy);
628 if (!in.has_value()) {
629 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, tar_filename);
630 return false;
631 }
632
633 /* Now open the 'output' file. */
634 auto out = FileHandle::Open(filename, "wb");
635 if (!out.has_value()) {
636 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, filename);
637 return false;
638 }
639
640 /* Now read from the tar and write it into the file. */
641 char buffer[4096];
642 size_t read;
643 for (; to_copy != 0; to_copy -= read) {
644 read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), *in);
645 if (read <= 0 || fwrite(buffer, 1, read, *out) != read) break;
646 }
647
648 if (to_copy != 0) {
649 Debug(misc, 6, "Extracting {} failed; still {} bytes to copy", filename, to_copy);
650 return false;
651 }
652 }
653
654 Debug(misc, 9, " extraction successful");
655 return true;
656}
657
658#if defined(_WIN32)
664extern void DetermineBasePaths(std::string_view exe);
665
667char *getcwd(char *buf, size_t size);
668#else /* defined(_WIN32) */
669
677static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
678{
679 std::string path{exe};
680
681#ifdef WITH_COCOA
682 for (size_t pos = path.find_first_of('.'); pos != std::string::npos; pos = path.find_first_of('.', pos + 1)) {
683 if (StrEqualsIgnoreCase(path.substr(pos, 4), ".app")) {
684 path.erase(pos);
685 break;
686 }
687 }
688#endif /* WITH_COCOA */
689
690 size_t pos = path.find_last_of(PATHSEPCHAR);
691 if (pos == std::string::npos) return false;
692
693 path.erase(pos);
694
695 if (chdir(path.c_str()) != 0) {
696 Debug(misc, 0, "Directory with the binary does not exist?");
697 return false;
698 }
699
700 return true;
701}
702
714{
715 /* No working directory, so nothing to do. */
716 if (_searchpaths[SP_WORKING_DIR].empty()) return false;
717
718 /* Working directory is root, so do nothing. */
719 if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
720
721 /* No personal/home directory, so the working directory won't be that. */
722 if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
723
724 std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
726
727 return _searchpaths[SP_PERSONAL_DIR] != tmp;
728}
729
735static std::string GetHomeDir()
736{
737#ifdef __HAIKU__
738 BPath path;
739 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
740 return std::string(path.Path());
741#else
742 auto home_env = GetEnv("HOME"); // Stack var, shouldn't be freed
743 if (home_env.has_value()) return std::string(*home_env);
744
745 const struct passwd *pw = getpwuid(getuid());
746 if (pw != nullptr) return std::string(pw->pw_dir);
747#endif
748 return {};
749}
750
755void DetermineBasePaths(std::string_view exe)
756{
757 std::string tmp;
758 const std::string homedir = GetHomeDir();
759#ifdef USE_XDG
760 if (auto xdg_data_home = GetEnv("XDG_DATA_HOME"); xdg_data_home.has_value()) {
761 tmp = *xdg_data_home;
762 tmp += PATHSEP;
763 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
765 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
766
767 tmp += "content_download";
770 } else if (!homedir.empty()) {
771 tmp = homedir;
772 tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
773 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
775 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
776
777 tmp += "content_download";
780 } else {
781 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
783 }
784#endif
785
786#if !defined(WITH_PERSONAL_DIR)
788#else
789 if (!homedir.empty()) {
790 tmp = std::move(homedir);
791 tmp += PATHSEP;
792 tmp += PERSONAL_DIR;
795
796 tmp += "content_download";
799 } else {
802 }
803#endif
804
805#if defined(WITH_SHARED_DIR)
806 tmp = SHARED_DIR;
809#else
811#endif
812
813 char cwd[MAX_PATH];
814 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
815
816 if (_config_file.empty()) {
817 /* Get the path to working directory of OpenTTD. */
818 tmp = cwd;
821
823 } else {
824 /* Use the folder of the config file as working directory. */
825 size_t end = _config_file.find_last_of(PATHSEPCHAR);
826 if (end == std::string::npos) {
827 /* _config_file is not in a folder, so use current directory. */
828 tmp = cwd;
829 } else {
830 tmp = FS2OTTD(std::filesystem::weakly_canonical(std::filesystem::path(OTTD2FS(_config_file))).parent_path().native());
831 }
834 }
835
836 /* Change the working directory to that one of the executable */
838 char buf[MAX_PATH];
839 if (getcwd(buf, lengthof(buf)) == nullptr) {
840 tmp.clear();
841 } else {
842 tmp = buf;
843 }
846 } else {
848 }
849
850 if (cwd[0] != '\0') {
851 /* Go back to the current working directory. */
852 if (chdir(cwd) != 0) {
853 Debug(misc, 0, "Failed to return to working directory!");
854 }
855 }
856
857#if !defined(GLOBAL_DATA_DIR)
859#else
860 tmp = GLOBAL_DATA_DIR;
862 _searchpaths[SP_INSTALLATION_DIR] = std::move(tmp);
863#endif
864#ifdef WITH_COCOA
865extern void CocoaSetApplicationBundleDir();
866 CocoaSetApplicationBundleDir();
867#else
869#endif
870}
871#endif /* defined(_WIN32) */
872
873std::string _personal_dir;
874
882void DeterminePaths(std::string_view exe, bool only_local_path)
883{
885 FillValidSearchPaths(only_local_path);
886
887#ifdef USE_XDG
888 std::string config_home;
889 std::string homedir = GetHomeDir();
890 if (auto xdg_config_home = GetEnv("XDG_CONFIG_HOME"); xdg_config_home.has_value()) {
891 config_home = *xdg_config_home;
892 config_home += PATHSEP;
893 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
894 } else if (!homedir.empty()) {
895 /* Defaults to ~/.config */
896 config_home = std::move(homedir);
897 config_home += PATHSEP ".config" PATHSEP;
898 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
899 }
900 AppendPathSeparator(config_home);
901#endif
902
903 for (Searchpath sp : _valid_searchpaths) {
904 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
905 Debug(misc, 3, "{} added as search path", _searchpaths[sp]);
906 }
907
908 std::string config_dir;
909 if (!_config_file.empty()) {
910 config_dir = _searchpaths[SP_WORKING_DIR];
911 } else {
912 std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
913 if (!personal_dir.empty()) {
914 auto end = personal_dir.find_last_of(PATHSEPCHAR);
915 if (end != std::string::npos) personal_dir.erase(end + 1);
916 config_dir = std::move(personal_dir);
917 } else {
918#ifdef USE_XDG
919 /* No previous configuration file found. Use the configuration folder from XDG. */
920 config_dir = config_home;
921#else
922 static const Searchpath new_openttd_cfg_order[] = {
924 };
925
926 config_dir.clear();
927 for (const auto &searchpath : new_openttd_cfg_order) {
928 if (IsValidSearchPath(searchpath)) {
929 config_dir = _searchpaths[searchpath];
930 break;
931 }
932 }
933#endif
934 }
935 _config_file = config_dir + "openttd.cfg";
936 }
937
938 Debug(misc, 1, "{} found as config directory", config_dir);
939
940 _highscore_file = config_dir + "hs.dat";
941 extern std::string _hotkeys_file;
942 _hotkeys_file = config_dir + "hotkeys.cfg";
943 extern std::string _windows_file;
944 _windows_file = config_dir + "windows.cfg";
945 extern std::string _private_file;
946 _private_file = config_dir + "private.cfg";
947 extern std::string _secrets_file;
948 _secrets_file = config_dir + "secrets.cfg";
949 extern std::string _favs_file;
950 _favs_file = config_dir + "favs.cfg";
951
952#ifdef USE_XDG
953 if (config_dir == config_home) {
954 /* We are using the XDG configuration home for the config file,
955 * then store the rest in the XDG data home folder. */
956 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
957 if (only_local_path) {
958 /* In case of XDG and we only want local paths and we detected that
959 * the user either manually indicated the XDG path or didn't use
960 * "-c" option, we change the working-dir to the XDG personal-dir,
961 * as this is most likely what the user is expecting. */
962 _searchpaths[SP_WORKING_DIR] = _searchpaths[SP_PERSONAL_DIR_XDG];
963 }
964 } else
965#endif
966 {
967 _personal_dir = config_dir;
968 }
969
970 /* Make the necessary folders */
971 FioCreateDirectory(config_dir);
972#if defined(WITH_PERSONAL_DIR)
974#endif
975
976 Debug(misc, 1, "{} found as personal directory", _personal_dir);
977
978 static const Subdirectory default_subdirs[] = {
980 };
981
982 for (const auto &default_subdir : default_subdirs) {
983 FioCreateDirectory(fmt::format("{}{}", _personal_dir, _subdirs[default_subdir]));
984 }
985
986 /* If we have network we make a directory for the autodownloading of content */
987 _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
988 Debug(misc, 3, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
990 FillValidSearchPaths(only_local_path);
991
992 /* Create the directory for each of the types of content */
994 for (const auto &subdir : subdirs) {
995 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, subdir));
996 }
997
998 extern std::string _log_file;
999 _log_file = _personal_dir + "openttd.log";
1000}
1001
1006void SanitizeFilename(std::string &filename)
1007{
1008 for (auto &c : filename) {
1009 switch (c) {
1010 /* The following characters are not allowed in filenames
1011 * on at least one of the supported operating systems: */
1012 case ':': case '\\': case '*': case '?': case '/':
1013 case '<': case '>': case '|': case '"':
1014 c = '_';
1015 break;
1016 }
1017 }
1018}
1019
1028std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1029{
1030 auto in = FileHandle::Open(filename, "rb");
1031 if (!in.has_value()) return nullptr;
1032
1033 fseek(*in, 0, SEEK_END);
1034 size_t len = ftell(*in);
1035 fseek(*in, 0, SEEK_SET);
1036 if (len > maxsize) return nullptr;
1037
1038 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1039
1040 mem.get()[len] = 0;
1041 if (fread(mem.get(), len, 1, *in) != 1) return nullptr;
1042
1043 lenp = len;
1044 return mem;
1045}
1046
1053static bool MatchesExtension(std::string_view extension, const std::string &filename)
1054{
1055 if (extension.empty()) return true;
1056 if (filename.length() < extension.length()) return false;
1057
1058 std::string_view filename_sv = filename; // String view to avoid making another copy of the substring.
1059 return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1060}
1061
1071static uint ScanPath(FileScanner *fs, std::string_view extension, const std::filesystem::path &path, size_t basepath_length, bool recursive)
1072{
1073 uint num = 0;
1074
1075 std::error_code error_code;
1076 for (const auto &dir_entry : std::filesystem::directory_iterator(path, error_code)) {
1077 if (dir_entry.is_directory()) {
1078 if (!recursive) continue;
1079 num += ScanPath(fs, extension, dir_entry.path(), basepath_length, recursive);
1080 } else if (dir_entry.is_regular_file()) {
1081 std::string file = FS2OTTD(dir_entry.path().native());
1082 if (!MatchesExtension(extension, file)) continue;
1083 if (fs->AddFile(file, basepath_length, {})) num++;
1084 }
1085 }
1086 if (error_code) {
1087 Debug(misc, 9, "Unable to read directory {}: {}", path.string(), error_code.message());
1088 }
1089
1090 return num;
1091}
1092
1099static uint ScanTar(FileScanner *fs, std::string_view extension, const TarFileList::value_type &tar)
1100{
1101 uint num = 0;
1102
1103 if (MatchesExtension(extension, tar.first) && fs->AddFile(tar.first, 0, tar.second.tar_filename)) num++;
1104
1105 return num;
1106}
1107
1117uint FileScanner::Scan(std::string_view extension, Subdirectory sd, bool tars, bool recursive)
1118{
1119 this->subdir = sd;
1120
1121 uint num = 0;
1122
1123 for (Searchpath sp : _valid_searchpaths) {
1124 /* Don't search in the working directory */
1125 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1126
1127 std::string path = FioGetDirectory(sp, sd);
1128 num += ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1129 }
1130
1131 if (tars && sd != NO_DIRECTORY) {
1132 for (const auto &tar : _tar_filelist[sd]) {
1133 num += ScanTar(this, extension, tar);
1134 }
1135 }
1136
1137 switch (sd) {
1138 case BASESET_DIR:
1139 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1140 [[fallthrough]];
1141 case NEWGRF_DIR:
1142 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1143 break;
1144
1145 default: break;
1146 }
1147
1148 return num;
1149}
1150
1159uint FileScanner::Scan(std::string_view extension, const std::string &directory, bool recursive)
1160{
1161 std::string path(directory);
1162 AppendPathSeparator(path);
1163 return ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1164}
1165
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:1117
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:379
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:442
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:677
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:592
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:882
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:1006
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:1071
void DetermineBasePaths(std::string_view exe)
Determine the base (personal dir and game data dir) paths.
Definition fileio.cpp:755
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:350
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:873
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:735
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
Definition fileio.cpp:362
static bool MatchesExtension(std::string_view extension, const std::string &filename)
Helper to see whether a given filename matches the extension.
Definition fileio.cpp:1053
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:1099
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:437
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:713
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:1028
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:350
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