20#elif defined(__HAIKU__)
22#include <storage/FindDirectory.h>
42 "save" PATHSEP
"autosave" PATHSEP,
44 "scenario" PATHSEP
"heightmap" PATHSEP,
51 "ai" PATHSEP
"library" PATHSEP,
53 "game" PATHSEP
"library" PATHSEP,
55 "social_integration" PATHSEP,
66std::vector<Searchpath> _valid_searchpaths;
82static void FillValidSearchPaths(
bool only_local_path)
84 _valid_searchpaths.clear();
86 std::set<std::string> seen{};
90 if (only_local_path) {
105 _valid_searchpaths.emplace_back(sp);
126 return f.has_value();
137 return std::filesystem::exists(
OTTD2FS(filename), ec);
151 std::string buf = FioGetDirectory(sp, subdir);
180 std::string ret = FioGetDirectory(sp, subdir);
188static std::optional<FileHandle> FioFOpenFileSp(std::string_view filename, std::string_view mode,
Searchpath sp,
Subdirectory subdir,
size_t *filesize)
196 MultiByteToWideChar(CP_ACP, 0, mode.data(),
static_cast<int>(std::size(mode)), Lmode,
static_cast<int>(std::size(Lmode)));
203 buf = fmt::format(
"{}{}", FioGetDirectory(sp, subdir), filename);
212 if (f.has_value() && filesize !=
nullptr) {
214 fseek(*f, 0, SEEK_END);
215 *filesize = ftell(*f);
216 fseek(*f, 0, SEEK_SET);
231 if (!f.has_value())
return std::nullopt;
233 if (fseek(*f, entry.position, SEEK_SET) < 0) {
237 if (filesize !=
nullptr) *filesize = entry.size;
251 std::optional<FileHandle> f = std::nullopt;
255 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
262 std::string resolved_name{filename};
263 strtolower(resolved_name);
266 std::istringstream ss(resolved_name);
267 std::vector<std::string> tokens;
268 for (std::string token; std::getline(ss, token, PATHSEPCHAR); ) {
270 if (tokens.size() < 2)
return std::nullopt;
272 }
else if (token ==
".") {
276 tokens.push_back(token);
280 resolved_name.clear();
282 for (
const std::string &token : tokens) {
284 resolved_name += PATHSEP;
286 resolved_name += token;
290 TarFileList::iterator it =
_tar_filelist[subdir].find(resolved_name);
302 if (f.has_value())
break;
325 std::error_code error_code;
326 std::filesystem::create_directories(
OTTD2FS(name), error_code);
336 std::filesystem::path path =
OTTD2FS(filename);
337 std::error_code error_code;
338 if (!std::filesystem::remove(path, error_code)) {
340 Debug(misc, 0,
"Removing {} failed: {}", filename, error_code.message());
342 Debug(misc, 0,
"Removing {} failed: file does not exist", filename);
356 if (buf.empty())
return;
358 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
368 for (
char &c : name) {
371#if (PATHSEPCHAR != '/')
373 if (c ==
'/') c = PATHSEPCHAR;
387 uint num = this->
Scan(
".tar", sd,
false);
399 Debug(misc, 2,
"Scanning for tars");
420 Debug(misc, 2,
"Scan complete, found {} files", num);
433 return this->
AddFile(filename, 0);
448 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
451bool TarScanner::AddFile(
const std::string &filename,
size_t, [[maybe_unused]]
const std::string &tar_filename)
454 assert(tar_filename.empty());
477 static_assert(
sizeof(TarHeader) == 512);
480 TarList::iterator it =
_tar_list[this->subdir].find(filename);
481 if (it !=
_tar_list[this->subdir].end())
return false;
488 if (!of.has_value())
return false;
491 _tar_list[this->subdir][filename] = std::string{};
493 std::string filename_base =
FS2OTTD(std::filesystem::path(
OTTD2FS(filename)).filename().native());
497 size_t num = 0, pos = 0;
500 size_t num_bytes_read = fread(&th, 1,
sizeof(TarHeader), f);
501 if (num_bytes_read !=
sizeof(TarHeader))
break;
502 pos += num_bytes_read;
505 auto last_of_th = &th.unused[std::size(th.unused)];
506 if (std::string_view{th.magic, 5} !=
"ustar" && std::any_of(th.magic, last_of_th, [](
auto c) { return c != 0; })) {
508 if (std::all_of(th.name, last_of_th, [](
auto c) { return c == 0; }))
continue;
510 Debug(misc, 0,
"The file '{}' isn't a valid tar-file", filename);
517 if (th.prefix[0] !=
'\0') {
530 if (!value.has_value()) {
531 Debug(misc, 0,
"The file '{}' has an invalid size for '{}'", filename, name);
538 switch (th.typeflag) {
541 if (name.empty())
break;
545 entry.tar_filename = filename;
547 entry.position = pos;
552 Debug(misc, 6,
"Found file in tar: {} ({} bytes, {} offset)", name, skip, pos);
553 if (
_tar_filelist[this->subdir].insert(TarFileList::value_type(filename_base + PATHSEPCHAR + name, entry)).second) num++;
562 Debug(misc, 5,
"Ignoring link in tar: {} -> {}", name, link);
571 Debug(misc, 6,
"Found dir in tar: {}", name);
572 if (
_tar_list[this->subdir][filename].empty())
_tar_list[this->subdir][filename] = std::move(name);
581 skip =
Align(skip, 512);
582 if (fseek(f, skip, SEEK_CUR) < 0) {
583 Debug(misc, 0,
"The file '{}' can't be read as a valid tar-file", filename);
589 Debug(misc, 4,
"Found tar '{}' with {} new files", filename, num);
603 TarList::iterator it =
_tar_list[subdir].find(tar_filename);
605 if (it ==
_tar_list[subdir].end())
return false;
608 if (it->second.empty()) {
609 Debug(misc, 3,
"Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
613 auto p = tar_filename.rfind(
".tar");
615 if (p == std::string::npos)
return false;
617 const std::string dirname = tar_filename.substr(0, p);
618 Debug(misc, 8,
"Extracting {} to directory {}", tar_filename, dirname);
622 if (tar_filename != it2.second.tar_filename)
continue;
625 std::string_view name = it2.first;
626 name.remove_prefix(name.find_last_of(PATHSEPCHAR) + 1);
627 std::string filename = fmt::format(
"{}{}{}", dirname, PATHSEP, name);
629 Debug(misc, 9,
" extracting {}", filename);
634 if (!in.has_value()) {
635 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, tar_filename);
641 if (!out.has_value()) {
642 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, filename);
649 for (; to_copy != 0; to_copy -= read) {
650 read = fread(buffer, 1, std::min(to_copy,
lengthof(buffer)), *in);
651 if (read <= 0 || fwrite(buffer, 1, read, *out) != read)
break;
655 Debug(misc, 6,
"Extracting {} failed; still {} bytes to copy", filename, to_copy);
660 Debug(misc, 9,
" extraction successful");
673char *getcwd(
char *buf,
size_t size);
686 std::string path{exe};
689 for (
size_t pos = path.find_first_of(
'.'); pos != std::string::npos; pos = path.find_first_of(
'.', pos + 1)) {
697 size_t pos = path.find_last_of(PATHSEPCHAR);
698 if (pos == std::string::npos)
return false;
702 if (chdir(path.c_str()) != 0) {
703 Debug(misc, 0,
"Directory with the binary does not exist?");
746 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
747 return std::string(path.Path());
749 auto home_env =
GetEnv(
"HOME");
750 if (home_env.has_value())
return std::string(*home_env);
752 const struct passwd *pw = getpwuid(getuid());
753 if (pw !=
nullptr)
return std::string(pw->pw_dir);
767 if (
auto xdg_data_home =
GetEnv(
"XDG_DATA_HOME"); xdg_data_home.has_value()) {
768 tmp = *xdg_data_home;
770 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
774 tmp +=
"content_download";
777 }
else if (!homedir.empty()) {
779 tmp += PATHSEP
".local" PATHSEP
"share" PATHSEP;
780 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
784 tmp +=
"content_download";
793#if !defined(WITH_PERSONAL_DIR)
796 if (!homedir.empty()) {
797 tmp = std::move(homedir);
803 tmp +=
"content_download";
812#if defined(WITH_SHARED_DIR)
821 if (getcwd(cwd, MAX_PATH) ==
nullptr) *cwd =
'\0';
833 if (end == std::string::npos) {
846 if (getcwd(buf,
lengthof(buf)) ==
nullptr) {
857 if (cwd[0] !=
'\0') {
859 if (chdir(cwd) != 0) {
860 Debug(misc, 0,
"Failed to return to working directory!");
864#if !defined(GLOBAL_DATA_DIR)
867 tmp = GLOBAL_DATA_DIR;
872extern void CocoaSetApplicationBundleDir();
873 CocoaSetApplicationBundleDir();
879 std::string config_file_path;
880 const std::string atari_ini_filename =
"Atari/Transport Tycoon Deluxe/installpath.ini";
885extern std::string CocoaGetAppSupportDir();
886 config_file_path = CocoaGetAppSupportDir();
888 if (!config_file_path.empty()) {
890 config_file_path += atari_ini_filename;
895 if (!config_file_path.empty()) {
897 config_file_path +=
".local/share/";
898 config_file_path += atari_ini_filename;
902 if (!config_file_path.empty()) {
903 size_t installpath_len;
904 std::unique_ptr<char[]> installpath =
ReadFileToMem(config_file_path, installpath_len, MAX_PATH);
906 if (installpath !=
nullptr && installpath_len > 0) {
907 std::string ttd_path = installpath.get();
912 ttd_path +=
"../Resources/";
936 FillValidSearchPaths(only_local_path);
939 std::string config_home;
941 if (
auto xdg_config_home =
GetEnv(
"XDG_CONFIG_HOME"); xdg_config_home.has_value()) {
942 config_home = *xdg_config_home;
943 config_home += PATHSEP;
944 config_home += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
945 }
else if (!homedir.empty()) {
947 config_home = std::move(homedir);
948 config_home += PATHSEP
".config" PATHSEP;
949 config_home += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
959 std::string config_dir;
964 if (!personal_dir.empty()) {
965 auto end = personal_dir.find_last_of(PATHSEPCHAR);
966 if (end != std::string::npos) personal_dir.erase(end + 1);
967 config_dir = std::move(personal_dir);
971 config_dir = config_home;
973 static const Searchpath new_openttd_cfg_order[] = {
978 for (
const auto &searchpath : new_openttd_cfg_order) {
989 Debug(misc, 1,
"{} found as config directory", config_dir);
992 extern std::string _hotkeys_file;
993 _hotkeys_file = config_dir +
"hotkeys.cfg";
1004 if (config_dir == config_home) {
1008 if (only_local_path) {
1023#if defined(WITH_PERSONAL_DIR)
1030 Subdirectory::Save,
Subdirectory::Autosave,
Subdirectory::Scenario,
Subdirectory::Heightmap,
Subdirectory::Baseset,
Subdirectory::NewGrf,
Subdirectory::Ai,
Subdirectory::AiLibrary,
Subdirectory::Gs,
Subdirectory::GsLibrary,
Subdirectory::Screenshot,
Subdirectory::SocialIntegration
1033 for (
const auto &default_subdir : default_subdirs) {
1041 FillValidSearchPaths(only_local_path);
1045 for (
const auto &subdir : subdirs) {
1059 for (
auto &c : filename) {
1063 case ':':
case '\\':
case '*':
case '?':
case '/':
1064 case '<':
case '>':
case '|':
case '"':
1079std::unique_ptr<char[]>
ReadFileToMem(
const std::string &filename,
size_t &lenp,
size_t maxsize)
1082 if (!in.has_value())
return nullptr;
1084 fseek(*in, 0, SEEK_END);
1085 size_t len = ftell(*in);
1086 fseek(*in, 0, SEEK_SET);
1087 if (len > maxsize)
return nullptr;
1089 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1092 if (fread(mem.get(), len, 1, *in) != 1)
return nullptr;
1106 if (extension.empty())
return true;
1107 if (filename.length() < extension.length())
return false;
1109 std::string_view filename_sv = filename;
1110 return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1123static uint
ScanPath(
FileScanner *fs, std::string_view extension,
const std::filesystem::path &path,
size_t basepath_length,
bool recursive)
1127 std::error_code error_code;
1128 for (
const auto &dir_entry : std::filesystem::directory_iterator(path, error_code)) {
1129 if (dir_entry.is_directory()) {
1130 if (!recursive)
continue;
1131 num +=
ScanPath(fs, extension, dir_entry.path(), basepath_length, recursive);
1132 }
else if (dir_entry.is_regular_file()) {
1133 std::string file =
FS2OTTD(dir_entry.path().native());
1135 if (fs->
AddFile(file, basepath_length, {})) num++;
1139 Debug(misc, 9,
"Unable to read directory {}: {}", path.string(), error_code.message());
1180 std::string path = FioGetDirectory(sp, sd);
1181 num +=
ScanPath(
this, extension,
OTTD2FS(path), path.size(), recursive);
1186 num +=
ScanTar(
this, extension, tar);
1214 std::string path(directory);
1216 return ScanPath(
this, extension,
OTTD2FS(path), path.size(), recursive);
1223 auto f = _wfopen(
OTTD2FS(filename).c_str(),
OTTD2FS(mode).c_str());
1225 auto f = fopen(filename.c_str(), std::string{mode}.c_str());
1228 if (f ==
nullptr)
return std::nullopt;
1229 return FileHandle(f);
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Helper for scanning for files with a given name.
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.
virtual bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)=0
Add a file with the given filename.
Helper for scanning for files with tar as extension.
@ 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.
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,...)
Output a line of debugging information.
std::string _log_file
Filename to reroute output of a forked OpenTTD to.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
EnumClassIndexContainer< std::array< T, to_underlying(N)>, Index > EnumIndexArray
A typedef for EnumClassIndexContainer using std::array as the backing container type.
static bool ChangeWorkingDirectoryToExecutable(std::string_view exe)
Changes the working directory to the path of the give executable.
bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
Extract the tar with the given filename in the directory where the tar resides.
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,...
static bool IsValidSearchPath(Searchpath sp)
Checks whether the given search path is a valid search path.
static const EnumIndexArray< std::string_view, Subdirectory, Subdirectory::End > _subdirs
Subdirectory names.
bool FioRemove(const std::string &filename)
Remove a file.
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
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.
void DetermineBasePaths(std::string_view exe)
Determine the base (personal dir and game data dir) paths.
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
EnumIndexArray< TarFileList, Subdirectory, Subdirectory::End > _tar_filelist
List of files within tar files found in each subdirectory.
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.
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
static bool _do_scan_working_directory
Whether the working directory should be scanned.
static std::string GetHomeDir()
Gets the home directory of the user.
static void SimplifyFileName(std::string &name)
Simplify filenames from tars.
static bool MatchesExtension(std::string_view extension, const std::string &filename)
Helper to see whether a given filename matches the extension.
EnumIndexArray< TarList, Subdirectory, Subdirectory::End > _tar_list
List of tar files found in each subdirectory.
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 ...
bool FileExists(std::string_view filename)
Test whether the given filename exists.
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.
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
static std::optional< FileHandle > FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
Opens a file from inside a tar archive.
static std::string ExtractString(std::span< char > buffer)
Helper to extract a string for the tar header.
std::string _highscore_file
The file to store the highscore data in.
bool DoScanWorkingDirectory()
Whether we should scan the working directory.
std::string _config_file
Configuration file of OpenTTD.
EnumIndexArray< std::string, Searchpath, Searchpath::End > _searchpaths
The search paths OpenTTD could search through.
std::string FioFindFullPath(Subdirectory subdir, std::string_view filename)
Find a path to the filename in one of the search directories.
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
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.
Searchpath
Types of searchpaths OpenTTD might use.
@ Begin
The lowest valid value.
@ WorkingDir
Search in the working directory.
@ AutodownloadDir
Search within the autodownload directory.
@ ApplicationBundleDir
Search within the application bundle.
@ InstallationDir
Search in the installation directory.
@ AutodownloadPersonalDir
Search within the autodownload directory located in the personal directory.
@ SharedDir
Search in the shared directory, like 'Shared Files' under Windows.
@ TransportTycoonDeluxeDir
Search within the Transport Tycoon Deluxe data directory (if installed).
@ BinaryDir
Search in the directory where the binary resides.
@ AutodownloadPersonalDirXdg
Search within the autodownload directory located in the personal directory (XDG variant).
@ PersonalDir
Search in the personal directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ Base
Base directory for all subdirectories.
@ Autosave
Subdirectory of save for autosaves.
@ NewGrf
Subdirectory for all NewGRFs.
@ Screenshot
Subdirectory for all screenshots.
@ GsLibrary
Subdirectory for all GS libraries.
@ Scenario
Base directory for all scenarios.
@ Heightmap
Subdirectory of scenario for heightmaps.
@ None
A path without any base directory.
@ Ai
Subdirectory for all AI files.
@ SocialIntegration
Subdirectory for all social integration plugins.
@ Gs
Subdirectory for all game scripts.
@ Baseset
Subdirectory for all base data (base sets, intro game).
@ Save
Base directory for all savegames.
@ OldData
Old subdirectory for the data.
@ AiLibrary
Subdirectory for all AI libraries.
@ OldGm
Old subdirectory for the music.
Declarations for savegames operations.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
A number of safeguards to prevent using unsafe methods.
std::string _secrets_file
Secrets configuration file of OpenTTD.
std::string _favs_file
Picker favourites configuration file of OpenTTD.
std::string _private_file
Private configuration file of OpenTTD.
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.
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),...
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
int StrCompareIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
Functions related to low-level strings.
Structs, typedefs and macros used for TAR file handling.
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
std::string _windows_file
Config file to store WindowDesc.