OpenTTD Source 20251005-master-ga617d009cc
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 <http://www.gnu.org/licenses/>.
6 */
7
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
242std::optional<FileHandle> FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
243{
244 std::optional<FileHandle> f = std::nullopt;
245 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
246
247 for (Searchpath sp : _valid_searchpaths) {
248 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
249 if (f.has_value() || subdir == NO_DIRECTORY) break;
250 }
251
252 /* We can only use .tar in case of data-dir, and read-mode */
253 if (!f.has_value() && mode[0] == 'r' && subdir != NO_DIRECTORY) {
254 /* Filenames in tars are always forced to be lowercase */
255 std::string resolved_name{filename};
256 strtolower(resolved_name);
257
258 /* Resolve ".." */
259 std::istringstream ss(resolved_name);
260 std::vector<std::string> tokens;
261 for (std::string token; std::getline(ss, token, PATHSEPCHAR); /* nothing */) {
262 if (token == "..") {
263 if (tokens.size() < 2) return std::nullopt;
264 tokens.pop_back();
265 } else if (token == ".") {
266 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
267 * This confuses our file resolver. So, act like this folder doesn't exist. */
268 } else {
269 tokens.push_back(token);
270 }
271 }
272
273 resolved_name.clear();
274 bool first = true;
275 for (const std::string &token : tokens) {
276 if (!first) {
277 resolved_name += PATHSEP;
278 }
279 resolved_name += token;
280 first = false;
281 }
282
283 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
284 if (it != _tar_filelist[subdir].end()) {
285 f = FioFOpenFileTar(it->second, filesize);
286 }
287 }
288
289 /* Sometimes a full path is given. To support
290 * the 'subdirectory' must be 'removed'. */
291 if (!f.has_value() && subdir != NO_DIRECTORY) {
292 switch (subdir) {
293 case BASESET_DIR:
294 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
295 if (f.has_value()) break;
296 [[fallthrough]];
297 case NEWGRF_DIR:
298 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
299 break;
300
301 default:
302 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
303 break;
304 }
305 }
306
307 return f;
308}
309
315void FioCreateDirectory(const std::string &name)
316{
317 /* Ignore directory creation errors; they'll surface later on. */
318 std::error_code error_code;
319 std::filesystem::create_directories(OTTD2FS(name), error_code);
320}
321
327bool FioRemove(const std::string &filename)
328{
329 std::filesystem::path path = OTTD2FS(filename);
330 std::error_code error_code;
331 if (!std::filesystem::remove(path, error_code)) {
332 if (error_code) {
333 Debug(misc, 0, "Removing {} failed: {}", filename, error_code.message());
334 } else {
335 Debug(misc, 0, "Removing {} failed: file does not exist", filename);
336 }
337 return false;
338 }
339 return true;
340}
341
348void AppendPathSeparator(std::string &buf)
349{
350 if (buf.empty()) return;
351
352 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
353}
354
360static void SimplifyFileName(std::string &name)
361{
362 for (char &c : name) {
363 /* Force lowercase */
364 c = std::tolower(c);
365#if (PATHSEPCHAR != '/')
366 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
367 if (c == '/') c = PATHSEPCHAR;
368#endif
369 }
370}
371
378{
379 _tar_filelist[sd].clear();
380 _tar_list[sd].clear();
381 uint num = this->Scan(".tar", sd, false);
382 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
383 return num;
384}
385
386/* static */ uint TarScanner::DoScan(TarScanner::Modes modes)
387{
388 Debug(misc, 2, "Scanning for tars");
389 TarScanner fs;
390 uint num = 0;
391 if (modes.Test(TarScanner::Mode::Baseset)) {
392 num += fs.DoScan(BASESET_DIR);
393 }
394 if (modes.Test(TarScanner::Mode::NewGRF)) {
395 num += fs.DoScan(NEWGRF_DIR);
396 }
397 if (modes.Test(TarScanner::Mode::AI)) {
398 num += fs.DoScan(AI_DIR);
399 num += fs.DoScan(AI_LIBRARY_DIR);
400 }
401 if (modes.Test(TarScanner::Mode::Game)) {
402 num += fs.DoScan(GAME_DIR);
403 num += fs.DoScan(GAME_LIBRARY_DIR);
404 }
405 if (modes.Test(TarScanner::Mode::Scenario)) {
406 num += fs.DoScan(SCENARIO_DIR);
407 num += fs.DoScan(HEIGHTMAP_DIR);
408 }
409 Debug(misc, 2, "Scan complete, found {} files", num);
410 return num;
411}
412
419bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
420{
421 this->subdir = sd;
422 return this->AddFile(filename, 0);
423}
424
435static std::string ExtractString(std::span<char> buffer)
436{
437 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
438}
439
440bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] const std::string &tar_filename)
441{
442 /* No tar within tar. */
443 assert(tar_filename.empty());
444
445 /* The TAR-header, repeated for every file */
446 struct TarHeader {
447 char name[100];
448 char mode[8];
449 char uid[8];
450 char gid[8];
451 char size[12];
452 char mtime[12];
453 char chksum[8];
454 char typeflag;
455 char linkname[100];
456 char magic[6];
457 char version[2];
458 char uname[32];
459 char gname[32];
460 char devmajor[8];
461 char devminor[8];
462 char prefix[155];
463
464 char unused[12];
465 };
466 static_assert(sizeof(TarHeader) == 512);
467
468 /* Check if we already seen this file */
469 TarList::iterator it = _tar_list[this->subdir].find(filename);
470 if (it != _tar_list[this->subdir].end()) return false;
471
472 auto of = FileHandle::Open(filename, "rb");
473 /* Although the file has been found there can be
474 * a number of reasons we cannot open the file.
475 * Most common case is when we simply have not
476 * been given read access. */
477 if (!of.has_value()) return false;
478 auto &f = *of;
479
480 _tar_list[this->subdir][filename] = std::string{};
481
482 std::string filename_base = FS2OTTD(std::filesystem::path(OTTD2FS(filename)).filename().native());
483 SimplifyFileName(filename_base);
484
485 TarHeader th;
486 size_t num = 0, pos = 0;
487
488 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
489 size_t num_bytes_read = fread(&th, 1, sizeof(TarHeader), f);
490 if (num_bytes_read != sizeof(TarHeader)) break;
491 pos += num_bytes_read;
492
493 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
494 auto last_of_th = &th.unused[std::size(th.unused)];
495 if (std::string_view{th.magic, 5} != "ustar" && std::any_of(th.magic, last_of_th, [](auto c) { return c != 0; })) {
496 /* If we have only zeros in the block, it can be an end-of-file indicator */
497 if (std::all_of(th.name, last_of_th, [](auto c) { return c == 0; })) continue;
498
499 Debug(misc, 0, "The file '{}' isn't a valid tar-file", filename);
500 return false;
501 }
502
503 std::string name;
504
505 /* The prefix contains the directory-name */
506 if (th.prefix[0] != '\0') {
507 name = ExtractString(th.prefix);
508 name += PATHSEP;
509 }
510
511 /* Copy the name of the file in a safe way at the end of 'name' */
512 name += ExtractString(th.name);
513
514 /* The size of the file, for some strange reason, this is stored as a string in octals. */
515 std::string size = ExtractString(th.size);
516 size_t skip = 0;
517 if (!size.empty()) {
518 auto value = ParseInteger<size_t>(size, 8);
519 if (!value.has_value()) {
520 Debug(misc, 0, "The file '{}' has an invalid size for '{}'", filename, name);
521 fclose(f);
522 return false;
523 }
524 skip = *value;
525 }
526
527 switch (th.typeflag) {
528 case '\0':
529 case '0': { // regular file
530 if (name.empty()) break;
531
532 /* Store this entry in the list */
533 TarFileListEntry entry;
534 entry.tar_filename = filename;
535 entry.size = skip;
536 entry.position = pos;
537
538 /* Convert to lowercase and our PATHSEPCHAR */
539 SimplifyFileName(name);
540
541 Debug(misc, 6, "Found file in tar: {} ({} bytes, {} offset)", name, skip, pos);
542 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(filename_base + PATHSEPCHAR + name, entry)).second) num++;
543
544 break;
545 }
546
547 case '1': // hard links
548 case '2': { // symbolic links
549 std::string link = ExtractString(th.linkname);
550
551 Debug(misc, 5, "Ignoring link in tar: {} -> {}", name, link);
552 break;
553 }
554
555 case '5': // directory
556 /* Convert to lowercase and our PATHSEPCHAR */
557 SimplifyFileName(name);
558
559 /* Store the first directory name we detect */
560 Debug(misc, 6, "Found dir in tar: {}", name);
561 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = std::move(name);
562 break;
563
564 default:
565 /* Ignore other types */
566 break;
567 }
568
569 /* Skip to the next block.. */
570 skip = Align(skip, 512);
571 if (fseek(f, skip, SEEK_CUR) < 0) {
572 Debug(misc, 0, "The file '{}' can't be read as a valid tar-file", filename);
573 return false;
574 }
575 pos += skip;
576 }
577
578 Debug(misc, 4, "Found tar '{}' with {} new files", filename, num);
579
580 return true;
581}
582
590bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
591{
592 TarList::iterator it = _tar_list[subdir].find(tar_filename);
593 /* We don't know the file. */
594 if (it == _tar_list[subdir].end()) return false;
595
596 const auto &dirname = it->second;
597
598 /* The file doesn't have a sub directory! */
599 if (dirname.empty()) {
600 Debug(misc, 3, "Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
601 return false;
602 }
603
604 std::string filename = tar_filename;
605 auto p = filename.find_last_of(PATHSEPCHAR);
606 /* The file's path does not have a separator? */
607 if (p == std::string::npos) return false;
608
609 filename.replace(p + 1, std::string::npos, dirname);
610 Debug(misc, 8, "Extracting {} to directory {}", tar_filename, filename);
611 FioCreateDirectory(filename);
612
613 for (auto &it2 : _tar_filelist[subdir]) {
614 if (tar_filename != it2.second.tar_filename) continue;
615
616 /* it2.first is tarball + PATHSEPCHAR + name. */
617 std::string_view name = it2.first;
618 name.remove_prefix(name.find_first_of(PATHSEPCHAR) + 1);
619 filename.replace(p + 1, std::string::npos, name);
620
621 Debug(misc, 9, " extracting {}", filename);
622
623 /* First open the file in the .tar. */
624 size_t to_copy = 0;
625 auto in = FioFOpenFileTar(it2.second, &to_copy);
626 if (!in.has_value()) {
627 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, tar_filename);
628 return false;
629 }
630
631 /* Now open the 'output' file. */
632 auto out = FileHandle::Open(filename, "wb");
633 if (!out.has_value()) {
634 Debug(misc, 6, "Extracting {} failed; could not open {}", filename, filename);
635 return false;
636 }
637
638 /* Now read from the tar and write it into the file. */
639 char buffer[4096];
640 size_t read;
641 for (; to_copy != 0; to_copy -= read) {
642 read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), *in);
643 if (read <= 0 || fwrite(buffer, 1, read, *out) != read) break;
644 }
645
646 if (to_copy != 0) {
647 Debug(misc, 6, "Extracting {} failed; still {} bytes to copy", filename, to_copy);
648 return false;
649 }
650 }
651
652 Debug(misc, 9, " extraction successful");
653 return true;
654}
655
656#if defined(_WIN32)
662extern void DetermineBasePaths(std::string_view exe);
663
665char *getcwd(char *buf, size_t size);
666#else /* defined(_WIN32) */
667
675static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
676{
677 std::string path{exe};
678
679#ifdef WITH_COCOA
680 for (size_t pos = path.find_first_of('.'); pos != std::string::npos; pos = path.find_first_of('.', pos + 1)) {
681 if (StrEqualsIgnoreCase(path.substr(pos, 4), ".app")) {
682 path.erase(pos);
683 break;
684 }
685 }
686#endif /* WITH_COCOA */
687
688 size_t pos = path.find_last_of(PATHSEPCHAR);
689 if (pos == std::string::npos) return false;
690
691 path.erase(pos);
692
693 if (chdir(path.c_str()) != 0) {
694 Debug(misc, 0, "Directory with the binary does not exist?");
695 return false;
696 }
697
698 return true;
699}
700
712{
713 /* No working directory, so nothing to do. */
714 if (_searchpaths[SP_WORKING_DIR].empty()) return false;
715
716 /* Working directory is root, so do nothing. */
717 if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
718
719 /* No personal/home directory, so the working directory won't be that. */
720 if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
721
722 std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
724
725 return _searchpaths[SP_PERSONAL_DIR] != tmp;
726}
727
733static std::string GetHomeDir()
734{
735#ifdef __HAIKU__
736 BPath path;
737 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
738 return std::string(path.Path());
739#else
740 auto home_env = GetEnv("HOME"); // Stack var, shouldn't be freed
741 if (home_env.has_value()) return std::string(*home_env);
742
743 const struct passwd *pw = getpwuid(getuid());
744 if (pw != nullptr) return std::string(pw->pw_dir);
745#endif
746 return {};
747}
748
753void DetermineBasePaths(std::string_view exe)
754{
755 std::string tmp;
756 const std::string homedir = GetHomeDir();
757#ifdef USE_XDG
758 if (auto xdg_data_home = GetEnv("XDG_DATA_HOME"); xdg_data_home.has_value()) {
759 tmp = *xdg_data_home;
760 tmp += PATHSEP;
761 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
763 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
764
765 tmp += "content_download";
768 } else if (!homedir.empty()) {
769 tmp = homedir;
770 tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
771 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
773 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
774
775 tmp += "content_download";
778 } else {
779 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
781 }
782#endif
783
784#if !defined(WITH_PERSONAL_DIR)
786#else
787 if (!homedir.empty()) {
788 tmp = std::move(homedir);
789 tmp += PATHSEP;
790 tmp += PERSONAL_DIR;
793
794 tmp += "content_download";
797 } else {
800 }
801#endif
802
803#if defined(WITH_SHARED_DIR)
804 tmp = SHARED_DIR;
807#else
809#endif
810
811 char cwd[MAX_PATH];
812 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
813
814 if (_config_file.empty()) {
815 /* Get the path to working directory of OpenTTD. */
816 tmp = cwd;
819
821 } else {
822 /* Use the folder of the config file as working directory. */
823 size_t end = _config_file.find_last_of(PATHSEPCHAR);
824 if (end == std::string::npos) {
825 /* _config_file is not in a folder, so use current directory. */
826 tmp = cwd;
827 } else {
828 tmp = FS2OTTD(std::filesystem::weakly_canonical(std::filesystem::path(OTTD2FS(_config_file))).parent_path().native());
829 }
832 }
833
834 /* Change the working directory to that one of the executable */
836 char buf[MAX_PATH];
837 if (getcwd(buf, lengthof(buf)) == nullptr) {
838 tmp.clear();
839 } else {
840 tmp = buf;
841 }
844 } else {
846 }
847
848 if (cwd[0] != '\0') {
849 /* Go back to the current working directory. */
850 if (chdir(cwd) != 0) {
851 Debug(misc, 0, "Failed to return to working directory!");
852 }
853 }
854
855#if !defined(GLOBAL_DATA_DIR)
857#else
858 tmp = GLOBAL_DATA_DIR;
860 _searchpaths[SP_INSTALLATION_DIR] = std::move(tmp);
861#endif
862#ifdef WITH_COCOA
863extern void CocoaSetApplicationBundleDir();
864 CocoaSetApplicationBundleDir();
865#else
867#endif
868}
869#endif /* defined(_WIN32) */
870
871std::string _personal_dir;
872
880void DeterminePaths(std::string_view exe, bool only_local_path)
881{
883 FillValidSearchPaths(only_local_path);
884
885#ifdef USE_XDG
886 std::string config_home;
887 std::string homedir = GetHomeDir();
888 if (auto xdg_config_home = GetEnv("XDG_CONFIG_HOME"); xdg_config_home.has_value()) {
889 config_home = *xdg_config_home;
890 config_home += PATHSEP;
891 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
892 } else if (!homedir.empty()) {
893 /* Defaults to ~/.config */
894 config_home = std::move(homedir);
895 config_home += PATHSEP ".config" PATHSEP;
896 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
897 }
898 AppendPathSeparator(config_home);
899#endif
900
901 for (Searchpath sp : _valid_searchpaths) {
902 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
903 Debug(misc, 3, "{} added as search path", _searchpaths[sp]);
904 }
905
906 std::string config_dir;
907 if (!_config_file.empty()) {
908 config_dir = _searchpaths[SP_WORKING_DIR];
909 } else {
910 std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
911 if (!personal_dir.empty()) {
912 auto end = personal_dir.find_last_of(PATHSEPCHAR);
913 if (end != std::string::npos) personal_dir.erase(end + 1);
914 config_dir = std::move(personal_dir);
915 } else {
916#ifdef USE_XDG
917 /* No previous configuration file found. Use the configuration folder from XDG. */
918 config_dir = config_home;
919#else
920 static const Searchpath new_openttd_cfg_order[] = {
922 };
923
924 config_dir.clear();
925 for (const auto &searchpath : new_openttd_cfg_order) {
926 if (IsValidSearchPath(searchpath)) {
927 config_dir = _searchpaths[searchpath];
928 break;
929 }
930 }
931#endif
932 }
933 _config_file = config_dir + "openttd.cfg";
934 }
935
936 Debug(misc, 1, "{} found as config directory", config_dir);
937
938 _highscore_file = config_dir + "hs.dat";
939 extern std::string _hotkeys_file;
940 _hotkeys_file = config_dir + "hotkeys.cfg";
941 extern std::string _windows_file;
942 _windows_file = config_dir + "windows.cfg";
943 extern std::string _private_file;
944 _private_file = config_dir + "private.cfg";
945 extern std::string _secrets_file;
946 _secrets_file = config_dir + "secrets.cfg";
947 extern std::string _favs_file;
948 _favs_file = config_dir + "favs.cfg";
949
950#ifdef USE_XDG
951 if (config_dir == config_home) {
952 /* We are using the XDG configuration home for the config file,
953 * then store the rest in the XDG data home folder. */
954 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
955 if (only_local_path) {
956 /* In case of XDG and we only want local paths and we detected that
957 * the user either manually indicated the XDG path or didn't use
958 * "-c" option, we change the working-dir to the XDG personal-dir,
959 * as this is most likely what the user is expecting. */
960 _searchpaths[SP_WORKING_DIR] = _searchpaths[SP_PERSONAL_DIR_XDG];
961 }
962 } else
963#endif
964 {
965 _personal_dir = config_dir;
966 }
967
968 /* Make the necessary folders */
969 FioCreateDirectory(config_dir);
970#if defined(WITH_PERSONAL_DIR)
972#endif
973
974 Debug(misc, 1, "{} found as personal directory", _personal_dir);
975
976 static const Subdirectory default_subdirs[] = {
978 };
979
980 for (const auto &default_subdir : default_subdirs) {
981 FioCreateDirectory(fmt::format("{}{}", _personal_dir, _subdirs[default_subdir]));
982 }
983
984 /* If we have network we make a directory for the autodownloading of content */
985 _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
986 Debug(misc, 3, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
988 FillValidSearchPaths(only_local_path);
989
990 /* Create the directory for each of the types of content */
992 for (const auto &subdir : subdirs) {
993 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, subdir));
994 }
995
996 extern std::string _log_file;
997 _log_file = _personal_dir + "openttd.log";
998}
999
1004void SanitizeFilename(std::string &filename)
1005{
1006 for (auto &c : filename) {
1007 switch (c) {
1008 /* The following characters are not allowed in filenames
1009 * on at least one of the supported operating systems: */
1010 case ':': case '\\': case '*': case '?': case '/':
1011 case '<': case '>': case '|': case '"':
1012 c = '_';
1013 break;
1014 }
1015 }
1016}
1017
1026std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1027{
1028 auto in = FileHandle::Open(filename, "rb");
1029 if (!in.has_value()) return nullptr;
1030
1031 fseek(*in, 0, SEEK_END);
1032 size_t len = ftell(*in);
1033 fseek(*in, 0, SEEK_SET);
1034 if (len > maxsize) return nullptr;
1035
1036 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1037
1038 mem.get()[len] = 0;
1039 if (fread(mem.get(), len, 1, *in) != 1) return nullptr;
1040
1041 lenp = len;
1042 return mem;
1043}
1044
1051static bool MatchesExtension(std::string_view extension, const std::string &filename)
1052{
1053 if (extension.empty()) return true;
1054 if (filename.length() < extension.length()) return false;
1055
1056 std::string_view filename_sv = filename; // String view to avoid making another copy of the substring.
1057 return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1058}
1059
1069static uint ScanPath(FileScanner *fs, std::string_view extension, const std::filesystem::path &path, size_t basepath_length, bool recursive)
1070{
1071 uint num = 0;
1072
1073 std::error_code error_code;
1074 for (const auto &dir_entry : std::filesystem::directory_iterator(path, error_code)) {
1075 if (dir_entry.is_directory()) {
1076 if (!recursive) continue;
1077 num += ScanPath(fs, extension, dir_entry.path(), basepath_length, recursive);
1078 } else if (dir_entry.is_regular_file()) {
1079 std::string file = FS2OTTD(dir_entry.path().native());
1080 if (!MatchesExtension(extension, file)) continue;
1081 if (fs->AddFile(file, basepath_length, {})) num++;
1082 }
1083 }
1084 if (error_code) {
1085 Debug(misc, 9, "Unable to read directory {}: {}", path.string(), error_code.message());
1086 }
1087
1088 return num;
1089}
1090
1097static uint ScanTar(FileScanner *fs, std::string_view extension, const TarFileList::value_type &tar)
1098{
1099 uint num = 0;
1100
1101 if (MatchesExtension(extension, tar.first) && fs->AddFile(tar.first, 0, tar.second.tar_filename)) num++;
1102
1103 return num;
1104}
1105
1115uint FileScanner::Scan(std::string_view extension, Subdirectory sd, bool tars, bool recursive)
1116{
1117 this->subdir = sd;
1118
1119 uint num = 0;
1120
1121 for (Searchpath sp : _valid_searchpaths) {
1122 /* Don't search in the working directory */
1123 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1124
1125 std::string path = FioGetDirectory(sp, sd);
1126 num += ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1127 }
1128
1129 if (tars && sd != NO_DIRECTORY) {
1130 for (const auto &tar : _tar_filelist[sd]) {
1131 num += ScanTar(this, extension, tar);
1132 }
1133 }
1134
1135 switch (sd) {
1136 case BASESET_DIR:
1137 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1138 [[fallthrough]];
1139 case NEWGRF_DIR:
1140 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1141 break;
1142
1143 default: break;
1144 }
1145
1146 return num;
1147}
1148
1157uint FileScanner::Scan(std::string_view extension, const std::string &directory, bool recursive)
1158{
1159 std::string path(directory);
1160 AppendPathSeparator(path);
1161 return ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1162}
1163
1171std::optional<FileHandle> FileHandle::Open(const std::string &filename, std::string_view mode)
1172{
1173#if defined(_WIN32)
1174 /* Windows also requires mode to be wchar_t. */
1175 auto f = _wfopen(OTTD2FS(filename).c_str(), OTTD2FS(mode).c_str());
1176#else
1177 auto f = fopen(filename.c_str(), std::string{mode}.c_str());
1178#endif /* _WIN32 */
1179
1180 if (f == nullptr) return std::nullopt;
1181 return FileHandle(f);
1182}
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
Enum-as-bit-set wrapper.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1171
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:1115
virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)=0
Add a file with the given filename.
Subdirectory subdir
The current sub directory we are searching through.
Definition fileio_func.h:39
Helper for scanning for files with tar as extension.
Definition fileio_func.h:59
@ AI
Scan for AIs and its libraries.
@ Scenario
Scan for scenarios and heightmaps.
@ Game
Scan for game scripts.
@ Baseset
Scan for base sets.
@ NewGRF
Scan for non-base sets.
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition fileio.cpp:377
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:440
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:675
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:590
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:880
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:327
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
Definition fileio.cpp:1004
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:1069
void DetermineBasePaths(std::string_view exe)
Determine the base (personal dir and game data dir) paths.
Definition fileio.cpp:753
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:348
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:242
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition fileio.cpp:871
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:733
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
Definition fileio.cpp:360
static bool MatchesExtension(std::string_view extension, const std::string &filename)
Helper to see whether a given filename matches the extension.
Definition fileio.cpp:1051
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:315
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:1097
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:435
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:711
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:1026
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:348
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:857
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:321
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
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:308
Parse strings.
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:357
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