# fios.cpp

Go to the documentation of this file.

    00001 /* $Id: fios.cpp 16060 2009-04-14 21:13:07Z rubidium $ */
    00002
    00007 #include "stdafx.h"
    00008 #include "openttd.h"
    00009 #include "fios.h"
    00010 #include "fileio_func.h"
    00011 #include "tar_type.h"
    00012 #include "string_func.h"
    00013 #include <sys/stat.h>
    00014
    00015 #ifdef WIN32
    00016 # define access _taccess
    00017 #else
    00018 # include <unistd.h>
    00019 #endif /* WIN32 */
    00020
    00021 #include "table/strings.h"
    00022
    00023 /* Variables to display file lists */
    00024 SmallVector<FiosItem, 32> _fios_items;
    00025 static char *_fios_path;
    00026 SmallFiosItem _file_to_saveload;
    00027
    00028 /* OS-specific functions are taken from their respective files (win32/unix/os2 .c) */
    00029 extern bool FiosIsRoot(const char *path);
    00030 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
    00031 extern bool FiosIsHiddenFile(const struct dirent *ent);
    00032 extern void FiosGetDrives();
    00033 extern bool FiosGetDiskFreeSpace(const char *path, uint64 *tot);
    00034
    00035 /* get the name of an oldstyle savegame */
    00036 extern void GetOldSaveGameName(const char *file, char *title, const char *last);
    00037
    00044 int CDECL compare_FiosItems(const void *a, const void *b)
    00045 {
    00046   const FiosItem *da = (const FiosItem *)a;
    00047   const FiosItem *db = (const FiosItem *)b;
    00048   int r = 0;
    00049
    00050   if ((_savegame_sort_order & SORT_BY_NAME) == 0 && da->mtime != db->mtime) {
    00051     r = da->mtime < db->mtime ? -1 : 1;
    00052   } else {
    00053     r = strcasecmp(da->title, db->title);
    00054   }
    00055
    00056   if (_savegame_sort_order & SORT_DESCENDING) r = -r;
    00057   return r;
    00058 }
    00059
    00061 void FiosFreeSavegameList()
    00062 {
    00063   _fios_items.Clear();
    00064   _fios_items.Compact();
    00065 };
    00066
    00074 StringID FiosGetDescText(const char **path, uint64 *total_free)
    00075 {
    00076   *path = _fios_path;
    00077   return FiosGetDiskFreeSpace(*path, total_free) ? STR_4005_BYTES_FREE : STR_4006_UNABLE_TO_READ_DRIVE;
    00078 }
    00079
    00080 /* Browse to a new path based on the passed FiosItem struct
    00081  * @param *item FiosItem object telling us what to do
    00082  * @return a string if we have given a file as a target, otherwise NULL */
    00083 const char *FiosBrowseTo(const FiosItem *item)
    00084 {
    00085   char *path = _fios_path;
    00086
    00087   switch (item->type) {
    00088     case FIOS_TYPE_DRIVE:
    00089 #if defined(WINCE)
    00090       snprintf(path, MAX_PATH, PATHSEP "");
    00091 #elif defined(WIN32) || defined(__OS2__)
    00092       snprintf(path, MAX_PATH, "%c:" PATHSEP, item->title[0]);
    00093 #endif
    00094     /* Fallthrough */
    00095     case FIOS_TYPE_INVALID:
    00096       break;
    00097
    00098     case FIOS_TYPE_PARENT: {
    00099       /* Check for possible NULL ptr (not required for UNIXes, but AmigaOS-alikes) */
    00100       char *s = strrchr(path, PATHSEPCHAR);
    00101       if (s != NULL && s != path) {
    00102         s[0] = '\0'; // Remove last path separator character, so we can go up one level.
    00103       }
    00104       s = strrchr(path, PATHSEPCHAR);
    00105       if (s != NULL) s[1] = '\0'; // go up a directory
    00106 #if defined(__MORPHOS__) || defined(__AMIGAOS__)
    00107       /* On MorphOS or AmigaOS paths look like: "Volume:directory/subdirectory" */
    00108       else if ((s = strrchr(path, ':')) != NULL) s[1] = '\0';
    00109 #endif
    00110       break;
    00111     }
    00112
    00113     case FIOS_TYPE_DIR:
    00114       strcat(path, item->name);
    00115       strcat(path, PATHSEP);
    00116       break;
    00117
    00118     case FIOS_TYPE_DIRECT:
    00119       snprintf(path, MAX_PATH, "%s", item->name);
    00120       break;
    00121
    00122     case FIOS_TYPE_FILE:
    00123     case FIOS_TYPE_OLDFILE:
    00124     case FIOS_TYPE_SCENARIO:
    00125     case FIOS_TYPE_OLD_SCENARIO:
    00126     case FIOS_TYPE_PNG:
    00127     case FIOS_TYPE_BMP:
    00128       return item->name;
    00129   }
    00130
    00131   return NULL;
    00132 }
    00133
    00134 void FiosMakeSavegameName(char *buf, const char *name, size_t size)
    00135 {
    00136   const char *extension, *period;
    00137
    00138   extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav";
    00139
    00140   /* Don't append the extension if it is already there */
    00141   period = strrchr(name, '.');
    00142   if (period != NULL && strcasecmp(period, extension) == 0) extension = "";
    00143 #if  defined(__MORPHOS__) || defined(__AMIGAOS__)
    00144   if (_fios_path != NULL) {
    00145     unsigned char sepchar = _fios_path[(strlen(_fios_path) - 1)];
    00146
    00147     if (sepchar != ':' && sepchar != '/') {
    00148       snprintf(buf, size, "%s" PATHSEP "%s%s", _fios_path, name, extension);
    00149     } else {
    00150       snprintf(buf, size, "%s%s%s", _fios_path, name, extension);
    00151     }
    00152   } else {
    00153     snprintf(buf, size, "%s%s", name, extension);
    00154   }
    00155 #else
    00156   snprintf(buf, size, "%s" PATHSEP "%s%s", _fios_path, name, extension);
    00157 #endif
    00158 }
    00159
    00160 bool FiosDelete(const char *name)
    00161 {
    00162   char filename[512];
    00163
    00164   FiosMakeSavegameName(filename, name, lengthof(filename));
    00165   return unlink(filename) == 0;
    00166 }
    00167
    00168 bool FileExists(const char *filename)
    00169 {
    00170 #if defined(WINCE)
    00171   /* There is always one platform that doesn't support basic commands... */
    00172   HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    00173   if (hand == INVALID_HANDLE_VALUE) return 1;
    00174   CloseHandle(hand);
    00175   return 0;
    00176 #else
    00177   return access(OTTD2FS(filename), 0) == 0;
    00178 #endif
    00179 }
    00180
    00181 typedef FiosType fios_getlist_callback_proc(SaveLoadDialogMode mode, const char *filename, const char *ext, char *title, const char *last);
    00182
    00186 class FiosFileScanner : public FileScanner {
    00187   SaveLoadDialogMode mode;
    00188   fios_getlist_callback_proc *callback_proc;
    00189 public:
    00195   FiosFileScanner(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc) :
    00196     mode(mode),
    00197     callback_proc(callback_proc)
    00198   {}
    00199
    00200   /* virtual */ bool AddFile(const char *filename, size_t basepath_length);
    00201 };
    00202
    00209 bool FiosFileScanner::AddFile(const char *filename, size_t basepath_length)
    00210 {
    00211   const char *ext = strrchr(filename, '.');
    00212   if (ext == NULL) return false;
    00213
    00214   char fios_title[64];
    00215   fios_title[0] = '\0'; // reset the title;
    00216
    00217   FiosType type = this->callback_proc(this->mode, filename, ext, fios_title, lastof(fios_title));
    00218   if (type == FIOS_TYPE_INVALID) return false;
    00219
    00220   for (const FiosItem *fios = _fios_items.Begin(); fios != _fios_items.End(); fios++) {
    00221     if (strcmp(fios->name, filename) == 0) return false;
    00222   }
    00223
    00224   FiosItem *fios = _fios_items.Append();
    00225 #ifdef WIN32
    00226   struct _stat sb;
    00227   if (_tstat(OTTD2FS(filename), &sb) == 0) {
    00228 #else
    00229   struct stat sb;
    00230   if (stat(filename, &sb) == 0) {
    00231 #endif
    00232     fios->mtime = sb.st_mtime;
    00233   } else {
    00234     fios->mtime = 0;
    00235   }
    00236
    00237   fios->type = type;
    00238   strecpy(fios->name, filename, lastof(fios->name));
    00239
    00240   /* If the file doesn't have a title, use it's filename */
    00241   const char *t = fios_title;
    00242   if (StrEmpty(fios_title)) {
    00243     t = strrchr(filename, PATHSEPCHAR);
    00244     t = (t == NULL) ? filename : (t + 1);
    00245   }
    00246   strecpy(fios->title, t, lastof(fios->title));
    00247   str_validate(fios->title, lastof(fios->title));
    00248
    00249   return true;
    00250 }
    00251
    00252
    00258 static void FiosGetFileList(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc, Subdirectory subdir)
    00259 {
    00260   struct stat sb;
    00261   struct dirent *dirent;
    00262   DIR *dir;
    00263   FiosItem *fios;
    00264   int sort_start;
    00265   char d_name[sizeof(fios->name)];
    00266
    00267   _fios_items.Clear();
    00268
    00269   /* A parent directory link exists if we are not in the root directory */
    00270   if (!FiosIsRoot(_fios_path) && mode != SLD_NEW_GAME) {
    00271     fios = _fios_items.Append();
    00272     fios->type = FIOS_TYPE_PARENT;
    00273     fios->mtime = 0;
    00274     strecpy(fios->name, "..", lastof(fios->name));
    00275     strecpy(fios->title, ".. (Parent directory)", lastof(fios->title));
    00276   }
    00277
    00278   /* Show subdirectories */
    00279   if (mode != SLD_NEW_GAME && (dir = ttd_opendir(_fios_path)) != NULL) {
    00280     while ((dirent = readdir(dir)) != NULL) {
    00281       strecpy(d_name, FS2OTTD(dirent->d_name), lastof(d_name));
    00282
    00283       /* found file must be directory, but not '.' or '..' */
    00284       if (FiosIsValidFile(_fios_path, dirent, &sb) && S_ISDIR(sb.st_mode) &&
    00285           (!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
    00286           strcmp(d_name, ".") != 0 && strcmp(d_name, "..") != 0) {
    00287         fios = _fios_items.Append();
    00288         fios->type = FIOS_TYPE_DIR;
    00289         fios->mtime = 0;
    00290         strecpy(fios->name, d_name, lastof(fios->name));
    00291         snprintf(fios->title, lengthof(fios->title), "%s" PATHSEP " (Directory)", d_name);
    00292         str_validate(fios->title, lastof(fios->title));
    00293       }
    00294     }
    00295     closedir(dir);
    00296   }
    00297
    00298   /* Sort the subdirs always by name, ascending, remember user-sorting order */
    00299   {
    00300     byte order = _savegame_sort_order;
    00301     _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
    00302     qsort(_fios_items.Begin(), _fios_items.Length(), sizeof(FiosItem), compare_FiosItems);
    00303     _savegame_sort_order = order;
    00304   }
    00305
    00306   /* This is where to start sorting for the filenames */
    00307   sort_start = _fios_items.Length();
    00308
    00309   /* Show files */
    00310   FiosFileScanner scanner(mode, callback_proc);
    00311   if (subdir == NO_DIRECTORY) {
    00312     scanner.Scan(NULL, _fios_path, false);
    00313   } else {
    00314     scanner.Scan(NULL, subdir, true, true);
    00315   }
    00316
    00317   qsort(_fios_items.Get(sort_start), _fios_items.Length() - sort_start, sizeof(FiosItem), compare_FiosItems);
    00318
    00319   /* Show drives */
    00320   if (mode != SLD_NEW_GAME) FiosGetDrives();
    00321
    00322   _fios_items.Compact();
    00323 }
    00324
    00332 static void GetFileTitle(const char *file, char *title, const char *last)
    00333 {
    00334   char buf[MAX_PATH];
    00335   strecpy(buf, file, lastof(buf));
    00336   strecat(buf, ".title", lastof(buf));
    00337
    00338   FILE *f = FioFOpenFile(buf, "r");
    00339   if (f == NULL) return;
    00340
    00341   size_t read = fread(title, 1, last - title, f);
    00342   assert(title + read <= last);
    00343   title[read] = '\0';
    00344   str_validate(title, last);
    00345   FioFCloseFile(f);
    00346 }
    00347
    00359 FiosType FiosGetSavegameListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00360 {
    00361   /* Show savegame files
    00362    * .SAV OpenTTD saved game
    00363    * .SS1 Transport Tycoon Deluxe preset game
    00364    * .SV1 Transport Tycoon Deluxe (Patch) saved game
    00365    * .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
    00366   if (strcasecmp(ext, ".sav") == 0) {
    00367     GetFileTitle(file, title, last);
    00368     return FIOS_TYPE_FILE;
    00369   }
    00370
    00371   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
    00372     if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
    00373         strcasecmp(ext, ".sv2") == 0) {
    00374       if (title != NULL) GetOldSaveGameName(file, title, last);
    00375       return FIOS_TYPE_OLDFILE;
    00376     }
    00377   }
    00378
    00379   return FIOS_TYPE_INVALID;
    00380 }
    00381
    00388 void FiosGetSavegameList(SaveLoadDialogMode mode)
    00389 {
    00390   static char *fios_save_path = NULL;
    00391
    00392   if (fios_save_path == NULL) {
    00393     fios_save_path = MallocT<char>(MAX_PATH);
    00394     FioGetDirectory(fios_save_path, MAX_PATH, SAVE_DIR);
    00395   }
    00396
    00397   _fios_path = fios_save_path;
    00398
    00399   FiosGetFileList(mode, &FiosGetSavegameListCallback, NO_DIRECTORY);
    00400 }
    00401
    00413 static FiosType FiosGetScenarioListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00414 {
    00415   /* Show scenario files
    00416    * .SCN OpenTTD style scenario file
    00417    * .SV0 Transport Tycoon Deluxe (Patch) scenario
    00418    * .SS0 Transport Tycoon Deluxe preset scenario */
    00419   if (strcasecmp(ext, ".scn") == 0) {
    00420     GetFileTitle(file, title, last);
    00421     return FIOS_TYPE_SCENARIO;
    00422   }
    00423
    00424   if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO || mode == SLD_NEW_GAME) {
    00425     if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
    00426       GetOldSaveGameName(file, title, last);
    00427       return FIOS_TYPE_OLD_SCENARIO;
    00428     }
    00429   }
    00430
    00431   return FIOS_TYPE_INVALID;
    00432 }
    00433
    00440 void FiosGetScenarioList(SaveLoadDialogMode mode)
    00441 {
    00442   static char *fios_scn_path = NULL;
    00443
    00444   /* Copy the default path on first run or on 'New Game' */
    00445   if (fios_scn_path == NULL) {
    00446     fios_scn_path = MallocT<char>(MAX_PATH);
    00447     FioGetDirectory(fios_scn_path, MAX_PATH, SCENARIO_DIR);
    00448   }
    00449
    00450   _fios_path = fios_scn_path;
    00451
    00452   char base_path[MAX_PATH];
    00453   FioGetDirectory(base_path, sizeof(base_path), SCENARIO_DIR);
    00454
    00455   FiosGetFileList(mode, &FiosGetScenarioListCallback, (mode == SLD_LOAD_SCENARIO && strcmp(base_path, _fios_path) == 0) ? SCENARIO_DIR : NO_DIRECTORY);
    00456 }
    00457
    00458 static FiosType FiosGetHeightmapListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
    00459 {
    00460   /* Show heightmap files
    00461    * .PNG PNG Based heightmap files
    00462    * .BMP BMP Based heightmap files
    00463    */
    00464
    00465   FiosType type = FIOS_TYPE_INVALID;
    00466
    00467 #ifdef WITH_PNG
    00468   if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
    00469 #endif /* WITH_PNG */
    00470
    00471   if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
    00472
    00473   if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
    00474
    00475   TarFileList::iterator it = _tar_filelist.find(file);
    00476   if (it != _tar_filelist.end()) {
    00477     /* If the file is in a tar and that tar is not in a heightmap
    00478      * directory we are for sure not supposed to see it.
    00479      * Examples of this are pngs part of documentation within
    00480      * collections of NewGRFs or 32 bpp graphics replacement PNGs.
    00481      */
    00482     bool match = false;
    00483     Searchpath sp;
    00484     FOR_ALL_SEARCHPATHS(sp) {
    00485       char buf[MAX_PATH];
    00486       FioAppendDirectory(buf, sizeof(buf), sp, HEIGHTMAP_DIR);
    00487
    00488       if (strncmp(buf, it->second.tar_filename, strlen(buf)) == 0) {
    00489         match = true;
    00490         break;
    00491       }
    00492     }
    00493
    00494     if (!match) return FIOS_TYPE_INVALID;
    00495   }
    00496
    00497   GetFileTitle(file, title, last);
    00498
    00499   return type;
    00500 }
    00501
    00502 /* Get a list of Heightmaps */
    00503 void FiosGetHeightmapList(SaveLoadDialogMode mode)
    00504 {
    00505   static char *fios_hmap_path = NULL;
    00506
    00507   if (fios_hmap_path == NULL) {
    00508     fios_hmap_path = MallocT<char>(MAX_PATH);
    00509     FioGetDirectory(fios_hmap_path, MAX_PATH, HEIGHTMAP_DIR);
    00510   }
    00511
    00512   _fios_path = fios_hmap_path;
    00513
    00514   char base_path[MAX_PATH];
    00515   FioGetDirectory(base_path, sizeof(base_path), HEIGHTMAP_DIR);
    00516
    00517   FiosGetFileList(mode, &FiosGetHeightmapListCallback, strcmp(base_path, _fios_path) == 0 ? HEIGHTMAP_DIR : NO_DIRECTORY);
    00518 }
    00519
    00520 #if defined(ENABLE_NETWORK)
    00521 #include "core/smallvec_type.hpp"
    00522 #include "network/network_content.h"
    00523 #include "md5.h"
    00524
    00526 struct ScenarioIdentifier {
    00527   uint32 scenid;
    00528   uint8 md5sum[16];
    00529
    00530   bool operator == (const ScenarioIdentifier &other) const
    00531   {
    00532     return this->scenid == other.scenid &&
    00533         memcmp(this->md5sum, other.md5sum, sizeof(this->md5sum)) == 0;
    00534   }
    00535
    00536   bool operator != (const ScenarioIdentifier &other) const
    00537   {
    00538     return !(*this == other);
    00539   }
    00540 };
    00541
    00545 class ScenarioScanner : protected FileScanner, public SmallVector<ScenarioIdentifier, 8> {
    00546   bool scanned;
    00547 public:
    00549   ScenarioScanner() : scanned(false) {}
    00550
    00555   void Scan(bool rescan)
    00556   {
    00557     if (this->scanned && !rescan) return;
    00558
    00559     this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
    00560     this->scanned = true;
    00561   }
    00562
    00563   /* virtual */ bool AddFile(const char *filename, size_t basepath_length)
    00564   {
    00565     FILE *f = FioFOpenFile(filename, "r");
    00566     if (f == NULL) return false;
    00567
    00568     ScenarioIdentifier id;
    00569     int fret = fscanf(f, "%i", &id.scenid);
    00570     FioFCloseFile(f);
    00571     if (fret != 1) return false;
    00572
    00573     Md5 checksum;
    00574     uint8 buffer[1024];
    00575     size_t len, size;
    00576
    00577     /* open the scenario file, but first get the name.
    00578      * This is safe as we check on extension which
    00579      * must always exist. */
    00580     *(char *)strrchr(filename, '.') = '\0';
    00581     f = FioFOpenFile(filename, "rb", SCENARIO_DIR, &size);
    00582     if (f == NULL) return false;
    00583
    00584     /* calculate md5sum */
    00585     while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
    00586       size -= len;
    00587       checksum.Append(buffer, len);
    00588     }
    00589     checksum.Finish(id.md5sum);
    00590
    00591     FioFCloseFile(f);
    00592
    00593     this->Include(id);
    00594     return true;
    00595   }
    00596 };
    00597
    00599 static ScenarioScanner _scanner;
    00600
    00607 bool HasScenario(const ContentInfo *ci, bool md5sum)
    00608 {
    00609   _scanner.Scan(false);
    00610
    00611   for (ScenarioIdentifier *id = _scanner.Begin(); id != _scanner.End(); id++) {
    00612     if (md5sum ?
    00613         (memcmp(id->md5sum, ci->md5sum, sizeof(id->md5sum)) == 0) :
    00614         (id->scenid == ci->unique_id)) {
    00615       return true;
    00616     }
    00617   }
    00618
    00619   return false;
    00620 }
    00621
    00625 void ScanScenarios()
    00626 {
    00627   _scanner.Scan(true);
    00628 }
    00629
    00630 #endif /* ENABLE_NETWORK */