# fios.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
    00015 #include "stdafx.h"
    00016 #include "fios.h"
    00017 #include "fileio_func.h"
    00018 #include "tar_type.h"
    00019 #include "screenshot.h"
    00020 #include "string_func.h"
    00021 #include <sys/stat.h>
    00022
    00023 #ifndef WIN32
    00024 # include <unistd.h>
    00025 #endif /* WIN32 */
    00026
    00027 #include "table/strings.h"
    00028
    00029 /* Variables to display file lists */
    00030 SmallVector<FiosItem, 32> _fios_items;
    00031 static char *_fios_path;
    00032 SmallFiosItem _file_to_saveload;
    00033 SortingBits _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
    00034
    00035 /* OS-specific functions are taken from their respective files (win32/unix/os2 .c) */
    00036 extern bool FiosIsRoot(const char *path);
    00037 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
    00038 extern bool FiosIsHiddenFile(const struct dirent *ent);
    00039 extern void FiosGetDrives();
    00040 extern bool FiosGetDiskFreeSpace(const char *path, uint64 *tot);
    00041
    00042 /* get the name of an oldstyle savegame */
    00043 extern void GetOldSaveGameName(const char *file, char *title, const char *last);
    00044
    00051 int CDECL CompareFiosItems(const FiosItem *da, const FiosItem *db)
    00052 {
    00053   int r = 0;
    00054
    00055   if ((_savegame_sort_order & SORT_BY_NAME) == 0 && da->mtime != db->mtime) {
    00056     r = da->mtime < db->mtime ? -1 : 1;
    00057   } else {
    00058     r = strcasecmp(da->title, db->title);
    00059   }
    00060
    00061   if (_savegame_sort_order & SORT_DESCENDING) r = -r;
    00062   return r;
    00063 }
    00064
    00066 void FiosFreeSavegameList()
    00067 {
    00068   _fios_items.Clear();
    00069   _fios_items.Compact();
    00070 }
    00071
    00079 StringID FiosGetDescText(const char **path, uint64 *total_free)
    00080 {
    00081   *path = _fios_path;
    00082   return FiosGetDiskFreeSpace(*path, total_free) ? STR_SAVELOAD_BYTES_FREE : STR_ERROR_UNABLE_TO_READ_DRIVE;
    00083 }
    00084
    00090 const char *FiosBrowseTo(const FiosItem *item)
    00091 {
    00092   char *path = _fios_path;
    00093
    00094   switch (item->type) {
    00095     case FIOS_TYPE_DRIVE:
    00096 #if defined(WINCE)
    00097       snprintf(path, MAX_PATH, PATHSEP "");
    00098 #elif defined(WIN32) || defined(__OS2__)
    00099       snprintf(path, MAX_PATH, "%c:" PATHSEP, item->title[0]);
    00100 #endif
    00101       /* FALL THROUGH */
    00102     case FIOS_TYPE_INVALID:
    00103       break;
    00104
    00105     case FIOS_TYPE_PARENT: {
    00106       /* Check for possible NULL ptr (not required for UNIXes, but AmigaOS-alikes) */
    00107       char *s = strrchr(path, PATHSEPCHAR);
    00108       if (s != NULL && s != path) {
    00109         s[0] = '\0'; // Remove last path separator character, so we can go up one level.
    00110       }
    00111       s = strrchr(path, PATHSEPCHAR);
    00112       if (s != NULL) {
    00113         s[1] = '\0'; // go up a directory
    00114 #if defined(__MORPHOS__) || defined(__AMIGAOS__)
    00115       /* On MorphOS or AmigaOS paths look like: "Volume:directory/subdirectory" */
    00116       } else if ((s = strrchr(path, ':')) != NULL) {
    00117         s[1] = '\0';
    00118 #endif
    00119       }
    00120       break;
    00121     }
    00122
    00123     case FIOS_TYPE_DIR:
    00124       strcat(path, item->name);
    00125       strcat(path, PATHSEP);
    00126       break;
    00127
    00128     case FIOS_TYPE_DIRECT:
    00129       snprintf(path, MAX_PATH, "%s", item->name);
    00130       break;
    00131
    00132     case FIOS_TYPE_FILE:
    00133     case FIOS_TYPE_OLDFILE:
    00134     case FIOS_TYPE_SCENARIO:
    00135     case FIOS_TYPE_OLD_SCENARIO:
    00136     case FIOS_TYPE_PNG:
    00137     case FIOS_TYPE_BMP:
    00138       return item->name;
    00139   }
    00140
    00141   return NULL;
    00142 }
    00143
    00152 static void FiosMakeFilename(char *buf, const char *path, const char *name, const char *ext, size_t size)
    00153 {
    00154   const char *period;
    00155
    00156   /* Don't append the extension if it is already there */
    00157   period = strrchr(name, '.');
    00158   if (period != NULL && strcasecmp(period, ext) == 0) ext = "";
    00159 #if  defined(__MORPHOS__) || defined(__AMIGAOS__)
    00160   if (path != NULL) {
    00161     unsigned char sepchar = path[(strlen(path) - 1)];
    00162
    00163     if (sepchar != ':' && sepchar != '/') {
    00164       snprintf(buf, size, "%s" PATHSEP "%s%s", path, name, ext);
    00165     } else {
    00166       snprintf(buf, size, "%s%s%s", path, name, ext);
    00167     }
    00168   } else {
    00169     snprintf(buf, size, "%s%s", name, ext);
    00170   }
    00171 #else
    00172   snprintf(buf, size, "%s" PATHSEP "%s%s", path, name, ext);
    00173 #endif
    00174 }
    00175
    00182 void FiosMakeSavegameName(char *buf, const char *name, size_t size)
    00183 {
    00184   const char *extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav";
    00185
    00186   FiosMakeFilename(buf, _fios_path, name, extension, size);
    00187 }
    00188
    00195 void FiosMakeHeightmapName(char *buf, const char *name, size_t size)
    00196 {
    00197   char ext[5];
    00198   ext[0] = '.';
    00199   strecpy(ext + 1, GetCurrentScreenshotExtension(), lastof(ext));
    00200
    00201   FiosMakeFilename(buf, _fios_path, name, ext, size);
    00202 }
    00203
    00208 bool FiosDelete(const char *name)
    00209 {
    00210   char filename[512];
    00211
    00212   FiosMakeSavegameName(filename, name, lengthof(filename));
    00213   return unlink(filename) == 0;
    00214 }
    00215
    00216 typedef FiosType fios_getlist_callback_proc(SaveLoadDialogMode mode, const char *filename, const char *ext, char *title, const char *last);
    00217
    00221 class FiosFileScanner : public FileScanner {
    00222   SaveLoadDialogMode mode;
    00223   fios_getlist_callback_proc *callback_proc;
    00224 public:
    00230   FiosFileScanner(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc) :
    00231     mode(mode),
    00232     callback_proc(callback_proc)
    00233   {}
    00234
    00235   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename);
    00236 };
    00237
    00244 bool FiosFileScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00245 {
    00246   const char *ext = strrchr(filename, '.');
    00247   if (ext == NULL) return false;
    00248
    00249   char fios_title[64];
    00250   fios_title[0] = '\0'; // reset the title;
    00251
    00252   FiosType type = this->callback_proc(this->mode, filename, ext, fios_title, lastof(fios_title));
    00253   if (type == FIOS_TYPE_INVALID) return false;
    00254
    00255   for (const FiosItem *fios = _fios_items.Begin(); fios != _fios_items.End(); fios++) {
    00256     if (strcmp(fios->name, filename) == 0) return false;
    00257   }
    00258
    00259   FiosItem *fios = _fios_items.Append();
    00260 #ifdef WIN32
    00261   struct _stat sb;
    00262   if (_tstat(OTTD2FS(filename), &sb) == 0) {
    00263 #else
    00264   struct stat sb;
    00265   if (stat(filename, &sb) == 0) {
    00266 #endif
    00267     fios->mtime = sb.st_mtime;
    00268   } else {
    00269     fios->mtime = 0;
    00270   }
    00271
    00272   fios->type = type;
    00273   strecpy(fios->name, filename, lastof(fios->name));
    00274
    00275   /* If the file doesn't have a title, use its filename */
    00276   const char *t = fios_title;
    00277   if (StrEmpty(fios_title)) {
    00278     t = strrchr(filename, PATHSEPCHAR);
    00279     t = (t == NULL) ? filename : (t + 1);
    00280   }
    00281   strecpy(fios->title, t, lastof(fios->title));
    00282   str_validate(fios->title, lastof(fios->title));
    00283
    00284   return true;
    00285 }
    00286
    00287
    00294 static void FiosGetFileList(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc, Subdirectory subdir)
    00295 {
    00296   struct stat sb;
    00297   struct dirent *dirent;
    00298   DIR *dir;
    00299   FiosItem *fios;
    00300   int sort_start;
    00301   char d_name[sizeof(fios->name)];
    00302
    00303   _fios_items.Clear();
    00304
    00305   /* A parent directory link exists if we are not in the root directory */
    00306   if (!FiosIsRoot(_fios_path)) {
    00307     fios = _fios_items.Append();
    00308     fios->type = FIOS_TYPE_PARENT;
    00309     fios->mtime = 0;
    00310     strecpy(fios->name, "..", lastof(fios->name));
    00311     strecpy(fios->title, ".. (Parent directory)", lastof(fios->title));
    00312   }
    00313
    00314   /* Show subdirectories */
    00315   if ((dir = ttd_opendir(_fios_path)) != NULL) {
    00316     while ((dirent = readdir(dir)) != NULL) {
    00317       strecpy(d_name, FS2OTTD(dirent->d_name), lastof(d_name));
    00318
    00319       /* found file must be directory, but not '.' or '..' */
    00320       if (FiosIsValidFile(_fios_path, dirent, &sb) && S_ISDIR(sb.st_mode) &&
    00321           (!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
    00322           strcmp(d_name, ".") != 0 && strcmp(d_name, "..") != 0) {
    00323         fios = _fios_items.Append();
    00324         fios->type = FIOS_TYPE_DIR;
    00325         fios->mtime = 0;
    00326         strecpy(fios->name, d_name, lastof(fios->name));
    00327         snprintf(fios->title, lengthof(fios->title), "%s" PATHSEP " (Directory)", d_name);
    00328         str_validate(fios->title, lastof(fios->title));
    00329       }
    00330     }
    00331     closedir(dir);
    00332   }
    00333
    00334   /* Sort the subdirs always by name, ascending, remember user-sorting order */
    00335   {
    00336     SortingBits order = _savegame_sort_order;
    00337     _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
    00338     QSortT(_fios_items.Begin(), _fios_items.Length(), CompareFiosItems);
    00339     _savegame_sort_order = order;
    00340   }
    00341
    00342   /* This is where to start sorting for the filenames */
    00343   sort_start = _fios_items.Length();
    00344
    00345   /* Show files */
    00346   FiosFileScanner scanner(mode, callback_proc);
    00347   if (subdir == NO_DIRECTORY) {
    00348     scanner.Scan(NULL, _fios_path, false);
    00349   } else {
    00350     scanner.Scan(NULL, subdir, true, true);
    00351   }
    00352
    00353   QSortT(_fios_items.Get(sort_start), _fios_items.Length() - sort_start, CompareFiosItems);
    00354
    00355   /* Show drives */
    00356   FiosGetDrives();
    00357
    00358   _fios_items.Compact();
    00359 }
    00360
    00369 static void GetFileTitle(const char *file, char *title, const char *last, Subdirectory subdir)
    00370 {
    00371   char buf[MAX_PATH];
    00372   strecpy(buf, file, lastof(buf));
    00373   strecat(buf, ".title", lastof(buf));
    00374
    00375   FILE *f = FioFOpenFile(buf, "r", subdir);
    00376   if (f == NULL) return;
    00377
    00378   size_t read = fread(title, 1, last - title, f);
    00379   assert(title + read <= last);
    00380   title[read] = '\0';
    00381   str_validate(title, last);
    00382   FioFCloseFile(f);
    00383 }
    00384
    00396 FiosType FiosGetSavegameListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00397 {
    00398   /* Show savegame files
    00399    * .SAV OpenTTD saved game
    00400    * .SS1 Transport Tycoon Deluxe preset game
    00401    * .SV1 Transport Tycoon Deluxe (Patch) saved game
    00402    * .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
    00403   if (strcasecmp(ext, ".sav") == 0) {
    00404     GetFileTitle(file, title, last, SAVE_DIR);
    00405     return FIOS_TYPE_FILE;
    00406   }
    00407
    00408   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
    00409     if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
    00410         strcasecmp(ext, ".sv2") == 0) {
    00411       if (title != NULL) GetOldSaveGameName(file, title, last);
    00412       return FIOS_TYPE_OLDFILE;
    00413     }
    00414   }
    00415
    00416   return FIOS_TYPE_INVALID;
    00417 }
    00418
    00424 void FiosGetSavegameList(SaveLoadDialogMode mode)
    00425 {
    00426   static char *fios_save_path = NULL;
    00427
    00428   if (fios_save_path == NULL) {
    00429     fios_save_path = MallocT<char>(MAX_PATH);
    00430     FioGetDirectory(fios_save_path, MAX_PATH, SAVE_DIR);
    00431   }
    00432
    00433   _fios_path = fios_save_path;
    00434
    00435   FiosGetFileList(mode, &FiosGetSavegameListCallback, NO_DIRECTORY);
    00436 }
    00437
    00449 static FiosType FiosGetScenarioListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00450 {
    00451   /* Show scenario files
    00452    * .SCN OpenTTD style scenario file
    00453    * .SV0 Transport Tycoon Deluxe (Patch) scenario
    00454    * .SS0 Transport Tycoon Deluxe preset scenario */
    00455   if (strcasecmp(ext, ".scn") == 0) {
    00456     GetFileTitle(file, title, last, SCENARIO_DIR);
    00457     return FIOS_TYPE_SCENARIO;
    00458   }
    00459
    00460   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
    00461     if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
    00462       GetOldSaveGameName(file, title, last);
    00463       return FIOS_TYPE_OLD_SCENARIO;
    00464     }
    00465   }
    00466
    00467   return FIOS_TYPE_INVALID;
    00468 }
    00469
    00475 void FiosGetScenarioList(SaveLoadDialogMode mode)
    00476 {
    00477   static char *fios_scn_path = NULL;
    00478
    00479   /* Copy the default path on first run or on 'New Game' */
    00480   if (fios_scn_path == NULL) {
    00481     fios_scn_path = MallocT<char>(MAX_PATH);
    00482     FioGetDirectory(fios_scn_path, MAX_PATH, SCENARIO_DIR);
    00483   }
    00484
    00485   _fios_path = fios_scn_path;
    00486
    00487   char base_path[MAX_PATH];
    00488   FioGetDirectory(base_path, sizeof(base_path), SCENARIO_DIR);
    00489
    00490   FiosGetFileList(mode, &FiosGetScenarioListCallback, (mode == SLD_LOAD_SCENARIO && strcmp(base_path, _fios_path) == 0) ? SCENARIO_DIR : NO_DIRECTORY);
    00491 }
    00492
    00493 static FiosType FiosGetHeightmapListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00494 {
    00495   /* Show heightmap files
    00496    * .PNG PNG Based heightmap files
    00497    * .BMP BMP Based heightmap files
    00498    */
    00499
    00500   FiosType type = FIOS_TYPE_INVALID;
    00501
    00502 #ifdef WITH_PNG
    00503   if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
    00504 #endif /* WITH_PNG */
    00505
    00506   if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
    00507
    00508   if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
    00509
    00510   TarFileList::iterator it = _tar_filelist[SCENARIO_DIR].find(file);
    00511   if (it != _tar_filelist[SCENARIO_DIR].end()) {
    00512     /* If the file is in a tar and that tar is not in a heightmap
    00513      * directory we are for sure not supposed to see it.
    00514      * Examples of this are pngs part of documentation within
    00515      * collections of NewGRFs or 32 bpp graphics replacement PNGs.
    00516      */
    00517     bool match = false;
    00518     Searchpath sp;
    00519     FOR_ALL_SEARCHPATHS(sp) {
    00520       char buf[MAX_PATH];
    00521       FioAppendDirectory(buf, sizeof(buf), sp, HEIGHTMAP_DIR);
    00522
    00523       if (strncmp(buf, it->second.tar_filename, strlen(buf)) == 0) {
    00524         match = true;
    00525         break;
    00526       }
    00527     }
    00528
    00529     if (!match) return FIOS_TYPE_INVALID;
    00530   }
    00531
    00532   GetFileTitle(file, title, last, HEIGHTMAP_DIR);
    00533
    00534   return type;
    00535 }
    00536
    00541 void FiosGetHeightmapList(SaveLoadDialogMode mode)
    00542 {
    00543   static char *fios_hmap_path = NULL;
    00544
    00545   if (fios_hmap_path == NULL) {
    00546     fios_hmap_path = MallocT<char>(MAX_PATH);
    00547     FioGetDirectory(fios_hmap_path, MAX_PATH, HEIGHTMAP_DIR);
    00548   }
    00549
    00550   _fios_path = fios_hmap_path;
    00551
    00552   char base_path[MAX_PATH];
    00553   FioGetDirectory(base_path, sizeof(base_path), HEIGHTMAP_DIR);
    00554
    00555   FiosGetFileList(mode, &FiosGetHeightmapListCallback, strcmp(base_path, _fios_path) == 0 ? HEIGHTMAP_DIR : NO_DIRECTORY);
    00556 }
    00557
    00558 #if defined(ENABLE_NETWORK)
    00559 #include "network/network_content.h"
    00560 #include "3rdparty/md5/md5.h"
    00561
    00563 struct ScenarioIdentifier {
    00564   uint32 scenid;
    00565   uint8 md5sum[16];
    00566
    00567   bool operator == (const ScenarioIdentifier &other) const
    00568   {
    00569     return this->scenid == other.scenid &&
    00570         memcmp(this->md5sum, other.md5sum, sizeof(this->md5sum)) == 0;
    00571   }
    00572
    00573   bool operator != (const ScenarioIdentifier &other) const
    00574   {
    00575     return !(*this == other);
    00576   }
    00577 };
    00578
    00582 class ScenarioScanner : protected FileScanner, public SmallVector<ScenarioIdentifier, 8> {
    00583   bool scanned;
    00584 public:
    00586   ScenarioScanner() : scanned(false) {}
    00587
    00592   void Scan(bool rescan)
    00593   {
    00594     if (this->scanned && !rescan) return;
    00595
    00596     this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
    00597     this->scanned = true;
    00598   }
    00599
    00600   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00601   {
    00602     FILE *f = FioFOpenFile(filename, "r", SCENARIO_DIR);
    00603     if (f == NULL) return false;
    00604
    00605     ScenarioIdentifier id;
    00606     int fret = fscanf(f, "%i", &id.scenid);
    00607     FioFCloseFile(f);
    00608     if (fret != 1) return false;
    00609
    00610     Md5 checksum;
    00611     uint8 buffer[1024];
    00612     char basename[MAX_PATH];
    00613     size_t len, size;
    00614
    00615     /* open the scenario file, but first get the name.
    00616      * This is safe as we check on extension which
    00617      * must always exist. */
    00618     strecpy(basename, filename, lastof(basename));
    00619     *strrchr(basename, '.') = '\0';
    00620     f = FioFOpenFile(basename, "rb", SCENARIO_DIR, &size);
    00621     if (f == NULL) return false;
    00622
    00623     /* calculate md5sum */
    00624     while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
    00625       size -= len;
    00626       checksum.Append(buffer, len);
    00627     }
    00628     checksum.Finish(id.md5sum);
    00629
    00630     FioFCloseFile(f);
    00631
    00632     this->Include(id);
    00633     return true;
    00634   }
    00635 };
    00636
    00638 static ScenarioScanner _scanner;
    00639
    00646 bool HasScenario(const ContentInfo *ci, bool md5sum)
    00647 {
    00648   _scanner.Scan(false);
    00649
    00650   for (ScenarioIdentifier *id = _scanner.Begin(); id != _scanner.End(); id++) {
    00651     if (md5sum ?
    00652         (memcmp(id->md5sum, ci->md5sum, sizeof(id->md5sum)) == 0) :
    00653         (id->scenid == ci->unique_id)) {
    00654       return true;
    00655     }
    00656   }
    00657
    00658   return false;
    00659 }
    00660
    00664 void ScanScenarios()
    00665 {
    00666   _scanner.Scan(true);
    00667 }
    00668
    00669 #endif /* ENABLE_NETWORK */