19 # define access _taccess
20 #elif defined(__HAIKU__)
22 #include <storage/FindDirectory.h>
40 static const char *
const _subdirs[] = {
43 "save" PATHSEP
"autosave" PATHSEP,
45 "scenario" PATHSEP
"heightmap" PATHSEP,
52 "ai" PATHSEP
"library" PATHSEP,
54 "game" PATHSEP
"library" PATHSEP,
56 "social_integration" PATHSEP,
67 std::vector<Searchpath> _valid_searchpaths;
68 std::array<TarList, NUM_SUBDIRS> _tar_list;
71 typedef std::map<std::string, std::string> TarLinkList;
74 extern bool FiosIsValidFile(
const std::string &path,
const struct dirent *ent,
struct stat *sb);
86 static void FillValidSearchPaths(
bool only_local_path)
88 _valid_searchpaths.clear();
90 std::set<std::string> seen{};
91 for (
Searchpath sp = SP_FIRST_DIR; sp < NUM_SEARCHPATHS; sp++) {
94 if (only_local_path) {
109 _valid_searchpaths.emplace_back(sp);
117 _valid_searchpaths.insert(_valid_searchpaths.begin(),
SP_WORKING_DIR);
130 if (f ==
nullptr)
return false;
143 return access(
OTTD2FS(filename).c_str(), 0) == 0;
165 std::string buf = FioGetDirectory(sp, subdir);
182 assert(sp < NUM_SEARCHPATHS);
191 std::string ret = FioGetDirectory(sp, subdir);
199 static FILE *FioFOpenFileSp(
const std::string &filename,
const char *mode,
Searchpath sp,
Subdirectory subdir,
size_t *filesize)
207 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode,
lengthof(Lmode));
219 if (mode[0] ==
'r' && GetFileAttributes(
OTTD2FS(buf).c_str()) == INVALID_FILE_ATTRIBUTES)
return nullptr;
222 f = fopen(buf.c_str(), mode);
225 f = fopen(buf.c_str(), mode);
228 if (f !=
nullptr && filesize !=
nullptr) {
230 fseek(f, 0, SEEK_END);
231 *filesize = ftell(f);
232 fseek(f, 0, SEEK_SET);
246 FILE *f = fopen(entry.tar_filename.c_str(),
"rb");
247 if (f ==
nullptr)
return f;
249 if (fseek(f, entry.position, SEEK_SET) < 0) {
254 if (filesize !=
nullptr) *filesize = entry.size;
271 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
276 if (f ==
nullptr && mode[0] ==
'r' && subdir !=
NO_DIRECTORY) {
278 std::string resolved_name = filename;
279 strtolower(resolved_name);
282 std::istringstream ss(resolved_name);
283 std::vector<std::string> tokens;
285 while (std::getline(ss, token, PATHSEPCHAR)) {
287 if (tokens.size() < 2)
return nullptr;
289 }
else if (token ==
".") {
293 tokens.push_back(token);
297 resolved_name.clear();
299 for (
const std::string &token : tokens) {
301 resolved_name += PATHSEP;
303 resolved_name += token;
309 const std::string &src = link.first;
310 size_t len = src.length();
311 if (resolved_name.length() >= len && resolved_name[len - 1] == PATHSEPCHAR && src.compare(0, len, resolved_name, 0, len) == 0) {
313 resolved_name.replace(0, len, link.second);
318 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
319 if (it != _tar_filelist[subdir].end()) {
330 if (f !=
nullptr)
break;
352 auto p = name.find_last_of(PATHSEPCHAR);
353 if (p != std::string::npos) {
354 std::string dirname = name.substr(0, p);
356 if (dir ==
nullptr) {
366 CreateDirectory(
OTTD2FS(name).c_str(),
nullptr);
368 mkdir(
OTTD2FS(name).c_str(), 0755);
380 if (buf.empty())
return;
382 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
385 static void TarAddLink(
const std::string &srcParam,
const std::string &destParam,
Subdirectory subdir)
387 std::string src = srcParam;
388 std::string dest = destParam;
390 std::transform(src.begin(), src.end(), src.begin(), tolower);
391 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
393 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
394 if (dest_file != _tar_filelist[subdir].end()) {
396 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
400 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
401 const std::string dst_path = (dest.length() == 0 ?
"" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
402 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
413 for (
char &c : name) {
416 #if (PATHSEPCHAR != '/')
418 if (c ==
'/') c = PATHSEPCHAR;
430 _tar_filelist[sd].clear();
431 _tar_list[sd].clear();
432 uint num = this->
Scan(
".tar", sd,
false);
439 Debug(misc, 2,
"Scanning for tars");
460 Debug(misc, 2,
"Scan complete, found {} files", num);
473 return this->
AddFile(filename, 0);
490 for (; length < buffer_length && buffer[length] !=
'\0'; length++) {}
494 bool TarScanner::AddFile(
const std::string &filename,
size_t, [[maybe_unused]]
const std::string &tar_filename)
497 assert(tar_filename.empty());
522 TarList::iterator it = _tar_list[this->
subdir].find(filename);
523 if (it != _tar_list[this->subdir].end())
return false;
525 FILE *f = fopen(filename.c_str(),
"rb");
530 if (f ==
nullptr)
return false;
532 _tar_list[this->
subdir][filename] = std::string{};
534 std::string filename_base = std::filesystem::path(filename).filename().string();
540 size_t num = 0, pos = 0;
544 memset(&empty[0], 0,
sizeof(empty));
547 size_t num_bytes_read = fread(&th, 1, 512, f);
548 if (num_bytes_read != 512)
break;
549 pos += num_bytes_read;
552 if (strncmp(th.magic,
"ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
554 if (memcmp(&th, &empty[0], 512) == 0)
continue;
556 Debug(misc, 0,
"The file '{}' isn't a valid tar-file", filename);
564 if (th.prefix[0] !=
'\0') {
577 auto [_, err] = std::from_chars(size.data(), size.data() + size.size(), skip, 8);
578 if (err != std::errc()) {
579 Debug(misc, 0,
"The file '{}' has an invalid size for '{}'", filename, name);
585 switch (th.typeflag) {
588 if (name.empty())
break;
592 entry.tar_filename = filename;
594 entry.position = pos;
599 Debug(misc, 6,
"Found file in tar: {} ({} bytes, {} offset)", name, skip, pos);
600 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(filename_base + PATHSEPCHAR + name, entry)).second) num++;
610 if (name.empty() || link.empty())
break;
617 if (link[0] == PATHSEPCHAR) {
618 Debug(misc, 5,
"Ignoring absolute link in tar: {} -> {}", name, link);
624 std::string dest = (std::filesystem::path(name).remove_filename() /= link).lexically_normal().string();
625 if (dest[0] == PATHSEPCHAR || dest.starts_with(
"..")) {
626 Debug(misc, 5,
"Ignoring link pointing outside of data directory: {} -> {}", name, link);
631 Debug(misc, 6,
"Found link in tar: {} -> {}", name, dest);
632 links.insert(TarLinkList::value_type(filename_base + PATHSEPCHAR + name, filename_base + PATHSEPCHAR + dest));
642 Debug(misc, 6,
"Found dir in tar: {}", name);
643 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->
subdir][filename] = name;
652 skip =
Align(skip, 512);
653 if (fseek(f, skip, SEEK_CUR) < 0) {
654 Debug(misc, 0,
"The file '{}' can't be read as a valid tar-file", filename);
661 Debug(misc, 4,
"Found tar '{}' with {} new files", filename, num);
673 for (
auto &it : links) {
674 TarAddLink(it.first, it.second, this->subdir);
689 TarList::iterator it = _tar_list[subdir].find(tar_filename);
691 if (it == _tar_list[subdir].end())
return false;
693 const auto &dirname = (*it).second;
696 if (dirname.empty()) {
697 Debug(misc, 3,
"Extracting {} failed; archive rejected, the contents must be in a sub directory", tar_filename);
701 std::string filename = tar_filename;
702 auto p = filename.find_last_of(PATHSEPCHAR);
704 if (p == std::string::npos)
return false;
706 filename.replace(p + 1, std::string::npos, dirname);
707 Debug(misc, 8,
"Extracting {} to directory {}", tar_filename, filename);
710 for (
auto &it2 : _tar_filelist[subdir]) {
711 if (tar_filename != it2.second.tar_filename)
continue;
714 std::string_view name = it2.first;
715 name.remove_prefix(name.find_first_of(PATHSEPCHAR) + 1);
716 filename.replace(p + 1, std::string::npos, name);
718 Debug(misc, 9,
" extracting {}", filename);
722 std::unique_ptr<FILE, FileDeleter> in(
FioFOpenFileTar(it2.second, &to_copy));
724 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, tar_filename);
729 std::unique_ptr<FILE, FileDeleter> out(fopen(filename.c_str(),
"wb"));
731 Debug(misc, 6,
"Extracting {} failed; could not open {}", filename, filename);
738 for (; to_copy != 0; to_copy -= read) {
739 read = fread(buffer, 1, std::min(to_copy,
lengthof(buffer)), in.get());
740 if (read <= 0 || fwrite(buffer, 1, read, out.get()) != read)
break;
744 Debug(misc, 6,
"Extracting {} failed; still {} bytes to copy", filename, to_copy);
749 Debug(misc, 9,
" extraction successful");
771 std::string path = exe;
774 for (
size_t pos = path.find_first_of(
'.'); pos != std::string::npos; pos = path.find_first_of(
'.', pos + 1)) {
782 size_t pos = path.find_last_of(PATHSEPCHAR);
783 if (pos == std::string::npos)
return false;
787 if (chdir(path.c_str()) != 0) {
788 Debug(misc, 0,
"Directory with the binary does not exist?");
831 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
832 return std::string(path.Path());
834 const char *home_env = std::getenv(
"HOME");
835 if (home_env !=
nullptr)
return std::string(home_env);
837 const struct passwd *pw = getpwuid(getuid());
838 if (pw !=
nullptr)
return std::string(pw->pw_dir);
852 const char *xdg_data_home = std::getenv(
"XDG_DATA_HOME");
853 if (xdg_data_home !=
nullptr) {
856 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
860 tmp +=
"content_download";
863 }
else if (!homedir.empty()) {
865 tmp += PATHSEP
".local" PATHSEP
"share" PATHSEP;
866 tmp += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
870 tmp +=
"content_download";
879 #if !defined(WITH_PERSONAL_DIR)
882 if (!homedir.empty()) {
889 tmp +=
"content_download";
898 #if defined(WITH_SHARED_DIR)
907 if (getcwd(cwd, MAX_PATH) ==
nullptr) *cwd =
'\0';
919 if (end == std::string::npos) {
932 if (getcwd(buf,
lengthof(buf)) ==
nullptr) {
943 if (cwd[0] !=
'\0') {
945 if (chdir(cwd) != 0) {
946 Debug(misc, 0,
"Failed to return to working directory!");
950 #if !defined(GLOBAL_DATA_DIR)
953 tmp = GLOBAL_DATA_DIR;
958 extern void CocoaSetApplicationBundleDir();
959 CocoaSetApplicationBundleDir();
978 FillValidSearchPaths(only_local_path);
981 std::string config_home;
983 const char *xdg_config_home = std::getenv(
"XDG_CONFIG_HOME");
984 if (xdg_config_home !=
nullptr) {
985 config_home = xdg_config_home;
986 config_home += PATHSEP;
987 config_home += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
988 }
else if (!homedir.empty()) {
990 config_home = homedir;
991 config_home += PATHSEP
".config" PATHSEP;
992 config_home += PERSONAL_DIR[0] ==
'.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1002 std::string config_dir;
1007 if (!personal_dir.empty()) {
1008 auto end = personal_dir.find_last_of(PATHSEPCHAR);
1009 if (end != std::string::npos) personal_dir.erase(end + 1);
1010 config_dir = personal_dir;
1014 config_dir = config_home;
1016 static const Searchpath new_openttd_cfg_order[] = {
1021 for (uint i = 0; i <
lengthof(new_openttd_cfg_order); i++) {
1032 Debug(misc, 1,
"{} found as config directory", config_dir);
1035 extern std::string _hotkeys_file;
1036 _hotkeys_file = config_dir +
"hotkeys.cfg";
1045 if (config_dir == config_home) {
1049 if (only_local_path) {
1064 #if defined(WITH_PERSONAL_DIR)
1071 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
1074 for (uint i = 0; i <
lengthof(default_subdirs); i++) {
1082 FillValidSearchPaths(only_local_path);
1086 for (uint i = 0; i <
lengthof(dirs); i++) {
1100 for (
auto &c : filename) {
1104 case ':':
case '\\':
case '*':
case '?':
case '/':
1105 case '<':
case '>':
case '|':
case '"':
1120 std::unique_ptr<char[]>
ReadFileToMem(
const std::string &filename,
size_t &lenp,
size_t maxsize)
1122 FILE *in = fopen(filename.c_str(),
"rb");
1123 if (in ==
nullptr)
return nullptr;
1127 fseek(in, 0, SEEK_END);
1128 size_t len = ftell(in);
1129 fseek(in, 0, SEEK_SET);
1130 if (len > maxsize)
return nullptr;
1132 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1135 if (fread(mem.get(), len, 1, in) != 1)
return nullptr;
1149 if (extension ==
nullptr)
return true;
1151 const char *ext = strrchr(filename, extension[0]);
1164 static uint
ScanPath(
FileScanner *fs,
const char *extension,
const char *path,
size_t basepath_length,
bool recursive)
1168 struct dirent *dirent;
1171 if (path ==
nullptr || (dir =
ttd_opendir(path)) ==
nullptr)
return 0;
1173 while ((dirent = readdir(dir)) !=
nullptr) {
1174 std::string d_name =
FS2OTTD(dirent->d_name);
1176 if (!FiosIsValidFile(path, dirent, &sb))
continue;
1178 std::string filename(path);
1181 if (S_ISDIR(sb.st_mode)) {
1183 if (!recursive)
continue;
1184 if (d_name ==
"." || d_name ==
"..")
continue;
1186 num +=
ScanPath(fs, extension, filename.c_str(), basepath_length, recursive);
1187 }
else if (S_ISREG(sb.st_mode)) {
1207 const auto &filename = tar.first;
1209 if (
MatchesExtension(extension, filename.c_str()) && fs->
AddFile(filename, 0, tar.second.tar_filename)) num++;
1233 std::string path = FioGetDirectory(sp, sd);
1234 num +=
ScanPath(
this, extension, path.c_str(), path.size(), recursive);
1238 for (
const auto &tar : _tar_filelist[sd]) {
1239 num +=
ScanTar(
this, extension, tar);
1267 std::string path(directory);
1269 return ScanPath(
this, extension, path.c_str(), path.size(), recursive);