Add: INR currency (#8136)
[openttd-github.git] / src / fileio.cpp
blob5797c592b3363c36ad1ed9bec8aeae0e7ef2655e
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file fileio.cpp Standard In/Out file operations */
10 #include "stdafx.h"
11 #include "fileio_func.h"
12 #include "debug.h"
13 #include "fios.h"
14 #include "string_func.h"
15 #include "tar_type.h"
16 #ifdef _WIN32
17 #include <windows.h>
18 # define access _taccess
19 #elif defined(__HAIKU__)
20 #include <Path.h>
21 #include <storage/FindDirectory.h>
22 #else
23 #include <unistd.h>
24 #include <pwd.h>
25 #endif
26 #include <sys/stat.h>
27 #include <algorithm>
29 #ifdef WITH_XDG_BASEDIR
30 #include <basedir.h>
31 #endif
33 #include "safeguards.h"
35 /** Size of the #Fio data buffer. */
36 #define FIO_BUFFER_SIZE 512
38 /** Structure for keeping several open files with just one data buffer. */
39 struct Fio {
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;
62 /**
63 * Get position in the current file.
64 * @return Position in the file.
66 size_t FioGetPos()
68 return _fio.pos + (_fio.buffer - _fio.buffer_end);
71 /**
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];
81 /**
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;
90 _fio.pos = pos;
91 if (fseek(_fio.cur_fh, _fio.pos, SEEK_SET) < 0) {
92 DEBUG(misc, 0, "Seeking in %s failed", _fio.filename);
96 #if defined(LIMITED_FDS)
97 static void FioRestoreFile(int slot)
99 /* Do we still have the file open, or should we reopen it? */
100 if (_fio.handles[slot] == nullptr) {
101 DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
102 FioOpenFile(slot, _fio.filenames[slot]);
104 _fio.usage_count[slot]++;
106 #endif /* LIMITED_FDS */
109 * Switch to a different file and seek to a position.
110 * @param slot Slot number of the new file.
111 * @param pos New absolute position in the new file.
113 void FioSeekToFile(uint8 slot, size_t pos)
115 FILE *f;
116 #if defined(LIMITED_FDS)
117 /* Make sure we have this file open */
118 FioRestoreFile(slot);
119 #endif /* LIMITED_FDS */
120 f = _fio.handles[slot];
121 assert(f != nullptr);
122 _fio.cur_fh = f;
123 _fio.filename = _fio.filenames[slot];
124 FioSeekTo(pos, SEEK_SET);
128 * Read a byte from the file.
129 * @return Read byte.
131 byte FioReadByte()
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);
136 _fio.pos += size;
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)
150 for (;;) {
151 int m = min(_fio.buffer_end - _fio.buffer, n);
152 _fio.buffer += m;
153 n -= m;
154 if (n == 0) break;
155 FioReadByte();
156 n--;
161 * Read a word (16 bits) from the file (in low endian format).
162 * @return Read word.
164 uint16 FioReadWord()
166 byte b = FioReadByte();
167 return (FioReadByte() << 8) | b;
171 * Read a double word (32 bits) from the file (in low endian format).
172 * @return Read word.
174 uint32 FioReadDword()
176 uint b = FioReadWord();
177 return (FioReadWord() << 16) | b;
181 * Read a block.
182 * @param ptr Destination buffer.
183 * @param size Number of bytes to read.
185 void FioReadBlock(void *ptr, size_t size)
187 FioSeekTo(FioGetPos(), SEEK_SET);
188 _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
192 * Close the file at the given slot number.
193 * @param slot File index to close.
195 static inline void FioCloseFile(int slot)
197 if (_fio.handles[slot] != nullptr) {
198 fclose(_fio.handles[slot]);
200 free(_fio.shortnames[slot]);
201 _fio.shortnames[slot] = nullptr;
203 _fio.handles[slot] = nullptr;
204 #if defined(LIMITED_FDS)
205 _fio.open_handles--;
206 #endif /* LIMITED_FDS */
210 /** Close all slotted open files. */
211 void FioCloseAll()
213 for (int i = 0; i != lengthof(_fio.handles); i++) {
214 FioCloseFile(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) {
223 uint i, count;
224 int slot;
226 count = UINT_MAX;
227 slot = -1;
228 /* Find the file that is used the least */
229 for (i = 0; i < lengthof(_fio.handles); i++) {
230 if (_fio.handles[i] != nullptr && _fio.usage_count[i] < count) {
231 count = _fio.usage_count[i];
232 slot = i;
235 assert(slot != -1);
236 DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
237 FioCloseFile(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)
250 FILE *f;
252 #if defined(LIMITED_FDS)
253 FioFreeHandle();
254 #endif /* LIMITED_FDS */
255 f = FioFOpenFile(filename, "rb", subdir);
256 if (f == nullptr) usererror("Cannot open file '%s'", filename);
257 long pos = ftell(f);
258 if (pos < 0) usererror("Cannot read file '%s'", filename);
260 FioCloseFile(slot); // if file was opened before, close it
261 _fio.handles[slot] = f;
262 _fio.filenames[slot] = filename;
264 /* Store the filename without path and extension */
265 const char *t = strrchr(filename, PATHSEPCHAR);
266 _fio.shortnames[slot] = stredup(t == nullptr ? filename : t);
267 char *t2 = strrchr(_fio.shortnames[slot], '.');
268 if (t2 != nullptr) *t2 = '\0';
269 strtolower(_fio.shortnames[slot]);
271 #if defined(LIMITED_FDS)
272 _fio.usage_count[slot] = 0;
273 _fio.open_handles++;
274 #endif /* LIMITED_FDS */
275 FioSeekToFile(slot, (uint32)pos);
278 static const char * const _subdirs[] = {
280 "save" PATHSEP,
281 "save" PATHSEP "autosave" PATHSEP,
282 "scenario" PATHSEP,
283 "scenario" PATHSEP "heightmap" PATHSEP,
284 "gm" PATHSEP,
285 "data" PATHSEP,
286 "baseset" PATHSEP,
287 "newgrf" PATHSEP,
288 "lang" PATHSEP,
289 "ai" PATHSEP,
290 "ai" PATHSEP "library" PATHSEP,
291 "game" PATHSEP,
292 "game" PATHSEP "library" PATHSEP,
293 "screenshot" PATHSEP,
295 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
297 const char *_searchpaths[NUM_SEARCHPATHS];
298 TarList _tar_list[NUM_SUBDIRS];
299 TarFileList _tar_filelist[NUM_SUBDIRS];
301 typedef std::map<std::string, std::string> TarLinkList;
302 static TarLinkList _tar_linklist[NUM_SUBDIRS]; ///< List of directory links
305 * Check whether the given file exists
306 * @param filename the file to try for existence.
307 * @param subdir the subdirectory to look in
308 * @return true if and only if the file can be opened
310 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
312 FILE *f = FioFOpenFile(filename, "rb", subdir);
313 if (f == nullptr) return false;
315 FioFCloseFile(f);
316 return true;
320 * Test whether the given filename exists.
321 * @param filename the file to test.
322 * @return true if and only if the file exists.
324 bool FileExists(const char *filename)
326 return access(OTTD2FS(filename), 0) == 0;
330 * Close a file in a safe way.
332 void FioFCloseFile(FILE *f)
334 fclose(f);
337 char *FioGetFullPath(char *buf, const char *last, Searchpath sp, Subdirectory subdir, const char *filename)
339 assert(subdir < NUM_SUBDIRS);
340 assert(sp < NUM_SEARCHPATHS);
342 seprintf(buf, last, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
343 return buf;
347 * Find a path to the filename in one of the search directories.
348 * @param[out] buf Destination buffer for the path.
349 * @param last End of the destination buffer.
350 * @param subdir Subdirectory to try.
351 * @param filename Filename to look for.
352 * @return \a buf containing the path if the path was found, else \c nullptr.
354 char *FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
356 Searchpath sp;
357 assert(subdir < NUM_SUBDIRS);
359 FOR_ALL_SEARCHPATHS(sp) {
360 FioGetFullPath(buf, last, sp, subdir, filename);
361 if (FileExists(buf)) return buf;
362 #if !defined(_WIN32)
363 /* Be, as opening files, aware that sometimes the filename
364 * might be in uppercase when it is in lowercase on the
365 * disk. Of course Windows doesn't care about casing. */
366 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
367 #endif
370 return nullptr;
373 char *FioAppendDirectory(char *buf, const char *last, Searchpath sp, Subdirectory subdir)
375 assert(subdir < NUM_SUBDIRS);
376 assert(sp < NUM_SEARCHPATHS);
378 seprintf(buf, last, "%s%s", _searchpaths[sp], _subdirs[subdir]);
379 return buf;
382 char *FioGetDirectory(char *buf, const char *last, Subdirectory subdir)
384 Searchpath sp;
386 /* Find and return the first valid directory */
387 FOR_ALL_SEARCHPATHS(sp) {
388 char *ret = FioAppendDirectory(buf, last, sp, subdir);
389 if (FileExists(buf)) return ret;
392 /* Could not find the directory, fall back to a base path */
393 strecpy(buf, _personal_dir, last);
395 return buf;
398 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
400 #if defined(_WIN32) && defined(UNICODE)
401 /* fopen is implemented as a define with ellipses for
402 * Unicode support (prepend an L). As we are not sending
403 * a string, but a variable, it 'renames' the variable,
404 * so make that variable to makes it compile happily */
405 wchar_t Lmode[5];
406 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
407 #endif
408 FILE *f = nullptr;
409 char buf[MAX_PATH];
411 if (subdir == NO_DIRECTORY) {
412 strecpy(buf, filename, lastof(buf));
413 } else {
414 seprintf(buf, lastof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
417 #if defined(_WIN32)
418 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return nullptr;
419 #endif
421 f = fopen(buf, mode);
422 #if !defined(_WIN32)
423 if (f == nullptr && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
424 f = fopen(buf, mode);
426 #endif
427 if (f != nullptr && filesize != nullptr) {
428 /* Find the size of the file */
429 fseek(f, 0, SEEK_END);
430 *filesize = ftell(f);
431 fseek(f, 0, SEEK_SET);
433 return f;
437 * Opens a file from inside a tar archive.
438 * @param entry The entry to open.
439 * @param[out] filesize If not \c nullptr, size of the opened file.
440 * @return File handle of the opened file, or \c nullptr if the file is not available.
441 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
443 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
445 FILE *f = fopen(entry->tar_filename, "rb");
446 if (f == nullptr) return f;
448 if (fseek(f, entry->position, SEEK_SET) < 0) {
449 fclose(f);
450 return nullptr;
453 if (filesize != nullptr) *filesize = entry->size;
454 return f;
458 * Opens a OpenTTD file somewhere in a personal or global directory.
459 * @param filename Name of the file to open.
460 * @param subdir Subdirectory to open.
461 * @return File handle of the opened file, or \c nullptr if the file is not available.
463 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
465 FILE *f = nullptr;
466 Searchpath sp;
468 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
470 FOR_ALL_SEARCHPATHS(sp) {
471 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
472 if (f != nullptr || subdir == NO_DIRECTORY) break;
475 /* We can only use .tar in case of data-dir, and read-mode */
476 if (f == nullptr && mode[0] == 'r' && subdir != NO_DIRECTORY) {
477 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
478 char resolved_name[MAX_RESOLVED_LENGTH];
480 /* Filenames in tars are always forced to be lowercase */
481 strecpy(resolved_name, filename, lastof(resolved_name));
482 strtolower(resolved_name);
484 size_t resolved_len = strlen(resolved_name);
486 /* Resolve ONE directory link */
487 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
488 const std::string &src = link->first;
489 size_t len = src.length();
490 if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
491 /* Apply link */
492 char resolved_name2[MAX_RESOLVED_LENGTH];
493 const std::string &dest = link->second;
494 strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
495 strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
496 strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
497 break; // Only resolve one level
501 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
502 if (it != _tar_filelist[subdir].end()) {
503 f = FioFOpenFileTar(&((*it).second), filesize);
507 /* Sometimes a full path is given. To support
508 * the 'subdirectory' must be 'removed'. */
509 if (f == nullptr && subdir != NO_DIRECTORY) {
510 switch (subdir) {
511 case BASESET_DIR:
512 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
513 if (f != nullptr) break;
514 FALLTHROUGH;
515 case NEWGRF_DIR:
516 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
517 break;
519 default:
520 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
521 break;
525 return f;
529 * Create a directory with the given name
530 * If the parent directory does not exist, it will try to create that as well.
531 * @param name the new name of the directory
533 void FioCreateDirectory(const char *name)
535 char dirname[MAX_PATH];
536 strecpy(dirname, name, lastof(dirname));
537 char *p = strrchr(dirname, PATHSEPCHAR);
538 if (p != nullptr) {
539 *p = '\0';
540 DIR *dir = ttd_opendir(dirname);
541 if (dir == nullptr) {
542 FioCreateDirectory(dirname); // Try creating the parent directory, if we couldn't open it
543 } else {
544 closedir(dir);
548 /* Ignore directory creation errors; they'll surface later on, and most
549 * of the time they are 'directory already exists' errors anyhow. */
550 #if defined(_WIN32)
551 CreateDirectory(OTTD2FS(name), nullptr);
552 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
553 mkdir(OTTD2FS(name));
554 #else
555 mkdir(OTTD2FS(name), 0755);
556 #endif
560 * Appends, if necessary, the path separator character to the end of the string.
561 * It does not add the path separator to zero-sized strings.
562 * @param buf string to append the separator to
563 * @param last the last element of \a buf.
564 * @return true iff the operation succeeded
566 bool AppendPathSeparator(char *buf, const char *last)
568 size_t s = strlen(buf);
570 /* Length of string + path separator + '\0' */
571 if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
572 if (&buf[s] >= last) return false;
574 seprintf(buf + s, last, "%c", PATHSEPCHAR);
577 return true;
580 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
582 std::string src = srcParam;
583 std::string dest = destParam;
584 /* Tar internals assume lowercase */
585 std::transform(src.begin(), src.end(), src.begin(), tolower);
586 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
588 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
589 if (dest_file != _tar_filelist[subdir].end()) {
590 /* Link to file. Process the link like the destination file. */
591 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
592 } else {
593 /* Destination file not found. Assume 'link to directory'
594 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
595 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
596 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
597 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
602 * Simplify filenames from tars.
603 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
604 * @param name Filename to process.
606 static void SimplifyFileName(char *name)
608 /* Force lowercase */
609 strtolower(name);
611 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
612 #if (PATHSEPCHAR != '/')
613 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
614 #endif
618 * Perform the scanning of a particular subdirectory.
619 * @param sd The subdirectory to scan.
620 * @return The number of found tar files.
622 uint TarScanner::DoScan(Subdirectory sd)
624 _tar_filelist[sd].clear();
625 _tar_list[sd].clear();
626 uint num = this->Scan(".tar", sd, false);
627 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
628 return num;
631 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
633 DEBUG(misc, 1, "Scanning for tars");
634 TarScanner fs;
635 uint num = 0;
636 if (mode & TarScanner::BASESET) {
637 num += fs.DoScan(BASESET_DIR);
639 if (mode & TarScanner::NEWGRF) {
640 num += fs.DoScan(NEWGRF_DIR);
642 if (mode & TarScanner::AI) {
643 num += fs.DoScan(AI_DIR);
644 num += fs.DoScan(AI_LIBRARY_DIR);
646 if (mode & TarScanner::GAME) {
647 num += fs.DoScan(GAME_DIR);
648 num += fs.DoScan(GAME_LIBRARY_DIR);
650 if (mode & TarScanner::SCENARIO) {
651 num += fs.DoScan(SCENARIO_DIR);
652 num += fs.DoScan(HEIGHTMAP_DIR);
654 DEBUG(misc, 1, "Scan complete, found %d files", num);
655 return num;
659 * Add a single file to the scanned files of a tar, circumventing the scanning code.
660 * @param sd The sub directory the file is in.
661 * @param filename The name of the file to add.
662 * @return True if the additions went correctly.
664 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
666 this->subdir = sd;
667 return this->AddFile(filename, 0);
670 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
672 /* No tar within tar. */
673 assert(tar_filename == nullptr);
675 /* The TAR-header, repeated for every file */
676 struct TarHeader {
677 char name[100]; ///< Name of the file
678 char mode[8];
679 char uid[8];
680 char gid[8];
681 char size[12]; ///< Size of the file, in ASCII
682 char mtime[12];
683 char chksum[8];
684 char typeflag;
685 char linkname[100];
686 char magic[6];
687 char version[2];
688 char uname[32];
689 char gname[32];
690 char devmajor[8];
691 char devminor[8];
692 char prefix[155]; ///< Path of the file
694 char unused[12];
697 /* Check if we already seen this file */
698 TarList::iterator it = _tar_list[this->subdir].find(filename);
699 if (it != _tar_list[this->subdir].end()) return false;
701 FILE *f = fopen(filename, "rb");
702 /* Although the file has been found there can be
703 * a number of reasons we cannot open the file.
704 * Most common case is when we simply have not
705 * been given read access. */
706 if (f == nullptr) return false;
708 const char *dupped_filename = stredup(filename);
709 _tar_list[this->subdir][filename].filename = dupped_filename;
710 _tar_list[this->subdir][filename].dirname = nullptr;
712 TarLinkList links; ///< Temporary list to collect links
714 TarHeader th;
715 char buf[sizeof(th.name) + 1], *end;
716 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
717 char link[sizeof(th.linkname) + 1];
718 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
719 size_t num = 0, pos = 0;
721 /* Make a char of 512 empty bytes */
722 char empty[512];
723 memset(&empty[0], 0, sizeof(empty));
725 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
726 size_t num_bytes_read = fread(&th, 1, 512, f);
727 if (num_bytes_read != 512) break;
728 pos += num_bytes_read;
730 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
731 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
732 /* If we have only zeros in the block, it can be an end-of-file indicator */
733 if (memcmp(&th, &empty[0], 512) == 0) continue;
735 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
736 fclose(f);
737 return false;
740 name[0] = '\0';
742 /* The prefix contains the directory-name */
743 if (th.prefix[0] != '\0') {
744 strecpy(name, th.prefix, lastof(name));
745 strecat(name, PATHSEP, lastof(name));
748 /* Copy the name of the file in a safe way at the end of 'name' */
749 strecat(name, th.name, lastof(name));
751 /* Calculate the size of the file.. for some strange reason this is stored as a string */
752 strecpy(buf, th.size, lastof(buf));
753 size_t skip = strtoul(buf, &end, 8);
755 switch (th.typeflag) {
756 case '\0':
757 case '0': { // regular file
758 /* Ignore empty files */
759 if (skip == 0) break;
761 if (strlen(name) == 0) break;
763 /* Store this entry in the list */
764 TarFileListEntry entry;
765 entry.tar_filename = dupped_filename;
766 entry.size = skip;
767 entry.position = pos;
769 /* Convert to lowercase and our PATHSEPCHAR */
770 SimplifyFileName(name);
772 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
773 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
775 break;
778 case '1': // hard links
779 case '2': { // symbolic links
780 /* Copy the destination of the link in a safe way at the end of 'linkname' */
781 strecpy(link, th.linkname, lastof(link));
783 if (strlen(name) == 0 || strlen(link) == 0) break;
785 /* Convert to lowercase and our PATHSEPCHAR */
786 SimplifyFileName(name);
787 SimplifyFileName(link);
789 /* Only allow relative links */
790 if (link[0] == PATHSEPCHAR) {
791 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
792 break;
795 /* Process relative path.
796 * Note: The destination of links must not contain any directory-links. */
797 strecpy(dest, name, lastof(dest));
798 char *destpos = strrchr(dest, PATHSEPCHAR);
799 if (destpos == nullptr) destpos = dest;
800 *destpos = '\0';
802 char *pos = link;
803 while (*pos != '\0') {
804 char *next = strchr(pos, PATHSEPCHAR);
805 if (next == nullptr) {
806 next = pos + strlen(pos);
807 } else {
808 /* Terminate the substring up to the path separator character. */
809 *next++= '\0';
812 if (strcmp(pos, ".") == 0) {
813 /* Skip '.' (current dir) */
814 } else if (strcmp(pos, "..") == 0) {
815 /* level up */
816 if (dest[0] == '\0') {
817 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
818 break;
821 /* Truncate 'dest' after last PATHSEPCHAR.
822 * This assumes that the truncated part is a real directory and not a link. */
823 destpos = strrchr(dest, PATHSEPCHAR);
824 if (destpos == nullptr) destpos = dest;
825 *destpos = '\0';
826 } else {
827 /* Append at end of 'dest' */
828 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
829 destpos = strecpy(destpos, pos, lastof(dest));
832 if (destpos >= lastof(dest)) {
833 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
834 fclose(f);
835 return false;
838 pos = next;
841 /* Store links in temporary list */
842 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
843 links.insert(TarLinkList::value_type(name, dest));
845 break;
848 case '5': // directory
849 /* Convert to lowercase and our PATHSEPCHAR */
850 SimplifyFileName(name);
852 /* Store the first directory name we detect */
853 DEBUG(misc, 6, "Found dir in tar: %s", name);
854 if (_tar_list[this->subdir][filename].dirname == nullptr) _tar_list[this->subdir][filename].dirname = stredup(name);
855 break;
857 default:
858 /* Ignore other types */
859 break;
862 /* Skip to the next block.. */
863 skip = Align(skip, 512);
864 if (fseek(f, skip, SEEK_CUR) < 0) {
865 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
866 fclose(f);
867 return false;
869 pos += skip;
872 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
873 fclose(f);
875 /* Resolve file links and store directory links.
876 * We restrict usage of links to two cases:
877 * 1) Links to directories:
878 * Both the source path and the destination path must NOT contain any further links.
879 * When resolving files at most one directory link is resolved.
880 * 2) Links to files:
881 * The destination path must NOT contain any links.
882 * The source path may contain one directory link.
884 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
885 const std::string &src = link->first;
886 const std::string &dest = link->second;
887 TarAddLink(src, dest, this->subdir);
890 return true;
894 * Extract the tar with the given filename in the directory
895 * where the tar resides.
896 * @param tar_filename the name of the tar to extract.
897 * @param subdir The sub directory the tar is in.
898 * @return false on failure.
900 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
902 TarList::iterator it = _tar_list[subdir].find(tar_filename);
903 /* We don't know the file. */
904 if (it == _tar_list[subdir].end()) return false;
906 const char *dirname = (*it).second.dirname;
908 /* The file doesn't have a sub directory! */
909 if (dirname == nullptr) return false;
911 char filename[MAX_PATH];
912 strecpy(filename, tar_filename, lastof(filename));
913 char *p = strrchr(filename, PATHSEPCHAR);
914 /* The file's path does not have a separator? */
915 if (p == nullptr) return false;
917 p++;
918 strecpy(p, dirname, lastof(filename));
919 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
920 FioCreateDirectory(filename);
922 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
923 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
925 strecpy(p, (*it2).first.c_str(), lastof(filename));
927 DEBUG(misc, 9, " extracting %s", filename);
929 /* First open the file in the .tar. */
930 size_t to_copy = 0;
931 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
932 if (in == nullptr) {
933 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
934 return false;
937 /* Now open the 'output' file. */
938 FILE *out = fopen(filename, "wb");
939 if (out == nullptr) {
940 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
941 fclose(in);
942 return false;
945 /* Now read from the tar and write it into the file. */
946 char buffer[4096];
947 size_t read;
948 for (; to_copy != 0; to_copy -= read) {
949 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
950 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
953 /* Close everything up. */
954 fclose(in);
955 fclose(out);
957 if (to_copy != 0) {
958 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
959 return false;
963 DEBUG(misc, 9, " extraction successful");
964 return true;
967 #if defined(_WIN32)
969 * Determine the base (personal dir and game data dir) paths
970 * @param exe the path from the current path to the executable
971 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
973 extern void DetermineBasePaths(const char *exe);
974 #else /* defined(_WIN32) */
977 * Changes the working directory to the path of the give executable.
978 * For OSX application bundles '.app' is the required extension of the bundle,
979 * so when we crop the path to there, when can remove the name of the bundle
980 * in the same way we remove the name from the executable name.
981 * @param exe the path to the executable
983 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
985 char tmp[MAX_PATH];
986 strecpy(tmp, exe, lastof(tmp));
988 bool success = false;
989 #ifdef WITH_COCOA
990 char *app_bundle = strchr(tmp, '.');
991 while (app_bundle != nullptr && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
993 if (app_bundle != nullptr) *app_bundle = '\0';
994 #endif /* WITH_COCOA */
995 char *s = strrchr(tmp, PATHSEPCHAR);
996 if (s != nullptr) {
997 *s = '\0';
998 if (chdir(tmp) != 0) {
999 DEBUG(misc, 0, "Directory with the binary does not exist?");
1000 } else {
1001 success = true;
1004 return success;
1008 * Whether we should scan the working directory.
1009 * It should not be scanned if it's the root or
1010 * the home directory as in both cases a big data
1011 * directory can cause huge amounts of unrelated
1012 * files scanned. Furthermore there are nearly no
1013 * use cases for the home/root directory to have
1014 * OpenTTD directories.
1015 * @return true if it should be scanned.
1017 bool DoScanWorkingDirectory()
1019 /* No working directory, so nothing to do. */
1020 if (_searchpaths[SP_WORKING_DIR] == nullptr) return false;
1022 /* Working directory is root, so do nothing. */
1023 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1025 /* No personal/home directory, so the working directory won't be that. */
1026 if (_searchpaths[SP_PERSONAL_DIR] == nullptr) return true;
1028 char tmp[MAX_PATH];
1029 seprintf(tmp, lastof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1030 AppendPathSeparator(tmp, lastof(tmp));
1031 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1035 * Determine the base (personal dir and game data dir) paths
1036 * @param exe the path to the executable
1038 void DetermineBasePaths(const char *exe)
1040 char tmp[MAX_PATH];
1041 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1042 const char *xdg_data_home = xdgDataHome(nullptr);
1043 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", xdg_data_home,
1044 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1045 free(xdg_data_home);
1047 AppendPathSeparator(tmp, lastof(tmp));
1048 _searchpaths[SP_PERSONAL_DIR_XDG] = stredup(tmp);
1049 #endif
1050 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1051 _searchpaths[SP_PERSONAL_DIR] = nullptr;
1052 #else
1053 #ifdef __HAIKU__
1054 BPath path;
1055 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1056 const char *homedir = stredup(path.Path());
1057 #else
1058 /* getenv is highly unsafe; duplicate it as soon as possible,
1059 * or at least before something else touches the environment
1060 * variables in any way. It can also contain all kinds of
1061 * unvalidated data we rather not want internally. */
1062 const char *homedir = getenv("HOME");
1063 if (homedir != nullptr) {
1064 homedir = stredup(homedir);
1067 if (homedir == nullptr) {
1068 const struct passwd *pw = getpwuid(getuid());
1069 homedir = (pw == nullptr) ? nullptr : stredup(pw->pw_dir);
1071 #endif
1073 if (homedir != nullptr) {
1074 ValidateString(homedir);
1075 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1076 AppendPathSeparator(tmp, lastof(tmp));
1078 _searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
1079 free(homedir);
1080 } else {
1081 _searchpaths[SP_PERSONAL_DIR] = nullptr;
1083 #endif
1085 #if defined(WITH_SHARED_DIR)
1086 seprintf(tmp, lastof(tmp), "%s", SHARED_DIR);
1087 AppendPathSeparator(tmp, lastof(tmp));
1088 _searchpaths[SP_SHARED_DIR] = stredup(tmp);
1089 #else
1090 _searchpaths[SP_SHARED_DIR] = nullptr;
1091 #endif
1093 if (getcwd(tmp, MAX_PATH) == nullptr) *tmp = '\0';
1094 AppendPathSeparator(tmp, lastof(tmp));
1095 _searchpaths[SP_WORKING_DIR] = stredup(tmp);
1097 _do_scan_working_directory = DoScanWorkingDirectory();
1099 /* Change the working directory to that one of the executable */
1100 if (ChangeWorkingDirectoryToExecutable(exe)) {
1101 if (getcwd(tmp, MAX_PATH) == nullptr) *tmp = '\0';
1102 AppendPathSeparator(tmp, lastof(tmp));
1103 _searchpaths[SP_BINARY_DIR] = stredup(tmp);
1104 } else {
1105 _searchpaths[SP_BINARY_DIR] = nullptr;
1108 if (_searchpaths[SP_WORKING_DIR] != nullptr) {
1109 /* Go back to the current working directory. */
1110 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1111 DEBUG(misc, 0, "Failed to return to working directory!");
1115 #if !defined(GLOBAL_DATA_DIR)
1116 _searchpaths[SP_INSTALLATION_DIR] = nullptr;
1117 #else
1118 seprintf(tmp, lastof(tmp), "%s", GLOBAL_DATA_DIR);
1119 AppendPathSeparator(tmp, lastof(tmp));
1120 _searchpaths[SP_INSTALLATION_DIR] = stredup(tmp);
1121 #endif
1122 #ifdef WITH_COCOA
1123 extern void cocoaSetApplicationBundleDir();
1124 cocoaSetApplicationBundleDir();
1125 #else
1126 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = nullptr;
1127 #endif
1129 #endif /* defined(_WIN32) */
1131 const char *_personal_dir;
1134 * Acquire the base paths (personal dir and game data dir),
1135 * fill all other paths (save dir, autosave dir etc) and
1136 * make the save and scenario directories.
1137 * @param exe the path from the current path to the executable
1139 void DeterminePaths(const char *exe)
1141 DetermineBasePaths(exe);
1143 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1144 char config_home[MAX_PATH];
1146 const char *xdg_config_home = xdgConfigHome(nullptr);
1147 seprintf(config_home, lastof(config_home), "%s" PATHSEP "%s", xdg_config_home,
1148 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1149 free(xdg_config_home);
1151 AppendPathSeparator(config_home, lastof(config_home));
1152 #endif
1154 Searchpath sp;
1155 FOR_ALL_SEARCHPATHS(sp) {
1156 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1157 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1160 char *config_dir;
1161 if (_config_file != nullptr) {
1162 config_dir = stredup(_config_file);
1163 char *end = strrchr(config_dir, PATHSEPCHAR);
1164 if (end == nullptr) {
1165 config_dir[0] = '\0';
1166 } else {
1167 end[1] = '\0';
1169 } else {
1170 char personal_dir[MAX_PATH];
1171 if (FioFindFullPath(personal_dir, lastof(personal_dir), BASE_DIR, "openttd.cfg") != nullptr) {
1172 char *end = strrchr(personal_dir, PATHSEPCHAR);
1173 if (end != nullptr) end[1] = '\0';
1174 config_dir = stredup(personal_dir);
1175 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1176 } else {
1177 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1178 /* No previous configuration file found. Use the configuration folder from XDG. */
1179 config_dir = config_home;
1180 #else
1181 static const Searchpath new_openttd_cfg_order[] = {
1182 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1185 config_dir = nullptr;
1186 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1187 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1188 config_dir = stredup(_searchpaths[new_openttd_cfg_order[i]]);
1189 break;
1192 assert(config_dir != nullptr);
1193 #endif
1194 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1198 DEBUG(misc, 3, "%s found as config directory", config_dir);
1200 _highscore_file = str_fmt("%shs.dat", config_dir);
1201 extern char *_hotkeys_file;
1202 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1203 extern char *_windows_file;
1204 _windows_file = str_fmt("%swindows.cfg", config_dir);
1206 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1207 if (config_dir == config_home) {
1208 /* We are using the XDG configuration home for the config file,
1209 * then store the rest in the XDG data home folder. */
1210 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1211 FioCreateDirectory(_personal_dir);
1212 } else
1213 #endif
1215 _personal_dir = config_dir;
1218 /* Make the necessary folders */
1219 #if defined(WITH_PERSONAL_DIR)
1220 FioCreateDirectory(config_dir);
1221 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1222 #endif
1224 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1226 static const Subdirectory default_subdirs[] = {
1227 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1230 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1231 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1232 FioCreateDirectory(dir);
1233 free(dir);
1236 /* If we have network we make a directory for the autodownloading of content */
1237 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1238 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1240 /* Create the directory for each of the types of content */
1241 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1242 for (uint i = 0; i < lengthof(dirs); i++) {
1243 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1244 FioCreateDirectory(tmp);
1245 free(tmp);
1248 extern char *_log_file;
1249 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1253 * Sanitizes a filename, i.e. removes all illegal characters from it.
1254 * @param filename the "\0" terminated filename
1256 void SanitizeFilename(char *filename)
1258 for (; *filename != '\0'; filename++) {
1259 switch (*filename) {
1260 /* The following characters are not allowed in filenames
1261 * on at least one of the supported operating systems: */
1262 case ':': case '\\': case '*': case '?': case '/':
1263 case '<': case '>': case '|': case '"':
1264 *filename = '_';
1265 break;
1271 * Load a file into memory.
1272 * @param filename Name of the file to load.
1273 * @param[out] lenp Length of loaded data.
1274 * @param maxsize Maximum size to load.
1275 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1276 * @note If \a maxsize less than the length of the file, loading fails.
1278 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1280 FILE *in = fopen(filename, "rb");
1281 if (in == nullptr) return nullptr;
1283 fseek(in, 0, SEEK_END);
1284 size_t len = ftell(in);
1285 fseek(in, 0, SEEK_SET);
1286 if (len > maxsize) {
1287 fclose(in);
1288 return nullptr;
1290 byte *mem = MallocT<byte>(len + 1);
1291 mem[len] = 0;
1292 if (fread(mem, len, 1, in) != 1) {
1293 fclose(in);
1294 free(mem);
1295 return nullptr;
1297 fclose(in);
1299 *lenp = len;
1300 return mem;
1304 * Helper to see whether a given filename matches the extension.
1305 * @param extension The extension to look for.
1306 * @param filename The filename to look in for the extension.
1307 * @return True iff the extension is nullptr, or the filename ends with it.
1309 static bool MatchesExtension(const char *extension, const char *filename)
1311 if (extension == nullptr) return true;
1313 const char *ext = strrchr(filename, extension[0]);
1314 return ext != nullptr && strcasecmp(ext, extension) == 0;
1318 * Scan a single directory (and recursively its children) and add
1319 * any graphics sets that are found.
1320 * @param fs the file scanner to add the files to
1321 * @param extension the extension of files to search for.
1322 * @param path full path we're currently at
1323 * @param basepath_length from where in the path are we 'based' on the search path
1324 * @param recursive whether to recursively search the sub directories
1326 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1328 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1330 uint num = 0;
1331 struct stat sb;
1332 struct dirent *dirent;
1333 DIR *dir;
1335 if (path == nullptr || (dir = ttd_opendir(path)) == nullptr) return 0;
1337 while ((dirent = readdir(dir)) != nullptr) {
1338 const char *d_name = FS2OTTD(dirent->d_name);
1339 char filename[MAX_PATH];
1341 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1343 seprintf(filename, lastof(filename), "%s%s", path, d_name);
1345 if (S_ISDIR(sb.st_mode)) {
1346 /* Directory */
1347 if (!recursive) continue;
1348 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1349 if (!AppendPathSeparator(filename, lastof(filename))) continue;
1350 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1351 } else if (S_ISREG(sb.st_mode)) {
1352 /* File */
1353 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, nullptr)) num++;
1357 closedir(dir);
1359 return num;
1363 * Scan the given tar and add graphics sets when it finds one.
1364 * @param fs the file scanner to scan for
1365 * @param extension the extension of files to search for.
1366 * @param tar the tar to search in.
1368 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1370 uint num = 0;
1371 const char *filename = (*tar).first.c_str();
1373 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1375 return num;
1379 * Scan for files with the given extension in the given search path.
1380 * @param extension the extension of files to search for.
1381 * @param sd the sub directory to search in.
1382 * @param tars whether to search in the tars too.
1383 * @param recursive whether to search recursively
1384 * @return the number of found files, i.e. the number of times that
1385 * AddFile returned true.
1387 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1389 this->subdir = sd;
1391 Searchpath sp;
1392 char path[MAX_PATH];
1393 TarFileList::iterator tar;
1394 uint num = 0;
1396 FOR_ALL_SEARCHPATHS(sp) {
1397 /* Don't search in the working directory */
1398 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1400 FioAppendDirectory(path, lastof(path), sp, sd);
1401 num += ScanPath(this, extension, path, strlen(path), recursive);
1404 if (tars && sd != NO_DIRECTORY) {
1405 FOR_ALL_TARS(tar, sd) {
1406 num += ScanTar(this, extension, tar);
1410 switch (sd) {
1411 case BASESET_DIR:
1412 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1413 FALLTHROUGH;
1414 case NEWGRF_DIR:
1415 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1416 break;
1418 default: break;
1421 return num;
1425 * Scan for files with the given extension in the given search path.
1426 * @param extension the extension of files to search for.
1427 * @param directory the sub directory to search in.
1428 * @param recursive whether to search recursively
1429 * @return the number of found files, i.e. the number of times that
1430 * AddFile returned true.
1432 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1434 char path[MAX_PATH];
1435 strecpy(path, directory, lastof(path));
1436 if (!AppendPathSeparator(path, lastof(path))) return 0;
1437 return ScanPath(this, extension, path, strlen(path), recursive);