Delete pointless project file remnants.
[openttd-joker.git] / src / fileio.cpp
blobe6a43ecdd77788ea757e5c93a0ef84d538c07df5
1 /* $Id: fileio.cpp 26114 2013-11-25 21:50:54Z rubidium $ */
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(uint 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] == nullptr) {
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(uint 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 != nullptr);
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] != nullptr) {
200 fclose(_fio.handles[slot]);
202 free(_fio.shortnames[slot]);
203 _fio.shortnames[slot] = nullptr;
205 _fio.handles[slot] = nullptr;
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] != nullptr && _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(uint 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 == nullptr) 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 == nullptr ? filename : t);
269 char *t2 = strrchr(_fio.shortnames[slot], '.');
270 if (t2 != nullptr) *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 == nullptr) 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, nullptr, OPEN_EXISTING, 0, nullptr);
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 nullptr.
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 nullptr;
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 = nullptr;
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 nullptr;
429 #endif
431 f = fopen(buf, mode);
432 #if !defined(WIN32)
433 if (f == nullptr && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
434 f = fopen(buf, mode);
436 #endif
437 if (f != nullptr && filesize != nullptr) {
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 nullptr, size of the opened file.
450 * @return File handle of the opened file, or \c nullptr 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 == nullptr) return f;
458 if (fseek(f, entry->position, SEEK_SET) < 0) {
459 fclose(f);
460 return nullptr;
463 if (filesize != nullptr) *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 nullptr if the file is not available.
474 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
476 FILE *f = nullptr;
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 != nullptr || subdir == NO_DIRECTORY) break;
486 /* We can only use .tar in case of data-dir, and read-mode */
487 if (f == nullptr && 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 == nullptr && subdir != NO_DIRECTORY) {
521 switch (subdir) {
522 case BASESET_DIR:
523 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
524 if (f != nullptr) 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), nullptr);
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 * Find the first directory in a tar archive.
589 * @param tarname the name of the tar archive to look in.
590 * @param subdir the subdirectory to look in.
592 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
594 TarList::iterator it = _tar_list[subdir].find(tarname);
595 if (it == _tar_list[subdir].end()) return nullptr;
596 return (*it).second.dirname;
599 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
601 std::string src = srcParam;
602 std::string dest = destParam;
603 /* Tar internals assume lowercase */
604 std::transform(src.begin(), src.end(), src.begin(), tolower);
605 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
607 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
608 if (dest_file != _tar_filelist[subdir].end()) {
609 /* Link to file. Process the link like the destination file. */
610 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
611 } else {
612 /* Destination file not found. Assume 'link to directory'
613 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
614 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
615 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
616 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
620 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
622 TarAddLink(src, dest, subdir);
626 * Simplify filenames from tars.
627 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
628 * @param name Filename to process.
630 static void SimplifyFileName(char *name)
632 /* Force lowercase */
633 strtolower(name);
635 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
636 #if (PATHSEPCHAR != '/')
637 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
638 #endif
642 * Perform the scanning of a particular subdirectory.
643 * @param subdir The subdirectory to scan.
644 * @return The number of found tar files.
646 uint TarScanner::DoScan(Subdirectory sd)
648 _tar_filelist[sd].clear();
649 _tar_list[sd].clear();
650 uint num = this->Scan(".tar", sd, false);
651 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
652 return num;
655 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
657 DEBUG(misc, 1, "Scanning for tars");
658 TarScanner fs;
659 uint num = 0;
660 if (mode & TarScanner::BASESET) {
661 num += fs.DoScan(BASESET_DIR);
663 if (mode & TarScanner::NEWGRF) {
664 num += fs.DoScan(NEWGRF_DIR);
666 if (mode & TarScanner::AI) {
667 num += fs.DoScan(AI_DIR);
668 num += fs.DoScan(AI_LIBRARY_DIR);
670 if (mode & TarScanner::GAME) {
671 num += fs.DoScan(GAME_DIR);
672 num += fs.DoScan(GAME_LIBRARY_DIR);
674 if (mode & TarScanner::SCENARIO) {
675 num += fs.DoScan(SCENARIO_DIR);
676 num += fs.DoScan(HEIGHTMAP_DIR);
678 DEBUG(misc, 1, "Scan complete, found %d files", num);
679 return num;
683 * Add a single file to the scanned files of a tar, circumventing the scanning code.
684 * @param sd The sub directory the file is in.
685 * @param filename The name of the file to add.
686 * @return True if the additions went correctly.
688 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
690 this->subdir = sd;
691 return this->AddFile(filename, 0);
694 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
696 /* No tar within tar. */
697 assert(tar_filename == nullptr);
699 /* The TAR-header, repeated for every file */
700 struct TarHeader {
701 char name[100]; ///< Name of the file
702 char mode[8];
703 char uid[8];
704 char gid[8];
705 char size[12]; ///< Size of the file, in ASCII
706 char mtime[12];
707 char chksum[8];
708 char typeflag;
709 char linkname[100];
710 char magic[6];
711 char version[2];
712 char uname[32];
713 char gname[32];
714 char devmajor[8];
715 char devminor[8];
716 char prefix[155]; ///< Path of the file
718 char unused[12];
721 /* Check if we already seen this file */
722 TarList::iterator it = _tar_list[this->subdir].find(filename);
723 if (it != _tar_list[this->subdir].end()) return false;
725 FILE *f = fopen(filename, "rb");
726 /* Although the file has been found there can be
727 * a number of reasons we cannot open the file.
728 * Most common case is when we simply have not
729 * been given read access. */
730 if (f == nullptr) return false;
732 const char *dupped_filename = stredup(filename);
733 _tar_list[this->subdir][filename].filename = dupped_filename;
734 _tar_list[this->subdir][filename].dirname = nullptr;
736 TarLinkList links; ///< Temporary list to collect links
738 TarHeader th;
739 char buf[sizeof(th.name) + 1], *end;
740 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
741 char link[sizeof(th.linkname) + 1];
742 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
743 size_t num = 0, pos = 0;
745 /* Make a char of 512 empty bytes */
746 char empty[512];
747 memset(&empty[0], 0, sizeof(empty));
749 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
750 size_t num_bytes_read = fread(&th, 1, 512, f);
751 if (num_bytes_read != 512) break;
752 pos += num_bytes_read;
754 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
755 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
756 /* If we have only zeros in the block, it can be an end-of-file indicator */
757 if (memcmp(&th, &empty[0], 512) == 0) continue;
759 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
760 fclose(f);
761 return false;
764 name[0] = '\0';
766 /* The prefix contains the directory-name */
767 if (th.prefix[0] != '\0') {
768 strecpy(name, th.prefix, lastof(name));
769 strecat(name, PATHSEP, lastof(name));
772 /* Copy the name of the file in a safe way at the end of 'name' */
773 strecat(name, th.name, lastof(name));
775 /* Calculate the size of the file.. for some strange reason this is stored as a string */
776 strecpy(buf, th.size, lastof(buf));
777 size_t skip = strtoul(buf, &end, 8);
779 switch (th.typeflag) {
780 case '\0':
781 case '0': { // regular file
782 /* Ignore empty files */
783 if (skip == 0) break;
785 if (strlen(name) == 0) break;
787 /* Store this entry in the list */
788 TarFileListEntry entry;
789 entry.tar_filename = dupped_filename;
790 entry.size = skip;
791 entry.position = pos;
793 /* Convert to lowercase and our PATHSEPCHAR */
794 SimplifyFileName(name);
796 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
797 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
799 break;
802 case '1': // hard links
803 case '2': { // symbolic links
804 /* Copy the destination of the link in a safe way at the end of 'linkname' */
805 strecpy(link, th.linkname, lastof(link));
807 if (strlen(name) == 0 || strlen(link) == 0) break;
809 /* Convert to lowercase and our PATHSEPCHAR */
810 SimplifyFileName(name);
811 SimplifyFileName(link);
813 /* Only allow relative links */
814 if (link[0] == PATHSEPCHAR) {
815 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
816 break;
819 /* Process relative path.
820 * Note: The destination of links must not contain any directory-links. */
821 strecpy(dest, name, lastof(dest));
822 char *destpos = strrchr(dest, PATHSEPCHAR);
823 if (destpos == nullptr) destpos = dest;
824 *destpos = '\0';
826 char *pos = link;
827 while (*pos != '\0') {
828 char *next = strchr(pos, PATHSEPCHAR);
829 if (next == nullptr) {
830 next = pos + strlen(pos);
831 } else {
832 /* Terminate the substring up to the path separator character. */
833 *next++= '\0';
836 if (strcmp(pos, ".") == 0) {
837 /* Skip '.' (current dir) */
838 } else if (strcmp(pos, "..") == 0) {
839 /* level up */
840 if (dest[0] == '\0') {
841 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
842 break;
845 /* Truncate 'dest' after last PATHSEPCHAR.
846 * This assumes that the truncated part is a real directory and not a link. */
847 destpos = strrchr(dest, PATHSEPCHAR);
848 if (destpos == nullptr) destpos = dest;
849 *destpos = '\0';
850 } else {
851 /* Append at end of 'dest' */
852 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
853 destpos = strecpy(destpos, pos, lastof(dest));
856 if (destpos >= lastof(dest)) {
857 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
858 fclose(f);
859 return false;
862 pos = next;
865 /* Store links in temporary list */
866 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
867 links.insert(TarLinkList::value_type(name, dest));
869 break;
872 case '5': // directory
873 /* Convert to lowercase and our PATHSEPCHAR */
874 SimplifyFileName(name);
876 /* Store the first directory name we detect */
877 DEBUG(misc, 6, "Found dir in tar: %s", name);
878 if (_tar_list[this->subdir][filename].dirname == nullptr) _tar_list[this->subdir][filename].dirname = stredup(name);
879 break;
881 default:
882 /* Ignore other types */
883 break;
886 /* Skip to the next block.. */
887 skip = Align(skip, 512);
888 if (fseek(f, skip, SEEK_CUR) < 0) {
889 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
890 fclose(f);
891 return false;
893 pos += skip;
896 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
897 fclose(f);
899 /* Resolve file links and store directory links.
900 * We restrict usage of links to two cases:
901 * 1) Links to directories:
902 * Both the source path and the destination path must NOT contain any further links.
903 * When resolving files at most one directory link is resolved.
904 * 2) Links to files:
905 * The destination path must NOT contain any links.
906 * The source path may contain one directory link.
908 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
909 const std::string &src = link->first;
910 const std::string &dest = link->second;
911 TarAddLink(src, dest, this->subdir);
914 return true;
918 * Extract the tar with the given filename in the directory
919 * where the tar resides.
920 * @param tar_filename the name of the tar to extract.
921 * @param subdir The sub directory the tar is in.
922 * @return false on failure.
924 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
926 TarList::iterator it = _tar_list[subdir].find(tar_filename);
927 /* We don't know the file. */
928 if (it == _tar_list[subdir].end()) return false;
930 const char *dirname = (*it).second.dirname;
932 /* The file doesn't have a sub directory! */
933 if (dirname == nullptr) return false;
935 char filename[MAX_PATH];
936 strecpy(filename, tar_filename, lastof(filename));
937 char *p = strrchr(filename, PATHSEPCHAR);
938 /* The file's path does not have a separator? */
939 if (p == nullptr) return false;
941 p++;
942 strecpy(p, dirname, lastof(filename));
943 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
944 FioCreateDirectory(filename);
946 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
947 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
949 strecpy(p, (*it2).first.c_str(), lastof(filename));
951 DEBUG(misc, 9, " extracting %s", filename);
953 /* First open the file in the .tar. */
954 size_t to_copy = 0;
955 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
956 if (in == nullptr) {
957 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
958 return false;
961 /* Now open the 'output' file. */
962 FILE *out = fopen(filename, "wb");
963 if (out == nullptr) {
964 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
965 fclose(in);
966 return false;
969 /* Now read from the tar and write it into the file. */
970 char buffer[4096];
971 size_t read;
972 for (; to_copy != 0; to_copy -= read) {
973 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
974 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
977 /* Close everything up. */
978 fclose(in);
979 fclose(out);
981 if (to_copy != 0) {
982 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
983 return false;
987 DEBUG(misc, 9, " extraction successful");
988 return true;
991 #if defined(WIN32) || defined(WINCE)
993 * Determine the base (personal dir and game data dir) paths
994 * @param exe the path from the current path to the executable
995 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
997 extern void DetermineBasePaths(const char *exe);
998 #else /* defined(WIN32) || defined(WINCE) */
1001 * Changes the working directory to the path of the give executable.
1002 * For OSX application bundles '.app' is the required extension of the bundle,
1003 * so when we crop the path to there, when can remove the name of the bundle
1004 * in the same way we remove the name from the executable name.
1005 * @param exe the path to the executable
1007 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
1009 char tmp[MAX_PATH];
1010 strecpy(tmp, exe, lastof(tmp));
1012 bool success = false;
1013 #ifdef WITH_COCOA
1014 char *app_bundle = strchr(tmp, '.');
1015 while (app_bundle != nullptr && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1017 if (app_bundle != nullptr) *app_bundle = '\0';
1018 #endif /* WITH_COCOA */
1019 char *s = strrchr(tmp, PATHSEPCHAR);
1020 if (s != nullptr) {
1021 *s = '\0';
1022 #if defined(__DJGPP__)
1023 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1024 if (s > tmp && *(s - 1) == ':') chdir("/");
1025 #endif
1026 if (chdir(tmp) != 0) {
1027 DEBUG(misc, 0, "Directory with the binary does not exist?");
1028 } else {
1029 success = true;
1032 return success;
1036 * Whether we should scan the working directory.
1037 * It should not be scanned if it's the root or
1038 * the home directory as in both cases a big data
1039 * directory can cause huge amounts of unrelated
1040 * files scanned. Furthermore there are nearly no
1041 * use cases for the home/root directory to have
1042 * OpenTTD directories.
1043 * @return true if it should be scanned.
1045 bool DoScanWorkingDirectory()
1047 /* No working directory, so nothing to do. */
1048 if (_searchpaths[SP_WORKING_DIR] == nullptr) return false;
1050 /* Working directory is root, so do nothing. */
1051 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1053 /* No personal/home directory, so the working directory won't be that. */
1054 if (_searchpaths[SP_PERSONAL_DIR] == nullptr) return true;
1056 char tmp[MAX_PATH];
1057 seprintf(tmp, lastof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1058 AppendPathSeparator(tmp, lastof(tmp));
1059 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1063 * Determine the base (personal dir and game data dir) paths
1064 * @param exe the path to the executable
1066 void DetermineBasePaths(const char *exe)
1068 char tmp[MAX_PATH];
1069 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1070 const char *xdg_data_home = xdgDataHome(nullptr);
1071 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", xdg_data_home,
1072 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1073 free(xdg_data_home);
1075 AppendPathSeparator(tmp, lastof(tmp));
1076 _searchpaths[SP_PERSONAL_DIR_XDG] = stredup(tmp);
1077 #endif
1078 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1079 _searchpaths[SP_PERSONAL_DIR] = nullptr;
1080 #else
1081 #ifdef __HAIKU__
1082 BPath path;
1083 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1084 const char *homedir = stredup(path.Path());
1085 #else
1086 /* getenv is highly unsafe; duplicate it as soon as possible,
1087 * or at least before something else touches the environment
1088 * variables in any way. It can also contain all kinds of
1089 * unvalidated data we rather not want internally. */
1090 const char *homedir = getenv("HOME");
1091 if (homedir != nullptr) {
1092 homedir = stredup(homedir);
1095 if (homedir == nullptr) {
1096 const struct passwd *pw = getpwuid(getuid());
1097 homedir = (pw == nullptr) ? nullptr : stredup(pw->pw_dir);
1099 #endif
1101 if (homedir != nullptr) {
1102 ValidateString(homedir);
1103 seprintf(tmp, lastof(tmp), "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1104 AppendPathSeparator(tmp, lastof(tmp));
1106 _searchpaths[SP_PERSONAL_DIR] = stredup(tmp);
1107 free(homedir);
1108 } else {
1109 _searchpaths[SP_PERSONAL_DIR] = nullptr;
1111 #endif
1113 #if defined(WITH_SHARED_DIR)
1114 seprintf(tmp, lastof(tmp), "%s", SHARED_DIR);
1115 AppendPathSeparator(tmp, lastof(tmp));
1116 _searchpaths[SP_SHARED_DIR] = stredup(tmp);
1117 #else
1118 _searchpaths[SP_SHARED_DIR] = nullptr;
1119 #endif
1121 #if defined(__MORPHOS__) || defined(__AMIGA__)
1122 _searchpaths[SP_WORKING_DIR] = nullptr;
1123 #else
1124 if (getcwd(tmp, MAX_PATH) == nullptr) *tmp = '\0';
1125 AppendPathSeparator(tmp, lastof(tmp));
1126 _searchpaths[SP_WORKING_DIR] = stredup(tmp);
1127 #endif
1129 _do_scan_working_directory = DoScanWorkingDirectory();
1131 /* Change the working directory to that one of the executable */
1132 if (ChangeWorkingDirectoryToExecutable(exe)) {
1133 if (getcwd(tmp, MAX_PATH) == nullptr) *tmp = '\0';
1134 AppendPathSeparator(tmp, lastof(tmp));
1135 _searchpaths[SP_BINARY_DIR] = stredup(tmp);
1136 } else {
1137 _searchpaths[SP_BINARY_DIR] = nullptr;
1140 if (_searchpaths[SP_WORKING_DIR] != nullptr) {
1141 /* Go back to the current working directory. */
1142 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1143 DEBUG(misc, 0, "Failed to return to working directory!");
1147 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1148 _searchpaths[SP_INSTALLATION_DIR] = nullptr;
1149 #else
1150 seprintf(tmp, lastof(tmp), "%s", GLOBAL_DATA_DIR);
1151 AppendPathSeparator(tmp, lastof(tmp));
1152 _searchpaths[SP_INSTALLATION_DIR] = stredup(tmp);
1153 #endif
1154 #ifdef WITH_COCOA
1155 extern void cocoaSetApplicationBundleDir();
1156 cocoaSetApplicationBundleDir();
1157 #else
1158 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = nullptr;
1159 #endif
1161 #endif /* defined(WIN32) || defined(WINCE) */
1163 const char *_personal_dir;
1166 * Acquire the base paths (personal dir and game data dir),
1167 * fill all other paths (save dir, autosave dir etc) and
1168 * make the save and scenario directories.
1169 * @param exe the path from the current path to the executable
1171 void DeterminePaths(const char *exe)
1173 DetermineBasePaths(exe);
1175 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1176 char config_home[MAX_PATH];
1178 const char *xdg_config_home = xdgConfigHome(nullptr);
1179 seprintf(config_home, lastof(config_home), "%s" PATHSEP "%s", xdg_config_home,
1180 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1181 free(xdg_config_home);
1183 AppendPathSeparator(config_home, lastof(config_home));
1184 #endif
1186 Searchpath sp;
1187 FOR_ALL_SEARCHPATHS(sp) {
1188 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1189 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1192 char *config_dir;
1193 if (_config_file != nullptr) {
1194 config_dir = stredup(_config_file);
1195 char *end = strrchr(config_dir, PATHSEPCHAR);
1196 if (end == nullptr) {
1197 config_dir[0] = '\0';
1198 } else {
1199 end[1] = '\0';
1201 } else {
1202 char personal_dir[MAX_PATH];
1203 if (FioFindFullPath(personal_dir, lastof(personal_dir), BASE_DIR, "openttd.cfg") != nullptr) {
1204 char *end = strrchr(personal_dir, PATHSEPCHAR);
1205 if (end != nullptr) end[1] = '\0';
1206 config_dir = stredup(personal_dir);
1207 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1208 } else {
1209 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1210 /* No previous configuration file found. Use the configuration folder from XDG. */
1211 config_dir = config_home;
1212 #else
1213 static const Searchpath new_openttd_cfg_order[] = {
1214 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1217 config_dir = nullptr;
1218 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1219 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1220 config_dir = stredup(_searchpaths[new_openttd_cfg_order[i]]);
1221 break;
1224 assert(config_dir != nullptr);
1225 #endif
1226 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1230 DEBUG(misc, 3, "%s found as config directory", config_dir);
1232 _highscore_file = str_fmt("%shs.dat", config_dir);
1233 extern char *_hotkeys_file;
1234 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1235 extern char *_windows_file;
1236 _windows_file = str_fmt("%swindows.cfg", config_dir);
1238 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1239 if (config_dir == config_home) {
1240 /* We are using the XDG configuration home for the config file,
1241 * then store the rest in the XDG data home folder. */
1242 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1243 FioCreateDirectory(_personal_dir);
1244 } else
1245 #endif
1247 _personal_dir = config_dir;
1250 /* Make the necessary folders */
1251 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1252 FioCreateDirectory(config_dir);
1253 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1254 #endif
1256 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1258 static const Subdirectory default_subdirs[] = {
1259 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1262 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1263 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1264 FioCreateDirectory(dir);
1265 free(dir);
1268 /* If we have network we make a directory for the autodownloading of content */
1269 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1270 #ifdef ENABLE_NETWORK
1271 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1273 /* Create the directory for each of the types of content */
1274 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1275 for (uint i = 0; i < lengthof(dirs); i++) {
1276 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1277 FioCreateDirectory(tmp);
1278 free(tmp);
1281 extern char *_log_file;
1282 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1283 #else /* ENABLE_NETWORK */
1284 /* If we don't have networking, we don't need to make the directory. But
1285 * if it exists we keep it, otherwise remove it from the search paths. */
1286 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1287 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1288 _searchpaths[SP_AUTODOWNLOAD_DIR] = nullptr;
1290 #endif /* ENABLE_NETWORK */
1294 * Sanitizes a filename, i.e. removes all illegal characters from it.
1295 * @param filename the "\0" terminated filename
1297 void SanitizeFilename(char *filename)
1299 for (; *filename != '\0'; filename++) {
1300 switch (*filename) {
1301 /* The following characters are not allowed in filenames
1302 * on at least one of the supported operating systems: */
1303 case ':': case '\\': case '*': case '?': case '/':
1304 case '<': case '>': case '|': case '"':
1305 *filename = '_';
1306 break;
1312 * Load a file into memory.
1313 * @param filename Name of the file to load.
1314 * @param lenp [out] Length of loaded data.
1315 * @param maxsize Maximum size to load.
1316 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1317 * @note If \a maxsize less than the length of the file, loading fails.
1319 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1321 FILE *in = fopen(filename, "rb");
1322 if (in == nullptr) return nullptr;
1324 fseek(in, 0, SEEK_END);
1325 size_t len = ftell(in);
1326 fseek(in, 0, SEEK_SET);
1327 if (len > maxsize) {
1328 fclose(in);
1329 return nullptr;
1331 byte *mem = MallocT<byte>(len + 1);
1332 mem[len] = 0;
1333 if (fread(mem, len, 1, in) != 1) {
1334 fclose(in);
1335 free(mem);
1336 return nullptr;
1338 fclose(in);
1340 *lenp = len;
1341 return mem;
1345 * Helper to see whether a given filename matches the extension.
1346 * @param extension The extension to look for.
1347 * @param filename The filename to look in for the extension.
1348 * @return True iff the extension is nullptr, or the filename ends with it.
1350 static bool MatchesExtension(const char *extension, const char *filename)
1352 if (extension == nullptr) return true;
1354 const char *ext = strrchr(filename, extension[0]);
1355 return ext != nullptr && strcasecmp(ext, extension) == 0;
1359 * Scan a single directory (and recursively its children) and add
1360 * any graphics sets that are found.
1361 * @param fs the file scanner to add the files to
1362 * @param extension the extension of files to search for.
1363 * @param path full path we're currently at
1364 * @param basepath_length from where in the path are we 'based' on the search path
1365 * @param recursive whether to recursively search the sub directories
1367 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1369 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1371 uint num = 0;
1372 struct stat sb;
1373 struct dirent *dirent;
1374 DIR *dir;
1376 if (path == nullptr || (dir = ttd_opendir(path)) == nullptr) return 0;
1378 while ((dirent = readdir(dir)) != nullptr) {
1379 const char *d_name = FS2OTTD(dirent->d_name);
1380 char filename[MAX_PATH];
1382 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1384 seprintf(filename, lastof(filename), "%s%s", path, d_name);
1386 if (S_ISDIR(sb.st_mode)) {
1387 /* Directory */
1388 if (!recursive) continue;
1389 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1390 if (!AppendPathSeparator(filename, lastof(filename))) continue;
1391 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1392 } else if (S_ISREG(sb.st_mode)) {
1393 /* File */
1394 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, nullptr)) num++;
1398 closedir(dir);
1400 return num;
1404 * Scan the given tar and add graphics sets when it finds one.
1405 * @param fs the file scanner to scan for
1406 * @param extension the extension of files to search for.
1407 * @param tar the tar to search in.
1409 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1411 uint num = 0;
1412 const char *filename = (*tar).first.c_str();
1414 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1416 return num;
1420 * Scan for files with the given extension in the given search path.
1421 * @param extension the extension of files to search for.
1422 * @param sd the sub directory to search in.
1423 * @param tars whether to search in the tars too.
1424 * @param recursive whether to search recursively
1425 * @return the number of found files, i.e. the number of times that
1426 * AddFile returned true.
1428 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1430 this->subdir = sd;
1432 Searchpath sp;
1433 char path[MAX_PATH];
1434 TarFileList::iterator tar;
1435 uint num = 0;
1437 FOR_ALL_SEARCHPATHS(sp) {
1438 /* Don't search in the working directory */
1439 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1441 FioAppendDirectory(path, lastof(path), sp, sd);
1442 num += ScanPath(this, extension, path, strlen(path), recursive);
1445 if (tars && sd != NO_DIRECTORY) {
1446 FOR_ALL_TARS(tar, sd) {
1447 num += ScanTar(this, extension, tar);
1451 switch (sd) {
1452 case BASESET_DIR:
1453 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1454 FALLTHROUGH;
1455 case NEWGRF_DIR:
1456 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1457 break;
1459 default: break;
1462 return num;
1466 * Scan for files with the given extension in the given search path.
1467 * @param extension the extension of files to search for.
1468 * @param directory the sub directory to search in.
1469 * @param recursive whether to search recursively
1470 * @return the number of found files, i.e. the number of times that
1471 * AddFile returned true.
1473 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1475 char path[MAX_PATH];
1476 strecpy(path, directory, lastof(path));
1477 if (!AppendPathSeparator(path, lastof(path))) return 0;
1478 return ScanPath(this, extension, path, strlen(path), recursive);