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;
432 } else if (token
== ".") {
433 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
434 * This confuses our file resolver. So, act like this folder doesn't exist. */
436 tokens
.push_back(token
);
440 resolved_name
.clear();
442 for (const std::string
&token
: tokens
) {
444 resolved_name
+= PATHSEP
;
446 resolved_name
+= token
;
450 /* Resolve ONE directory link */
451 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
452 const std::string
&src
= link
->first
;
453 size_t len
= src
.length();
454 if (resolved_name
.length() >= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& src
.compare(0, len
, resolved_name
, 0, len
) == 0) {
456 resolved_name
.replace(0, len
, link
->second
);
457 break; // Only resolve one level
461 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
462 if (it
!= _tar_filelist
[subdir
].end()) {
463 f
= FioFOpenFileTar(it
->second
, filesize
);
467 /* Sometimes a full path is given. To support
468 * the 'subdirectory' must be 'removed'. */
469 if (f
== nullptr && subdir
!= NO_DIRECTORY
) {
472 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
473 if (f
!= nullptr) break;
476 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
480 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
489 * Create a directory with the given name
490 * If the parent directory does not exist, it will try to create that as well.
491 * @param name the new name of the directory
493 void FioCreateDirectory(const std::string
&name
)
495 auto p
= name
.find_last_of(PATHSEPCHAR
);
496 if (p
!= std::string::npos
) {
497 std::string dirname
= name
.substr(0, p
);
498 DIR *dir
= ttd_opendir(dirname
.c_str());
499 if (dir
== nullptr) {
500 FioCreateDirectory(dirname
); // Try creating the parent directory, if we couldn't open it
506 /* Ignore directory creation errors; they'll surface later on, and most
507 * of the time they are 'directory already exists' errors anyhow. */
509 CreateDirectory(OTTD2FS(name
).c_str(), nullptr);
510 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
511 mkdir(OTTD2FS(name
).c_str());
513 mkdir(OTTD2FS(name
).c_str(), 0755);
518 * Appends, if necessary, the path separator character to the end of the string.
519 * It does not add the path separator to zero-sized strings.
520 * @param buf string to append the separator to
521 * @return true iff the operation succeeded
523 void AppendPathSeparator(std::string
&buf
)
525 if (buf
.empty()) return;
527 if (buf
.back() != PATHSEPCHAR
) buf
.push_back(PATHSEPCHAR
);
530 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
532 std::string src
= srcParam
;
533 std::string dest
= destParam
;
534 /* Tar internals assume lowercase */
535 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
536 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
538 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
539 if (dest_file
!= _tar_filelist
[subdir
].end()) {
540 /* Link to file. Process the link like the destination file. */
541 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
543 /* Destination file not found. Assume 'link to directory'
544 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
545 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
546 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
547 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
552 * Simplify filenames from tars.
553 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
554 * @param name Filename to process.
556 static void SimplifyFileName(char *name
)
558 /* Force lowercase */
561 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
562 #if (PATHSEPCHAR != '/')
563 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
568 * Perform the scanning of a particular subdirectory.
569 * @param sd The subdirectory to scan.
570 * @return The number of found tar files.
572 uint
TarScanner::DoScan(Subdirectory sd
)
574 _tar_filelist
[sd
].clear();
575 _tar_list
[sd
].clear();
576 uint num
= this->Scan(".tar", sd
, false);
577 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
581 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
583 DEBUG(misc
, 1, "Scanning for tars");
586 if (mode
& TarScanner::BASESET
) {
587 num
+= fs
.DoScan(BASESET_DIR
);
589 if (mode
& TarScanner::NEWGRF
) {
590 num
+= fs
.DoScan(NEWGRF_DIR
);
592 if (mode
& TarScanner::AI
) {
593 num
+= fs
.DoScan(AI_DIR
);
594 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
596 if (mode
& TarScanner::GAME
) {
597 num
+= fs
.DoScan(GAME_DIR
);
598 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
600 if (mode
& TarScanner::SCENARIO
) {
601 num
+= fs
.DoScan(SCENARIO_DIR
);
602 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
604 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
609 * Add a single file to the scanned files of a tar, circumventing the scanning code.
610 * @param sd The sub directory the file is in.
611 * @param filename The name of the file to add.
612 * @return True if the additions went correctly.
614 bool TarScanner::AddFile(Subdirectory sd
, const std::string
&filename
)
617 return this->AddFile(filename
, 0);
620 bool TarScanner::AddFile(const std::string
&filename
, size_t basepath_length
, const std::string
&tar_filename
)
622 /* No tar within tar. */
623 assert(tar_filename
.empty());
625 /* The TAR-header, repeated for every file */
627 char name
[100]; ///< Name of the file
631 char size
[12]; ///< Size of the file, in ASCII
642 char prefix
[155]; ///< Path of the file
647 /* Check if we already seen this file */
648 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
649 if (it
!= _tar_list
[this->subdir
].end()) return false;
651 FILE *f
= fopen(filename
.c_str(), "rb");
652 /* Although the file has been found there can be
653 * a number of reasons we cannot open the file.
654 * Most common case is when we simply have not
655 * been given read access. */
656 if (f
== nullptr) return false;
658 _tar_list
[this->subdir
][filename
] = std::string
{};
660 TarLinkList links
; ///< Temporary list to collect links
663 char buf
[sizeof(th
.name
) + 1], *end
;
664 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
665 char link
[sizeof(th
.linkname
) + 1];
666 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
667 size_t num
= 0, pos
= 0;
669 /* Make a char of 512 empty bytes */
671 memset(&empty
[0], 0, sizeof(empty
));
673 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
674 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
675 if (num_bytes_read
!= 512) break;
676 pos
+= num_bytes_read
;
678 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
679 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
680 /* If we have only zeros in the block, it can be an end-of-file indicator */
681 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
683 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
.c_str());
690 /* The prefix contains the directory-name */
691 if (th
.prefix
[0] != '\0') {
692 strecpy(name
, th
.prefix
, lastof(name
));
693 strecat(name
, PATHSEP
, lastof(name
));
696 /* Copy the name of the file in a safe way at the end of 'name' */
697 strecat(name
, th
.name
, lastof(name
));
699 /* Calculate the size of the file.. for some strange reason this is stored as a string */
700 strecpy(buf
, th
.size
, lastof(buf
));
701 size_t skip
= strtoul(buf
, &end
, 8);
703 switch (th
.typeflag
) {
705 case '0': { // regular file
706 /* Ignore empty files */
707 if (skip
== 0) break;
709 if (strlen(name
) == 0) break;
711 /* Store this entry in the list */
712 TarFileListEntry entry
;
713 entry
.tar_filename
= filename
;
715 entry
.position
= pos
;
717 /* Convert to lowercase and our PATHSEPCHAR */
718 SimplifyFileName(name
);
720 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
721 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
726 case '1': // hard links
727 case '2': { // symbolic links
728 /* Copy the destination of the link in a safe way at the end of 'linkname' */
729 strecpy(link
, th
.linkname
, lastof(link
));
731 if (strlen(name
) == 0 || strlen(link
) == 0) break;
733 /* Convert to lowercase and our PATHSEPCHAR */
734 SimplifyFileName(name
);
735 SimplifyFileName(link
);
737 /* Only allow relative links */
738 if (link
[0] == PATHSEPCHAR
) {
739 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
743 /* Process relative path.
744 * Note: The destination of links must not contain any directory-links. */
745 strecpy(dest
, name
, lastof(dest
));
746 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
747 if (destpos
== nullptr) destpos
= dest
;
751 while (*pos
!= '\0') {
752 char *next
= strchr(pos
, PATHSEPCHAR
);
753 if (next
== nullptr) {
754 next
= pos
+ strlen(pos
);
756 /* Terminate the substring up to the path separator character. */
760 if (strcmp(pos
, ".") == 0) {
761 /* Skip '.' (current dir) */
762 } else if (strcmp(pos
, "..") == 0) {
764 if (dest
[0] == '\0') {
765 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
769 /* Truncate 'dest' after last PATHSEPCHAR.
770 * This assumes that the truncated part is a real directory and not a link. */
771 destpos
= strrchr(dest
, PATHSEPCHAR
);
772 if (destpos
== nullptr) destpos
= dest
;
775 /* Append at end of 'dest' */
776 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
777 destpos
= strecpy(destpos
, pos
, lastof(dest
));
780 if (destpos
>= lastof(dest
)) {
781 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
.c_str());
789 /* Store links in temporary list */
790 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
791 links
.insert(TarLinkList::value_type(name
, dest
));
796 case '5': // directory
797 /* Convert to lowercase and our PATHSEPCHAR */
798 SimplifyFileName(name
);
800 /* Store the first directory name we detect */
801 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
802 if (_tar_list
[this->subdir
][filename
].empty()) _tar_list
[this->subdir
][filename
] = name
;
806 /* Ignore other types */
810 /* Skip to the next block.. */
811 skip
= Align(skip
, 512);
812 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
813 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
.c_str());
820 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
.c_str(), num
);
823 /* Resolve file links and store directory links.
824 * We restrict usage of links to two cases:
825 * 1) Links to directories:
826 * Both the source path and the destination path must NOT contain any further links.
827 * When resolving files at most one directory link is resolved.
829 * The destination path must NOT contain any links.
830 * The source path may contain one directory link.
832 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
833 const std::string
&src
= link
->first
;
834 const std::string
&dest
= link
->second
;
835 TarAddLink(src
, dest
, this->subdir
);
842 * Extract the tar with the given filename in the directory
843 * where the tar resides.
844 * @param tar_filename the name of the tar to extract.
845 * @param subdir The sub directory the tar is in.
846 * @return false on failure.
848 bool ExtractTar(const std::string
&tar_filename
, Subdirectory subdir
)
850 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
851 /* We don't know the file. */
852 if (it
== _tar_list
[subdir
].end()) return false;
854 const auto &dirname
= (*it
).second
;
856 /* The file doesn't have a sub directory! */
857 if (dirname
.empty()) {
858 DEBUG(misc
, 1, "Extracting %s failed; archive rejected, the contents must be in a sub directory", tar_filename
.c_str());
862 std::string filename
= tar_filename
;
863 auto p
= filename
.find_last_of(PATHSEPCHAR
);
864 /* The file's path does not have a separator? */
865 if (p
== std::string::npos
) return false;
867 filename
.replace(p
+ 1, std::string::npos
, dirname
);
868 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
.c_str(), filename
.c_str());
869 FioCreateDirectory(filename
);
871 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
872 if (tar_filename
!= it2
->second
.tar_filename
) continue;
874 filename
.replace(p
+ 1, std::string::npos
, it2
->first
);
876 DEBUG(misc
, 9, " extracting %s", filename
.c_str());
878 /* First open the file in the .tar. */
880 std::unique_ptr
<FILE, FileDeleter
> in(FioFOpenFileTar(it2
->second
, &to_copy
));
882 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
.c_str(), tar_filename
.c_str());
886 /* Now open the 'output' file. */
887 std::unique_ptr
<FILE, FileDeleter
> out(fopen(filename
.c_str(), "wb"));
889 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
.c_str(), filename
.c_str());
893 /* Now read from the tar and write it into the file. */
896 for (; to_copy
!= 0; to_copy
-= read
) {
897 read
= fread(buffer
, 1, std::min(to_copy
, lengthof(buffer
)), in
.get());
898 if (read
<= 0 || fwrite(buffer
, 1, read
, out
.get()) != read
) break;
902 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
.c_str(), (int)to_copy
);
907 DEBUG(misc
, 9, " extraction successful");
913 * Determine the base (personal dir and game data dir) paths
914 * @param exe the path from the current path to the executable
915 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
917 extern void DetermineBasePaths(const char *exe
);
918 #else /* defined(_WIN32) */
921 * Changes the working directory to the path of the give executable.
922 * For OSX application bundles '.app' is the required extension of the bundle,
923 * so when we crop the path to there, when can remove the name of the bundle
924 * in the same way we remove the name from the executable name.
925 * @param exe the path to the executable
927 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
930 strecpy(tmp
, exe
, lastof(tmp
));
932 bool success
= false;
934 char *app_bundle
= strchr(tmp
, '.');
935 while (app_bundle
!= nullptr && strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
937 if (app_bundle
!= nullptr) *app_bundle
= '\0';
938 #endif /* WITH_COCOA */
939 char *s
= strrchr(tmp
, PATHSEPCHAR
);
942 if (chdir(tmp
) != 0) {
943 DEBUG(misc
, 0, "Directory with the binary does not exist?");
952 * Whether we should scan the working directory.
953 * It should not be scanned if it's the root or
954 * the home directory as in both cases a big data
955 * directory can cause huge amounts of unrelated
956 * files scanned. Furthermore there are nearly no
957 * use cases for the home/root directory to have
958 * OpenTTD directories.
959 * @return true if it should be scanned.
961 bool DoScanWorkingDirectory()
963 /* No working directory, so nothing to do. */
964 if (_searchpaths
[SP_WORKING_DIR
].empty()) return false;
966 /* Working directory is root, so do nothing. */
967 if (_searchpaths
[SP_WORKING_DIR
] == PATHSEP
) return false;
969 /* No personal/home directory, so the working directory won't be that. */
970 if (_searchpaths
[SP_PERSONAL_DIR
].empty()) return true;
972 std::string tmp
= _searchpaths
[SP_WORKING_DIR
] + PERSONAL_DIR
;
973 AppendPathSeparator(tmp
);
975 return _searchpaths
[SP_PERSONAL_DIR
] != tmp
;
979 * Gets the home directory of the user.
980 * May return an empty string in the unlikely scenario that the home directory cannot be found.
981 * @return User's home directory
983 static std::string
GetHomeDir()
987 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
988 return std::string(path
.Path());
990 const char *home_env
= getenv("HOME"); // Stack var, shouldn't be freed
991 if (home_env
!= nullptr) return std::string(home_env
);
993 const struct passwd
*pw
= getpwuid(getuid());
994 if (pw
!= nullptr) return std::string(pw
->pw_dir
);
1000 * Determine the base (personal dir and game data dir) paths
1001 * @param exe the path to the executable
1003 void DetermineBasePaths(const char *exe
)
1006 const std::string homedir
= GetHomeDir();
1008 const char *xdg_data_home
= getenv("XDG_DATA_HOME");
1009 if (xdg_data_home
!= nullptr) {
1010 tmp
= xdg_data_home
;
1012 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1013 AppendPathSeparator(tmp
);
1014 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
1016 tmp
+= "content_download";
1017 AppendPathSeparator(tmp
);
1018 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
1019 } else if (!homedir
.empty()) {
1021 tmp
+= PATHSEP
".local" PATHSEP
"share" PATHSEP
;
1022 tmp
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1023 AppendPathSeparator(tmp
);
1024 _searchpaths
[SP_PERSONAL_DIR_XDG
] = tmp
;
1026 tmp
+= "content_download";
1027 AppendPathSeparator(tmp
);
1028 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
] = tmp
;
1030 _searchpaths
[SP_PERSONAL_DIR_XDG
].clear();
1031 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG
].clear();
1035 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1036 _searchpaths
[SP_PERSONAL_DIR
].clear();
1038 if (!homedir
.empty()) {
1041 tmp
+= PERSONAL_DIR
;
1042 AppendPathSeparator(tmp
);
1043 _searchpaths
[SP_PERSONAL_DIR
] = tmp
;
1045 tmp
+= "content_download";
1046 AppendPathSeparator(tmp
);
1047 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
] = tmp
;
1049 _searchpaths
[SP_PERSONAL_DIR
].clear();
1050 _searchpaths
[SP_AUTODOWNLOAD_PERSONAL_DIR
].clear();
1054 #if defined(WITH_SHARED_DIR)
1056 AppendPathSeparator(tmp
);
1057 _searchpaths
[SP_SHARED_DIR
] = tmp
;
1059 _searchpaths
[SP_SHARED_DIR
].clear();
1063 if (getcwd(cwd
, MAX_PATH
) == nullptr) *cwd
= '\0';
1065 if (_config_file
.empty()) {
1066 /* Get the path to working directory of OpenTTD. */
1068 AppendPathSeparator(tmp
);
1069 _searchpaths
[SP_WORKING_DIR
] = tmp
;
1071 _do_scan_working_directory
= DoScanWorkingDirectory();
1073 /* Use the folder of the config file as working directory. */
1074 size_t end
= _config_file
.find_last_of(PATHSEPCHAR
);
1075 if (end
== std::string::npos
) {
1076 /* _config_file is not in a folder, so use current directory. */
1078 AppendPathSeparator(tmp
);
1079 _searchpaths
[SP_WORKING_DIR
] = tmp
;
1081 _searchpaths
[SP_WORKING_DIR
] = _config_file
.substr(0, end
+ 1);
1085 /* Change the working directory to that one of the executable */
1086 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1088 if (getcwd(buf
, lengthof(buf
)) == nullptr) {
1093 AppendPathSeparator(tmp
);
1094 _searchpaths
[SP_BINARY_DIR
] = tmp
;
1096 _searchpaths
[SP_BINARY_DIR
].clear();
1099 if (cwd
[0] != '\0') {
1100 /* Go back to the current working directory. */
1101 if (chdir(cwd
) != 0) {
1102 DEBUG(misc
, 0, "Failed to return to working directory!");
1106 #if !defined(GLOBAL_DATA_DIR)
1107 _searchpaths
[SP_INSTALLATION_DIR
].clear();
1109 tmp
= GLOBAL_DATA_DIR
;
1110 AppendPathSeparator(tmp
);
1111 _searchpaths
[SP_INSTALLATION_DIR
] = tmp
;
1114 extern void CocoaSetApplicationBundleDir();
1115 CocoaSetApplicationBundleDir();
1117 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
].clear();
1120 #endif /* defined(_WIN32) */
1122 std::string _personal_dir
;
1125 * Acquire the base paths (personal dir and game data dir),
1126 * fill all other paths (save dir, autosave dir etc) and
1127 * make the save and scenario directories.
1128 * @param exe the path from the current path to the executable
1130 void DeterminePaths(const char *exe
)
1132 DetermineBasePaths(exe
);
1135 std::string config_home
;
1136 const std::string homedir
= GetHomeDir();
1137 const char *xdg_config_home
= getenv("XDG_CONFIG_HOME");
1138 if (xdg_config_home
!= nullptr) {
1139 config_home
= xdg_config_home
;
1140 config_home
+= PATHSEP
;
1141 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1142 } else if (!homedir
.empty()) {
1143 /* Defaults to ~/.config */
1144 config_home
= homedir
;
1145 config_home
+= PATHSEP
".config" PATHSEP
;
1146 config_home
+= PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
;
1148 AppendPathSeparator(config_home
);
1152 FOR_ALL_SEARCHPATHS(sp
) {
1153 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1154 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
].c_str());
1157 std::string config_dir
;
1158 if (!_config_file
.empty()) {
1159 config_dir
= _searchpaths
[SP_WORKING_DIR
];
1161 std::string personal_dir
= FioFindFullPath(BASE_DIR
, "openttd.cfg");
1162 if (!personal_dir
.empty()) {
1163 auto end
= personal_dir
.find_last_of(PATHSEPCHAR
);
1164 if (end
!= std::string::npos
) personal_dir
.erase(end
+ 1);
1165 config_dir
= personal_dir
;
1168 /* No previous configuration file found. Use the configuration folder from XDG. */
1169 config_dir
= config_home
;
1171 static const Searchpath new_openttd_cfg_order
[] = {
1172 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1176 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1177 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1178 config_dir
= _searchpaths
[new_openttd_cfg_order
[i
]];
1184 _config_file
= config_dir
+ "openttd.cfg";
1187 DEBUG(misc
, 3, "%s found as config directory", config_dir
.c_str());
1189 _highscore_file
= config_dir
+ "hs.dat";
1190 extern std::string _hotkeys_file
;
1191 _hotkeys_file
= config_dir
+ "hotkeys.cfg";
1192 extern std::string _windows_file
;
1193 _windows_file
= config_dir
+ "windows.cfg";
1196 if (config_dir
== config_home
) {
1197 /* We are using the XDG configuration home for the config file,
1198 * then store the rest in the XDG data home folder. */
1199 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1203 _personal_dir
= config_dir
;
1206 /* Make the necessary folders */
1207 FioCreateDirectory(config_dir
);
1208 #if defined(WITH_PERSONAL_DIR)
1209 FioCreateDirectory(_personal_dir
);
1212 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
.c_str());
1214 static const Subdirectory default_subdirs
[] = {
1215 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1218 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1219 FioCreateDirectory(_personal_dir
+ _subdirs
[default_subdirs
[i
]]);
1222 /* If we have network we make a directory for the autodownloading of content */
1223 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = _personal_dir
+ "content_download" PATHSEP
;
1224 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1226 /* Create the directory for each of the types of content */
1227 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1228 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1229 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR
, dirs
[i
]));
1232 extern std::string _log_file
;
1233 _log_file
= _personal_dir
+ "openttd.log";
1237 * Sanitizes a filename, i.e. removes all illegal characters from it.
1238 * @param filename the "\0" terminated filename
1240 void SanitizeFilename(char *filename
)
1242 for (; *filename
!= '\0'; filename
++) {
1243 switch (*filename
) {
1244 /* The following characters are not allowed in filenames
1245 * on at least one of the supported operating systems: */
1246 case ':': case '\\': case '*': case '?': case '/':
1247 case '<': case '>': case '|': case '"':
1255 * Load a file into memory.
1256 * @param filename Name of the file to load.
1257 * @param[out] lenp Length of loaded data.
1258 * @param maxsize Maximum size to load.
1259 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1260 * @note If \a maxsize less than the length of the file, loading fails.
1262 std::unique_ptr
<char[]> ReadFileToMem(const std::string
&filename
, size_t &lenp
, size_t maxsize
)
1264 FILE *in
= fopen(filename
.c_str(), "rb");
1265 if (in
== nullptr) return nullptr;
1269 fseek(in
, 0, SEEK_END
);
1270 size_t len
= ftell(in
);
1271 fseek(in
, 0, SEEK_SET
);
1272 if (len
> maxsize
) return nullptr;
1274 std::unique_ptr
<char[]> mem
= std::make_unique
<char[]>(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 std::string 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 (d_name
== "." || d_name
== "..") 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
);