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(uint8 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
] == NULL
) {
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(uint8 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
];
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
] != NULL
) {
200 fclose(_fio
.handles
[slot
]);
202 free(_fio
.shortnames
[slot
]);
203 _fio
.shortnames
[slot
] = NULL
;
205 _fio
.handles
[slot
] = NULL
;
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
] != NULL
&& _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(int slot
, const char *filename
, Subdirectory subdir
)
254 #if defined(LIMITED_FDS)
256 #endif /* LIMITED_FDS */
257 f
= FioFOpenFile(filename
, "rb", subdir
);
258 if (f
== NULL
) 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
== NULL
? filename
: t
);
269 char *t2
= strrchr(_fio
.shortnames
[slot
], '.');
270 if (t2
!= NULL
) *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
== NULL
) 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
)
328 return access(OTTD2FS(filename
), 0) == 0;
332 * Close a file in a safe way.
334 void FioFCloseFile(FILE *f
)
339 char *FioGetFullPath(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
, const char *filename
)
341 assert(subdir
< NUM_SUBDIRS
);
342 assert(sp
< NUM_SEARCHPATHS
);
344 seprintf(buf
, last
, "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
349 * Find a path to the filename in one of the search directories.
350 * @param[out] buf Destination buffer for the path.
351 * @param last End of the destination buffer.
352 * @param subdir Subdirectory to try.
353 * @param filename Filename to look for.
354 * @return \a buf containing the path if the path was found, else \c NULL.
356 char *FioFindFullPath(char *buf
, const char *last
, Subdirectory subdir
, const char *filename
)
359 assert(subdir
< NUM_SUBDIRS
);
361 FOR_ALL_SEARCHPATHS(sp
) {
362 FioGetFullPath(buf
, last
, sp
, subdir
, filename
);
363 if (FileExists(buf
)) return buf
;
365 /* Be, as opening files, aware that sometimes the filename
366 * might be in uppercase when it is in lowercase on the
367 * disk. Of course Windows doesn't care about casing. */
368 if (strtolower(buf
+ strlen(_searchpaths
[sp
]) - 1) && FileExists(buf
)) return buf
;
375 char *FioAppendDirectory(char *buf
, const char *last
, Searchpath sp
, Subdirectory subdir
)
377 assert(subdir
< NUM_SUBDIRS
);
378 assert(sp
< NUM_SEARCHPATHS
);
380 seprintf(buf
, last
, "%s%s", _searchpaths
[sp
], _subdirs
[subdir
]);
384 char *FioGetDirectory(char *buf
, const char *last
, Subdirectory subdir
)
388 /* Find and return the first valid directory */
389 FOR_ALL_SEARCHPATHS(sp
) {
390 char *ret
= FioAppendDirectory(buf
, last
, sp
, subdir
);
391 if (FileExists(buf
)) return ret
;
394 /* Could not find the directory, fall back to a base path */
395 strecpy(buf
, _personal_dir
, last
);
400 static FILE *FioFOpenFileSp(const char *filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
402 #if defined(_WIN32) && defined(UNICODE)
403 /* fopen is implemented as a define with ellipses for
404 * Unicode support (prepend an L). As we are not sending
405 * a string, but a variable, it 'renames' the variable,
406 * so make that variable to makes it compile happily */
408 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
413 if (subdir
== NO_DIRECTORY
) {
414 strecpy(buf
, filename
, lastof(buf
));
416 seprintf(buf
, lastof(buf
), "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
420 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
)) == INVALID_FILE_ATTRIBUTES
) return NULL
;
423 f
= fopen(buf
, mode
);
425 if (f
== NULL
&& strtolower(buf
+ ((subdir
== NO_DIRECTORY
) ? 0 : strlen(_searchpaths
[sp
]) - 1))) {
426 f
= fopen(buf
, mode
);
429 if (f
!= NULL
&& filesize
!= NULL
) {
430 /* Find the size of the file */
431 fseek(f
, 0, SEEK_END
);
432 *filesize
= ftell(f
);
433 fseek(f
, 0, SEEK_SET
);
439 * Opens a file from inside a tar archive.
440 * @param entry The entry to open.
441 * @param[out] filesize If not \c NULL, size of the opened file.
442 * @return File handle of the opened file, or \c NULL if the file is not available.
443 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
445 FILE *FioFOpenFileTar(TarFileListEntry
*entry
, size_t *filesize
)
447 FILE *f
= fopen(entry
->tar_filename
, "rb");
448 if (f
== NULL
) return f
;
450 if (fseek(f
, entry
->position
, SEEK_SET
) < 0) {
455 if (filesize
!= NULL
) *filesize
= entry
->size
;
460 * Opens a OpenTTD file somewhere in a personal or global directory.
461 * @param filename Name of the file to open.
462 * @param subdir Subdirectory to open.
463 * @return File handle of the opened file, or \c NULL if the file is not available.
465 FILE *FioFOpenFile(const char *filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
470 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
472 FOR_ALL_SEARCHPATHS(sp
) {
473 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
474 if (f
!= NULL
|| subdir
== NO_DIRECTORY
) break;
477 /* We can only use .tar in case of data-dir, and read-mode */
478 if (f
== NULL
&& mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
479 static const uint MAX_RESOLVED_LENGTH
= 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
480 char resolved_name
[MAX_RESOLVED_LENGTH
];
482 /* Filenames in tars are always forced to be lowercase */
483 strecpy(resolved_name
, filename
, lastof(resolved_name
));
484 strtolower(resolved_name
);
486 size_t resolved_len
= strlen(resolved_name
);
488 /* Resolve ONE directory link */
489 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
490 const std::string
&src
= link
->first
;
491 size_t len
= src
.length();
492 if (resolved_len
>= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& strncmp(src
.c_str(), resolved_name
, len
) == 0) {
494 char resolved_name2
[MAX_RESOLVED_LENGTH
];
495 const std::string
&dest
= link
->second
;
496 strecpy(resolved_name2
, &(resolved_name
[len
]), lastof(resolved_name2
));
497 strecpy(resolved_name
, dest
.c_str(), lastof(resolved_name
));
498 strecpy(&(resolved_name
[dest
.length()]), resolved_name2
, lastof(resolved_name
));
499 break; // Only resolve one level
503 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
504 if (it
!= _tar_filelist
[subdir
].end()) {
505 f
= FioFOpenFileTar(&((*it
).second
), filesize
);
509 /* Sometimes a full path is given. To support
510 * the 'subdirectory' must be 'removed'. */
511 if (f
== NULL
&& subdir
!= NO_DIRECTORY
) {
514 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
515 if (f
!= NULL
) break;
518 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
522 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
531 * Create a directory with the given name
532 * @param name the new name of the directory
534 void FioCreateDirectory(const char *name
)
536 /* Ignore directory creation errors; they'll surface later on, and most
537 * of the time they are 'directory already exists' errors anyhow. */
539 CreateDirectory(OTTD2FS(name
), NULL
);
540 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
541 mkdir(OTTD2FS(name
));
542 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
544 strecpy(buf
, name
, lastof(buf
));
546 size_t len
= strlen(name
) - 1;
547 if (buf
[len
] == '/') {
548 buf
[len
] = '\0'; // Kill pathsep, so mkdir() will not fail
551 mkdir(OTTD2FS(buf
), 0755);
553 mkdir(OTTD2FS(name
), 0755);
558 * Appends, if necessary, the path separator character to the end of the string.
559 * It does not add the path separator to zero-sized strings.
560 * @param buf string to append the separator to
561 * @param last the last element of \a buf.
562 * @return true iff the operation succeeded
564 bool AppendPathSeparator(char *buf
, const char *last
)
566 size_t s
= strlen(buf
);
568 /* Length of string + path separator + '\0' */
569 if (s
!= 0 && buf
[s
- 1] != PATHSEPCHAR
) {
570 if (&buf
[s
] >= last
) return false;
572 seprintf(buf
+ s
, last
, "%c", PATHSEPCHAR
);
579 * Find the first directory in a tar archive.
580 * @param tarname the name of the tar archive to look in.
581 * @param subdir the subdirectory to look in.
583 const char *FioTarFirstDir(const char *tarname
, Subdirectory subdir
)
585 TarList::iterator it
= _tar_list
[subdir
].find(tarname
);
586 if (it
== _tar_list
[subdir
].end()) return NULL
;
587 return (*it
).second
.dirname
;
590 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
592 std::string src
= srcParam
;
593 std::string dest
= destParam
;
594 /* Tar internals assume lowercase */
595 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
596 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
598 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
599 if (dest_file
!= _tar_filelist
[subdir
].end()) {
600 /* Link to file. Process the link like the destination file. */
601 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
603 /* Destination file not found. Assume 'link to directory'
604 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
605 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
606 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
607 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
611 void FioTarAddLink(const char *src
, const char *dest
, Subdirectory subdir
)
613 TarAddLink(src
, dest
, subdir
);
617 * Simplify filenames from tars.
618 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
619 * @param name Filename to process.
621 static void SimplifyFileName(char *name
)
623 /* Force lowercase */
626 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
627 #if (PATHSEPCHAR != '/')
628 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
633 * Perform the scanning of a particular subdirectory.
634 * @param sd The subdirectory to scan.
635 * @return The number of found tar files.
637 uint
TarScanner::DoScan(Subdirectory sd
)
639 _tar_filelist
[sd
].clear();
640 _tar_list
[sd
].clear();
641 uint num
= this->Scan(".tar", sd
, false);
642 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
646 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
648 DEBUG(misc
, 1, "Scanning for tars");
651 if (mode
& TarScanner::BASESET
) {
652 num
+= fs
.DoScan(BASESET_DIR
);
654 if (mode
& TarScanner::NEWGRF
) {
655 num
+= fs
.DoScan(NEWGRF_DIR
);
657 if (mode
& TarScanner::AI
) {
658 num
+= fs
.DoScan(AI_DIR
);
659 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
661 if (mode
& TarScanner::GAME
) {
662 num
+= fs
.DoScan(GAME_DIR
);
663 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
665 if (mode
& TarScanner::SCENARIO
) {
666 num
+= fs
.DoScan(SCENARIO_DIR
);
667 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
669 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
674 * Add a single file to the scanned files of a tar, circumventing the scanning code.
675 * @param sd The sub directory the file is in.
676 * @param filename The name of the file to add.
677 * @return True if the additions went correctly.
679 bool TarScanner::AddFile(Subdirectory sd
, const char *filename
)
682 return this->AddFile(filename
, 0);
685 bool TarScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
687 /* No tar within tar. */
688 assert(tar_filename
== NULL
);
690 /* The TAR-header, repeated for every file */
692 char name
[100]; ///< Name of the file
696 char size
[12]; ///< Size of the file, in ASCII
707 char prefix
[155]; ///< Path of the file
712 /* Check if we already seen this file */
713 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
714 if (it
!= _tar_list
[this->subdir
].end()) return false;
716 FILE *f
= fopen(filename
, "rb");
717 /* Although the file has been found there can be
718 * a number of reasons we cannot open the file.
719 * Most common case is when we simply have not
720 * been given read access. */
721 if (f
== NULL
) return false;
723 const char *dupped_filename
= stredup(filename
);
724 _tar_list
[this->subdir
][filename
].filename
= dupped_filename
;
725 _tar_list
[this->subdir
][filename
].dirname
= NULL
;
727 TarLinkList links
; ///< Temporary list to collect links
730 char buf
[sizeof(th
.name
) + 1], *end
;
731 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
732 char link
[sizeof(th
.linkname
) + 1];
733 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
734 size_t num
= 0, pos
= 0;
736 /* Make a char of 512 empty bytes */
738 memset(&empty
[0], 0, sizeof(empty
));
740 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
741 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
742 if (num_bytes_read
!= 512) break;
743 pos
+= num_bytes_read
;
745 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
746 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
747 /* If we have only zeros in the block, it can be an end-of-file indicator */
748 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
750 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
);
757 /* The prefix contains the directory-name */
758 if (th
.prefix
[0] != '\0') {
759 strecpy(name
, th
.prefix
, lastof(name
));
760 strecat(name
, PATHSEP
, lastof(name
));
763 /* Copy the name of the file in a safe way at the end of 'name' */
764 strecat(name
, th
.name
, lastof(name
));
766 /* Calculate the size of the file.. for some strange reason this is stored as a string */
767 strecpy(buf
, th
.size
, lastof(buf
));
768 size_t skip
= strtoul(buf
, &end
, 8);
770 switch (th
.typeflag
) {
772 case '0': { // regular file
773 /* Ignore empty files */
774 if (skip
== 0) break;
776 if (strlen(name
) == 0) break;
778 /* Store this entry in the list */
779 TarFileListEntry entry
;
780 entry
.tar_filename
= dupped_filename
;
782 entry
.position
= pos
;
784 /* Convert to lowercase and our PATHSEPCHAR */
785 SimplifyFileName(name
);
787 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
788 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
793 case '1': // hard links
794 case '2': { // symbolic links
795 /* Copy the destination of the link in a safe way at the end of 'linkname' */
796 strecpy(link
, th
.linkname
, lastof(link
));
798 if (strlen(name
) == 0 || strlen(link
) == 0) break;
800 /* Convert to lowercase and our PATHSEPCHAR */
801 SimplifyFileName(name
);
802 SimplifyFileName(link
);
804 /* Only allow relative links */
805 if (link
[0] == PATHSEPCHAR
) {
806 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
810 /* Process relative path.
811 * Note: The destination of links must not contain any directory-links. */
812 strecpy(dest
, name
, lastof(dest
));
813 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
814 if (destpos
== NULL
) destpos
= dest
;
818 while (*pos
!= '\0') {
819 char *next
= strchr(pos
, PATHSEPCHAR
);
821 next
= pos
+ strlen(pos
);
823 /* Terminate the substring up to the path separator character. */
827 if (strcmp(pos
, ".") == 0) {
828 /* Skip '.' (current dir) */
829 } else if (strcmp(pos
, "..") == 0) {
831 if (dest
[0] == '\0') {
832 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
836 /* Truncate 'dest' after last PATHSEPCHAR.
837 * This assumes that the truncated part is a real directory and not a link. */
838 destpos
= strrchr(dest
, PATHSEPCHAR
);
839 if (destpos
== NULL
) destpos
= dest
;
842 /* Append at end of 'dest' */
843 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
844 destpos
= strecpy(destpos
, pos
, lastof(dest
));
847 if (destpos
>= lastof(dest
)) {
848 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
);
856 /* Store links in temporary list */
857 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
858 links
.insert(TarLinkList::value_type(name
, dest
));
863 case '5': // directory
864 /* Convert to lowercase and our PATHSEPCHAR */
865 SimplifyFileName(name
);
867 /* Store the first directory name we detect */
868 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
869 if (_tar_list
[this->subdir
][filename
].dirname
== NULL
) _tar_list
[this->subdir
][filename
].dirname
= stredup(name
);
873 /* Ignore other types */
877 /* Skip to the next block.. */
878 skip
= Align(skip
, 512);
879 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
880 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
);
887 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
, num
);
890 /* Resolve file links and store directory links.
891 * We restrict usage of links to two cases:
892 * 1) Links to directories:
893 * Both the source path and the destination path must NOT contain any further links.
894 * When resolving files at most one directory link is resolved.
896 * The destination path must NOT contain any links.
897 * The source path may contain one directory link.
899 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
900 const std::string
&src
= link
->first
;
901 const std::string
&dest
= link
->second
;
902 TarAddLink(src
, dest
, this->subdir
);
909 * Extract the tar with the given filename in the directory
910 * where the tar resides.
911 * @param tar_filename the name of the tar to extract.
912 * @param subdir The sub directory the tar is in.
913 * @return false on failure.
915 bool ExtractTar(const char *tar_filename
, Subdirectory subdir
)
917 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
918 /* We don't know the file. */
919 if (it
== _tar_list
[subdir
].end()) return false;
921 const char *dirname
= (*it
).second
.dirname
;
923 /* The file doesn't have a sub directory! */
924 if (dirname
== NULL
) return false;
926 char filename
[MAX_PATH
];
927 strecpy(filename
, tar_filename
, lastof(filename
));
928 char *p
= strrchr(filename
, PATHSEPCHAR
);
929 /* The file's path does not have a separator? */
930 if (p
== NULL
) return false;
933 strecpy(p
, dirname
, lastof(filename
));
934 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
, filename
);
935 FioCreateDirectory(filename
);
937 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
938 if (strcmp((*it2
).second
.tar_filename
, tar_filename
) != 0) continue;
940 strecpy(p
, (*it2
).first
.c_str(), lastof(filename
));
942 DEBUG(misc
, 9, " extracting %s", filename
);
944 /* First open the file in the .tar. */
946 FILE *in
= FioFOpenFileTar(&(*it2
).second
, &to_copy
);
948 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, tar_filename
);
952 /* Now open the 'output' file. */
953 FILE *out
= fopen(filename
, "wb");
955 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, filename
);
960 /* Now read from the tar and write it into the file. */
963 for (; to_copy
!= 0; to_copy
-= read
) {
964 read
= fread(buffer
, 1, min(to_copy
, lengthof(buffer
)), in
);
965 if (read
<= 0 || fwrite(buffer
, 1, read
, out
) != read
) break;
968 /* Close everything up. */
973 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
, (int)to_copy
);
978 DEBUG(misc
, 9, " extraction successful");
984 * Determine the base (personal dir and game data dir) paths
985 * @param exe the path from the current path to the executable
986 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
988 extern void DetermineBasePaths(const char *exe
);
989 #else /* defined(_WIN32) */
992 * Changes the working directory to the path of the give executable.
993 * For OSX application bundles '.app' is the required extension of the bundle,
994 * so when we crop the path to there, when can remove the name of the bundle
995 * in the same way we remove the name from the executable name.
996 * @param exe the path to the executable
998 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
1001 strecpy(tmp
, exe
, lastof(tmp
));
1003 bool success
= false;
1005 char *app_bundle
= strchr(tmp
, '.');
1006 while (app_bundle
!= NULL
&& strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
1008 if (app_bundle
!= NULL
) *app_bundle
= '\0';
1009 #endif /* WITH_COCOA */
1010 char *s
= strrchr(tmp
, PATHSEPCHAR
);
1013 #if defined(__DJGPP__)
1014 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1015 if (s
> tmp
&& *(s
- 1) == ':') chdir("/");
1017 if (chdir(tmp
) != 0) {
1018 DEBUG(misc
, 0, "Directory with the binary does not exist?");
1027 * Whether we should scan the working directory.
1028 * It should not be scanned if it's the root or
1029 * the home directory as in both cases a big data
1030 * directory can cause huge amounts of unrelated
1031 * files scanned. Furthermore there are nearly no
1032 * use cases for the home/root directory to have
1033 * OpenTTD directories.
1034 * @return true if it should be scanned.
1036 bool DoScanWorkingDirectory()
1038 /* No working directory, so nothing to do. */
1039 if (_searchpaths
[SP_WORKING_DIR
] == NULL
) return false;
1041 /* Working directory is root, so do nothing. */
1042 if (strcmp(_searchpaths
[SP_WORKING_DIR
], PATHSEP
) == 0) return false;
1044 /* No personal/home directory, so the working directory won't be that. */
1045 if (_searchpaths
[SP_PERSONAL_DIR
] == NULL
) return true;
1048 seprintf(tmp
, lastof(tmp
), "%s%s", _searchpaths
[SP_WORKING_DIR
], PERSONAL_DIR
);
1049 AppendPathSeparator(tmp
, lastof(tmp
));
1050 return strcmp(tmp
, _searchpaths
[SP_PERSONAL_DIR
]) != 0;
1054 * Determine the base (personal dir and game data dir) paths
1055 * @param exe the path to the executable
1057 void DetermineBasePaths(const char *exe
)
1060 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1061 const char *xdg_data_home
= xdgDataHome(NULL
);
1062 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", xdg_data_home
,
1063 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1064 free(xdg_data_home
);
1066 AppendPathSeparator(tmp
, lastof(tmp
));
1067 _searchpaths
[SP_PERSONAL_DIR_XDG
] = stredup(tmp
);
1069 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1070 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1074 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
1075 const char *homedir
= stredup(path
.Path());
1077 /* getenv is highly unsafe; duplicate it as soon as possible,
1078 * or at least before something else touches the environment
1079 * variables in any way. It can also contain all kinds of
1080 * unvalidated data we rather not want internally. */
1081 const char *homedir
= getenv("HOME");
1082 if (homedir
!= NULL
) {
1083 homedir
= stredup(homedir
);
1086 if (homedir
== NULL
) {
1087 const struct passwd
*pw
= getpwuid(getuid());
1088 homedir
= (pw
== NULL
) ? NULL
: stredup(pw
->pw_dir
);
1092 if (homedir
!= NULL
) {
1093 ValidateString(homedir
);
1094 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", homedir
, PERSONAL_DIR
);
1095 AppendPathSeparator(tmp
, lastof(tmp
));
1097 _searchpaths
[SP_PERSONAL_DIR
] = stredup(tmp
);
1100 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1104 #if defined(WITH_SHARED_DIR)
1105 seprintf(tmp
, lastof(tmp
), "%s", SHARED_DIR
);
1106 AppendPathSeparator(tmp
, lastof(tmp
));
1107 _searchpaths
[SP_SHARED_DIR
] = stredup(tmp
);
1109 _searchpaths
[SP_SHARED_DIR
] = NULL
;
1112 #if defined(__MORPHOS__) || defined(__AMIGA__)
1113 _searchpaths
[SP_WORKING_DIR
] = NULL
;
1115 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1116 AppendPathSeparator(tmp
, lastof(tmp
));
1117 _searchpaths
[SP_WORKING_DIR
] = stredup(tmp
);
1120 _do_scan_working_directory
= DoScanWorkingDirectory();
1122 /* Change the working directory to that one of the executable */
1123 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1124 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1125 AppendPathSeparator(tmp
, lastof(tmp
));
1126 _searchpaths
[SP_BINARY_DIR
] = stredup(tmp
);
1128 _searchpaths
[SP_BINARY_DIR
] = NULL
;
1131 if (_searchpaths
[SP_WORKING_DIR
] != NULL
) {
1132 /* Go back to the current working directory. */
1133 if (chdir(_searchpaths
[SP_WORKING_DIR
]) != 0) {
1134 DEBUG(misc
, 0, "Failed to return to working directory!");
1138 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1139 _searchpaths
[SP_INSTALLATION_DIR
] = NULL
;
1141 seprintf(tmp
, lastof(tmp
), "%s", GLOBAL_DATA_DIR
);
1142 AppendPathSeparator(tmp
, lastof(tmp
));
1143 _searchpaths
[SP_INSTALLATION_DIR
] = stredup(tmp
);
1146 extern void cocoaSetApplicationBundleDir();
1147 cocoaSetApplicationBundleDir();
1149 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
] = NULL
;
1152 #endif /* defined(_WIN32) */
1154 const char *_personal_dir
;
1157 * Acquire the base paths (personal dir and game data dir),
1158 * fill all other paths (save dir, autosave dir etc) and
1159 * make the save and scenario directories.
1160 * @param exe the path from the current path to the executable
1162 void DeterminePaths(const char *exe
)
1164 DetermineBasePaths(exe
);
1166 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1167 char config_home
[MAX_PATH
];
1169 const char *xdg_config_home
= xdgConfigHome(NULL
);
1170 seprintf(config_home
, lastof(config_home
), "%s" PATHSEP
"%s", xdg_config_home
,
1171 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1172 free(xdg_config_home
);
1174 AppendPathSeparator(config_home
, lastof(config_home
));
1178 FOR_ALL_SEARCHPATHS(sp
) {
1179 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1180 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
]);
1184 if (_config_file
!= NULL
) {
1185 config_dir
= stredup(_config_file
);
1186 char *end
= strrchr(config_dir
, PATHSEPCHAR
);
1188 config_dir
[0] = '\0';
1193 char personal_dir
[MAX_PATH
];
1194 if (FioFindFullPath(personal_dir
, lastof(personal_dir
), BASE_DIR
, "openttd.cfg") != NULL
) {
1195 char *end
= strrchr(personal_dir
, PATHSEPCHAR
);
1196 if (end
!= NULL
) end
[1] = '\0';
1197 config_dir
= stredup(personal_dir
);
1198 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1200 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1201 /* No previous configuration file found. Use the configuration folder from XDG. */
1202 config_dir
= config_home
;
1204 static const Searchpath new_openttd_cfg_order
[] = {
1205 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1209 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1210 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1211 config_dir
= stredup(_searchpaths
[new_openttd_cfg_order
[i
]]);
1215 assert(config_dir
!= NULL
);
1217 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1221 DEBUG(misc
, 3, "%s found as config directory", config_dir
);
1223 _highscore_file
= str_fmt("%shs.dat", config_dir
);
1224 extern char *_hotkeys_file
;
1225 _hotkeys_file
= str_fmt("%shotkeys.cfg", config_dir
);
1226 extern char *_windows_file
;
1227 _windows_file
= str_fmt("%swindows.cfg", config_dir
);
1229 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1230 if (config_dir
== config_home
) {
1231 /* We are using the XDG configuration home for the config file,
1232 * then store the rest in the XDG data home folder. */
1233 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1234 FioCreateDirectory(_personal_dir
);
1238 _personal_dir
= config_dir
;
1241 /* Make the necessary folders */
1242 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1243 FioCreateDirectory(config_dir
);
1244 if (config_dir
!= _personal_dir
) FioCreateDirectory(_personal_dir
);
1247 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
);
1249 static const Subdirectory default_subdirs
[] = {
1250 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1253 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1254 char *dir
= str_fmt("%s%s", _personal_dir
, _subdirs
[default_subdirs
[i
]]);
1255 FioCreateDirectory(dir
);
1259 /* If we have network we make a directory for the autodownloading of content */
1260 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = str_fmt("%s%s", _personal_dir
, "content_download" PATHSEP
);
1261 #ifdef ENABLE_NETWORK
1262 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1264 /* Create the directory for each of the types of content */
1265 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1266 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1267 char *tmp
= str_fmt("%s%s", _searchpaths
[SP_AUTODOWNLOAD_DIR
], _subdirs
[dirs
[i
]]);
1268 FioCreateDirectory(tmp
);
1272 extern char *_log_file
;
1273 _log_file
= str_fmt("%sopenttd.log", _personal_dir
);
1274 #else /* ENABLE_NETWORK */
1275 /* If we don't have networking, we don't need to make the directory. But
1276 * if it exists we keep it, otherwise remove it from the search paths. */
1277 if (!FileExists(_searchpaths
[SP_AUTODOWNLOAD_DIR
])) {
1278 free(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1279 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = NULL
;
1281 #endif /* ENABLE_NETWORK */
1285 * Sanitizes a filename, i.e. removes all illegal characters from it.
1286 * @param filename the "\0" terminated filename
1288 void SanitizeFilename(char *filename
)
1290 for (; *filename
!= '\0'; filename
++) {
1291 switch (*filename
) {
1292 /* The following characters are not allowed in filenames
1293 * on at least one of the supported operating systems: */
1294 case ':': case '\\': case '*': case '?': case '/':
1295 case '<': case '>': case '|': case '"':
1303 * Load a file into memory.
1304 * @param filename Name of the file to load.
1305 * @param[out] lenp Length of loaded data.
1306 * @param maxsize Maximum size to load.
1307 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1308 * @note If \a maxsize less than the length of the file, loading fails.
1310 void *ReadFileToMem(const char *filename
, size_t *lenp
, size_t maxsize
)
1312 FILE *in
= fopen(filename
, "rb");
1313 if (in
== NULL
) return NULL
;
1315 fseek(in
, 0, SEEK_END
);
1316 size_t len
= ftell(in
);
1317 fseek(in
, 0, SEEK_SET
);
1318 if (len
> maxsize
) {
1322 byte
*mem
= MallocT
<byte
>(len
+ 1);
1324 if (fread(mem
, len
, 1, in
) != 1) {
1336 * Helper to see whether a given filename matches the extension.
1337 * @param extension The extension to look for.
1338 * @param filename The filename to look in for the extension.
1339 * @return True iff the extension is NULL, or the filename ends with it.
1341 static bool MatchesExtension(const char *extension
, const char *filename
)
1343 if (extension
== NULL
) return true;
1345 const char *ext
= strrchr(filename
, extension
[0]);
1346 return ext
!= NULL
&& strcasecmp(ext
, extension
) == 0;
1350 * Scan a single directory (and recursively its children) and add
1351 * any graphics sets that are found.
1352 * @param fs the file scanner to add the files to
1353 * @param extension the extension of files to search for.
1354 * @param path full path we're currently at
1355 * @param basepath_length from where in the path are we 'based' on the search path
1356 * @param recursive whether to recursively search the sub directories
1358 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1360 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1364 struct dirent
*dirent
;
1367 if (path
== NULL
|| (dir
= ttd_opendir(path
)) == NULL
) return 0;
1369 while ((dirent
= readdir(dir
)) != NULL
) {
1370 const char *d_name
= FS2OTTD(dirent
->d_name
);
1371 char filename
[MAX_PATH
];
1373 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1375 seprintf(filename
, lastof(filename
), "%s%s", path
, d_name
);
1377 if (S_ISDIR(sb
.st_mode
)) {
1379 if (!recursive
) continue;
1380 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1381 if (!AppendPathSeparator(filename
, lastof(filename
))) continue;
1382 num
+= ScanPath(fs
, extension
, filename
, basepath_length
, recursive
);
1383 } else if (S_ISREG(sb
.st_mode
)) {
1385 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, basepath_length
, NULL
)) num
++;
1395 * Scan the given tar and add graphics sets when it finds one.
1396 * @param fs the file scanner to scan for
1397 * @param extension the extension of files to search for.
1398 * @param tar the tar to search in.
1400 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1403 const char *filename
= (*tar
).first
.c_str();
1405 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1411 * Scan for files with the given extension in the given search path.
1412 * @param extension the extension of files to search for.
1413 * @param sd the sub directory to search in.
1414 * @param tars whether to search in the tars too.
1415 * @param recursive whether to search recursively
1416 * @return the number of found files, i.e. the number of times that
1417 * AddFile returned true.
1419 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1424 char path
[MAX_PATH
];
1425 TarFileList::iterator tar
;
1428 FOR_ALL_SEARCHPATHS(sp
) {
1429 /* Don't search in the working directory */
1430 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1432 FioAppendDirectory(path
, lastof(path
), sp
, sd
);
1433 num
+= ScanPath(this, extension
, path
, strlen(path
), recursive
);
1436 if (tars
&& sd
!= NO_DIRECTORY
) {
1437 FOR_ALL_TARS(tar
, sd
) {
1438 num
+= ScanTar(this, extension
, tar
);
1444 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1447 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1457 * Scan for files with the given extension in the given search path.
1458 * @param extension the extension of files to search for.
1459 * @param directory the sub directory to search in.
1460 * @param recursive whether to search recursively
1461 * @return the number of found files, i.e. the number of times that
1462 * AddFile returned true.
1464 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1466 char path
[MAX_PATH
];
1467 strecpy(path
, directory
, lastof(path
));
1468 if (!AppendPathSeparator(path
, lastof(path
))) return 0;
1469 return ScanPath(this, extension
, path
, strlen(path
), recursive
);