OpenTTD
fios.cpp
Go to the documentation of this file.
1 /* $Id: fios.cpp 26554 2014-05-03 15:45:54Z alberth $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
15 #include "stdafx.h"
16 #include "fios.h"
17 #include "fileio_func.h"
18 #include "tar_type.h"
19 #include "screenshot.h"
20 #include "string_func.h"
21 #include <sys/stat.h>
22 
23 #ifndef WIN32
24 # include <unistd.h>
25 #endif /* WIN32 */
26 
27 #include "table/strings.h"
28 
29 #include "safeguards.h"
30 
31 /* Variables to display file lists */
32 SmallVector<FiosItem, 32> _fios_items;
33 static char *_fios_path;
34 static const char *_fios_path_last;
35 SmallFiosItem _file_to_saveload;
36 SortingBits _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
37 
38 /* OS-specific functions are taken from their respective files (win32/unix/os2 .c) */
39 extern bool FiosIsRoot(const char *path);
40 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
41 extern bool FiosIsHiddenFile(const struct dirent *ent);
42 extern void FiosGetDrives();
43 extern bool FiosGetDiskFreeSpace(const char *path, uint64 *tot);
44 
45 /* get the name of an oldstyle savegame */
46 extern void GetOldSaveGameName(const char *file, char *title, const char *last);
47 
54 int CDECL CompareFiosItems(const FiosItem *da, const FiosItem *db)
55 {
56  int r = 0;
57 
58  if ((_savegame_sort_order & SORT_BY_NAME) == 0 && da->mtime != db->mtime) {
59  r = da->mtime < db->mtime ? -1 : 1;
60  } else {
61  r = strcasecmp(da->title, db->title);
62  }
63 
64  if (_savegame_sort_order & SORT_DESCENDING) r = -r;
65  return r;
66 }
67 
70 {
71  _fios_items.Clear();
72  _fios_items.Compact();
73 }
74 
82 StringID FiosGetDescText(const char **path, uint64 *total_free)
83 {
84  *path = _fios_path;
85  return FiosGetDiskFreeSpace(*path, total_free) ? STR_SAVELOAD_BYTES_FREE : STR_ERROR_UNABLE_TO_READ_DRIVE;
86 }
87 
93 const char *FiosBrowseTo(const FiosItem *item)
94 {
95  switch (item->type) {
96  case FIOS_TYPE_DRIVE:
97 #if defined(WINCE)
98  seprintf(_fios_path, _fios_path_last, PATHSEP "");
99 #elif defined(WIN32) || defined(__OS2__)
100  seprintf(_fios_path, _fios_path_last, "%c:" PATHSEP, item->title[0]);
101 #endif
102  /* FALL THROUGH */
103  case FIOS_TYPE_INVALID:
104  break;
105 
106  case FIOS_TYPE_PARENT: {
107  /* Check for possible NULL ptr (not required for UNIXes, but AmigaOS-alikes) */
108  char *s = strrchr(_fios_path, PATHSEPCHAR);
109  if (s != NULL && s != _fios_path) {
110  s[0] = '\0'; // Remove last path separator character, so we can go up one level.
111  }
112  s = strrchr(_fios_path, PATHSEPCHAR);
113  if (s != NULL) {
114  s[1] = '\0'; // go up a directory
115 #if defined(__MORPHOS__) || defined(__AMIGAOS__)
116  /* On MorphOS or AmigaOS paths look like: "Volume:directory/subdirectory" */
117  } else if ((s = strrchr(_fios_path, ':')) != NULL) {
118  s[1] = '\0';
119 #endif
120  }
121  break;
122  }
123 
124  case FIOS_TYPE_DIR:
125  strecat(_fios_path, item->name, _fios_path_last);
126  strecat(_fios_path, PATHSEP, _fios_path_last);
127  break;
128 
129  case FIOS_TYPE_DIRECT:
130  seprintf(_fios_path, _fios_path_last, "%s", item->name);
131  break;
132 
133  case FIOS_TYPE_FILE:
134  case FIOS_TYPE_OLDFILE:
135  case FIOS_TYPE_SCENARIO:
136  case FIOS_TYPE_OLD_SCENARIO:
137  case FIOS_TYPE_PNG:
138  case FIOS_TYPE_BMP:
139  return item->name;
140  }
141 
142  return NULL;
143 }
144 
153 static void FiosMakeFilename(char *buf, const char *path, const char *name, const char *ext, const char *last)
154 {
155  const char *period;
156 
157  /* Don't append the extension if it is already there */
158  period = strrchr(name, '.');
159  if (period != NULL && strcasecmp(period, ext) == 0) ext = "";
160 #if defined(__MORPHOS__) || defined(__AMIGAOS__)
161  if (path != NULL) {
162  unsigned char sepchar = path[(strlen(path) - 1)];
163 
164  if (sepchar != ':' && sepchar != '/') {
165  seprintf(buf, last, "%s" PATHSEP "%s%s", path, name, ext);
166  } else {
167  seprintf(buf, last, "%s%s%s", path, name, ext);
168  }
169  } else {
170  seprintf(buf, last, "%s%s", name, ext);
171  }
172 #else
173  seprintf(buf, last, "%s" PATHSEP "%s%s", path, name, ext);
174 #endif
175 }
176 
183 void FiosMakeSavegameName(char *buf, const char *name, const char *last)
184 {
185  const char *extension = (_game_mode == GM_EDITOR) ? ".scn" : ".sav";
186 
187  FiosMakeFilename(buf, _fios_path, name, extension, last);
188 }
189 
196 void FiosMakeHeightmapName(char *buf, const char *name, const char *last)
197 {
198  char ext[5];
199  ext[0] = '.';
200  strecpy(ext + 1, GetCurrentScreenshotExtension(), lastof(ext));
201 
202  FiosMakeFilename(buf, _fios_path, name, ext, last);
203 }
204 
210 bool FiosDelete(const char *name)
211 {
212  char filename[512];
213 
214  FiosMakeSavegameName(filename, name, lastof(filename));
215  return unlink(filename) == 0;
216 }
217 
218 typedef FiosType fios_getlist_callback_proc(SaveLoadDialogMode mode, const char *filename, const char *ext, char *title, const char *last);
219 
223 class FiosFileScanner : public FileScanner {
225  fios_getlist_callback_proc *callback_proc;
226 public:
232  FiosFileScanner(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc) :
233  mode(mode),
234  callback_proc(callback_proc)
235  {}
236 
237  /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename);
238 };
239 
246 bool FiosFileScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
247 {
248  const char *ext = strrchr(filename, '.');
249  if (ext == NULL) return false;
250 
251  char fios_title[64];
252  fios_title[0] = '\0'; // reset the title;
253 
254  FiosType type = this->callback_proc(this->mode, filename, ext, fios_title, lastof(fios_title));
255  if (type == FIOS_TYPE_INVALID) return false;
256 
257  for (const FiosItem *fios = _fios_items.Begin(); fios != _fios_items.End(); fios++) {
258  if (strcmp(fios->name, filename) == 0) return false;
259  }
260 
261  FiosItem *fios = _fios_items.Append();
262 #ifdef WIN32
263  struct _stat sb;
264  if (_tstat(OTTD2FS(filename), &sb) == 0) {
265 #else
266  struct stat sb;
267  if (stat(filename, &sb) == 0) {
268 #endif
269  fios->mtime = sb.st_mtime;
270  } else {
271  fios->mtime = 0;
272  }
273 
274  fios->type = type;
275  strecpy(fios->name, filename, lastof(fios->name));
276 
277  /* If the file doesn't have a title, use its filename */
278  const char *t = fios_title;
279  if (StrEmpty(fios_title)) {
280  t = strrchr(filename, PATHSEPCHAR);
281  t = (t == NULL) ? filename : (t + 1);
282  }
283  strecpy(fios->title, t, lastof(fios->title));
284  str_validate(fios->title, lastof(fios->title));
285 
286  return true;
287 }
288 
289 
296 static void FiosGetFileList(SaveLoadDialogMode mode, fios_getlist_callback_proc *callback_proc, Subdirectory subdir)
297 {
298  struct stat sb;
299  struct dirent *dirent;
300  DIR *dir;
301  FiosItem *fios;
302  int sort_start;
303  char d_name[sizeof(fios->name)];
304 
305  _fios_items.Clear();
306 
307  /* A parent directory link exists if we are not in the root directory */
308  if (!FiosIsRoot(_fios_path)) {
309  fios = _fios_items.Append();
310  fios->type = FIOS_TYPE_PARENT;
311  fios->mtime = 0;
312  strecpy(fios->name, "..", lastof(fios->name));
313  strecpy(fios->title, ".. (Parent directory)", lastof(fios->title));
314  }
315 
316  /* Show subdirectories */
317  if ((dir = ttd_opendir(_fios_path)) != NULL) {
318  while ((dirent = readdir(dir)) != NULL) {
319  strecpy(d_name, FS2OTTD(dirent->d_name), lastof(d_name));
320 
321  /* found file must be directory, but not '.' or '..' */
322  if (FiosIsValidFile(_fios_path, dirent, &sb) && S_ISDIR(sb.st_mode) &&
323  (!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
324  strcmp(d_name, ".") != 0 && strcmp(d_name, "..") != 0) {
325  fios = _fios_items.Append();
326  fios->type = FIOS_TYPE_DIR;
327  fios->mtime = 0;
328  strecpy(fios->name, d_name, lastof(fios->name));
329  seprintf(fios->title, lastof(fios->title), "%s" PATHSEP " (Directory)", d_name);
330  str_validate(fios->title, lastof(fios->title));
331  }
332  }
333  closedir(dir);
334  }
335 
336  /* Sort the subdirs always by name, ascending, remember user-sorting order */
337  {
338  SortingBits order = _savegame_sort_order;
339  _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
340  QSortT(_fios_items.Begin(), _fios_items.Length(), CompareFiosItems);
341  _savegame_sort_order = order;
342  }
343 
344  /* This is where to start sorting for the filenames */
345  sort_start = _fios_items.Length();
346 
347  /* Show files */
348  FiosFileScanner scanner(mode, callback_proc);
349  if (subdir == NO_DIRECTORY) {
350  scanner.Scan(NULL, _fios_path, false);
351  } else {
352  scanner.Scan(NULL, subdir, true, true);
353  }
354 
355  QSortT(_fios_items.Get(sort_start), _fios_items.Length() - sort_start, CompareFiosItems);
356 
357  /* Show drives */
358  FiosGetDrives();
359 
360  _fios_items.Compact();
361 }
362 
371 static void GetFileTitle(const char *file, char *title, const char *last, Subdirectory subdir)
372 {
373  char buf[MAX_PATH];
374  strecpy(buf, file, lastof(buf));
375  strecat(buf, ".title", lastof(buf));
376 
377  FILE *f = FioFOpenFile(buf, "r", subdir);
378  if (f == NULL) return;
379 
380  size_t read = fread(title, 1, last - title, f);
381  assert(title + read <= last);
382  title[read] = '\0';
383  str_validate(title, last);
384  FioFCloseFile(f);
385 }
386 
398 FiosType FiosGetSavegameListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
399 {
400  /* Show savegame files
401  * .SAV OpenTTD saved game
402  * .SS1 Transport Tycoon Deluxe preset game
403  * .SV1 Transport Tycoon Deluxe (Patch) saved game
404  * .SV2 Transport Tycoon Deluxe (Patch) saved 2-player game */
405 
406  /* Don't crash if we supply no extension */
407  if (ext == NULL) return FIOS_TYPE_INVALID;
408 
409  if (strcasecmp(ext, ".sav") == 0) {
410  GetFileTitle(file, title, last, SAVE_DIR);
411  return FIOS_TYPE_FILE;
412  }
413 
414  if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
415  if (strcasecmp(ext, ".ss1") == 0 || strcasecmp(ext, ".sv1") == 0 ||
416  strcasecmp(ext, ".sv2") == 0) {
417  if (title != NULL) GetOldSaveGameName(file, title, last);
418  return FIOS_TYPE_OLDFILE;
419  }
420  }
421 
422  return FIOS_TYPE_INVALID;
423 }
424 
431 {
432  static char *fios_save_path = NULL;
433  static char *fios_save_path_last = NULL;
434 
435  if (fios_save_path == NULL) {
436  fios_save_path = MallocT<char>(MAX_PATH);
437  fios_save_path_last = fios_save_path + MAX_PATH - 1;
438  FioGetDirectory(fios_save_path, fios_save_path_last, SAVE_DIR);
439  }
440 
441  _fios_path = fios_save_path;
442  _fios_path_last = fios_save_path_last;
443 
445 }
446 
458 static FiosType FiosGetScenarioListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
459 {
460  /* Show scenario files
461  * .SCN OpenTTD style scenario file
462  * .SV0 Transport Tycoon Deluxe (Patch) scenario
463  * .SS0 Transport Tycoon Deluxe preset scenario */
464  if (strcasecmp(ext, ".scn") == 0) {
465  GetFileTitle(file, title, last, SCENARIO_DIR);
466  return FIOS_TYPE_SCENARIO;
467  }
468 
469  if (mode == SLD_LOAD_GAME || mode == SLD_LOAD_SCENARIO) {
470  if (strcasecmp(ext, ".sv0") == 0 || strcasecmp(ext, ".ss0") == 0 ) {
471  GetOldSaveGameName(file, title, last);
472  return FIOS_TYPE_OLD_SCENARIO;
473  }
474  }
475 
476  return FIOS_TYPE_INVALID;
477 }
478 
485 {
486  static char *fios_scn_path = NULL;
487  static char *fios_scn_path_last = NULL;
488 
489  /* Copy the default path on first run or on 'New Game' */
490  if (fios_scn_path == NULL) {
491  fios_scn_path = MallocT<char>(MAX_PATH);
492  fios_scn_path_last = fios_scn_path + MAX_PATH - 1;
493  FioGetDirectory(fios_scn_path, fios_scn_path_last, SCENARIO_DIR);
494  }
495 
496  _fios_path = fios_scn_path;
497  _fios_path_last = fios_scn_path_last;
498 
499  char base_path[MAX_PATH];
500  FioGetDirectory(base_path, lastof(base_path), SCENARIO_DIR);
501 
502  FiosGetFileList(mode, &FiosGetScenarioListCallback, (mode == SLD_LOAD_SCENARIO && strcmp(base_path, _fios_path) == 0) ? SCENARIO_DIR : NO_DIRECTORY);
503 }
504 
505 static FiosType FiosGetHeightmapListCallback(SaveLoadDialogMode mode, const char *file, const char *ext, char *title, const char *last)
506 {
507  /* Show heightmap files
508  * .PNG PNG Based heightmap files
509  * .BMP BMP Based heightmap files
510  */
511 
512  FiosType type = FIOS_TYPE_INVALID;
513 
514 #ifdef WITH_PNG
515  if (strcasecmp(ext, ".png") == 0) type = FIOS_TYPE_PNG;
516 #endif /* WITH_PNG */
517 
518  if (strcasecmp(ext, ".bmp") == 0) type = FIOS_TYPE_BMP;
519 
520  if (type == FIOS_TYPE_INVALID) return FIOS_TYPE_INVALID;
521 
522  TarFileList::iterator it = _tar_filelist[SCENARIO_DIR].find(file);
523  if (it != _tar_filelist[SCENARIO_DIR].end()) {
524  /* If the file is in a tar and that tar is not in a heightmap
525  * directory we are for sure not supposed to see it.
526  * Examples of this are pngs part of documentation within
527  * collections of NewGRFs or 32 bpp graphics replacement PNGs.
528  */
529  bool match = false;
530  Searchpath sp;
531  FOR_ALL_SEARCHPATHS(sp) {
532  char buf[MAX_PATH];
533  FioAppendDirectory(buf, lastof(buf), sp, HEIGHTMAP_DIR);
534 
535  if (strncmp(buf, it->second.tar_filename, strlen(buf)) == 0) {
536  match = true;
537  break;
538  }
539  }
540 
541  if (!match) return FIOS_TYPE_INVALID;
542  }
543 
544  GetFileTitle(file, title, last, HEIGHTMAP_DIR);
545 
546  return type;
547 }
548 
554 {
555  static char *fios_hmap_path = NULL;
556  static char *fios_hmap_path_last = NULL;
557 
558  if (fios_hmap_path == NULL) {
559  fios_hmap_path = MallocT<char>(MAX_PATH);
560  fios_hmap_path_last = fios_hmap_path + MAX_PATH - 1;
561  FioGetDirectory(fios_hmap_path, fios_hmap_path_last, HEIGHTMAP_DIR);
562  }
563 
564  _fios_path = fios_hmap_path;
565  _fios_path_last = fios_hmap_path_last;
566 
567  char base_path[MAX_PATH];
568  FioGetDirectory(base_path, lastof(base_path), HEIGHTMAP_DIR);
569 
570  FiosGetFileList(mode, &FiosGetHeightmapListCallback, strcmp(base_path, _fios_path) == 0 ? HEIGHTMAP_DIR : NO_DIRECTORY);
571 }
572 
577 const char *FiosGetScreenshotDir()
578 {
579  static char *fios_screenshot_path = NULL;
580 
581  if (fios_screenshot_path == NULL) {
582  fios_screenshot_path = MallocT<char>(MAX_PATH);
583  FioGetDirectory(fios_screenshot_path, fios_screenshot_path + MAX_PATH - 1, SCREENSHOT_DIR);
584  }
585 
586  return fios_screenshot_path;
587 }
588 
589 #if defined(ENABLE_NETWORK)
590 #include "network/network_content.h"
591 #include "3rdparty/md5/md5.h"
592 
595  uint32 scenid;
596  uint8 md5sum[16];
597  char filename[MAX_PATH];
598 
599  bool operator == (const ScenarioIdentifier &other) const
600  {
601  return this->scenid == other.scenid &&
602  memcmp(this->md5sum, other.md5sum, sizeof(this->md5sum)) == 0;
603  }
604 
605  bool operator != (const ScenarioIdentifier &other) const
606  {
607  return !(*this == other);
608  }
609 };
610 
614 class ScenarioScanner : protected FileScanner, public SmallVector<ScenarioIdentifier, 8> {
615  bool scanned;
616 public:
618  ScenarioScanner() : scanned(false) {}
619 
624  void Scan(bool rescan)
625  {
626  if (this->scanned && !rescan) return;
627 
628  this->FileScanner::Scan(".id", SCENARIO_DIR, true, true);
629  this->scanned = true;
630  }
631 
632  /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
633  {
634  FILE *f = FioFOpenFile(filename, "r", SCENARIO_DIR);
635  if (f == NULL) return false;
636 
638  int fret = fscanf(f, "%i", &id.scenid);
639  FioFCloseFile(f);
640  if (fret != 1) return false;
641  strecpy(id.filename, filename, lastof(id.filename));
642 
643  Md5 checksum;
644  uint8 buffer[1024];
645  char basename[MAX_PATH];
646  size_t len, size;
647 
648  /* open the scenario file, but first get the name.
649  * This is safe as we check on extension which
650  * must always exist. */
651  strecpy(basename, filename, lastof(basename));
652  *strrchr(basename, '.') = '\0';
653  f = FioFOpenFile(basename, "rb", SCENARIO_DIR, &size);
654  if (f == NULL) return false;
655 
656  /* calculate md5sum */
657  while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
658  size -= len;
659  checksum.Append(buffer, len);
660  }
661  checksum.Finish(id.md5sum);
662 
663  FioFCloseFile(f);
664 
665  this->Include(id);
666  return true;
667  }
668 };
669 
672 
679 const char *FindScenario(const ContentInfo *ci, bool md5sum)
680 {
681  _scanner.Scan(false);
682 
683  for (ScenarioIdentifier *id = _scanner.Begin(); id != _scanner.End(); id++) {
684  if (md5sum ? (memcmp(id->md5sum, ci->md5sum, sizeof(id->md5sum)) == 0)
685  : (id->scenid == ci->unique_id)) {
686  return id->filename;
687  }
688  }
689 
690  return NULL;
691 }
692 
699 bool HasScenario(const ContentInfo *ci, bool md5sum)
700 {
701  return (FindScenario(ci, md5sum) != NULL);
702 }
703 
708 {
709  _scanner.Scan(true);
710 }
711 
712 #endif /* ENABLE_NETWORK */