fios.cpp

Go to the documentation of this file.
00001 /* $Id: fios.cpp 26460 2014-04-13 10:47:39Z frosch $ */
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 
00404   /* Don't crash if we supply no extension */
00405   if (ext == NULL) return FIOS_TYPE_INVALID;
00406 
00407   if (strcasecmp(ext, ".sav") == 0) {
00408     GetFileTitle(file, title, last, SAVE_DIR);
00409     return FIOS_TYPE_FILE;
00410   }
00411 
00412   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
00413     if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
00414         strcasecmp(ext, ".sv2") == 0) {
00415       if (title != NULL) GetOldSaveGameName(file, title, last);
00416       return FIOS_TYPE_OLDFILE;
00417     }
00418   }
00419 
00420   return FIOS_TYPE_INVALID;
00421 }
00422 
00428 void FiosGetSavegameList(SaveLoadDialogMode mode)
00429 {
00430   static char *fios_save_path = NULL;
00431 
00432   if (fios_save_path == NULL) {
00433     fios_save_path = MallocT<char>(MAX_PATH);
00434     FioGetDirectory(fios_save_path, MAX_PATH, SAVE_DIR);
00435   }
00436 
00437   _fios_path = fios_save_path;
00438 
00439   FiosGetFileList(mode, &FiosGetSavegameListCallback, NO_DIRECTORY);
00440 }
00441 
00453 static FiosType FiosGetScenarioListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00454 {
00455   /* Show scenario files
00456    * .SCN OpenTTD style scenario file
00457    * .SV0 Transport Tycoon Deluxe (Patch) scenario
00458    * .SS0 Transport Tycoon Deluxe preset scenario */
00459   if (strcasecmp(ext, ".scn") == 0) {
00460     GetFileTitle(file, title, last, SCENARIO_DIR);
00461     return FIOS_TYPE_SCENARIO;
00462   }
00463 
00464   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
00465     if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
00466       GetOldSaveGameName(file, title, last);
00467       return FIOS_TYPE_OLD_SCENARIO;
00468     }
00469   }
00470 
00471   return FIOS_TYPE_INVALID;
00472 }
00473 
00479 void FiosGetScenarioList(SaveLoadDialogMode mode)
00480 {
00481   static char *fios_scn_path = NULL;
00482 
00483   /* Copy the default path on first run or on 'New Game' */
00484   if (fios_scn_path == NULL) {
00485     fios_scn_path = MallocT<char>(MAX_PATH);
00486     FioGetDirectory(fios_scn_path, MAX_PATH, SCENARIO_DIR);
00487   }
00488 
00489   _fios_path = fios_scn_path;
00490 
00491   char base_path[MAX_PATH];
00492   FioGetDirectory(base_path, sizeof(base_path), SCENARIO_DIR);
00493 
00494   FiosGetFileList(mode, &FiosGetScenarioListCallback, (mode == SLD_LOAD_SCENARIO && strcmp(base_path, _fios_path) == 0) ? SCENARIO_DIR : NO_DIRECTORY);
00495 }
00496 
00497 static FiosType FiosGetHeightmapListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00498 {
00499   /* Show heightmap files
00500    * .PNG PNG Based heightmap files
00501    * .BMP BMP Based heightmap files
00502    */
00503 
00504   FiosType type = FIOS_TYPE_INVALID;
00505 
00506 #ifdef WITH_PNG
00507   if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
00508 #endif /* WITH_PNG */
00509 
00510   if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
00511 
00512   if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
00513 
00514   TarFileList::iterator it = _tar_filelist[SCENARIO_DIR].find(file);
00515   if (it != _tar_filelist[SCENARIO_DIR].end()) {
00516     /* If the file is in a tar and that tar is not in a heightmap
00517      * directory we are for sure not supposed to see it.
00518      * Examples of this are pngs part of documentation within
00519      * collections of NewGRFs or 32 bpp graphics replacement PNGs.
00520      */
00521     bool match = false;
00522     Searchpath sp;
00523     FOR_ALL_SEARCHPATHS(sp) {
00524       char buf[MAX_PATH];
00525       FioAppendDirectory(buf, sizeof(buf), sp, HEIGHTMAP_DIR);
00526 
00527       if (strncmp(buf, it->second.tar_filename, strlen(buf)) == 0) {
00528         match = true;
00529         break;
00530       }
00531     }
00532 
00533     if (!match) return FIOS_TYPE_INVALID;
00534   }
00535 
00536   GetFileTitle(file, title, last, HEIGHTMAP_DIR);
00537 
00538   return type;
00539 }
00540 
00545 void FiosGetHeightmapList(SaveLoadDialogMode mode)
00546 {
00547   static char *fios_hmap_path = NULL;
00548 
00549   if (fios_hmap_path == NULL) {
00550     fios_hmap_path = MallocT<char>(MAX_PATH);
00551     FioGetDirectory(fios_hmap_path, MAX_PATH, HEIGHTMAP_DIR);
00552   }
00553 
00554   _fios_path = fios_hmap_path;
00555 
00556   char base_path[MAX_PATH];
00557   FioGetDirectory(base_path, sizeof(base_path), HEIGHTMAP_DIR);
00558 
00559   FiosGetFileList(mode, &FiosGetHeightmapListCallback, strcmp(base_path, _fios_path) == 0 ? HEIGHTMAP_DIR : NO_DIRECTORY);
00560 }
00561 
00566 const char *FiosGetScreenshotDir()
00567 {
00568   static char *fios_screenshot_path = NULL;
00569 
00570   if (fios_screenshot_path == NULL) {
00571     fios_screenshot_path = MallocT<char>(MAX_PATH);
00572     FioGetDirectory(fios_screenshot_path, MAX_PATH, SCREENSHOT_DIR);
00573   }
00574 
00575   return fios_screenshot_path;
00576 }
00577 
00578 #if defined(ENABLE_NETWORK)
00579 #include "network/network_content.h"
00580 #include "3rdparty/md5/md5.h"
00581 
00583 struct ScenarioIdentifier {
00584   uint32 scenid;           
00585   uint8 md5sum[16];        
00586   char filename[MAX_PATH]; 
00587 
00588   bool operator == (const ScenarioIdentifier &other) const
00589   {
00590     return this->scenid == other.scenid &&
00591         memcmp(this->md5sum, other.md5sum, sizeof(this->md5sum)) == 0;
00592   }
00593 
00594   bool operator != (const ScenarioIdentifier &other) const
00595   {
00596     return !(*this == other);
00597   }
00598 };
00599 
00603 class ScenarioScanner : protected FileScanner, public SmallVector<ScenarioIdentifier, 8> {
00604   bool scanned; 
00605 public:
00607   ScenarioScanner() : scanned(false) {}
00608 
00613   void Scan(bool rescan)
00614   {
00615     if (this->scanned && !rescan) return;
00616 
00617     this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
00618     this->scanned = true;
00619   }
00620 
00621   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00622   {
00623     FILE *f = FioFOpenFile(filename, "r", SCENARIO_DIR);
00624     if (f == NULL) return false;
00625 
00626     ScenarioIdentifier id;
00627     int fret = fscanf(f, "%i", &id.scenid);
00628     FioFCloseFile(f);
00629     if (fret != 1) return false;
00630     strecpy(id.filename, filename, lastof(id.filename));
00631 
00632     Md5 checksum;
00633     uint8 buffer[1024];
00634     char basename[MAX_PATH]; 
00635     size_t len, size;
00636 
00637     /* open the scenario file, but first get the name.
00638      * This is safe as we check on extension which
00639      * must always exist. */
00640     strecpy(basename, filename, lastof(basename));
00641     *strrchr(basename, '.') = '\0';
00642     f = FioFOpenFile(basename, "rb", SCENARIO_DIR, &size);
00643     if (f == NULL) return false;
00644 
00645     /* calculate md5sum */
00646     while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
00647       size -= len;
00648       checksum.Append(buffer, len);
00649     }
00650     checksum.Finish(id.md5sum);
00651 
00652     FioFCloseFile(f);
00653 
00654     this->Include(id);
00655     return true;
00656   }
00657 };
00658 
00660 static ScenarioScanner _scanner;
00661 
00668 const char *FindScenario(const ContentInfo *ci, bool md5sum)
00669 {
00670   _scanner.Scan(false);
00671 
00672   for (ScenarioIdentifier *id = _scanner.Begin(); id != _scanner.End(); id++) {
00673     if (md5sum ? (memcmp(id->md5sum, ci->md5sum, sizeof(id->md5sum)) == 0)
00674                : (id->scenid == ci->unique_id)) {
00675       return id->filename;
00676     }
00677   }
00678 
00679   return NULL;
00680 }
00681 
00688 bool HasScenario(const ContentInfo *ci, bool md5sum)
00689 {
00690   return (FindScenario(ci, md5sum) != NULL);
00691 }
00692 
00696 void ScanScenarios()
00697 {
00698   _scanner.Scan(true);
00699 }
00700 
00701 #endif /* ENABLE_NETWORK */