Rearrange storage of reserved tracks for railway tiles
[openttd/fttd.git] / src / fileio.cpp
blob7c35a318902fe60c6b92a5e79cc826ed35265aa9
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 /** Size of the #Fio data buffer. */
36 #define FIO_BUFFER_SIZE 512
38 /** Structure for keeping several open files with just one data buffer. */
39 struct Fio {
40 byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer
41 size_t pos; ///< current (system) position in file
42 FILE *cur_fh; ///< current file handle
43 const char *filename; ///< current filename
44 FILE *handles[MAX_FILE_SLOTS]; ///< array of file handles we can have open
45 byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
46 const char *filenames[MAX_FILE_SLOTS]; ///< array of filenames we (should) have open
47 char *shortnames[MAX_FILE_SLOTS]; ///< array of short names for spriteloader's use
48 #if defined(LIMITED_FDS)
49 uint open_handles; ///< current amount of open handles
50 uint usage_count[MAX_FILE_SLOTS]; ///< count how many times this file has been opened
51 #endif /* LIMITED_FDS */
54 static Fio _fio; ///< #Fio instance.
56 /** Whether the working directory should be scanned. */
57 static bool _do_scan_working_directory = true;
59 extern char *_config_file;
60 extern char *_highscore_file;
62 /**
63 * Get position in the current file.
64 * @return Position in the file.
66 size_t FioGetPos()
68 return _fio.pos + (_fio.buffer - _fio.buffer_end);
71 /**
72 * Get the filename associated with a slot.
73 * @param slot Index of queried file.
74 * @return Name of the file.
76 const char *FioGetFilename(uint8 slot)
78 return _fio.shortnames[slot];
81 /**
82 * Seek in the current file.
83 * @param pos New position.
84 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
86 void FioSeekTo(size_t pos, int mode)
88 if (mode == SEEK_CUR) pos += FioGetPos();
89 _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
90 _fio.pos = pos;
91 if (fseek(_fio.cur_fh, _fio.pos, SEEK_SET) < 0) {
92 DEBUG(misc, 0, "Seeking in %s failed", _fio.filename);
96 #if defined(LIMITED_FDS)
97 static void FioRestoreFile(int slot)
99 /* Do we still have the file open, or should we reopen it? */
100 if (_fio.handles[slot] == NULL) {
101 DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
102 FioOpenFile(slot, _fio.filenames[slot]);
104 _fio.usage_count[slot]++;
106 #endif /* LIMITED_FDS */
109 * Switch to a different file and seek to a position.
110 * @param slot Slot number of the new file.
111 * @param pos New absolute position in the new file.
113 void FioSeekToFile(uint8 slot, size_t pos)
115 FILE *f;
116 #if defined(LIMITED_FDS)
117 /* Make sure we have this file open */
118 FioRestoreFile(slot);
119 #endif /* LIMITED_FDS */
120 f = _fio.handles[slot];
121 assert(f != NULL);
122 _fio.cur_fh = f;
123 _fio.filename = _fio.filenames[slot];
124 FioSeekTo(pos, SEEK_SET);
128 * Read a byte from the file.
129 * @return Read byte.
131 byte FioReadByte()
133 if (_fio.buffer == _fio.buffer_end) {
134 _fio.buffer = _fio.buffer_start;
135 size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
136 _fio.pos += size;
137 _fio.buffer_end = _fio.buffer_start + size;
139 if (size == 0) return 0;
141 return *_fio.buffer++;
145 * Skip \a n bytes ahead in the file.
146 * @param n Number of bytes to skip reading.
148 void FioSkipBytes(int n)
150 for (;;) {
151 int m = min(_fio.buffer_end - _fio.buffer, n);
152 _fio.buffer += m;
153 n -= m;
154 if (n == 0) break;
155 FioReadByte();
156 n--;
161 * Read a word (16 bits) from the file (in low endian format).
162 * @return Read word.
164 uint16 FioReadWord()
166 byte b = FioReadByte();
167 return (FioReadByte() << 8) | b;
171 * Read a double word (32 bits) from the file (in low endian format).
172 * @return Read word.
174 uint32 FioReadDword()
176 uint b = FioReadWord();
177 return (FioReadWord() << 16) | b;
181 * Read a block.
182 * @param ptr Destination buffer.
183 * @param size Number of bytes to read.
185 void FioReadBlock(void *ptr, size_t size)
187 FioSeekTo(FioGetPos(), SEEK_SET);
188 _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
192 * Close the file at the given slot number.
193 * @param slot File index to close.
195 static inline void FioCloseFile(int slot)
197 if (_fio.handles[slot] != NULL) {
198 fclose(_fio.handles[slot]);
200 free(_fio.shortnames[slot]);
201 _fio.shortnames[slot] = NULL;
203 _fio.handles[slot] = NULL;
204 #if defined(LIMITED_FDS)
205 _fio.open_handles--;
206 #endif /* LIMITED_FDS */
210 /** Close all slotted open files. */
211 void FioCloseAll()
213 for (int i = 0; i != lengthof(_fio.handles); i++) {
214 FioCloseFile(i);
218 #if defined(LIMITED_FDS)
219 static void FioFreeHandle()
221 /* If we are about to open a file that will exceed the limit, close a file */
222 if (_fio.open_handles + 1 == LIMITED_FDS) {
223 uint i, count;
224 int slot;
226 count = UINT_MAX;
227 slot = -1;
228 /* Find the file that is used the least */
229 for (i = 0; i < lengthof(_fio.handles); i++) {
230 if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
231 count = _fio.usage_count[i];
232 slot = i;
235 assert(slot != -1);
236 DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
237 FioCloseFile(slot);
240 #endif /* LIMITED_FDS */
243 * Open a slotted file.
244 * @param slot Index to assign.
245 * @param filename Name of the file at the disk.
246 * @param subdir The sub directory to search this file in.
248 void FioOpenFile(int slot, const char *filename, Subdirectory subdir)
250 FILE *f;
252 #if defined(LIMITED_FDS)
253 FioFreeHandle();
254 #endif /* LIMITED_FDS */
255 f = FioFOpenFile(filename, "rb", subdir);
256 if (f == NULL) usererror("Cannot open file '%s'", filename);
257 long pos = ftell(f);
258 if (pos < 0) usererror("Cannot read file '%s'", filename);
260 FioCloseFile(slot); // if file was opened before, close it
261 _fio.handles[slot] = f;
262 _fio.filenames[slot] = filename;
264 /* Store the filename without path and extension */
265 const char *t = strrchr(filename, PATHSEPCHAR);
266 _fio.shortnames[slot] = strdup(t == NULL ? filename : t);
267 char *t2 = strrchr(_fio.shortnames[slot], '.');
268 if (t2 != NULL) *t2 = '\0';
269 strtolower(_fio.shortnames[slot]);
271 #if defined(LIMITED_FDS)
272 _fio.usage_count[slot] = 0;
273 _fio.open_handles++;
274 #endif /* LIMITED_FDS */
275 FioSeekToFile(slot, (uint32)pos);
278 static const char * const _subdirs[] = {
280 "save" PATHSEP,
281 "save" PATHSEP "autosave" PATHSEP,
282 "scenario" PATHSEP,
283 "scenario" PATHSEP "heightmap" PATHSEP,
284 "gm" PATHSEP,
285 "data" PATHSEP,
286 "baseset" PATHSEP,
287 "newgrf" PATHSEP,
288 "lang" PATHSEP,
289 "ai" PATHSEP,
290 "ai" PATHSEP "library" PATHSEP,
291 "game" PATHSEP,
292 "game" PATHSEP "library" PATHSEP,
293 "screenshot" PATHSEP,
295 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
297 const char *_searchpaths[NUM_SEARCHPATHS];
298 TarList _tar_list[NUM_SUBDIRS];
299 TarFileList _tar_filelist[NUM_SUBDIRS];
301 typedef std::map<std::string, std::string> TarLinkList;
302 static TarLinkList _tar_linklist[NUM_SUBDIRS]; ///< List of directory links
305 * Check whether the given file exists
306 * @param filename the file to try for existence.
307 * @param subdir the subdirectory to look in
308 * @return true if and only if the file can be opened
310 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
312 FILE *f = FioFOpenFile(filename, "rb", subdir);
313 if (f == NULL) return false;
315 FioFCloseFile(f);
316 return true;
320 * Test whether the given filename exists.
321 * @param filename the file to test.
322 * @return true if and only if the file exists.
324 bool FileExists(const char *filename)
326 #if defined(WINCE)
327 /* There is always one platform that doesn't support basic commands... */
328 HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
329 if (hand == INVALID_HANDLE_VALUE) return 1;
330 CloseHandle(hand);
331 return 0;
332 #else
333 return access(OTTD2FS(filename), 0) == 0;
334 #endif
338 * Close a file in a safe way.
340 void FioFCloseFile(FILE *f)
342 fclose(f);
345 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
347 assert(subdir < NUM_SUBDIRS);
348 assert(sp < NUM_SEARCHPATHS);
350 snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
351 return buf;
355 * Find a path to the filename in one of the search directories.
356 * @param buf [out] Destination buffer for the path.
357 * @param buflen Length of the destination buffer.
358 * @param subdir Subdirectory to try.
359 * @param filename Filename to look for.
360 * @return \a buf containing the path if the path was found, else \c NULL.
362 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
364 Searchpath sp;
365 assert(subdir < NUM_SUBDIRS);
367 FOR_ALL_SEARCHPATHS(sp) {
368 FioGetFullPath(buf, buflen, sp, subdir, filename);
369 if (FileExists(buf)) return buf;
370 #if !defined(WIN32)
371 /* Be, as opening files, aware that sometimes the filename
372 * might be in uppercase when it is in lowercase on the
373 * disk. Of course Windows doesn't care about casing. */
374 if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
375 #endif
378 return NULL;
381 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
383 assert(subdir < NUM_SUBDIRS);
384 assert(sp < NUM_SEARCHPATHS);
386 snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
387 return buf;
390 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
392 Searchpath sp;
394 /* Find and return the first valid directory */
395 FOR_ALL_SEARCHPATHS(sp) {
396 char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
397 if (FileExists(buf)) return ret;
400 /* Could not find the directory, fall back to a base path */
401 ttd_strlcpy(buf, _personal_dir, buflen);
403 return buf;
406 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
408 #if defined(WIN32) && defined(UNICODE)
409 /* fopen is implemented as a define with ellipses for
410 * Unicode support (prepend an L). As we are not sending
411 * a string, but a variable, it 'renames' the variable,
412 * so make that variable to makes it compile happily */
413 wchar_t Lmode[5];
414 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
415 #endif
416 FILE *f = NULL;
417 char buf[MAX_PATH];
419 if (subdir == NO_DIRECTORY) {
420 strecpy(buf, filename, lastof(buf));
421 } else {
422 snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
425 #if defined(WIN32)
426 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
427 #endif
429 f = fopen(buf, mode);
430 #if !defined(WIN32)
431 if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
432 f = fopen(buf, mode);
434 #endif
435 if (f != NULL && filesize != NULL) {
436 /* Find the size of the file */
437 fseek(f, 0, SEEK_END);
438 *filesize = ftell(f);
439 fseek(f, 0, SEEK_SET);
441 return f;
445 * Opens a file from inside a tar archive.
446 * @param entry The entry to open.
447 * @param filesize [out] If not \c NULL, size of the opened file.
448 * @return File handle of the opened file, or \c NULL if the file is not available.
449 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
451 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
453 FILE *f = fopen(entry->tar_filename, "rb");
454 if (f == NULL) return f;
456 if (fseek(f, entry->position, SEEK_SET) < 0) {
457 fclose(f);
458 return NULL;
461 if (filesize != NULL) *filesize = entry->size;
462 return f;
466 * Opens a OpenTTD file somewhere in a personal or global directory.
467 * @param filename Name of the file to open.
468 * @param subdir Subdirectory to open.
469 * @param filename Name of the file to open.
470 * @return File handle of the opened file, or \c NULL if the file is not available.
472 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
474 FILE *f = NULL;
475 Searchpath sp;
477 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
479 FOR_ALL_SEARCHPATHS(sp) {
480 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
481 if (f != NULL || subdir == NO_DIRECTORY) break;
484 /* We can only use .tar in case of data-dir, and read-mode */
485 if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
486 static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
487 char resolved_name[MAX_RESOLVED_LENGTH];
489 /* Filenames in tars are always forced to be lowercase */
490 strecpy(resolved_name, filename, lastof(resolved_name));
491 strtolower(resolved_name);
493 size_t resolved_len = strlen(resolved_name);
495 /* Resolve ONE directory link */
496 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
497 const std::string &src = link->first;
498 size_t len = src.length();
499 if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
500 /* Apply link */
501 char resolved_name2[MAX_RESOLVED_LENGTH];
502 const std::string &dest = link->second;
503 strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
504 strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
505 strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
506 break; // Only resolve one level
510 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
511 if (it != _tar_filelist[subdir].end()) {
512 f = FioFOpenFileTar(&((*it).second), filesize);
516 /* Sometimes a full path is given. To support
517 * the 'subdirectory' must be 'removed'. */
518 if (f == NULL && subdir != NO_DIRECTORY) {
519 switch (subdir) {
520 case BASESET_DIR:
521 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
522 if (f != NULL) break;
523 /* FALL THROUGH */
524 case NEWGRF_DIR:
525 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
526 break;
528 default:
529 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
530 break;
534 return f;
538 * Create a directory with the given name
539 * @param name the new name of the directory
541 static void FioCreateDirectory(const char *name)
543 /* Ignore directory creation errors; they'll surface later on, and most
544 * of the time they are 'directory already exists' errors anyhow. */
545 #if defined(WIN32) || defined(WINCE)
546 CreateDirectory(OTTD2FS(name), NULL);
547 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
548 mkdir(OTTD2FS(name));
549 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
550 char buf[MAX_PATH];
551 ttd_strlcpy(buf, name, MAX_PATH);
553 size_t len = strlen(name) - 1;
554 if (buf[len] == '/') {
555 buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
558 mkdir(OTTD2FS(buf), 0755);
559 #else
560 mkdir(OTTD2FS(name), 0755);
561 #endif
565 * Appends, if necessary, the path separator character to the end of the string.
566 * It does not add the path separator to zero-sized strings.
567 * @param buf string to append the separator to
568 * @param buflen the length of \a buf.
569 * @return true iff the operation succeeded
571 bool AppendPathSeparator(char *buf, size_t buflen)
573 size_t s = strlen(buf);
575 /* Length of string + path separator + '\0' */
576 if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
577 if (s + 2 >= buflen) return false;
579 buf[s] = PATHSEPCHAR;
580 buf[s + 1] = '\0';
583 return true;
587 * Allocates and files a variable with the full path
588 * based on the given directory.
589 * @param dir the directory to base the path on
590 * @return the malloced full path
592 char *BuildWithFullPath(const char *dir)
594 char *dest = MallocT<char>(MAX_PATH);
595 ttd_strlcpy(dest, dir, MAX_PATH);
597 /* Check if absolute or relative path */
598 const char *s = strchr(dest, PATHSEPCHAR);
600 /* Add absolute path */
601 if (s == NULL || dest != s) {
602 if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
603 AppendPathSeparator(dest, MAX_PATH);
604 ttd_strlcat(dest, dir, MAX_PATH);
606 AppendPathSeparator(dest, MAX_PATH);
608 return dest;
612 * Find the first directory in a tar archive.
613 * @param tarname the name of the tar archive to look in.
614 * @param subdir the subdirectory to look in.
616 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
618 TarList::iterator it = _tar_list[subdir].find(tarname);
619 if (it == _tar_list[subdir].end()) return NULL;
620 return (*it).second.dirname;
623 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
625 std::string src = srcParam;
626 std::string dest = destParam;
627 /* Tar internals assume lowercase */
628 std::transform(src.begin(), src.end(), src.begin(), tolower);
629 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
631 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
632 if (dest_file != _tar_filelist[subdir].end()) {
633 /* Link to file. Process the link like the destination file. */
634 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
635 } else {
636 /* Destination file not found. Assume 'link to directory'
637 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
638 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
639 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
640 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
644 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
646 TarAddLink(src, dest, subdir);
650 * Simplify filenames from tars.
651 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
652 * @param name Filename to process.
654 static void SimplifyFileName(char *name)
656 /* Force lowercase */
657 strtolower(name);
659 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
660 #if (PATHSEPCHAR != '/')
661 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
662 #endif
666 * Perform the scanning of a particular subdirectory.
667 * @param subdir The subdirectory to scan.
668 * @return The number of found tar files.
670 uint TarScanner::DoScan(Subdirectory sd)
672 _tar_filelist[sd].clear();
673 _tar_list[sd].clear();
674 uint num = this->Scan(".tar", sd, false);
675 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
676 return num;
679 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
681 DEBUG(misc, 1, "Scanning for tars");
682 TarScanner fs;
683 uint num = 0;
684 if (mode & TarScanner::BASESET) {
685 num += fs.DoScan(BASESET_DIR);
687 if (mode & TarScanner::NEWGRF) {
688 num += fs.DoScan(NEWGRF_DIR);
690 if (mode & TarScanner::AI) {
691 num += fs.DoScan(AI_DIR);
692 num += fs.DoScan(AI_LIBRARY_DIR);
694 if (mode & TarScanner::GAME) {
695 num += fs.DoScan(GAME_DIR);
696 num += fs.DoScan(GAME_LIBRARY_DIR);
698 if (mode & TarScanner::SCENARIO) {
699 num += fs.DoScan(SCENARIO_DIR);
700 num += fs.DoScan(HEIGHTMAP_DIR);
702 DEBUG(misc, 1, "Scan complete, found %d files", num);
703 return num;
707 * Add a single file to the scanned files of a tar, circumventing the scanning code.
708 * @param sd The sub directory the file is in.
709 * @param filename The name of the file to add.
710 * @return True if the additions went correctly.
712 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
714 this->subdir = sd;
715 return this->AddFile(filename, 0);
718 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
720 /* No tar within tar. */
721 assert(tar_filename == NULL);
723 /* The TAR-header, repeated for every file */
724 struct TarHeader {
725 char name[100]; ///< Name of the file
726 char mode[8];
727 char uid[8];
728 char gid[8];
729 char size[12]; ///< Size of the file, in ASCII
730 char mtime[12];
731 char chksum[8];
732 char typeflag;
733 char linkname[100];
734 char magic[6];
735 char version[2];
736 char uname[32];
737 char gname[32];
738 char devmajor[8];
739 char devminor[8];
740 char prefix[155]; ///< Path of the file
742 char unused[12];
745 /* Check if we already seen this file */
746 TarList::iterator it = _tar_list[this->subdir].find(filename);
747 if (it != _tar_list[this->subdir].end()) return false;
749 FILE *f = fopen(filename, "rb");
750 /* Although the file has been found there can be
751 * a number of reasons we cannot open the file.
752 * Most common case is when we simply have not
753 * been given read access. */
754 if (f == NULL) return false;
756 const char *dupped_filename = strdup(filename);
757 _tar_list[this->subdir][filename].filename = dupped_filename;
758 _tar_list[this->subdir][filename].dirname = NULL;
760 TarLinkList links; ///< Temporary list to collect links
762 TarHeader th;
763 char buf[sizeof(th.name) + 1], *end;
764 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
765 char link[sizeof(th.linkname) + 1];
766 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
767 size_t num = 0, pos = 0;
769 /* Make a char of 512 empty bytes */
770 char empty[512];
771 memset(&empty[0], 0, sizeof(empty));
773 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
774 size_t num_bytes_read = fread(&th, 1, 512, f);
775 if (num_bytes_read != 512) break;
776 pos += num_bytes_read;
778 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
779 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
780 /* If we have only zeros in the block, it can be an end-of-file indicator */
781 if (memcmp(&th, &empty[0], 512) == 0) continue;
783 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
784 fclose(f);
785 return false;
788 name[0] = '\0';
790 /* The prefix contains the directory-name */
791 if (th.prefix[0] != '\0') {
792 ttd_strlcpy(name, th.prefix, lengthof(name));
793 ttd_strlcat(name, PATHSEP, lengthof(name));
796 /* Copy the name of the file in a safe way at the end of 'name' */
797 ttd_strlcat(name, th.name, lengthof(name));
799 /* Calculate the size of the file.. for some strange reason this is stored as a string */
800 ttd_strlcpy(buf, th.size, lengthof(buf));
801 size_t skip = strtoul(buf, &end, 8);
803 switch (th.typeflag) {
804 case '\0':
805 case '0': { // regular file
806 /* Ignore empty files */
807 if (skip == 0) break;
809 if (strlen(name) == 0) break;
811 /* Store this entry in the list */
812 TarFileListEntry entry;
813 entry.tar_filename = dupped_filename;
814 entry.size = skip;
815 entry.position = pos;
817 /* Convert to lowercase and our PATHSEPCHAR */
818 SimplifyFileName(name);
820 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
821 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
823 break;
826 case '1': // hard links
827 case '2': { // symbolic links
828 /* Copy the destination of the link in a safe way at the end of 'linkname' */
829 ttd_strlcpy(link, th.linkname, lengthof(link));
831 if (strlen(name) == 0 || strlen(link) == 0) break;
833 /* Convert to lowercase and our PATHSEPCHAR */
834 SimplifyFileName(name);
835 SimplifyFileName(link);
837 /* Only allow relative links */
838 if (link[0] == PATHSEPCHAR) {
839 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
840 break;
843 /* Process relative path.
844 * Note: The destination of links must not contain any directory-links. */
845 ttd_strlcpy(dest, name, lengthof(dest));
846 char *destpos = strrchr(dest, PATHSEPCHAR);
847 if (destpos == NULL) destpos = dest;
848 *destpos = '\0';
850 char *pos = link;
851 while (*pos != '\0') {
852 char *next = strchr(link, PATHSEPCHAR);
853 if (next == NULL) next = pos + strlen(pos);
855 /* Skip '.' (current dir) */
856 if (next != pos + 1 || pos[0] != '.') {
857 if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
858 /* level up */
859 if (dest[0] == '\0') {
860 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
861 break;
864 /* Truncate 'dest' after last PATHSEPCHAR.
865 * This assumes that the truncated part is a real directory and not a link. */
866 destpos = strrchr(dest, PATHSEPCHAR);
867 if (destpos == NULL) destpos = dest;
868 } else {
869 /* Append at end of 'dest' */
870 if (destpos != dest) *(destpos++) = PATHSEPCHAR;
871 strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
872 destpos += next - pos;
874 *destpos = '\0';
877 pos = next;
880 /* Store links in temporary list */
881 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
882 links.insert(TarLinkList::value_type(name, dest));
884 break;
887 case '5': // directory
888 /* Convert to lowercase and our PATHSEPCHAR */
889 SimplifyFileName(name);
891 /* Store the first directory name we detect */
892 DEBUG(misc, 6, "Found dir in tar: %s", name);
893 if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = strdup(name);
894 break;
896 default:
897 /* Ignore other types */
898 break;
901 /* Skip to the next block.. */
902 skip = Align(skip, 512);
903 if (fseek(f, skip, SEEK_CUR) < 0) {
904 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
905 fclose(f);
906 return false;
908 pos += skip;
911 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
912 fclose(f);
914 /* Resolve file links and store directory links.
915 * We restrict usage of links to two cases:
916 * 1) Links to directories:
917 * Both the source path and the destination path must NOT contain any further links.
918 * When resolving files at most one directory link is resolved.
919 * 2) Links to files:
920 * The destination path must NOT contain any links.
921 * The source path may contain one directory link.
923 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
924 const std::string &src = link->first;
925 const std::string &dest = link->second;
926 TarAddLink(src, dest, this->subdir);
929 return true;
933 * Extract the tar with the given filename in the directory
934 * where the tar resides.
935 * @param tar_filename the name of the tar to extract.
936 * @param subdir The sub directory the tar is in.
937 * @return false on failure.
939 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
941 TarList::iterator it = _tar_list[subdir].find(tar_filename);
942 /* We don't know the file. */
943 if (it == _tar_list[subdir].end()) return false;
945 const char *dirname = (*it).second.dirname;
947 /* The file doesn't have a sub directory! */
948 if (dirname == NULL) return false;
950 char filename[MAX_PATH];
951 strecpy(filename, tar_filename, lastof(filename));
952 char *p = strrchr(filename, PATHSEPCHAR);
953 /* The file's path does not have a separator? */
954 if (p == NULL) return false;
956 p++;
957 strecpy(p, dirname, lastof(filename));
958 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
959 FioCreateDirectory(filename);
961 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
962 if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
964 strecpy(p, (*it2).first.c_str(), lastof(filename));
966 DEBUG(misc, 9, " extracting %s", filename);
968 /* First open the file in the .tar. */
969 size_t to_copy = 0;
970 FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
971 if (in == NULL) {
972 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
973 return false;
976 /* Now open the 'output' file. */
977 FILE *out = fopen(filename, "wb");
978 if (out == NULL) {
979 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
980 fclose(in);
981 return false;
984 /* Now read from the tar and write it into the file. */
985 char buffer[4096];
986 size_t read;
987 for (; to_copy != 0; to_copy -= read) {
988 read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
989 if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
992 /* Close everything up. */
993 fclose(in);
994 fclose(out);
996 if (to_copy != 0) {
997 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
998 return false;
1002 DEBUG(misc, 9, " extraction successful");
1003 return true;
1006 #if defined(WIN32) || defined(WINCE)
1008 * Determine the base (personal dir and game data dir) paths
1009 * @param exe the path from the current path to the executable
1010 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
1012 extern void DetermineBasePaths(const char *exe);
1013 #else /* defined(WIN32) || defined(WINCE) */
1016 * Changes the working directory to the path of the give executable.
1017 * For OSX application bundles '.app' is the required extension of the bundle,
1018 * so when we crop the path to there, when can remove the name of the bundle
1019 * in the same way we remove the name from the executable name.
1020 * @param exe the path to the executable
1022 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
1024 bool success = false;
1025 #ifdef WITH_COCOA
1026 char *app_bundle = strchr(exe, '.');
1027 while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
1029 if (app_bundle != NULL) app_bundle[0] = '\0';
1030 #endif /* WITH_COCOA */
1031 char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
1032 if (s != NULL) {
1033 *s = '\0';
1034 #if defined(__DJGPP__)
1035 /* If we want to go to the root, we can't use cd C:, but we must use '/' */
1036 if (s[-1] == ':') chdir("/");
1037 #endif
1038 if (chdir(exe) != 0) {
1039 DEBUG(misc, 0, "Directory with the binary does not exist?");
1040 } else {
1041 success = true;
1043 *s = PATHSEPCHAR;
1045 #ifdef WITH_COCOA
1046 if (app_bundle != NULL) app_bundle[0] = '.';
1047 #endif /* WITH_COCOA */
1048 return success;
1052 * Whether we should scan the working directory.
1053 * It should not be scanned if it's the root or
1054 * the home directory as in both cases a big data
1055 * directory can cause huge amounts of unrelated
1056 * files scanned. Furthermore there are nearly no
1057 * use cases for the home/root directory to have
1058 * OpenTTD directories.
1059 * @return true if it should be scanned.
1061 bool DoScanWorkingDirectory()
1063 /* No working directory, so nothing to do. */
1064 if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
1066 /* Working directory is root, so do nothing. */
1067 if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
1069 /* No personal/home directory, so the working directory won't be that. */
1070 if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
1072 char tmp[MAX_PATH];
1073 snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
1074 AppendPathSeparator(tmp, MAX_PATH);
1075 return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
1079 * Determine the base (personal dir and game data dir) paths
1080 * @param exe the path to the executable
1082 void DetermineBasePaths(const char *exe)
1084 char tmp[MAX_PATH];
1085 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1086 const char *xdg_data_home = xdgDataHome(NULL);
1087 snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", xdg_data_home,
1088 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1089 free(xdg_data_home);
1091 AppendPathSeparator(tmp, MAX_PATH);
1092 _searchpaths[SP_PERSONAL_DIR_XDG] = strdup(tmp);
1093 #endif
1094 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
1095 _searchpaths[SP_PERSONAL_DIR] = NULL;
1096 #else
1097 #ifdef __HAIKU__
1098 BPath path;
1099 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
1100 const char *homedir = strdup(path.Path());
1101 #else
1102 /* getenv is highly unsafe; duplicate it as soon as possible,
1103 * or at least before something else touches the environment
1104 * variables in any way. It can also contain all kinds of
1105 * unvalidated data we rather not want internally. */
1106 const char *homedir = getenv("HOME");
1107 if (homedir != NULL) {
1108 homedir = strndup(homedir, MAX_PATH);
1111 if (homedir == NULL) {
1112 const struct passwd *pw = getpwuid(getuid());
1113 homedir = (pw == NULL) ? NULL : strdup(pw->pw_dir);
1115 #endif
1117 if (homedir != NULL) {
1118 ValidateString(homedir);
1119 snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
1120 AppendPathSeparator(tmp, MAX_PATH);
1122 _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
1123 free(homedir);
1124 } else {
1125 _searchpaths[SP_PERSONAL_DIR] = NULL;
1127 #endif
1129 #if defined(WITH_SHARED_DIR)
1130 snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
1131 AppendPathSeparator(tmp, MAX_PATH);
1132 _searchpaths[SP_SHARED_DIR] = strdup(tmp);
1133 #else
1134 _searchpaths[SP_SHARED_DIR] = NULL;
1135 #endif
1137 #if defined(__MORPHOS__) || defined(__AMIGA__)
1138 _searchpaths[SP_WORKING_DIR] = NULL;
1139 #else
1140 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1141 AppendPathSeparator(tmp, MAX_PATH);
1142 _searchpaths[SP_WORKING_DIR] = strdup(tmp);
1143 #endif
1145 _do_scan_working_directory = DoScanWorkingDirectory();
1147 /* Change the working directory to that one of the executable */
1148 if (ChangeWorkingDirectoryToExecutable(exe)) {
1149 if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
1150 AppendPathSeparator(tmp, MAX_PATH);
1151 _searchpaths[SP_BINARY_DIR] = strdup(tmp);
1152 } else {
1153 _searchpaths[SP_BINARY_DIR] = NULL;
1156 if (_searchpaths[SP_WORKING_DIR] != NULL) {
1157 /* Go back to the current working directory. */
1158 if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
1159 DEBUG(misc, 0, "Failed to return to working directory!");
1163 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
1164 _searchpaths[SP_INSTALLATION_DIR] = NULL;
1165 #else
1166 snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
1167 AppendPathSeparator(tmp, MAX_PATH);
1168 _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
1169 #endif
1170 #ifdef WITH_COCOA
1171 extern void cocoaSetApplicationBundleDir();
1172 cocoaSetApplicationBundleDir();
1173 #else
1174 _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
1175 #endif
1177 #endif /* defined(WIN32) || defined(WINCE) */
1179 const char *_personal_dir;
1182 * Acquire the base paths (personal dir and game data dir),
1183 * fill all other paths (save dir, autosave dir etc) and
1184 * make the save and scenario directories.
1185 * @param exe the path from the current path to the executable
1187 void DeterminePaths(const char *exe)
1189 DetermineBasePaths(exe);
1191 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1192 char config_home[MAX_PATH];
1194 const char *xdg_config_home = xdgConfigHome(NULL);
1195 snprintf(config_home, MAX_PATH, "%s" PATHSEP "%s", xdg_config_home,
1196 PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
1197 free(xdg_config_home);
1199 AppendPathSeparator(config_home, MAX_PATH);
1200 #endif
1202 Searchpath sp;
1203 FOR_ALL_SEARCHPATHS(sp) {
1204 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1205 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
1208 const char *config_dir;
1209 if (_config_file != NULL) {
1210 char *end = strrchr(_config_file, PATHSEPCHAR);
1211 if (end == NULL) {
1212 config_dir = "";
1213 } else {
1214 size_t n = (end - _config_file) + 1;
1215 char *dir = (char*)malloc(n + 1);
1216 memcpy(dir, _config_file, n);
1217 dir[n] = '\0';
1218 config_dir = dir;
1220 } else {
1221 char personal_dir[MAX_PATH];
1222 if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
1223 char *end = strrchr(personal_dir, PATHSEPCHAR);
1224 if (end != NULL) end[1] = '\0';
1225 config_dir = strdup(personal_dir);
1226 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1227 } else {
1228 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1229 /* No previous configuration file found. Use the configuration folder from XDG. */
1230 config_dir = config_home;
1231 #else
1232 static const Searchpath new_openttd_cfg_order[] = {
1233 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1236 config_dir = NULL;
1237 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1238 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1239 config_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
1240 break;
1243 assert(config_dir != NULL);
1244 #endif
1245 _config_file = str_fmt("%sopenttd.cfg", config_dir);
1249 DEBUG(misc, 3, "%s found as config directory", config_dir);
1251 _highscore_file = str_fmt("%shs.dat", config_dir);
1252 extern char *_hotkeys_file;
1253 _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
1254 extern char *_windows_file;
1255 _windows_file = str_fmt("%swindows.cfg", config_dir);
1257 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
1258 if (config_dir == config_home) {
1259 /* We are using the XDG configuration home for the config file,
1260 * then store the rest in the XDG data home folder. */
1261 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1262 FioCreateDirectory(_personal_dir);
1263 } else
1264 #endif
1266 _personal_dir = config_dir;
1269 /* Make the necessary folders */
1270 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
1271 FioCreateDirectory(config_dir);
1272 if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
1273 #endif
1275 DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
1277 static const Subdirectory default_subdirs[] = {
1278 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1281 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1282 char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
1283 FioCreateDirectory(dir);
1284 free(dir);
1287 /* If we have network we make a directory for the autodownloading of content */
1288 _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
1289 #ifdef ENABLE_NETWORK
1290 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1292 /* Create the directory for each of the types of content */
1293 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1294 for (uint i = 0; i < lengthof(dirs); i++) {
1295 char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
1296 FioCreateDirectory(tmp);
1297 free(tmp);
1300 extern char *_log_file;
1301 _log_file = str_fmt("%sopenttd.log", _personal_dir);
1302 #else /* ENABLE_NETWORK */
1303 /* If we don't have networking, we don't need to make the directory. But
1304 * if it exists we keep it, otherwise remove it from the search paths. */
1305 if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR])) {
1306 free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1307 _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
1309 #endif /* ENABLE_NETWORK */
1313 * Sanitizes a filename, i.e. removes all illegal characters from it.
1314 * @param filename the "\0" terminated filename
1316 void SanitizeFilename(char *filename)
1318 for (; *filename != '\0'; filename++) {
1319 switch (*filename) {
1320 /* The following characters are not allowed in filenames
1321 * on at least one of the supported operating systems: */
1322 case ':': case '\\': case '*': case '?': case '/':
1323 case '<': case '>': case '|': case '"':
1324 *filename = '_';
1325 break;
1331 * Load a file into memory.
1332 * @param filename Name of the file to load.
1333 * @param lenp [out] Length of loaded data.
1334 * @param maxsize Maximum size to load.
1335 * @return Pointer to new memory containing the loaded data, or \c NULL if loading failed.
1336 * @note If \a maxsize less than the length of the file, loading fails.
1338 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
1340 FILE *in = fopen(filename, "rb");
1341 if (in == NULL) return NULL;
1343 fseek(in, 0, SEEK_END);
1344 size_t len = ftell(in);
1345 fseek(in, 0, SEEK_SET);
1346 if (len > maxsize) {
1347 fclose(in);
1348 return NULL;
1350 byte *mem = MallocT<byte>(len + 1);
1351 mem[len] = 0;
1352 if (fread(mem, len, 1, in) != 1) {
1353 fclose(in);
1354 free(mem);
1355 return NULL;
1357 fclose(in);
1359 *lenp = len;
1360 return mem;
1364 * Helper to see whether a given filename matches the extension.
1365 * @param extension The extension to look for.
1366 * @param filename The filename to look in for the extension.
1367 * @return True iff the extension is NULL, or the filename ends with it.
1369 static bool MatchesExtension(const char *extension, const char *filename)
1371 if (extension == NULL) return true;
1373 const char *ext = strrchr(filename, extension[0]);
1374 return ext != NULL && strcasecmp(ext, extension) == 0;
1378 * Scan a single directory (and recursively its children) and add
1379 * any graphics sets that are found.
1380 * @param fs the file scanner to add the files to
1381 * @param extension the extension of files to search for.
1382 * @param path full path we're currently at
1383 * @param basepath_length from where in the path are we 'based' on the search path
1384 * @param recursive whether to recursively search the sub directories
1386 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1388 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1390 uint num = 0;
1391 struct stat sb;
1392 struct dirent *dirent;
1393 DIR *dir;
1395 if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
1397 while ((dirent = readdir(dir)) != NULL) {
1398 const char *d_name = FS2OTTD(dirent->d_name);
1399 char filename[MAX_PATH];
1401 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1403 snprintf(filename, lengthof(filename), "%s%s", path, d_name);
1405 if (S_ISDIR(sb.st_mode)) {
1406 /* Directory */
1407 if (!recursive) continue;
1408 if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
1409 if (!AppendPathSeparator(filename, lengthof(filename))) continue;
1410 num += ScanPath(fs, extension, filename, basepath_length, recursive);
1411 } else if (S_ISREG(sb.st_mode)) {
1412 /* File */
1413 if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
1417 closedir(dir);
1419 return num;
1423 * Scan the given tar and add graphics sets when it finds one.
1424 * @param fs the file scanner to scan for
1425 * @param extension the extension of files to search for.
1426 * @param tar the tar to search in.
1428 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1430 uint num = 0;
1431 const char *filename = (*tar).first.c_str();
1433 if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1435 return num;
1439 * Scan for files with the given extension in the given search path.
1440 * @param extension the extension of files to search for.
1441 * @param sd the sub directory to search in.
1442 * @param tars whether to search in the tars too.
1443 * @param recursive whether to search recursively
1444 * @return the number of found files, i.e. the number of times that
1445 * AddFile returned true.
1447 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1449 this->subdir = sd;
1451 Searchpath sp;
1452 char path[MAX_PATH];
1453 TarFileList::iterator tar;
1454 uint num = 0;
1456 FOR_ALL_SEARCHPATHS(sp) {
1457 /* Don't search in the working directory */
1458 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1460 FioAppendDirectory(path, MAX_PATH, sp, sd);
1461 num += ScanPath(this, extension, path, strlen(path), recursive);
1464 if (tars && sd != NO_DIRECTORY) {
1465 FOR_ALL_TARS(tar, sd) {
1466 num += ScanTar(this, extension, tar);
1470 switch (sd) {
1471 case BASESET_DIR:
1472 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1473 /* FALL THROUGH */
1474 case NEWGRF_DIR:
1475 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1476 break;
1478 default: break;
1481 return num;
1485 * Scan for files with the given extension in the given search path.
1486 * @param extension the extension of files to search for.
1487 * @param directory the sub directory to search in.
1488 * @param recursive whether to search recursively
1489 * @return the number of found files, i.e. the number of times that
1490 * AddFile returned true.
1492 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1494 char path[MAX_PATH];
1495 strecpy(path, directory, lastof(path));
1496 if (!AppendPathSeparator(path, lengthof(path))) return 0;
1497 return ScanPath(this, extension, path, strlen(path), recursive);