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>
29 #ifdef WITH_XDG_BASEDIR
33 #include "safeguards.h"
35 /** Size of the #Fio data buffer. */
36 #define FIO_BUFFER_SIZE 512
38 /** Structure for keeping several open files with just one data buffer. */
40 byte
*buffer
, *buffer_end
; ///< position pointer in local buffer and last valid byte of buffer
41 size_t pos
; ///< current (system) position in file
42 FILE *cur_fh
; ///< current file handle
43 const char *filename
; ///< current filename
44 FILE *handles
[MAX_FILE_SLOTS
]; ///< array of file handles we can have open
45 byte buffer_start
[FIO_BUFFER_SIZE
]; ///< local buffer when read from file
46 const char *filenames
[MAX_FILE_SLOTS
]; ///< array of filenames we (should) have open
47 char *shortnames
[MAX_FILE_SLOTS
]; ///< array of short names for spriteloader's use
48 #if defined(LIMITED_FDS)
49 uint open_handles
; ///< current amount of open handles
50 uint usage_count
[MAX_FILE_SLOTS
]; ///< count how many times this file has been opened
51 #endif /* LIMITED_FDS */
54 static Fio _fio
; ///< #Fio instance.
56 /** Whether the working directory should be scanned. */
57 static bool _do_scan_working_directory
= true;
59 extern char *_config_file
;
60 extern char *_highscore_file
;
63 * Get position in the current file.
64 * @return Position in the file.
68 return _fio
.pos
+ (_fio
.buffer
- _fio
.buffer_end
);
72 * Get the filename associated with a slot.
73 * @param slot Index of queried file.
74 * @return Name of the file.
76 const char *FioGetFilename(uint8 slot
)
78 return _fio
.shortnames
[slot
];
82 * Seek in the current file.
83 * @param pos New position.
84 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
86 void FioSeekTo(size_t pos
, int mode
)
88 if (mode
== SEEK_CUR
) pos
+= FioGetPos();
89 _fio
.buffer
= _fio
.buffer_end
= _fio
.buffer_start
+ FIO_BUFFER_SIZE
;
91 if (fseek(_fio
.cur_fh
, _fio
.pos
, SEEK_SET
) < 0) {
92 DEBUG(misc
, 0, "Seeking in %s failed", _fio
.filename
);
96 #if defined(LIMITED_FDS)
97 static void FioRestoreFile(int slot
)
99 /* Do we still have the file open, or should we reopen it? */
100 if (_fio
.handles
[slot
] == nullptr) {
101 DEBUG(misc
, 6, "Restoring file '%s' in slot '%d' from disk", _fio
.filenames
[slot
], slot
);
102 FioOpenFile(slot
, _fio
.filenames
[slot
]);
104 _fio
.usage_count
[slot
]++;
106 #endif /* LIMITED_FDS */
109 * Switch to a different file and seek to a position.
110 * @param slot Slot number of the new file.
111 * @param pos New absolute position in the new file.
113 void FioSeekToFile(uint8 slot
, size_t pos
)
116 #if defined(LIMITED_FDS)
117 /* Make sure we have this file open */
118 FioRestoreFile(slot
);
119 #endif /* LIMITED_FDS */
120 f
= _fio
.handles
[slot
];
121 assert(f
!= nullptr);
123 _fio
.filename
= _fio
.filenames
[slot
];
124 FioSeekTo(pos
, SEEK_SET
);
128 * Read a byte from the file.
133 if (_fio
.buffer
== _fio
.buffer_end
) {
134 _fio
.buffer
= _fio
.buffer_start
;
135 size_t size
= fread(_fio
.buffer
, 1, FIO_BUFFER_SIZE
, _fio
.cur_fh
);
137 _fio
.buffer_end
= _fio
.buffer_start
+ size
;
139 if (size
== 0) return 0;
141 return *_fio
.buffer
++;
145 * Skip \a n bytes ahead in the file.
146 * @param n Number of bytes to skip reading.
148 void FioSkipBytes(int n
)
151 int m
= min(_fio
.buffer_end
- _fio
.buffer
, n
);
161 * Read a word (16 bits) from the file (in low endian format).
166 byte b
= FioReadByte();
167 return (FioReadByte() << 8) | b
;
171 * Read a double word (32 bits) from the file (in low endian format).
174 uint32
FioReadDword()
176 uint b
= FioReadWord();
177 return (FioReadWord() << 16) | b
;
182 * @param ptr Destination buffer.
183 * @param size Number of bytes to read.
185 void FioReadBlock(void *ptr
, size_t size
)
187 FioSeekTo(FioGetPos(), SEEK_SET
);
188 _fio
.pos
+= fread(ptr
, 1, size
, _fio
.cur_fh
);
192 * Close the file at the given slot number.
193 * @param slot File index to close.
195 static inline void FioCloseFile(int slot
)
197 if (_fio
.handles
[slot
] != nullptr) {
198 fclose(_fio
.handles
[slot
]);
200 free(_fio
.shortnames
[slot
]);
201 _fio
.shortnames
[slot
] = nullptr;
203 _fio
.handles
[slot
] = nullptr;
204 #if defined(LIMITED_FDS)
206 #endif /* LIMITED_FDS */
210 /** Close all slotted open files. */
213 for (int i
= 0; i
!= lengthof(_fio
.handles
); i
++) {
218 #if defined(LIMITED_FDS)
219 static void FioFreeHandle()
221 /* If we are about to open a file that will exceed the limit, close a file */
222 if (_fio
.open_handles
+ 1 == LIMITED_FDS
) {
228 /* Find the file that is used the least */
229 for (i
= 0; i
< lengthof(_fio
.handles
); i
++) {
230 if (_fio
.handles
[i
] != nullptr && _fio
.usage_count
[i
] < count
) {
231 count
= _fio
.usage_count
[i
];
236 DEBUG(misc
, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio
.filenames
[slot
], slot
);
240 #endif /* LIMITED_FDS */
243 * Open a slotted file.
244 * @param slot Index to assign.
245 * @param filename Name of the file at the disk.
246 * @param subdir The sub directory to search this file in.
248 void FioOpenFile(int slot
, const char *filename
, Subdirectory subdir
)
252 #if defined(LIMITED_FDS)
254 #endif /* LIMITED_FDS */
255 f
= FioFOpenFile(filename
, "rb", subdir
);
256 if (f
== nullptr) usererror("Cannot open file '%s'", filename
);
258 if (pos
< 0) usererror("Cannot read file '%s'", filename
);
260 FioCloseFile(slot
); // if file was opened before, close it
261 _fio
.handles
[slot
] = f
;
262 _fio
.filenames
[slot
] = filename
;
264 /* Store the filename without path and extension */
265 const char *t
= strrchr(filename
, PATHSEPCHAR
);
266 _fio
.shortnames
[slot
] = stredup(t
== nullptr ? filename
: t
);
267 char *t2
= strrchr(_fio
.shortnames
[slot
], '.');
268 if (t2
!= nullptr) *t2
= '\0';
269 strtolower(_fio
.shortnames
[slot
]);
271 #if defined(LIMITED_FDS)
272 _fio
.usage_count
[slot
] = 0;
274 #endif /* LIMITED_FDS */
275 FioSeekToFile(slot
, (uint32
)pos
);
278 static const char * const _subdirs
[] = {
281 "save" PATHSEP
"autosave" PATHSEP
,
283 "scenario" PATHSEP
"heightmap" PATHSEP
,
290 "ai" PATHSEP
"library" PATHSEP
,
292 "game" PATHSEP
"library" PATHSEP
,
293 "screenshot" PATHSEP
,
295 assert_compile(lengthof(_subdirs
) == NUM_SUBDIRS
);
297 const char *_searchpaths
[NUM_SEARCHPATHS
];
298 TarList _tar_list
[NUM_SUBDIRS
];
299 TarFileList _tar_filelist
[NUM_SUBDIRS
];
301 typedef std::map
<std::string
, std::string
> TarLinkList
;
302 static TarLinkList _tar_linklist
[NUM_SUBDIRS
]; ///< List of directory links
305 * Check whether the given file exists
306 * @param filename the file to try for existence.
307 * @param subdir the subdirectory to look in
308 * @return true if and only if the file can be opened
310 bool FioCheckFileExists(const char *filename
, Subdirectory subdir
)
312 FILE *f
= FioFOpenFile(filename
, "rb", subdir
);
313 if (f
== nullptr) return false;
320 * Test whether the given filename exists.
321 * @param filename the file to test.
322 * @return true if and only if the file exists.
324 bool FileExists(const char *filename
)
326 return access(OTTD2FS(filename
), 0) == 0;
330 * Close a file in a safe way.
332 void FioFCloseFile(FILE *f
)
337 char *FioGetFullPath(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
, const char *filename
)
339 assert(subdir
< NUM_SUBDIRS
);
340 assert(sp
< NUM_SEARCHPATHS
);
342 seprintf(buf
, last
, "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
347 * Find a path to the filename in one of the search directories.
348 * @param[out] buf Destination buffer for the path.
349 * @param last End of the destination buffer.
350 * @param subdir Subdirectory to try.
351 * @param filename Filename to look for.
352 * @return \a buf containing the path if the path was found, else \c nullptr.
354 char *FioFindFullPath(char *buf
, const char *last
, Subdirectory subdir
, const char *filename
)
357 assert(subdir
< NUM_SUBDIRS
);
359 FOR_ALL_SEARCHPATHS(sp
) {
360 FioGetFullPath(buf
, last
, sp
, subdir
, filename
);
361 if (FileExists(buf
)) return buf
;
363 /* Be, as opening files, aware that sometimes the filename
364 * might be in uppercase when it is in lowercase on the
365 * disk. Of course Windows doesn't care about casing. */
366 if (strtolower(buf
+ strlen(_searchpaths
[sp
]) - 1) && FileExists(buf
)) return buf
;
373 char *FioAppendDirectory(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
)
375 assert(subdir
< NUM_SUBDIRS
);
376 assert(sp
< NUM_SEARCHPATHS
);
378 seprintf(buf
, last
, "%s%s", _searchpaths
[sp
], _subdirs
[subdir
]);
382 char *FioGetDirectory(char *buf
, const char *last
, Subdirectory subdir
)
386 /* Find and return the first valid directory */
387 FOR_ALL_SEARCHPATHS(sp
) {
388 char *ret
= FioAppendDirectory(buf
, last
, sp
, subdir
);
389 if (FileExists(buf
)) return ret
;
392 /* Could not find the directory, fall back to a base path */
393 strecpy(buf
, _personal_dir
, last
);
398 static FILE *FioFOpenFileSp(const char *filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
400 #if defined(_WIN32) && defined(UNICODE)
401 /* fopen is implemented as a define with ellipses for
402 * Unicode support (prepend an L). As we are not sending
403 * a string, but a variable, it 'renames' the variable,
404 * so make that variable to makes it compile happily */
406 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
411 if (subdir
== NO_DIRECTORY
) {
412 strecpy(buf
, filename
, lastof(buf
));
414 seprintf(buf
, lastof(buf
), "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
418 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
)) == INVALID_FILE_ATTRIBUTES
) return nullptr;
421 f
= fopen(buf
, mode
);
423 if (f
== nullptr && strtolower(buf
+ ((subdir
== NO_DIRECTORY
) ? 0 : strlen(_searchpaths
[sp
]) - 1))) {
424 f
= fopen(buf
, mode
);
427 if (f
!= nullptr && filesize
!= nullptr) {
428 /* Find the size of the file */
429 fseek(f
, 0, SEEK_END
);
430 *filesize
= ftell(f
);
431 fseek(f
, 0, SEEK_SET
);
437 * Opens a file from inside a tar archive.
438 * @param entry The entry to open.
439 * @param[out] filesize If not \c nullptr, size of the opened file.
440 * @return File handle of the opened file, or \c nullptr if the file is not available.
441 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
443 FILE *FioFOpenFileTar(TarFileListEntry
*entry
, size_t *filesize
)
445 FILE *f
= fopen(entry
->tar_filename
, "rb");
446 if (f
== nullptr) return f
;
448 if (fseek(f
, entry
->position
, SEEK_SET
) < 0) {
453 if (filesize
!= nullptr) *filesize
= entry
->size
;
458 * Opens a OpenTTD file somewhere in a personal or global directory.
459 * @param filename Name of the file to open.
460 * @param subdir Subdirectory to open.
461 * @return File handle of the opened file, or \c nullptr if the file is not available.
463 FILE *FioFOpenFile(const char *filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
468 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
470 FOR_ALL_SEARCHPATHS(sp
) {
471 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
472 if (f
!= nullptr || subdir
== NO_DIRECTORY
) break;
475 /* We can only use .tar in case of data-dir, and read-mode */
476 if (f
== nullptr && mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
477 static const uint MAX_RESOLVED_LENGTH
= 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
478 char resolved_name
[MAX_RESOLVED_LENGTH
];
480 /* Filenames in tars are always forced to be lowercase */
481 strecpy(resolved_name
, filename
, lastof(resolved_name
));
482 strtolower(resolved_name
);
484 size_t resolved_len
= strlen(resolved_name
);
486 /* Resolve ONE directory link */
487 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
488 const std::string
&src
= link
->first
;
489 size_t len
= src
.length();
490 if (resolved_len
>= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& strncmp(src
.c_str(), resolved_name
, len
) == 0) {
492 char resolved_name2
[MAX_RESOLVED_LENGTH
];
493 const std::string
&dest
= link
->second
;
494 strecpy(resolved_name2
, &(resolved_name
[len
]), lastof(resolved_name2
));
495 strecpy(resolved_name
, dest
.c_str(), lastof(resolved_name
));
496 strecpy(&(resolved_name
[dest
.length()]), resolved_name2
, lastof(resolved_name
));
497 break; // Only resolve one level
501 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
502 if (it
!= _tar_filelist
[subdir
].end()) {
503 f
= FioFOpenFileTar(&((*it
).second
), filesize
);
507 /* Sometimes a full path is given. To support
508 * the 'subdirectory' must be 'removed'. */
509 if (f
== nullptr && subdir
!= NO_DIRECTORY
) {
512 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
513 if (f
!= nullptr) break;
516 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
520 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
529 * Create a directory with the given name
530 * If the parent directory does not exist, it will try to create that as well.
531 * @param name the new name of the directory
533 void FioCreateDirectory(const char *name
)
535 char dirname
[MAX_PATH
];
536 strecpy(dirname
, name
, lastof(dirname
));
537 char *p
= strrchr(dirname
, PATHSEPCHAR
);
540 DIR *dir
= ttd_opendir(dirname
);
541 if (dir
== nullptr) {
542 FioCreateDirectory(dirname
); // Try creating the parent directory, if we couldn't open it
548 /* Ignore directory creation errors; they'll surface later on, and most
549 * of the time they are 'directory already exists' errors anyhow. */
551 CreateDirectory(OTTD2FS(name
), nullptr);
552 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
553 mkdir(OTTD2FS(name
));
555 mkdir(OTTD2FS(name
), 0755);
560 * Appends, if necessary, the path separator character to the end of the string.
561 * It does not add the path separator to zero-sized strings.
562 * @param buf string to append the separator to
563 * @param last the last element of \a buf.
564 * @return true iff the operation succeeded
566 bool AppendPathSeparator(char *buf
, const char *last
)
568 size_t s
= strlen(buf
);
570 /* Length of string + path separator + '\0' */
571 if (s
!= 0 && buf
[s
- 1] != PATHSEPCHAR
) {
572 if (&buf
[s
] >= last
) return false;
574 seprintf(buf
+ s
, last
, "%c", PATHSEPCHAR
);
580 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
582 std::string src
= srcParam
;
583 std::string dest
= destParam
;
584 /* Tar internals assume lowercase */
585 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
586 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
588 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
589 if (dest_file
!= _tar_filelist
[subdir
].end()) {
590 /* Link to file. Process the link like the destination file. */
591 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
593 /* Destination file not found. Assume 'link to directory'
594 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
595 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
596 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
597 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
602 * Simplify filenames from tars.
603 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
604 * @param name Filename to process.
606 static void SimplifyFileName(char *name
)
608 /* Force lowercase */
611 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
612 #if (PATHSEPCHAR != '/')
613 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
618 * Perform the scanning of a particular subdirectory.
619 * @param sd The subdirectory to scan.
620 * @return The number of found tar files.
622 uint
TarScanner::DoScan(Subdirectory sd
)
624 _tar_filelist
[sd
].clear();
625 _tar_list
[sd
].clear();
626 uint num
= this->Scan(".tar", sd
, false);
627 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
631 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
633 DEBUG(misc
, 1, "Scanning for tars");
636 if (mode
& TarScanner::BASESET
) {
637 num
+= fs
.DoScan(BASESET_DIR
);
639 if (mode
& TarScanner::NEWGRF
) {
640 num
+= fs
.DoScan(NEWGRF_DIR
);
642 if (mode
& TarScanner::AI
) {
643 num
+= fs
.DoScan(AI_DIR
);
644 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
646 if (mode
& TarScanner::GAME
) {
647 num
+= fs
.DoScan(GAME_DIR
);
648 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
650 if (mode
& TarScanner::SCENARIO
) {
651 num
+= fs
.DoScan(SCENARIO_DIR
);
652 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
654 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
659 * Add a single file to the scanned files of a tar, circumventing the scanning code.
660 * @param sd The sub directory the file is in.
661 * @param filename The name of the file to add.
662 * @return True if the additions went correctly.
664 bool TarScanner::AddFile(Subdirectory sd
, const char *filename
)
667 return this->AddFile(filename
, 0);
670 bool TarScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
672 /* No tar within tar. */
673 assert(tar_filename
== nullptr);
675 /* The TAR-header, repeated for every file */
677 char name
[100]; ///< Name of the file
681 char size
[12]; ///< Size of the file, in ASCII
692 char prefix
[155]; ///< Path of the file
697 /* Check if we already seen this file */
698 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
699 if (it
!= _tar_list
[this->subdir
].end()) return false;
701 FILE *f
= fopen(filename
, "rb");
702 /* Although the file has been found there can be
703 * a number of reasons we cannot open the file.
704 * Most common case is when we simply have not
705 * been given read access. */
706 if (f
== nullptr) return false;
708 const char *dupped_filename
= stredup(filename
);
709 _tar_list
[this->subdir
][filename
].filename
= dupped_filename
;
710 _tar_list
[this->subdir
][filename
].dirname
= nullptr;
712 TarLinkList links
; ///< Temporary list to collect links
715 char buf
[sizeof(th
.name
) + 1], *end
;
716 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
717 char link
[sizeof(th
.linkname
) + 1];
718 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
719 size_t num
= 0, pos
= 0;
721 /* Make a char of 512 empty bytes */
723 memset(&empty
[0], 0, sizeof(empty
));
725 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
726 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
727 if (num_bytes_read
!= 512) break;
728 pos
+= num_bytes_read
;
730 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
731 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
732 /* If we have only zeros in the block, it can be an end-of-file indicator */
733 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
735 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
);
742 /* The prefix contains the directory-name */
743 if (th
.prefix
[0] != '\0') {
744 strecpy(name
, th
.prefix
, lastof(name
));
745 strecat(name
, PATHSEP
, lastof(name
));
748 /* Copy the name of the file in a safe way at the end of 'name' */
749 strecat(name
, th
.name
, lastof(name
));
751 /* Calculate the size of the file.. for some strange reason this is stored as a string */
752 strecpy(buf
, th
.size
, lastof(buf
));
753 size_t skip
= strtoul(buf
, &end
, 8);
755 switch (th
.typeflag
) {
757 case '0': { // regular file
758 /* Ignore empty files */
759 if (skip
== 0) break;
761 if (strlen(name
) == 0) break;
763 /* Store this entry in the list */
764 TarFileListEntry entry
;
765 entry
.tar_filename
= dupped_filename
;
767 entry
.position
= pos
;
769 /* Convert to lowercase and our PATHSEPCHAR */
770 SimplifyFileName(name
);
772 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
773 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
778 case '1': // hard links
779 case '2': { // symbolic links
780 /* Copy the destination of the link in a safe way at the end of 'linkname' */
781 strecpy(link
, th
.linkname
, lastof(link
));
783 if (strlen(name
) == 0 || strlen(link
) == 0) break;
785 /* Convert to lowercase and our PATHSEPCHAR */
786 SimplifyFileName(name
);
787 SimplifyFileName(link
);
789 /* Only allow relative links */
790 if (link
[0] == PATHSEPCHAR
) {
791 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
795 /* Process relative path.
796 * Note: The destination of links must not contain any directory-links. */
797 strecpy(dest
, name
, lastof(dest
));
798 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
799 if (destpos
== nullptr) destpos
= dest
;
803 while (*pos
!= '\0') {
804 char *next
= strchr(pos
, PATHSEPCHAR
);
805 if (next
== nullptr) {
806 next
= pos
+ strlen(pos
);
808 /* Terminate the substring up to the path separator character. */
812 if (strcmp(pos
, ".") == 0) {
813 /* Skip '.' (current dir) */
814 } else if (strcmp(pos
, "..") == 0) {
816 if (dest
[0] == '\0') {
817 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
821 /* Truncate 'dest' after last PATHSEPCHAR.
822 * This assumes that the truncated part is a real directory and not a link. */
823 destpos
= strrchr(dest
, PATHSEPCHAR
);
824 if (destpos
== nullptr) destpos
= dest
;
827 /* Append at end of 'dest' */
828 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
829 destpos
= strecpy(destpos
, pos
, lastof(dest
));
832 if (destpos
>= lastof(dest
)) {
833 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
);
841 /* Store links in temporary list */
842 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
843 links
.insert(TarLinkList::value_type(name
, dest
));
848 case '5': // directory
849 /* Convert to lowercase and our PATHSEPCHAR */
850 SimplifyFileName(name
);
852 /* Store the first directory name we detect */
853 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
854 if (_tar_list
[this->subdir
][filename
].dirname
== nullptr) _tar_list
[this->subdir
][filename
].dirname
= stredup(name
);
858 /* Ignore other types */
862 /* Skip to the next block.. */
863 skip
= Align(skip
, 512);
864 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
865 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
);
872 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
, num
);
875 /* Resolve file links and store directory links.
876 * We restrict usage of links to two cases:
877 * 1) Links to directories:
878 * Both the source path and the destination path must NOT contain any further links.
879 * When resolving files at most one directory link is resolved.
881 * The destination path must NOT contain any links.
882 * The source path may contain one directory link.
884 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
885 const std::string
&src
= link
->first
;
886 const std::string
&dest
= link
->second
;
887 TarAddLink(src
, dest
, this->subdir
);
894 * Extract the tar with the given filename in the directory
895 * where the tar resides.
896 * @param tar_filename the name of the tar to extract.
897 * @param subdir The sub directory the tar is in.
898 * @return false on failure.
900 bool ExtractTar(const char *tar_filename
, Subdirectory subdir
)
902 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
903 /* We don't know the file. */
904 if (it
== _tar_list
[subdir
].end()) return false;
906 const char *dirname
= (*it
).second
.dirname
;
908 /* The file doesn't have a sub directory! */
909 if (dirname
== nullptr) return false;
911 char filename
[MAX_PATH
];
912 strecpy(filename
, tar_filename
, lastof(filename
));
913 char *p
= strrchr(filename
, PATHSEPCHAR
);
914 /* The file's path does not have a separator? */
915 if (p
== nullptr) return false;
918 strecpy(p
, dirname
, lastof(filename
));
919 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
, filename
);
920 FioCreateDirectory(filename
);
922 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
923 if (strcmp((*it2
).second
.tar_filename
, tar_filename
) != 0) continue;
925 strecpy(p
, (*it2
).first
.c_str(), lastof(filename
));
927 DEBUG(misc
, 9, " extracting %s", filename
);
929 /* First open the file in the .tar. */
931 FILE *in
= FioFOpenFileTar(&(*it2
).second
, &to_copy
);
933 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, tar_filename
);
937 /* Now open the 'output' file. */
938 FILE *out
= fopen(filename
, "wb");
939 if (out
== nullptr) {
940 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, filename
);
945 /* Now read from the tar and write it into the file. */
948 for (; to_copy
!= 0; to_copy
-= read
) {
949 read
= fread(buffer
, 1, min(to_copy
, lengthof(buffer
)), in
);
950 if (read
<= 0 || fwrite(buffer
, 1, read
, out
) != read
) break;
953 /* Close everything up. */
958 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
, (int)to_copy
);
963 DEBUG(misc
, 9, " extraction successful");
969 * Determine the base (personal dir and game data dir) paths
970 * @param exe the path from the current path to the executable
971 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
973 extern void DetermineBasePaths(const char *exe
);
974 #else /* defined(_WIN32) */
977 * Changes the working directory to the path of the give executable.
978 * For OSX application bundles '.app' is the required extension of the bundle,
979 * so when we crop the path to there, when can remove the name of the bundle
980 * in the same way we remove the name from the executable name.
981 * @param exe the path to the executable
983 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
986 strecpy(tmp
, exe
, lastof(tmp
));
988 bool success
= false;
990 char *app_bundle
= strchr(tmp
, '.');
991 while (app_bundle
!= nullptr && strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
993 if (app_bundle
!= nullptr) *app_bundle
= '\0';
994 #endif /* WITH_COCOA */
995 char *s
= strrchr(tmp
, PATHSEPCHAR
);
998 if (chdir(tmp
) != 0) {
999 DEBUG(misc
, 0, "Directory with the binary does not exist?");
1008 * Whether we should scan the working directory.
1009 * It should not be scanned if it's the root or
1010 * the home directory as in both cases a big data
1011 * directory can cause huge amounts of unrelated
1012 * files scanned. Furthermore there are nearly no
1013 * use cases for the home/root directory to have
1014 * OpenTTD directories.
1015 * @return true if it should be scanned.
1017 bool DoScanWorkingDirectory()
1019 /* No working directory, so nothing to do. */
1020 if (_searchpaths
[SP_WORKING_DIR
] == nullptr) return false;
1022 /* Working directory is root, so do nothing. */
1023 if (strcmp(_searchpaths
[SP_WORKING_DIR
], PATHSEP
) == 0) return false;
1025 /* No personal/home directory, so the working directory won't be that. */
1026 if (_searchpaths
[SP_PERSONAL_DIR
] == nullptr) return true;
1029 seprintf(tmp
, lastof(tmp
), "%s%s", _searchpaths
[SP_WORKING_DIR
], PERSONAL_DIR
);
1030 AppendPathSeparator(tmp
, lastof(tmp
));
1031 return strcmp(tmp
, _searchpaths
[SP_PERSONAL_DIR
]) != 0;
1035 * Determine the base (personal dir and game data dir) paths
1036 * @param exe the path to the executable
1038 void DetermineBasePaths(const char *exe
)
1041 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1042 const char *xdg_data_home
= xdgDataHome(nullptr);
1043 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", xdg_data_home
,
1044 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1045 free(xdg_data_home
);
1047 AppendPathSeparator(tmp
, lastof(tmp
));
1048 _searchpaths
[SP_PERSONAL_DIR_XDG
] = stredup(tmp
);
1050 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1051 _searchpaths
[SP_PERSONAL_DIR
] = nullptr;
1055 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
1056 const char *homedir
= stredup(path
.Path());
1058 /* getenv is highly unsafe; duplicate it as soon as possible,
1059 * or at least before something else touches the environment
1060 * variables in any way. It can also contain all kinds of
1061 * unvalidated data we rather not want internally. */
1062 const char *homedir
= getenv("HOME");
1063 if (homedir
!= nullptr) {
1064 homedir
= stredup(homedir
);
1067 if (homedir
== nullptr) {
1068 const struct passwd
*pw
= getpwuid(getuid());
1069 homedir
= (pw
== nullptr) ? nullptr : stredup(pw
->pw_dir
);
1073 if (homedir
!= nullptr) {
1074 ValidateString(homedir
);
1075 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", homedir
, PERSONAL_DIR
);
1076 AppendPathSeparator(tmp
, lastof(tmp
));
1078 _searchpaths
[SP_PERSONAL_DIR
] = stredup(tmp
);
1081 _searchpaths
[SP_PERSONAL_DIR
] = nullptr;
1085 #if defined(WITH_SHARED_DIR)
1086 seprintf(tmp
, lastof(tmp
), "%s", SHARED_DIR
);
1087 AppendPathSeparator(tmp
, lastof(tmp
));
1088 _searchpaths
[SP_SHARED_DIR
] = stredup(tmp
);
1090 _searchpaths
[SP_SHARED_DIR
] = nullptr;
1093 if (getcwd(tmp
, MAX_PATH
) == nullptr) *tmp
= '\0';
1094 AppendPathSeparator(tmp
, lastof(tmp
));
1095 _searchpaths
[SP_WORKING_DIR
] = stredup(tmp
);
1097 _do_scan_working_directory
= DoScanWorkingDirectory();
1099 /* Change the working directory to that one of the executable */
1100 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1101 if (getcwd(tmp
, MAX_PATH
) == nullptr) *tmp
= '\0';
1102 AppendPathSeparator(tmp
, lastof(tmp
));
1103 _searchpaths
[SP_BINARY_DIR
] = stredup(tmp
);
1105 _searchpaths
[SP_BINARY_DIR
] = nullptr;
1108 if (_searchpaths
[SP_WORKING_DIR
] != nullptr) {
1109 /* Go back to the current working directory. */
1110 if (chdir(_searchpaths
[SP_WORKING_DIR
]) != 0) {
1111 DEBUG(misc
, 0, "Failed to return to working directory!");
1115 #if !defined(GLOBAL_DATA_DIR)
1116 _searchpaths
[SP_INSTALLATION_DIR
] = nullptr;
1118 seprintf(tmp
, lastof(tmp
), "%s", GLOBAL_DATA_DIR
);
1119 AppendPathSeparator(tmp
, lastof(tmp
));
1120 _searchpaths
[SP_INSTALLATION_DIR
] = stredup(tmp
);
1123 extern void cocoaSetApplicationBundleDir();
1124 cocoaSetApplicationBundleDir();
1126 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
] = nullptr;
1129 #endif /* defined(_WIN32) */
1131 const char *_personal_dir
;
1134 * Acquire the base paths (personal dir and game data dir),
1135 * fill all other paths (save dir, autosave dir etc) and
1136 * make the save and scenario directories.
1137 * @param exe the path from the current path to the executable
1139 void DeterminePaths(const char *exe
)
1141 DetermineBasePaths(exe
);
1143 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1144 char config_home
[MAX_PATH
];
1146 const char *xdg_config_home
= xdgConfigHome(nullptr);
1147 seprintf(config_home
, lastof(config_home
), "%s" PATHSEP
"%s", xdg_config_home
,
1148 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1149 free(xdg_config_home
);
1151 AppendPathSeparator(config_home
, lastof(config_home
));
1155 FOR_ALL_SEARCHPATHS(sp
) {
1156 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1157 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
]);
1161 if (_config_file
!= nullptr) {
1162 config_dir
= stredup(_config_file
);
1163 char *end
= strrchr(config_dir
, PATHSEPCHAR
);
1164 if (end
== nullptr) {
1165 config_dir
[0] = '\0';
1170 char personal_dir
[MAX_PATH
];
1171 if (FioFindFullPath(personal_dir
, lastof(personal_dir
), BASE_DIR
, "openttd.cfg") != nullptr) {
1172 char *end
= strrchr(personal_dir
, PATHSEPCHAR
);
1173 if (end
!= nullptr) end
[1] = '\0';
1174 config_dir
= stredup(personal_dir
);
1175 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1177 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1178 /* No previous configuration file found. Use the configuration folder from XDG. */
1179 config_dir
= config_home
;
1181 static const Searchpath new_openttd_cfg_order
[] = {
1182 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1185 config_dir
= nullptr;
1186 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1187 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1188 config_dir
= stredup(_searchpaths
[new_openttd_cfg_order
[i
]]);
1192 assert(config_dir
!= nullptr);
1194 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1198 DEBUG(misc
, 3, "%s found as config directory", config_dir
);
1200 _highscore_file
= str_fmt("%shs.dat", config_dir
);
1201 extern char *_hotkeys_file
;
1202 _hotkeys_file
= str_fmt("%shotkeys.cfg", config_dir
);
1203 extern char *_windows_file
;
1204 _windows_file
= str_fmt("%swindows.cfg", config_dir
);
1206 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1207 if (config_dir
== config_home
) {
1208 /* We are using the XDG configuration home for the config file,
1209 * then store the rest in the XDG data home folder. */
1210 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1211 FioCreateDirectory(_personal_dir
);
1215 _personal_dir
= config_dir
;
1218 /* Make the necessary folders */
1219 #if defined(WITH_PERSONAL_DIR)
1220 FioCreateDirectory(config_dir
);
1221 if (config_dir
!= _personal_dir
) FioCreateDirectory(_personal_dir
);
1224 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
);
1226 static const Subdirectory default_subdirs
[] = {
1227 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1230 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1231 char *dir
= str_fmt("%s%s", _personal_dir
, _subdirs
[default_subdirs
[i
]]);
1232 FioCreateDirectory(dir
);
1236 /* If we have network we make a directory for the autodownloading of content */
1237 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = str_fmt("%s%s", _personal_dir
, "content_download" PATHSEP
);
1238 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1240 /* Create the directory for each of the types of content */
1241 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1242 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1243 char *tmp
= str_fmt("%s%s", _searchpaths
[SP_AUTODOWNLOAD_DIR
], _subdirs
[dirs
[i
]]);
1244 FioCreateDirectory(tmp
);
1248 extern char *_log_file
;
1249 _log_file
= str_fmt("%sopenttd.log", _personal_dir
);
1253 * Sanitizes a filename, i.e. removes all illegal characters from it.
1254 * @param filename the "\0" terminated filename
1256 void SanitizeFilename(char *filename
)
1258 for (; *filename
!= '\0'; filename
++) {
1259 switch (*filename
) {
1260 /* The following characters are not allowed in filenames
1261 * on at least one of the supported operating systems: */
1262 case ':': case '\\': case '*': case '?': case '/':
1263 case '<': case '>': case '|': case '"':
1271 * Load a file into memory.
1272 * @param filename Name of the file to load.
1273 * @param[out] lenp Length of loaded data.
1274 * @param maxsize Maximum size to load.
1275 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1276 * @note If \a maxsize less than the length of the file, loading fails.
1278 void *ReadFileToMem(const char *filename
, size_t *lenp
, size_t maxsize
)
1280 FILE *in
= fopen(filename
, "rb");
1281 if (in
== nullptr) return nullptr;
1283 fseek(in
, 0, SEEK_END
);
1284 size_t len
= ftell(in
);
1285 fseek(in
, 0, SEEK_SET
);
1286 if (len
> maxsize
) {
1290 byte
*mem
= MallocT
<byte
>(len
+ 1);
1292 if (fread(mem
, len
, 1, in
) != 1) {
1304 * Helper to see whether a given filename matches the extension.
1305 * @param extension The extension to look for.
1306 * @param filename The filename to look in for the extension.
1307 * @return True iff the extension is nullptr, or the filename ends with it.
1309 static bool MatchesExtension(const char *extension
, const char *filename
)
1311 if (extension
== nullptr) return true;
1313 const char *ext
= strrchr(filename
, extension
[0]);
1314 return ext
!= nullptr && strcasecmp(ext
, extension
) == 0;
1318 * Scan a single directory (and recursively its children) and add
1319 * any graphics sets that are found.
1320 * @param fs the file scanner to add the files to
1321 * @param extension the extension of files to search for.
1322 * @param path full path we're currently at
1323 * @param basepath_length from where in the path are we 'based' on the search path
1324 * @param recursive whether to recursively search the sub directories
1326 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1328 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1332 struct dirent
*dirent
;
1335 if (path
== nullptr || (dir
= ttd_opendir(path
)) == nullptr) return 0;
1337 while ((dirent
= readdir(dir
)) != nullptr) {
1338 const char *d_name
= FS2OTTD(dirent
->d_name
);
1339 char filename
[MAX_PATH
];
1341 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1343 seprintf(filename
, lastof(filename
), "%s%s", path
, d_name
);
1345 if (S_ISDIR(sb
.st_mode
)) {
1347 if (!recursive
) continue;
1348 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1349 if (!AppendPathSeparator(filename
, lastof(filename
))) continue;
1350 num
+= ScanPath(fs
, extension
, filename
, basepath_length
, recursive
);
1351 } else if (S_ISREG(sb
.st_mode
)) {
1353 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, basepath_length
, nullptr)) num
++;
1363 * Scan the given tar and add graphics sets when it finds one.
1364 * @param fs the file scanner to scan for
1365 * @param extension the extension of files to search for.
1366 * @param tar the tar to search in.
1368 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1371 const char *filename
= (*tar
).first
.c_str();
1373 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1379 * Scan for files with the given extension in the given search path.
1380 * @param extension the extension of files to search for.
1381 * @param sd the sub directory to search in.
1382 * @param tars whether to search in the tars too.
1383 * @param recursive whether to search recursively
1384 * @return the number of found files, i.e. the number of times that
1385 * AddFile returned true.
1387 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1392 char path
[MAX_PATH
];
1393 TarFileList::iterator tar
;
1396 FOR_ALL_SEARCHPATHS(sp
) {
1397 /* Don't search in the working directory */
1398 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1400 FioAppendDirectory(path
, lastof(path
), sp
, sd
);
1401 num
+= ScanPath(this, extension
, path
, strlen(path
), recursive
);
1404 if (tars
&& sd
!= NO_DIRECTORY
) {
1405 FOR_ALL_TARS(tar
, sd
) {
1406 num
+= ScanTar(this, extension
, tar
);
1412 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1415 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1425 * Scan for files with the given extension in the given search path.
1426 * @param extension the extension of files to search for.
1427 * @param directory the sub directory to search in.
1428 * @param recursive whether to search recursively
1429 * @return the number of found files, i.e. the number of times that
1430 * AddFile returned true.
1432 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1434 char path
[MAX_PATH
];
1435 strecpy(path
, directory
, lastof(path
));
1436 if (!AppendPathSeparator(path
, lastof(path
))) return 0;
1437 return ScanPath(this, extension
, path
, strlen(path
), recursive
);