Factor out OnClick dispatching in Window::HandleEditBoxKey
[openttd/fttd.git] / src / fileio.cpp
blob664d90ebaf3ace6ca94568fdb2a39f99636e9862
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.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 /** 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] == 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)
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 != NULL);
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] != 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)
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] != NULL && _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 == NULL) 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] = xstrdup(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;
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];
299 TarCache TarCache::cache [NUM_SUBDIRS];
302 * Check whether the given file exists
303 * @param filename the file to try for existence.
304 * @param subdir the subdirectory to look in
305 * @return true if and only if the file can be opened
307 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
309 FILE *f = FioFOpenFile(filename, "rb", subdir);
310 if (f == NULL) return false;
312 FioFCloseFile(f);
313 return true;
317 * Test whether the given filename exists.
318 * @param filename the file to test.
319 * @return true if and only if the file exists.
321 bool FileExists(const char *filename)
323 #if defined(WINCE)
324 /* There is always one platform that doesn't support basic commands... */
325 HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
326 if (hand == INVALID_HANDLE_VALUE) return 1;
327 CloseHandle(hand);
328 return 0;
329 #else
330 return access(OTTD2FS(filename), 0) == 0;
331 #endif
335 * Close a file in a safe way.
337 void FioFCloseFile(FILE *f)
339 fclose(f);
342 int FioGetFullPath (char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
344 assert(subdir < NUM_SUBDIRS);
345 assert(sp < NUM_SEARCHPATHS);
347 return snprintf (buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename != NULL ? filename : "");
351 * Find a path to the filename in one of the search directories.
352 * @param buf [out] Destination buffer for the path.
353 * @param buflen Length of the destination buffer.
354 * @param subdir Subdirectory to try.
355 * @param filename Filename to look for.
356 * @return \a buf containing the path if the path was found, else \c NULL.
358 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
360 Searchpath sp;
361 assert(subdir < NUM_SUBDIRS);
363 FOR_ALL_SEARCHPATHS(sp) {
364 FioGetFullPath(buf, buflen, sp, subdir, filename);
365 if (FileExists(buf)) return buf;
366 #if !defined(WIN32)
367 /* Be, as opening files, aware that sometimes the filename
368 * might be in uppercase when it is in lowercase on the
369 * disk. Of course Windows doesn't care about casing. */
370 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
371 #endif
374 return NULL;
377 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
379 Searchpath sp;
381 /* Find and return the first valid directory */
382 FOR_ALL_SEARCHPATHS(sp) {
383 FioGetFullPath (buf, buflen, sp, subdir);
384 if (FileExists(buf)) return buf;
387 /* Could not find the directory, fall back to a base path */
388 ttd_strlcpy(buf, _personal_dir, buflen);
390 return buf;
393 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
395 #if defined(WIN32) && defined(UNICODE)
396 /* fopen is implemented as a define with ellipses for
397 * Unicode support (prepend an L). As we are not sending
398 * a string, but a variable, it 'renames' the variable,
399 * so make that variable to makes it compile happily */
400 wchar_t Lmode[5];
401 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
402 #endif
403 FILE *f = NULL;
404 char buf[MAX_PATH];
406 if (subdir == NO_DIRECTORY) {
407 bstrcpy (buf, filename);
408 } else {
409 bstrfmt (buf, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
412 #if defined(WIN32)
413 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
414 #endif
416 f = fopen(buf, mode);
417 #if !defined(WIN32)
418 if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
419 f = fopen(buf, mode);
421 #endif
422 if (f != NULL && filesize != NULL) {
423 /* Find the size of the file */
424 fseek(f, 0, SEEK_END);
425 *filesize = ftell(f);
426 fseek(f, 0, SEEK_SET);
428 return f;
432 * Opens a file from inside a tar archive.
433 * @param entry The entry to open.
434 * @param filesize [out] If not \c NULL, size of the opened file.
435 * @return File handle of the opened file, or \c NULL if the file is not available.
436 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
438 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
440 FILE *f = fopen(entry->tar_filename, "rb");
441 if (f == NULL) return f;
443 if (fseek(f, entry->position, SEEK_SET) < 0) {
444 fclose(f);
445 return NULL;
448 if (filesize != NULL) *filesize = entry->size;
449 return f;
453 * Opens a OpenTTD file somewhere in a personal or global directory.
454 * @param filename Name of the file to open.
455 * @param subdir Subdirectory to open.
456 * @param filename Name of the file to open.
457 * @return File handle of the opened file, or \c NULL if the file is not available.
459 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
461 FILE *f = NULL;
462 Searchpath sp;
464 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
466 FOR_ALL_SEARCHPATHS(sp) {
467 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
468 if (f != NULL || subdir == NO_DIRECTORY) break;
471 /* We can only use .tar in case of data-dir, and read-mode */
472 if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
473 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
474 sstring<MAX_RESOLVED_LENGTH> resolved_name;
476 /* Filenames in tars are always forced to be lowercase */
477 resolved_name.copy (filename);
478 resolved_name.tolower();
480 /* Resolve ONE directory link */
481 for (TarLinkList::iterator link = TarCache::cache[subdir].links.begin(); link != TarCache::cache[subdir].links.end(); link++) {
482 const std::string &src = link->first;
483 size_t len = src.length();
484 if (resolved_name.length() >= len && resolved_name.c_str()[len - 1] == PATHSEPCHAR && memcmp (src.c_str(), resolved_name.c_str(), len) == 0) {
485 /* Apply link */
486 char resolved_name2[MAX_RESOLVED_LENGTH];
487 bstrcpy (resolved_name2, resolved_name.c_str() + len);
488 resolved_name.copy (link->second.c_str());
489 resolved_name.append (resolved_name2);
490 break; // Only resolve one level
494 TarFileList::iterator it = TarCache::cache[subdir].files.find(resolved_name.c_str());
495 if (it != TarCache::cache[subdir].files.end()) {
496 f = FioFOpenFileTar(&((*it).second), filesize);
500 /* Sometimes a full path is given. To support
501 * the 'subdirectory' must be 'removed'. */
502 if (f == NULL && subdir != NO_DIRECTORY) {
503 switch (subdir) {
504 case BASESET_DIR:
505 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
506 if (f != NULL) break;
507 FALLTHROUGH;
508 case NEWGRF_DIR:
509 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
510 break;
512 default:
513 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
514 break;
518 return f;
522 * Create a directory with the given name
523 * @param name the new name of the directory
525 static void FioCreateDirectory(const char *name)
527 /* Ignore directory creation errors; they'll surface later on, and most
528 * of the time they are 'directory already exists' errors anyhow. */
529 #if defined(WIN32) || defined(WINCE)
530 CreateDirectory(OTTD2FS(name), NULL);
531 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
532 mkdir(OTTD2FS(name));
533 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
534 char buf[MAX_PATH];
535 bstrcpy (buf, name);
537 size_t len = strlen(name) - 1;
538 if (buf[len] == '/') {
539 buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
542 mkdir(OTTD2FS(buf), 0755);
543 #else
544 mkdir(OTTD2FS(name), 0755);
545 #endif
549 * Appends, if necessary, the path separator character to the end of the string.
550 * It does not add the path separator to zero-sized strings.
551 * @param buf string to append the separator to
552 * @return true iff the operation succeeded
554 static bool AppendPathSeparator (stringb *buf)
556 size_t s = buf->length();
558 return (s == 0) || (buf->get_buffer()[s-1] == PATHSEPCHAR)
559 || buf->append (PATHSEPCHAR);
563 * Construct a path by concatenating the given parts with intervening and
564 * trailing path separators, and return it in an newly allocated buffer.
565 * @param n the number of components to concatenate
566 * @param parts the strings to concatenate
567 * @return the resulting path, in a buffer allocated with malloc
569 char *BuildDirPath (uint n, const char *const *parts)
571 /* some sanity checks */
572 assert (n > 0);
573 assert (parts[0][0] != '\0'); // the first string must not be empty
575 size_t lengths [4];
576 /* change and recompile if you need something bigger */
577 assert (n <= lengthof(lengths));
579 size_t total = n + 1; // account for path separators and trailing null
580 for (uint i = 0; i < n; i++) {
581 total += lengths[i] = strlen (parts[i]);
584 char *buf = xmalloc (total);
585 char *p = buf;
587 for (uint i = 0; i < n; i++) {
588 assert (p + lengths[i] + 1 < buf + total);
589 memcpy (p, parts[i], lengths[i]);
590 p += lengths[i];
591 /* the first string is not empty, so p > buf */
592 if (p[-1] != PATHSEPCHAR) *p++ = PATHSEPCHAR;
595 *p = '\0';
596 return buf;
600 void TarCache::add_link (const std::string &srcp, const std::string &destp)
602 std::string src = srcp;
603 std::string dest = destp;
604 /* Tar internals assume lowercase */
605 std::transform(src.begin(), src.end(), src.begin(), tolower);
606 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
608 TarFileList::iterator dest_file = this->files.find(dest);
609 if (dest_file != this->files.end()) {
610 /* Link to file. Process the link like the destination file. */
611 this->files.insert (TarFileList::value_type (src, dest_file->second));
612 } else {
613 /* Destination file not found. Assume 'link to directory'
614 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
615 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
616 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
617 this->links.insert (TarLinkList::value_type (src_path, dst_path));
622 * Simplify filenames from tars.
623 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
624 * @param name Filename to process.
626 static void SimplifyFileName(char *name)
628 /* Force lowercase */
629 strtolower(name);
631 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
632 #if (PATHSEPCHAR != '/')
633 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
634 #endif
638 * Perform the scanning of a particular subdirectory.
639 * @param subdir The subdirectory to scan.
640 * @return The number of found tar files.
642 uint TarScanner::DoScan(Subdirectory sd)
644 TarCache::cache[sd].files.clear();
645 TarCache::cache[sd].tars.clear();
646 uint num = this->Scan (sd);
647 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan (OLD_DATA_DIR);
648 return num;
651 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
653 DEBUG(misc, 1, "Scanning for tars");
654 TarScanner fs;
655 uint num = 0;
656 if (mode & TarScanner::BASESET) {
657 num += fs.DoScan(BASESET_DIR);
659 if (mode & TarScanner::NEWGRF) {
660 num += fs.DoScan(NEWGRF_DIR);
662 if (mode & TarScanner::AI) {
663 num += fs.DoScan(AI_DIR);
664 num += fs.DoScan(AI_LIBRARY_DIR);
666 if (mode & TarScanner::GAME) {
667 num += fs.DoScan(GAME_DIR);
668 num += fs.DoScan(GAME_LIBRARY_DIR);
670 if (mode & TarScanner::SCENARIO) {
671 num += fs.DoScan(SCENARIO_DIR);
672 num += fs.DoScan(HEIGHTMAP_DIR);
674 DEBUG(misc, 1, "Scan complete, found %d files", num);
675 return num;
679 * Add a scanned file to the scanned files of a tar.
680 * @param filename the full path to the file to read
681 * @param basepath_length amount of characters to chop of before to get a
682 * filename relative to the search path.
683 * @param tar_filename the name of the tar file the file is read from.
684 * @return true if the file is added.
686 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
688 /* No tar within tar. */
689 assert(tar_filename == NULL);
691 return TarCache::cache[this->subdir].add (filename, basepath_length);
695 * Add a scanned file to a tar cache.
696 * @param filename the full path to the file to read
697 * @param basepath_length amount of characters to chop of before to get a
698 * filename relative to the search path.
699 * @return true if the file is added.
701 bool TarCache::add (const char *filename, size_t basepath_length)
703 /* The TAR-header, repeated for every file */
704 struct TarHeader {
705 char name[100]; ///< Name of the file
706 char mode[8];
707 char uid[8];
708 char gid[8];
709 char size[12]; ///< Size of the file, in ASCII
710 char mtime[12];
711 char chksum[8];
712 char typeflag;
713 char linkname[100];
714 char magic[6];
715 char version[2];
716 char uname[32];
717 char gname[32];
718 char devmajor[8];
719 char devminor[8];
720 char prefix[155]; ///< Path of the file
722 char unused[12];
725 assert_compile (sizeof(TarHeader) == 512);
727 /* Check if we already seen this file */
728 TarList::iterator it = this->tars.find(filename);
729 if (it != this->tars.end()) return false;
731 FILE *f = fopen(filename, "rb");
732 /* Although the file has been found there can be
733 * a number of reasons we cannot open the file.
734 * Most common case is when we simply have not
735 * been given read access. */
736 if (f == NULL) return false;
738 char *dupped_filename = xstrdup(filename);
739 this->tars[filename].filename.reset (dupped_filename);
740 this->tars[filename].dirname.reset();
742 TarLinkList links; ///< Temporary list to collect links
743 size_t num = 0, pos = 0;
745 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
746 TarHeader th;
747 if (fread (&th, 1, 512, f) != 512) break;
748 pos += 512;
750 /* Check if we have the new tar-format (ustar). */
751 if (strncmp(th.magic, "ustar", 5) != 0) {
752 /* Check if we have the old one (header zeroed after 'link' field). */
753 if ((th.magic[0] != '\0') || (memcmp (&th.magic[0], &th.magic[1], 511 - offsetof(TarHeader, magic)) != 0)) {
754 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
755 fclose(f);
756 return false;
760 /* The prefix contains the directory-name */
761 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
762 if (th.prefix[0] != '\0') {
763 bstrfmt (name, "%.*s" PATHSEP "%.*s",
764 (int)sizeof(th.prefix), th.prefix,
765 (int)sizeof(th.name), th.name);
766 } else {
767 bstrfmt (name, "%.*s", (int)sizeof(th.name), th.name);
770 /* Calculate the size of the file.. for some strange reason this is stored as a string */
771 char buf[sizeof(th.size) + 1];
772 bstrcpy (buf, th.size);
773 size_t skip = strtoul(buf, NULL, 8);
775 switch (th.typeflag) {
776 case '\0':
777 case '0': { // regular file
778 /* Ignore empty files */
779 if (skip == 0) break;
781 if (strlen(name) == 0) break;
783 /* Store this entry in the list */
784 TarFileListEntry entry;
785 entry.tar_filename = dupped_filename;
786 entry.size = skip;
787 entry.position = pos;
789 /* Convert to lowercase and our PATHSEPCHAR */
790 SimplifyFileName(name);
792 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
793 if (this->files.insert(TarFileList::value_type(name, entry)).second) num++;
795 break;
798 case '1': // hard links
799 case '2': { // symbolic links
800 /* Copy the destination of the link in a safe way at the end of 'linkname' */
801 char link[sizeof(th.linkname) + 1];
802 bstrcpy (link, th.linkname);
804 if (strlen(name) == 0 || strlen(link) == 0) break;
806 /* Convert to lowercase and our PATHSEPCHAR */
807 SimplifyFileName(name);
808 SimplifyFileName(link);
810 /* Only allow relative links */
811 if (link[0] == PATHSEPCHAR) {
812 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
813 break;
816 /* Process relative path.
817 * Note: The destination of links must not contain any directory-links. */
818 char dest[sizeof(name) + 1 + sizeof(th.linkname) + 1];
819 bstrcpy (dest, name);
820 char *destpos = strrchr(dest, PATHSEPCHAR);
821 if (destpos == NULL) destpos = dest;
822 *destpos = '\0';
824 const char *pos = link;
825 for (;;) {
826 const char *next = strchr (pos, PATHSEPCHAR);
827 if (next == NULL) next = pos + strlen(pos);
829 /* Skip '.' (current dir) */
830 if (next != pos + 1 || pos[0] != '.') {
831 if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
832 /* level up */
833 if (dest[0] == '\0') {
834 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
835 break;
838 /* Truncate 'dest' after last PATHSEPCHAR.
839 * This assumes that the truncated part is a real directory and not a link. */
840 destpos = strrchr(dest, PATHSEPCHAR);
841 if (destpos == NULL) destpos = dest;
842 } else {
843 /* Append at end of 'dest' */
844 if (destpos != dest) *(destpos++) = PATHSEPCHAR;
845 uint len = next - pos;
846 assert (destpos + len < endof(dest));
847 memcpy (destpos, pos, len); // Safe as we do '\0'-termination ourselves
848 destpos += next - pos;
850 *destpos = '\0';
853 if (*next == '\0') break;
855 pos = next + 1;
858 /* Store links in temporary list */
859 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
860 links.insert(TarLinkList::value_type(name, dest));
862 break;
865 case '5': // directory
866 /* Convert to lowercase and our PATHSEPCHAR */
867 SimplifyFileName(name);
869 /* Store the first directory name we detect */
870 DEBUG(misc, 6, "Found dir in tar: %s", name);
871 if (!this->tars[filename].dirname) {
872 this->tars[filename].dirname.reset (xstrdup(name));
874 break;
876 default:
877 /* Ignore other types */
878 break;
881 /* Skip to the next block.. */
882 skip = Align(skip, 512);
883 if (fseek(f, skip, SEEK_CUR) < 0) {
884 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
885 fclose(f);
886 return false;
888 pos += skip;
891 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
892 fclose(f);
894 /* Resolve file links and store directory links.
895 * We restrict usage of links to two cases:
896 * 1) Links to directories:
897 * Both the source path and the destination path must NOT contain any further links.
898 * When resolving files at most one directory link is resolved.
899 * 2) Links to files:
900 * The destination path must NOT contain any links.
901 * The source path may contain one directory link.
903 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
904 this->add_link (link->first, link->second);
907 return true;
911 * Extract the tar with the given filename in the directory
912 * where the tar resides.
913 * @param tar_filename the name of the tar to extract.
914 * @return false on failure.
916 bool TarCache::extract (const char *tar_filename)
918 TarList::iterator it = this->tars.find(tar_filename);
919 /* We don't know the file. */
920 if (it == this->tars.end()) return false;
922 /* The file doesn't have a sub directory! */
923 if (!(*it).second.dirname) return false;
925 const char *p = strrchr (tar_filename, PATHSEPCHAR);
926 /* The file's path does not have a separator? */
927 if (p == NULL) return false;
928 size_t base_length = p - tar_filename + 1;
930 sstring<MAX_PATH> filename;
931 filename.fmt ("%.*s%s", (int)base_length, tar_filename, (*it).second.dirname.get());
932 DEBUG (misc, 8, "Extracting %s to directory %s", tar_filename, filename.c_str());
933 FioCreateDirectory (filename.c_str());
935 for (TarFileList::iterator it2 = this->files.begin(); it2 != this->files.end(); it2++) {
936 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
938 filename.truncate (base_length);
939 filename.append ((*it2).first.c_str());
941 DEBUG(misc, 9, " extracting %s", filename.c_str());
943 /* First open the file in the .tar. */
944 size_t to_copy = 0;
945 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
946 if (in == NULL) {
947 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), tar_filename);
948 return false;
951 /* Now open the 'output' file. */
952 FILE *out = fopen (filename.c_str(), "wb");
953 if (out == NULL) {
954 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), filename.c_str());
955 fclose(in);
956 return false;
959 /* Now read from the tar and write it into the file. */
960 char buffer[4096];
961 size_t read;
962 for (; to_copy != 0; to_copy -= read) {
963 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
964 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
967 /* Close everything up. */
968 fclose(in);
969 fclose(out);
971 if (to_copy != 0) {
972 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename.c_str(), (int)to_copy);
973 return false;
977 DEBUG(misc, 9, " extraction successful");
978 return true;
981 #if defined(WIN32) || defined(WINCE)
983 * Determine the base (personal dir and game data dir) paths
984 * @param exe the path from the current path to the executable
985 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
987 extern void DetermineBasePaths(const char *exe);
988 #else /* defined(WIN32) || defined(WINCE) */
991 * Changes the working directory to the path of the give executable.
992 * For OSX application bundles '.app' is the required extension of the bundle,
993 * so when we crop the path to there, when can remove the name of the bundle
994 * in the same way we remove the name from the executable name.
995 * @param exe the path to the executable
997 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
999 char tmp[MAX_PATH];
1000 bstrcpy (tmp, exe);
1002 bool success = false;
1003 #ifdef WITH_COCOA
1004 char *app_bundle = strchr(tmp, '.');
1005 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1007 if (app_bundle != NULL) *app_bundle = '\0';
1008 #endif /* WITH_COCOA */
1009 char *s = strrchr(tmp, PATHSEPCHAR);
1010 if (s != NULL) {
1011 *s = '\0';
1012 #if defined(__DJGPP__)
1013 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1014 if (s > tmp && *(s - 1) == ':') chdir("/");
1015 #endif
1016 if (chdir(tmp) != 0) {
1017 DEBUG(misc, 0, "Directory with the binary does not exist?");
1018 } else {
1019 success = true;
1022 return success;
1026 * Whether we should scan the working directory.
1027 * It should not be scanned if it's the root or
1028 * the home directory as in both cases a big data
1029 * directory can cause huge amounts of unrelated
1030 * files scanned. Furthermore there are nearly no
1031 * use cases for the home/root directory to have
1032 * OpenTTD directories.
1033 * @return true if it should be scanned.
1035 static bool DoScanWorkingDirectory()
1037 /* No working directory, so nothing to do. */
1038 if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
1040 /* Working directory is root, so do nothing. */
1041 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1043 /* No personal/home directory, so the working directory won't be that. */
1044 if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
1046 sstring<MAX_PATH> tmp;
1047 tmp.fmt ("%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1048 AppendPathSeparator (&tmp);
1049 return strcmp (tmp.c_str(), _searchpaths[SP_PERSONAL_DIR]) != 0;
1052 /** strdup the current working directory, with a trailing path separator. */
1053 static char *dupcwd (void)
1055 char tmp [MAX_PATH];
1056 if (getcwd (tmp, MAX_PATH) == NULL) *tmp = '\0';
1057 return BuildDirPath (tmp);
1061 * Determine the base (personal dir and game data dir) paths
1062 * @param exe the path to the executable
1064 static void DetermineBasePaths(const char *exe)
1066 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1067 const char *xdg_data_home = xdgDataHome(NULL);
1068 _searchpaths[SP_PERSONAL_DIR_XDG] = BuildDirPath (xdg_data_home,
1069 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1070 free(xdg_data_home);
1071 #endif
1072 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1073 _searchpaths[SP_PERSONAL_DIR] = NULL;
1074 #else
1075 #ifdef __HAIKU__
1076 BPath path;
1077 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1078 char *homedir = xstrdup(path.Path());
1079 #else
1080 /* getenv is highly unsafe; duplicate it as soon as possible,
1081 * or at least before something else touches the environment
1082 * variables in any way. It can also contain all kinds of
1083 * unvalidated data we rather not want internally. */
1084 char *homedir = getenv("HOME");
1085 if (homedir != NULL) {
1086 homedir = xstrdup (homedir);
1087 } else {
1088 const struct passwd *pw = getpwuid(getuid());
1089 homedir = (pw == NULL) ? NULL : xstrdup(pw->pw_dir);
1091 #endif
1093 if (homedir != NULL) {
1094 ValidateString(homedir);
1095 _searchpaths[SP_PERSONAL_DIR] = BuildDirPath (homedir, PERSONAL_DIR);
1096 free(homedir);
1097 } else {
1098 _searchpaths[SP_PERSONAL_DIR] = NULL;
1100 #endif
1102 #if defined(WITH_SHARED_DIR)
1103 _searchpaths[SP_SHARED_DIR] = BuildDirPath (SHARED_DIR);
1104 #else
1105 _searchpaths[SP_SHARED_DIR] = NULL;
1106 #endif
1108 #if defined(__MORPHOS__) || defined(__AMIGA__)
1109 _searchpaths[SP_WORKING_DIR] = NULL;
1110 #else
1111 _searchpaths[SP_WORKING_DIR] = dupcwd();
1112 #endif
1114 _do_scan_working_directory = DoScanWorkingDirectory();
1116 /* Change the working directory to that one of the executable */
1117 if (ChangeWorkingDirectoryToExecutable(exe)) {
1118 _searchpaths[SP_BINARY_DIR] = dupcwd();
1119 } else {
1120 _searchpaths[SP_BINARY_DIR] = NULL;
1123 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1124 /* Go back to the current working directory. */
1125 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1126 DEBUG(misc, 0, "Failed to return to working directory!");
1130 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1131 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1132 #else
1133 _searchpaths[SP_INSTALLATION_DIR] = BuildDirPath (GLOBAL_DATA_DIR);
1134 #endif
1135 #ifdef WITH_COCOA
1136 extern char *cocoaSetApplicationBundleDir();
1137 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = cocoaSetApplicationBundleDir();
1138 #else
1139 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1140 #endif
1142 #endif /* defined(WIN32) || defined(WINCE) */
1144 const char *_personal_dir;
1147 * Acquire the base paths (personal dir and game data dir),
1148 * fill all other paths (save dir, autosave dir etc) and
1149 * make the save and scenario directories.
1150 * @param exe the path from the current path to the executable
1152 void DeterminePaths(const char *exe)
1154 DetermineBasePaths(exe);
1156 Searchpath sp;
1157 FOR_ALL_SEARCHPATHS(sp) {
1158 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1159 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1162 char config_buffer[MAX_PATH];
1163 const char *config_dir;
1164 if (_config_file != NULL) {
1165 char *end = strrchr(_config_file, PATHSEPCHAR);
1166 config_dir = (end == NULL) ? "" : xstrmemdup (_config_file, end - _config_file);
1167 } else {
1168 if (FioFindFullPath (config_buffer, lengthof(config_buffer), BASE_DIR, "openttd.cfg") != NULL) {
1169 char *end = strrchr (config_buffer, PATHSEPCHAR);
1170 if (end != NULL) end[1] = '\0';
1171 config_dir = xstrdup (config_buffer);
1172 } else {
1173 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1174 /* No previous configuration file found. Use the configuration folder from XDG. */
1175 stringb config_home (config_buffer);
1176 const char *xdg_config_home = xdgConfigHome (NULL);
1177 config_home.fmt ("%s" PATHSEP "%s", xdg_config_home,
1178 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1179 free (xdg_config_home);
1181 AppendPathSeparator (&config_home);
1183 config_dir = config_buffer;
1184 #else
1185 static const Searchpath new_openttd_cfg_order[] = {
1186 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1189 for (uint i = 0; ; i++) {
1190 assert (i < lengthof(new_openttd_cfg_order));
1191 config_dir = _searchpaths[new_openttd_cfg_order[i]];
1192 if (config_dir != NULL) break;
1194 #endif
1196 _config_file = str_fmt ("%sopenttd.cfg", config_dir);
1199 DEBUG(misc, 3, "%s found as config directory", config_dir);
1201 _highscore_file = str_fmt("%shs.dat", config_dir);
1202 extern char *_hotkeys_file;
1203 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1204 extern char *_windows_file;
1205 _windows_file = str_fmt("%swindows.cfg", config_dir);
1207 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1208 if (config_dir == config_buffer) {
1209 /* We are using the XDG configuration home for the config file,
1210 * then store the rest in the XDG data home folder. */
1211 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1212 FioCreateDirectory(_personal_dir);
1213 } else
1214 #endif
1216 _personal_dir = config_dir;
1219 /* Make the necessary folders */
1220 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1221 FioCreateDirectory(config_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 #ifdef ENABLE_NETWORK
1239 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1241 /* Create the directory for each of the types of content */
1242 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1243 for (uint i = 0; i < lengthof(dirs); i++) {
1244 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1245 FioCreateDirectory(tmp);
1246 free(tmp);
1249 extern char *_log_file;
1250 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1251 #else /* ENABLE_NETWORK */
1252 /* If we don't have networking, we don't need to make the directory. But
1253 * if it exists we keep it, otherwise remove it from the search paths. */
1254 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1255 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1256 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1258 #endif /* ENABLE_NETWORK */
1262 * Sanitizes a filename, i.e. removes all illegal characters from it.
1263 * @param filename the "\0" terminated filename
1265 void SanitizeFilename(char *filename)
1267 for (; *filename != '\0'; filename++) {
1268 switch (*filename) {
1269 /* The following characters are not allowed in filenames
1270 * on at least one of the supported operating systems: */
1271 case ':': case '\\': case '*': case '?': case '/':
1272 case '<': case '>': case '|': case '"':
1273 *filename = '_';
1274 break;
1280 * Load a file into memory.
1281 * @param filename Name of the file to load.
1282 * @param lenp [out] Length of loaded data.
1283 * @param maxsize Maximum size to load.
1284 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1285 * @note If \a maxsize less than the length of the file, loading fails.
1287 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1289 FILE *in = fopen(filename, "rb");
1290 if (in == NULL) return NULL;
1292 fseek(in, 0, SEEK_END);
1293 size_t len = ftell(in);
1294 fseek(in, 0, SEEK_SET);
1295 if (len > maxsize) {
1296 fclose(in);
1297 return NULL;
1299 byte *mem = xmalloct<byte>(len + 1);
1300 mem[len] = 0;
1301 if (fread(mem, len, 1, in) != 1) {
1302 fclose(in);
1303 free(mem);
1304 return NULL;
1306 fclose(in);
1308 *lenp = len;
1309 return mem;
1313 * Helper to see whether a given filename matches the extension.
1314 * @param extension The extension to look for.
1315 * @param filename The filename to look in for the extension.
1316 * @return True iff the extension is NULL, or the filename ends with it.
1318 static bool MatchesExtension(const char *extension, const char *filename)
1320 if (extension == NULL) return true;
1322 const char *ext = strrchr(filename, extension[0]);
1323 return ext != NULL && strcasecmp(ext, extension) == 0;
1327 * Scan a single directory (and recursively its children) and add
1328 * any graphics sets that are found.
1329 * @param fs the file scanner to add the files to
1330 * @param extension the extension of files to search for.
1331 * @param path full path we're currently at
1332 * @param basepath_length from where in the path are we 'based' on the search path
1333 * @param recursive whether to recursively search the sub directories
1335 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1337 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1339 uint num = 0;
1340 struct stat sb;
1341 struct dirent *dirent;
1342 DIR *dir;
1344 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1346 while ((dirent = readdir(dir)) != NULL) {
1347 const char *d_name = FS2OTTD(dirent->d_name);
1348 sstring<MAX_PATH> filename;
1350 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1352 filename.fmt ("%s%s", path, d_name);
1354 if (S_ISDIR(sb.st_mode)) {
1355 /* Directory */
1356 if (!recursive) continue;
1357 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1358 if (!AppendPathSeparator (&filename)) continue;
1359 num += ScanPath (fs, extension, filename.c_str(), basepath_length, recursive);
1360 } else if (S_ISREG(sb.st_mode)) {
1361 /* File */
1362 if (MatchesExtension (extension, filename.c_str()) && fs->AddFile (filename.c_str(), basepath_length, NULL)) num++;
1366 closedir(dir);
1368 return num;
1372 * Scan the given tar and add graphics sets when it finds one.
1373 * @param fs the file scanner to scan for
1374 * @param extension the extension of files to search for.
1375 * @param tar the tar to search in.
1377 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1379 uint num = 0;
1380 const char *filename = (*tar).first.c_str();
1382 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1384 return num;
1388 * Scan for files with the given extension in the given search path.
1389 * @param extension the extension of files to search for.
1390 * @param sd the sub directory to search in.
1391 * @param tars whether to search in the tars too.
1392 * @param recursive whether to search recursively
1393 * @return the number of found files, i.e. the number of times that
1394 * AddFile returned true.
1396 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1398 this->subdir = sd;
1400 Searchpath sp;
1401 char path[MAX_PATH];
1402 TarFileList::iterator tar;
1403 uint num = 0;
1405 FOR_ALL_SEARCHPATHS(sp) {
1406 /* Don't search in the working directory */
1407 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1409 FioGetFullPath (path, MAX_PATH, sp, sd);
1410 num += ScanPath(this, extension, path, strlen(path), recursive);
1413 if (tars && sd != NO_DIRECTORY) {
1414 FOR_ALL_TARS(tar, sd) {
1415 num += ScanTar(this, extension, tar);
1419 switch (sd) {
1420 case BASESET_DIR:
1421 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1422 FALLTHROUGH;
1423 case NEWGRF_DIR:
1424 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1425 break;
1427 default: break;
1430 return num;
1434 * Scan for files with the given extension in the given search path.
1435 * @param extension the extension of files to search for.
1436 * @param directory the sub directory to search in.
1437 * @param dirend if not null, end directory here
1438 * @param recursive whether to search recursively
1439 * @return the number of found files, i.e. the number of times that
1440 * AddFile returned true.
1442 uint FileScanner::Scan (const char *extension, const char *directory,
1443 const char *dirend, bool recursive)
1445 sstring<MAX_PATH> path;
1446 if (dirend == NULL) {
1447 path.copy (directory);
1448 } else {
1449 path.fmt ("%.*s", (int)(dirend - directory), directory);
1451 if (!AppendPathSeparator (&path)) return 0;
1452 return ScanPath (this, extension, path.c_str(), path.length(), recursive);