OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

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
69 void FiosFreeSavegameList()
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 {
224 SaveLoadDialogMode mode;
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
430 void FiosGetSavegameList(SaveLoadDialogMode mode)
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
444 FiosGetFileList(mode, &FiosGetSavegameListCallback, NO_DIRECTORY);
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
484 void FiosGetScenarioList(SaveLoadDialogMode mode)
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
553 void FiosGetHeightmapList(SaveLoadDialogMode mode)
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
594 struct ScenarioIdentifier {
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
637 ScenarioIdentifier id;
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
671 static ScenarioScanner _scanner;
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
707 void ScanScenarios()
708 {
709 _scanner.Scan(true);
710 }
711
712 #endif /* ENABLE_NETWORK */