1 /* $Id: fileio.cpp 26114 2013-11-25 21:50:54Z rubidium $ */
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file fileio.cpp Standard In/Out file operations */
13 #include "fileio_func.h"
16 #include "string_func.h"
20 # define access _taccess
21 #elif defined(__HAIKU__)
23 #include <storage/FindDirectory.h>
31 #ifdef WITH_XDG_BASEDIR
35 #include "safeguards.h"
37 /** Size of the #Fio data buffer. */
38 #define FIO_BUFFER_SIZE 512
40 /** Structure for keeping several open files with just one data buffer. */
42 byte
*buffer
, *buffer_end
; ///< position pointer in local buffer and last valid byte of buffer
43 size_t pos
; ///< current (system) position in file
44 FILE *cur_fh
; ///< current file handle
45 const char *filename
; ///< current filename
46 FILE *handles
[MAX_FILE_SLOTS
]; ///< array of file handles we can have open
47 byte buffer_start
[FIO_BUFFER_SIZE
]; ///< local buffer when read from file
48 const char *filenames
[MAX_FILE_SLOTS
]; ///< array of filenames we (should) have open
49 char *shortnames
[MAX_FILE_SLOTS
]; ///< array of short names for spriteloader's use
50 #if defined(LIMITED_FDS)
51 uint open_handles
; ///< current amount of open handles
52 uint usage_count
[MAX_FILE_SLOTS
]; ///< count how many times this file has been opened
53 #endif /* LIMITED_FDS */
56 static Fio _fio
; ///< #Fio instance.
58 /** Whether the working directory should be scanned. */
59 static bool _do_scan_working_directory
= true;
61 extern char *_config_file
;
62 extern char *_highscore_file
;
65 * Get position in the current file.
66 * @return Position in the file.
70 return _fio
.pos
+ (_fio
.buffer
- _fio
.buffer_end
);
74 * Get the filename associated with a slot.
75 * @param slot Index of queried file.
76 * @return Name of the file.
78 const char *FioGetFilename(uint slot
)
80 return _fio
.shortnames
[slot
];
84 * Seek in the current file.
85 * @param pos New position.
86 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
88 void FioSeekTo(size_t pos
, int mode
)
90 if (mode
== SEEK_CUR
) pos
+= FioGetPos();
91 _fio
.buffer
= _fio
.buffer_end
= _fio
.buffer_start
+ FIO_BUFFER_SIZE
;
93 if (fseek(_fio
.cur_fh
, _fio
.pos
, SEEK_SET
) < 0) {
94 DEBUG(misc
, 0, "Seeking in %s failed", _fio
.filename
);
98 #if defined(LIMITED_FDS)
99 static void FioRestoreFile(int slot
)
101 /* Do we still have the file open, or should we reopen it? */
102 if (_fio
.handles
[slot
] == nullptr) {
103 DEBUG(misc
, 6, "Restoring file '%s' in slot '%d' from disk", _fio
.filenames
[slot
], slot
);
104 FioOpenFile(slot
, _fio
.filenames
[slot
]);
106 _fio
.usage_count
[slot
]++;
108 #endif /* LIMITED_FDS */
111 * Switch to a different file and seek to a position.
112 * @param slot Slot number of the new file.
113 * @param pos New absolute position in the new file.
115 void FioSeekToFile(uint slot
, size_t pos
)
118 #if defined(LIMITED_FDS)
119 /* Make sure we have this file open */
120 FioRestoreFile(slot
);
121 #endif /* LIMITED_FDS */
122 f
= _fio
.handles
[slot
];
123 assert(f
!= nullptr);
125 _fio
.filename
= _fio
.filenames
[slot
];
126 FioSeekTo(pos
, SEEK_SET
);
130 * Read a byte from the file.
135 if (_fio
.buffer
== _fio
.buffer_end
) {
136 _fio
.buffer
= _fio
.buffer_start
;
137 size_t size
= fread(_fio
.buffer
, 1, FIO_BUFFER_SIZE
, _fio
.cur_fh
);
139 _fio
.buffer_end
= _fio
.buffer_start
+ size
;
141 if (size
== 0) return 0;
143 return *_fio
.buffer
++;
147 * Skip \a n bytes ahead in the file.
148 * @param n Number of bytes to skip reading.
150 void FioSkipBytes(int n
)
153 int m
= min(_fio
.buffer_end
- _fio
.buffer
, n
);
163 * Read a word (16 bits) from the file (in low endian format).
168 byte b
= FioReadByte();
169 return (FioReadByte() << 8) | b
;
173 * Read a double word (32 bits) from the file (in low endian format).
176 uint32
FioReadDword()
178 uint b
= FioReadWord();
179 return (FioReadWord() << 16) | b
;
184 * @param ptr Destination buffer.
185 * @param size Number of bytes to read.
187 void FioReadBlock(void *ptr
, size_t size
)
189 FioSeekTo(FioGetPos(), SEEK_SET
);
190 _fio
.pos
+= fread(ptr
, 1, size
, _fio
.cur_fh
);
194 * Close the file at the given slot number.
195 * @param slot File index to close.
197 static inline void FioCloseFile(int slot
)
199 if (_fio
.handles
[slot
] != nullptr) {
200 fclose(_fio
.handles
[slot
]);
202 free(_fio
.shortnames
[slot
]);
203 _fio
.shortnames
[slot
] = nullptr;
205 _fio
.handles
[slot
] = nullptr;
206 #if defined(LIMITED_FDS)
208 #endif /* LIMITED_FDS */
212 /** Close all slotted open files. */
215 for (int i
= 0; i
!= lengthof(_fio
.handles
); i
++) {
220 #if defined(LIMITED_FDS)
221 static void FioFreeHandle()
223 /* If we are about to open a file that will exceed the limit, close a file */
224 if (_fio
.open_handles
+ 1 == LIMITED_FDS
) {
230 /* Find the file that is used the least */
231 for (i
= 0; i
< lengthof(_fio
.handles
); i
++) {
232 if (_fio
.handles
[i
] != nullptr && _fio
.usage_count
[i
] < count
) {
233 count
= _fio
.usage_count
[i
];
238 DEBUG(misc
, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio
.filenames
[slot
], slot
);
242 #endif /* LIMITED_FDS */
245 * Open a slotted file.
246 * @param slot Index to assign.
247 * @param filename Name of the file at the disk.
248 * @param subdir The sub directory to search this file in.
250 void FioOpenFile(uint slot
, const char *filename
, Subdirectory subdir
)
254 #if defined(LIMITED_FDS)
256 #endif /* LIMITED_FDS */
257 f
= FioFOpenFile(filename
, "rb", subdir
);
258 if (f
== nullptr) usererror("Cannot open file '%s'", filename
);
260 if (pos
< 0) usererror("Cannot read file '%s'", filename
);
262 FioCloseFile(slot
); // if file was opened before, close it
263 _fio
.handles
[slot
] = f
;
264 _fio
.filenames
[slot
] = filename
;
266 /* Store the filename without path and extension */
267 const char *t
= strrchr(filename
, PATHSEPCHAR
);
268 _fio
.shortnames
[slot
] = stredup(t
== nullptr ? filename
: t
);
269 char *t2
= strrchr(_fio
.shortnames
[slot
], '.');
270 if (t2
!= nullptr) *t2
= '\0';
271 strtolower(_fio
.shortnames
[slot
]);
273 #if defined(LIMITED_FDS)
274 _fio
.usage_count
[slot
] = 0;
276 #endif /* LIMITED_FDS */
277 FioSeekToFile(slot
, (uint32
)pos
);
280 static const char * const _subdirs
[] = {
283 "save" PATHSEP
"autosave" PATHSEP
,
285 "scenario" PATHSEP
"heightmap" PATHSEP
,
292 "ai" PATHSEP
"library" PATHSEP
,
294 "game" PATHSEP
"library" PATHSEP
,
295 "screenshot" PATHSEP
,
297 assert_compile(lengthof(_subdirs
) == NUM_SUBDIRS
);
299 const char *_searchpaths
[NUM_SEARCHPATHS
];
300 TarList _tar_list
[NUM_SUBDIRS
];
301 TarFileList _tar_filelist
[NUM_SUBDIRS
];
303 typedef std::map
<std::string
, std::string
> TarLinkList
;
304 static TarLinkList _tar_linklist
[NUM_SUBDIRS
]; ///< List of directory links
307 * Check whether the given file exists
308 * @param filename the file to try for existence.
309 * @param subdir the subdirectory to look in
310 * @return true if and only if the file can be opened
312 bool FioCheckFileExists(const char *filename
, Subdirectory subdir
)
314 FILE *f
= FioFOpenFile(filename
, "rb", subdir
);
315 if (f
== nullptr) return false;
322 * Test whether the given filename exists.
323 * @param filename the file to test.
324 * @return true if and only if the file exists.
326 bool FileExists(const char *filename
)
329 /* There is always one platform that doesn't support basic commands... */
330 HANDLE hand
= CreateFile(OTTD2FS(filename
), 0, 0, nullptr, OPEN_EXISTING
, 0, nullptr);
331 if (hand
== INVALID_HANDLE_VALUE
) return 1;
335 return access(OTTD2FS(filename
), 0) == 0;
340 * Close a file in a safe way.
342 void FioFCloseFile(FILE *f
)
347 char *FioGetFullPath(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
, const char *filename
)
349 assert(subdir
< NUM_SUBDIRS
);
350 assert(sp
< NUM_SEARCHPATHS
);
352 seprintf(buf
, last
, "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
357 * Find a path to the filename in one of the search directories.
358 * @param buf [out] Destination buffer for the path.
359 * @param last End of the destination buffer.
360 * @param subdir Subdirectory to try.
361 * @param filename Filename to look for.
362 * @return \a buf containing the path if the path was found, else \c nullptr.
364 char *FioFindFullPath(char *buf
, const char *last
, Subdirectory subdir
, const char *filename
)
367 assert(subdir
< NUM_SUBDIRS
);
369 FOR_ALL_SEARCHPATHS(sp
) {
370 FioGetFullPath(buf
, last
, sp
, subdir
, filename
);
371 if (FileExists(buf
)) return buf
;
373 /* Be, as opening files, aware that sometimes the filename
374 * might be in uppercase when it is in lowercase on the
375 * disk. Of course Windows doesn't care about casing. */
376 if (strtolower(buf
+ strlen(_searchpaths
[sp
]) - 1) && FileExists(buf
)) return buf
;
383 char *FioAppendDirectory(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
)
385 assert(subdir
< NUM_SUBDIRS
);
386 assert(sp
< NUM_SEARCHPATHS
);
388 seprintf(buf
, last
, "%s%s", _searchpaths
[sp
], _subdirs
[subdir
]);
392 char *FioGetDirectory(char *buf
, const char *last
, Subdirectory subdir
)
396 /* Find and return the first valid directory */
397 FOR_ALL_SEARCHPATHS(sp
) {
398 char *ret
= FioAppendDirectory(buf
, last
, sp
, subdir
);
399 if (FileExists(buf
)) return ret
;
402 /* Could not find the directory, fall back to a base path */
403 strecpy(buf
, _personal_dir
, last
);
408 static FILE *FioFOpenFileSp(const char *filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
410 #if defined(WIN32) && defined(UNICODE)
411 /* fopen is implemented as a define with ellipses for
412 * Unicode support (prepend an L). As we are not sending
413 * a string, but a variable, it 'renames' the variable,
414 * so make that variable to makes it compile happily */
416 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
421 if (subdir
== NO_DIRECTORY
) {
422 strecpy(buf
, filename
, lastof(buf
));
424 seprintf(buf
, lastof(buf
), "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
428 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
)) == INVALID_FILE_ATTRIBUTES
) return nullptr;
431 f
= fopen(buf
, mode
);
433 if (f
== nullptr && strtolower(buf
+ ((subdir
== NO_DIRECTORY
) ? 0 : strlen(_searchpaths
[sp
]) - 1))) {
434 f
= fopen(buf
, mode
);
437 if (f
!= nullptr && filesize
!= nullptr) {
438 /* Find the size of the file */
439 fseek(f
, 0, SEEK_END
);
440 *filesize
= ftell(f
);
441 fseek(f
, 0, SEEK_SET
);
447 * Opens a file from inside a tar archive.
448 * @param entry The entry to open.
449 * @param filesize [out] If not \c nullptr, size of the opened file.
450 * @return File handle of the opened file, or \c nullptr if the file is not available.
451 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
453 FILE *FioFOpenFileTar(TarFileListEntry
*entry
, size_t *filesize
)
455 FILE *f
= fopen(entry
->tar_filename
, "rb");
456 if (f
== nullptr) return f
;
458 if (fseek(f
, entry
->position
, SEEK_SET
) < 0) {
463 if (filesize
!= nullptr) *filesize
= entry
->size
;
468 * Opens a OpenTTD file somewhere in a personal or global directory.
469 * @param filename Name of the file to open.
470 * @param subdir Subdirectory to open.
471 * @param filename Name of the file to open.
472 * @return File handle of the opened file, or \c nullptr if the file is not available.
474 FILE *FioFOpenFile(const char *filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
479 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
481 FOR_ALL_SEARCHPATHS(sp
) {
482 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
483 if (f
!= nullptr || subdir
== NO_DIRECTORY
) break;
486 /* We can only use .tar in case of data-dir, and read-mode */
487 if (f
== nullptr && mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
488 static const uint MAX_RESOLVED_LENGTH
= 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
489 char resolved_name
[MAX_RESOLVED_LENGTH
];
491 /* Filenames in tars are always forced to be lowercase */
492 strecpy(resolved_name
, filename
, lastof(resolved_name
));
493 strtolower(resolved_name
);
495 size_t resolved_len
= strlen(resolved_name
);
497 /* Resolve ONE directory link */
498 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
499 const std::string
&src
= link
->first
;
500 size_t len
= src
.length();
501 if (resolved_len
>= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& strncmp(src
.c_str(), resolved_name
, len
) == 0) {
503 char resolved_name2
[MAX_RESOLVED_LENGTH
];
504 const std::string
&dest
= link
->second
;
505 strecpy(resolved_name2
, &(resolved_name
[len
]), lastof(resolved_name2
));
506 strecpy(resolved_name
, dest
.c_str(), lastof(resolved_name
));
507 strecpy(&(resolved_name
[dest
.length()]), resolved_name2
, lastof(resolved_name
));
508 break; // Only resolve one level
512 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
513 if (it
!= _tar_filelist
[subdir
].end()) {
514 f
= FioFOpenFileTar(&((*it
).second
), filesize
);
518 /* Sometimes a full path is given. To support
519 * the 'subdirectory' must be 'removed'. */
520 if (f
== nullptr && subdir
!= NO_DIRECTORY
) {
523 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
524 if (f
!= nullptr) break;
527 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
531 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
540 * Create a directory with the given name
541 * @param name the new name of the directory
543 static void FioCreateDirectory(const char *name
)
545 /* Ignore directory creation errors; they'll surface later on, and most
546 * of the time they are 'directory already exists' errors anyhow. */
547 #if defined(WIN32) || defined(WINCE)
548 CreateDirectory(OTTD2FS(name
), nullptr);
549 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
550 mkdir(OTTD2FS(name
));
551 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
553 strecpy(buf
, name
, lastof(buf
));
555 size_t len
= strlen(name
) - 1;
556 if (buf
[len
] == '/') {
557 buf
[len
] = '\0'; // Kill pathsep, so mkdir() will not fail
560 mkdir(OTTD2FS(buf
), 0755);
562 mkdir(OTTD2FS(name
), 0755);
567 * Appends, if necessary, the path separator character to the end of the string.
568 * It does not add the path separator to zero-sized strings.
569 * @param buf string to append the separator to
570 * @param last the last element of \a buf.
571 * @return true iff the operation succeeded
573 bool AppendPathSeparator(char *buf
, const char *last
)
575 size_t s
= strlen(buf
);
577 /* Length of string + path separator + '\0' */
578 if (s
!= 0 && buf
[s
- 1] != PATHSEPCHAR
) {
579 if (&buf
[s
] >= last
) return false;
581 seprintf(buf
+ s
, last
, "%c", PATHSEPCHAR
);
588 * Find the first directory in a tar archive.
589 * @param tarname the name of the tar archive to look in.
590 * @param subdir the subdirectory to look in.
592 const char *FioTarFirstDir(const char *tarname
, Subdirectory subdir
)
594 TarList::iterator it
= _tar_list
[subdir
].find(tarname
);
595 if (it
== _tar_list
[subdir
].end()) return nullptr;
596 return (*it
).second
.dirname
;
599 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
601 std::string src
= srcParam
;
602 std::string dest
= destParam
;
603 /* Tar internals assume lowercase */
604 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
605 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
607 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
608 if (dest_file
!= _tar_filelist
[subdir
].end()) {
609 /* Link to file. Process the link like the destination file. */
610 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
612 /* Destination file not found. Assume 'link to directory'
613 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
614 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
615 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
616 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
620 void FioTarAddLink(const char *src
, const char *dest
, Subdirectory subdir
)
622 TarAddLink(src
, dest
, subdir
);
626 * Simplify filenames from tars.
627 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
628 * @param name Filename to process.
630 static void SimplifyFileName(char *name
)
632 /* Force lowercase */
635 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
636 #if (PATHSEPCHAR != '/')
637 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
642 * Perform the scanning of a particular subdirectory.
643 * @param subdir The subdirectory to scan.
644 * @return The number of found tar files.
646 uint
TarScanner::DoScan(Subdirectory sd
)
648 _tar_filelist
[sd
].clear();
649 _tar_list
[sd
].clear();
650 uint num
= this->Scan(".tar", sd
, false);
651 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
655 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
657 DEBUG(misc
, 1, "Scanning for tars");
660 if (mode
& TarScanner::BASESET
) {
661 num
+= fs
.DoScan(BASESET_DIR
);
663 if (mode
& TarScanner::NEWGRF
) {
664 num
+= fs
.DoScan(NEWGRF_DIR
);
666 if (mode
& TarScanner::AI
) {
667 num
+= fs
.DoScan(AI_DIR
);
668 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
670 if (mode
& TarScanner::GAME
) {
671 num
+= fs
.DoScan(GAME_DIR
);
672 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
674 if (mode
& TarScanner::SCENARIO
) {
675 num
+= fs
.DoScan(SCENARIO_DIR
);
676 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
678 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
683 * Add a single file to the scanned files of a tar, circumventing the scanning code.
684 * @param sd The sub directory the file is in.
685 * @param filename The name of the file to add.
686 * @return True if the additions went correctly.
688 bool TarScanner::AddFile(Subdirectory sd
, const char *filename
)
691 return this->AddFile(filename
, 0);
694 bool TarScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
696 /* No tar within tar. */
697 assert(tar_filename
== nullptr);
699 /* The TAR-header, repeated for every file */
701 char name
[100]; ///< Name of the file
705 char size
[12]; ///< Size of the file, in ASCII
716 char prefix
[155]; ///< Path of the file
721 /* Check if we already seen this file */
722 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
723 if (it
!= _tar_list
[this->subdir
].end()) return false;
725 FILE *f
= fopen(filename
, "rb");
726 /* Although the file has been found there can be
727 * a number of reasons we cannot open the file.
728 * Most common case is when we simply have not
729 * been given read access. */
730 if (f
== nullptr) return false;
732 const char *dupped_filename
= stredup(filename
);
733 _tar_list
[this->subdir
][filename
].filename
= dupped_filename
;
734 _tar_list
[this->subdir
][filename
].dirname
= nullptr;
736 TarLinkList links
; ///< Temporary list to collect links
739 char buf
[sizeof(th
.name
) + 1], *end
;
740 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
741 char link
[sizeof(th
.linkname
) + 1];
742 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
743 size_t num
= 0, pos
= 0;
745 /* Make a char of 512 empty bytes */
747 memset(&empty
[0], 0, sizeof(empty
));
749 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
750 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
751 if (num_bytes_read
!= 512) break;
752 pos
+= num_bytes_read
;
754 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
755 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
756 /* If we have only zeros in the block, it can be an end-of-file indicator */
757 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
759 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
);
766 /* The prefix contains the directory-name */
767 if (th
.prefix
[0] != '\0') {
768 strecpy(name
, th
.prefix
, lastof(name
));
769 strecat(name
, PATHSEP
, lastof(name
));
772 /* Copy the name of the file in a safe way at the end of 'name' */
773 strecat(name
, th
.name
, lastof(name
));
775 /* Calculate the size of the file.. for some strange reason this is stored as a string */
776 strecpy(buf
, th
.size
, lastof(buf
));
777 size_t skip
= strtoul(buf
, &end
, 8);
779 switch (th
.typeflag
) {
781 case '0': { // regular file
782 /* Ignore empty files */
783 if (skip
== 0) break;
785 if (strlen(name
) == 0) break;
787 /* Store this entry in the list */
788 TarFileListEntry entry
;
789 entry
.tar_filename
= dupped_filename
;
791 entry
.position
= pos
;
793 /* Convert to lowercase and our PATHSEPCHAR */
794 SimplifyFileName(name
);
796 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
797 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
802 case '1': // hard links
803 case '2': { // symbolic links
804 /* Copy the destination of the link in a safe way at the end of 'linkname' */
805 strecpy(link
, th
.linkname
, lastof(link
));
807 if (strlen(name
) == 0 || strlen(link
) == 0) break;
809 /* Convert to lowercase and our PATHSEPCHAR */
810 SimplifyFileName(name
);
811 SimplifyFileName(link
);
813 /* Only allow relative links */
814 if (link
[0] == PATHSEPCHAR
) {
815 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
819 /* Process relative path.
820 * Note: The destination of links must not contain any directory-links. */
821 strecpy(dest
, name
, lastof(dest
));
822 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
823 if (destpos
== nullptr) destpos
= dest
;
827 while (*pos
!= '\0') {
828 char *next
= strchr(pos
, PATHSEPCHAR
);
829 if (next
== nullptr) {
830 next
= pos
+ strlen(pos
);
832 /* Terminate the substring up to the path separator character. */
836 if (strcmp(pos
, ".") == 0) {
837 /* Skip '.' (current dir) */
838 } else if (strcmp(pos
, "..") == 0) {
840 if (dest
[0] == '\0') {
841 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
845 /* Truncate 'dest' after last PATHSEPCHAR.
846 * This assumes that the truncated part is a real directory and not a link. */
847 destpos
= strrchr(dest
, PATHSEPCHAR
);
848 if (destpos
== nullptr) destpos
= dest
;
851 /* Append at end of 'dest' */
852 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
853 destpos
= strecpy(destpos
, pos
, lastof(dest
));
856 if (destpos
>= lastof(dest
)) {
857 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
);
865 /* Store links in temporary list */
866 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
867 links
.insert(TarLinkList::value_type(name
, dest
));
872 case '5': // directory
873 /* Convert to lowercase and our PATHSEPCHAR */
874 SimplifyFileName(name
);
876 /* Store the first directory name we detect */
877 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
878 if (_tar_list
[this->subdir
][filename
].dirname
== nullptr) _tar_list
[this->subdir
][filename
].dirname
= stredup(name
);
882 /* Ignore other types */
886 /* Skip to the next block.. */
887 skip
= Align(skip
, 512);
888 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
889 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
);
896 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
, num
);
899 /* Resolve file links and store directory links.
900 * We restrict usage of links to two cases:
901 * 1) Links to directories:
902 * Both the source path and the destination path must NOT contain any further links.
903 * When resolving files at most one directory link is resolved.
905 * The destination path must NOT contain any links.
906 * The source path may contain one directory link.
908 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
909 const std::string
&src
= link
->first
;
910 const std::string
&dest
= link
->second
;
911 TarAddLink(src
, dest
, this->subdir
);
918 * Extract the tar with the given filename in the directory
919 * where the tar resides.
920 * @param tar_filename the name of the tar to extract.
921 * @param subdir The sub directory the tar is in.
922 * @return false on failure.
924 bool ExtractTar(const char *tar_filename
, Subdirectory subdir
)
926 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
927 /* We don't know the file. */
928 if (it
== _tar_list
[subdir
].end()) return false;
930 const char *dirname
= (*it
).second
.dirname
;
932 /* The file doesn't have a sub directory! */
933 if (dirname
== nullptr) return false;
935 char filename
[MAX_PATH
];
936 strecpy(filename
, tar_filename
, lastof(filename
));
937 char *p
= strrchr(filename
, PATHSEPCHAR
);
938 /* The file's path does not have a separator? */
939 if (p
== nullptr) return false;
942 strecpy(p
, dirname
, lastof(filename
));
943 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
, filename
);
944 FioCreateDirectory(filename
);
946 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
947 if (strcmp((*it2
).second
.tar_filename
, tar_filename
) != 0) continue;
949 strecpy(p
, (*it2
).first
.c_str(), lastof(filename
));
951 DEBUG(misc
, 9, " extracting %s", filename
);
953 /* First open the file in the .tar. */
955 FILE *in
= FioFOpenFileTar(&(*it2
).second
, &to_copy
);
957 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, tar_filename
);
961 /* Now open the 'output' file. */
962 FILE *out
= fopen(filename
, "wb");
963 if (out
== nullptr) {
964 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, filename
);
969 /* Now read from the tar and write it into the file. */
972 for (; to_copy
!= 0; to_copy
-= read
) {
973 read
= fread(buffer
, 1, min(to_copy
, lengthof(buffer
)), in
);
974 if (read
<= 0 || fwrite(buffer
, 1, read
, out
) != read
) break;
977 /* Close everything up. */
982 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
, (int)to_copy
);
987 DEBUG(misc
, 9, " extraction successful");
991 #if defined(WIN32) || defined(WINCE)
993 * Determine the base (personal dir and game data dir) paths
994 * @param exe the path from the current path to the executable
995 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
997 extern void DetermineBasePaths(const char *exe
);
998 #else /* defined(WIN32) || defined(WINCE) */
1001 * Changes the working directory to the path of the give executable.
1002 * For OSX application bundles '.app' is the required extension of the bundle,
1003 * so when we crop the path to there, when can remove the name of the bundle
1004 * in the same way we remove the name from the executable name.
1005 * @param exe the path to the executable
1007 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
1010 strecpy(tmp
, exe
, lastof(tmp
));
1012 bool success
= false;
1014 char *app_bundle
= strchr(tmp
, '.');
1015 while (app_bundle
!= nullptr && strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
1017 if (app_bundle
!= nullptr) *app_bundle
= '\0';
1018 #endif /* WITH_COCOA */
1019 char *s
= strrchr(tmp
, PATHSEPCHAR
);
1022 #if defined(__DJGPP__)
1023 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1024 if (s
> tmp
&& *(s
- 1) == ':') chdir("/");
1026 if (chdir(tmp
) != 0) {
1027 DEBUG(misc
, 0, "Directory with the binary does not exist?");
1036 * Whether we should scan the working directory.
1037 * It should not be scanned if it's the root or
1038 * the home directory as in both cases a big data
1039 * directory can cause huge amounts of unrelated
1040 * files scanned. Furthermore there are nearly no
1041 * use cases for the home/root directory to have
1042 * OpenTTD directories.
1043 * @return true if it should be scanned.
1045 bool DoScanWorkingDirectory()
1047 /* No working directory, so nothing to do. */
1048 if (_searchpaths
[SP_WORKING_DIR
] == nullptr) return false;
1050 /* Working directory is root, so do nothing. */
1051 if (strcmp(_searchpaths
[SP_WORKING_DIR
], PATHSEP
) == 0) return false;
1053 /* No personal/home directory, so the working directory won't be that. */
1054 if (_searchpaths
[SP_PERSONAL_DIR
] == nullptr) return true;
1057 seprintf(tmp
, lastof(tmp
), "%s%s", _searchpaths
[SP_WORKING_DIR
], PERSONAL_DIR
);
1058 AppendPathSeparator(tmp
, lastof(tmp
));
1059 return strcmp(tmp
, _searchpaths
[SP_PERSONAL_DIR
]) != 0;
1063 * Determine the base (personal dir and game data dir) paths
1064 * @param exe the path to the executable
1066 void DetermineBasePaths(const char *exe
)
1069 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1070 const char *xdg_data_home
= xdgDataHome(nullptr);
1071 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", xdg_data_home
,
1072 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1073 free(xdg_data_home
);
1075 AppendPathSeparator(tmp
, lastof(tmp
));
1076 _searchpaths
[SP_PERSONAL_DIR_XDG
] = stredup(tmp
);
1078 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1079 _searchpaths
[SP_PERSONAL_DIR
] = nullptr;
1083 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
1084 const char *homedir
= stredup(path
.Path());
1086 /* getenv is highly unsafe; duplicate it as soon as possible,
1087 * or at least before something else touches the environment
1088 * variables in any way. It can also contain all kinds of
1089 * unvalidated data we rather not want internally. */
1090 const char *homedir
= getenv("HOME");
1091 if (homedir
!= nullptr) {
1092 homedir
= stredup(homedir
);
1095 if (homedir
== nullptr) {
1096 const struct passwd
*pw
= getpwuid(getuid());
1097 homedir
= (pw
== nullptr) ? nullptr : stredup(pw
->pw_dir
);
1101 if (homedir
!= nullptr) {
1102 ValidateString(homedir
);
1103 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", homedir
, PERSONAL_DIR
);
1104 AppendPathSeparator(tmp
, lastof(tmp
));
1106 _searchpaths
[SP_PERSONAL_DIR
] = stredup(tmp
);
1109 _searchpaths
[SP_PERSONAL_DIR
] = nullptr;
1113 #if defined(WITH_SHARED_DIR)
1114 seprintf(tmp
, lastof(tmp
), "%s", SHARED_DIR
);
1115 AppendPathSeparator(tmp
, lastof(tmp
));
1116 _searchpaths
[SP_SHARED_DIR
] = stredup(tmp
);
1118 _searchpaths
[SP_SHARED_DIR
] = nullptr;
1121 #if defined(__MORPHOS__) || defined(__AMIGA__)
1122 _searchpaths
[SP_WORKING_DIR
] = nullptr;
1124 if (getcwd(tmp
, MAX_PATH
) == nullptr) *tmp
= '\0';
1125 AppendPathSeparator(tmp
, lastof(tmp
));
1126 _searchpaths
[SP_WORKING_DIR
] = stredup(tmp
);
1129 _do_scan_working_directory
= DoScanWorkingDirectory();
1131 /* Change the working directory to that one of the executable */
1132 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1133 if (getcwd(tmp
, MAX_PATH
) == nullptr) *tmp
= '\0';
1134 AppendPathSeparator(tmp
, lastof(tmp
));
1135 _searchpaths
[SP_BINARY_DIR
] = stredup(tmp
);
1137 _searchpaths
[SP_BINARY_DIR
] = nullptr;
1140 if (_searchpaths
[SP_WORKING_DIR
] != nullptr) {
1141 /* Go back to the current working directory. */
1142 if (chdir(_searchpaths
[SP_WORKING_DIR
]) != 0) {
1143 DEBUG(misc
, 0, "Failed to return to working directory!");
1147 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1148 _searchpaths
[SP_INSTALLATION_DIR
] = nullptr;
1150 seprintf(tmp
, lastof(tmp
), "%s", GLOBAL_DATA_DIR
);
1151 AppendPathSeparator(tmp
, lastof(tmp
));
1152 _searchpaths
[SP_INSTALLATION_DIR
] = stredup(tmp
);
1155 extern void cocoaSetApplicationBundleDir();
1156 cocoaSetApplicationBundleDir();
1158 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
] = nullptr;
1161 #endif /* defined(WIN32) || defined(WINCE) */
1163 const char *_personal_dir
;
1166 * Acquire the base paths (personal dir and game data dir),
1167 * fill all other paths (save dir, autosave dir etc) and
1168 * make the save and scenario directories.
1169 * @param exe the path from the current path to the executable
1171 void DeterminePaths(const char *exe
)
1173 DetermineBasePaths(exe
);
1175 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1176 char config_home
[MAX_PATH
];
1178 const char *xdg_config_home
= xdgConfigHome(nullptr);
1179 seprintf(config_home
, lastof(config_home
), "%s" PATHSEP
"%s", xdg_config_home
,
1180 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1181 free(xdg_config_home
);
1183 AppendPathSeparator(config_home
, lastof(config_home
));
1187 FOR_ALL_SEARCHPATHS(sp
) {
1188 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1189 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
]);
1193 if (_config_file
!= nullptr) {
1194 config_dir
= stredup(_config_file
);
1195 char *end
= strrchr(config_dir
, PATHSEPCHAR
);
1196 if (end
== nullptr) {
1197 config_dir
[0] = '\0';
1202 char personal_dir
[MAX_PATH
];
1203 if (FioFindFullPath(personal_dir
, lastof(personal_dir
), BASE_DIR
, "openttd.cfg") != nullptr) {
1204 char *end
= strrchr(personal_dir
, PATHSEPCHAR
);
1205 if (end
!= nullptr) end
[1] = '\0';
1206 config_dir
= stredup(personal_dir
);
1207 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1209 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1210 /* No previous configuration file found. Use the configuration folder from XDG. */
1211 config_dir
= config_home
;
1213 static const Searchpath new_openttd_cfg_order
[] = {
1214 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1217 config_dir
= nullptr;
1218 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1219 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1220 config_dir
= stredup(_searchpaths
[new_openttd_cfg_order
[i
]]);
1224 assert(config_dir
!= nullptr);
1226 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1230 DEBUG(misc
, 3, "%s found as config directory", config_dir
);
1232 _highscore_file
= str_fmt("%shs.dat", config_dir
);
1233 extern char *_hotkeys_file
;
1234 _hotkeys_file
= str_fmt("%shotkeys.cfg", config_dir
);
1235 extern char *_windows_file
;
1236 _windows_file
= str_fmt("%swindows.cfg", config_dir
);
1238 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1239 if (config_dir
== config_home
) {
1240 /* We are using the XDG configuration home for the config file,
1241 * then store the rest in the XDG data home folder. */
1242 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1243 FioCreateDirectory(_personal_dir
);
1247 _personal_dir
= config_dir
;
1250 /* Make the necessary folders */
1251 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1252 FioCreateDirectory(config_dir
);
1253 if (config_dir
!= _personal_dir
) FioCreateDirectory(_personal_dir
);
1256 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
);
1258 static const Subdirectory default_subdirs
[] = {
1259 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1262 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1263 char *dir
= str_fmt("%s%s", _personal_dir
, _subdirs
[default_subdirs
[i
]]);
1264 FioCreateDirectory(dir
);
1268 /* If we have network we make a directory for the autodownloading of content */
1269 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = str_fmt("%s%s", _personal_dir
, "content_download" PATHSEP
);
1270 #ifdef ENABLE_NETWORK
1271 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1273 /* Create the directory for each of the types of content */
1274 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1275 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1276 char *tmp
= str_fmt("%s%s", _searchpaths
[SP_AUTODOWNLOAD_DIR
], _subdirs
[dirs
[i
]]);
1277 FioCreateDirectory(tmp
);
1281 extern char *_log_file
;
1282 _log_file
= str_fmt("%sopenttd.log", _personal_dir
);
1283 #else /* ENABLE_NETWORK */
1284 /* If we don't have networking, we don't need to make the directory. But
1285 * if it exists we keep it, otherwise remove it from the search paths. */
1286 if (!FileExists(_searchpaths
[SP_AUTODOWNLOAD_DIR
])) {
1287 free(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1288 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = nullptr;
1290 #endif /* ENABLE_NETWORK */
1294 * Sanitizes a filename, i.e. removes all illegal characters from it.
1295 * @param filename the "\0" terminated filename
1297 void SanitizeFilename(char *filename
)
1299 for (; *filename
!= '\0'; filename
++) {
1300 switch (*filename
) {
1301 /* The following characters are not allowed in filenames
1302 * on at least one of the supported operating systems: */
1303 case ':': case '\\': case '*': case '?': case '/':
1304 case '<': case '>': case '|': case '"':
1312 * Load a file into memory.
1313 * @param filename Name of the file to load.
1314 * @param lenp [out] Length of loaded data.
1315 * @param maxsize Maximum size to load.
1316 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1317 * @note If \a maxsize less than the length of the file, loading fails.
1319 void *ReadFileToMem(const char *filename
, size_t *lenp
, size_t maxsize
)
1321 FILE *in
= fopen(filename
, "rb");
1322 if (in
== nullptr) return nullptr;
1324 fseek(in
, 0, SEEK_END
);
1325 size_t len
= ftell(in
);
1326 fseek(in
, 0, SEEK_SET
);
1327 if (len
> maxsize
) {
1331 byte
*mem
= MallocT
<byte
>(len
+ 1);
1333 if (fread(mem
, len
, 1, in
) != 1) {
1345 * Helper to see whether a given filename matches the extension.
1346 * @param extension The extension to look for.
1347 * @param filename The filename to look in for the extension.
1348 * @return True iff the extension is nullptr, or the filename ends with it.
1350 static bool MatchesExtension(const char *extension
, const char *filename
)
1352 if (extension
== nullptr) return true;
1354 const char *ext
= strrchr(filename
, extension
[0]);
1355 return ext
!= nullptr && strcasecmp(ext
, extension
) == 0;
1359 * Scan a single directory (and recursively its children) and add
1360 * any graphics sets that are found.
1361 * @param fs the file scanner to add the files to
1362 * @param extension the extension of files to search for.
1363 * @param path full path we're currently at
1364 * @param basepath_length from where in the path are we 'based' on the search path
1365 * @param recursive whether to recursively search the sub directories
1367 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1369 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1373 struct dirent
*dirent
;
1376 if (path
== nullptr || (dir
= ttd_opendir(path
)) == nullptr) return 0;
1378 while ((dirent
= readdir(dir
)) != nullptr) {
1379 const char *d_name
= FS2OTTD(dirent
->d_name
);
1380 char filename
[MAX_PATH
];
1382 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1384 seprintf(filename
, lastof(filename
), "%s%s", path
, d_name
);
1386 if (S_ISDIR(sb
.st_mode
)) {
1388 if (!recursive
) continue;
1389 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1390 if (!AppendPathSeparator(filename
, lastof(filename
))) continue;
1391 num
+= ScanPath(fs
, extension
, filename
, basepath_length
, recursive
);
1392 } else if (S_ISREG(sb
.st_mode
)) {
1394 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, basepath_length
, nullptr)) num
++;
1404 * Scan the given tar and add graphics sets when it finds one.
1405 * @param fs the file scanner to scan for
1406 * @param extension the extension of files to search for.
1407 * @param tar the tar to search in.
1409 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1412 const char *filename
= (*tar
).first
.c_str();
1414 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1420 * Scan for files with the given extension in the given search path.
1421 * @param extension the extension of files to search for.
1422 * @param sd the sub directory to search in.
1423 * @param tars whether to search in the tars too.
1424 * @param recursive whether to search recursively
1425 * @return the number of found files, i.e. the number of times that
1426 * AddFile returned true.
1428 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1433 char path
[MAX_PATH
];
1434 TarFileList::iterator tar
;
1437 FOR_ALL_SEARCHPATHS(sp
) {
1438 /* Don't search in the working directory */
1439 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1441 FioAppendDirectory(path
, lastof(path
), sp
, sd
);
1442 num
+= ScanPath(this, extension
, path
, strlen(path
), recursive
);
1445 if (tars
&& sd
!= NO_DIRECTORY
) {
1446 FOR_ALL_TARS(tar
, sd
) {
1447 num
+= ScanTar(this, extension
, tar
);
1453 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1456 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1466 * Scan for files with the given extension in the given search path.
1467 * @param extension the extension of files to search for.
1468 * @param directory the sub directory to search in.
1469 * @param recursive whether to search recursively
1470 * @return the number of found files, i.e. the number of times that
1471 * AddFile returned true.
1473 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1475 char path
[MAX_PATH
];
1476 strecpy(path
, directory
, lastof(path
));
1477 if (!AppendPathSeparator(path
, lastof(path
))) return 0;
1478 return ScanPath(this, extension
, path
, strlen(path
), recursive
);