Add: Draw network password indicator lock in company drop down list. (#7079)
[openttd-github.git] / src / fileio.cpp
blob0e6d86e3c765ddab1c3f142ec0e39c0ef5d4f015
1 /* $Id$ */
3 /*
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/>.
8 */
10 /** @file fileio.cpp Standard In/Out file operations */
12 #include "stdafx.h"
13 #include "fileio_func.h"
14 #include "debug.h"
15 #include "fios.h"
16 #include "string_func.h"
17 #include "tar_type.h"
18 #ifdef _WIN32
19 #include <windows.h>
20 # define access _taccess
21 #elif defined(__HAIKU__)
22 #include <Path.h>
23 #include <storage/FindDirectory.h>
24 #else
25 #include <unistd.h>
26 #include <pwd.h>
27 #endif
28 #include <sys/stat.h>
29 #include <algorithm>
31 #ifdef WITH_XDG_BASEDIR
32 #include "basedir.h"
33 #endif
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. */
41 struct Fio {
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;
64 /**
65 * Get position in the current file.
66 * @return Position in the file.
68 size_t FioGetPos()
70 return _fio.pos + (_fio.buffer - _fio.buffer_end);
73 /**
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];
83 /**
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;
92 _fio.pos = pos;
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)
117 FILE *f;
118 #if defined(LIMITED_FDS)
119 /* Make sure we have this file open */
120 FioRestoreFile(slot);
121 #endif /* LIMITED_FDS */
122 f = _fio.handles[slot];
123 assert(f != NULL);
124 _fio.cur_fh = f;
125 _fio.filename = _fio.filenames[slot];
126 FioSeekTo(pos, SEEK_SET);
130 * Read a byte from the file.
131 * @return Read byte.
133 byte FioReadByte()
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);
138 _fio.pos += size;
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)
152 for (;;) {
153 int m = min(_fio.buffer_end - _fio.buffer, n);
154 _fio.buffer += m;
155 n -= m;
156 if (n == 0) break;
157 FioReadByte();
158 n--;
163 * Read a word (16 bits) from the file (in low endian format).
164 * @return Read word.
166 uint16 FioReadWord()
168 byte b = FioReadByte();
169 return (FioReadByte() << 8) | b;
173 * Read a double word (32 bits) from the file (in low endian format).
174 * @return Read word.
176 uint32 FioReadDword()
178 uint b = FioReadWord();
179 return (FioReadWord() << 16) | b;
183 * Read a block.
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)
207 _fio.open_handles--;
208 #endif /* LIMITED_FDS */
212 /** Close all slotted open files. */
213 void FioCloseAll()
215 for (int i = 0; i != lengthof(_fio.handles); i++) {
216 FioCloseFile(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) {
225 uint i, count;
226 int slot;
228 count = UINT_MAX;
229 slot = -1;
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];
234 slot = i;
237 assert(slot != -1);
238 DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
239 FioCloseFile(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)
252 FILE *f;
254 #if defined(LIMITED_FDS)
255 FioFreeHandle();
256 #endif /* LIMITED_FDS */
257 f = FioFOpenFile(filename, "rb", subdir);
258 if (f == NULL) usererror("Cannot open file '%s'", filename);
259 long pos = ftell(f);
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;
275 _fio.open_handles++;
276 #endif /* LIMITED_FDS */
277 FioSeekToFile(slot, (uint32)pos);
280 static const char * const _subdirs[] = {
282 "save" PATHSEP,
283 "save" PATHSEP "autosave" PATHSEP,
284 "scenario" PATHSEP,
285 "scenario" PATHSEP "heightmap" PATHSEP,
286 "gm" PATHSEP,
287 "data" PATHSEP,
288 "baseset" PATHSEP,
289 "newgrf" PATHSEP,
290 "lang" PATHSEP,
291 "ai" PATHSEP,
292 "ai" PATHSEP "library" PATHSEP,
293 "game" 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;
317 FioFCloseFile(f);
318 return true;
322 * Test whether the given filename exists.
323 * @param filename the file to test.
324 * @return true if and only if the file exists.
326 bool FileExists(const char *filename)
328 return access(OTTD2FS(filename), 0) == 0;
332 * Close a file in a safe way.
334 void FioFCloseFile(FILE *f)
336 fclose(f);
339 char *FioGetFullPath(char *buf, const char *last, Searchpath sp, Subdirectory subdir, const char *filename)
341 assert(subdir < NUM_SUBDIRS);
342 assert(sp < NUM_SEARCHPATHS);
344 seprintf(buf, last, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
345 return buf;
349 * Find a path to the filename in one of the search directories.
350 * @param[out] buf Destination buffer for the path.
351 * @param last End of the destination buffer.
352 * @param subdir Subdirectory to try.
353 * @param filename Filename to look for.
354 * @return \a buf containing the path if the path was found, else \c NULL.
356 char *FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
358 Searchpath sp;
359 assert(subdir < NUM_SUBDIRS);
361 FOR_ALL_SEARCHPATHS(sp) {
362 FioGetFullPath(buf, last, sp, subdir, filename);
363 if (FileExists(buf)) return buf;
364 #if !defined(_WIN32)
365 /* Be, as opening files, aware that sometimes the filename
366 * might be in uppercase when it is in lowercase on the
367 * disk. Of course Windows doesn't care about casing. */
368 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
369 #endif
372 return NULL;
375 char *FioAppendDirectory(char *buf, const char *last, Searchpath sp, Subdirectory subdir)
377 assert(subdir < NUM_SUBDIRS);
378 assert(sp < NUM_SEARCHPATHS);
380 seprintf(buf, last, "%s%s", _searchpaths[sp], _subdirs[subdir]);
381 return buf;
384 char *FioGetDirectory(char *buf, const char *last, Subdirectory subdir)
386 Searchpath sp;
388 /* Find and return the first valid directory */
389 FOR_ALL_SEARCHPATHS(sp) {
390 char *ret = FioAppendDirectory(buf, last, sp, subdir);
391 if (FileExists(buf)) return ret;
394 /* Could not find the directory, fall back to a base path */
395 strecpy(buf, _personal_dir, last);
397 return buf;
400 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
402 #if defined(_WIN32) && defined(UNICODE)
403 /* fopen is implemented as a define with ellipses for
404 * Unicode support (prepend an L). As we are not sending
405 * a string, but a variable, it 'renames' the variable,
406 * so make that variable to makes it compile happily */
407 wchar_t Lmode[5];
408 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
409 #endif
410 FILE *f = NULL;
411 char buf[MAX_PATH];
413 if (subdir == NO_DIRECTORY) {
414 strecpy(buf, filename, lastof(buf));
415 } else {
416 seprintf(buf, lastof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
419 #if defined(_WIN32)
420 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
421 #endif
423 f = fopen(buf, mode);
424 #if !defined(_WIN32)
425 if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
426 f = fopen(buf, mode);
428 #endif
429 if (f != NULL && filesize != NULL) {
430 /* Find the size of the file */
431 fseek(f, 0, SEEK_END);
432 *filesize = ftell(f);
433 fseek(f, 0, SEEK_SET);
435 return f;
439 * Opens a file from inside a tar archive.
440 * @param entry The entry to open.
441 * @param[out] filesize If not \c NULL, size of the opened file.
442 * @return File handle of the opened file, or \c NULL if the file is not available.
443 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
445 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
447 FILE *f = fopen(entry->tar_filename, "rb");
448 if (f == NULL) return f;
450 if (fseek(f, entry->position, SEEK_SET) < 0) {
451 fclose(f);
452 return NULL;
455 if (filesize != NULL) *filesize = entry->size;
456 return f;
460 * Opens a OpenTTD file somewhere in a personal or global directory.
461 * @param filename Name of the file to open.
462 * @param subdir Subdirectory to open.
463 * @return File handle of the opened file, or \c NULL if the file is not available.
465 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
467 FILE *f = NULL;
468 Searchpath sp;
470 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
472 FOR_ALL_SEARCHPATHS(sp) {
473 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
474 if (f != NULL || subdir == NO_DIRECTORY) break;
477 /* We can only use .tar in case of data-dir, and read-mode */
478 if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
479 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
480 char resolved_name[MAX_RESOLVED_LENGTH];
482 /* Filenames in tars are always forced to be lowercase */
483 strecpy(resolved_name, filename, lastof(resolved_name));
484 strtolower(resolved_name);
486 size_t resolved_len = strlen(resolved_name);
488 /* Resolve ONE directory link */
489 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
490 const std::string &src = link->first;
491 size_t len = src.length();
492 if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
493 /* Apply link */
494 char resolved_name2[MAX_RESOLVED_LENGTH];
495 const std::string &dest = link->second;
496 strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
497 strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
498 strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
499 break; // Only resolve one level
503 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
504 if (it != _tar_filelist[subdir].end()) {
505 f = FioFOpenFileTar(&((*it).second), filesize);
509 /* Sometimes a full path is given. To support
510 * the 'subdirectory' must be 'removed'. */
511 if (f == NULL && subdir != NO_DIRECTORY) {
512 switch (subdir) {
513 case BASESET_DIR:
514 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
515 if (f != NULL) break;
516 FALLTHROUGH;
517 case NEWGRF_DIR:
518 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
519 break;
521 default:
522 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
523 break;
527 return f;
531 * Create a directory with the given name
532 * @param name the new name of the directory
534 void FioCreateDirectory(const char *name)
536 /* Ignore directory creation errors; they'll surface later on, and most
537 * of the time they are 'directory already exists' errors anyhow. */
538 #if defined(_WIN32)
539 CreateDirectory(OTTD2FS(name), NULL);
540 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
541 mkdir(OTTD2FS(name));
542 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
543 char buf[MAX_PATH];
544 strecpy(buf, name, lastof(buf));
546 size_t len = strlen(name) - 1;
547 if (buf[len] == '/') {
548 buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
551 mkdir(OTTD2FS(buf), 0755);
552 #else
553 mkdir(OTTD2FS(name), 0755);
554 #endif
558 * Appends, if necessary, the path separator character to the end of the string.
559 * It does not add the path separator to zero-sized strings.
560 * @param buf string to append the separator to
561 * @param last the last element of \a buf.
562 * @return true iff the operation succeeded
564 bool AppendPathSeparator(char *buf, const char *last)
566 size_t s = strlen(buf);
568 /* Length of string + path separator + '\0' */
569 if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
570 if (&buf[s] >= last) return false;
572 seprintf(buf + s, last, "%c", PATHSEPCHAR);
575 return true;
579 * Find the first directory in a tar archive.
580 * @param tarname the name of the tar archive to look in.
581 * @param subdir the subdirectory to look in.
583 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
585 TarList::iterator it = _tar_list[subdir].find(tarname);
586 if (it == _tar_list[subdir].end()) return NULL;
587 return (*it).second.dirname;
590 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
592 std::string src = srcParam;
593 std::string dest = destParam;
594 /* Tar internals assume lowercase */
595 std::transform(src.begin(), src.end(), src.begin(), tolower);
596 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
598 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
599 if (dest_file != _tar_filelist[subdir].end()) {
600 /* Link to file. Process the link like the destination file. */
601 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
602 } else {
603 /* Destination file not found. Assume 'link to directory'
604 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
605 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
606 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
607 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
611 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
613 TarAddLink(src, dest, subdir);
617 * Simplify filenames from tars.
618 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
619 * @param name Filename to process.
621 static void SimplifyFileName(char *name)
623 /* Force lowercase */
624 strtolower(name);
626 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
627 #if (PATHSEPCHAR != '/')
628 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
629 #endif
633 * Perform the scanning of a particular subdirectory.
634 * @param sd The subdirectory to scan.
635 * @return The number of found tar files.
637 uint TarScanner::DoScan(Subdirectory sd)
639 _tar_filelist[sd].clear();
640 _tar_list[sd].clear();
641 uint num = this->Scan(".tar", sd, false);
642 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
643 return num;
646 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
648 DEBUG(misc, 1, "Scanning for tars");
649 TarScanner fs;
650 uint num = 0;
651 if (mode & TarScanner::BASESET) {
652 num += fs.DoScan(BASESET_DIR);
654 if (mode & TarScanner::NEWGRF) {
655 num += fs.DoScan(NEWGRF_DIR);
657 if (mode & TarScanner::AI) {
658 num += fs.DoScan(AI_DIR);
659 num += fs.DoScan(AI_LIBRARY_DIR);
661 if (mode & TarScanner::GAME) {
662 num += fs.DoScan(GAME_DIR);
663 num += fs.DoScan(GAME_LIBRARY_DIR);
665 if (mode & TarScanner::SCENARIO) {
666 num += fs.DoScan(SCENARIO_DIR);
667 num += fs.DoScan(HEIGHTMAP_DIR);
669 DEBUG(misc, 1, "Scan complete, found %d files", num);
670 return num;
674 * Add a single file to the scanned files of a tar, circumventing the scanning code.
675 * @param sd The sub directory the file is in.
676 * @param filename The name of the file to add.
677 * @return True if the additions went correctly.
679 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
681 this->subdir = sd;
682 return this->AddFile(filename, 0);
685 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
687 /* No tar within tar. */
688 assert(tar_filename == NULL);
690 /* The TAR-header, repeated for every file */
691 struct TarHeader {
692 char name[100]; ///< Name of the file
693 char mode[8];
694 char uid[8];
695 char gid[8];
696 char size[12]; ///< Size of the file, in ASCII
697 char mtime[12];
698 char chksum[8];
699 char typeflag;
700 char linkname[100];
701 char magic[6];
702 char version[2];
703 char uname[32];
704 char gname[32];
705 char devmajor[8];
706 char devminor[8];
707 char prefix[155]; ///< Path of the file
709 char unused[12];
712 /* Check if we already seen this file */
713 TarList::iterator it = _tar_list[this->subdir].find(filename);
714 if (it != _tar_list[this->subdir].end()) return false;
716 FILE *f = fopen(filename, "rb");
717 /* Although the file has been found there can be
718 * a number of reasons we cannot open the file.
719 * Most common case is when we simply have not
720 * been given read access. */
721 if (f == NULL) return false;
723 const char *dupped_filename = stredup(filename);
724 _tar_list[this->subdir][filename].filename = dupped_filename;
725 _tar_list[this->subdir][filename].dirname = NULL;
727 TarLinkList links; ///< Temporary list to collect links
729 TarHeader th;
730 char buf[sizeof(th.name) + 1], *end;
731 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
732 char link[sizeof(th.linkname) + 1];
733 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
734 size_t num = 0, pos = 0;
736 /* Make a char of 512 empty bytes */
737 char empty[512];
738 memset(&empty[0], 0, sizeof(empty));
740 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
741 size_t num_bytes_read = fread(&th, 1, 512, f);
742 if (num_bytes_read != 512) break;
743 pos += num_bytes_read;
745 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
746 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
747 /* If we have only zeros in the block, it can be an end-of-file indicator */
748 if (memcmp(&th, &empty[0], 512) == 0) continue;
750 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
751 fclose(f);
752 return false;
755 name[0] = '\0';
757 /* The prefix contains the directory-name */
758 if (th.prefix[0] != '\0') {
759 strecpy(name, th.prefix, lastof(name));
760 strecat(name, PATHSEP, lastof(name));
763 /* Copy the name of the file in a safe way at the end of 'name' */
764 strecat(name, th.name, lastof(name));
766 /* Calculate the size of the file.. for some strange reason this is stored as a string */
767 strecpy(buf, th.size, lastof(buf));
768 size_t skip = strtoul(buf, &end, 8);
770 switch (th.typeflag) {
771 case '\0':
772 case '0': { // regular file
773 /* Ignore empty files */
774 if (skip == 0) break;
776 if (strlen(name) == 0) break;
778 /* Store this entry in the list */
779 TarFileListEntry entry;
780 entry.tar_filename = dupped_filename;
781 entry.size = skip;
782 entry.position = pos;
784 /* Convert to lowercase and our PATHSEPCHAR */
785 SimplifyFileName(name);
787 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
788 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
790 break;
793 case '1': // hard links
794 case '2': { // symbolic links
795 /* Copy the destination of the link in a safe way at the end of 'linkname' */
796 strecpy(link, th.linkname, lastof(link));
798 if (strlen(name) == 0 || strlen(link) == 0) break;
800 /* Convert to lowercase and our PATHSEPCHAR */
801 SimplifyFileName(name);
802 SimplifyFileName(link);
804 /* Only allow relative links */
805 if (link[0] == PATHSEPCHAR) {
806 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
807 break;
810 /* Process relative path.
811 * Note: The destination of links must not contain any directory-links. */
812 strecpy(dest, name, lastof(dest));
813 char *destpos = strrchr(dest, PATHSEPCHAR);
814 if (destpos == NULL) destpos = dest;
815 *destpos = '\0';
817 char *pos = link;
818 while (*pos != '\0') {
819 char *next = strchr(pos, PATHSEPCHAR);
820 if (next == NULL) {
821 next = pos + strlen(pos);
822 } else {
823 /* Terminate the substring up to the path separator character. */
824 *next++= '\0';
827 if (strcmp(pos, ".") == 0) {
828 /* Skip '.' (current dir) */
829 } else if (strcmp(pos, "..") == 0) {
830 /* level up */
831 if (dest[0] == '\0') {
832 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
833 break;
836 /* Truncate 'dest' after last PATHSEPCHAR.
837 * This assumes that the truncated part is a real directory and not a link. */
838 destpos = strrchr(dest, PATHSEPCHAR);
839 if (destpos == NULL) destpos = dest;
840 *destpos = '\0';
841 } else {
842 /* Append at end of 'dest' */
843 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
844 destpos = strecpy(destpos, pos, lastof(dest));
847 if (destpos >= lastof(dest)) {
848 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
849 fclose(f);
850 return false;
853 pos = next;
856 /* Store links in temporary list */
857 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
858 links.insert(TarLinkList::value_type(name, dest));
860 break;
863 case '5': // directory
864 /* Convert to lowercase and our PATHSEPCHAR */
865 SimplifyFileName(name);
867 /* Store the first directory name we detect */
868 DEBUG(misc, 6, "Found dir in tar: %s", name);
869 if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = stredup(name);
870 break;
872 default:
873 /* Ignore other types */
874 break;
877 /* Skip to the next block.. */
878 skip = Align(skip, 512);
879 if (fseek(f, skip, SEEK_CUR) < 0) {
880 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
881 fclose(f);
882 return false;
884 pos += skip;
887 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
888 fclose(f);
890 /* Resolve file links and store directory links.
891 * We restrict usage of links to two cases:
892 * 1) Links to directories:
893 * Both the source path and the destination path must NOT contain any further links.
894 * When resolving files at most one directory link is resolved.
895 * 2) Links to files:
896 * The destination path must NOT contain any links.
897 * The source path may contain one directory link.
899 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
900 const std::string &src = link->first;
901 const std::string &dest = link->second;
902 TarAddLink(src, dest, this->subdir);
905 return true;
909 * Extract the tar with the given filename in the directory
910 * where the tar resides.
911 * @param tar_filename the name of the tar to extract.
912 * @param subdir The sub directory the tar is in.
913 * @return false on failure.
915 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
917 TarList::iterator it = _tar_list[subdir].find(tar_filename);
918 /* We don't know the file. */
919 if (it == _tar_list[subdir].end()) return false;
921 const char *dirname = (*it).second.dirname;
923 /* The file doesn't have a sub directory! */
924 if (dirname == NULL) return false;
926 char filename[MAX_PATH];
927 strecpy(filename, tar_filename, lastof(filename));
928 char *p = strrchr(filename, PATHSEPCHAR);
929 /* The file's path does not have a separator? */
930 if (p == NULL) return false;
932 p++;
933 strecpy(p, dirname, lastof(filename));
934 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
935 FioCreateDirectory(filename);
937 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
938 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
940 strecpy(p, (*it2).first.c_str(), lastof(filename));
942 DEBUG(misc, 9, " extracting %s", filename);
944 /* First open the file in the .tar. */
945 size_t to_copy = 0;
946 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
947 if (in == NULL) {
948 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
949 return false;
952 /* Now open the 'output' file. */
953 FILE *out = fopen(filename, "wb");
954 if (out == NULL) {
955 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
956 fclose(in);
957 return false;
960 /* Now read from the tar and write it into the file. */
961 char buffer[4096];
962 size_t read;
963 for (; to_copy != 0; to_copy -= read) {
964 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
965 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
968 /* Close everything up. */
969 fclose(in);
970 fclose(out);
972 if (to_copy != 0) {
973 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
974 return false;
978 DEBUG(misc, 9, " extraction successful");
979 return true;
982 #if defined(_WIN32)
984 * Determine the base (personal dir and game data dir) paths
985 * @param exe the path from the current path to the executable
986 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
988 extern void DetermineBasePaths(const char *exe);
989 #else /* defined(_WIN32) */
992 * Changes the working directory to the path of the give executable.
993 * For OSX application bundles '.app' is the required extension of the bundle,
994 * so when we crop the path to there, when can remove the name of the bundle
995 * in the same way we remove the name from the executable name.
996 * @param exe the path to the executable
998 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
1000 char tmp[MAX_PATH];
1001 strecpy(tmp, exe, lastof(tmp));
1003 bool success = false;
1004 #ifdef WITH_COCOA
1005 char *app_bundle = strchr(tmp, '.');
1006 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1008 if (app_bundle != NULL) *app_bundle = '\0';
1009 #endif /* WITH_COCOA */
1010 char *s = strrchr(tmp, PATHSEPCHAR);
1011 if (s != NULL) {
1012 *s = '\0';
1013 #if defined(__DJGPP__)
1014 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1015 if (s > tmp && *(s - 1) == ':') chdir("/");
1016 #endif
1017 if (chdir(tmp) != 0) {
1018 DEBUG(misc, 0, "Directory with the binary does not exist?");
1019 } else {
1020 success = true;
1023 return success;
1027 * Whether we should scan the working directory.
1028 * It should not be scanned if it's the root or
1029 * the home directory as in both cases a big data
1030 * directory can cause huge amounts of unrelated
1031 * files scanned. Furthermore there are nearly no
1032 * use cases for the home/root directory to have
1033 * OpenTTD directories.
1034 * @return true if it should be scanned.
1036 bool DoScanWorkingDirectory()
1038 /* No working directory, so nothing to do. */
1039 if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
1041 /* Working directory is root, so do nothing. */
1042 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1044 /* No personal/home directory, so the working directory won't be that. */
1045 if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
1047 char tmp[MAX_PATH];
1048 seprintf(tmp, lastof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1049 AppendPathSeparator(tmp, lastof(tmp));
1050 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1054 * Determine the base (personal dir and game data dir) paths
1055 * @param exe the path to the executable
1057 void DetermineBasePaths(const char *exe)
1059 char tmp[MAX_PATH];
1060 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1061 const char *xdg_data_home = xdgDataHome(NULL);
1062 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", xdg_data_home,
1063 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1064 free(xdg_data_home);
1066 AppendPathSeparator(tmp, lastof(tmp));
1067 _searchpaths[SP_PERSONAL_DIR_XDG] = stredup(tmp);
1068 #endif
1069 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1070 _searchpaths[SP_PERSONAL_DIR] = NULL;
1071 #else
1072 #ifdef __HAIKU__
1073 BPath path;
1074 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1075 const char *homedir = stredup(path.Path());
1076 #else
1077 /* getenv is highly unsafe; duplicate it as soon as possible,
1078 * or at least before something else touches the environment
1079 * variables in any way. It can also contain all kinds of
1080 * unvalidated data we rather not want internally. */
1081 const char *homedir = getenv("HOME");
1082 if (homedir != NULL) {
1083 homedir = stredup(homedir);
1086 if (homedir == NULL) {
1087 const struct passwd *pw = getpwuid(getuid());
1088 homedir = (pw == NULL) ? NULL : stredup(pw->pw_dir);
1090 #endif
1092 if (homedir != NULL) {
1093 ValidateString(homedir);
1094 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1095 AppendPathSeparator(tmp, lastof(tmp));
1097 _searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
1098 free(homedir);
1099 } else {
1100 _searchpaths[SP_PERSONAL_DIR] = NULL;
1102 #endif
1104 #if defined(WITH_SHARED_DIR)
1105 seprintf(tmp, lastof(tmp), "%s", SHARED_DIR);
1106 AppendPathSeparator(tmp, lastof(tmp));
1107 _searchpaths[SP_SHARED_DIR] = stredup(tmp);
1108 #else
1109 _searchpaths[SP_SHARED_DIR] = NULL;
1110 #endif
1112 #if defined(__MORPHOS__) || defined(__AMIGA__)
1113 _searchpaths[SP_WORKING_DIR] = NULL;
1114 #else
1115 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1116 AppendPathSeparator(tmp, lastof(tmp));
1117 _searchpaths[SP_WORKING_DIR] = stredup(tmp);
1118 #endif
1120 _do_scan_working_directory = DoScanWorkingDirectory();
1122 /* Change the working directory to that one of the executable */
1123 if (ChangeWorkingDirectoryToExecutable(exe)) {
1124 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1125 AppendPathSeparator(tmp, lastof(tmp));
1126 _searchpaths[SP_BINARY_DIR] = stredup(tmp);
1127 } else {
1128 _searchpaths[SP_BINARY_DIR] = NULL;
1131 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1132 /* Go back to the current working directory. */
1133 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1134 DEBUG(misc, 0, "Failed to return to working directory!");
1138 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1139 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1140 #else
1141 seprintf(tmp, lastof(tmp), "%s", GLOBAL_DATA_DIR);
1142 AppendPathSeparator(tmp, lastof(tmp));
1143 _searchpaths[SP_INSTALLATION_DIR] = stredup(tmp);
1144 #endif
1145 #ifdef WITH_COCOA
1146 extern void cocoaSetApplicationBundleDir();
1147 cocoaSetApplicationBundleDir();
1148 #else
1149 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1150 #endif
1152 #endif /* defined(_WIN32) */
1154 const char *_personal_dir;
1157 * Acquire the base paths (personal dir and game data dir),
1158 * fill all other paths (save dir, autosave dir etc) and
1159 * make the save and scenario directories.
1160 * @param exe the path from the current path to the executable
1162 void DeterminePaths(const char *exe)
1164 DetermineBasePaths(exe);
1166 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1167 char config_home[MAX_PATH];
1169 const char *xdg_config_home = xdgConfigHome(NULL);
1170 seprintf(config_home, lastof(config_home), "%s" PATHSEP "%s", xdg_config_home,
1171 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1172 free(xdg_config_home);
1174 AppendPathSeparator(config_home, lastof(config_home));
1175 #endif
1177 Searchpath sp;
1178 FOR_ALL_SEARCHPATHS(sp) {
1179 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1180 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1183 char *config_dir;
1184 if (_config_file != NULL) {
1185 config_dir = stredup(_config_file);
1186 char *end = strrchr(config_dir, PATHSEPCHAR);
1187 if (end == NULL) {
1188 config_dir[0] = '\0';
1189 } else {
1190 end[1] = '\0';
1192 } else {
1193 char personal_dir[MAX_PATH];
1194 if (FioFindFullPath(personal_dir, lastof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
1195 char *end = strrchr(personal_dir, PATHSEPCHAR);
1196 if (end != NULL) end[1] = '\0';
1197 config_dir = stredup(personal_dir);
1198 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1199 } else {
1200 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1201 /* No previous configuration file found. Use the configuration folder from XDG. */
1202 config_dir = config_home;
1203 #else
1204 static const Searchpath new_openttd_cfg_order[] = {
1205 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1208 config_dir = NULL;
1209 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1210 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1211 config_dir = stredup(_searchpaths[new_openttd_cfg_order[i]]);
1212 break;
1215 assert(config_dir != NULL);
1216 #endif
1217 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1221 DEBUG(misc, 3, "%s found as config directory", config_dir);
1223 _highscore_file = str_fmt("%shs.dat", config_dir);
1224 extern char *_hotkeys_file;
1225 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1226 extern char *_windows_file;
1227 _windows_file = str_fmt("%swindows.cfg", config_dir);
1229 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1230 if (config_dir == config_home) {
1231 /* We are using the XDG configuration home for the config file,
1232 * then store the rest in the XDG data home folder. */
1233 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1234 FioCreateDirectory(_personal_dir);
1235 } else
1236 #endif
1238 _personal_dir = config_dir;
1241 /* Make the necessary folders */
1242 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1243 FioCreateDirectory(config_dir);
1244 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1245 #endif
1247 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1249 static const Subdirectory default_subdirs[] = {
1250 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1253 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1254 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1255 FioCreateDirectory(dir);
1256 free(dir);
1259 /* If we have network we make a directory for the autodownloading of content */
1260 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1261 #ifdef ENABLE_NETWORK
1262 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1264 /* Create the directory for each of the types of content */
1265 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1266 for (uint i = 0; i < lengthof(dirs); i++) {
1267 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1268 FioCreateDirectory(tmp);
1269 free(tmp);
1272 extern char *_log_file;
1273 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1274 #else /* ENABLE_NETWORK */
1275 /* If we don't have networking, we don't need to make the directory. But
1276 * if it exists we keep it, otherwise remove it from the search paths. */
1277 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1278 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1279 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1281 #endif /* ENABLE_NETWORK */
1285 * Sanitizes a filename, i.e. removes all illegal characters from it.
1286 * @param filename the "\0" terminated filename
1288 void SanitizeFilename(char *filename)
1290 for (; *filename != '\0'; filename++) {
1291 switch (*filename) {
1292 /* The following characters are not allowed in filenames
1293 * on at least one of the supported operating systems: */
1294 case ':': case '\\': case '*': case '?': case '/':
1295 case '<': case '>': case '|': case '"':
1296 *filename = '_';
1297 break;
1303 * Load a file into memory.
1304 * @param filename Name of the file to load.
1305 * @param[out] lenp Length of loaded data.
1306 * @param maxsize Maximum size to load.
1307 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1308 * @note If \a maxsize less than the length of the file, loading fails.
1310 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1312 FILE *in = fopen(filename, "rb");
1313 if (in == NULL) return NULL;
1315 fseek(in, 0, SEEK_END);
1316 size_t len = ftell(in);
1317 fseek(in, 0, SEEK_SET);
1318 if (len > maxsize) {
1319 fclose(in);
1320 return NULL;
1322 byte *mem = MallocT<byte>(len + 1);
1323 mem[len] = 0;
1324 if (fread(mem, len, 1, in) != 1) {
1325 fclose(in);
1326 free(mem);
1327 return NULL;
1329 fclose(in);
1331 *lenp = len;
1332 return mem;
1336 * Helper to see whether a given filename matches the extension.
1337 * @param extension The extension to look for.
1338 * @param filename The filename to look in for the extension.
1339 * @return True iff the extension is NULL, or the filename ends with it.
1341 static bool MatchesExtension(const char *extension, const char *filename)
1343 if (extension == NULL) return true;
1345 const char *ext = strrchr(filename, extension[0]);
1346 return ext != NULL && strcasecmp(ext, extension) == 0;
1350 * Scan a single directory (and recursively its children) and add
1351 * any graphics sets that are found.
1352 * @param fs the file scanner to add the files to
1353 * @param extension the extension of files to search for.
1354 * @param path full path we're currently at
1355 * @param basepath_length from where in the path are we 'based' on the search path
1356 * @param recursive whether to recursively search the sub directories
1358 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1360 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1362 uint num = 0;
1363 struct stat sb;
1364 struct dirent *dirent;
1365 DIR *dir;
1367 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1369 while ((dirent = readdir(dir)) != NULL) {
1370 const char *d_name = FS2OTTD(dirent->d_name);
1371 char filename[MAX_PATH];
1373 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1375 seprintf(filename, lastof(filename), "%s%s", path, d_name);
1377 if (S_ISDIR(sb.st_mode)) {
1378 /* Directory */
1379 if (!recursive) continue;
1380 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1381 if (!AppendPathSeparator(filename, lastof(filename))) continue;
1382 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1383 } else if (S_ISREG(sb.st_mode)) {
1384 /* File */
1385 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
1389 closedir(dir);
1391 return num;
1395 * Scan the given tar and add graphics sets when it finds one.
1396 * @param fs the file scanner to scan for
1397 * @param extension the extension of files to search for.
1398 * @param tar the tar to search in.
1400 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1402 uint num = 0;
1403 const char *filename = (*tar).first.c_str();
1405 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1407 return num;
1411 * Scan for files with the given extension in the given search path.
1412 * @param extension the extension of files to search for.
1413 * @param sd the sub directory to search in.
1414 * @param tars whether to search in the tars too.
1415 * @param recursive whether to search recursively
1416 * @return the number of found files, i.e. the number of times that
1417 * AddFile returned true.
1419 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1421 this->subdir = sd;
1423 Searchpath sp;
1424 char path[MAX_PATH];
1425 TarFileList::iterator tar;
1426 uint num = 0;
1428 FOR_ALL_SEARCHPATHS(sp) {
1429 /* Don't search in the working directory */
1430 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1432 FioAppendDirectory(path, lastof(path), sp, sd);
1433 num += ScanPath(this, extension, path, strlen(path), recursive);
1436 if (tars && sd != NO_DIRECTORY) {
1437 FOR_ALL_TARS(tar, sd) {
1438 num += ScanTar(this, extension, tar);
1442 switch (sd) {
1443 case BASESET_DIR:
1444 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1445 FALLTHROUGH;
1446 case NEWGRF_DIR:
1447 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1448 break;
1450 default: break;
1453 return num;
1457 * Scan for files with the given extension in the given search path.
1458 * @param extension the extension of files to search for.
1459 * @param directory the sub directory to search in.
1460 * @param recursive whether to search recursively
1461 * @return the number of found files, i.e. the number of times that
1462 * AddFile returned true.
1464 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1466 char path[MAX_PATH];
1467 strecpy(path, directory, lastof(path));
1468 if (!AppendPathSeparator(path, lastof(path))) return 0;
1469 return ScanPath(this, extension, path, strlen(path), recursive);