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/>.
8 /** @file fileio.cpp Standard In/Out file operations */
11 #include "fileio_func.h"
14 #include "string_func.h"
18 # define access _taccess
19 #elif defined(__HAIKU__)
21 #include <storage/FindDirectory.h>
30 #include "safeguards.h"
32 /** Size of the #Fio data buffer. */
33 #define FIO_BUFFER_SIZE 512
35 /** Structure for keeping several open files with just one data buffer. */
37 byte
*buffer
, *buffer_end
; ///< position pointer in local buffer and last valid byte of buffer
38 byte buffer_start
[FIO_BUFFER_SIZE
]; ///< local buffer when read from file
39 size_t pos
; ///< current (system) position in file
40 FILE *cur_fh
; ///< current file handle
41 std::string filename
; ///< current filename
42 std::array
<FILE *, MAX_FILE_SLOTS
> handles
; ///< array of file handles we can have open
43 std::array
<std::string
, MAX_FILE_SLOTS
> filenames
; ///< array of filenames we (should) have open
44 std::array
<std::string
, MAX_FILE_SLOTS
> shortnames
;///< array of short names for spriteloader's use
47 static Fio _fio
; ///< #Fio instance.
49 /** Whether the working directory should be scanned. */
50 static bool _do_scan_working_directory
= true;
52 extern std::string _config_file
;
53 extern std::string _highscore_file
;
56 * Get position in the current file.
57 * @return Position in the file.
61 return _fio
.pos
+ (_fio
.buffer
- _fio
.buffer_end
);
65 * Get the filename associated with a slot.
66 * @param slot Index of queried file.
67 * @return Name of the file.
69 const char *FioGetFilename(uint8 slot
)
71 return _fio
.shortnames
[slot
].c_str();
75 * Seek in the current file.
76 * @param pos New position.
77 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
79 void FioSeekTo(size_t pos
, int mode
)
81 if (mode
== SEEK_CUR
) pos
+= FioGetPos();
82 _fio
.buffer
= _fio
.buffer_end
= _fio
.buffer_start
+ FIO_BUFFER_SIZE
;
84 if (fseek(_fio
.cur_fh
, _fio
.pos
, SEEK_SET
) < 0) {
85 DEBUG(misc
, 0, "Seeking in %s failed", _fio
.filename
.c_str());
90 * Switch to a different file and seek to a position.
91 * @param slot Slot number of the new file.
92 * @param pos New absolute position in the new file.
94 void FioSeekToFile(uint8 slot
, size_t pos
)
96 FILE *f
= _fio
.handles
[slot
];
99 _fio
.filename
= _fio
.filenames
[slot
];
100 FioSeekTo(pos
, SEEK_SET
);
104 * Read a byte from the file.
109 if (_fio
.buffer
== _fio
.buffer_end
) {
110 _fio
.buffer
= _fio
.buffer_start
;
111 size_t size
= fread(_fio
.buffer
, 1, FIO_BUFFER_SIZE
, _fio
.cur_fh
);
113 _fio
.buffer_end
= _fio
.buffer_start
+ size
;
115 if (size
== 0) return 0;
117 return *_fio
.buffer
++;
121 * Skip \a n bytes ahead in the file.
122 * @param n Number of bytes to skip reading.
124 void FioSkipBytes(int n
)
127 int m
= std::min
<int>(_fio
.buffer_end
- _fio
.buffer
, n
);
137 * Read a word (16 bits) from the file (in low endian format).
142 byte b
= FioReadByte();
143 return (FioReadByte() << 8) | b
;
147 * Read a double word (32 bits) from the file (in low endian format).
150 uint32
FioReadDword()
152 uint b
= FioReadWord();
153 return (FioReadWord() << 16) | b
;
158 * @param ptr Destination buffer.
159 * @param size Number of bytes to read.
161 void FioReadBlock(void *ptr
, size_t size
)
163 FioSeekTo(FioGetPos(), SEEK_SET
);
164 _fio
.pos
+= fread(ptr
, 1, size
, _fio
.cur_fh
);
168 * Close the file at the given slot number.
169 * @param slot File index to close.
171 static inline void FioCloseFile(int slot
)
173 if (_fio
.handles
[slot
] != nullptr) {
174 fclose(_fio
.handles
[slot
]);
176 _fio
.shortnames
[slot
].clear();
178 _fio
.handles
[slot
] = nullptr;
182 /** Close all slotted open files. */
185 for (int i
= 0; i
!= lengthof(_fio
.handles
); i
++) {
191 * Open a slotted file.
192 * @param slot Index to assign.
193 * @param filename Name of the file at the disk.
194 * @param subdir The sub directory to search this file in.
196 void FioOpenFile(int slot
, const std::string
&filename
, Subdirectory subdir
)
200 f
= FioFOpenFile(filename
, "rb", subdir
);
201 if (f
== nullptr) usererror("Cannot open file '%s'", filename
.c_str());
203 if (pos
< 0) usererror("Cannot read file '%s'", filename
.c_str());
205 FioCloseFile(slot
); // if file was opened before, close it
206 _fio
.handles
[slot
] = f
;
207 _fio
.filenames
[slot
] = filename
;
209 /* Store the filename without path and extension */
210 auto t
= filename
.rfind(PATHSEPCHAR
);
211 std::string sn
= filename
.substr(t
!= std::string::npos
? t
+ 1 : 0);
212 _fio
.shortnames
[slot
] = sn
.substr(0, sn
.rfind('.'));
213 strtolower(_fio
.shortnames
[slot
]);
215 FioSeekToFile(slot
, (size_t)pos
);
218 static const char * const _subdirs
[] = {
221 "save" PATHSEP
"autosave" PATHSEP
,
223 "scenario" PATHSEP
"heightmap" PATHSEP
,
230 "ai" PATHSEP
"library" PATHSEP
,
232 "game" PATHSEP
"library" PATHSEP
,
233 "screenshot" PATHSEP
,
235 static_assert(lengthof(_subdirs
) == NUM_SUBDIRS
);
238 * The search paths OpenTTD could search through.
239 * At least one of the slots has to be filled with a path.
240 * An empty string tells that there is no such path for the
241 * current operating system.
243 std::array
<std::string
, NUM_SEARCHPATHS
> _searchpaths
;
244 std::array
<TarList
, NUM_SUBDIRS
> _tar_list
;
245 TarFileList _tar_filelist
[NUM_SUBDIRS
];
247 typedef std::map
<std::string
, std::string
> TarLinkList
;
248 static TarLinkList _tar_linklist
[NUM_SUBDIRS
]; ///< List of directory links
251 * Checks whether the given search path is a valid search path
252 * @param sp the search path to check
253 * @return true if the search path is valid
255 bool IsValidSearchPath(Searchpath sp
)
257 return sp
< _searchpaths
.size() && !_searchpaths
[sp
].empty();
261 * Check whether the given file exists
262 * @param filename the file to try for existence.
263 * @param subdir the subdirectory to look in
264 * @return true if and only if the file can be opened
266 bool FioCheckFileExists(const std::string
&filename
, Subdirectory subdir
)
268 FILE *f
= FioFOpenFile(filename
, "rb", subdir
);
269 if (f
== nullptr) return false;
276 * Test whether the given filename exists.
277 * @param filename the file to test.
278 * @return true if and only if the file exists.
280 bool FileExists(const std::string
&filename
)
282 return access(OTTD2FS(filename
.c_str()), 0) == 0;
286 * Close a file in a safe way.
288 void FioFCloseFile(FILE *f
)
294 * Find a path to the filename in one of the search directories.
295 * @param subdir Subdirectory to try.
296 * @param filename Filename to look for.
297 * @return String containing the path if the path was found, else an empty string.
299 std::string
FioFindFullPath(Subdirectory subdir
, const char *filename
)
302 assert(subdir
< NUM_SUBDIRS
);
304 FOR_ALL_SEARCHPATHS(sp
) {
305 std::string buf
= FioGetDirectory(sp
, subdir
);
307 if (FileExists(buf
)) return buf
;
309 /* Be, as opening files, aware that sometimes the filename
310 * might be in uppercase when it is in lowercase on the
311 * disk. Of course Windows doesn't care about casing. */
312 if (strtolower(buf
, _searchpaths
[sp
].size() - 1) && FileExists(buf
)) return buf
;
319 std::string
FioGetDirectory(Searchpath sp
, Subdirectory subdir
)
321 assert(subdir
< NUM_SUBDIRS
);
322 assert(sp
< NUM_SEARCHPATHS
);
324 return _searchpaths
[sp
] + _subdirs
[subdir
];
327 std::string
FioFindDirectory(Subdirectory subdir
)
331 /* Find and return the first valid directory */
332 FOR_ALL_SEARCHPATHS(sp
) {
333 std::string ret
= FioGetDirectory(sp
, subdir
);
334 if (FileExists(ret
)) return ret
;
337 /* Could not find the directory, fall back to a base path */
338 return _personal_dir
;
341 static FILE *FioFOpenFileSp(const std::string
&filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
344 /* fopen is implemented as a define with ellipses for
345 * Unicode support (prepend an L). As we are not sending
346 * a string, but a variable, it 'renames' the variable,
347 * so make that variable to makes it compile happily */
349 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
354 if (subdir
== NO_DIRECTORY
) {
357 buf
= _searchpaths
[sp
] + _subdirs
[subdir
] + filename
;
361 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
.c_str())) == INVALID_FILE_ATTRIBUTES
) return nullptr;
364 f
= fopen(buf
.c_str(), mode
);
366 if (f
== nullptr && strtolower(buf
, subdir
== NO_DIRECTORY
? 0 : _searchpaths
[sp
].size() - 1) ) {
367 f
= fopen(buf
.c_str(), mode
);
370 if (f
!= nullptr && filesize
!= nullptr) {
371 /* Find the size of the file */
372 fseek(f
, 0, SEEK_END
);
373 *filesize
= ftell(f
);
374 fseek(f
, 0, SEEK_SET
);
380 * Opens a file from inside a tar archive.
381 * @param entry The entry to open.
382 * @param[out] filesize If not \c nullptr, size of the opened file.
383 * @return File handle of the opened file, or \c nullptr if the file is not available.
384 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
386 FILE *FioFOpenFileTar(const TarFileListEntry
&entry
, size_t *filesize
)
388 FILE *f
= fopen(entry
.tar_filename
.c_str(), "rb");
389 if (f
== nullptr) return f
;
391 if (fseek(f
, entry
.position
, SEEK_SET
) < 0) {
396 if (filesize
!= nullptr) *filesize
= entry
.size
;
401 * Opens a OpenTTD file somewhere in a personal or global directory.
402 * @param filename Name of the file to open.
403 * @param subdir Subdirectory to open.
404 * @return File handle of the opened file, or \c nullptr if the file is not available.
406 FILE *FioFOpenFile(const std::string
&filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
411 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
413 FOR_ALL_SEARCHPATHS(sp
) {
414 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
415 if (f
!= nullptr || subdir
== NO_DIRECTORY
) break;
418 /* We can only use .tar in case of data-dir, and read-mode */
419 if (f
== nullptr && mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
420 /* Filenames in tars are always forced to be lowercase */
421 std::string resolved_name
= filename
;
422 strtolower(resolved_name
);
425 std::istringstream
ss(resolved_name
);
426 std::vector
<std::string
> tokens
;
428 while (std::getline(ss
, token
, PATHSEPCHAR
)) {
430 if (tokens
.size() < 2) return nullptr;
433 tokens
.push_back(token
);
437 resolved_name
.clear();
439 for (const std::string
&token
: tokens
) {
441 resolved_name
+= PATHSEP
;
443 resolved_name
+= token
;
447 /* Resolve ONE directory link */
448 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
449 const std::string
&src
= link
->first
;
450 size_t len
= src
.length();
451 if (resolved_name
.length() >= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& src
.compare(0, len
, resolved_name
, 0, len
) == 0) {
453 resolved_name
.replace(0, len
, link
->second
);
454 break; // Only resolve one level
458 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
459 if (it
!= _tar_filelist
[subdir
].end()) {
460 f
= FioFOpenFileTar(it
->second
, filesize
);
464 /* Sometimes a full path is given. To support
465 * the 'subdirectory' must be 'removed'. */
466 if (f
== nullptr && subdir
!= NO_DIRECTORY
) {
469 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
470 if (f
!= nullptr) break;
473 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
477 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
486 * Create a directory with the given name
487 * If the parent directory does not exist, it will try to create that as well.
488 * @param name the new name of the directory
490 void FioCreateDirectory(const std::string
&name
)
492 auto p
= name
.find_last_of(PATHSEPCHAR
);
493 if (p
!= std::string::npos
) {
494 std::string dirname
= name
.substr(0, p
);
495 DIR *dir
= ttd_opendir(dirname
.c_str());
496 if (dir
== nullptr) {
497 FioCreateDirectory(dirname
); // Try creating the parent directory, if we couldn't open it
503 /* Ignore directory creation errors; they'll surface later on, and most
504 * of the time they are 'directory already exists' errors anyhow. */
506 CreateDirectory(OTTD2FS(name
.c_str()), nullptr);
507 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
508 mkdir(OTTD2FS(name
.c_str()));
510 mkdir(OTTD2FS(name
.c_str()), 0755);
515 * Appends, if necessary, the path separator character to the end of the string.
516 * It does not add the path separator to zero-sized strings.
517 * @param buf string to append the separator to
518 * @return true iff the operation succeeded
520 void AppendPathSeparator(std::string
&buf
)
522 if (buf
.empty()) return;
524 if (buf
.back() != PATHSEPCHAR
) buf
.push_back(PATHSEPCHAR
);
527 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
529 std::string src
= srcParam
;
530 std::string dest
= destParam
;
531 /* Tar internals assume lowercase */
532 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
533 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
535 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
536 if (dest_file
!= _tar_filelist
[subdir
].end()) {
537 /* Link to file. Process the link like the destination file. */
538 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
540 /* Destination file not found. Assume 'link to directory'
541 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
542 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
543 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
544 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
549 * Simplify filenames from tars.
550 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
551 * @param name Filename to process.
553 static void SimplifyFileName(char *name
)
555 /* Force lowercase */
558 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
559 #if (PATHSEPCHAR != '/')
560 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
565 * Perform the scanning of a particular subdirectory.
566 * @param sd The subdirectory to scan.
567 * @return The number of found tar files.
569 uint
TarScanner::DoScan(Subdirectory sd
)
571 _tar_filelist
[sd
].clear();
572 _tar_list
[sd
].clear();
573 uint num
= this->Scan(".tar", sd
, false);
574 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
578 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
580 DEBUG(misc
, 1, "Scanning for tars");
583 if (mode
& TarScanner::BASESET
) {
584 num
+= fs
.DoScan(BASESET_DIR
);
586 if (mode
& TarScanner::NEWGRF
) {
587 num
+= fs
.DoScan(NEWGRF_DIR
);
589 if (mode
& TarScanner::AI
) {
590 num
+= fs
.DoScan(AI_DIR
);
591 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
593 if (mode
& TarScanner::GAME
) {
594 num
+= fs
.DoScan(GAME_DIR
);
595 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
597 if (mode
& TarScanner::SCENARIO
) {
598 num
+= fs
.DoScan(SCENARIO_DIR
);
599 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
601 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
606 * Add a single file to the scanned files of a tar, circumventing the scanning code.
607 * @param sd The sub directory the file is in.
608 * @param filename The name of the file to add.
609 * @return True if the additions went correctly.
611 bool TarScanner::AddFile(Subdirectory sd
, const std::string
&filename
)
614 return this->AddFile(filename
, 0);
617 bool TarScanner::AddFile(const std::string
&filename
, size_t basepath_length
, const std::string
&tar_filename
)
619 /* No tar within tar. */
620 assert(tar_filename
.empty());
622 /* The TAR-header, repeated for every file */
624 char name
[100]; ///< Name of the file
628 char size
[12]; ///< Size of the file, in ASCII
639 char prefix
[155]; ///< Path of the file
644 /* Check if we already seen this file */
645 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
646 if (it
!= _tar_list
[this->subdir
].end()) return false;
648 FILE *f
= fopen(filename
.c_str(), "rb");
649 /* Although the file has been found there can be
650 * a number of reasons we cannot open the file.
651 * Most common case is when we simply have not
652 * been given read access. */
653 if (f
== nullptr) return false;
655 _tar_list
[this->subdir
][filename
] = std::string
{};
657 TarLinkList links
; ///< Temporary list to collect links
660 char buf
[sizeof(th
.name
) + 1], *end
;
661 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
662 char link
[sizeof(th
.linkname
) + 1];
663 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
664 size_t num
= 0, pos
= 0;
666 /* Make a char of 512 empty bytes */
668 memset(&empty
[0], 0, sizeof(empty
));
670 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
671 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
672 if (num_bytes_read
!= 512) break;
673 pos
+= num_bytes_read
;
675 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
676 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
677 /* If we have only zeros in the block, it can be an end-of-file indicator */
678 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
680 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
.c_str());
687 /* The prefix contains the directory-name */
688 if (th
.prefix
[0] != '\0') {
689 strecpy(name
, th
.prefix
, lastof(name
));
690 strecat(name
, PATHSEP
, lastof(name
));
693 /* Copy the name of the file in a safe way at the end of 'name' */
694 strecat(name
, th
.name
, lastof(name
));
696 /* Calculate the size of the file.. for some strange reason this is stored as a string */
697 strecpy(buf
, th
.size
, lastof(buf
));
698 size_t skip
= strtoul(buf
, &end
, 8);
700 switch (th
.typeflag
) {
702 case '0': { // regular file
703 /* Ignore empty files */
704 if (skip
== 0) break;
706 if (strlen(name
) == 0) break;
708 /* Store this entry in the list */
709 TarFileListEntry entry
;
710 entry
.tar_filename
= filename
;
712 entry
.position
= pos
;
714 /* Convert to lowercase and our PATHSEPCHAR */
715 SimplifyFileName(name
);
717 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
718 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
723 case '1': // hard links
724 case '2': { // symbolic links
725 /* Copy the destination of the link in a safe way at the end of 'linkname' */
726 strecpy(link
, th
.linkname
, lastof(link
));
728 if (strlen(name
) == 0 || strlen(link
) == 0) break;
730 /* Convert to lowercase and our PATHSEPCHAR */
731 SimplifyFileName(name
);
732 SimplifyFileName(link
);
734 /* Only allow relative links */
735 if (link
[0] == PATHSEPCHAR
) {
736 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
740 /* Process relative path.
741 * Note: The destination of links must not contain any directory-links. */
742 strecpy(dest
, name
, lastof(dest
));
743 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
744 if (destpos
== nullptr) destpos
= dest
;
748 while (*pos
!= '\0') {
749 char *next
= strchr(pos
, PATHSEPCHAR
);
750 if (next
== nullptr) {
751 next
= pos
+ strlen(pos
);
753 /* Terminate the substring up to the path separator character. */
757 if (strcmp(pos
, ".") == 0) {
758 /* Skip '.' (current dir) */
759 } else if (strcmp(pos
, "..") == 0) {
761 if (dest
[0] == '\0') {
762 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
766 /* Truncate 'dest' after last PATHSEPCHAR.
767 * This assumes that the truncated part is a real directory and not a link. */
768 destpos
= strrchr(dest
, PATHSEPCHAR
);
769 if (destpos
== nullptr) destpos
= dest
;
772 /* Append at end of 'dest' */
773 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
774 destpos
= strecpy(destpos
, pos
, lastof(dest
));
777 if (destpos
>= lastof(dest
)) {
778 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
.c_str());
786 /* Store links in temporary list */
787 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
788 links
.insert(TarLinkList::value_type(name
, dest
));
793 case '5': // directory
794 /* Convert to lowercase and our PATHSEPCHAR */
795 SimplifyFileName(name
);
797 /* Store the first directory name we detect */
798 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
799 if (_tar_list
[this->subdir
][filename
].empty()) _tar_list
[this->subdir
][filename
] = name
;
803 /* Ignore other types */
807 /* Skip to the next block.. */
808 skip
= Align(skip
, 512);
809 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
810 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
.c_str());
817 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
.c_str(), num
);
820 /* Resolve file links and store directory links.
821 * We restrict usage of links to two cases:
822 * 1) Links to directories:
823 * Both the source path and the destination path must NOT contain any further links.
824 * When resolving files at most one directory link is resolved.
826 * The destination path must NOT contain any links.
827 * The source path may contain one directory link.
829 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
830 const std::string
&src
= link
->first
;
831 const std::string
&dest
= link
->second
;
832 TarAddLink(src
, dest
, this->subdir
);
839 * Extract the tar with the given filename in the directory
840 * where the tar resides.
841 * @param tar_filename the name of the tar to extract.
842 * @param subdir The sub directory the tar is in.
843 * @return false on failure.
845 bool ExtractTar(const std::string
&tar_filename
, Subdirectory subdir
)
847 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
848 /* We don't know the file. */
849 if (it
== _tar_list
[subdir
].end()) return false;
851 const auto &dirname
= (*it
).second
;
853 /* The file doesn't have a sub directory! */
854 if (dirname
.empty()) {
855 DEBUG(misc
, 1, "Extracting %s failed; archive rejected, the contents must be in a sub directory", tar_filename
.c_str());
859 std::string filename
= tar_filename
;
860 auto p
= filename
.find_last_of(PATHSEPCHAR
);
861 /* The file's path does not have a separator? */
862 if (p
== std::string::npos
) return false;
864 filename
.replace(p
+ 1, std::string::npos
, dirname
);
865 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
.c_str(), filename
.c_str());
866 FioCreateDirectory(filename
);
868 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
869 if (tar_filename
!= it2
->second
.tar_filename
) continue;
871 filename
.replace(p
+ 1, std::string::npos
, it2
->first
);
873 DEBUG(misc
, 9, " extracting %s", filename
.c_str());
875 /* First open the file in the .tar. */
877 std::unique_ptr
<FILE, FileDeleter
> in(FioFOpenFileTar(it2
->second
, &to_copy
));
879 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
.c_str(), tar_filename
.c_str());
883 /* Now open the 'output' file. */
884 std::unique_ptr
<FILE, FileDeleter
> out(fopen(filename
.c_str(), "wb"));
886 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
.c_str(), filename
.c_str());
890 /* Now read from the tar and write it into the file. */
893 for (; to_copy
!= 0; to_copy
-= read
) {
894 read
= fread(buffer
, 1, std::min(to_copy
, lengthof(buffer
)), in
.get());
895 if (read
<= 0 || fwrite(buffer
, 1, read
, out
.get()) != read
) break;
899 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
.c_str(), (int)to_copy
);
904 DEBUG(misc
, 9, " extraction successful");
910 * Determine the base (personal dir and game data dir) paths
911 * @param exe the path from the current path to the executable
912 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
914 extern void DetermineBasePaths(const char *exe
);
915 #else /* defined(_WIN32) */
918 * Changes the working directory to the path of the give executable.
919 * For OSX application bundles '.app' is the required extension of the bundle,
920 * so when we crop the path to there, when can remove the name of the bundle
921 * in the same way we remove the name from the executable name.
922 * @param exe the path to the executable
924 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
927 strecpy(tmp
, exe
, lastof(tmp
));
929 bool success
= false;
931 char *app_bundle
= strchr(tmp
, '.');
932 while (app_bundle
!= nullptr && strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
934 if (app_bundle
!= nullptr) *app_bundle
= '\0';
935 #endif /* WITH_COCOA */
936 char *s
= strrchr(tmp
, PATHSEPCHAR
);
939 if (chdir(tmp
) != 0) {
940 DEBUG(misc
, 0, "Directory with the binary does not exist?");
949 * Whether we should scan the working directory.
950 * It should not be scanned if it's the root or
951 * the home directory as in both cases a big data
952 * directory can cause huge amounts of unrelated
953 * files scanned. Furthermore there are nearly no
954 * use cases for the home/root directory to have
955 * OpenTTD directories.
956 * @return true if it should be scanned.
958 bool DoScanWorkingDirectory()
960 /* No working directory, so nothing to do. */
961 if (_searchpaths
[SP_WORKING_DIR
].empty()) return false;
963 /* Working directory is root, so do nothing. */
964 if (_searchpaths
[SP_WORKING_DIR
] == PATHSEP
) return false;
966 /* No personal/home directory, so the working directory won't be that. */
967 if (_searchpaths
[SP_PERSONAL_DIR
].empty()) return true;
969 std::string tmp
= _searchpaths
[SP_WORKING_DIR
] + PERSONAL_DIR
;
970 AppendPathSeparator(tmp
);
972 return _searchpaths
[SP_PERSONAL_DIR
] != tmp
;
976 * Gets the home directory of the user.
977 * May return an empty string in the unlikely scenario that the home directory cannot be found.
978 * @return User's home directory
980 static std::string
GetHomeDir()
984 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
985 return std::string(path
.Path());
987 const char *home_env
= getenv("HOME"); // Stack var, shouldn't be freed
988 if (home_env
!= nullptr) return std::string(home_env
);
990 const struct passwd
*pw
= getpwuid(getuid());
991 if (pw
!= nullptr) return std::string(pw
->pw_dir
);
997 * Determine the base (personal dir and game data dir) paths
998 * @param exe the path to the executable
1000 void DetermineBasePaths(const char *exe
)
1003 const std::string homedir
= GetHomeDir();
1005 const char *xdg_data_home
= getenv("XDG_DATA_HOME");
1006 if (xdg_data_home
!= nullptr) {
1007 tmp
= xdg_data_home
;
1009 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1010 AppendPathSeparator(tmp
);
1011 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
1013 tmp
+= "content_download";
1014 AppendPathSeparator(tmp
);
1015 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
1016 } else if (!homedir
.empty()) {
1018 tmp
+= PATHSEP
".local" PATHSEP
"share" PATHSEP
;
1019 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1020 AppendPathSeparator(tmp
);
1021 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
1023 tmp
+= "content_download";
1024 AppendPathSeparator(tmp
);
1025 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
1027 _searchpaths
[SP_PERSONAL_DIR_XDG
].clear();
1028 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
].clear();
1032 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1033 _searchpaths
[SP_PERSONAL_DIR
].clear();
1035 if (!homedir
.empty()) {
1038 tmp
+= PERSONAL_DIR
;
1039 AppendPathSeparator(tmp
);
1040 _searchpaths
[SP_PERSONAL_DIR
] = tmp
;
1042 tmp
+= "content_download";
1043 AppendPathSeparator(tmp
);
1044 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
] = tmp
;
1046 _searchpaths
[SP_PERSONAL_DIR
].clear();
1047 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
].clear();
1051 #if defined(WITH_SHARED_DIR)
1053 AppendPathSeparator(tmp
);
1054 _searchpaths
[SP_SHARED_DIR
] = tmp
;
1056 _searchpaths
[SP_SHARED_DIR
].clear();
1060 if (getcwd(cwd
, MAX_PATH
) == nullptr) *cwd
= '\0';
1062 if (_config_file
.empty()) {
1063 /* Get the path to working directory of OpenTTD. */
1065 AppendPathSeparator(tmp
);
1066 _searchpaths
[SP_WORKING_DIR
] = tmp
;
1068 _do_scan_working_directory
= DoScanWorkingDirectory();
1070 /* Use the folder of the config file as working directory. */
1071 size_t end
= _config_file
.find_last_of(PATHSEPCHAR
);
1072 if (end
== std::string::npos
) {
1073 /* _config_file is not in a folder, so use current directory. */
1075 AppendPathSeparator(tmp
);
1076 _searchpaths
[SP_WORKING_DIR
] = tmp
;
1078 _searchpaths
[SP_WORKING_DIR
] = _config_file
.substr(0, end
+ 1);
1082 /* Change the working directory to that one of the executable */
1083 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1085 if (getcwd(buf
, lengthof(buf
)) == nullptr) {
1090 AppendPathSeparator(tmp
);
1091 _searchpaths
[SP_BINARY_DIR
] = tmp
;
1093 _searchpaths
[SP_BINARY_DIR
].clear();
1096 if (cwd
[0] != '\0') {
1097 /* Go back to the current working directory. */
1098 if (chdir(cwd
) != 0) {
1099 DEBUG(misc
, 0, "Failed to return to working directory!");
1103 #if !defined(GLOBAL_DATA_DIR)
1104 _searchpaths
[SP_INSTALLATION_DIR
].clear();
1106 tmp
= GLOBAL_DATA_DIR
;
1107 AppendPathSeparator(tmp
);
1108 _searchpaths
[SP_INSTALLATION_DIR
] = tmp
;
1111 extern void CocoaSetApplicationBundleDir();
1112 CocoaSetApplicationBundleDir();
1114 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
].clear();
1117 #endif /* defined(_WIN32) */
1119 std::string _personal_dir
;
1122 * Acquire the base paths (personal dir and game data dir),
1123 * fill all other paths (save dir, autosave dir etc) and
1124 * make the save and scenario directories.
1125 * @param exe the path from the current path to the executable
1127 void DeterminePaths(const char *exe
)
1129 DetermineBasePaths(exe
);
1132 std::string config_home
;
1133 const std::string homedir
= GetHomeDir();
1134 const char *xdg_config_home
= getenv("XDG_CONFIG_HOME");
1135 if (xdg_config_home
!= nullptr) {
1136 config_home
= xdg_config_home
;
1137 config_home
+= PATHSEP
;
1138 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1139 } else if (!homedir
.empty()) {
1140 /* Defaults to ~/.config */
1141 config_home
= homedir
;
1142 config_home
+= PATHSEP
".config" PATHSEP
;
1143 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1145 AppendPathSeparator(config_home
);
1149 FOR_ALL_SEARCHPATHS(sp
) {
1150 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1151 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
].c_str());
1154 std::string config_dir
;
1155 if (!_config_file
.empty()) {
1156 config_dir
= _searchpaths
[SP_WORKING_DIR
];
1158 std::string personal_dir
= FioFindFullPath(BASE_DIR
, "openttd.cfg");
1159 if (!personal_dir
.empty()) {
1160 auto end
= personal_dir
.find_last_of(PATHSEPCHAR
);
1161 if (end
!= std::string::npos
) personal_dir
.erase(end
+ 1);
1162 config_dir
= personal_dir
;
1165 /* No previous configuration file found. Use the configuration folder from XDG. */
1166 config_dir
= config_home
;
1168 static const Searchpath new_openttd_cfg_order
[] = {
1169 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1173 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1174 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1175 config_dir
= _searchpaths
[new_openttd_cfg_order
[i
]];
1181 _config_file
= config_dir
+ "openttd.cfg";
1184 DEBUG(misc
, 3, "%s found as config directory", config_dir
.c_str());
1186 _highscore_file
= config_dir
+ "hs.dat";
1187 extern std::string _hotkeys_file
;
1188 _hotkeys_file
= config_dir
+ "hotkeys.cfg";
1189 extern std::string _windows_file
;
1190 _windows_file
= config_dir
+ "windows.cfg";
1193 if (config_dir
== config_home
) {
1194 /* We are using the XDG configuration home for the config file,
1195 * then store the rest in the XDG data home folder. */
1196 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1200 _personal_dir
= config_dir
;
1203 /* Make the necessary folders */
1204 FioCreateDirectory(config_dir
);
1205 #if defined(WITH_PERSONAL_DIR)
1206 FioCreateDirectory(_personal_dir
);
1209 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
.c_str());
1211 static const Subdirectory default_subdirs
[] = {
1212 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1215 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1216 FioCreateDirectory(_personal_dir
+ _subdirs
[default_subdirs
[i
]]);
1219 /* If we have network we make a directory for the autodownloading of content */
1220 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = _personal_dir
+ "content_download" PATHSEP
;
1221 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1223 /* Create the directory for each of the types of content */
1224 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1225 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1226 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR
, dirs
[i
]));
1229 extern std::string _log_file
;
1230 _log_file
= _personal_dir
+ "openttd.log";
1234 * Sanitizes a filename, i.e. removes all illegal characters from it.
1235 * @param filename the "\0" terminated filename
1237 void SanitizeFilename(char *filename
)
1239 for (; *filename
!= '\0'; filename
++) {
1240 switch (*filename
) {
1241 /* The following characters are not allowed in filenames
1242 * on at least one of the supported operating systems: */
1243 case ':': case '\\': case '*': case '?': case '/':
1244 case '<': case '>': case '|': case '"':
1252 * Load a file into memory.
1253 * @param filename Name of the file to load.
1254 * @param[out] lenp Length of loaded data.
1255 * @param maxsize Maximum size to load.
1256 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1257 * @note If \a maxsize less than the length of the file, loading fails.
1259 std::unique_ptr
<char> ReadFileToMem(const std::string
&filename
, size_t &lenp
, size_t maxsize
)
1261 FILE *in
= fopen(filename
.c_str(), "rb");
1262 if (in
== nullptr) return nullptr;
1266 fseek(in
, 0, SEEK_END
);
1267 size_t len
= ftell(in
);
1268 fseek(in
, 0, SEEK_SET
);
1269 if (len
> maxsize
) return nullptr;
1271 /* std::unique_ptr assumes new/delete unless a custom deleter is supplied.
1272 * As we don't want to have to carry that deleter all over the place, use
1273 * new directly to allocate the memory instead of malloc. */
1274 std::unique_ptr
<char> mem(static_cast<char *>(::operator new(len
+ 1)));
1277 if (fread(mem
.get(), len
, 1, in
) != 1) return nullptr;
1284 * Helper to see whether a given filename matches the extension.
1285 * @param extension The extension to look for.
1286 * @param filename The filename to look in for the extension.
1287 * @return True iff the extension is nullptr, or the filename ends with it.
1289 static bool MatchesExtension(const char *extension
, const char *filename
)
1291 if (extension
== nullptr) return true;
1293 const char *ext
= strrchr(filename
, extension
[0]);
1294 return ext
!= nullptr && strcasecmp(ext
, extension
) == 0;
1298 * Scan a single directory (and recursively its children) and add
1299 * any graphics sets that are found.
1300 * @param fs the file scanner to add the files to
1301 * @param extension the extension of files to search for.
1302 * @param path full path we're currently at
1303 * @param basepath_length from where in the path are we 'based' on the search path
1304 * @param recursive whether to recursively search the sub directories
1306 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1308 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1312 struct dirent
*dirent
;
1315 if (path
== nullptr || (dir
= ttd_opendir(path
)) == nullptr) return 0;
1317 while ((dirent
= readdir(dir
)) != nullptr) {
1318 const char *d_name
= FS2OTTD(dirent
->d_name
);
1320 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1322 std::string
filename(path
);
1325 if (S_ISDIR(sb
.st_mode
)) {
1327 if (!recursive
) continue;
1328 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1329 AppendPathSeparator(filename
);
1330 num
+= ScanPath(fs
, extension
, filename
.c_str(), basepath_length
, recursive
);
1331 } else if (S_ISREG(sb
.st_mode
)) {
1333 if (MatchesExtension(extension
, filename
.c_str()) && fs
->AddFile(filename
, basepath_length
, {})) num
++;
1343 * Scan the given tar and add graphics sets when it finds one.
1344 * @param fs the file scanner to scan for
1345 * @param extension the extension of files to search for.
1346 * @param tar the tar to search in.
1348 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1351 const auto &filename
= (*tar
).first
;
1353 if (MatchesExtension(extension
, filename
.c_str()) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1359 * Scan for files with the given extension in the given search path.
1360 * @param extension the extension of files to search for.
1361 * @param sd the sub directory to search in.
1362 * @param tars whether to search in the tars too.
1363 * @param recursive whether to search recursively
1364 * @return the number of found files, i.e. the number of times that
1365 * AddFile returned true.
1367 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1372 TarFileList::iterator tar
;
1375 FOR_ALL_SEARCHPATHS(sp
) {
1376 /* Don't search in the working directory */
1377 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1379 std::string path
= FioGetDirectory(sp
, sd
);
1380 num
+= ScanPath(this, extension
, path
.c_str(), path
.size(), recursive
);
1383 if (tars
&& sd
!= NO_DIRECTORY
) {
1384 FOR_ALL_TARS(tar
, sd
) {
1385 num
+= ScanTar(this, extension
, tar
);
1391 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1394 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1404 * Scan for files with the given extension in the given search path.
1405 * @param extension the extension of files to search for.
1406 * @param directory the sub directory to search in.
1407 * @param recursive whether to search recursively
1408 * @return the number of found files, i.e. the number of times that
1409 * AddFile returned true.
1411 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1413 std::string
path(directory
);
1414 AppendPathSeparator(path
);
1415 return ScanPath(this, extension
, path
.c_str(), path
.size(), recursive
);