14 #include "3rdparty/md5/md5.h"
31 #include "table/strings.h"
36 static std::string *_fios_path =
nullptr;
37 SortingBits _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
40 extern bool FiosIsRoot(
const char *path);
41 extern bool FiosIsValidFile(
const char *path,
const struct dirent *ent,
struct stat *sb);
42 extern bool FiosIsHiddenFile(
const struct dirent *ent);
43 extern void FiosGetDrives(
FileList &file_list);
44 extern bool FiosGetDiskFreeSpace(
const char *path, uint64 *tot);
47 extern void GetOldSaveGameName(
const std::string &file,
char *title,
const char *last);
58 if ((_savegame_sort_order & SORT_BY_NAME) == 0 && (*this).mtime != other.mtime) {
59 r = (*this).mtime - other.mtime;
61 r =
strnatcmp((*this).title, other.title);
63 if (r == 0)
return false;
64 return (_savegame_sort_order & SORT_DESCENDING) ? r > 0 : r < 0;
77 switch (abstract_filetype) {
106 for (
const auto &it : *
this) {
108 if (strcmp(file, item->name) == 0)
return item;
109 if (strcmp(file, item->title) == 0)
return item;
114 int i = strtol(file, &endptr, 10);
115 if (file == endptr || *endptr !=
'\0') i = -1;
117 if (
IsInsideMM(i, 0, this->size()))
return &this->at(i);
121 char long_file[MAX_PATH];
123 for (
const auto &it : *
this) {
125 if (strcmp(long_file, item->name) == 0)
return item;
126 if (strcmp(long_file, item->title) == 0)
return item;
141 *path = _fios_path->c_str();
142 return FiosGetDiskFreeSpace(*path, total_free) ? STR_SAVELOAD_BYTES_FREE : STR_ERROR_UNABLE_TO_READ_DRIVE;
152 switch (item->type) {
153 case FIOS_TYPE_DRIVE:
154 #if defined(_WIN32) || defined(__OS2__)
155 assert(_fios_path !=
nullptr);
156 *_fios_path = std::string{ item->title[0] } +
":" PATHSEP;
160 case FIOS_TYPE_INVALID:
163 case FIOS_TYPE_PARENT: {
164 assert(_fios_path !=
nullptr);
165 auto s = _fios_path->find_last_of(PATHSEPCHAR);
166 if (s != std::string::npos && s != 0) {
167 _fios_path->erase(s);
170 s = _fios_path->find_last_of(PATHSEPCHAR);
171 if (s != std::string::npos) {
172 _fios_path->erase(s + 1);
178 assert(_fios_path !=
nullptr);
179 *_fios_path += item->name;
180 *_fios_path += PATHSEP;
183 case FIOS_TYPE_DIRECT:
184 assert(_fios_path !=
nullptr);
185 *_fios_path = item->name;
189 case FIOS_TYPE_OLDFILE:
190 case FIOS_TYPE_SCENARIO:
191 case FIOS_TYPE_OLD_SCENARIO:
207 static std::string
FiosMakeFilename(
const std::string *path,
const char *name,
const char *ext)
211 if (path !=
nullptr) {
214 if (!buf.empty() && buf.back() == PATHSEPCHAR) buf.pop_back();
218 const char *period = strrchr(name,
'.');
219 if (period !=
nullptr && strcasecmp(period, ext) == 0) ext =
"";
221 return buf + PATHSEP + name + ext;
233 const char *extension = (_game_mode == GM_EDITOR) ?
".scn" :
".sav";
245 std::string ext(
".");
259 return unlink(filename.c_str()) == 0;
262 typedef FiosType fios_getlist_callback_proc(
SaveLoadOperation fop,
const std::string &filename,
const char *ext,
char *title,
const char *last);
282 bool AddFile(
const std::string &filename,
size_t basepath_length,
const std::string &tar_filename)
override;
293 auto sep = filename.rfind(
'.');
294 if (sep == std::string::npos)
return false;
295 std::string ext = filename.substr(sep);
298 fios_title[0] =
'\0';
301 if (type == FIOS_TYPE_INVALID)
return false;
304 if (filename == fios.name)
return false;
310 HANDLE fh = CreateFile(
OTTD2FS(filename).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0,
nullptr);
312 if (fh != INVALID_HANDLE_VALUE) {
314 ULARGE_INTEGER ft_int64;
316 if (GetFileTime(fh,
nullptr,
nullptr, &ft) != 0) {
317 ft_int64.HighPart = ft.dwHighDateTime;
318 ft_int64.LowPart = ft.dwLowDateTime;
321 fios->mtime = ft_int64.QuadPart / 10000000ULL - 11644473600ULL;
329 if (stat(filename.c_str(), &sb) == 0) {
330 fios->mtime = sb.st_mtime;
340 const char *t = fios_title;
342 auto ps = filename.rfind(PATHSEPCHAR);
343 t = filename.c_str() + (ps == std::string::npos ? 0 : ps + 1);
362 struct dirent *dirent;
366 char d_name[
sizeof(fios->name)];
370 assert(_fios_path !=
nullptr);
373 if (!FiosIsRoot(_fios_path->c_str())) {
375 fios->type = FIOS_TYPE_PARENT;
379 GetString(fios->title, STR_SAVELOAD_PARENT_DIRECTORY,
lastof(fios->title));
383 if ((dir =
ttd_opendir(_fios_path->c_str())) !=
nullptr) {
384 while ((dirent = readdir(dir)) !=
nullptr) {
388 if (FiosIsValidFile(_fios_path->c_str(), dirent, &sb) && S_ISDIR(sb.st_mode) &&
389 (!FiosIsHiddenFile(dirent) || strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) == 0) &&
390 strcmp(d_name,
".") != 0 && strcmp(d_name,
"..") != 0) {
392 fios->type = FIOS_TYPE_DIR;
395 std::string dirname = std::string(d_name) + PATHSEP;
397 GetString(fios->title, STR_SAVELOAD_DIRECTORY,
lastof(fios->title));
406 SortingBits order = _savegame_sort_order;
407 _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
409 _savegame_sort_order = order;
418 scanner.
Scan(
nullptr, _fios_path->c_str(),
false);
441 std::string buf = file;
445 if (f ==
nullptr)
return;
447 size_t read = fread(title, 1, last - title, f);
448 assert(title + read <= last);
474 if (ext ==
nullptr)
return FIOS_TYPE_INVALID;
476 if (strcasecmp(ext,
".sav") == 0) {
478 return FIOS_TYPE_FILE;
482 if (strcasecmp(ext,
".ss1") == 0 || strcasecmp(ext,
".sv1") == 0 ||
483 strcasecmp(ext,
".sv2") == 0) {
484 if (title !=
nullptr) GetOldSaveGameName(file, title, last);
485 return FIOS_TYPE_OLDFILE;
489 return FIOS_TYPE_INVALID;
500 static std::optional<std::string> fios_save_path;
502 if (!fios_save_path) fios_save_path = FioFindDirectory(
SAVE_DIR);
504 _fios_path = &(*fios_save_path);
526 if (strcasecmp(ext,
".scn") == 0) {
528 return FIOS_TYPE_SCENARIO;
532 if (strcasecmp(ext,
".sv0") == 0 || strcasecmp(ext,
".ss0") == 0 ) {
533 GetOldSaveGameName(file, title, last);
534 return FIOS_TYPE_OLD_SCENARIO;
538 return FIOS_TYPE_INVALID;
549 static std::optional<std::string> fios_scn_path;
552 if (!fios_scn_path) fios_scn_path = FioFindDirectory(
SCENARIO_DIR);
554 _fios_path = &(*fios_scn_path);
561 static FiosType FiosGetHeightmapListCallback(
SaveLoadOperation fop,
const std::string &file,
const char *ext,
char *title,
const char *last)
571 if (strcasecmp(ext,
".png") == 0) type = FIOS_TYPE_PNG;
574 if (strcasecmp(ext,
".bmp") == 0) type = FIOS_TYPE_BMP;
576 if (type == FIOS_TYPE_INVALID)
return FIOS_TYPE_INVALID;
578 TarFileList::iterator it = _tar_filelist[
SCENARIO_DIR].find(file);
589 if (buf.compare(0, buf.size(), it->second.tar_filename, 0, buf.size()) == 0) {
595 if (!match)
return FIOS_TYPE_INVALID;
610 static std::optional<std::string> fios_hmap_path;
612 if (!fios_hmap_path) fios_hmap_path = FioFindDirectory(
HEIGHTMAP_DIR);
614 _fios_path = &(*fios_hmap_path);
627 static std::optional<std::string> fios_screenshot_path;
629 if (!fios_screenshot_path) fios_screenshot_path = FioFindDirectory(
SCREENSHOT_DIR);
631 return fios_screenshot_path->c_str();
638 char filename[MAX_PATH];
642 return this->scenid == other.
scenid &&
643 memcmp(this->md5sum, other.
md5sum,
sizeof(this->md5sum)) == 0;
648 return !(*
this == other);
667 if (this->scanned && !rescan)
return;
670 this->scanned =
true;
673 bool AddFile(
const std::string &filename,
size_t basepath_length,
const std::string &tar_filename)
override
676 if (f ==
nullptr)
return false;
679 int fret = fscanf(f,
"%u", &
id.scenid);
681 if (fret != 1)
return false;
692 if (f ==
nullptr)
return false;
695 while ((len = fread(buffer, 1, (size >
sizeof(buffer)) ?
sizeof(buffer) : size, f)) != 0 && size != 0) {
697 checksum.Append(buffer, len);
699 checksum.Finish(
id.md5sum);
722 if (md5sum ? (memcmp(
id.md5sum, ci->
md5sum,
sizeof(
id.md5sum)) == 0)
756 static std::optional<std::string> _autosave_path;
757 if (!_autosave_path) _autosave_path = FioFindDirectory(
AUTOSAVE_DIR);
759 static std::string _prefix;
762 static fios_getlist_callback_proc *proc = [](
SaveLoadOperation fop,
const std::string &file,
const char *ext,
char *title,
const char *last) {
763 if (strcasecmp(ext,
".sav") == 0 &&
StrStartsWith(file, _prefix))
return FIOS_TYPE_FILE;
764 return FIOS_TYPE_INVALID;
768 _prefix = *_autosave_path + this->prefix;
773 scanner.
Scan(
".sav", _autosave_path->c_str(),
false);
776 if (list.begin() != list.end()) {
777 SortingBits order = _savegame_sort_order;
778 _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
779 std::sort(list.begin(), list.end());
780 _savegame_sort_order = order;
782 std::string_view name = list.begin()->title;
783 std::from_chars(name.data() + this->prefix.size(), name.data() + name.size(), this->number);
794 return fmt::format(
"{}{}.sav", this->prefix, this->number);
803 return fmt::format(
"-{}.sav", this->prefix);