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     case FIOS_TYPE_TEMPLATE:
00139       return item->name;
00140   }
00141 
00142   return NULL;
00143 }
00144 
00153 static void FiosMakeFilename(char *buf, const char *path, const char *name, const char *ext, size_t size)
00154 {
00155   const char *period;
00156 
00157 //<<<<<<< .mine  // Todo: remove after testing
00158 //  extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav";
00159 //  if (_saveload_mode == SLD_SAVE_TEMPLATE || _saveload_mode == SLD_LOAD_TEMPLATE) {
00160 //    extension = ".tmpl";
00161 //  }
00162 //=======
00163 //>>>>>>> .r22515
00164   /* Don't append the extension if it is already there */
00165   period = strrchr(name, '.');
00166   if (period != NULL && strcasecmp(period, ext) == 0) ext = "";
00167 #if  defined(__MORPHOS__) || defined(__AMIGAOS__)
00168   if (path != NULL) {
00169     unsigned char sepchar = path[(strlen(path) - 1)];
00170 
00171     if (sepchar != ':' && sepchar != '/') {
00172       snprintf(buf, size, "%s" PATHSEP "%s%s", path, name, ext);
00173     } else {
00174       snprintf(buf, size, "%s%s%s", path, name, ext);
00175     }
00176   } else {
00177     snprintf(buf, size, "%s%s", name, ext);
00178   }
00179 #else
00180   snprintf(buf, size, "%s" PATHSEP "%s%s", path, name, ext);
00181 #endif
00182 }
00183 
00190 void FiosMakeSavegameName(char *buf, const char *name, size_t size)
00191 {
00192   const char *extension;
00193 
00194   if (_saveload_mode == SLD_SAVE_TEMPLATE || _saveload_mode == SLD_LOAD_TEMPLATE) {
00195     extension = ".tmpl";
00196   } else {
00197     extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav";
00198   }
00199 
00200   FiosMakeFilename(buf, _fios_path, name, extension, size);
00201 }
00202 
00209 void FiosMakeHeightmapName(char *buf, const char *name, size_t size)
00210 {
00211   char ext[5];
00212   ext[0] = '.';
00213   strecpy(ext + 1, GetCurrentScreenshotExtension(), lastof(ext));
00214 
00215   FiosMakeFilename(buf, _fios_path, name, ext, size);
00216 }
00217 
00222 bool FiosDelete(const char *name)
00223 {
00224   char filename[512];
00225 
00226   FiosMakeSavegameName(filename, name, lengthof(filename));
00227   return unlink(filename) == 0;
00228 }
00229 
00230 typedef FiosType fios_getlist_callback_proc(SaveLoadDialogMode mode, const char *filename, const char *ext, char *title, const char *last);
00231 
00235 class FiosFileScanner : public FileScanner {
00236   SaveLoadDialogMode mode; 
00237   fios_getlist_callback_proc *callback_proc; 
00238 public:
00244   FiosFileScanner(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc) :
00245     mode(mode),
00246     callback_proc(callback_proc)
00247   {}
00248 
00249   /* virtual */ bool AddFile(const char *filename, size_t basepath_length);
00250 };
00251 
00258 bool FiosFileScanner::AddFile(const char *filename, size_t basepath_length)
00259 {
00260   const char *ext = strrchr(filename, '.');
00261   if (ext == NULL) return false;
00262 
00263   char fios_title[64];
00264   fios_title[0] = '\0'; // reset the title;
00265 
00266   FiosType type = this->callback_proc(this->mode, filename, ext, fios_title, lastof(fios_title));
00267   if (type == FIOS_TYPE_INVALID) return false;
00268 
00269   for (const FiosItem *fios = _fios_items.Begin(); fios != _fios_items.End(); fios++) {
00270     if (strcmp(fios->name, filename) == 0) return false;
00271   }
00272 
00273   FiosItem *fios = _fios_items.Append();
00274 #ifdef WIN32
00275   struct _stat sb;
00276   if (_tstat(OTTD2FS(filename), &sb) == 0) {
00277 #else
00278   struct stat sb;
00279   if (stat(filename, &sb) == 0) {
00280 #endif
00281     fios->mtime = sb.st_mtime;
00282   } else {
00283     fios->mtime = 0;
00284   }
00285 
00286   fios->type = type;
00287   strecpy(fios->name, filename, lastof(fios->name));
00288 
00289   /* If the file doesn't have a title, use its filename */
00290   const char *t = fios_title;
00291   if (StrEmpty(fios_title)) {
00292     t = strrchr(filename, PATHSEPCHAR);
00293     t = (t == NULL) ? filename : (t + 1);
00294   }
00295   strecpy(fios->title, t, lastof(fios->title));
00296   str_validate(fios->title, lastof(fios->title));
00297 
00298   return true;
00299 }
00300 
00301 
00308 static void FiosGetFileList(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc, Subdirectory subdir)
00309 {
00310   struct stat sb;
00311   struct dirent *dirent;
00312   DIR *dir;
00313   FiosItem *fios;
00314   int sort_start;
00315   char d_name[sizeof(fios->name)];
00316 
00317   _fios_items.Clear();
00318 
00319   /* A parent directory link exists if we are not in the root directory */
00320   if (!FiosIsRoot(_fios_path)) {
00321     fios = _fios_items.Append();
00322     fios->type = FIOS_TYPE_PARENT;
00323     fios->mtime = 0;
00324     strecpy(fios->name, "..", lastof(fios->name));
00325     strecpy(fios->title, ".. (Parent directory)", lastof(fios->title));
00326   }
00327 
00328   /* Show subdirectories */
00329   if ((dir = ttd_opendir(_fios_path)) != NULL) {
00330     while ((dirent = readdir(dir)) != NULL) {
00331       strecpy(d_name, FS2OTTD(dirent->d_name), lastof(d_name));
00332 
00333       /* found file must be directory, but not '.' or '..' */
00334       if (FiosIsValidFile(_fios_path, dirent, &sb) && S_ISDIR(sb.st_mode) &&
00335           (!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
00336           strcmp(d_name, ".") != 0 && strcmp(d_name, "..") != 0) {
00337         fios = _fios_items.Append();
00338         fios->type = FIOS_TYPE_DIR;
00339         fios->mtime = 0;
00340         strecpy(fios->name, d_name, lastof(fios->name));
00341         snprintf(fios->title, lengthof(fios->title), "%s" PATHSEP " (Directory)", d_name);
00342         str_validate(fios->title, lastof(fios->title));
00343       }
00344     }
00345     closedir(dir);
00346   }
00347 
00348   /* Sort the subdirs always by name, ascending, remember user-sorting order */
00349   {
00350     SortingBits order = _savegame_sort_order;
00351     _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
00352     QSortT(_fios_items.Begin(), _fios_items.Length(), CompareFiosItems);
00353     _savegame_sort_order = order;
00354   }
00355 
00356   /* This is where to start sorting for the filenames */
00357   sort_start = _fios_items.Length();
00358 
00359   /* Show files */
00360   FiosFileScanner scanner(mode, callback_proc);
00361   if (subdir == NO_DIRECTORY) {
00362     scanner.Scan(NULL, _fios_path, false);
00363   } else {
00364     scanner.Scan(NULL, subdir, true, true);
00365   }
00366 
00367   QSortT(_fios_items.Get(sort_start), _fios_items.Length() - sort_start, CompareFiosItems);
00368 
00369   /* Show drives */
00370   FiosGetDrives();
00371 
00372   _fios_items.Compact();
00373 }
00374 
00382 static void GetFileTitle(const char *file, char *title, const char *last)
00383 {
00384   char buf[MAX_PATH];
00385   strecpy(buf, file, lastof(buf));
00386   strecat(buf, ".title", lastof(buf));
00387 
00388   FILE *f = FioFOpenFile(buf, "r");
00389   if (f == NULL) return;
00390 
00391   size_t read = fread(title, 1, last - title, f);
00392   assert(title + read <= last);
00393   title[read] = '\0';
00394   str_validate(title, last);
00395   FioFCloseFile(f);
00396 }
00397 
00409 FiosType FiosGetSavegameListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00410 {
00411   /* Show savegame files
00412    * .SAV OpenTTD saved game
00413    * .SS1 Transport Tycoon Deluxe preset game
00414    * .SV1 Transport Tycoon Deluxe (Patch) saved game
00415    * .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
00416   if (strcasecmp(ext, ".sav") == 0) {
00417     GetFileTitle(file, title, last);
00418     return FIOS_TYPE_FILE;
00419   }
00420 
00421   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
00422     if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
00423         strcasecmp(ext, ".sv2") == 0) {
00424       if (title != NULL) GetOldSaveGameName(file, title, last);
00425       return FIOS_TYPE_OLDFILE;
00426     }
00427   }
00428 
00429   return FIOS_TYPE_INVALID;
00430 }
00431 
00437 void FiosGetSavegameList(SaveLoadDialogMode mode)
00438 {
00439   static char *fios_save_path = NULL;
00440 
00441   if (fios_save_path == NULL) {
00442     fios_save_path = MallocT<char>(MAX_PATH);
00443     FioGetDirectory(fios_save_path, MAX_PATH, SAVE_DIR);
00444   }
00445 
00446   _fios_path = fios_save_path;
00447 
00448   FiosGetFileList(mode, &FiosGetSavegameListCallback, NO_DIRECTORY);
00449 }
00450 
00461 static FiosType FiosGetTemplateListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00462 {
00463   /* Show template files
00464    * .tmpl OpenTTD template */
00465   if (strcasecmp(ext, ".tmpl") == 0) return FIOS_TYPE_TEMPLATE;
00466 
00467   return FIOS_TYPE_INVALID;
00468 }
00469 
00476 void FiosGetTemplateList(SaveLoadDialogMode mode)
00477 {
00478   static char *_fios_save_path = NULL;
00479 
00480   if (_fios_save_path == NULL) {
00481     _fios_save_path = MallocT<char>(MAX_PATH);
00482     FioGetDirectory(_fios_save_path, MAX_PATH, SAVE_DIR);
00483   }
00484 
00485   _fios_path = _fios_save_path;
00486 
00487   FiosGetFileList(mode, &FiosGetTemplateListCallback, NO_DIRECTORY);
00488 }
00489 
00501 static FiosType FiosGetScenarioListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00502 {
00503   /* Show scenario files
00504    * .SCN OpenTTD style scenario file
00505    * .SV0 Transport Tycoon Deluxe (Patch) scenario
00506    * .SS0 Transport Tycoon Deluxe preset scenario */
00507   if (strcasecmp(ext, ".scn") == 0) {
00508     GetFileTitle(file, title, last);
00509     return FIOS_TYPE_SCENARIO;
00510   }
00511 
00512   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
00513     if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
00514       GetOldSaveGameName(file, title, last);
00515       return FIOS_TYPE_OLD_SCENARIO;
00516     }
00517   }
00518 
00519   return FIOS_TYPE_INVALID;
00520 }
00521 
00527 void FiosGetScenarioList(SaveLoadDialogMode mode)
00528 {
00529   static char *fios_scn_path = NULL;
00530 
00531   /* Copy the default path on first run or on 'New Game' */
00532   if (fios_scn_path == NULL) {
00533     fios_scn_path = MallocT<char>(MAX_PATH);
00534     FioGetDirectory(fios_scn_path, MAX_PATH, SCENARIO_DIR);
00535   }
00536 
00537   _fios_path = fios_scn_path;
00538 
00539   char base_path[MAX_PATH];
00540   FioGetDirectory(base_path, sizeof(base_path), SCENARIO_DIR);
00541 
00542   FiosGetFileList(mode, &FiosGetScenarioListCallback, (mode == SLD_LOAD_SCENARIO && strcmp(base_path, _fios_path) == 0) ? SCENARIO_DIR : NO_DIRECTORY);
00543 }
00544 
00545 static FiosType FiosGetHeightmapListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
00546 {
00547   /* Show heightmap files
00548    * .PNG PNG Based heightmap files
00549    * .BMP BMP Based heightmap files
00550    */
00551 
00552   FiosType type = FIOS_TYPE_INVALID;
00553 
00554 #ifdef WITH_PNG
00555   if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
00556 #endif /* WITH_PNG */
00557 
00558   if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
00559 
00560   if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
00561 
00562   TarFileList::iterator it = _tar_filelist.find(file);
00563   if (it != _tar_filelist.end()) {
00564     /* If the file is in a tar and that tar is not in a heightmap
00565      * directory we are for sure not supposed to see it.
00566      * Examples of this are pngs part of documentation within
00567      * collections of NewGRFs or 32 bpp graphics replacement PNGs.
00568      */
00569     bool match = false;
00570     Searchpath sp;
00571     FOR_ALL_SEARCHPATHS(sp) {
00572       char buf[MAX_PATH];
00573       FioAppendDirectory(buf, sizeof(buf), sp, HEIGHTMAP_DIR);
00574 
00575       if (strncmp(buf, it->second.tar_filename, strlen(buf)) == 0) {
00576         match = true;
00577         break;
00578       }
00579     }
00580 
00581     if (!match) return FIOS_TYPE_INVALID;
00582   }
00583 
00584   GetFileTitle(file, title, last);
00585 
00586   return type;
00587 }
00588 
00593 void FiosGetHeightmapList(SaveLoadDialogMode mode)
00594 {
00595   static char *fios_hmap_path = NULL;
00596 
00597   if (fios_hmap_path == NULL) {
00598     fios_hmap_path = MallocT<char>(MAX_PATH);
00599     FioGetDirectory(fios_hmap_path, MAX_PATH, HEIGHTMAP_DIR);
00600   }
00601 
00602   _fios_path = fios_hmap_path;
00603 
00604   char base_path[MAX_PATH];
00605   FioGetDirectory(base_path, sizeof(base_path), HEIGHTMAP_DIR);
00606 
00607   FiosGetFileList(mode, &FiosGetHeightmapListCallback, strcmp(base_path, _fios_path) == 0 ? HEIGHTMAP_DIR : NO_DIRECTORY);
00608 }
00609 
00610 #if defined(ENABLE_NETWORK)
00611 #include "network/network_content.h"
00612 #include "3rdparty/md5/md5.h"
00613 
00615 struct ScenarioIdentifier {
00616   uint32 scenid;    
00617   uint8 md5sum[16]; 
00618 
00619   bool operator == (const ScenarioIdentifier &other) const
00620   {
00621     return this->scenid == other.scenid &&
00622         memcmp(this->md5sum, other.md5sum, sizeof(this->md5sum)) == 0;
00623   }
00624 
00625   bool operator != (const ScenarioIdentifier &other) const
00626   {
00627     return !(*this == other);
00628   }
00629 };
00630 
00634 class ScenarioScanner : protected FileScanner, public SmallVector<ScenarioIdentifier, 8> {
00635   bool scanned; 
00636 public:
00638   ScenarioScanner() : scanned(false) {}
00639 
00644   void Scan(bool rescan)
00645   {
00646     if (this->scanned && !rescan) return;
00647 
00648     this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
00649     this->scanned = true;
00650   }
00651 
00652   /* virtual */ bool AddFile(const char *filename, size_t basepath_length)
00653   {
00654     FILE *f = FioFOpenFile(filename, "r");
00655     if (f == NULL) return false;
00656 
00657     ScenarioIdentifier id;
00658     int fret = fscanf(f, "%i", &id.scenid);
00659     FioFCloseFile(f);
00660     if (fret != 1) return false;
00661 
00662     Md5 checksum;
00663     uint8 buffer[1024];
00664     char basename[MAX_PATH]; 
00665     size_t len, size;
00666 
00667     /* open the scenario file, but first get the name.
00668      * This is safe as we check on extension which
00669      * must always exist. */
00670     strecpy(basename, filename, lastof(basename));
00671     *strrchr(basename, '.') = '\0';
00672     f = FioFOpenFile(basename, "rb", SCENARIO_DIR, &size);
00673     if (f == NULL) return false;
00674 
00675     /* calculate md5sum */
00676     while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
00677       size -= len;
00678       checksum.Append(buffer, len);
00679     }
00680     checksum.Finish(id.md5sum);
00681 
00682     FioFCloseFile(f);
00683 
00684     this->Include(id);
00685     return true;
00686   }
00687 };
00688 
00690 static ScenarioScanner _scanner;
00691 
00698 bool HasScenario(const ContentInfo *ci, bool md5sum)
00699 {
00700   _scanner.Scan(false);
00701 
00702   for (ScenarioIdentifier *id = _scanner.Begin(); id != _scanner.End(); id++) {
00703     if (md5sum ?
00704         (memcmp(id->md5sum, ci->md5sum, sizeof(id->md5sum)) == 0) :
00705         (id->scenid == ci->unique_id)) {
00706       return true;
00707     }
00708   }
00709 
00710   return false;
00711 }
00712 
00716 void ScanScenarios()
00717 {
00718   _scanner.Scan(true);
00719 }
00720 
00721 #endif /* ENABLE_NETWORK */