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
)
329 /* There is always one platform that doesn't support basic commands... */
330 HANDLE hand
= CreateFile(OTTD2FS(filename
), 0, 0, NULL
, OPEN_EXISTING
, 0, NULL
);
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 NULL.
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 NULL
;
431 f
= fopen(buf
, mode
);
433 if (f
== NULL
&& strtolower(buf
+ ((subdir
== NO_DIRECTORY
) ? 0 : strlen(_searchpaths
[sp
]) - 1))) {
434 f
= fopen(buf
, mode
);
437 if (f
!= NULL
&& filesize
!= NULL
) {
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 NULL, size of the opened file.
450 * @return File handle of the opened file, or \c NULL 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
== NULL
) return f
;
458 if (fseek(f
, entry
->position
, SEEK_SET
) < 0) {
463 if (filesize
!= NULL
) *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 NULL 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
!= NULL
|| subdir
== NO_DIRECTORY
) break;
486 /* We can only use .tar in case of data-dir, and read-mode */
487 if (f
== NULL
&& 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
== NULL
&& subdir
!= NO_DIRECTORY
) {
523 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
524 if (f
!= NULL
) 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
), NULL
);
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 * Allocates and files a variable with the full path
589 * based on the given directory.
590 * @param dir the directory to base the path on
591 * @return the malloced full path
593 char *BuildWithFullPath(const char *dir
)
595 char *dest
= MallocT
<char>(MAX_PATH
);
596 char *last
= dest
+ MAX_PATH
- 1;
597 strecpy(dest
, dir
, last
);
599 /* Check if absolute or relative path */
600 const char *s
= strchr(dest
, PATHSEPCHAR
);
602 /* Add absolute path */
603 if (s
== NULL
|| dest
!= s
) {
604 if (getcwd(dest
, MAX_PATH
) == NULL
) *dest
= '\0';
605 AppendPathSeparator(dest
, last
);
606 strecat(dest
, dir
, last
);
608 AppendPathSeparator(dest
, last
);
614 * Find the first directory in a tar archive.
615 * @param tarname the name of the tar archive to look in.
616 * @param subdir the subdirectory to look in.
618 const char *FioTarFirstDir(const char *tarname
, Subdirectory subdir
)
620 TarList::iterator it
= _tar_list
[subdir
].find(tarname
);
621 if (it
== _tar_list
[subdir
].end()) return NULL
;
622 return (*it
).second
.dirname
;
625 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
627 std::string src
= srcParam
;
628 std::string dest
= destParam
;
629 /* Tar internals assume lowercase */
630 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
631 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
633 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
634 if (dest_file
!= _tar_filelist
[subdir
].end()) {
635 /* Link to file. Process the link like the destination file. */
636 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
638 /* Destination file not found. Assume 'link to directory'
639 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
640 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
641 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
642 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
646 void FioTarAddLink(const char *src
, const char *dest
, Subdirectory subdir
)
648 TarAddLink(src
, dest
, subdir
);
652 * Simplify filenames from tars.
653 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
654 * @param name Filename to process.
656 static void SimplifyFileName(char *name
)
658 /* Force lowercase */
661 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
662 #if (PATHSEPCHAR != '/')
663 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
668 * Perform the scanning of a particular subdirectory.
669 * @param subdir The subdirectory to scan.
670 * @return The number of found tar files.
672 uint
TarScanner::DoScan(Subdirectory sd
)
674 _tar_filelist
[sd
].clear();
675 _tar_list
[sd
].clear();
676 uint num
= this->Scan(".tar", sd
, false);
677 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
681 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
683 DEBUG(misc
, 1, "Scanning for tars");
686 if (mode
& TarScanner::BASESET
) {
687 num
+= fs
.DoScan(BASESET_DIR
);
689 if (mode
& TarScanner::NEWGRF
) {
690 num
+= fs
.DoScan(NEWGRF_DIR
);
692 if (mode
& TarScanner::AI
) {
693 num
+= fs
.DoScan(AI_DIR
);
694 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
696 if (mode
& TarScanner::GAME
) {
697 num
+= fs
.DoScan(GAME_DIR
);
698 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
700 if (mode
& TarScanner::SCENARIO
) {
701 num
+= fs
.DoScan(SCENARIO_DIR
);
702 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
704 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
709 * Add a single file to the scanned files of a tar, circumventing the scanning code.
710 * @param sd The sub directory the file is in.
711 * @param filename The name of the file to add.
712 * @return True if the additions went correctly.
714 bool TarScanner::AddFile(Subdirectory sd
, const char *filename
)
717 return this->AddFile(filename
, 0);
720 bool TarScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
722 /* No tar within tar. */
723 assert(tar_filename
== NULL
);
725 /* The TAR-header, repeated for every file */
727 char name
[100]; ///< Name of the file
731 char size
[12]; ///< Size of the file, in ASCII
742 char prefix
[155]; ///< Path of the file
747 /* Check if we already seen this file */
748 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
749 if (it
!= _tar_list
[this->subdir
].end()) return false;
751 FILE *f
= fopen(filename
, "rb");
752 /* Although the file has been found there can be
753 * a number of reasons we cannot open the file.
754 * Most common case is when we simply have not
755 * been given read access. */
756 if (f
== NULL
) return false;
758 const char *dupped_filename
= stredup(filename
);
759 _tar_list
[this->subdir
][filename
].filename
= dupped_filename
;
760 _tar_list
[this->subdir
][filename
].dirname
= NULL
;
762 TarLinkList links
; ///< Temporary list to collect links
765 char buf
[sizeof(th
.name
) + 1], *end
;
766 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
767 char link
[sizeof(th
.linkname
) + 1];
768 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
769 size_t num
= 0, pos
= 0;
771 /* Make a char of 512 empty bytes */
773 memset(&empty
[0], 0, sizeof(empty
));
775 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
776 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
777 if (num_bytes_read
!= 512) break;
778 pos
+= num_bytes_read
;
780 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
781 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
782 /* If we have only zeros in the block, it can be an end-of-file indicator */
783 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
785 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
);
792 /* The prefix contains the directory-name */
793 if (th
.prefix
[0] != '\0') {
794 strecpy(name
, th
.prefix
, lastof(name
));
795 strecat(name
, PATHSEP
, lastof(name
));
798 /* Copy the name of the file in a safe way at the end of 'name' */
799 strecat(name
, th
.name
, lastof(name
));
801 /* Calculate the size of the file.. for some strange reason this is stored as a string */
802 strecpy(buf
, th
.size
, lastof(buf
));
803 size_t skip
= strtoul(buf
, &end
, 8);
805 switch (th
.typeflag
) {
807 case '0': { // regular file
808 /* Ignore empty files */
809 if (skip
== 0) break;
811 if (strlen(name
) == 0) break;
813 /* Store this entry in the list */
814 TarFileListEntry entry
;
815 entry
.tar_filename
= dupped_filename
;
817 entry
.position
= pos
;
819 /* Convert to lowercase and our PATHSEPCHAR */
820 SimplifyFileName(name
);
822 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
823 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
828 case '1': // hard links
829 case '2': { // symbolic links
830 /* Copy the destination of the link in a safe way at the end of 'linkname' */
831 strecpy(link
, th
.linkname
, lastof(link
));
833 if (strlen(name
) == 0 || strlen(link
) == 0) break;
835 /* Convert to lowercase and our PATHSEPCHAR */
836 SimplifyFileName(name
);
837 SimplifyFileName(link
);
839 /* Only allow relative links */
840 if (link
[0] == PATHSEPCHAR
) {
841 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
845 /* Process relative path.
846 * Note: The destination of links must not contain any directory-links. */
847 strecpy(dest
, name
, lastof(dest
));
848 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
849 if (destpos
== NULL
) destpos
= dest
;
853 while (*pos
!= '\0') {
854 char *next
= strchr(pos
, PATHSEPCHAR
);
856 next
= pos
+ strlen(pos
);
858 /* Terminate the substring up to the path separator character. */
862 if (strcmp(pos
, ".") == 0) {
863 /* Skip '.' (current dir) */
864 } else if (strcmp(pos
, "..") == 0) {
866 if (dest
[0] == '\0') {
867 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
871 /* Truncate 'dest' after last PATHSEPCHAR.
872 * This assumes that the truncated part is a real directory and not a link. */
873 destpos
= strrchr(dest
, PATHSEPCHAR
);
874 if (destpos
== NULL
) destpos
= dest
;
877 /* Append at end of 'dest' */
878 if (destpos
!= dest
) destpos
= strecpy(destpos
, PATHSEP
, lastof(dest
));
879 destpos
= strecpy(destpos
, pos
, lastof(dest
));
882 if (destpos
>= lastof(dest
)) {
883 DEBUG(misc
, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename
);
891 /* Store links in temporary list */
892 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
893 links
.insert(TarLinkList::value_type(name
, dest
));
898 case '5': // directory
899 /* Convert to lowercase and our PATHSEPCHAR */
900 SimplifyFileName(name
);
902 /* Store the first directory name we detect */
903 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
904 if (_tar_list
[this->subdir
][filename
].dirname
== NULL
) _tar_list
[this->subdir
][filename
].dirname
= stredup(name
);
908 /* Ignore other types */
912 /* Skip to the next block.. */
913 skip
= Align(skip
, 512);
914 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
915 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
);
922 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
, num
);
925 /* Resolve file links and store directory links.
926 * We restrict usage of links to two cases:
927 * 1) Links to directories:
928 * Both the source path and the destination path must NOT contain any further links.
929 * When resolving files at most one directory link is resolved.
931 * The destination path must NOT contain any links.
932 * The source path may contain one directory link.
934 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
935 const std::string
&src
= link
->first
;
936 const std::string
&dest
= link
->second
;
937 TarAddLink(src
, dest
, this->subdir
);
944 * Extract the tar with the given filename in the directory
945 * where the tar resides.
946 * @param tar_filename the name of the tar to extract.
947 * @param subdir The sub directory the tar is in.
948 * @return false on failure.
950 bool ExtractTar(const char *tar_filename
, Subdirectory subdir
)
952 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
953 /* We don't know the file. */
954 if (it
== _tar_list
[subdir
].end()) return false;
956 const char *dirname
= (*it
).second
.dirname
;
958 /* The file doesn't have a sub directory! */
959 if (dirname
== NULL
) return false;
961 char filename
[MAX_PATH
];
962 strecpy(filename
, tar_filename
, lastof(filename
));
963 char *p
= strrchr(filename
, PATHSEPCHAR
);
964 /* The file's path does not have a separator? */
965 if (p
== NULL
) return false;
968 strecpy(p
, dirname
, lastof(filename
));
969 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
, filename
);
970 FioCreateDirectory(filename
);
972 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
973 if (strcmp((*it2
).second
.tar_filename
, tar_filename
) != 0) continue;
975 strecpy(p
, (*it2
).first
.c_str(), lastof(filename
));
977 DEBUG(misc
, 9, " extracting %s", filename
);
979 /* First open the file in the .tar. */
981 FILE *in
= FioFOpenFileTar(&(*it2
).second
, &to_copy
);
983 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, tar_filename
);
987 /* Now open the 'output' file. */
988 FILE *out
= fopen(filename
, "wb");
990 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, filename
);
995 /* Now read from the tar and write it into the file. */
998 for (; to_copy
!= 0; to_copy
-= read
) {
999 read
= fread(buffer
, 1, min(to_copy
, lengthof(buffer
)), in
);
1000 if (read
<= 0 || fwrite(buffer
, 1, read
, out
) != read
) break;
1003 /* Close everything up. */
1008 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
, (int)to_copy
);
1013 DEBUG(misc
, 9, " extraction successful");
1017 #if defined(WIN32) || defined(WINCE)
1019 * Determine the base (personal dir and game data dir) paths
1020 * @param exe the path from the current path to the executable
1021 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
1023 extern void DetermineBasePaths(const char *exe
);
1024 #else /* defined(WIN32) || defined(WINCE) */
1027 * Changes the working directory to the path of the give executable.
1028 * For OSX application bundles '.app' is the required extension of the bundle,
1029 * so when we crop the path to there, when can remove the name of the bundle
1030 * in the same way we remove the name from the executable name.
1031 * @param exe the path to the executable
1033 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
1036 strecpy(tmp
, exe
, lastof(tmp
));
1038 bool success
= false;
1040 char *app_bundle
= strchr(tmp
, '.');
1041 while (app_bundle
!= NULL
&& strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
1043 if (app_bundle
!= NULL
) *app_bundle
= '\0';
1044 #endif /* WITH_COCOA */
1045 char *s
= strrchr(tmp
, PATHSEPCHAR
);
1048 #if defined(__DJGPP__)
1049 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1050 if (s
> tmp
&& *(s
- 1) == ':') chdir("/");
1052 if (chdir(tmp
) != 0) {
1053 DEBUG(misc
, 0, "Directory with the binary does not exist?");
1062 * Whether we should scan the working directory.
1063 * It should not be scanned if it's the root or
1064 * the home directory as in both cases a big data
1065 * directory can cause huge amounts of unrelated
1066 * files scanned. Furthermore there are nearly no
1067 * use cases for the home/root directory to have
1068 * OpenTTD directories.
1069 * @return true if it should be scanned.
1071 bool DoScanWorkingDirectory()
1073 /* No working directory, so nothing to do. */
1074 if (_searchpaths
[SP_WORKING_DIR
] == NULL
) return false;
1076 /* Working directory is root, so do nothing. */
1077 if (strcmp(_searchpaths
[SP_WORKING_DIR
], PATHSEP
) == 0) return false;
1079 /* No personal/home directory, so the working directory won't be that. */
1080 if (_searchpaths
[SP_PERSONAL_DIR
] == NULL
) return true;
1083 seprintf(tmp
, lastof(tmp
), "%s%s", _searchpaths
[SP_WORKING_DIR
], PERSONAL_DIR
);
1084 AppendPathSeparator(tmp
, lastof(tmp
));
1085 return strcmp(tmp
, _searchpaths
[SP_PERSONAL_DIR
]) != 0;
1089 * Determine the base (personal dir and game data dir) paths
1090 * @param exe the path to the executable
1092 void DetermineBasePaths(const char *exe
)
1095 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1096 const char *xdg_data_home
= xdgDataHome(NULL
);
1097 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", xdg_data_home
,
1098 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1099 free(xdg_data_home
);
1101 AppendPathSeparator(tmp
, lastof(tmp
));
1102 _searchpaths
[SP_PERSONAL_DIR_XDG
] = stredup(tmp
);
1104 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1105 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1109 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
1110 const char *homedir
= stredup(path
.Path());
1112 /* getenv is highly unsafe; duplicate it as soon as possible,
1113 * or at least before something else touches the environment
1114 * variables in any way. It can also contain all kinds of
1115 * unvalidated data we rather not want internally. */
1116 const char *homedir
= getenv("HOME");
1117 if (homedir
!= NULL
) {
1118 homedir
= stredup(homedir
);
1121 if (homedir
== NULL
) {
1122 const struct passwd
*pw
= getpwuid(getuid());
1123 homedir
= (pw
== NULL
) ? NULL
: stredup(pw
->pw_dir
);
1127 if (homedir
!= NULL
) {
1128 ValidateString(homedir
);
1129 seprintf(tmp
, lastof(tmp
), "%s" PATHSEP
"%s", homedir
, PERSONAL_DIR
);
1130 AppendPathSeparator(tmp
, lastof(tmp
));
1132 _searchpaths
[SP_PERSONAL_DIR
] = stredup(tmp
);
1135 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1139 #if defined(WITH_SHARED_DIR)
1140 seprintf(tmp
, lastof(tmp
), "%s", SHARED_DIR
);
1141 AppendPathSeparator(tmp
, lastof(tmp
));
1142 _searchpaths
[SP_SHARED_DIR
] = stredup(tmp
);
1144 _searchpaths
[SP_SHARED_DIR
] = NULL
;
1147 #if defined(__MORPHOS__) || defined(__AMIGA__)
1148 _searchpaths
[SP_WORKING_DIR
] = NULL
;
1150 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1151 AppendPathSeparator(tmp
, lastof(tmp
));
1152 _searchpaths
[SP_WORKING_DIR
] = stredup(tmp
);
1155 _do_scan_working_directory
= DoScanWorkingDirectory();
1157 /* Change the working directory to that one of the executable */
1158 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1159 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1160 AppendPathSeparator(tmp
, lastof(tmp
));
1161 _searchpaths
[SP_BINARY_DIR
] = stredup(tmp
);
1163 _searchpaths
[SP_BINARY_DIR
] = NULL
;
1166 if (_searchpaths
[SP_WORKING_DIR
] != NULL
) {
1167 /* Go back to the current working directory. */
1168 if (chdir(_searchpaths
[SP_WORKING_DIR
]) != 0) {
1169 DEBUG(misc
, 0, "Failed to return to working directory!");
1173 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1174 _searchpaths
[SP_INSTALLATION_DIR
] = NULL
;
1176 seprintf(tmp
, lastof(tmp
), "%s", GLOBAL_DATA_DIR
);
1177 AppendPathSeparator(tmp
, lastof(tmp
));
1178 _searchpaths
[SP_INSTALLATION_DIR
] = stredup(tmp
);
1181 extern void cocoaSetApplicationBundleDir();
1182 cocoaSetApplicationBundleDir();
1184 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
] = NULL
;
1187 #endif /* defined(WIN32) || defined(WINCE) */
1189 const char *_personal_dir
;
1192 * Acquire the base paths (personal dir and game data dir),
1193 * fill all other paths (save dir, autosave dir etc) and
1194 * make the save and scenario directories.
1195 * @param exe the path from the current path to the executable
1197 void DeterminePaths(const char *exe
)
1199 DetermineBasePaths(exe
);
1201 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1202 char config_home
[MAX_PATH
];
1204 const char *xdg_config_home
= xdgConfigHome(NULL
);
1205 seprintf(config_home
, lastof(config_home
), "%s" PATHSEP
"%s", xdg_config_home
,
1206 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1207 free(xdg_config_home
);
1209 AppendPathSeparator(config_home
, lastof(config_home
));
1213 FOR_ALL_SEARCHPATHS(sp
) {
1214 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1215 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
]);
1219 if (_config_file
!= NULL
) {
1220 config_dir
= stredup(_config_file
);
1221 char *end
= strrchr(config_dir
, PATHSEPCHAR
);
1223 config_dir
[0] = '\0';
1228 char personal_dir
[MAX_PATH
];
1229 if (FioFindFullPath(personal_dir
, lastof(personal_dir
), BASE_DIR
, "openttd.cfg") != NULL
) {
1230 char *end
= strrchr(personal_dir
, PATHSEPCHAR
);
1231 if (end
!= NULL
) end
[1] = '\0';
1232 config_dir
= stredup(personal_dir
);
1233 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1235 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1236 /* No previous configuration file found. Use the configuration folder from XDG. */
1237 config_dir
= config_home
;
1239 static const Searchpath new_openttd_cfg_order
[] = {
1240 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1244 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1245 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1246 config_dir
= stredup(_searchpaths
[new_openttd_cfg_order
[i
]]);
1250 assert(config_dir
!= NULL
);
1252 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1256 DEBUG(misc
, 3, "%s found as config directory", config_dir
);
1258 _highscore_file
= str_fmt("%shs.dat", config_dir
);
1259 extern char *_hotkeys_file
;
1260 _hotkeys_file
= str_fmt("%shotkeys.cfg", config_dir
);
1261 extern char *_windows_file
;
1262 _windows_file
= str_fmt("%swindows.cfg", config_dir
);
1264 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1265 if (config_dir
== config_home
) {
1266 /* We are using the XDG configuration home for the config file,
1267 * then store the rest in the XDG data home folder. */
1268 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1269 FioCreateDirectory(_personal_dir
);
1273 _personal_dir
= config_dir
;
1276 /* Make the necessary folders */
1277 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1278 FioCreateDirectory(config_dir
);
1279 if (config_dir
!= _personal_dir
) FioCreateDirectory(_personal_dir
);
1282 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
);
1284 static const Subdirectory default_subdirs
[] = {
1285 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1288 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1289 char *dir
= str_fmt("%s%s", _personal_dir
, _subdirs
[default_subdirs
[i
]]);
1290 FioCreateDirectory(dir
);
1294 /* If we have network we make a directory for the autodownloading of content */
1295 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = str_fmt("%s%s", _personal_dir
, "content_download" PATHSEP
);
1296 #ifdef ENABLE_NETWORK
1297 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1299 /* Create the directory for each of the types of content */
1300 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1301 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1302 char *tmp
= str_fmt("%s%s", _searchpaths
[SP_AUTODOWNLOAD_DIR
], _subdirs
[dirs
[i
]]);
1303 FioCreateDirectory(tmp
);
1307 extern char *_log_file
;
1308 _log_file
= str_fmt("%sopenttd.log", _personal_dir
);
1309 #else /* ENABLE_NETWORK */
1310 /* If we don't have networking, we don't need to make the directory. But
1311 * if it exists we keep it, otherwise remove it from the search paths. */
1312 if (!FileExists(_searchpaths
[SP_AUTODOWNLOAD_DIR
])) {
1313 free(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1314 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = NULL
;
1316 #endif /* ENABLE_NETWORK */
1320 * Sanitizes a filename, i.e. removes all illegal characters from it.
1321 * @param filename the "\0" terminated filename
1323 void SanitizeFilename(char *filename
)
1325 for (; *filename
!= '\0'; filename
++) {
1326 switch (*filename
) {
1327 /* The following characters are not allowed in filenames
1328 * on at least one of the supported operating systems: */
1329 case ':': case '\\': case '*': case '?': case '/':
1330 case '<': case '>': case '|': case '"':
1338 * Load a file into memory.
1339 * @param filename Name of the file to load.
1340 * @param lenp [out] Length of loaded data.
1341 * @param maxsize Maximum size to load.
1342 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1343 * @note If \a maxsize less than the length of the file, loading fails.
1345 void *ReadFileToMem(const char *filename
, size_t *lenp
, size_t maxsize
)
1347 FILE *in
= fopen(filename
, "rb");
1348 if (in
== NULL
) return NULL
;
1350 fseek(in
, 0, SEEK_END
);
1351 size_t len
= ftell(in
);
1352 fseek(in
, 0, SEEK_SET
);
1353 if (len
> maxsize
) {
1357 byte
*mem
= MallocT
<byte
>(len
+ 1);
1359 if (fread(mem
, len
, 1, in
) != 1) {
1371 * Helper to see whether a given filename matches the extension.
1372 * @param extension The extension to look for.
1373 * @param filename The filename to look in for the extension.
1374 * @return True iff the extension is NULL, or the filename ends with it.
1376 static bool MatchesExtension(const char *extension
, const char *filename
)
1378 if (extension
== NULL
) return true;
1380 const char *ext
= strrchr(filename
, extension
[0]);
1381 return ext
!= NULL
&& strcasecmp(ext
, extension
) == 0;
1385 * Scan a single directory (and recursively its children) and add
1386 * any graphics sets that are found.
1387 * @param fs the file scanner to add the files to
1388 * @param extension the extension of files to search for.
1389 * @param path full path we're currently at
1390 * @param basepath_length from where in the path are we 'based' on the search path
1391 * @param recursive whether to recursively search the sub directories
1393 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1395 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1399 struct dirent
*dirent
;
1402 if (path
== NULL
|| (dir
= ttd_opendir(path
)) == NULL
) return 0;
1404 while ((dirent
= readdir(dir
)) != NULL
) {
1405 const char *d_name
= FS2OTTD(dirent
->d_name
);
1406 char filename
[MAX_PATH
];
1408 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1410 seprintf(filename
, lastof(filename
), "%s%s", path
, d_name
);
1412 if (S_ISDIR(sb
.st_mode
)) {
1414 if (!recursive
) continue;
1415 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1416 if (!AppendPathSeparator(filename
, lastof(filename
))) continue;
1417 num
+= ScanPath(fs
, extension
, filename
, basepath_length
, recursive
);
1418 } else if (S_ISREG(sb
.st_mode
)) {
1420 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, basepath_length
, NULL
)) num
++;
1430 * Scan the given tar and add graphics sets when it finds one.
1431 * @param fs the file scanner to scan for
1432 * @param extension the extension of files to search for.
1433 * @param tar the tar to search in.
1435 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1438 const char *filename
= (*tar
).first
.c_str();
1440 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1446 * Scan for files with the given extension in the given search path.
1447 * @param extension the extension of files to search for.
1448 * @param sd the sub directory to search in.
1449 * @param tars whether to search in the tars too.
1450 * @param recursive whether to search recursively
1451 * @return the number of found files, i.e. the number of times that
1452 * AddFile returned true.
1454 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1459 char path
[MAX_PATH
];
1460 TarFileList::iterator tar
;
1463 FOR_ALL_SEARCHPATHS(sp
) {
1464 /* Don't search in the working directory */
1465 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1467 FioAppendDirectory(path
, lastof(path
), sp
, sd
);
1468 num
+= ScanPath(this, extension
, path
, strlen(path
), recursive
);
1471 if (tars
&& sd
!= NO_DIRECTORY
) {
1472 FOR_ALL_TARS(tar
, sd
) {
1473 num
+= ScanTar(this, extension
, tar
);
1479 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1482 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1492 * Scan for files with the given extension in the given search path.
1493 * @param extension the extension of files to search for.
1494 * @param directory the sub directory to search in.
1495 * @param recursive whether to search recursively
1496 * @return the number of found files, i.e. the number of times that
1497 * AddFile returned true.
1499 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1501 char path
[MAX_PATH
];
1502 strecpy(path
, directory
, lastof(path
));
1503 if (!AppendPathSeparator(path
, lastof(path
))) return 0;
1504 return ScanPath(this, extension
, path
, strlen(path
), recursive
);