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