OpenTTD Source: src/fileio.cpp Source File

OpenTTD Source 15.0-RC2

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
10#include "stdafx.h"
11#include "core/string_consumer.hpp"
12#include "fileio_func.h"
13#include "spriteloader/spriteloader.hpp"
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
33 static 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
75 static bool IsValidSearchPath(Searchpath sp)
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. */
110 if (IsValidSearchPath(SP_WORKING_DIR) && seen.count(_searchpaths[SP_WORKING_DIR]) == 0) {
111 _valid_searchpaths.insert(_valid_searchpaths.begin(), SP_WORKING_DIR);
112 }
113}
114
121 bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
122{
123 auto f = FioFOpenFile(filename, "rb", subdir);
124 return f.has_value();
125}
126
132 bool 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
223 static 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
315 void 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
327 bool 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
348 void AppendPathSeparator(std::string &buf)
349{
350 if (buf.empty()) return;
351
352 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
353}
354
360 static 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
377uint TarScanner::DoScan(Subdirectory sd)
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
419 bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
420{
421 this->subdir = sd;
422 return this->AddFile(filename, 0);
423}
424
435 static std::string ExtractString(std::span<char> buffer)
436{
437 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
438}
439
440 bool 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
590 bool 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
675 static 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
711 bool DoScanWorkingDirectory()
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;
723 AppendPathSeparator(tmp);
724
725 return _searchpaths[SP_PERSONAL_DIR] != tmp;
726}
727
733 static 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
753 void 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;
762 AppendPathSeparator(tmp);
763 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
764
765 tmp += "content_download";
766 AppendPathSeparator(tmp);
767 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
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;
772 AppendPathSeparator(tmp);
773 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
774
775 tmp += "content_download";
776 AppendPathSeparator(tmp);
777 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
778 } else {
779 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
780 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG].clear();
781 }
782#endif
783
784#if !defined(WITH_PERSONAL_DIR)
785 _searchpaths[SP_PERSONAL_DIR].clear();
786#else
787 if (!homedir.empty()) {
788 tmp = std::move(homedir);
789 tmp += PATHSEP;
790 tmp += PERSONAL_DIR;
791 AppendPathSeparator(tmp);
792 _searchpaths[SP_PERSONAL_DIR] = tmp;
793
794 tmp += "content_download";
795 AppendPathSeparator(tmp);
796 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR] = tmp;
797 } else {
798 _searchpaths[SP_PERSONAL_DIR].clear();
799 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR].clear();
800 }
801#endif
802
803#if defined(WITH_SHARED_DIR)
804 tmp = SHARED_DIR;
805 AppendPathSeparator(tmp);
806 _searchpaths[SP_SHARED_DIR] = tmp;
807#else
808 _searchpaths[SP_SHARED_DIR].clear();
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;
817 AppendPathSeparator(tmp);
818 _searchpaths[SP_WORKING_DIR] = tmp;
819
820 _do_scan_working_directory = DoScanWorkingDirectory();
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 }
830 AppendPathSeparator(tmp);
831 _searchpaths[SP_WORKING_DIR] = tmp;
832 }
833
834 /* Change the working directory to that one of the executable */
835 if (ChangeWorkingDirectoryToExecutable(exe)) {
836 char buf[MAX_PATH];
837 if (getcwd(buf, lengthof(buf)) == nullptr) {
838 tmp.clear();
839 } else {
840 tmp = buf;
841 }
842 AppendPathSeparator(tmp);
843 _searchpaths[SP_BINARY_DIR] = tmp;
844 } else {
845 _searchpaths[SP_BINARY_DIR].clear();
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)
856 _searchpaths[SP_INSTALLATION_DIR].clear();
857#else
858 tmp = GLOBAL_DATA_DIR;
859 AppendPathSeparator(tmp);
860 _searchpaths[SP_INSTALLATION_DIR] = std::move(tmp);
861#endif
862#ifdef WITH_COCOA
863extern void CocoaSetApplicationBundleDir();
864 CocoaSetApplicationBundleDir();
865#else
866 _searchpaths[SP_APPLICATION_BUNDLE_DIR].clear();
867#endif
868}
869#endif /* defined(_WIN32) */
870
871std::string _personal_dir;
872
880 void DeterminePaths(std::string_view exe, bool only_local_path)
881{
882 DetermineBasePaths(exe);
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[] = {
921 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
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)
971 FioCreateDirectory(_personal_dir);
972#endif
973
974 Debug(misc, 1, "{} found as personal directory", _personal_dir);
975
976 static const Subdirectory default_subdirs[] = {
977 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR, SOCIAL_INTEGRATION_DIR
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]);
987 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
988 FillValidSearchPaths(only_local_path);
989
990 /* Create the directory for each of the types of content */
991 const Subdirectory subdirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SOCIAL_INTEGRATION_DIR };
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
1004 void 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
1051 static 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
1069 static 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
1097 static 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}
BaseBitSet::Test
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
Definition base_bitset_type.hpp:141
EnumBitSet
Enum-as-bit-set wrapper.
Definition enum_type.hpp:177
FileHandle
Definition fileio_type.h:132
FileHandle::Open
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1171
FileScanner
Helper for scanning for files with a given name.
Definition fileio_func.h:37
FileScanner::Scan
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
FileScanner::AddFile
virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)=0
Add a file with the given filename.
FileScanner::subdir
Subdirectory subdir
The current sub directory we are searching through.
Definition fileio_func.h:39
TarScanner
Helper for scanning for files with tar as extension.
Definition fileio_func.h:59
TarScanner::Mode::AI
@ AI
Scan for AIs and its libraries.
TarScanner::Mode::Scenario
@ Scenario
Scan for scenarios and heightmaps.
TarScanner::Mode::Game
@ Game
Scan for game scripts.
TarScanner::Mode::Baseset
@ Baseset
Scan for base sets.
TarScanner::Mode::NewGRF
@ NewGRF
Scan for non-base sets.
TarScanner::DoScan
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition fileio.cpp:377
TarScanner::AddFile
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
debug.h
Functions related to debugging.
Debug
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
_log_file
std::string _log_file
Filename to reroute output of a forked OpenTTD to.
Definition dedicated.cpp:14
ChangeWorkingDirectoryToExecutable
static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
Changes the working directory to the path of the give executable.
Definition fileio.cpp:675
ExtractTar
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
DeterminePaths
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
IsValidSearchPath
static bool IsValidSearchPath(Searchpath sp)
Checks whether the given search path is a valid search path.
Definition fileio.cpp:75
FioRemove
bool FioRemove(const std::string &filename)
Remove a file.
Definition fileio.cpp:327
SanitizeFilename
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
Definition fileio.cpp:1004
ScanPath
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
DetermineBasePaths
void DetermineBasePaths(std::string_view exe)
Determine the base (personal dir and game data dir) paths.
Definition fileio.cpp:753
AppendPathSeparator
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:348
FioFOpenFile
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
_personal_dir
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition fileio.cpp:871
_do_scan_working_directory
static bool _do_scan_working_directory
Whether the working directory should be scanned.
Definition fileio.cpp:33
GetHomeDir
static std::string GetHomeDir()
Gets the home directory of the user.
Definition fileio.cpp:733
SimplifyFileName
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
Definition fileio.cpp:360
MatchesExtension
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
FioCreateDirectory
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
FileExists
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
ScanTar
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
_searchpaths
std::array< std::string, NUM_SEARCHPATHS > _searchpaths
The search paths OpenTTD could search through.
Definition fileio.cpp:65
FioCheckFileExists
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
FioFOpenFileTar
static std::optional< FileHandle > FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
Opens a file from inside a tar archive.
Definition fileio.cpp:223
ExtractString
static std::string ExtractString(std::span< char > buffer)
Helper to extract a string for the tar header.
Definition fileio.cpp:435
_highscore_file
std::string _highscore_file
The file to store the highscore data in.
Definition highscore.cpp:25
DoScanWorkingDirectory
bool DoScanWorkingDirectory()
Whether we should scan the working directory.
Definition fileio.cpp:711
_config_file
std::string _config_file
Configuration file of OpenTTD.
Definition settings.cpp:64
FioFindFullPath
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
ReadFileToMem
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
Definition fileio.cpp:1026
fileio_func.h
Functions for Standard In/Out file operations.
AppendPathSeparator
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:348
Searchpath
Searchpath
Types of searchpaths OpenTTD might use.
Definition fileio_type.h:113
SP_SHARED_DIR
@ SP_SHARED_DIR
Search in the shared directory, like 'Shared Files' under Windows.
Definition fileio_type.h:120
SP_INSTALLATION_DIR
@ SP_INSTALLATION_DIR
Search in the installation directory.
Definition fileio_type.h:122
SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
@ SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
Search within the autodownload directory located in the personal directory (XDG variant)
Definition fileio_type.h:126
SP_BINARY_DIR
@ SP_BINARY_DIR
Search in the directory where the binary resides.
Definition fileio_type.h:121
SP_AUTODOWNLOAD_PERSONAL_DIR
@ SP_AUTODOWNLOAD_PERSONAL_DIR
Search within the autodownload directory located in the personal directory.
Definition fileio_type.h:125
SP_PERSONAL_DIR
@ SP_PERSONAL_DIR
Search in the personal directory.
Definition fileio_type.h:119
SP_WORKING_DIR
@ SP_WORKING_DIR
Search in the working directory.
Definition fileio_type.h:115
SP_APPLICATION_BUNDLE_DIR
@ SP_APPLICATION_BUNDLE_DIR
Search within the application bundle.
Definition fileio_type.h:123
SP_AUTODOWNLOAD_DIR
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Definition fileio_type.h:124
Subdirectory
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
OLD_DATA_DIR
@ OLD_DATA_DIR
Old subdirectory for the data.
Definition fileio_type.h:95
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition fileio_type.h:107
AI_LIBRARY_DIR
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
Definition fileio_type.h:100
SCREENSHOT_DIR
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
Definition fileio_type.h:103
SOCIAL_INTEGRATION_DIR
@ SOCIAL_INTEGRATION_DIR
Subdirectory for all social integration plugins.
Definition fileio_type.h:104
GAME_LIBRARY_DIR
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
Definition fileio_type.h:102
AI_DIR
@ AI_DIR
Subdirectory for all AI files.
Definition fileio_type.h:99
OLD_GM_DIR
@ OLD_GM_DIR
Old subdirectory for the music.
Definition fileio_type.h:94
SCENARIO_DIR
@ SCENARIO_DIR
Base directory for all scenarios.
Definition fileio_type.h:92
BASE_DIR
@ BASE_DIR
Base directory for all subdirectories.
Definition fileio_type.h:89
SAVE_DIR
@ SAVE_DIR
Base directory for all savegames.
Definition fileio_type.h:90
NUM_SUBDIRS
@ NUM_SUBDIRS
Number of subdirectories.
Definition fileio_type.h:106
HEIGHTMAP_DIR
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
Definition fileio_type.h:93
NEWGRF_DIR
@ NEWGRF_DIR
Subdirectory for all NewGRFs.
Definition fileio_type.h:97
AUTOSAVE_DIR
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
Definition fileio_type.h:91
BASESET_DIR
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition fileio_type.h:96
GAME_DIR
@ GAME_DIR
Subdirectory for all game scripts.
Definition fileio_type.h:101
fios.h
Declarations for savegames operations.
Align
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
Definition math_func.hpp:37
safeguards.h
A number of safeguards to prevent using unsafe methods.
_secrets_file
std::string _secrets_file
Secrets configuration file of OpenTTD.
Definition settings.cpp:66
_favs_file
std::string _favs_file
Picker favourites configuration file of OpenTTD.
Definition settings.cpp:67
_private_file
std::string _private_file
Private configuration file of OpenTTD.
Definition settings.cpp:65
spriteloader.hpp
Base for loading sprites.
stdafx.h
Definition of base types and functions in a cross-platform compatible way.
lengthof
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
GetEnv
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
StrEqualsIgnoreCase
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
StrMakeValid
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
StrCompareIgnoreCase
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
string_consumer.hpp
Parse strings.
string_func.h
Functions related to low-level strings.
TarFileListEntry
Definition tar_type.h:16
tar_type.h
Structs, typedefs and macros used for TAR file handling.
OTTD2FS
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
Definition win32.cpp:357
FS2OTTD
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:340
_windows_file
std::string _windows_file
Config file to store WindowDesc.
Definition window.cpp:105