OpenTTD Source  20241120-master-g6d3adc6169
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"
11 #include "fileio_func.h"
13 #include "debug.h"
14 #include "fios.h"
15 #include "string_func.h"
16 #include "tar_type.h"
17 #ifdef _WIN32
18 #include <windows.h>
19 #elif defined(__HAIKU__)
20 #include <Path.h>
21 #include <storage/FindDirectory.h>
22 #else
23 #include <unistd.h>
24 #include <pwd.h>
25 #endif
26 #include <charconv>
27 #include <sys/stat.h>
28 #include <sstream>
29 #include <filesystem>
30 
31 #include "safeguards.h"
32 
34 static bool _do_scan_working_directory = true;
35 
36 extern std::string _config_file;
37 extern std::string _highscore_file;
38 
39 static const char * const _subdirs[] = {
40  "",
41  "save" PATHSEP,
42  "save" PATHSEP "autosave" PATHSEP,
43  "scenario" PATHSEP,
44  "scenario" PATHSEP "heightmap" PATHSEP,
45  "gm" PATHSEP,
46  "data" PATHSEP,
47  "baseset" PATHSEP,
48  "newgrf" PATHSEP,
49  "lang" PATHSEP,
50  "ai" PATHSEP,
51  "ai" PATHSEP "library" PATHSEP,
52  "game" PATHSEP,
53  "game" PATHSEP "library" PATHSEP,
54  "screenshot" PATHSEP,
55  "social_integration" PATHSEP,
56 };
57 static_assert(lengthof(_subdirs) == NUM_SUBDIRS);
58 
65 std::array<std::string, NUM_SEARCHPATHS> _searchpaths;
66 std::vector<Searchpath> _valid_searchpaths;
67 std::array<TarList, NUM_SUBDIRS> _tar_list;
68 TarFileList _tar_filelist[NUM_SUBDIRS];
69 
76 {
77  return sp < _searchpaths.size() && !_searchpaths[sp].empty();
78 }
79 
80 static 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(const std::string &filename, Subdirectory subdir)
122 {
123  auto f = FioFOpenFile(filename, "rb", subdir);
124  return f.has_value();
125 }
126 
132 bool FileExists(const std::string &filename)
133 {
134  std::error_code ec;
135  return std::filesystem::exists(OTTD2FS(filename), ec);
136 }
137 
144 std::string FioFindFullPath(Subdirectory subdir, const std::string &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 
163 std::string FioGetDirectory(Searchpath sp, Subdirectory subdir)
164 {
165  assert(subdir < NUM_SUBDIRS);
166  assert(sp < NUM_SEARCHPATHS);
167 
168  return _searchpaths[sp] + _subdirs[subdir];
169 }
170 
171 std::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 
183 static std::optional<FileHandle> FioFOpenFileSp(const std::string &filename, const char *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, -1, 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 = _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 
242 std::optional<FileHandle> FioFOpenFile(const std::string &filename, const char *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  std::string token;
262  while (std::getline(ss, token, PATHSEPCHAR)) {
263  if (token == "..") {
264  if (tokens.size() < 2) return std::nullopt;
265  tokens.pop_back();
266  } else if (token == ".") {
267  /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
268  * This confuses our file resolver. So, act like this folder doesn't exist. */
269  } else {
270  tokens.push_back(token);
271  }
272  }
273 
274  resolved_name.clear();
275  bool first = true;
276  for (const std::string &token : tokens) {
277  if (!first) {
278  resolved_name += PATHSEP;
279  }
280  resolved_name += token;
281  first = false;
282  }
283 
284  TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
285  if (it != _tar_filelist[subdir].end()) {
286  f = FioFOpenFileTar(it->second, filesize);
287  }
288  }
289 
290  /* Sometimes a full path is given. To support
291  * the 'subdirectory' must be 'removed'. */
292  if (!f.has_value() && subdir != NO_DIRECTORY) {
293  switch (subdir) {
294  case BASESET_DIR:
295  f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
296  if (f.has_value()) break;
297  [[fallthrough]];
298  case NEWGRF_DIR:
299  f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
300  break;
301 
302  default:
303  f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
304  break;
305  }
306  }
307 
308  return f;
309 }
310 
316 void FioCreateDirectory(const std::string &name)
317 {
318  /* Ignore directory creation errors; they'll surface later on. */
319  std::error_code error_code;
320  std::filesystem::create_directories(OTTD2FS(name), error_code);
321 }
322 
328 bool FioRemove(const std::string &filename)
329 {
330  std::filesystem::path path = OTTD2FS(filename);
331  std::error_code error_code;
332  std::filesystem::remove(path, error_code);
333  if (error_code) {
334  Debug(misc, 0, "Removing {} failed: {}", filename, error_code.message());
335  return false;
336  }
337  return true;
338 }
339 
346 void AppendPathSeparator(std::string &buf)
347 {
348  if (buf.empty()) return;
349 
350  if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
351 }
352 
358 static void SimplifyFileName(std::string &name)
359 {
360  for (char &c : name) {
361  /* Force lowercase */
362  c = std::tolower(c);
363 #if (PATHSEPCHAR != '/')
364  /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
365  if (c == '/') c = PATHSEPCHAR;
366 #endif
367  }
368 }
369 
376 {
377  _tar_filelist[sd].clear();
378  _tar_list[sd].clear();
379  uint num = this->Scan(".tar", sd, false);
380  if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
381  return num;
382 }
383 
384 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
385 {
386  Debug(misc, 2, "Scanning for tars");
387  TarScanner fs;
388  uint num = 0;
389  if (mode & TarScanner::BASESET) {
390  num += fs.DoScan(BASESET_DIR);
391  }
392  if (mode & TarScanner::NEWGRF) {
393  num += fs.DoScan(NEWGRF_DIR);
394  }
395  if (mode & TarScanner::AI) {
396  num += fs.DoScan(AI_DIR);
397  num += fs.DoScan(AI_LIBRARY_DIR);
398  }
399  if (mode & TarScanner::GAME) {
400  num += fs.DoScan(GAME_DIR);
401  num += fs.DoScan(GAME_LIBRARY_DIR);
402  }
403  if (mode & TarScanner::SCENARIO) {
404  num += fs.DoScan(SCENARIO_DIR);
405  num += fs.DoScan(HEIGHTMAP_DIR);
406  }
407  Debug(misc, 2, "Scan complete, found {} files", num);
408  return num;
409 }
410 
417 bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
418 {
419  this->subdir = sd;
420  return this->AddFile(filename, 0);
421 }
422 
433 static std::string ExtractString(std::span<char> buffer)
434 {
435  return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
436 }
437 
438 bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] const std::string &tar_filename)
439 {
440  /* No tar within tar. */
441  assert(tar_filename.empty());
442 
443  /* The TAR-header, repeated for every file */
444  struct TarHeader {
445  char name[100];
446  char mode[8];
447  char uid[8];
448  char gid[8];
449  char size[12];
450  char mtime[12];
451  char chksum[8];
452  char typeflag;
453  char linkname[100];
454  char magic[6];
455  char version[2];
456  char uname[32];
457  char gname[32];
458  char devmajor[8];
459  char devminor[8];
460  char prefix[155];
461 
462  char unused[12];
463  };
464 
465  /* Check if we already seen this file */
466  TarList::iterator it = _tar_list[this->subdir].find(filename);
467  if (it != _tar_list[this->subdir].end()) return false;
468 
469  auto of = FileHandle::Open(filename, "rb");
470  /* Although the file has been found there can be
471  * a number of reasons we cannot open the file.
472  * Most common case is when we simply have not
473  * been given read access. */
474  if (!of.has_value()) return false;
475  auto &f = *of;
476 
477  _tar_list[this->subdir][filename] = std::string{};
478 
479  std::string filename_base = FS2OTTD(std::filesystem::path(OTTD2FS(filename)).filename());
480  SimplifyFileName(filename_base);
481 
482  TarHeader th;
483  size_t num = 0, pos = 0;
484 
485  /* Make a char of 512 empty bytes */
486  char empty[512];
487  memset(&empty[0], 0, sizeof(empty));
488 
489  for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
490  size_t num_bytes_read = fread(&th, 1, 512, f);
491  if (num_bytes_read != 512) break;
492  pos += num_bytes_read;
493 
494  /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
495  if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
496  /* If we have only zeros in the block, it can be an end-of-file indicator */
497  if (memcmp(&th, &empty[0], 512) == 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  StrTrimInPlace(size);
519  auto [_, err] = std::from_chars(size.data(), size.data() + size.size(), skip, 8);
520  if (err != std::errc()) {
521  Debug(misc, 0, "The file '{}' has an invalid size for '{}'", filename, name);
522  fclose(f);
523  return false;
524  }
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] = 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)
662 extern void DetermineBasePaths(const char *exe);
663 #else /* defined(_WIN32) */
664 
672 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
673 {
674  std::string path = exe;
675 
676 #ifdef WITH_COCOA
677  for (size_t pos = path.find_first_of('.'); pos != std::string::npos; pos = path.find_first_of('.', pos + 1)) {
678  if (StrEqualsIgnoreCase(path.substr(pos, 4), ".app")) {
679  path.erase(pos);
680  break;
681  }
682  }
683 #endif /* WITH_COCOA */
684 
685  size_t pos = path.find_last_of(PATHSEPCHAR);
686  if (pos == std::string::npos) return false;
687 
688  path.erase(pos);
689 
690  if (chdir(path.c_str()) != 0) {
691  Debug(misc, 0, "Directory with the binary does not exist?");
692  return false;
693  }
694 
695  return true;
696 }
697 
709 {
710  /* No working directory, so nothing to do. */
711  if (_searchpaths[SP_WORKING_DIR].empty()) return false;
712 
713  /* Working directory is root, so do nothing. */
714  if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
715 
716  /* No personal/home directory, so the working directory won't be that. */
717  if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
718 
719  std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
720  AppendPathSeparator(tmp);
721 
722  return _searchpaths[SP_PERSONAL_DIR] != tmp;
723 }
724 
730 static std::string GetHomeDir()
731 {
732 #ifdef __HAIKU__
733  BPath path;
734  find_directory(B_USER_SETTINGS_DIRECTORY, &path);
735  return std::string(path.Path());
736 #else
737  const char *home_env = std::getenv("HOME"); // Stack var, shouldn't be freed
738  if (home_env != nullptr) return std::string(home_env);
739 
740  const struct passwd *pw = getpwuid(getuid());
741  if (pw != nullptr) return std::string(pw->pw_dir);
742 #endif
743  return {};
744 }
745 
750 void DetermineBasePaths(const char *exe)
751 {
752  std::string tmp;
753  const std::string homedir = GetHomeDir();
754 #ifdef USE_XDG
755  const char *xdg_data_home = std::getenv("XDG_DATA_HOME");
756  if (xdg_data_home != nullptr) {
757  tmp = xdg_data_home;
758  tmp += PATHSEP;
759  tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
760  AppendPathSeparator(tmp);
761  _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
762 
763  tmp += "content_download";
764  AppendPathSeparator(tmp);
766  } else if (!homedir.empty()) {
767  tmp = homedir;
768  tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
769  tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
770  AppendPathSeparator(tmp);
771  _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
772 
773  tmp += "content_download";
774  AppendPathSeparator(tmp);
776  } else {
777  _searchpaths[SP_PERSONAL_DIR_XDG].clear();
779  }
780 #endif
781 
782 #if !defined(WITH_PERSONAL_DIR)
783  _searchpaths[SP_PERSONAL_DIR].clear();
784 #else
785  if (!homedir.empty()) {
786  tmp = homedir;
787  tmp += PATHSEP;
788  tmp += PERSONAL_DIR;
789  AppendPathSeparator(tmp);
791 
792  tmp += "content_download";
793  AppendPathSeparator(tmp);
795  } else {
796  _searchpaths[SP_PERSONAL_DIR].clear();
798  }
799 #endif
800 
801 #if defined(WITH_SHARED_DIR)
802  tmp = SHARED_DIR;
803  AppendPathSeparator(tmp);
805 #else
806  _searchpaths[SP_SHARED_DIR].clear();
807 #endif
808 
809  char cwd[MAX_PATH];
810  if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
811 
812  if (_config_file.empty()) {
813  /* Get the path to working directory of OpenTTD. */
814  tmp = cwd;
815  AppendPathSeparator(tmp);
817 
819  } else {
820  /* Use the folder of the config file as working directory. */
821  size_t end = _config_file.find_last_of(PATHSEPCHAR);
822  if (end == std::string::npos) {
823  /* _config_file is not in a folder, so use current directory. */
824  tmp = cwd;
825  } else {
826  tmp = FS2OTTD(std::filesystem::weakly_canonical(std::filesystem::path(OTTD2FS(_config_file))).parent_path());
827  }
828  AppendPathSeparator(tmp);
830  }
831 
832  /* Change the working directory to that one of the executable */
834  char buf[MAX_PATH];
835  if (getcwd(buf, lengthof(buf)) == nullptr) {
836  tmp.clear();
837  } else {
838  tmp = buf;
839  }
840  AppendPathSeparator(tmp);
842  } else {
843  _searchpaths[SP_BINARY_DIR].clear();
844  }
845 
846  if (cwd[0] != '\0') {
847  /* Go back to the current working directory. */
848  if (chdir(cwd) != 0) {
849  Debug(misc, 0, "Failed to return to working directory!");
850  }
851  }
852 
853 #if !defined(GLOBAL_DATA_DIR)
855 #else
856  tmp = GLOBAL_DATA_DIR;
857  AppendPathSeparator(tmp);
859 #endif
860 #ifdef WITH_COCOA
861 extern void CocoaSetApplicationBundleDir();
862  CocoaSetApplicationBundleDir();
863 #else
865 #endif
866 }
867 #endif /* defined(_WIN32) */
868 
869 std::string _personal_dir;
870 
878 void DeterminePaths(const char *exe, bool only_local_path)
879 {
880  DetermineBasePaths(exe);
881  FillValidSearchPaths(only_local_path);
882 
883 #ifdef USE_XDG
884  std::string config_home;
885  const std::string homedir = GetHomeDir();
886  const char *xdg_config_home = std::getenv("XDG_CONFIG_HOME");
887  if (xdg_config_home != nullptr) {
888  config_home = xdg_config_home;
889  config_home += PATHSEP;
890  config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
891  } else if (!homedir.empty()) {
892  /* Defaults to ~/.config */
893  config_home = homedir;
894  config_home += PATHSEP ".config" PATHSEP;
895  config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
896  }
897  AppendPathSeparator(config_home);
898 #endif
899 
900  for (Searchpath sp : _valid_searchpaths) {
901  if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
902  Debug(misc, 3, "{} added as search path", _searchpaths[sp]);
903  }
904 
905  std::string config_dir;
906  if (!_config_file.empty()) {
907  config_dir = _searchpaths[SP_WORKING_DIR];
908  } else {
909  std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
910  if (!personal_dir.empty()) {
911  auto end = personal_dir.find_last_of(PATHSEPCHAR);
912  if (end != std::string::npos) personal_dir.erase(end + 1);
913  config_dir = personal_dir;
914  } else {
915 #ifdef USE_XDG
916  /* No previous configuration file found. Use the configuration folder from XDG. */
917  config_dir = config_home;
918 #else
919  static const Searchpath new_openttd_cfg_order[] = {
921  };
922 
923  config_dir.clear();
924  for (const auto &searchpath : new_openttd_cfg_order) {
925  if (IsValidSearchPath(searchpath)) {
926  config_dir = _searchpaths[searchpath];
927  break;
928  }
929  }
930 #endif
931  }
932  _config_file = config_dir + "openttd.cfg";
933  }
934 
935  Debug(misc, 1, "{} found as config directory", config_dir);
936 
937  _highscore_file = config_dir + "hs.dat";
938  extern std::string _hotkeys_file;
939  _hotkeys_file = config_dir + "hotkeys.cfg";
940  extern std::string _windows_file;
941  _windows_file = config_dir + "windows.cfg";
942  extern std::string _private_file;
943  _private_file = config_dir + "private.cfg";
944  extern std::string _secrets_file;
945  _secrets_file = config_dir + "secrets.cfg";
946  extern std::string _favs_file;
947  _favs_file = config_dir + "favs.cfg";
948 
949 #ifdef USE_XDG
950  if (config_dir == config_home) {
951  /* We are using the XDG configuration home for the config file,
952  * then store the rest in the XDG data home folder. */
953  _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
954  if (only_local_path) {
955  /* In case of XDG and we only want local paths and we detected that
956  * the user either manually indicated the XDG path or didn't use
957  * "-c" option, we change the working-dir to the XDG personal-dir,
958  * as this is most likely what the user is expecting. */
959  _searchpaths[SP_WORKING_DIR] = _searchpaths[SP_PERSONAL_DIR_XDG];
960  }
961  } else
962 #endif
963  {
964  _personal_dir = config_dir;
965  }
966 
967  /* Make the necessary folders */
968  FioCreateDirectory(config_dir);
969 #if defined(WITH_PERSONAL_DIR)
971 #endif
972 
973  Debug(misc, 1, "{} found as personal directory", _personal_dir);
974 
975  static const Subdirectory default_subdirs[] = {
977  };
978 
979  for (const auto &default_subdir : default_subdirs) {
980  FioCreateDirectory(_personal_dir + _subdirs[default_subdir]);
981  }
982 
983  /* If we have network we make a directory for the autodownloading of content */
984  _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
985  Debug(misc, 3, "{} added as search path", _searchpaths[SP_AUTODOWNLOAD_DIR]);
987  FillValidSearchPaths(only_local_path);
988 
989  /* Create the directory for each of the types of content */
991  for (const auto &subdir : subdirs) {
992  FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, subdir));
993  }
994 
995  extern std::string _log_file;
996  _log_file = _personal_dir + "openttd.log";
997 }
998 
1003 void SanitizeFilename(std::string &filename)
1004 {
1005  for (auto &c : filename) {
1006  switch (c) {
1007  /* The following characters are not allowed in filenames
1008  * on at least one of the supported operating systems: */
1009  case ':': case '\\': case '*': case '?': case '/':
1010  case '<': case '>': case '|': case '"':
1011  c = '_';
1012  break;
1013  }
1014  }
1015 }
1016 
1025 std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1026 {
1027  auto in = FileHandle::Open(filename, "rb");
1028  if (!in.has_value()) return nullptr;
1029 
1030  fseek(*in, 0, SEEK_END);
1031  size_t len = ftell(*in);
1032  fseek(*in, 0, SEEK_SET);
1033  if (len > maxsize) return nullptr;
1034 
1035  std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1036 
1037  mem.get()[len] = 0;
1038  if (fread(mem.get(), len, 1, *in) != 1) return nullptr;
1039 
1040  lenp = len;
1041  return mem;
1042 }
1043 
1050 static bool MatchesExtension(std::string_view extension, const std::string &filename)
1051 {
1052  if (extension.empty()) return true;
1053  if (filename.length() < extension.length()) return false;
1054 
1055  std::string_view filename_sv = filename; // String view to avoid making another copy of the substring.
1056  return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1057 }
1058 
1068 static uint ScanPath(FileScanner *fs, std::string_view extension, const std::filesystem::path &path, size_t basepath_length, bool recursive)
1069 {
1070  uint num = 0;
1071 
1072  std::error_code error_code;
1073  for (const auto &dir_entry : std::filesystem::directory_iterator(path, error_code)) {
1074  if (dir_entry.is_directory()) {
1075  if (!recursive) continue;
1076  num += ScanPath(fs, extension, dir_entry.path(), basepath_length, recursive);
1077  } else if (dir_entry.is_regular_file()) {
1078  std::string file = FS2OTTD(dir_entry.path());
1079  if (!MatchesExtension(extension, file)) continue;
1080  if (fs->AddFile(file, basepath_length, {})) num++;
1081  }
1082  }
1083  if (error_code) {
1084  Debug(misc, 9, "Unable to read directory {}: {}", path.string(), error_code.message());
1085  }
1086 
1087  return num;
1088 }
1089 
1096 static uint ScanTar(FileScanner *fs, std::string_view extension, const TarFileList::value_type &tar)
1097 {
1098  uint num = 0;
1099 
1100  if (MatchesExtension(extension, tar.first) && fs->AddFile(tar.first, 0, tar.second.tar_filename)) num++;
1101 
1102  return num;
1103 }
1104 
1114 uint FileScanner::Scan(std::string_view extension, Subdirectory sd, bool tars, bool recursive)
1115 {
1116  this->subdir = sd;
1117 
1118  uint num = 0;
1119 
1120  for (Searchpath sp : _valid_searchpaths) {
1121  /* Don't search in the working directory */
1122  if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1123 
1124  std::string path = FioGetDirectory(sp, sd);
1125  num += ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1126  }
1127 
1128  if (tars && sd != NO_DIRECTORY) {
1129  for (const auto &tar : _tar_filelist[sd]) {
1130  num += ScanTar(this, extension, tar);
1131  }
1132  }
1133 
1134  switch (sd) {
1135  case BASESET_DIR:
1136  num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1137  [[fallthrough]];
1138  case NEWGRF_DIR:
1139  num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1140  break;
1141 
1142  default: break;
1143  }
1144 
1145  return num;
1146 }
1147 
1156 uint FileScanner::Scan(const std::string_view extension, const std::string &directory, bool recursive)
1157 {
1158  std::string path(directory);
1159  AppendPathSeparator(path);
1160  return ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
1161 }
1162 
1170 std::optional<FileHandle> FileHandle::Open(const std::string &filename, const std::string &mode)
1171 {
1172 #if defined(_WIN32)
1173  /* Windows also requires mode to be wchar_t. */
1174  auto f = _wfopen(OTTD2FS(filename).c_str(), OTTD2FS(mode).c_str());
1175 #else
1176  auto f = fopen(filename.c_str(), mode.c_str());
1177 #endif /* _WIN32 */
1178 
1179  if (f == nullptr) return std::nullopt;
1180  return FileHandle(f);
1181 }
static std::optional< FileHandle > Open(const std::string &filename, const std::string &mode)
Open an RAII file handle if possible.
Definition: fileio.cpp:1170
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:1114
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
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition: fileio.cpp:375
Mode
The mode of tar scanning.
Definition: fileio_func.h:63
@ NEWGRF
Scan for non-base sets.
Definition: fileio_func.h:66
@ BASESET
Scan for base sets.
Definition: fileio_func.h:65
@ GAME
Scan for game scripts.
Definition: fileio_func.h:69
@ SCENARIO
Scan for scenarios and heightmaps.
Definition: fileio_func.h:68
@ AI
Scan for AIs and its libraries.
Definition: fileio_func.h:67
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename={}) override
Add a file with the given filename.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Ouptut 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
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename)
Find a path to the filename in one of the search directories.
Definition: fileio.cpp:144
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(const char *exe, bool only_local_path)
Acquire the base paths (personal dir and game data dir), fill all other paths (save dir,...
Definition: fileio.cpp:878
static bool IsValidSearchPath(Searchpath sp)
Checks whether the given search path is a valid search path.
Definition: fileio.cpp:75
static std::optional< FileHandle > FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
Opens a file from inside a tar archive.
Definition: fileio.cpp:223
bool FioRemove(const std::string &filename)
Remove a file.
Definition: fileio.cpp:328
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
Definition: fileio.cpp:1003
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:1068
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition: fileio.cpp:346
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition: fileio.cpp:869
static bool _do_scan_working_directory
Whether the working directory should be scanned.
Definition: fileio.cpp:34
static std::string GetHomeDir()
Gets the home directory of the user.
Definition: fileio.cpp:730
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
Check whether the given file exists.
Definition: fileio.cpp:121
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
Definition: fileio.cpp:358
static bool MatchesExtension(std::string_view extension, const std::string &filename)
Helper to see whether a given filename matches the extension.
Definition: fileio.cpp:1050
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:316
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:1096
static bool ChangeWorkingDirectoryToExecutable(const char *exe)
Changes the working directory to the path of the give executable.
Definition: fileio.cpp:672
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition: fileio.cpp:132
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:242
std::array< std::string, NUM_SEARCHPATHS > _searchpaths
The search paths OpenTTD could search through.
Definition: fileio.cpp:57
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
Definition: fileio.cpp:1025
static std::string ExtractString(std::span< char > buffer)
Helper to extract a string for the tar header.
Definition: fileio.cpp:433
std::string _highscore_file
The file to store the highscore data in.
Definition: highscore.cpp:24
bool DoScanWorkingDirectory()
Whether we should scan the working directory.
Definition: fileio.cpp:708
std::string _config_file
Configuration file of OpenTTD.
Definition: settings.cpp:60
void DetermineBasePaths(const char *exe)
Determine the base (personal dir and game data dir) paths.
Definition: fileio.cpp:750
Functions for Standard In/Out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
Definition: fileio_type.h:139
@ SP_SHARED_DIR
Search in the shared directory, like 'Shared Files' under Windows.
Definition: fileio_type.h:146
@ SP_INSTALLATION_DIR
Search in the installation directory.
Definition: fileio_type.h:148
@ SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
Search within the autodownload directory located in the personal directory (XDG variant)
Definition: fileio_type.h:152
@ SP_BINARY_DIR
Search in the directory where the binary resides.
Definition: fileio_type.h:147
@ SP_AUTODOWNLOAD_PERSONAL_DIR
Search within the autodownload directory located in the personal directory.
Definition: fileio_type.h:151
@ SP_PERSONAL_DIR
Search in the personal directory.
Definition: fileio_type.h:145
@ SP_WORKING_DIR
Search in the working directory.
Definition: fileio_type.h:141
@ SP_APPLICATION_BUNDLE_DIR
Search within the application bundle.
Definition: fileio_type.h:149
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Definition: fileio_type.h:150
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:115
@ OLD_DATA_DIR
Old subdirectory for the data.
Definition: fileio_type.h:122
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:133
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
Definition: fileio_type.h:127
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
Definition: fileio_type.h:130
@ SOCIAL_INTEGRATION_DIR
Subdirectory for all social integration plugins.
Definition: fileio_type.h:131
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
Definition: fileio_type.h:129
@ AI_DIR
Subdirectory for all AI files.
Definition: fileio_type.h:126
@ OLD_GM_DIR
Old subdirectory for the music.
Definition: fileio_type.h:121
@ SCENARIO_DIR
Base directory for all scenarios.
Definition: fileio_type.h:119
@ BASE_DIR
Base directory for all subdirectories.
Definition: fileio_type.h:116
@ SAVE_DIR
Base directory for all savegames.
Definition: fileio_type.h:117
@ NUM_SUBDIRS
Number of subdirectories.
Definition: fileio_type.h:132
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
Definition: fileio_type.h:120
@ NEWGRF_DIR
Subdirectory for all NewGRFs.
Definition: fileio_type.h:124
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
Definition: fileio_type.h:118
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition: fileio_type.h:123
@ GAME_DIR
Subdirectory for all game scripts.
Definition: fileio_type.h:128
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:62
std::string _favs_file
Picker favourites configuration file of OpenTTD.
Definition: settings.cpp:63
std::string _private_file
Private configuration file of OpenTTD.
Definition: settings.cpp:61
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:280
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:107
bool StrEqualsIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition: string.cpp:347
int StrCompareIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
Definition: string.cpp:334
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
Definition: string.cpp:260
Functions related to low-level strings.
Structs, typedefs and macros used for TAR file handling.
std::wstring OTTD2FS(const std::string &name)
Convert from OpenTTD's encoding to a wide string.
Definition: win32.cpp:354
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition: win32.cpp:337
std::string _windows_file
Config file to store WindowDesc.
Definition: window.cpp:102