Remove second template parameter from class GUIList
[openttd/fttd.git] / src / fileio.cpp
blobafd515d309dbd25d70176879c301243eec64c609
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 /* FALL THROUGH */
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 (sstring<MAX_PATH> *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 bool success = false;
1000 #ifdef WITH_COCOA
1001 char *app_bundle = strchr(exe, '.');
1002 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1004 if (app_bundle != NULL) app_bundle[0] = '\0';
1005 #endif /* WITH_COCOA */
1006 char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
1007 if (s != NULL) {
1008 *s = '\0';
1009 #if defined(__DJGPP__)
1010 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1011 if (s[-1] == ':') chdir("/");
1012 #endif
1013 if (chdir(exe) != 0) {
1014 DEBUG(misc, 0, "Directory with the binary does not exist?");
1015 } else {
1016 success = true;
1018 *s = PATHSEPCHAR;
1020 #ifdef WITH_COCOA
1021 if (app_bundle != NULL) app_bundle[0] = '.';
1022 #endif /* WITH_COCOA */
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 static 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 sstring<MAX_PATH> tmp;
1048 tmp.fmt ("%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1049 AppendPathSeparator (&tmp);
1050 return strcmp (tmp.c_str(), _searchpaths[SP_PERSONAL_DIR]) != 0;
1053 /** strdup the current working directory, with a trailing path separator. */
1054 static char *dupcwd (void)
1056 char tmp [MAX_PATH];
1057 if (getcwd (tmp, MAX_PATH) == NULL) *tmp = '\0';
1058 return BuildDirPath (tmp);
1062 * Determine the base (personal dir and game data dir) paths
1063 * @param exe the path to the executable
1065 static void DetermineBasePaths(const char *exe)
1067 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1068 const char *xdg_data_home = xdgDataHome(NULL);
1069 _searchpaths[SP_PERSONAL_DIR_XDG] = BuildDirPath (xdg_data_home,
1070 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1071 free(xdg_data_home);
1072 #endif
1073 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1074 _searchpaths[SP_PERSONAL_DIR] = NULL;
1075 #else
1076 #ifdef __HAIKU__
1077 BPath path;
1078 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1079 char *homedir = xstrdup(path.Path());
1080 #else
1081 /* getenv is highly unsafe; duplicate it as soon as possible,
1082 * or at least before something else touches the environment
1083 * variables in any way. It can also contain all kinds of
1084 * unvalidated data we rather not want internally. */
1085 char *homedir = getenv("HOME");
1086 if (homedir != NULL) {
1087 homedir = xstrdup (homedir);
1088 } else {
1089 const struct passwd *pw = getpwuid(getuid());
1090 homedir = (pw == NULL) ? NULL : xstrdup(pw->pw_dir);
1092 #endif
1094 if (homedir != NULL) {
1095 ValidateString(homedir);
1096 _searchpaths[SP_PERSONAL_DIR] = BuildDirPath (homedir, PERSONAL_DIR);
1097 free(homedir);
1098 } else {
1099 _searchpaths[SP_PERSONAL_DIR] = NULL;
1101 #endif
1103 #if defined(WITH_SHARED_DIR)
1104 _searchpaths[SP_SHARED_DIR] = BuildDirPath (SHARED_DIR);
1105 #else
1106 _searchpaths[SP_SHARED_DIR] = NULL;
1107 #endif
1109 #if defined(__MORPHOS__) || defined(__AMIGA__)
1110 _searchpaths[SP_WORKING_DIR] = NULL;
1111 #else
1112 _searchpaths[SP_WORKING_DIR] = dupcwd();
1113 #endif
1115 _do_scan_working_directory = DoScanWorkingDirectory();
1117 /* Change the working directory to that one of the executable */
1118 if (ChangeWorkingDirectoryToExecutable(exe)) {
1119 _searchpaths[SP_BINARY_DIR] = dupcwd();
1120 } else {
1121 _searchpaths[SP_BINARY_DIR] = NULL;
1124 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1125 /* Go back to the current working directory. */
1126 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1127 DEBUG(misc, 0, "Failed to return to working directory!");
1131 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1132 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1133 #else
1134 _searchpaths[SP_INSTALLATION_DIR] = BuildDirPath (GLOBAL_DATA_DIR);
1135 #endif
1136 #ifdef WITH_COCOA
1137 extern void cocoaSetApplicationBundleDir();
1138 cocoaSetApplicationBundleDir();
1139 #else
1140 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1141 #endif
1143 #endif /* defined(WIN32) || defined(WINCE) */
1145 const char *_personal_dir;
1148 * Acquire the base paths (personal dir and game data dir),
1149 * fill all other paths (save dir, autosave dir etc) and
1150 * make the save and scenario directories.
1151 * @param exe the path from the current path to the executable
1153 void DeterminePaths(const char *exe)
1155 DetermineBasePaths(exe);
1157 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1158 sstring<MAX_PATH> config_home;
1159 #endif
1161 Searchpath sp;
1162 FOR_ALL_SEARCHPATHS(sp) {
1163 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1164 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1167 const char *config_dir;
1168 if (_config_file != NULL) {
1169 char *end = strrchr(_config_file, PATHSEPCHAR);
1170 config_dir = (end == NULL) ? "" : xstrmemdup (_config_file, end - _config_file);
1171 } else {
1172 char personal_dir[MAX_PATH];
1173 if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
1174 char *end = strrchr(personal_dir, PATHSEPCHAR);
1175 if (end != NULL) end[1] = '\0';
1176 config_dir = xstrdup(personal_dir);
1177 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1178 } else {
1179 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1180 /* No previous configuration file found. Use the configuration folder from XDG. */
1181 char *xdg_config_home = xdgConfigHome (NULL);
1182 config_home.fmt ("%s" PATHSEP "%s", xdg_config_home,
1183 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1184 free (xdg_config_home);
1186 AppendPathSeparator (&config_home);
1188 config_dir = config_home.c_str();
1189 #else
1190 static const Searchpath new_openttd_cfg_order[] = {
1191 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1194 config_dir = NULL;
1195 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1196 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1197 config_dir = xstrdup(_searchpaths[new_openttd_cfg_order[i]]);
1198 break;
1201 assert(config_dir != NULL);
1202 #endif
1203 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1207 DEBUG(misc, 3, "%s found as config directory", config_dir);
1209 _highscore_file = str_fmt("%shs.dat", config_dir);
1210 extern char *_hotkeys_file;
1211 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1212 extern char *_windows_file;
1213 _windows_file = str_fmt("%swindows.cfg", config_dir);
1215 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1216 if (config_dir == config_home.c_str()) {
1217 /* We are using the XDG configuration home for the config file,
1218 * then store the rest in the XDG data home folder. */
1219 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1220 FioCreateDirectory(_personal_dir);
1221 } else
1222 #endif
1224 _personal_dir = config_dir;
1227 /* Make the necessary folders */
1228 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1229 FioCreateDirectory(config_dir);
1230 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1231 #endif
1233 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1235 static const Subdirectory default_subdirs[] = {
1236 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1239 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1240 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1241 FioCreateDirectory(dir);
1242 free(dir);
1245 /* If we have network we make a directory for the autodownloading of content */
1246 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1247 #ifdef ENABLE_NETWORK
1248 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1250 /* Create the directory for each of the types of content */
1251 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1252 for (uint i = 0; i < lengthof(dirs); i++) {
1253 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1254 FioCreateDirectory(tmp);
1255 free(tmp);
1258 extern char *_log_file;
1259 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1260 #else /* ENABLE_NETWORK */
1261 /* If we don't have networking, we don't need to make the directory. But
1262 * if it exists we keep it, otherwise remove it from the search paths. */
1263 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1264 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1265 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1267 #endif /* ENABLE_NETWORK */
1271 * Sanitizes a filename, i.e. removes all illegal characters from it.
1272 * @param filename the "\0" terminated filename
1274 void SanitizeFilename(char *filename)
1276 for (; *filename != '\0'; filename++) {
1277 switch (*filename) {
1278 /* The following characters are not allowed in filenames
1279 * on at least one of the supported operating systems: */
1280 case ':': case '\\': case '*': case '?': case '/':
1281 case '<': case '>': case '|': case '"':
1282 *filename = '_';
1283 break;
1289 * Load a file into memory.
1290 * @param filename Name of the file to load.
1291 * @param lenp [out] Length of loaded data.
1292 * @param maxsize Maximum size to load.
1293 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1294 * @note If \a maxsize less than the length of the file, loading fails.
1296 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1298 FILE *in = fopen(filename, "rb");
1299 if (in == NULL) return NULL;
1301 fseek(in, 0, SEEK_END);
1302 size_t len = ftell(in);
1303 fseek(in, 0, SEEK_SET);
1304 if (len > maxsize) {
1305 fclose(in);
1306 return NULL;
1308 byte *mem = xmalloct<byte>(len + 1);
1309 mem[len] = 0;
1310 if (fread(mem, len, 1, in) != 1) {
1311 fclose(in);
1312 free(mem);
1313 return NULL;
1315 fclose(in);
1317 *lenp = len;
1318 return mem;
1322 * Helper to see whether a given filename matches the extension.
1323 * @param extension The extension to look for.
1324 * @param filename The filename to look in for the extension.
1325 * @return True iff the extension is NULL, or the filename ends with it.
1327 static bool MatchesExtension(const char *extension, const char *filename)
1329 if (extension == NULL) return true;
1331 const char *ext = strrchr(filename, extension[0]);
1332 return ext != NULL && strcasecmp(ext, extension) == 0;
1336 * Scan a single directory (and recursively its children) and add
1337 * any graphics sets that are found.
1338 * @param fs the file scanner to add the files to
1339 * @param extension the extension of files to search for.
1340 * @param path full path we're currently at
1341 * @param basepath_length from where in the path are we 'based' on the search path
1342 * @param recursive whether to recursively search the sub directories
1344 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1346 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1348 uint num = 0;
1349 struct stat sb;
1350 struct dirent *dirent;
1351 DIR *dir;
1353 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1355 while ((dirent = readdir(dir)) != NULL) {
1356 const char *d_name = FS2OTTD(dirent->d_name);
1357 sstring<MAX_PATH> filename;
1359 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1361 filename.fmt ("%s%s", path, d_name);
1363 if (S_ISDIR(sb.st_mode)) {
1364 /* Directory */
1365 if (!recursive) continue;
1366 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1367 if (!AppendPathSeparator (&filename)) continue;
1368 num += ScanPath (fs, extension, filename.c_str(), basepath_length, recursive);
1369 } else if (S_ISREG(sb.st_mode)) {
1370 /* File */
1371 if (MatchesExtension (extension, filename.c_str()) && fs->AddFile (filename.c_str(), basepath_length, NULL)) num++;
1375 closedir(dir);
1377 return num;
1381 * Scan the given tar and add graphics sets when it finds one.
1382 * @param fs the file scanner to scan for
1383 * @param extension the extension of files to search for.
1384 * @param tar the tar to search in.
1386 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1388 uint num = 0;
1389 const char *filename = (*tar).first.c_str();
1391 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1393 return num;
1397 * Scan for files with the given extension in the given search path.
1398 * @param extension the extension of files to search for.
1399 * @param sd the sub directory to search in.
1400 * @param tars whether to search in the tars too.
1401 * @param recursive whether to search recursively
1402 * @return the number of found files, i.e. the number of times that
1403 * AddFile returned true.
1405 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1407 this->subdir = sd;
1409 Searchpath sp;
1410 char path[MAX_PATH];
1411 TarFileList::iterator tar;
1412 uint num = 0;
1414 FOR_ALL_SEARCHPATHS(sp) {
1415 /* Don't search in the working directory */
1416 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1418 FioGetFullPath (path, MAX_PATH, sp, sd);
1419 num += ScanPath(this, extension, path, strlen(path), recursive);
1422 if (tars && sd != NO_DIRECTORY) {
1423 FOR_ALL_TARS(tar, sd) {
1424 num += ScanTar(this, extension, tar);
1428 switch (sd) {
1429 case BASESET_DIR:
1430 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1431 /* FALL THROUGH */
1432 case NEWGRF_DIR:
1433 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1434 break;
1436 default: break;
1439 return num;
1443 * Scan for files with the given extension in the given search path.
1444 * @param extension the extension of files to search for.
1445 * @param directory the sub directory to search in.
1446 * @param dirend if not null, end directory here
1447 * @param recursive whether to search recursively
1448 * @return the number of found files, i.e. the number of times that
1449 * AddFile returned true.
1451 uint FileScanner::Scan (const char *extension, const char *directory,
1452 const char *dirend, bool recursive)
1454 sstring<MAX_PATH> path;
1455 if (dirend == NULL) {
1456 path.copy (directory);
1457 } else {
1458 path.fmt ("%.*s", (int)(dirend - directory), directory);
1460 if (!AppendPathSeparator (&path)) return 0;
1461 return ScanPath (this, extension, path.c_str(), path.length(), recursive);