19 #elif defined(__HAIKU__)
21 #include <storage/FindDirectory.h>
39 static const char *
const _subdirs[] = {
42 "save" PATHSEP
"autosave" PATHSEP,
44 "scenario" PATHSEP
"heightmap" PATHSEP,
51 "ai" PATHSEP
"library" PATHSEP,
53 "game" PATHSEP
"library" PATHSEP,
55 "social_integration" PATHSEP,
66 std::vector<Searchpath> _valid_searchpaths;
67 std::array<TarList, NUM_SUBDIRS> _tar_list;
80 static void FillValidSearchPaths(
bool only_local_path)
82 _valid_searchpaths.clear();
84 std::set<std::string> seen{};
85 for (
Searchpath sp = SP_FIRST_DIR; sp < NUM_SEARCHPATHS; sp++) {
88 if (only_local_path) {
103 _valid_searchpaths.emplace_back(sp);
111 _valid_searchpaths.insert(_valid_searchpaths.begin(),
SP_WORKING_DIR);
124 return f.has_value();
135 return std::filesystem::exists(
OTTD2FS(filename), ec);
149 std::string buf = FioGetDirectory(sp, subdir);
166 assert(sp < NUM_SEARCHPATHS);
175 std::string ret = FioGetDirectory(sp, subdir);
183 static std::optional<FileHandle> FioFOpenFileSp(
const std::string &filename,
const char *mode,
Searchpath sp,
Subdirectory subdir,
size_t *filesize)
191 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode,
static_cast<int>(std::size(Lmode)));
207 if (f.has_value() && filesize !=
nullptr) {
209 fseek(*f, 0, SEEK_END);
210 *filesize = ftell(*f);
211 fseek(*f, 0, SEEK_SET);
226 if (!f.has_value())
return std::nullopt;
228 if (fseek(*f, entry.position, SEEK_SET) < 0) {
232 if (filesize !=
nullptr) *filesize = entry.size;
244 std::optional<FileHandle> f = std::nullopt;
248 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
253 if (!f.has_value() && mode[0] ==
'r' && subdir !=
NO_DIRECTORY) {
255 std::string resolved_name = filename;
256 strtolower(resolved_name);
259 std::istringstream ss(resolved_name);
260 std::vector<std::string> tokens;
262 while (std::getline(ss, token, PATHSEPCHAR)) {
264 if (tokens.size() < 2)
return std::nullopt;
266 }
else if (token ==
".") {
270 tokens.push_back(token);
274 resolved_name.clear();
276 for (
const std::string &token : tokens) {
278 resolved_name += PATHSEP;
280 resolved_name += token;
284 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
285 if (it != _tar_filelist[subdir].end()) {
296 if (f.has_value())
break;
319 std::error_code error_code;
320 std::filesystem::create_directories(
OTTD2FS(name), error_code);
330 std::filesystem::path path =
OTTD2FS(filename);
331 std::error_code error_code;
332 std::filesystem::remove(path, error_code);
334 Debug(misc, 0,
"Removing {} failed: {}", filename, error_code.message());
348 if (buf.empty())
return;
350 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
360 for (
char &c : name) {
363 #if (PATHSEPCHAR != '/')
365 if (c ==
'/') c = PATHSEPCHAR;
377 _tar_filelist[sd].clear();
378 _tar_list[sd].clear();
379 uint num = this->
Scan(
".tar", sd,
false);
386 Debug(misc, 2,
"Scanning for tars");
407 Debug(misc, 2,
"Scan complete, found {} files", num);
420 return this->
AddFile(filename, 0);
435 return StrMakeValid(std::string_view(buffer.begin(), buffer.end()));
438 bool TarScanner::AddFile(
const std::string &filename,
size_t, [[maybe_unused]]
const std::string &tar_filename)
441 assert(tar_filename.empty());
466 TarList::iterator it = _tar_list[this->
subdir].find(filename);
467 if (it != _tar_list[this->subdir].end())
return false;
474 if (!of.has_value())
return false;
477 _tar_list[this->
subdir][filename] = std::string{};
479 std::string filename_base =
FS2OTTD(std::filesystem::path(
OTTD2FS(filename)).filename());
483 size_t num = 0, pos = 0;
487 memset(&empty[0], 0,
sizeof(empty));
490 size_t num_bytes_read = fread(&th, 1, 512, f);
491 if (num_bytes_read != 512)
break;
492 pos += num_bytes_read;
495 if (strncmp(th.magic,
"ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
497 if (memcmp(&th, &empty[0], 512) == 0)
continue;
499 Debug(misc, 0,
"The file '{}' isn't a valid tar-file", filename);
506 if (th.prefix[0] !=
'\0') {
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);
527 switch (th.typeflag) {
530 if (name.empty())
break;
534 entry.tar_filename = filename;
536 entry.position = pos;
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++;
551 Debug(misc, 5,
"Ignoring link in tar: {} -> {}", name, link);
560 Debug(misc, 6,
"Found dir in tar: {}", name);
561 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->
subdir][filename] = name;
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);
578 Debug(misc, 4,
"Found tar '{}' with {} new files", filename, num);
592 TarList::iterator it = _tar_list[subdir].find(tar_filename);
594 if (it == _tar_list[subdir].end())
return false;
596 const auto &dirname = (*it).second;
599 if (dirname.empty()) {
600 Debug(misc, 3,
"Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
604 std::string filename = tar_filename;
605 auto p = filename.find_last_of(PATHSEPCHAR);
607 if (p == std::string::npos)
return false;
609 filename.replace(p + 1, std::string::npos, dirname);
610 Debug(misc, 8,
"Extracting {} to directory {}", tar_filename, filename);
613 for (
auto &it2 : _tar_filelist[subdir]) {
614 if (tar_filename != it2.second.tar_filename)
continue;
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);
621 Debug(misc, 9,
" extracting {}", filename);
626 if (!in.has_value()) {
627 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, tar_filename);
633 if (!out.has_value()) {
634 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, filename);
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;
647 Debug(misc, 6,
"Extracting {} failed; still {} bytes to copy", filename, to_copy);
652 Debug(misc, 9,
" extraction successful");
674 std::string path = exe;
677 for (
size_t pos = path.find_first_of(
'.'); pos != std::string::npos; pos = path.find_first_of(
'.', pos + 1)) {
685 size_t pos = path.find_last_of(PATHSEPCHAR);
686 if (pos == std::string::npos)
return false;
690 if (chdir(path.c_str()) != 0) {
691 Debug(misc, 0,
"Directory with the binary does not exist?");
734 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
735 return std::string(path.Path());
737 const char *home_env = std::getenv(
"HOME");
738 if (home_env !=
nullptr)
return std::string(home_env);
740 const struct passwd *pw = getpwuid(getuid());
741 if (pw !=
nullptr)
return std::string(pw->pw_dir);
755 const char *xdg_data_home = std::getenv(
"XDG_DATA_HOME");
756 if (xdg_data_home !=
nullptr) {
759 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
763 tmp +=
"content_download";
766 }
else if (!homedir.empty()) {
768 tmp += PATHSEP
".local" PATHSEP
"share" PATHSEP;
769 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
773 tmp +=
"content_download";
782 #if !defined(WITH_PERSONAL_DIR)
785 if (!homedir.empty()) {
792 tmp +=
"content_download";
801 #if defined(WITH_SHARED_DIR)
810 if (getcwd(cwd, MAX_PATH) ==
nullptr) *cwd =
'\0';
822 if (end == std::string::npos) {
835 if (getcwd(buf,
lengthof(buf)) ==
nullptr) {
846 if (cwd[0] !=
'\0') {
848 if (chdir(cwd) != 0) {
849 Debug(misc, 0,
"Failed to return to working directory!");
853 #if !defined(GLOBAL_DATA_DIR)
856 tmp = GLOBAL_DATA_DIR;
861 extern void CocoaSetApplicationBundleDir();
862 CocoaSetApplicationBundleDir();
881 FillValidSearchPaths(only_local_path);
884 std::string config_home;
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()) {
893 config_home = homedir;
894 config_home += PATHSEP
".config" PATHSEP;
895 config_home += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
905 std::string config_dir;
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;
917 config_dir = config_home;
919 static const Searchpath new_openttd_cfg_order[] = {
924 for (
const auto &searchpath : new_openttd_cfg_order) {
935 Debug(misc, 1,
"{} found as config directory", config_dir);
938 extern std::string _hotkeys_file;
939 _hotkeys_file = config_dir +
"hotkeys.cfg";
950 if (config_dir == config_home) {
954 if (only_local_path) {
969 #if defined(WITH_PERSONAL_DIR)
976 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
979 for (
const auto &default_subdir : default_subdirs) {
987 FillValidSearchPaths(only_local_path);
991 for (
const auto &subdir : subdirs) {
1005 for (
auto &c : filename) {
1009 case ':':
case '\\':
case '*':
case '?':
case '/':
1010 case '<':
case '>':
case '|':
case '"':
1025 std::unique_ptr<char[]>
ReadFileToMem(
const std::string &filename,
size_t &lenp,
size_t maxsize)
1028 if (!in.has_value())
return nullptr;
1030 fseek(*in, 0, SEEK_END);
1031 size_t len = ftell(*in);
1032 fseek(*in, 0, SEEK_SET);
1033 if (len > maxsize)
return nullptr;
1035 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1038 if (fread(mem.get(), len, 1, *in) != 1)
return nullptr;
1052 if (extension.empty())
return true;
1053 if (filename.length() < extension.length())
return false;
1055 std::string_view filename_sv = filename;
1056 return StrCompareIgnoreCase(extension, filename_sv.substr(filename_sv.length() - extension.length())) == 0;
1068 static uint
ScanPath(
FileScanner *fs, std::string_view extension,
const std::filesystem::path &path,
size_t basepath_length,
bool recursive)
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());
1080 if (fs->
AddFile(file, basepath_length, {})) num++;
1084 Debug(misc, 9,
"Unable to read directory {}: {}", path.string(), error_code.message());
1124 std::string path = FioGetDirectory(sp, sd);
1125 num +=
ScanPath(
this, extension,
OTTD2FS(path), path.size(), recursive);
1129 for (
const auto &tar : _tar_filelist[sd]) {
1130 num +=
ScanTar(
this, extension, tar);
1156 uint
FileScanner::Scan(
const std::string_view extension,
const std::string &directory,
bool recursive)
1158 std::string path(directory);
1160 return ScanPath(
this, extension,
OTTD2FS(path), path.size(), recursive);
1170 std::optional<FileHandle>
FileHandle::Open(
const std::string &filename,
const std::string &mode)
1174 auto f = _wfopen(
OTTD2FS(filename).c_str(),
OTTD2FS(mode).c_str());
1176 auto f = fopen(filename.c_str(), mode.c_str());
1179 if (f ==
nullptr)
return std::nullopt;
static std::optional< FileHandle > Open(const std::string &filename, const std::string &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.
Subdirectory subdir
The current sub directory we are searching through.
Helper for scanning for files with tar as extension.
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Mode
The mode of tar scanning.
@ NEWGRF
Scan for non-base sets.
@ BASESET
Scan for base sets.
@ GAME
Scan for game scripts.
@ SCENARIO
Scan for scenarios and heightmaps.
@ AI
Scan for AIs and its libraries.
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.
std::string _log_file
Filename to reroute output of a forked OpenTTD to.
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename)
Find a path to the filename in one of the search directories.
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(const char *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 std::optional< FileHandle > FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
Opens a file from inside a tar archive.
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 AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
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.
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
Check whether the given file exists.
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.
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 ...
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.
static bool ChangeWorkingDirectoryToExecutable(const char *exe)
Changes the working directory to the path of the give executable.
bool FileExists(const std::string &filename)
Test whether the given filename exists.
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.
std::array< std::string, NUM_SEARCHPATHS > _searchpaths
The search paths OpenTTD could search through.
std::unique_ptr< char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
Load a file into memory.
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.
void DetermineBasePaths(const char *exe)
Determine the base (personal dir and game data dir) paths.
Functions for Standard In/Out file operations.
Searchpath
Types of searchpaths OpenTTD might use.
@ SP_SHARED_DIR
Search in the shared directory, like 'Shared Files' under Windows.
@ SP_INSTALLATION_DIR
Search in the installation directory.
@ SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
Search within the autodownload directory located in the personal directory (XDG variant)
@ SP_BINARY_DIR
Search in the directory where the binary resides.
@ SP_AUTODOWNLOAD_PERSONAL_DIR
Search within the autodownload directory located in the personal directory.
@ SP_PERSONAL_DIR
Search in the personal directory.
@ SP_WORKING_DIR
Search in the working directory.
@ SP_APPLICATION_BUNDLE_DIR
Search within the application bundle.
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ OLD_DATA_DIR
Old subdirectory for the data.
@ NO_DIRECTORY
A path without any base directory.
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
@ SOCIAL_INTEGRATION_DIR
Subdirectory for all social integration plugins.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
@ OLD_GM_DIR
Old subdirectory for the music.
@ SCENARIO_DIR
Base directory for all scenarios.
@ BASE_DIR
Base directory for all subdirectories.
@ SAVE_DIR
Base directory for all savegames.
@ NUM_SUBDIRS
Number of subdirectories.
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
@ NEWGRF_DIR
Subdirectory for all NewGRFs.
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
@ GAME_DIR
Subdirectory for all game scripts.
Declarations for savegames operations.
constexpr T Align(const T x, uint n)
Return the smallest multiple of n equal or greater than x.
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.
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.
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.
int StrCompareIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
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.
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
std::string _windows_file
Config file to store WindowDesc.