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;