# fileio.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "fileio_func.h"
    00014 #include "debug.h"
    00015 #include "fios.h"
    00016 #include "string_func.h"
    00017 #include "tar_type.h"
    00018 #ifdef WIN32
    00019 #include <windows.h>
    00020 # define access _taccess
    00021 #elif defined(__HAIKU__)
    00022 #include <Path.h>
    00023 #include <storage/FindDirectory.h>
    00024 #else
    00025 #include <unistd.h>
    00026 #include <pwd.h>
    00027 #endif
    00028 #include <sys/stat.h>
    00029 #include <algorithm>
    00030
    00032 #define FIO_BUFFER_SIZE 512
    00033
    00035 struct Fio {
    00036   byte *buffer, *buffer_end;
    00037   size_t pos;
    00038   FILE *cur_fh;
    00039   const char *filename;
    00040   FILE *handles[MAX_FILE_SLOTS];
    00041   byte buffer_start[FIO_BUFFER_SIZE];
    00042   const char *filenames[MAX_FILE_SLOTS];
    00043   char *shortnames[MAX_FILE_SLOTS];
    00044 #if defined(LIMITED_FDS)
    00045   uint open_handles;
    00046   uint usage_count[MAX_FILE_SLOTS];
    00047 #endif /* LIMITED_FDS */
    00048 };
    00049
    00050 static Fio _fio;
    00051
    00053 static bool _do_scan_working_directory = true;
    00054
    00055 extern char *_config_file;
    00056 extern char *_highscore_file;
    00057
    00062 size_t FioGetPos()
    00063 {
    00064   return _fio.pos + (_fio.buffer - _fio.buffer_end);
    00065 }
    00066
    00072 const char *FioGetFilename(uint8 slot)
    00073 {
    00074   return _fio.shortnames[slot];
    00075 }
    00076
    00082 void FioSeekTo(size_t pos, int mode)
    00083 {
    00084   if (mode == SEEK_CUR) pos += FioGetPos();
    00085   _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
    00086   _fio.pos = pos;
    00087   fseek(_fio.cur_fh, _fio.pos, SEEK_SET);
    00088 }
    00089
    00090 #if defined(LIMITED_FDS)
    00091 static void FioRestoreFile(int slot)
    00092 {
    00093   /* Do we still have the file open, or should we reopen it? */
    00094   if (_fio.handles[slot] == NULL) {
    00095     DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
    00096     FioOpenFile(slot, _fio.filenames[slot]);
    00097   }
    00098   _fio.usage_count[slot]++;
    00099 }
    00100 #endif /* LIMITED_FDS */
    00101
    00107 void FioSeekToFile(uint8 slot, size_t pos)
    00108 {
    00109   FILE *f;
    00110 #if defined(LIMITED_FDS)
    00111   /* Make sure we have this file open */
    00112   FioRestoreFile(slot);
    00113 #endif /* LIMITED_FDS */
    00114   f = _fio.handles[slot];
    00115   assert(f != NULL);
    00116   _fio.cur_fh = f;
    00117   _fio.filename = _fio.filenames[slot];
    00118   FioSeekTo(pos, SEEK_SET);
    00119 }
    00120
    00125 byte FioReadByte()
    00126 {
    00127   if (_fio.buffer == _fio.buffer_end) {
    00128     _fio.buffer = _fio.buffer_start;
    00129     size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
    00130     _fio.pos += size;
    00131     _fio.buffer_end = _fio.buffer_start + size;
    00132
    00133     if (size == 0) return 0;
    00134   }
    00135   return *_fio.buffer++;
    00136 }
    00137
    00142 void FioSkipBytes(int n)
    00143 {
    00144   for (;;) {
    00145     int m = min(_fio.buffer_end - _fio.buffer, n);
    00146     _fio.buffer += m;
    00147     n -= m;
    00148     if (n == 0) break;
    00149     FioReadByte();
    00150     n--;
    00151   }
    00152 }
    00153
    00158 uint16 FioReadWord()
    00159 {
    00160   byte b = FioReadByte();
    00161   return (FioReadByte() << 8) | b;
    00162 }
    00163
    00168 uint32 FioReadDword()
    00169 {
    00170   uint b = FioReadWord();
    00171   return (FioReadWord() << 16) | b;
    00172 }
    00173
    00179 void FioReadBlock(void *ptr, size_t size)
    00180 {
    00181   FioSeekTo(FioGetPos(), SEEK_SET);
    00182   _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
    00183 }
    00184
    00189 static inline void FioCloseFile(int slot)
    00190 {
    00191   if (_fio.handles[slot] != NULL) {
    00192     fclose(_fio.handles[slot]);
    00193
    00194     free(_fio.shortnames[slot]);
    00195     _fio.shortnames[slot] = NULL;
    00196
    00197     _fio.handles[slot] = NULL;
    00198 #if defined(LIMITED_FDS)
    00199     _fio.open_handles--;
    00200 #endif /* LIMITED_FDS */
    00201   }
    00202 }
    00203
    00205 void FioCloseAll()
    00206 {
    00207   for (int i = 0; i != lengthof(_fio.handles); i++) {
    00208     FioCloseFile(i);
    00209   }
    00210 }
    00211
    00212 #if defined(LIMITED_FDS)
    00213 static void FioFreeHandle()
    00214 {
    00215   /* If we are about to open a file that will exceed the limit, close a file */
    00216   if (_fio.open_handles + 1 == LIMITED_FDS) {
    00217     uint i, count;
    00218     int slot;
    00219
    00220     count = UINT_MAX;
    00221     slot = -1;
    00222     /* Find the file that is used the least */
    00223     for (i = 0; i < lengthof(_fio.handles); i++) {
    00224       if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
    00225         count = _fio.usage_count[i];
    00226         slot  = i;
    00227       }
    00228     }
    00229     assert(slot != -1);
    00230     DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
    00231     FioCloseFile(slot);
    00232   }
    00233 }
    00234 #endif /* LIMITED_FDS */
    00235
    00242 void FioOpenFile(int slot, const char *filename, Subdirectory subdir)
    00243 {
    00244   FILE *f;
    00245
    00246 #if defined(LIMITED_FDS)
    00247   FioFreeHandle();
    00248 #endif /* LIMITED_FDS */
    00249   f = FioFOpenFile(filename, "rb", subdir);
    00250   if (f == NULL) usererror("Cannot open file '%s'", filename);
    00251   uint32 pos = ftell(f);
    00252
    00253   FioCloseFile(slot); // if file was opened before, close it
    00254   _fio.handles[slot] = f;
    00255   _fio.filenames[slot] = filename;
    00256
    00257   /* Store the filename without path and extension */
    00258   const char *t = strrchr(filename, PATHSEPCHAR);
    00259   _fio.shortnames[slot] = strdup(t == NULL ? filename : t);
    00260   char *t2 = strrchr(_fio.shortnames[slot], '.');
    00261   if (t2 != NULL) *t2 = '\0';
    00262   strtolower(_fio.shortnames[slot]);
    00263
    00264 #if defined(LIMITED_FDS)
    00265   _fio.usage_count[slot] = 0;
    00266   _fio.open_handles++;
    00267 #endif /* LIMITED_FDS */
    00268   FioSeekToFile(slot, pos);
    00269 }
    00270
    00271 static const char * const _subdirs[] = {
    00272   "",
    00273   "save" PATHSEP,
    00274   "save" PATHSEP "autosave" PATHSEP,
    00275   "scenario" PATHSEP,
    00276   "scenario" PATHSEP "heightmap" PATHSEP,
    00277   "gm" PATHSEP,
    00278   "data" PATHSEP,
    00279   "baseset" PATHSEP,
    00280   "newgrf" PATHSEP,
    00281   "lang" PATHSEP,
    00282   "ai" PATHSEP,
    00283   "ai" PATHSEP "library" PATHSEP,
    00284   "game" PATHSEP,
    00285   "game" PATHSEP "library" PATHSEP,
    00286 };
    00287 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
    00288
    00289 const char *_searchpaths[NUM_SEARCHPATHS];
    00290 TarList _tar_list[NUM_SUBDIRS];
    00291 TarFileList _tar_filelist[NUM_SUBDIRS];
    00292
    00293 typedef std::map<std::string, std::string> TarLinkList;
    00294 static TarLinkList _tar_linklist[NUM_SUBDIRS];
    00295
    00302 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
    00303 {
    00304   FILE *f = FioFOpenFile(filename, "rb", subdir);
    00305   if (f == NULL) return false;
    00306
    00307   FioFCloseFile(f);
    00308   return true;
    00309 }
    00310
    00316 bool FileExists(const char *filename)
    00317 {
    00318 #if defined(WINCE)
    00319   /* There is always one platform that doesn't support basic commands... */
    00320   HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    00321   if (hand == INVALID_HANDLE_VALUE) return 1;
    00322   CloseHandle(hand);
    00323   return 0;
    00324 #else
    00325   return access(OTTD2FS(filename), 0) == 0;
    00326 #endif
    00327 }
    00328
    00332 void FioFCloseFile(FILE *f)
    00333 {
    00334   fclose(f);
    00335 }
    00336
    00337 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
    00338 {
    00339   assert(subdir < NUM_SUBDIRS);
    00340   assert(sp < NUM_SEARCHPATHS);
    00341
    00342   snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00343   return buf;
    00344 }
    00345
    00354 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
    00355 {
    00356   Searchpath sp;
    00357   assert(subdir < NUM_SUBDIRS);
    00358
    00359   FOR_ALL_SEARCHPATHS(sp) {
    00360     FioGetFullPath(buf, buflen, sp, subdir, filename);
    00361     if (FileExists(buf)) return buf;
    00362 #if !defined(WIN32)
    00363     /* Be, as opening files, aware that sometimes the filename
    00364      * might be in uppercase when it is in lowercase on the
    00365      * disk. Of course Windows doesn't care about casing. */
    00366     if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
    00367 #endif
    00368   }
    00369
    00370   return NULL;
    00371 }
    00372
    00373 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
    00374 {
    00375   assert(subdir < NUM_SUBDIRS);
    00376   assert(sp < NUM_SEARCHPATHS);
    00377
    00378   snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
    00379   return buf;
    00380 }
    00381
    00382 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
    00383 {
    00384   Searchpath sp;
    00385
    00386   /* Find and return the first valid directory */
    00387   FOR_ALL_SEARCHPATHS(sp) {
    00388     char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
    00389     if (FileExists(buf)) return ret;
    00390   }
    00391
    00392   /* Could not find the directory, fall back to a base path */
    00393   ttd_strlcpy(buf, _personal_dir, buflen);
    00394
    00395   return buf;
    00396 }
    00397
    00398 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
    00399 {
    00400 #if defined(WIN32) && defined(UNICODE)
    00401   /* fopen is implemented as a define with ellipses for
    00402    * Unicode support (prepend an L). As we are not sending
    00403    * a string, but a variable, it 'renames' the variable,
    00404    * so make that variable to makes it compile happily */
    00405   wchar_t Lmode[5];
    00406   MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
    00407 #endif
    00408   FILE *f = NULL;
    00409   char buf[MAX_PATH];
    00410
    00411   if (subdir == NO_DIRECTORY) {
    00412     strecpy(buf, filename, lastof(buf));
    00413   } else {
    00414     snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00415   }
    00416
    00417 #if defined(WIN32)
    00418   if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
    00419 #endif
    00420
    00421   f = fopen(buf, mode);
    00422 #if !defined(WIN32)
    00423   if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
    00424     f = fopen(buf, mode);
    00425   }
    00426 #endif
    00427   if (f != NULL && filesize != NULL) {
    00428     /* Find the size of the file */
    00429     fseek(f, 0, SEEK_END);
    00430     *filesize = ftell(f);
    00431     fseek(f, 0, SEEK_SET);
    00432   }
    00433   return f;
    00434 }
    00435
    00443 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
    00444 {
    00445   FILE *f = fopen(entry->tar_filename, "rb");
    00446   if (f == NULL) return f;
    00447
    00448   fseek(f, entry->position, SEEK_SET);
    00449   if (filesize != NULL) *filesize = entry->size;
    00450   return f;
    00451 }
    00452
    00460 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
    00461 {
    00462   FILE *f = NULL;
    00463   Searchpath sp;
    00464
    00465   assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
    00466
    00467   FOR_ALL_SEARCHPATHS(sp) {
    00468     f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
    00469     if (f != NULL || subdir == NO_DIRECTORY) break;
    00470   }
    00471
    00472   /* We can only use .tar in case of data-dir, and read-mode */
    00473   if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
    00474     static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
    00475     char resolved_name[MAX_RESOLVED_LENGTH];
    00476
    00477     /* Filenames in tars are always forced to be lowercase */
    00478     strecpy(resolved_name, filename, lastof(resolved_name));
    00479     strtolower(resolved_name);
    00480
    00481     size_t resolved_len = strlen(resolved_name);
    00482
    00483     /* Resolve ONE directory link */
    00484     for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
    00485       const std::string &src = link->first;
    00486       size_t len = src.length();
    00487       if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
    00488         /* Apply link */
    00489         char resolved_name2[MAX_RESOLVED_LENGTH];
    00490         const std::string &dest = link->second;
    00491         strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
    00492         strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
    00493         strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
    00494         break; // Only resolve one level
    00495       }
    00496     }
    00497
    00498     TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
    00499     if (it != _tar_filelist[subdir].end()) {
    00500       f = FioFOpenFileTar(&((*it).second), filesize);
    00501     }
    00502   }
    00503
    00504   /* Sometimes a full path is given. To support
    00505    * the 'subdirectory' must be 'removed'. */
    00506   if (f == NULL && subdir != NO_DIRECTORY) {
    00507     switch (subdir) {
    00508       case BASESET_DIR:
    00509         f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
    00510         if (f != NULL) break;
    00511         /* FALL THROUGH */
    00512       case NEWGRF_DIR:
    00513         f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
    00514         break;
    00515
    00516       default:
    00517         f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
    00518         break;
    00519     }
    00520   }
    00521
    00522   return f;
    00523 }
    00524
    00529 static void FioCreateDirectory(const char *name)
    00530 {
    00531 #if defined(WIN32) || defined(WINCE)
    00532   CreateDirectory(OTTD2FS(name), NULL);
    00533 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
    00534   mkdir(OTTD2FS(name));
    00535 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
    00536   char buf[MAX_PATH];
    00537   ttd_strlcpy(buf, name, MAX_PATH);
    00538
    00539   size_t len = strlen(name) - 1;
    00540   if (buf[len] == '/') {
    00541     buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
    00542   }
    00543
    00544   mkdir(OTTD2FS(buf), 0755);
    00545 #else
    00546   mkdir(OTTD2FS(name), 0755);
    00547 #endif
    00548 }
    00549
    00557 bool AppendPathSeparator(char *buf, size_t buflen)
    00558 {
    00559   size_t s = strlen(buf);
    00560
    00561   /* Length of string + path separator + '\0' */
    00562   if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
    00563     if (s + 2 >= buflen) return false;
    00564
    00565     buf[s]     = PATHSEPCHAR;
    00566     buf[s + 1] = '\0';
    00567   }
    00568
    00569   return true;
    00570 }
    00571
    00578 char *BuildWithFullPath(const char *dir)
    00579 {
    00580   char *dest = MallocT<char>(MAX_PATH);
    00581   ttd_strlcpy(dest, dir, MAX_PATH);
    00582
    00583   /* Check if absolute or relative path */
    00584   const char *s = strchr(dest, PATHSEPCHAR);
    00585
    00586   /* Add absolute path */
    00587   if (s == NULL || dest != s) {
    00588     if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
    00589     AppendPathSeparator(dest, MAX_PATH);
    00590     ttd_strlcat(dest, dir, MAX_PATH);
    00591   }
    00592   AppendPathSeparator(dest, MAX_PATH);
    00593
    00594   return dest;
    00595 }
    00596
    00602 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
    00603 {
    00604   TarList::iterator it = _tar_list[subdir].find(tarname);
    00605   if (it == _tar_list[subdir].end()) return NULL;
    00606   return (*it).second.dirname;
    00607 }
    00608
    00609 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
    00610 {
    00611   std::string src = srcParam;
    00612   std::string dest = destParam;
    00613   /* Tar internals assume lowercase */
    00614   std::transform(src.begin(), src.end(), src.begin(), tolower);
    00615   std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
    00616
    00617   TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
    00618   if (dest_file != _tar_filelist[subdir].end()) {
    00619     /* Link to file. Process the link like the destination file. */
    00620     _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
    00621   } else {
    00622     /* Destination file not found. Assume 'link to directory'
    00623      * Append PATHSEPCHAR to 'src' and 'dest' if needed */
    00624     const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
    00625     const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
    00626     _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
    00627   }
    00628 }
    00629
    00630 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
    00631 {
    00632   TarAddLink(src, dest, subdir);
    00633 }
    00634
    00640 static void SimplifyFileName(char *name)
    00641 {
    00642   /* Force lowercase */
    00643   strtolower(name);
    00644
    00645   /* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
    00646 #if (PATHSEPCHAR != '/')
    00647   for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
    00648 #endif
    00649 }
    00650
    00656 uint TarScanner::DoScan(Subdirectory sd)
    00657 {
    00658   _tar_filelist[sd].clear();
    00659   _tar_list[sd].clear();
    00660   uint num = this->Scan(".tar", sd, false);
    00661   if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
    00662   return num;
    00663 }
    00664
    00665 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
    00666 {
    00667   DEBUG(misc, 1, "Scanning for tars");
    00668   TarScanner fs;
    00669   uint num = 0;
    00670   if (mode & TarScanner::BASESET) {
    00671     num += fs.DoScan(BASESET_DIR);
    00672   }
    00673   if (mode & TarScanner::NEWGRF) {
    00674     num += fs.DoScan(NEWGRF_DIR);
    00675   }
    00676   if (mode & TarScanner::AI) {
    00677     num += fs.DoScan(AI_DIR);
    00678     num += fs.DoScan(AI_LIBRARY_DIR);
    00679   }
    00680   if (mode & TarScanner::GAME) {
    00681     num += fs.DoScan(GAME_DIR);
    00682     num += fs.DoScan(GAME_LIBRARY_DIR);
    00683   }
    00684   if (mode & TarScanner::SCENARIO) {
    00685     num += fs.DoScan(SCENARIO_DIR);
    00686     num += fs.DoScan(HEIGHTMAP_DIR);
    00687   }
    00688   DEBUG(misc, 1, "Scan complete, found %d files", num);
    00689   return num;
    00690 }
    00691
    00698 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
    00699 {
    00700   this->subdir = sd;
    00701   return this->AddFile(filename, 0);
    00702 }
    00703
    00704 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00705 {
    00706   /* No tar within tar. */
    00707   assert(tar_filename == NULL);
    00708
    00709   /* The TAR-header, repeated for every file */
    00710   typedef struct TarHeader {
    00711     char name[100];
    00712     char mode[8];
    00713     char uid[8];
    00714     char gid[8];
    00715     char size[12];
    00716     char mtime[12];
    00717     char chksum[8];
    00718     char typeflag;
    00719     char linkname[100];
    00720     char magic[6];
    00721     char version[2];
    00722     char uname[32];
    00723     char gname[32];
    00724     char devmajor[8];
    00725     char devminor[8];
    00726     char prefix[155];
    00727
    00728     char unused[12];
    00729   } TarHeader;
    00730
    00731   /* Check if we already seen this file */
    00732   TarList::iterator it = _tar_list[this->subdir].find(filename);
    00733   if (it != _tar_list[this->subdir].end()) return false;
    00734
    00735   FILE *f = fopen(filename, "rb");
    00736   /* Although the file has been found there can be
    00737    * a number of reasons we cannot open the file.
    00738    * Most common case is when we simply have not
    00739    * been given read access. */
    00740   if (f == NULL) return false;
    00741
    00742   const char *dupped_filename = strdup(filename);
    00743   _tar_list[this->subdir][filename].filename = dupped_filename;
    00744   _tar_list[this->subdir][filename].dirname = NULL;
    00745
    00746   TarLinkList links;
    00747
    00748   TarHeader th;
    00749   char buf[sizeof(th.name) + 1], *end;
    00750   char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
    00751   char link[sizeof(th.linkname) + 1];
    00752   char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
    00753   size_t num = 0, pos = 0;
    00754
    00755   /* Make a char of 512 empty bytes */
    00756   char empty[512];
    00757   memset(&empty[0], 0, sizeof(empty));
    00758
    00759   for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
    00760     size_t num_bytes_read = fread(&th, 1, 512, f);
    00761     if (num_bytes_read != 512) break;
    00762     pos += num_bytes_read;
    00763
    00764     /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
    00765     if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
    00766       /* If we have only zeros in the block, it can be an end-of-file indicator */
    00767       if (memcmp(&th, &empty[0], 512) == 0) continue;
    00768
    00769       DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
    00770       return false;
    00771     }
    00772
    00773     name[0] = '\0';
    00774     size_t len = 0;
    00775
    00776     /* The prefix contains the directory-name */
    00777     if (th.prefix[0] != '\0') {
    00778       memcpy(name, th.prefix, sizeof(th.prefix));
    00779       name[sizeof(th.prefix)] = '\0';
    00780       len = strlen(name);
    00781       name[len] = PATHSEPCHAR;
    00782       len++;
    00783     }
    00784
    00785     /* Copy the name of the file in a safe way at the end of 'name' */
    00786     memcpy(&name[len], th.name, sizeof(th.name));
    00787     name[len + sizeof(th.name)] = '\0';
    00788
    00789     /* Calculate the size of the file.. for some strange reason this is stored as a string */
    00790     memcpy(buf, th.size, sizeof(th.size));
    00791     buf[sizeof(th.size)] = '\0';
    00792     size_t skip = strtoul(buf, &end, 8);
    00793
    00794     switch (th.typeflag) {
    00795       case '\0':
    00796       case '0': { // regular file
    00797         /* Ignore empty files */
    00798         if (skip == 0) break;
    00799
    00800         if (strlen(name) == 0) break;
    00801
    00802         /* Store this entry in the list */
    00803         TarFileListEntry entry;
    00804         entry.tar_filename = dupped_filename;
    00805         entry.size         = skip;
    00806         entry.position     = pos;
    00807
    00808         /* Convert to lowercase and our PATHSEPCHAR */
    00809         SimplifyFileName(name);
    00810
    00811         DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
    00812         if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
    00813
    00814         break;
    00815       }
    00816
    00817       case '1': // hard links
    00818       case '2': { // symbolic links
    00819         /* Copy the destination of the link in a safe way at the end of 'linkname' */
    00820         memcpy(link, th.linkname, sizeof(th.linkname));
    00821         link[sizeof(th.linkname)] = '\0';
    00822
    00823         if (strlen(name) == 0 || strlen(link) == 0) break;
    00824
    00825         /* Convert to lowercase and our PATHSEPCHAR */
    00826         SimplifyFileName(name);
    00827         SimplifyFileName(link);
    00828
    00829         /* Only allow relative links */
    00830         if (link[0] == PATHSEPCHAR) {
    00831           DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
    00832           break;
    00833         }
    00834
    00835         /* Process relative path.
    00836          * Note: The destination of links must not contain any directory-links. */
    00837         strecpy(dest, name, lastof(dest));
    00838         char *destpos = strrchr(dest, PATHSEPCHAR);
    00839         if (destpos == NULL) destpos = dest;
    00840         *destpos = '\0';
    00841
    00842         char *pos = link;
    00843         while (*pos != '\0') {
    00844           char *next = strchr(link, PATHSEPCHAR);
    00845           if (next == NULL) next = pos + strlen(pos);
    00846
    00847           /* Skip '.' (current dir) */
    00848           if (next != pos + 1 || pos[0] != '.') {
    00849             if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
    00850               /* level up */
    00851               if (dest[0] == '\0') {
    00852                 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
    00853                 break;
    00854               }
    00855
    00856               /* Truncate 'dest' after last PATHSEPCHAR.
    00857                * This assumes that the truncated part is a real directory and not a link. */
    00858               destpos = strrchr(dest, PATHSEPCHAR);
    00859               if (destpos == NULL) destpos = dest;
    00860             } else {
    00861               /* Append at end of 'dest' */
    00862               if (destpos != dest) *(destpos++) = PATHSEPCHAR;
    00863               strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
    00864               destpos += next - pos;
    00865             }
    00866             *destpos = '\0';
    00867           }
    00868
    00869           pos = next;
    00870         }
    00871
    00872         /* Store links in temporary list */
    00873         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
    00874         links.insert(TarLinkList::value_type(name, dest));
    00875
    00876         break;
    00877       }
    00878
    00879       case '5': // directory
    00880         /* Convert to lowercase and our PATHSEPCHAR */
    00881         SimplifyFileName(name);
    00882
    00883         /* Store the first directory name we detect */
    00884         DEBUG(misc, 6, "Found dir in tar: %s", name);
    00885         if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = strdup(name);
    00886         break;
    00887
    00888       default:
    00889         /* Ignore other types */
    00890         break;
    00891     }
    00892
    00893     /* Skip to the next block.. */
    00894     skip = Align(skip, 512);
    00895     fseek(f, skip, SEEK_CUR);
    00896     pos += skip;
    00897   }
    00898
    00899   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
    00900   fclose(f);
    00901
    00902   /* Resolve file links and store directory links.
    00903    * We restrict usage of links to two cases:
    00904    *  1) Links to directories:
    00905    *      Both the source path and the destination path must NOT contain any further links.
    00906    *      When resolving files at most one directory link is resolved.
    00907    *  2) Links to files:
    00908    *      The destination path must NOT contain any links.
    00909    *      The source path may contain one directory link.
    00910    */
    00911   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
    00912     const std::string &src = link->first;
    00913     const std::string &dest = link->second;
    00914     TarAddLink(src, dest, this->subdir);
    00915   }
    00916
    00917   return true;
    00918 }
    00919
    00927 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
    00928 {
    00929   TarList::iterator it = _tar_list[subdir].find(tar_filename);
    00930   /* We don't know the file. */
    00931   if (it == _tar_list[subdir].end()) return false;
    00932
    00933   const char *dirname = (*it).second.dirname;
    00934
    00935   /* The file doesn't have a sub directory! */
    00936   if (dirname == NULL) return false;
    00937
    00938   char filename[MAX_PATH];
    00939   strecpy(filename, tar_filename, lastof(filename));
    00940   char *p = strrchr(filename, PATHSEPCHAR);
    00941   /* The file's path does not have a separator? */
    00942   if (p == NULL) return false;
    00943
    00944   p++;
    00945   strecpy(p, dirname, lastof(filename));
    00946   DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
    00947   FioCreateDirectory(filename);
    00948
    00949   for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
    00950     if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
    00951
    00952     strecpy(p, (*it2).first.c_str(), lastof(filename));
    00953
    00954     DEBUG(misc, 9, "  extracting %s", filename);
    00955
    00956     /* First open the file in the .tar. */
    00957     size_t to_copy = 0;
    00958     FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
    00959     if (in == NULL) {
    00960       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
    00961       return false;
    00962     }
    00963
    00964     /* Now open the 'output' file. */
    00965     FILE *out = fopen(filename, "wb");
    00966     if (out == NULL) {
    00967       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
    00968       fclose(in);
    00969       return false;
    00970     }
    00971
    00972     /* Now read from the tar and write it into the file. */
    00973     char buffer[4096];
    00974     size_t read;
    00975     for (; to_copy != 0; to_copy -= read) {
    00976       read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
    00977       if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
    00978     }
    00979
    00980     /* Close everything up. */
    00981     fclose(in);
    00982     fclose(out);
    00983
    00984     if (to_copy != 0) {
    00985       DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
    00986       return false;
    00987     }
    00988   }
    00989
    00990   DEBUG(misc, 9, "  extraction successful");
    00991   return true;
    00992 }
    00993
    00994 #if defined(WIN32) || defined(WINCE)
    00995
    01000 extern void DetermineBasePaths(const char *exe);
    01001 #else /* defined(WIN32) || defined(WINCE) */
    01002
    01010 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
    01011 {
    01012   bool success = false;
    01013 #ifdef WITH_COCOA
    01014   char *app_bundle = strchr(exe, '.');
    01015   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
    01016
    01017   if (app_bundle != NULL) app_bundle[0] = '\0';
    01018 #endif /* WITH_COCOA */
    01019   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
    01020   if (s != NULL) {
    01021     *s = '\0';
    01022 #if defined(__DJGPP__)
    01023     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
    01024     if (s[-1] == ':') chdir("/");
    01025 #endif
    01026     if (chdir(exe) != 0) {
    01027       DEBUG(misc, 0, "Directory with the binary does not exist?");
    01028     } else {
    01029       success = true;
    01030     }
    01031     *s = PATHSEPCHAR;
    01032   }
    01033 #ifdef WITH_COCOA
    01034   if (app_bundle != NULL) app_bundle[0] = '.';
    01035 #endif /* WITH_COCOA */
    01036   return success;
    01037 }
    01038
    01049 bool DoScanWorkingDirectory()
    01050 {
    01051   /* No working directory, so nothing to do. */
    01052   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
    01053
    01054   /* Working directory is root, so do nothing. */
    01055   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
    01056
    01057   /* No personal/home directory, so the working directory won't be that. */
    01058   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
    01059
    01060   char tmp[MAX_PATH];
    01061   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
    01062   AppendPathSeparator(tmp, MAX_PATH);
    01063   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
    01064 }
    01065
    01070 void DetermineBasePaths(const char *exe)
    01071 {
    01072   char tmp[MAX_PATH];
    01073 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
    01074   _searchpaths[SP_PERSONAL_DIR] = NULL;
    01075 #else
    01076 #ifdef __HAIKU__
    01077   BPath path;
    01078   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
    01079   const char *homedir = path.Path();
    01080 #else
    01081   const char *homedir = getenv("HOME");
    01082
    01083   if (homedir == NULL) {
    01084     const struct passwd *pw = getpwuid(getuid());
    01085     homedir = (pw == NULL) ? "" : pw->pw_dir;
    01086   }
    01087 #endif
    01088
    01089   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
    01090   AppendPathSeparator(tmp, MAX_PATH);
    01091
    01092   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
    01093 #endif
    01094
    01095 #if defined(WITH_SHARED_DIR)
    01096   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
    01097   AppendPathSeparator(tmp, MAX_PATH);
    01098   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
    01099 #else
    01100   _searchpaths[SP_SHARED_DIR] = NULL;
    01101 #endif
    01102
    01103 #if defined(__MORPHOS__) || defined(__AMIGA__)
    01104   _searchpaths[SP_WORKING_DIR] = NULL;
    01105 #else
    01106   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    01107   AppendPathSeparator(tmp, MAX_PATH);
    01108   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
    01109 #endif
    01110
    01111   _do_scan_working_directory = DoScanWorkingDirectory();
    01112
    01113   /* Change the working directory to that one of the executable */
    01114   if (ChangeWorkingDirectoryToExecutable(exe)) {
    01115     if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    01116     AppendPathSeparator(tmp, MAX_PATH);
    01117     _searchpaths[SP_BINARY_DIR] = strdup(tmp);
    01118   } else {
    01119     _searchpaths[SP_BINARY_DIR] = NULL;
    01120   }
    01121
    01122   if (_searchpaths[SP_WORKING_DIR] != NULL) {
    01123     /* Go back to the current working directory. */
    01124     if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
    01125       DEBUG(misc, 0, "Failed to return to working directory!");
    01126     }
    01127   }
    01128
    01129 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
    01130   _searchpaths[SP_INSTALLATION_DIR] = NULL;
    01131 #else
    01132   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
    01133   AppendPathSeparator(tmp, MAX_PATH);
    01134   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
    01135 #endif
    01136 #ifdef WITH_COCOA
    01137 extern void cocoaSetApplicationBundleDir();
    01138   cocoaSetApplicationBundleDir();
    01139 #else
    01140   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
    01141 #endif
    01142 }
    01143 #endif /* defined(WIN32) || defined(WINCE) */
    01144
    01145 char *_personal_dir;
    01146
    01153 void DeterminePaths(const char *exe)
    01154 {
    01155   DetermineBasePaths(exe);
    01156
    01157   Searchpath sp;
    01158   FOR_ALL_SEARCHPATHS(sp) {
    01159     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01160     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
    01161   }
    01162
    01163   if (_config_file != NULL) {
    01164     _personal_dir = strdup(_config_file);
    01165     char *end = strrchr(_personal_dir, PATHSEPCHAR);
    01166     if (end == NULL) {
    01167       _personal_dir[0] = '\0';
    01168     } else {
    01169       end[1] = '\0';
    01170     }
    01171   } else {
    01172     char personal_dir[MAX_PATH];
    01173     if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
    01174       char *end = strrchr(personal_dir, PATHSEPCHAR);
    01175       if (end != NULL) end[1] = '\0';
    01176       _personal_dir = strdup(personal_dir);
    01177       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
    01178     } else {
    01179       static const Searchpath new_openttd_cfg_order[] = {
    01180           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
    01181         };
    01182
    01183       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
    01184         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
    01185           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
    01186           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
    01187           break;
    01188         }
    01189       }
    01190     }
    01191   }
    01192
    01193   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
    01194
    01195   _highscore_file = str_fmt("%shs.dat", _personal_dir);
    01196   extern char *_hotkeys_file;
    01197   _hotkeys_file = str_fmt("%shotkeys.cfg",  _personal_dir);
    01198
    01199   /* Make the necessary folders */
    01200 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
    01201   FioCreateDirectory(_personal_dir);
    01202 #endif
    01203
    01204   static const Subdirectory default_subdirs[] = {
    01205     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR
    01206   };
    01207
    01208   for (uint i = 0; i < lengthof(default_subdirs); i++) {
    01209     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
    01210     FioCreateDirectory(dir);
    01211     free(dir);
    01212   }
    01213
    01214   /* If we have network we make a directory for the autodownloading of content */
    01215   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
    01216 #ifdef ENABLE_NETWORK
    01217   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01218
    01219   /* Create the directory for each of the types of content */
    01220   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
    01221   for (uint i = 0; i < lengthof(dirs); i++) {
    01222     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
    01223     FioCreateDirectory(tmp);
    01224     free(tmp);
    01225   }
    01226
    01227   extern char *_log_file;
    01228   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
    01229 #else /* ENABLE_NETWORK */
    01230   /* If we don't have networking, we don't need to make the directory. But
    01231    * if it exists we keep it, otherwise remove it from the search paths. */
    01232   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
    01233     free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01234     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
    01235   }
    01236 #endif /* ENABLE_NETWORK */
    01237 }
    01238
    01243 void SanitizeFilename(char *filename)
    01244 {
    01245   for (; *filename != '\0'; filename++) {
    01246     switch (*filename) {
    01247       /* The following characters are not allowed in filenames
    01248        * on at least one of the supported operating systems: */
    01249       case ':': case '\\': case '*': case '?': case '/':
    01250       case '<': case '>': case '|': case '"':
    01251         *filename = '_';
    01252         break;
    01253     }
    01254   }
    01255 }
    01256
    01265 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
    01266 {
    01267   FILE *in = fopen(filename, "rb");
    01268   if (in == NULL) return NULL;
    01269
    01270   fseek(in, 0, SEEK_END);
    01271   size_t len = ftell(in);
    01272   fseek(in, 0, SEEK_SET);
    01273   if (len > maxsize) {
    01274     fclose(in);
    01275     return NULL;
    01276   }
    01277   byte *mem = MallocT<byte>(len + 1);
    01278   mem[len] = 0;
    01279   if (fread(mem, len, 1, in) != 1) {
    01280     fclose(in);
    01281     free(mem);
    01282     return NULL;
    01283   }
    01284   fclose(in);
    01285
    01286   *lenp = len;
    01287   return mem;
    01288 }
    01289
    01296 static bool MatchesExtension(const char *extension, const char *filename)
    01297 {
    01298   if (extension == NULL) return true;
    01299
    01300   const char *ext = strrchr(filename, extension[0]);
    01301   return ext != NULL && strcasecmp(ext, extension) == 0;
    01302 }
    01303
    01313 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
    01314 {
    01315   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
    01316
    01317   uint num = 0;
    01318   struct stat sb;
    01319   struct dirent *dirent;
    01320   DIR *dir;
    01321
    01322   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
    01323
    01324   while ((dirent = readdir(dir)) != NULL) {
    01325     const char *d_name = FS2OTTD(dirent->d_name);
    01326     char filename[MAX_PATH];
    01327
    01328     if (!FiosIsValidFile(path, dirent, &sb)) continue;
    01329
    01330     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
    01331
    01332     if (S_ISDIR(sb.st_mode)) {
    01333       /* Directory */
    01334       if (!recursive) continue;
    01335       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
    01336       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
    01337       num += ScanPath(fs, extension, filename, basepath_length, recursive);
    01338     } else if (S_ISREG(sb.st_mode)) {
    01339       /* File */
    01340       if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
    01341     }
    01342   }
    01343
    01344   closedir(dir);
    01345
    01346   return num;
    01347 }
    01348
    01355 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
    01356 {
    01357   uint num = 0;
    01358   const char *filename = (*tar).first.c_str();
    01359
    01360   if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
    01361
    01362   return num;
    01363 }
    01364
    01374 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
    01375 {
    01376   this->subdir = sd;
    01377
    01378   Searchpath sp;
    01379   char path[MAX_PATH];
    01380   TarFileList::iterator tar;
    01381   uint num = 0;
    01382
    01383   FOR_ALL_SEARCHPATHS(sp) {
    01384     /* Don't search in the working directory */
    01385     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01386
    01387     FioAppendDirectory(path, MAX_PATH, sp, sd);
    01388     num += ScanPath(this, extension, path, strlen(path), recursive);
    01389   }
    01390
    01391   if (tars && sd != NO_DIRECTORY) {
    01392     FOR_ALL_TARS(tar, sd) {
    01393       num += ScanTar(this, extension, tar);
    01394     }
    01395   }
    01396
    01397   switch (sd) {
    01398     case BASESET_DIR:
    01399       num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
    01400       /* FALL THROUGH */
    01401     case NEWGRF_DIR:
    01402       num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
    01403       break;
    01404
    01405     default: break;
    01406   }
    01407
    01408   return num;
    01409 }
    01410
    01419 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
    01420 {
    01421   char path[MAX_PATH];
    01422   strecpy(path, directory, lastof(path));
    01423   if (!AppendPathSeparator(path, lengthof(path))) return 0;
    01424   return ScanPath(this, extension, path, strlen(path), recursive);
    01425 }