(svn r27950) -Merge: Documentation updates from 1.7 branch
[openttd.git] / src / fileio.cpp
blob526f5b184f1cfeedd62ce1120e677035e16ae58b
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 #if defined(WINCE)
329 /* There is always one platform that doesn't support basic commands... */
330 HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
331 if (hand == INVALID_HANDLE_VALUE) return 1;
332 CloseHandle(hand);
333 return 0;
334 #else
335 return access(OTTD2FS(filename), 0) == 0;
336 #endif
340 * Close a file in a safe way.
342 void FioFCloseFile(FILE *f)
344 fclose(f);
347 char *FioGetFullPath(char *buf, const char *last, Searchpath sp, Subdirectory subdir, const char *filename)
349 assert(subdir < NUM_SUBDIRS);
350 assert(sp < NUM_SEARCHPATHS);
352 seprintf(buf, last, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
353 return buf;
357 * Find a path to the filename in one of the search directories.
358 * @param buf [out] Destination buffer for the path.
359 * @param last End of the destination buffer.
360 * @param subdir Subdirectory to try.
361 * @param filename Filename to look for.
362 * @return \a buf containing the path if the path was found, else \c NULL.
364 char *FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
366 Searchpath sp;
367 assert(subdir < NUM_SUBDIRS);
369 FOR_ALL_SEARCHPATHS(sp) {
370 FioGetFullPath(buf, last, sp, subdir, filename);
371 if (FileExists(buf)) return buf;
372 #if !defined(WIN32)
373 /* Be, as opening files, aware that sometimes the filename
374 * might be in uppercase when it is in lowercase on the
375 * disk. Of course Windows doesn't care about casing. */
376 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
377 #endif
380 return NULL;
383 char *FioAppendDirectory(char *buf, const char *last, Searchpath sp, Subdirectory subdir)
385 assert(subdir < NUM_SUBDIRS);
386 assert(sp < NUM_SEARCHPATHS);
388 seprintf(buf, last, "%s%s", _searchpaths[sp], _subdirs[subdir]);
389 return buf;
392 char *FioGetDirectory(char *buf, const char *last, Subdirectory subdir)
394 Searchpath sp;
396 /* Find and return the first valid directory */
397 FOR_ALL_SEARCHPATHS(sp) {
398 char *ret = FioAppendDirectory(buf, last, sp, subdir);
399 if (FileExists(buf)) return ret;
402 /* Could not find the directory, fall back to a base path */
403 strecpy(buf, _personal_dir, last);
405 return buf;
408 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
410 #if defined(WIN32) && defined(UNICODE)
411 /* fopen is implemented as a define with ellipses for
412 * Unicode support (prepend an L). As we are not sending
413 * a string, but a variable, it 'renames' the variable,
414 * so make that variable to makes it compile happily */
415 wchar_t Lmode[5];
416 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
417 #endif
418 FILE *f = NULL;
419 char buf[MAX_PATH];
421 if (subdir == NO_DIRECTORY) {
422 strecpy(buf, filename, lastof(buf));
423 } else {
424 seprintf(buf, lastof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
427 #if defined(WIN32)
428 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
429 #endif
431 f = fopen(buf, mode);
432 #if !defined(WIN32)
433 if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
434 f = fopen(buf, mode);
436 #endif
437 if (f != NULL && filesize != NULL) {
438 /* Find the size of the file */
439 fseek(f, 0, SEEK_END);
440 *filesize = ftell(f);
441 fseek(f, 0, SEEK_SET);
443 return f;
447 * Opens a file from inside a tar archive.
448 * @param entry The entry to open.
449 * @param filesize [out] If not \c NULL, size of the opened file.
450 * @return File handle of the opened file, or \c NULL if the file is not available.
451 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
453 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
455 FILE *f = fopen(entry->tar_filename, "rb");
456 if (f == NULL) return f;
458 if (fseek(f, entry->position, SEEK_SET) < 0) {
459 fclose(f);
460 return NULL;
463 if (filesize != NULL) *filesize = entry->size;
464 return f;
468 * Opens a OpenTTD file somewhere in a personal or global directory.
469 * @param filename Name of the file to open.
470 * @param subdir Subdirectory to open.
471 * @param filename Name of the file to open.
472 * @return File handle of the opened file, or \c NULL if the file is not available.
474 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
476 FILE *f = NULL;
477 Searchpath sp;
479 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
481 FOR_ALL_SEARCHPATHS(sp) {
482 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
483 if (f != NULL || subdir == NO_DIRECTORY) break;
486 /* We can only use .tar in case of data-dir, and read-mode */
487 if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
488 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
489 char resolved_name[MAX_RESOLVED_LENGTH];
491 /* Filenames in tars are always forced to be lowercase */
492 strecpy(resolved_name, filename, lastof(resolved_name));
493 strtolower(resolved_name);
495 size_t resolved_len = strlen(resolved_name);
497 /* Resolve ONE directory link */
498 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
499 const std::string &src = link->first;
500 size_t len = src.length();
501 if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
502 /* Apply link */
503 char resolved_name2[MAX_RESOLVED_LENGTH];
504 const std::string &dest = link->second;
505 strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
506 strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
507 strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
508 break; // Only resolve one level
512 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
513 if (it != _tar_filelist[subdir].end()) {
514 f = FioFOpenFileTar(&((*it).second), filesize);
518 /* Sometimes a full path is given. To support
519 * the 'subdirectory' must be 'removed'. */
520 if (f == NULL && subdir != NO_DIRECTORY) {
521 switch (subdir) {
522 case BASESET_DIR:
523 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
524 if (f != NULL) break;
525 FALLTHROUGH;
526 case NEWGRF_DIR:
527 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
528 break;
530 default:
531 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
532 break;
536 return f;
540 * Create a directory with the given name
541 * @param name the new name of the directory
543 static void FioCreateDirectory(const char *name)
545 /* Ignore directory creation errors; they'll surface later on, and most
546 * of the time they are 'directory already exists' errors anyhow. */
547 #if defined(WIN32) || defined(WINCE)
548 CreateDirectory(OTTD2FS(name), NULL);
549 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
550 mkdir(OTTD2FS(name));
551 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
552 char buf[MAX_PATH];
553 strecpy(buf, name, lastof(buf));
555 size_t len = strlen(name) - 1;
556 if (buf[len] == '/') {
557 buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
560 mkdir(OTTD2FS(buf), 0755);
561 #else
562 mkdir(OTTD2FS(name), 0755);
563 #endif
567 * Appends, if necessary, the path separator character to the end of the string.
568 * It does not add the path separator to zero-sized strings.
569 * @param buf string to append the separator to
570 * @param last the last element of \a buf.
571 * @return true iff the operation succeeded
573 bool AppendPathSeparator(char *buf, const char *last)
575 size_t s = strlen(buf);
577 /* Length of string + path separator + '\0' */
578 if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
579 if (&buf[s] >= last) return false;
581 seprintf(buf + s, last, "%c", PATHSEPCHAR);
584 return true;
588 * Allocates and files a variable with the full path
589 * based on the given directory.
590 * @param dir the directory to base the path on
591 * @return the malloced full path
593 char *BuildWithFullPath(const char *dir)
595 char *dest = MallocT<char>(MAX_PATH);
596 char *last = dest + MAX_PATH - 1;
597 strecpy(dest, dir, last);
599 /* Check if absolute or relative path */
600 const char *s = strchr(dest, PATHSEPCHAR);
602 /* Add absolute path */
603 if (s == NULL || dest != s) {
604 if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
605 AppendPathSeparator(dest, last);
606 strecat(dest, dir, last);
608 AppendPathSeparator(dest, last);
610 return dest;
614 * Find the first directory in a tar archive.
615 * @param tarname the name of the tar archive to look in.
616 * @param subdir the subdirectory to look in.
618 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
620 TarList::iterator it = _tar_list[subdir].find(tarname);
621 if (it == _tar_list[subdir].end()) return NULL;
622 return (*it).second.dirname;
625 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
627 std::string src = srcParam;
628 std::string dest = destParam;
629 /* Tar internals assume lowercase */
630 std::transform(src.begin(), src.end(), src.begin(), tolower);
631 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
633 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
634 if (dest_file != _tar_filelist[subdir].end()) {
635 /* Link to file. Process the link like the destination file. */
636 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
637 } else {
638 /* Destination file not found. Assume 'link to directory'
639 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
640 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
641 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
642 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
646 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
648 TarAddLink(src, dest, subdir);
652 * Simplify filenames from tars.
653 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
654 * @param name Filename to process.
656 static void SimplifyFileName(char *name)
658 /* Force lowercase */
659 strtolower(name);
661 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
662 #if (PATHSEPCHAR != '/')
663 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
664 #endif
668 * Perform the scanning of a particular subdirectory.
669 * @param subdir The subdirectory to scan.
670 * @return The number of found tar files.
672 uint TarScanner::DoScan(Subdirectory sd)
674 _tar_filelist[sd].clear();
675 _tar_list[sd].clear();
676 uint num = this->Scan(".tar", sd, false);
677 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
678 return num;
681 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
683 DEBUG(misc, 1, "Scanning for tars");
684 TarScanner fs;
685 uint num = 0;
686 if (mode & TarScanner::BASESET) {
687 num += fs.DoScan(BASESET_DIR);
689 if (mode & TarScanner::NEWGRF) {
690 num += fs.DoScan(NEWGRF_DIR);
692 if (mode & TarScanner::AI) {
693 num += fs.DoScan(AI_DIR);
694 num += fs.DoScan(AI_LIBRARY_DIR);
696 if (mode & TarScanner::GAME) {
697 num += fs.DoScan(GAME_DIR);
698 num += fs.DoScan(GAME_LIBRARY_DIR);
700 if (mode & TarScanner::SCENARIO) {
701 num += fs.DoScan(SCENARIO_DIR);
702 num += fs.DoScan(HEIGHTMAP_DIR);
704 DEBUG(misc, 1, "Scan complete, found %d files", num);
705 return num;
709 * Add a single file to the scanned files of a tar, circumventing the scanning code.
710 * @param sd The sub directory the file is in.
711 * @param filename The name of the file to add.
712 * @return True if the additions went correctly.
714 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
716 this->subdir = sd;
717 return this->AddFile(filename, 0);
720 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
722 /* No tar within tar. */
723 assert(tar_filename == NULL);
725 /* The TAR-header, repeated for every file */
726 struct TarHeader {
727 char name[100]; ///< Name of the file
728 char mode[8];
729 char uid[8];
730 char gid[8];
731 char size[12]; ///< Size of the file, in ASCII
732 char mtime[12];
733 char chksum[8];
734 char typeflag;
735 char linkname[100];
736 char magic[6];
737 char version[2];
738 char uname[32];
739 char gname[32];
740 char devmajor[8];
741 char devminor[8];
742 char prefix[155]; ///< Path of the file
744 char unused[12];
747 /* Check if we already seen this file */
748 TarList::iterator it = _tar_list[this->subdir].find(filename);
749 if (it != _tar_list[this->subdir].end()) return false;
751 FILE *f = fopen(filename, "rb");
752 /* Although the file has been found there can be
753 * a number of reasons we cannot open the file.
754 * Most common case is when we simply have not
755 * been given read access. */
756 if (f == NULL) return false;
758 const char *dupped_filename = stredup(filename);
759 _tar_list[this->subdir][filename].filename = dupped_filename;
760 _tar_list[this->subdir][filename].dirname = NULL;
762 TarLinkList links; ///< Temporary list to collect links
764 TarHeader th;
765 char buf[sizeof(th.name) + 1], *end;
766 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
767 char link[sizeof(th.linkname) + 1];
768 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
769 size_t num = 0, pos = 0;
771 /* Make a char of 512 empty bytes */
772 char empty[512];
773 memset(&empty[0], 0, sizeof(empty));
775 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
776 size_t num_bytes_read = fread(&th, 1, 512, f);
777 if (num_bytes_read != 512) break;
778 pos += num_bytes_read;
780 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
781 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
782 /* If we have only zeros in the block, it can be an end-of-file indicator */
783 if (memcmp(&th, &empty[0], 512) == 0) continue;
785 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
786 fclose(f);
787 return false;
790 name[0] = '\0';
792 /* The prefix contains the directory-name */
793 if (th.prefix[0] != '\0') {
794 strecpy(name, th.prefix, lastof(name));
795 strecat(name, PATHSEP, lastof(name));
798 /* Copy the name of the file in a safe way at the end of 'name' */
799 strecat(name, th.name, lastof(name));
801 /* Calculate the size of the file.. for some strange reason this is stored as a string */
802 strecpy(buf, th.size, lastof(buf));
803 size_t skip = strtoul(buf, &end, 8);
805 switch (th.typeflag) {
806 case '\0':
807 case '0': { // regular file
808 /* Ignore empty files */
809 if (skip == 0) break;
811 if (strlen(name) == 0) break;
813 /* Store this entry in the list */
814 TarFileListEntry entry;
815 entry.tar_filename = dupped_filename;
816 entry.size = skip;
817 entry.position = pos;
819 /* Convert to lowercase and our PATHSEPCHAR */
820 SimplifyFileName(name);
822 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
823 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
825 break;
828 case '1': // hard links
829 case '2': { // symbolic links
830 /* Copy the destination of the link in a safe way at the end of 'linkname' */
831 strecpy(link, th.linkname, lastof(link));
833 if (strlen(name) == 0 || strlen(link) == 0) break;
835 /* Convert to lowercase and our PATHSEPCHAR */
836 SimplifyFileName(name);
837 SimplifyFileName(link);
839 /* Only allow relative links */
840 if (link[0] == PATHSEPCHAR) {
841 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
842 break;
845 /* Process relative path.
846 * Note: The destination of links must not contain any directory-links. */
847 strecpy(dest, name, lastof(dest));
848 char *destpos = strrchr(dest, PATHSEPCHAR);
849 if (destpos == NULL) destpos = dest;
850 *destpos = '\0';
852 char *pos = link;
853 while (*pos != '\0') {
854 char *next = strchr(pos, PATHSEPCHAR);
855 if (next == NULL) {
856 next = pos + strlen(pos);
857 } else {
858 /* Terminate the substring up to the path separator character. */
859 *next++= '\0';
862 if (strcmp(pos, ".") == 0) {
863 /* Skip '.' (current dir) */
864 } else if (strcmp(pos, "..") == 0) {
865 /* level up */
866 if (dest[0] == '\0') {
867 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
868 break;
871 /* Truncate 'dest' after last PATHSEPCHAR.
872 * This assumes that the truncated part is a real directory and not a link. */
873 destpos = strrchr(dest, PATHSEPCHAR);
874 if (destpos == NULL) destpos = dest;
875 *destpos = '\0';
876 } else {
877 /* Append at end of 'dest' */
878 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
879 destpos = strecpy(destpos, pos, lastof(dest));
882 if (destpos >= lastof(dest)) {
883 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
884 fclose(f);
885 return false;
888 pos = next;
891 /* Store links in temporary list */
892 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
893 links.insert(TarLinkList::value_type(name, dest));
895 break;
898 case '5': // directory
899 /* Convert to lowercase and our PATHSEPCHAR */
900 SimplifyFileName(name);
902 /* Store the first directory name we detect */
903 DEBUG(misc, 6, "Found dir in tar: %s", name);
904 if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = stredup(name);
905 break;
907 default:
908 /* Ignore other types */
909 break;
912 /* Skip to the next block.. */
913 skip = Align(skip, 512);
914 if (fseek(f, skip, SEEK_CUR) < 0) {
915 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
916 fclose(f);
917 return false;
919 pos += skip;
922 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
923 fclose(f);
925 /* Resolve file links and store directory links.
926 * We restrict usage of links to two cases:
927 * 1) Links to directories:
928 * Both the source path and the destination path must NOT contain any further links.
929 * When resolving files at most one directory link is resolved.
930 * 2) Links to files:
931 * The destination path must NOT contain any links.
932 * The source path may contain one directory link.
934 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
935 const std::string &src = link->first;
936 const std::string &dest = link->second;
937 TarAddLink(src, dest, this->subdir);
940 return true;
944 * Extract the tar with the given filename in the directory
945 * where the tar resides.
946 * @param tar_filename the name of the tar to extract.
947 * @param subdir The sub directory the tar is in.
948 * @return false on failure.
950 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
952 TarList::iterator it = _tar_list[subdir].find(tar_filename);
953 /* We don't know the file. */
954 if (it == _tar_list[subdir].end()) return false;
956 const char *dirname = (*it).second.dirname;
958 /* The file doesn't have a sub directory! */
959 if (dirname == NULL) return false;
961 char filename[MAX_PATH];
962 strecpy(filename, tar_filename, lastof(filename));
963 char *p = strrchr(filename, PATHSEPCHAR);
964 /* The file's path does not have a separator? */
965 if (p == NULL) return false;
967 p++;
968 strecpy(p, dirname, lastof(filename));
969 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
970 FioCreateDirectory(filename);
972 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
973 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
975 strecpy(p, (*it2).first.c_str(), lastof(filename));
977 DEBUG(misc, 9, " extracting %s", filename);
979 /* First open the file in the .tar. */
980 size_t to_copy = 0;
981 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
982 if (in == NULL) {
983 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
984 return false;
987 /* Now open the 'output' file. */
988 FILE *out = fopen(filename, "wb");
989 if (out == NULL) {
990 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
991 fclose(in);
992 return false;
995 /* Now read from the tar and write it into the file. */
996 char buffer[4096];
997 size_t read;
998 for (; to_copy != 0; to_copy -= read) {
999 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
1000 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
1003 /* Close everything up. */
1004 fclose(in);
1005 fclose(out);
1007 if (to_copy != 0) {
1008 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
1009 return false;
1013 DEBUG(misc, 9, " extraction successful");
1014 return true;
1017 #if defined(WIN32) || defined(WINCE)
1019 * Determine the base (personal dir and game data dir) paths
1020 * @param exe the path from the current path to the executable
1021 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
1023 extern void DetermineBasePaths(const char *exe);
1024 #else /* defined(WIN32) || defined(WINCE) */
1027 * Changes the working directory to the path of the give executable.
1028 * For OSX application bundles '.app' is the required extension of the bundle,
1029 * so when we crop the path to there, when can remove the name of the bundle
1030 * in the same way we remove the name from the executable name.
1031 * @param exe the path to the executable
1033 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
1035 char tmp[MAX_PATH];
1036 strecpy(tmp, exe, lastof(tmp));
1038 bool success = false;
1039 #ifdef WITH_COCOA
1040 char *app_bundle = strchr(tmp, '.');
1041 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1043 if (app_bundle != NULL) *app_bundle = '\0';
1044 #endif /* WITH_COCOA */
1045 char *s = strrchr(tmp, PATHSEPCHAR);
1046 if (s != NULL) {
1047 *s = '\0';
1048 #if defined(__DJGPP__)
1049 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1050 if (s > tmp && *(s - 1) == ':') chdir("/");
1051 #endif
1052 if (chdir(tmp) != 0) {
1053 DEBUG(misc, 0, "Directory with the binary does not exist?");
1054 } else {
1055 success = true;
1058 return success;
1062 * Whether we should scan the working directory.
1063 * It should not be scanned if it's the root or
1064 * the home directory as in both cases a big data
1065 * directory can cause huge amounts of unrelated
1066 * files scanned. Furthermore there are nearly no
1067 * use cases for the home/root directory to have
1068 * OpenTTD directories.
1069 * @return true if it should be scanned.
1071 bool DoScanWorkingDirectory()
1073 /* No working directory, so nothing to do. */
1074 if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
1076 /* Working directory is root, so do nothing. */
1077 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1079 /* No personal/home directory, so the working directory won't be that. */
1080 if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
1082 char tmp[MAX_PATH];
1083 seprintf(tmp, lastof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1084 AppendPathSeparator(tmp, lastof(tmp));
1085 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1089 * Determine the base (personal dir and game data dir) paths
1090 * @param exe the path to the executable
1092 void DetermineBasePaths(const char *exe)
1094 char tmp[MAX_PATH];
1095 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1096 const char *xdg_data_home = xdgDataHome(NULL);
1097 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", xdg_data_home,
1098 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1099 free(xdg_data_home);
1101 AppendPathSeparator(tmp, lastof(tmp));
1102 _searchpaths[SP_PERSONAL_DIR_XDG] = stredup(tmp);
1103 #endif
1104 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1105 _searchpaths[SP_PERSONAL_DIR] = NULL;
1106 #else
1107 #ifdef __HAIKU__
1108 BPath path;
1109 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1110 const char *homedir = stredup(path.Path());
1111 #else
1112 /* getenv is highly unsafe; duplicate it as soon as possible,
1113 * or at least before something else touches the environment
1114 * variables in any way. It can also contain all kinds of
1115 * unvalidated data we rather not want internally. */
1116 const char *homedir = getenv("HOME");
1117 if (homedir != NULL) {
1118 homedir = stredup(homedir);
1121 if (homedir == NULL) {
1122 const struct passwd *pw = getpwuid(getuid());
1123 homedir = (pw == NULL) ? NULL : stredup(pw->pw_dir);
1125 #endif
1127 if (homedir != NULL) {
1128 ValidateString(homedir);
1129 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1130 AppendPathSeparator(tmp, lastof(tmp));
1132 _searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
1133 free(homedir);
1134 } else {
1135 _searchpaths[SP_PERSONAL_DIR] = NULL;
1137 #endif
1139 #if defined(WITH_SHARED_DIR)
1140 seprintf(tmp, lastof(tmp), "%s", SHARED_DIR);
1141 AppendPathSeparator(tmp, lastof(tmp));
1142 _searchpaths[SP_SHARED_DIR] = stredup(tmp);
1143 #else
1144 _searchpaths[SP_SHARED_DIR] = NULL;
1145 #endif
1147 #if defined(__MORPHOS__) || defined(__AMIGA__)
1148 _searchpaths[SP_WORKING_DIR] = NULL;
1149 #else
1150 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1151 AppendPathSeparator(tmp, lastof(tmp));
1152 _searchpaths[SP_WORKING_DIR] = stredup(tmp);
1153 #endif
1155 _do_scan_working_directory = DoScanWorkingDirectory();
1157 /* Change the working directory to that one of the executable */
1158 if (ChangeWorkingDirectoryToExecutable(exe)) {
1159 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1160 AppendPathSeparator(tmp, lastof(tmp));
1161 _searchpaths[SP_BINARY_DIR] = stredup(tmp);
1162 } else {
1163 _searchpaths[SP_BINARY_DIR] = NULL;
1166 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1167 /* Go back to the current working directory. */
1168 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1169 DEBUG(misc, 0, "Failed to return to working directory!");
1173 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1174 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1175 #else
1176 seprintf(tmp, lastof(tmp), "%s", GLOBAL_DATA_DIR);
1177 AppendPathSeparator(tmp, lastof(tmp));
1178 _searchpaths[SP_INSTALLATION_DIR] = stredup(tmp);
1179 #endif
1180 #ifdef WITH_COCOA
1181 extern void cocoaSetApplicationBundleDir();
1182 cocoaSetApplicationBundleDir();
1183 #else
1184 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1185 #endif
1187 #endif /* defined(WIN32) || defined(WINCE) */
1189 const char *_personal_dir;
1192 * Acquire the base paths (personal dir and game data dir),
1193 * fill all other paths (save dir, autosave dir etc) and
1194 * make the save and scenario directories.
1195 * @param exe the path from the current path to the executable
1197 void DeterminePaths(const char *exe)
1199 DetermineBasePaths(exe);
1201 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1202 char config_home[MAX_PATH];
1204 const char *xdg_config_home = xdgConfigHome(NULL);
1205 seprintf(config_home, lastof(config_home), "%s" PATHSEP "%s", xdg_config_home,
1206 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1207 free(xdg_config_home);
1209 AppendPathSeparator(config_home, lastof(config_home));
1210 #endif
1212 Searchpath sp;
1213 FOR_ALL_SEARCHPATHS(sp) {
1214 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1215 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1218 char *config_dir;
1219 if (_config_file != NULL) {
1220 config_dir = stredup(_config_file);
1221 char *end = strrchr(config_dir, PATHSEPCHAR);
1222 if (end == NULL) {
1223 config_dir[0] = '\0';
1224 } else {
1225 end[1] = '\0';
1227 } else {
1228 char personal_dir[MAX_PATH];
1229 if (FioFindFullPath(personal_dir, lastof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
1230 char *end = strrchr(personal_dir, PATHSEPCHAR);
1231 if (end != NULL) end[1] = '\0';
1232 config_dir = stredup(personal_dir);
1233 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1234 } else {
1235 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1236 /* No previous configuration file found. Use the configuration folder from XDG. */
1237 config_dir = config_home;
1238 #else
1239 static const Searchpath new_openttd_cfg_order[] = {
1240 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1243 config_dir = NULL;
1244 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1245 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1246 config_dir = stredup(_searchpaths[new_openttd_cfg_order[i]]);
1247 break;
1250 assert(config_dir != NULL);
1251 #endif
1252 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1256 DEBUG(misc, 3, "%s found as config directory", config_dir);
1258 _highscore_file = str_fmt("%shs.dat", config_dir);
1259 extern char *_hotkeys_file;
1260 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1261 extern char *_windows_file;
1262 _windows_file = str_fmt("%swindows.cfg", config_dir);
1264 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1265 if (config_dir == config_home) {
1266 /* We are using the XDG configuration home for the config file,
1267 * then store the rest in the XDG data home folder. */
1268 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1269 FioCreateDirectory(_personal_dir);
1270 } else
1271 #endif
1273 _personal_dir = config_dir;
1276 /* Make the necessary folders */
1277 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1278 FioCreateDirectory(config_dir);
1279 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1280 #endif
1282 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1284 static const Subdirectory default_subdirs[] = {
1285 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1288 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1289 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1290 FioCreateDirectory(dir);
1291 free(dir);
1294 /* If we have network we make a directory for the autodownloading of content */
1295 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1296 #ifdef ENABLE_NETWORK
1297 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1299 /* Create the directory for each of the types of content */
1300 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1301 for (uint i = 0; i < lengthof(dirs); i++) {
1302 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1303 FioCreateDirectory(tmp);
1304 free(tmp);
1307 extern char *_log_file;
1308 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1309 #else /* ENABLE_NETWORK */
1310 /* If we don't have networking, we don't need to make the directory. But
1311 * if it exists we keep it, otherwise remove it from the search paths. */
1312 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1313 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1314 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1316 #endif /* ENABLE_NETWORK */
1320 * Sanitizes a filename, i.e. removes all illegal characters from it.
1321 * @param filename the "\0" terminated filename
1323 void SanitizeFilename(char *filename)
1325 for (; *filename != '\0'; filename++) {
1326 switch (*filename) {
1327 /* The following characters are not allowed in filenames
1328 * on at least one of the supported operating systems: */
1329 case ':': case '\\': case '*': case '?': case '/':
1330 case '<': case '>': case '|': case '"':
1331 *filename = '_';
1332 break;
1338 * Load a file into memory.
1339 * @param filename Name of the file to load.
1340 * @param lenp [out] Length of loaded data.
1341 * @param maxsize Maximum size to load.
1342 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1343 * @note If \a maxsize less than the length of the file, loading fails.
1345 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1347 FILE *in = fopen(filename, "rb");
1348 if (in == NULL) return NULL;
1350 fseek(in, 0, SEEK_END);
1351 size_t len = ftell(in);
1352 fseek(in, 0, SEEK_SET);
1353 if (len > maxsize) {
1354 fclose(in);
1355 return NULL;
1357 byte *mem = MallocT<byte>(len + 1);
1358 mem[len] = 0;
1359 if (fread(mem, len, 1, in) != 1) {
1360 fclose(in);
1361 free(mem);
1362 return NULL;
1364 fclose(in);
1366 *lenp = len;
1367 return mem;
1371 * Helper to see whether a given filename matches the extension.
1372 * @param extension The extension to look for.
1373 * @param filename The filename to look in for the extension.
1374 * @return True iff the extension is NULL, or the filename ends with it.
1376 static bool MatchesExtension(const char *extension, const char *filename)
1378 if (extension == NULL) return true;
1380 const char *ext = strrchr(filename, extension[0]);
1381 return ext != NULL && strcasecmp(ext, extension) == 0;
1385 * Scan a single directory (and recursively its children) and add
1386 * any graphics sets that are found.
1387 * @param fs the file scanner to add the files to
1388 * @param extension the extension of files to search for.
1389 * @param path full path we're currently at
1390 * @param basepath_length from where in the path are we 'based' on the search path
1391 * @param recursive whether to recursively search the sub directories
1393 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1395 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1397 uint num = 0;
1398 struct stat sb;
1399 struct dirent *dirent;
1400 DIR *dir;
1402 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1404 while ((dirent = readdir(dir)) != NULL) {
1405 const char *d_name = FS2OTTD(dirent->d_name);
1406 char filename[MAX_PATH];
1408 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1410 seprintf(filename, lastof(filename), "%s%s", path, d_name);
1412 if (S_ISDIR(sb.st_mode)) {
1413 /* Directory */
1414 if (!recursive) continue;
1415 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1416 if (!AppendPathSeparator(filename, lastof(filename))) continue;
1417 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1418 } else if (S_ISREG(sb.st_mode)) {
1419 /* File */
1420 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
1424 closedir(dir);
1426 return num;
1430 * Scan the given tar and add graphics sets when it finds one.
1431 * @param fs the file scanner to scan for
1432 * @param extension the extension of files to search for.
1433 * @param tar the tar to search in.
1435 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1437 uint num = 0;
1438 const char *filename = (*tar).first.c_str();
1440 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1442 return num;
1446 * Scan for files with the given extension in the given search path.
1447 * @param extension the extension of files to search for.
1448 * @param sd the sub directory to search in.
1449 * @param tars whether to search in the tars too.
1450 * @param recursive whether to search recursively
1451 * @return the number of found files, i.e. the number of times that
1452 * AddFile returned true.
1454 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1456 this->subdir = sd;
1458 Searchpath sp;
1459 char path[MAX_PATH];
1460 TarFileList::iterator tar;
1461 uint num = 0;
1463 FOR_ALL_SEARCHPATHS(sp) {
1464 /* Don't search in the working directory */
1465 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1467 FioAppendDirectory(path, lastof(path), sp, sd);
1468 num += ScanPath(this, extension, path, strlen(path), recursive);
1471 if (tars && sd != NO_DIRECTORY) {
1472 FOR_ALL_TARS(tar, sd) {
1473 num += ScanTar(this, extension, tar);
1477 switch (sd) {
1478 case BASESET_DIR:
1479 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1480 FALLTHROUGH;
1481 case NEWGRF_DIR:
1482 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1483 break;
1485 default: break;
1488 return num;
1492 * Scan for files with the given extension in the given search path.
1493 * @param extension the extension of files to search for.
1494 * @param directory the sub directory to search in.
1495 * @param recursive whether to search recursively
1496 * @return the number of found files, i.e. the number of times that
1497 * AddFile returned true.
1499 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1501 char path[MAX_PATH];
1502 strecpy(path, directory, lastof(path));
1503 if (!AppendPathSeparator(path, lastof(path))) return 0;
1504 return ScanPath(this, extension, path, strlen(path), recursive);