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 /** Size of the #Fio data buffer. */
36 #define FIO_BUFFER_SIZE 512
38 /** Structure for keeping several open files with just one data buffer. */
40 byte
*buffer
, *buffer_end
; ///< position pointer in local buffer and last valid byte of buffer
41 size_t pos
; ///< current (system) position in file
42 FILE *cur_fh
; ///< current file handle
43 const char *filename
; ///< current filename
44 FILE *handles
[MAX_FILE_SLOTS
]; ///< array of file handles we can have open
45 byte buffer_start
[FIO_BUFFER_SIZE
]; ///< local buffer when read from file
46 const char *filenames
[MAX_FILE_SLOTS
]; ///< array of filenames we (should) have open
47 char *shortnames
[MAX_FILE_SLOTS
]; ///< array of short names for spriteloader's use
48 #if defined(LIMITED_FDS)
49 uint open_handles
; ///< current amount of open handles
50 uint usage_count
[MAX_FILE_SLOTS
]; ///< count how many times this file has been opened
51 #endif /* LIMITED_FDS */
54 static Fio _fio
; ///< #Fio instance.
56 /** Whether the working directory should be scanned. */
57 static bool _do_scan_working_directory
= true;
59 extern char *_config_file
;
60 extern char *_highscore_file
;
63 * Get position in the current file.
64 * @return Position in the file.
68 return _fio
.pos
+ (_fio
.buffer
- _fio
.buffer_end
);
72 * Get the filename associated with a slot.
73 * @param slot Index of queried file.
74 * @return Name of the file.
76 const char *FioGetFilename(uint8 slot
)
78 return _fio
.shortnames
[slot
];
82 * Seek in the current file.
83 * @param pos New position.
84 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
86 void FioSeekTo(size_t pos
, int mode
)
88 if (mode
== SEEK_CUR
) pos
+= FioGetPos();
89 _fio
.buffer
= _fio
.buffer_end
= _fio
.buffer_start
+ FIO_BUFFER_SIZE
;
91 if (fseek(_fio
.cur_fh
, _fio
.pos
, SEEK_SET
) < 0) {
92 DEBUG(misc
, 0, "Seeking in %s failed", _fio
.filename
);
96 #if defined(LIMITED_FDS)
97 static void FioRestoreFile(int slot
)
99 /* Do we still have the file open, or should we reopen it? */
100 if (_fio
.handles
[slot
] == NULL
) {
101 DEBUG(misc
, 6, "Restoring file '%s' in slot '%d' from disk", _fio
.filenames
[slot
], slot
);
102 FioOpenFile(slot
, _fio
.filenames
[slot
]);
104 _fio
.usage_count
[slot
]++;
106 #endif /* LIMITED_FDS */
109 * Switch to a different file and seek to a position.
110 * @param slot Slot number of the new file.
111 * @param pos New absolute position in the new file.
113 void FioSeekToFile(uint8 slot
, size_t pos
)
116 #if defined(LIMITED_FDS)
117 /* Make sure we have this file open */
118 FioRestoreFile(slot
);
119 #endif /* LIMITED_FDS */
120 f
= _fio
.handles
[slot
];
123 _fio
.filename
= _fio
.filenames
[slot
];
124 FioSeekTo(pos
, SEEK_SET
);
128 * Read a byte from the file.
133 if (_fio
.buffer
== _fio
.buffer_end
) {
134 _fio
.buffer
= _fio
.buffer_start
;
135 size_t size
= fread(_fio
.buffer
, 1, FIO_BUFFER_SIZE
, _fio
.cur_fh
);
137 _fio
.buffer_end
= _fio
.buffer_start
+ size
;
139 if (size
== 0) return 0;
141 return *_fio
.buffer
++;
145 * Skip \a n bytes ahead in the file.
146 * @param n Number of bytes to skip reading.
148 void FioSkipBytes(int n
)
151 int m
= min(_fio
.buffer_end
- _fio
.buffer
, n
);
161 * Read a word (16 bits) from the file (in low endian format).
166 byte b
= FioReadByte();
167 return (FioReadByte() << 8) | b
;
171 * Read a double word (32 bits) from the file (in low endian format).
174 uint32
FioReadDword()
176 uint b
= FioReadWord();
177 return (FioReadWord() << 16) | b
;
182 * @param ptr Destination buffer.
183 * @param size Number of bytes to read.
185 void FioReadBlock(void *ptr
, size_t size
)
187 FioSeekTo(FioGetPos(), SEEK_SET
);
188 _fio
.pos
+= fread(ptr
, 1, size
, _fio
.cur_fh
);
192 * Close the file at the given slot number.
193 * @param slot File index to close.
195 static inline void FioCloseFile(int slot
)
197 if (_fio
.handles
[slot
] != NULL
) {
198 fclose(_fio
.handles
[slot
]);
200 free(_fio
.shortnames
[slot
]);
201 _fio
.shortnames
[slot
] = NULL
;
203 _fio
.handles
[slot
] = NULL
;
204 #if defined(LIMITED_FDS)
206 #endif /* LIMITED_FDS */
210 /** Close all slotted open files. */
213 for (int i
= 0; i
!= lengthof(_fio
.handles
); i
++) {
218 #if defined(LIMITED_FDS)
219 static void FioFreeHandle()
221 /* If we are about to open a file that will exceed the limit, close a file */
222 if (_fio
.open_handles
+ 1 == LIMITED_FDS
) {
228 /* Find the file that is used the least */
229 for (i
= 0; i
< lengthof(_fio
.handles
); i
++) {
230 if (_fio
.handles
[i
] != NULL
&& _fio
.usage_count
[i
] < count
) {
231 count
= _fio
.usage_count
[i
];
236 DEBUG(misc
, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio
.filenames
[slot
], slot
);
240 #endif /* LIMITED_FDS */
243 * Open a slotted file.
244 * @param slot Index to assign.
245 * @param filename Name of the file at the disk.
246 * @param subdir The sub directory to search this file in.
248 void FioOpenFile(int slot
, const char *filename
, Subdirectory subdir
)
252 #if defined(LIMITED_FDS)
254 #endif /* LIMITED_FDS */
255 f
= FioFOpenFile(filename
, "rb", subdir
);
256 if (f
== NULL
) usererror("Cannot open file '%s'", filename
);
258 if (pos
< 0) usererror("Cannot read file '%s'", filename
);
260 FioCloseFile(slot
); // if file was opened before, close it
261 _fio
.handles
[slot
] = f
;
262 _fio
.filenames
[slot
] = filename
;
264 /* Store the filename without path and extension */
265 const char *t
= strrchr(filename
, PATHSEPCHAR
);
266 _fio
.shortnames
[slot
] = strdup(t
== NULL
? filename
: t
);
267 char *t2
= strrchr(_fio
.shortnames
[slot
], '.');
268 if (t2
!= NULL
) *t2
= '\0';
269 strtolower(_fio
.shortnames
[slot
]);
271 #if defined(LIMITED_FDS)
272 _fio
.usage_count
[slot
] = 0;
274 #endif /* LIMITED_FDS */
275 FioSeekToFile(slot
, (uint32
)pos
);
278 static const char * const _subdirs
[] = {
281 "save" PATHSEP
"autosave" PATHSEP
,
283 "scenario" PATHSEP
"heightmap" PATHSEP
,
290 "ai" PATHSEP
"library" PATHSEP
,
292 "game" PATHSEP
"library" PATHSEP
,
293 "screenshot" PATHSEP
,
295 assert_compile(lengthof(_subdirs
) == NUM_SUBDIRS
);
297 const char *_searchpaths
[NUM_SEARCHPATHS
];
298 TarList _tar_list
[NUM_SUBDIRS
];
299 TarFileList _tar_filelist
[NUM_SUBDIRS
];
301 typedef std::map
<std::string
, std::string
> TarLinkList
;
302 static TarLinkList _tar_linklist
[NUM_SUBDIRS
]; ///< List of directory links
305 * Check whether the given file exists
306 * @param filename the file to try for existence.
307 * @param subdir the subdirectory to look in
308 * @return true if and only if the file can be opened
310 bool FioCheckFileExists(const char *filename
, Subdirectory subdir
)
312 FILE *f
= FioFOpenFile(filename
, "rb", subdir
);
313 if (f
== NULL
) return false;
320 * Test whether the given filename exists.
321 * @param filename the file to test.
322 * @return true if and only if the file exists.
324 bool FileExists(const char *filename
)
327 /* There is always one platform that doesn't support basic commands... */
328 HANDLE hand
= CreateFile(OTTD2FS(filename
), 0, 0, NULL
, OPEN_EXISTING
, 0, NULL
);
329 if (hand
== INVALID_HANDLE_VALUE
) return 1;
333 return access(OTTD2FS(filename
), 0) == 0;
338 * Close a file in a safe way.
340 void FioFCloseFile(FILE *f
)
345 char *FioGetFullPath(char *buf
, size_t buflen
, Searchpath sp
, Subdirectory subdir
, const char *filename
)
347 assert(subdir
< NUM_SUBDIRS
);
348 assert(sp
< NUM_SEARCHPATHS
);
350 snprintf(buf
, buflen
, "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
355 * Find a path to the filename in one of the search directories.
356 * @param buf [out] Destination buffer for the path.
357 * @param buflen Length of the destination buffer.
358 * @param subdir Subdirectory to try.
359 * @param filename Filename to look for.
360 * @return \a buf containing the path if the path was found, else \c NULL.
362 char *FioFindFullPath(char *buf
, size_t buflen
, Subdirectory subdir
, const char *filename
)
365 assert(subdir
< NUM_SUBDIRS
);
367 FOR_ALL_SEARCHPATHS(sp
) {
368 FioGetFullPath(buf
, buflen
, sp
, subdir
, filename
);
369 if (FileExists(buf
)) return buf
;
371 /* Be, as opening files, aware that sometimes the filename
372 * might be in uppercase when it is in lowercase on the
373 * disk. Of course Windows doesn't care about casing. */
374 if (strtolower(buf
+ strlen(_searchpaths
[sp
]) - 1) && FileExists(buf
)) return buf
;
381 char *FioAppendDirectory(char *buf
, size_t buflen
, Searchpath sp
, Subdirectory subdir
)
383 assert(subdir
< NUM_SUBDIRS
);
384 assert(sp
< NUM_SEARCHPATHS
);
386 snprintf(buf
, buflen
, "%s%s", _searchpaths
[sp
], _subdirs
[subdir
]);
390 char *FioGetDirectory(char *buf
, size_t buflen
, Subdirectory subdir
)
394 /* Find and return the first valid directory */
395 FOR_ALL_SEARCHPATHS(sp
) {
396 char *ret
= FioAppendDirectory(buf
, buflen
, sp
, subdir
);
397 if (FileExists(buf
)) return ret
;
400 /* Could not find the directory, fall back to a base path */
401 ttd_strlcpy(buf
, _personal_dir
, buflen
);
406 static FILE *FioFOpenFileSp(const char *filename
, const char *mode
, Searchpath sp
, Subdirectory subdir
, size_t *filesize
)
408 #if defined(WIN32) && defined(UNICODE)
409 /* fopen is implemented as a define with ellipses for
410 * Unicode support (prepend an L). As we are not sending
411 * a string, but a variable, it 'renames' the variable,
412 * so make that variable to makes it compile happily */
414 MultiByteToWideChar(CP_ACP
, 0, mode
, -1, Lmode
, lengthof(Lmode
));
419 if (subdir
== NO_DIRECTORY
) {
420 strecpy(buf
, filename
, lastof(buf
));
422 snprintf(buf
, lengthof(buf
), "%s%s%s", _searchpaths
[sp
], _subdirs
[subdir
], filename
);
426 if (mode
[0] == 'r' && GetFileAttributes(OTTD2FS(buf
)) == INVALID_FILE_ATTRIBUTES
) return NULL
;
429 f
= fopen(buf
, mode
);
431 if (f
== NULL
&& strtolower(buf
+ ((subdir
== NO_DIRECTORY
) ? 0 : strlen(_searchpaths
[sp
]) - 1))) {
432 f
= fopen(buf
, mode
);
435 if (f
!= NULL
&& filesize
!= NULL
) {
436 /* Find the size of the file */
437 fseek(f
, 0, SEEK_END
);
438 *filesize
= ftell(f
);
439 fseek(f
, 0, SEEK_SET
);
445 * Opens a file from inside a tar archive.
446 * @param entry The entry to open.
447 * @param filesize [out] If not \c NULL, size of the opened file.
448 * @return File handle of the opened file, or \c NULL if the file is not available.
449 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
451 FILE *FioFOpenFileTar(TarFileListEntry
*entry
, size_t *filesize
)
453 FILE *f
= fopen(entry
->tar_filename
, "rb");
454 if (f
== NULL
) return f
;
456 if (fseek(f
, entry
->position
, SEEK_SET
) < 0) {
461 if (filesize
!= NULL
) *filesize
= entry
->size
;
466 * Opens a OpenTTD file somewhere in a personal or global directory.
467 * @param filename Name of the file to open.
468 * @param subdir Subdirectory to open.
469 * @param filename Name of the file to open.
470 * @return File handle of the opened file, or \c NULL if the file is not available.
472 FILE *FioFOpenFile(const char *filename
, const char *mode
, Subdirectory subdir
, size_t *filesize
)
477 assert(subdir
< NUM_SUBDIRS
|| subdir
== NO_DIRECTORY
);
479 FOR_ALL_SEARCHPATHS(sp
) {
480 f
= FioFOpenFileSp(filename
, mode
, sp
, subdir
, filesize
);
481 if (f
!= NULL
|| subdir
== NO_DIRECTORY
) break;
484 /* We can only use .tar in case of data-dir, and read-mode */
485 if (f
== NULL
&& mode
[0] == 'r' && subdir
!= NO_DIRECTORY
) {
486 static const uint MAX_RESOLVED_LENGTH
= 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
487 char resolved_name
[MAX_RESOLVED_LENGTH
];
489 /* Filenames in tars are always forced to be lowercase */
490 strecpy(resolved_name
, filename
, lastof(resolved_name
));
491 strtolower(resolved_name
);
493 size_t resolved_len
= strlen(resolved_name
);
495 /* Resolve ONE directory link */
496 for (TarLinkList::iterator link
= _tar_linklist
[subdir
].begin(); link
!= _tar_linklist
[subdir
].end(); link
++) {
497 const std::string
&src
= link
->first
;
498 size_t len
= src
.length();
499 if (resolved_len
>= len
&& resolved_name
[len
- 1] == PATHSEPCHAR
&& strncmp(src
.c_str(), resolved_name
, len
) == 0) {
501 char resolved_name2
[MAX_RESOLVED_LENGTH
];
502 const std::string
&dest
= link
->second
;
503 strecpy(resolved_name2
, &(resolved_name
[len
]), lastof(resolved_name2
));
504 strecpy(resolved_name
, dest
.c_str(), lastof(resolved_name
));
505 strecpy(&(resolved_name
[dest
.length()]), resolved_name2
, lastof(resolved_name
));
506 break; // Only resolve one level
510 TarFileList::iterator it
= _tar_filelist
[subdir
].find(resolved_name
);
511 if (it
!= _tar_filelist
[subdir
].end()) {
512 f
= FioFOpenFileTar(&((*it
).second
), filesize
);
516 /* Sometimes a full path is given. To support
517 * the 'subdirectory' must be 'removed'. */
518 if (f
== NULL
&& subdir
!= NO_DIRECTORY
) {
521 f
= FioFOpenFile(filename
, mode
, OLD_GM_DIR
, filesize
);
522 if (f
!= NULL
) break;
525 f
= FioFOpenFile(filename
, mode
, OLD_DATA_DIR
, filesize
);
529 f
= FioFOpenFile(filename
, mode
, NO_DIRECTORY
, filesize
);
538 * Create a directory with the given name
539 * @param name the new name of the directory
541 static void FioCreateDirectory(const char *name
)
543 /* Ignore directory creation errors; they'll surface later on, and most
544 * of the time they are 'directory already exists' errors anyhow. */
545 #if defined(WIN32) || defined(WINCE)
546 CreateDirectory(OTTD2FS(name
), NULL
);
547 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
548 mkdir(OTTD2FS(name
));
549 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
551 ttd_strlcpy(buf
, name
, MAX_PATH
);
553 size_t len
= strlen(name
) - 1;
554 if (buf
[len
] == '/') {
555 buf
[len
] = '\0'; // Kill pathsep, so mkdir() will not fail
558 mkdir(OTTD2FS(buf
), 0755);
560 mkdir(OTTD2FS(name
), 0755);
565 * Appends, if necessary, the path separator character to the end of the string.
566 * It does not add the path separator to zero-sized strings.
567 * @param buf string to append the separator to
568 * @param buflen the length of \a buf.
569 * @return true iff the operation succeeded
571 bool AppendPathSeparator(char *buf
, size_t buflen
)
573 size_t s
= strlen(buf
);
575 /* Length of string + path separator + '\0' */
576 if (s
!= 0 && buf
[s
- 1] != PATHSEPCHAR
) {
577 if (s
+ 2 >= buflen
) return false;
579 buf
[s
] = PATHSEPCHAR
;
587 * Allocates and files a variable with the full path
588 * based on the given directory.
589 * @param dir the directory to base the path on
590 * @return the malloced full path
592 char *BuildWithFullPath(const char *dir
)
594 char *dest
= MallocT
<char>(MAX_PATH
);
595 ttd_strlcpy(dest
, dir
, MAX_PATH
);
597 /* Check if absolute or relative path */
598 const char *s
= strchr(dest
, PATHSEPCHAR
);
600 /* Add absolute path */
601 if (s
== NULL
|| dest
!= s
) {
602 if (getcwd(dest
, MAX_PATH
) == NULL
) *dest
= '\0';
603 AppendPathSeparator(dest
, MAX_PATH
);
604 ttd_strlcat(dest
, dir
, MAX_PATH
);
606 AppendPathSeparator(dest
, MAX_PATH
);
612 * Find the first directory in a tar archive.
613 * @param tarname the name of the tar archive to look in.
614 * @param subdir the subdirectory to look in.
616 const char *FioTarFirstDir(const char *tarname
, Subdirectory subdir
)
618 TarList::iterator it
= _tar_list
[subdir
].find(tarname
);
619 if (it
== _tar_list
[subdir
].end()) return NULL
;
620 return (*it
).second
.dirname
;
623 static void TarAddLink(const std::string
&srcParam
, const std::string
&destParam
, Subdirectory subdir
)
625 std::string src
= srcParam
;
626 std::string dest
= destParam
;
627 /* Tar internals assume lowercase */
628 std::transform(src
.begin(), src
.end(), src
.begin(), tolower
);
629 std::transform(dest
.begin(), dest
.end(), dest
.begin(), tolower
);
631 TarFileList::iterator dest_file
= _tar_filelist
[subdir
].find(dest
);
632 if (dest_file
!= _tar_filelist
[subdir
].end()) {
633 /* Link to file. Process the link like the destination file. */
634 _tar_filelist
[subdir
].insert(TarFileList::value_type(src
, dest_file
->second
));
636 /* Destination file not found. Assume 'link to directory'
637 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
638 const std::string src_path
= ((*src
.rbegin() == PATHSEPCHAR
) ? src
: src
+ PATHSEPCHAR
);
639 const std::string dst_path
= (dest
.length() == 0 ? "" : ((*dest
.rbegin() == PATHSEPCHAR
) ? dest
: dest
+ PATHSEPCHAR
));
640 _tar_linklist
[subdir
].insert(TarLinkList::value_type(src_path
, dst_path
));
644 void FioTarAddLink(const char *src
, const char *dest
, Subdirectory subdir
)
646 TarAddLink(src
, dest
, subdir
);
650 * Simplify filenames from tars.
651 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
652 * @param name Filename to process.
654 static void SimplifyFileName(char *name
)
656 /* Force lowercase */
659 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
660 #if (PATHSEPCHAR != '/')
661 for (char *n
= name
; *n
!= '\0'; n
++) if (*n
== '/') *n
= PATHSEPCHAR
;
666 * Perform the scanning of a particular subdirectory.
667 * @param subdir The subdirectory to scan.
668 * @return The number of found tar files.
670 uint
TarScanner::DoScan(Subdirectory sd
)
672 _tar_filelist
[sd
].clear();
673 _tar_list
[sd
].clear();
674 uint num
= this->Scan(".tar", sd
, false);
675 if (sd
== BASESET_DIR
|| sd
== NEWGRF_DIR
) num
+= this->Scan(".tar", OLD_DATA_DIR
, false);
679 /* static */ uint
TarScanner::DoScan(TarScanner::Mode mode
)
681 DEBUG(misc
, 1, "Scanning for tars");
684 if (mode
& TarScanner::BASESET
) {
685 num
+= fs
.DoScan(BASESET_DIR
);
687 if (mode
& TarScanner::NEWGRF
) {
688 num
+= fs
.DoScan(NEWGRF_DIR
);
690 if (mode
& TarScanner::AI
) {
691 num
+= fs
.DoScan(AI_DIR
);
692 num
+= fs
.DoScan(AI_LIBRARY_DIR
);
694 if (mode
& TarScanner::GAME
) {
695 num
+= fs
.DoScan(GAME_DIR
);
696 num
+= fs
.DoScan(GAME_LIBRARY_DIR
);
698 if (mode
& TarScanner::SCENARIO
) {
699 num
+= fs
.DoScan(SCENARIO_DIR
);
700 num
+= fs
.DoScan(HEIGHTMAP_DIR
);
702 DEBUG(misc
, 1, "Scan complete, found %d files", num
);
707 * Add a single file to the scanned files of a tar, circumventing the scanning code.
708 * @param sd The sub directory the file is in.
709 * @param filename The name of the file to add.
710 * @return True if the additions went correctly.
712 bool TarScanner::AddFile(Subdirectory sd
, const char *filename
)
715 return this->AddFile(filename
, 0);
718 bool TarScanner::AddFile(const char *filename
, size_t basepath_length
, const char *tar_filename
)
720 /* No tar within tar. */
721 assert(tar_filename
== NULL
);
723 /* The TAR-header, repeated for every file */
725 char name
[100]; ///< Name of the file
729 char size
[12]; ///< Size of the file, in ASCII
740 char prefix
[155]; ///< Path of the file
745 /* Check if we already seen this file */
746 TarList::iterator it
= _tar_list
[this->subdir
].find(filename
);
747 if (it
!= _tar_list
[this->subdir
].end()) return false;
749 FILE *f
= fopen(filename
, "rb");
750 /* Although the file has been found there can be
751 * a number of reasons we cannot open the file.
752 * Most common case is when we simply have not
753 * been given read access. */
754 if (f
== NULL
) return false;
756 const char *dupped_filename
= strdup(filename
);
757 _tar_list
[this->subdir
][filename
].filename
= dupped_filename
;
758 _tar_list
[this->subdir
][filename
].dirname
= NULL
;
760 TarLinkList links
; ///< Temporary list to collect links
763 char buf
[sizeof(th
.name
) + 1], *end
;
764 char name
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1];
765 char link
[sizeof(th
.linkname
) + 1];
766 char dest
[sizeof(th
.prefix
) + 1 + sizeof(th
.name
) + 1 + 1 + sizeof(th
.linkname
) + 1];
767 size_t num
= 0, pos
= 0;
769 /* Make a char of 512 empty bytes */
771 memset(&empty
[0], 0, sizeof(empty
));
773 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
774 size_t num_bytes_read
= fread(&th
, 1, 512, f
);
775 if (num_bytes_read
!= 512) break;
776 pos
+= num_bytes_read
;
778 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
779 if (strncmp(th
.magic
, "ustar", 5) != 0 && memcmp(&th
.magic
, &empty
[0], 512 - offsetof(TarHeader
, magic
)) != 0) {
780 /* If we have only zeros in the block, it can be an end-of-file indicator */
781 if (memcmp(&th
, &empty
[0], 512) == 0) continue;
783 DEBUG(misc
, 0, "The file '%s' isn't a valid tar-file", filename
);
790 /* The prefix contains the directory-name */
791 if (th
.prefix
[0] != '\0') {
792 ttd_strlcpy(name
, th
.prefix
, lengthof(name
));
793 ttd_strlcat(name
, PATHSEP
, lengthof(name
));
796 /* Copy the name of the file in a safe way at the end of 'name' */
797 ttd_strlcat(name
, th
.name
, lengthof(name
));
799 /* Calculate the size of the file.. for some strange reason this is stored as a string */
800 ttd_strlcpy(buf
, th
.size
, lengthof(buf
));
801 size_t skip
= strtoul(buf
, &end
, 8);
803 switch (th
.typeflag
) {
805 case '0': { // regular file
806 /* Ignore empty files */
807 if (skip
== 0) break;
809 if (strlen(name
) == 0) break;
811 /* Store this entry in the list */
812 TarFileListEntry entry
;
813 entry
.tar_filename
= dupped_filename
;
815 entry
.position
= pos
;
817 /* Convert to lowercase and our PATHSEPCHAR */
818 SimplifyFileName(name
);
820 DEBUG(misc
, 6, "Found file in tar: %s (" PRINTF_SIZE
" bytes, " PRINTF_SIZE
" offset)", name
, skip
, pos
);
821 if (_tar_filelist
[this->subdir
].insert(TarFileList::value_type(name
, entry
)).second
) num
++;
826 case '1': // hard links
827 case '2': { // symbolic links
828 /* Copy the destination of the link in a safe way at the end of 'linkname' */
829 ttd_strlcpy(link
, th
.linkname
, lengthof(link
));
831 if (strlen(name
) == 0 || strlen(link
) == 0) break;
833 /* Convert to lowercase and our PATHSEPCHAR */
834 SimplifyFileName(name
);
835 SimplifyFileName(link
);
837 /* Only allow relative links */
838 if (link
[0] == PATHSEPCHAR
) {
839 DEBUG(misc
, 1, "Ignoring absolute link in tar: %s -> %s", name
, link
);
843 /* Process relative path.
844 * Note: The destination of links must not contain any directory-links. */
845 ttd_strlcpy(dest
, name
, lengthof(dest
));
846 char *destpos
= strrchr(dest
, PATHSEPCHAR
);
847 if (destpos
== NULL
) destpos
= dest
;
851 while (*pos
!= '\0') {
852 char *next
= strchr(link
, PATHSEPCHAR
);
853 if (next
== NULL
) next
= pos
+ strlen(pos
);
855 /* Skip '.' (current dir) */
856 if (next
!= pos
+ 1 || pos
[0] != '.') {
857 if (next
== pos
+ 2 && pos
[0] == '.' && pos
[1] == '.') {
859 if (dest
[0] == '\0') {
860 DEBUG(misc
, 1, "Ignoring link pointing outside of data directory: %s -> %s", name
, link
);
864 /* Truncate 'dest' after last PATHSEPCHAR.
865 * This assumes that the truncated part is a real directory and not a link. */
866 destpos
= strrchr(dest
, PATHSEPCHAR
);
867 if (destpos
== NULL
) destpos
= dest
;
869 /* Append at end of 'dest' */
870 if (destpos
!= dest
) *(destpos
++) = PATHSEPCHAR
;
871 strncpy(destpos
, pos
, next
- pos
); // Safe as we do '\0'-termination ourselves
872 destpos
+= next
- pos
;
880 /* Store links in temporary list */
881 DEBUG(misc
, 6, "Found link in tar: %s -> %s", name
, dest
);
882 links
.insert(TarLinkList::value_type(name
, dest
));
887 case '5': // directory
888 /* Convert to lowercase and our PATHSEPCHAR */
889 SimplifyFileName(name
);
891 /* Store the first directory name we detect */
892 DEBUG(misc
, 6, "Found dir in tar: %s", name
);
893 if (_tar_list
[this->subdir
][filename
].dirname
== NULL
) _tar_list
[this->subdir
][filename
].dirname
= strdup(name
);
897 /* Ignore other types */
901 /* Skip to the next block.. */
902 skip
= Align(skip
, 512);
903 if (fseek(f
, skip
, SEEK_CUR
) < 0) {
904 DEBUG(misc
, 0, "The file '%s' can't be read as a valid tar-file", filename
);
911 DEBUG(misc
, 1, "Found tar '%s' with " PRINTF_SIZE
" new files", filename
, num
);
914 /* Resolve file links and store directory links.
915 * We restrict usage of links to two cases:
916 * 1) Links to directories:
917 * Both the source path and the destination path must NOT contain any further links.
918 * When resolving files at most one directory link is resolved.
920 * The destination path must NOT contain any links.
921 * The source path may contain one directory link.
923 for (TarLinkList::iterator link
= links
.begin(); link
!= links
.end(); link
++) {
924 const std::string
&src
= link
->first
;
925 const std::string
&dest
= link
->second
;
926 TarAddLink(src
, dest
, this->subdir
);
933 * Extract the tar with the given filename in the directory
934 * where the tar resides.
935 * @param tar_filename the name of the tar to extract.
936 * @param subdir The sub directory the tar is in.
937 * @return false on failure.
939 bool ExtractTar(const char *tar_filename
, Subdirectory subdir
)
941 TarList::iterator it
= _tar_list
[subdir
].find(tar_filename
);
942 /* We don't know the file. */
943 if (it
== _tar_list
[subdir
].end()) return false;
945 const char *dirname
= (*it
).second
.dirname
;
947 /* The file doesn't have a sub directory! */
948 if (dirname
== NULL
) return false;
950 char filename
[MAX_PATH
];
951 strecpy(filename
, tar_filename
, lastof(filename
));
952 char *p
= strrchr(filename
, PATHSEPCHAR
);
953 /* The file's path does not have a separator? */
954 if (p
== NULL
) return false;
957 strecpy(p
, dirname
, lastof(filename
));
958 DEBUG(misc
, 8, "Extracting %s to directory %s", tar_filename
, filename
);
959 FioCreateDirectory(filename
);
961 for (TarFileList::iterator it2
= _tar_filelist
[subdir
].begin(); it2
!= _tar_filelist
[subdir
].end(); it2
++) {
962 if (strcmp((*it2
).second
.tar_filename
, tar_filename
) != 0) continue;
964 strecpy(p
, (*it2
).first
.c_str(), lastof(filename
));
966 DEBUG(misc
, 9, " extracting %s", filename
);
968 /* First open the file in the .tar. */
970 FILE *in
= FioFOpenFileTar(&(*it2
).second
, &to_copy
);
972 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, tar_filename
);
976 /* Now open the 'output' file. */
977 FILE *out
= fopen(filename
, "wb");
979 DEBUG(misc
, 6, "Extracting %s failed; could not open %s", filename
, filename
);
984 /* Now read from the tar and write it into the file. */
987 for (; to_copy
!= 0; to_copy
-= read
) {
988 read
= fread(buffer
, 1, min(to_copy
, lengthof(buffer
)), in
);
989 if (read
<= 0 || fwrite(buffer
, 1, read
, out
) != read
) break;
992 /* Close everything up. */
997 DEBUG(misc
, 6, "Extracting %s failed; still %i bytes to copy", filename
, (int)to_copy
);
1002 DEBUG(misc
, 9, " extraction successful");
1006 #if defined(WIN32) || defined(WINCE)
1008 * Determine the base (personal dir and game data dir) paths
1009 * @param exe the path from the current path to the executable
1010 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
1012 extern void DetermineBasePaths(const char *exe
);
1013 #else /* defined(WIN32) || defined(WINCE) */
1016 * Changes the working directory to the path of the give executable.
1017 * For OSX application bundles '.app' is the required extension of the bundle,
1018 * so when we crop the path to there, when can remove the name of the bundle
1019 * in the same way we remove the name from the executable name.
1020 * @param exe the path to the executable
1022 static bool ChangeWorkingDirectoryToExecutable(const char *exe
)
1024 bool success
= false;
1026 char *app_bundle
= strchr(exe
, '.');
1027 while (app_bundle
!= NULL
&& strncasecmp(app_bundle
, ".app", 4) != 0) app_bundle
= strchr(&app_bundle
[1], '.');
1029 if (app_bundle
!= NULL
) app_bundle
[0] = '\0';
1030 #endif /* WITH_COCOA */
1031 char *s
= const_cast<char *>(strrchr(exe
, PATHSEPCHAR
));
1034 #if defined(__DJGPP__)
1035 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1036 if (s
[-1] == ':') chdir("/");
1038 if (chdir(exe
) != 0) {
1039 DEBUG(misc
, 0, "Directory with the binary does not exist?");
1046 if (app_bundle
!= NULL
) app_bundle
[0] = '.';
1047 #endif /* WITH_COCOA */
1052 * Whether we should scan the working directory.
1053 * It should not be scanned if it's the root or
1054 * the home directory as in both cases a big data
1055 * directory can cause huge amounts of unrelated
1056 * files scanned. Furthermore there are nearly no
1057 * use cases for the home/root directory to have
1058 * OpenTTD directories.
1059 * @return true if it should be scanned.
1061 bool DoScanWorkingDirectory()
1063 /* No working directory, so nothing to do. */
1064 if (_searchpaths
[SP_WORKING_DIR
] == NULL
) return false;
1066 /* Working directory is root, so do nothing. */
1067 if (strcmp(_searchpaths
[SP_WORKING_DIR
], PATHSEP
) == 0) return false;
1069 /* No personal/home directory, so the working directory won't be that. */
1070 if (_searchpaths
[SP_PERSONAL_DIR
] == NULL
) return true;
1073 snprintf(tmp
, lengthof(tmp
), "%s%s", _searchpaths
[SP_WORKING_DIR
], PERSONAL_DIR
);
1074 AppendPathSeparator(tmp
, MAX_PATH
);
1075 return strcmp(tmp
, _searchpaths
[SP_PERSONAL_DIR
]) != 0;
1079 * Determine the base (personal dir and game data dir) paths
1080 * @param exe the path to the executable
1082 void DetermineBasePaths(const char *exe
)
1085 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1086 const char *xdg_data_home
= xdgDataHome(NULL
);
1087 snprintf(tmp
, MAX_PATH
, "%s" PATHSEP
"%s", xdg_data_home
,
1088 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1089 free(xdg_data_home
);
1091 AppendPathSeparator(tmp
, MAX_PATH
);
1092 _searchpaths
[SP_PERSONAL_DIR_XDG
] = strdup(tmp
);
1094 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1095 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1099 find_directory(B_USER_SETTINGS_DIRECTORY
, &path
);
1100 const char *homedir
= strdup(path
.Path());
1102 /* getenv is highly unsafe; duplicate it as soon as possible,
1103 * or at least before something else touches the environment
1104 * variables in any way. It can also contain all kinds of
1105 * unvalidated data we rather not want internally. */
1106 const char *homedir
= getenv("HOME");
1107 if (homedir
!= NULL
) {
1108 homedir
= strndup(homedir
, MAX_PATH
);
1111 if (homedir
== NULL
) {
1112 const struct passwd
*pw
= getpwuid(getuid());
1113 homedir
= (pw
== NULL
) ? NULL
: strdup(pw
->pw_dir
);
1117 if (homedir
!= NULL
) {
1118 ValidateString(homedir
);
1119 snprintf(tmp
, MAX_PATH
, "%s" PATHSEP
"%s", homedir
, PERSONAL_DIR
);
1120 AppendPathSeparator(tmp
, MAX_PATH
);
1122 _searchpaths
[SP_PERSONAL_DIR
] = strdup(tmp
);
1125 _searchpaths
[SP_PERSONAL_DIR
] = NULL
;
1129 #if defined(WITH_SHARED_DIR)
1130 snprintf(tmp
, MAX_PATH
, "%s", SHARED_DIR
);
1131 AppendPathSeparator(tmp
, MAX_PATH
);
1132 _searchpaths
[SP_SHARED_DIR
] = strdup(tmp
);
1134 _searchpaths
[SP_SHARED_DIR
] = NULL
;
1137 #if defined(__MORPHOS__) || defined(__AMIGA__)
1138 _searchpaths
[SP_WORKING_DIR
] = NULL
;
1140 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1141 AppendPathSeparator(tmp
, MAX_PATH
);
1142 _searchpaths
[SP_WORKING_DIR
] = strdup(tmp
);
1145 _do_scan_working_directory
= DoScanWorkingDirectory();
1147 /* Change the working directory to that one of the executable */
1148 if (ChangeWorkingDirectoryToExecutable(exe
)) {
1149 if (getcwd(tmp
, MAX_PATH
) == NULL
) *tmp
= '\0';
1150 AppendPathSeparator(tmp
, MAX_PATH
);
1151 _searchpaths
[SP_BINARY_DIR
] = strdup(tmp
);
1153 _searchpaths
[SP_BINARY_DIR
] = NULL
;
1156 if (_searchpaths
[SP_WORKING_DIR
] != NULL
) {
1157 /* Go back to the current working directory. */
1158 if (chdir(_searchpaths
[SP_WORKING_DIR
]) != 0) {
1159 DEBUG(misc
, 0, "Failed to return to working directory!");
1163 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1164 _searchpaths
[SP_INSTALLATION_DIR
] = NULL
;
1166 snprintf(tmp
, MAX_PATH
, "%s", GLOBAL_DATA_DIR
);
1167 AppendPathSeparator(tmp
, MAX_PATH
);
1168 _searchpaths
[SP_INSTALLATION_DIR
] = strdup(tmp
);
1171 extern void cocoaSetApplicationBundleDir();
1172 cocoaSetApplicationBundleDir();
1174 _searchpaths
[SP_APPLICATION_BUNDLE_DIR
] = NULL
;
1177 #endif /* defined(WIN32) || defined(WINCE) */
1179 const char *_personal_dir
;
1182 * Acquire the base paths (personal dir and game data dir),
1183 * fill all other paths (save dir, autosave dir etc) and
1184 * make the save and scenario directories.
1185 * @param exe the path from the current path to the executable
1187 void DeterminePaths(const char *exe
)
1189 DetermineBasePaths(exe
);
1191 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1192 char config_home
[MAX_PATH
];
1194 const char *xdg_config_home
= xdgConfigHome(NULL
);
1195 snprintf(config_home
, MAX_PATH
, "%s" PATHSEP
"%s", xdg_config_home
,
1196 PERSONAL_DIR
[0] == '.' ? &PERSONAL_DIR
[1] : PERSONAL_DIR
);
1197 free(xdg_config_home
);
1199 AppendPathSeparator(config_home
, MAX_PATH
);
1203 FOR_ALL_SEARCHPATHS(sp
) {
1204 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1205 DEBUG(misc
, 4, "%s added as search path", _searchpaths
[sp
]);
1208 const char *config_dir
;
1209 if (_config_file
!= NULL
) {
1210 char *end
= strrchr(_config_file
, PATHSEPCHAR
);
1214 size_t n
= (end
- _config_file
) + 1;
1215 char *dir
= (char*)malloc(n
+ 1);
1216 memcpy(dir
, _config_file
, n
);
1221 char personal_dir
[MAX_PATH
];
1222 if (FioFindFullPath(personal_dir
, lengthof(personal_dir
), BASE_DIR
, "openttd.cfg") != NULL
) {
1223 char *end
= strrchr(personal_dir
, PATHSEPCHAR
);
1224 if (end
!= NULL
) end
[1] = '\0';
1225 config_dir
= strdup(personal_dir
);
1226 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1228 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1229 /* No previous configuration file found. Use the configuration folder from XDG. */
1230 config_dir
= config_home
;
1232 static const Searchpath new_openttd_cfg_order
[] = {
1233 SP_PERSONAL_DIR
, SP_BINARY_DIR
, SP_WORKING_DIR
, SP_SHARED_DIR
, SP_INSTALLATION_DIR
1237 for (uint i
= 0; i
< lengthof(new_openttd_cfg_order
); i
++) {
1238 if (IsValidSearchPath(new_openttd_cfg_order
[i
])) {
1239 config_dir
= strdup(_searchpaths
[new_openttd_cfg_order
[i
]]);
1243 assert(config_dir
!= NULL
);
1245 _config_file
= str_fmt("%sopenttd.cfg", config_dir
);
1249 DEBUG(misc
, 3, "%s found as config directory", config_dir
);
1251 _highscore_file
= str_fmt("%shs.dat", config_dir
);
1252 extern char *_hotkeys_file
;
1253 _hotkeys_file
= str_fmt("%shotkeys.cfg", config_dir
);
1254 extern char *_windows_file
;
1255 _windows_file
= str_fmt("%swindows.cfg", config_dir
);
1257 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1258 if (config_dir
== config_home
) {
1259 /* We are using the XDG configuration home for the config file,
1260 * then store the rest in the XDG data home folder. */
1261 _personal_dir
= _searchpaths
[SP_PERSONAL_DIR_XDG
];
1262 FioCreateDirectory(_personal_dir
);
1266 _personal_dir
= config_dir
;
1269 /* Make the necessary folders */
1270 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1271 FioCreateDirectory(config_dir
);
1272 if (config_dir
!= _personal_dir
) FioCreateDirectory(_personal_dir
);
1275 DEBUG(misc
, 3, "%s found as personal directory", _personal_dir
);
1277 static const Subdirectory default_subdirs
[] = {
1278 SAVE_DIR
, AUTOSAVE_DIR
, SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
, SCREENSHOT_DIR
1281 for (uint i
= 0; i
< lengthof(default_subdirs
); i
++) {
1282 char *dir
= str_fmt("%s%s", _personal_dir
, _subdirs
[default_subdirs
[i
]]);
1283 FioCreateDirectory(dir
);
1287 /* If we have network we make a directory for the autodownloading of content */
1288 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = str_fmt("%s%s", _personal_dir
, "content_download" PATHSEP
);
1289 #ifdef ENABLE_NETWORK
1290 FioCreateDirectory(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1292 /* Create the directory for each of the types of content */
1293 const Subdirectory dirs
[] = { SCENARIO_DIR
, HEIGHTMAP_DIR
, BASESET_DIR
, NEWGRF_DIR
, AI_DIR
, AI_LIBRARY_DIR
, GAME_DIR
, GAME_LIBRARY_DIR
};
1294 for (uint i
= 0; i
< lengthof(dirs
); i
++) {
1295 char *tmp
= str_fmt("%s%s", _searchpaths
[SP_AUTODOWNLOAD_DIR
], _subdirs
[dirs
[i
]]);
1296 FioCreateDirectory(tmp
);
1300 extern char *_log_file
;
1301 _log_file
= str_fmt("%sopenttd.log", _personal_dir
);
1302 #else /* ENABLE_NETWORK */
1303 /* If we don't have networking, we don't need to make the directory. But
1304 * if it exists we keep it, otherwise remove it from the search paths. */
1305 if (!FileExists(_searchpaths
[SP_AUTODOWNLOAD_DIR
])) {
1306 free(_searchpaths
[SP_AUTODOWNLOAD_DIR
]);
1307 _searchpaths
[SP_AUTODOWNLOAD_DIR
] = NULL
;
1309 #endif /* ENABLE_NETWORK */
1313 * Sanitizes a filename, i.e. removes all illegal characters from it.
1314 * @param filename the "\0" terminated filename
1316 void SanitizeFilename(char *filename
)
1318 for (; *filename
!= '\0'; filename
++) {
1319 switch (*filename
) {
1320 /* The following characters are not allowed in filenames
1321 * on at least one of the supported operating systems: */
1322 case ':': case '\\': case '*': case '?': case '/':
1323 case '<': case '>': case '|': case '"':
1331 * Load a file into memory.
1332 * @param filename Name of the file to load.
1333 * @param lenp [out] Length of loaded data.
1334 * @param maxsize Maximum size to load.
1335 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1336 * @note If \a maxsize less than the length of the file, loading fails.
1338 void *ReadFileToMem(const char *filename
, size_t *lenp
, size_t maxsize
)
1340 FILE *in
= fopen(filename
, "rb");
1341 if (in
== NULL
) return NULL
;
1343 fseek(in
, 0, SEEK_END
);
1344 size_t len
= ftell(in
);
1345 fseek(in
, 0, SEEK_SET
);
1346 if (len
> maxsize
) {
1350 byte
*mem
= MallocT
<byte
>(len
+ 1);
1352 if (fread(mem
, len
, 1, in
) != 1) {
1364 * Helper to see whether a given filename matches the extension.
1365 * @param extension The extension to look for.
1366 * @param filename The filename to look in for the extension.
1367 * @return True iff the extension is NULL, or the filename ends with it.
1369 static bool MatchesExtension(const char *extension
, const char *filename
)
1371 if (extension
== NULL
) return true;
1373 const char *ext
= strrchr(filename
, extension
[0]);
1374 return ext
!= NULL
&& strcasecmp(ext
, extension
) == 0;
1378 * Scan a single directory (and recursively its children) and add
1379 * any graphics sets that are found.
1380 * @param fs the file scanner to add the files to
1381 * @param extension the extension of files to search for.
1382 * @param path full path we're currently at
1383 * @param basepath_length from where in the path are we 'based' on the search path
1384 * @param recursive whether to recursively search the sub directories
1386 static uint
ScanPath(FileScanner
*fs
, const char *extension
, const char *path
, size_t basepath_length
, bool recursive
)
1388 extern bool FiosIsValidFile(const char *path
, const struct dirent
*ent
, struct stat
*sb
);
1392 struct dirent
*dirent
;
1395 if (path
== NULL
|| (dir
= ttd_opendir(path
)) == NULL
) return 0;
1397 while ((dirent
= readdir(dir
)) != NULL
) {
1398 const char *d_name
= FS2OTTD(dirent
->d_name
);
1399 char filename
[MAX_PATH
];
1401 if (!FiosIsValidFile(path
, dirent
, &sb
)) continue;
1403 snprintf(filename
, lengthof(filename
), "%s%s", path
, d_name
);
1405 if (S_ISDIR(sb
.st_mode
)) {
1407 if (!recursive
) continue;
1408 if (strcmp(d_name
, ".") == 0 || strcmp(d_name
, "..") == 0) continue;
1409 if (!AppendPathSeparator(filename
, lengthof(filename
))) continue;
1410 num
+= ScanPath(fs
, extension
, filename
, basepath_length
, recursive
);
1411 } else if (S_ISREG(sb
.st_mode
)) {
1413 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, basepath_length
, NULL
)) num
++;
1423 * Scan the given tar and add graphics sets when it finds one.
1424 * @param fs the file scanner to scan for
1425 * @param extension the extension of files to search for.
1426 * @param tar the tar to search in.
1428 static uint
ScanTar(FileScanner
*fs
, const char *extension
, TarFileList::iterator tar
)
1431 const char *filename
= (*tar
).first
.c_str();
1433 if (MatchesExtension(extension
, filename
) && fs
->AddFile(filename
, 0, (*tar
).second
.tar_filename
)) num
++;
1439 * Scan for files with the given extension in the given search path.
1440 * @param extension the extension of files to search for.
1441 * @param sd the sub directory to search in.
1442 * @param tars whether to search in the tars too.
1443 * @param recursive whether to search recursively
1444 * @return the number of found files, i.e. the number of times that
1445 * AddFile returned true.
1447 uint
FileScanner::Scan(const char *extension
, Subdirectory sd
, bool tars
, bool recursive
)
1452 char path
[MAX_PATH
];
1453 TarFileList::iterator tar
;
1456 FOR_ALL_SEARCHPATHS(sp
) {
1457 /* Don't search in the working directory */
1458 if (sp
== SP_WORKING_DIR
&& !_do_scan_working_directory
) continue;
1460 FioAppendDirectory(path
, MAX_PATH
, sp
, sd
);
1461 num
+= ScanPath(this, extension
, path
, strlen(path
), recursive
);
1464 if (tars
&& sd
!= NO_DIRECTORY
) {
1465 FOR_ALL_TARS(tar
, sd
) {
1466 num
+= ScanTar(this, extension
, tar
);
1472 num
+= this->Scan(extension
, OLD_GM_DIR
, tars
, recursive
);
1475 num
+= this->Scan(extension
, OLD_DATA_DIR
, tars
, recursive
);
1485 * Scan for files with the given extension in the given search path.
1486 * @param extension the extension of files to search for.
1487 * @param directory the sub directory to search in.
1488 * @param recursive whether to search recursively
1489 * @return the number of found files, i.e. the number of times that
1490 * AddFile returned true.
1492 uint
FileScanner::Scan(const char *extension
, const char *directory
, bool recursive
)
1494 char path
[MAX_PATH
];
1495 strecpy(path
, directory
, lastof(path
));
1496 if (!AppendPathSeparator(path
, lengthof(path
))) return 0;
1497 return ScanPath(this, extension
, path
, strlen(path
), recursive
);