14#include "3rdparty/md5/md5.h"
27#include "table/strings.h"
32static std::string *_fios_path =
nullptr;
33SortingBits _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
36extern bool FiosIsRoot(
const std::string &path);
37extern bool FiosIsHiddenFile(
const std::filesystem::path &path);
38extern void FiosGetDrives(
FileList &file_list);
41extern std::string GetOldSaveGameName(std::string_view file);
52 if ((_savegame_sort_order & SORT_BY_NAME) == 0 && (*this).mtime != other.mtime) {
53 r = ClampTo<int32_t>(this->mtime - other.mtime);
57 if (r == 0)
return false;
58 return (_savegame_sort_order & SORT_DESCENDING) ? r > 0 : r < 0;
72 switch (abstract_filetype) {
105 for (
const auto &it : *
this) {
107 if (file == item->name)
return item;
108 if (file == item->title)
return item;
114 if (number.has_value() && !consumer.AnyBytesLeft() &&
IsInsideMM(*number, 0, this->size()))
return &this->at(*number);
118 std::string long_file(file);
120 for (
const auto &it : *
this) {
122 if (long_file == item->name)
return item;
123 if (long_file == item->title)
return item;
147 assert(_fios_path !=
nullptr);
148 *_fios_path = std::string{ item->title, 0, 1 } +
":" PATHSEP;
156 assert(_fios_path !=
nullptr);
157 auto s = _fios_path->find_last_of(PATHSEPCHAR);
158 if (s != std::string::npos && s != 0) {
159 _fios_path->erase(s);
162 s = _fios_path->find_last_of(PATHSEPCHAR);
163 if (s != std::string::npos) {
164 _fios_path->erase(s + 1);
170 assert(_fios_path !=
nullptr);
171 *_fios_path += item->name;
172 *_fios_path += PATHSEP;
176 assert(_fios_path !=
nullptr);
177 *_fios_path = item->name;
194static std::string
FiosMakeFilename(
const std::string *path, std::string_view name, std::string_view ext)
196 std::string_view base_path;
198 if (path !=
nullptr) {
201 if (!base_path.empty() && base_path.back() == PATHSEPCHAR) base_path.remove_suffix(1);
205 auto period = name.find_last_of(
'.');
206 if (period != std::string_view::npos &&
StrEqualsIgnoreCase(name.substr(period), ext)) ext =
"";
208 return fmt::format(
"{}{}{}{}", base_path, PATHSEP, name, ext);
194static std::string
FiosMakeFilename(
const std::string *path, std::string_view name, std::string_view ext) {
…}
218 std::string_view extension = (_game_mode == GM_EDITOR) ?
".scn" :
".sav";
243typedef std::tuple<FiosType, std::string> FiosGetTypeAndNameProc(
SaveLoadOperation fop, std::string_view filename, std::string_view ext);
263 bool AddFile(
const std::string &filename,
size_t basepath_length,
const std::string &tar_filename)
override;
273 auto sep = filename.rfind(
'.');
274 if (sep == std::string::npos)
return false;
275 std::string ext = filename.substr(sep);
278 if (type == FIOS_TYPE_INVALID)
return false;
281 if (filename == fios.name)
return false;
286 std::error_code error_code;
287 auto write_time = std::filesystem::last_write_time(
OTTD2FS(filename), error_code);
291 fios->mtime = std::chrono::duration_cast<std::chrono::milliseconds>(write_time.time_since_epoch()).count();
295 fios->name = filename;
299 auto ps = filename.rfind(PATHSEPCHAR);
300 fios->title =
StrMakeValid(filename.substr((ps == std::string::npos ? 0 : ps + 1)));
319 size_t sort_start = 0;
323 assert(_fios_path !=
nullptr);
327 if (!FiosIsRoot(*_fios_path)) {
328 FiosItem &fios = file_list.emplace_back();
329 fios.type = FIOS_TYPE_PARENT;
332 fios.title =
GetString(STR_SAVELOAD_PARENT_DIRECTORY,
".."sv);
333 sort_start = file_list.size();
337 std::error_code error_code;
338 for (
const auto &dir_entry : std::filesystem::directory_iterator(
OTTD2FS(*_fios_path), error_code)) {
339 if (!dir_entry.is_directory())
continue;
340 if (FiosIsHiddenFile(dir_entry) && dir_entry.path().filename() != PERSONAL_DIR)
continue;
342 FiosItem &fios = file_list.emplace_back();
343 fios.type = FIOS_TYPE_DIR;
345 fios.name =
FS2OTTD(dir_entry.path().filename().native());
346 fios.title =
GetString(STR_SAVELOAD_DIRECTORY, fios.name + PATHSEP);
350 SortingBits order = _savegame_sort_order;
351 _savegame_sort_order = SORT_BY_NAME | SORT_ASCENDING;
352 std::sort(file_list.begin() + sort_start, file_list.end());
353 _savegame_sort_order = order;
357 sort_start = file_list.size();
362 scanner.
Scan({}, *_fios_path,
false);
364 scanner.
Scan({}, subdir,
true,
true);
367 std::sort(file_list.begin() + sort_start, file_list.end());
370 FiosGetDrives(file_list);
382 std::string filename = fmt::format(
"{}.title", file);
384 if (!f.has_value())
return {};
387 size_t read = fread(title, 1,
lengthof(title), *f);
417 return { FIOS_TYPE_OLDFILE, GetOldSaveGameName(file) };
421 return { FIOS_TYPE_INVALID, {} };
433 static std::optional<std::string> fios_save_path;
435 if (!fios_save_path) fios_save_path = FioFindDirectory(
SAVE_DIR);
437 _fios_path = &(*fios_save_path);
464 return { FIOS_TYPE_OLD_SCENARIO, GetOldSaveGameName(file) };
468 return { FIOS_TYPE_INVALID, {} };
480 static std::optional<std::string> fios_scn_path;
483 if (!fios_scn_path) fios_scn_path = FioFindDirectory(
SCENARIO_DIR);
485 _fios_path = &(*fios_scn_path);
492std::tuple<FiosType, std::string> FiosGetHeightmapListCallback(
SaveLoadOperation, std::string_view file, std::string_view ext)
507 if (type == FIOS_TYPE_INVALID)
return { FIOS_TYPE_INVALID, {} };
509 TarFileList::iterator it = _tar_filelist[
SCENARIO_DIR].find(file);
520 if (buf.compare(0, buf.size(), it->second.tar_filename, 0, buf.size()) == 0) {
526 if (!match)
return { FIOS_TYPE_INVALID, {} };
540 static std::optional<std::string> fios_hmap_path;
542 if (!fios_hmap_path) fios_hmap_path = FioFindDirectory(
HEIGHTMAP_DIR);
544 _fios_path = &(*fios_hmap_path);
548 FiosGetFileList(fop, show_dirs, &FiosGetHeightmapListCallback, subdir, file_list);
565 return { FIOS_TYPE_INVALID, {} };
576 static std::optional<std::string> fios_town_data_path;
578 if (!fios_town_data_path) fios_town_data_path = FioFindDirectory(
HEIGHTMAP_DIR);
580 _fios_path = &(*fios_town_data_path);
593 static std::optional<std::string> fios_screenshot_path;
595 if (!fios_screenshot_path) fios_screenshot_path = FioFindDirectory(
SCREENSHOT_DIR);
597 return *fios_screenshot_path;
608 return this->scenid == other.
scenid && this->md5sum == other.
md5sum;
627 if (this->scanned && !rescan)
return;
630 this->scanned =
true;
633 bool AddFile(
const std::string &filename,
size_t,
const std::string &)
override
636 if (!f.has_value())
return false;
639 int fret = fscanf(*f,
"%u", &
id.scenid);
640 if (fret != 1)
return false;
641 id.filename = filename;
644 uint8_t buffer[1024];
651 if (!f.has_value())
return false;
654 while ((len = fread(buffer, 1, (size >
sizeof(buffer)) ?
sizeof(buffer) : size, *f)) != 0 && size != 0) {
656 checksum.Append(buffer, len);
658 checksum.Finish(
id.md5sum);
633 bool AddFile(
const std::string &filename,
size_t,
const std::string &)
override {
…}
679 if (md5sum ? (
id.md5sum == ci.
md5sum)
713 static std::optional<std::string> _autosave_path;
714 if (!_autosave_path) _autosave_path = FioFindDirectory(
AUTOSAVE_DIR);
716 static std::string _prefix;
719 static FiosGetTypeAndNameProc *
const proc = [](
SaveLoadOperation, std::string_view file, std::string_view ext) {
720 if (
StrEqualsIgnoreCase(ext,
".sav") && file.starts_with(_prefix))
return std::tuple(FIOS_TYPE_FILE, std::string{});
721 return std::tuple(FIOS_TYPE_INVALID, std::string{});
725 _prefix = *_autosave_path + this->prefix;
730 scanner.
Scan(
".sav", *_autosave_path,
false);
733 if (list.begin() != list.end()) {
734 SortingBits order = _savegame_sort_order;
735 _savegame_sort_order = SORT_BY_DATE | SORT_DESCENDING;
736 std::sort(list.begin(), list.end());
737 _savegame_sort_order = order;
739 std::string_view name = list.begin()->title;
740 std::from_chars(name.data() + this->prefix.size(), name.data() + name.size(), this->number);
751 return fmt::format(
"{}{}.sav", this->prefix, this->number);
760 return fmt::format(
"-{}.sav", this->prefix);
List of file information.
void BuildFileList(AbstractFileType abstract_filetype, SaveLoadOperation fop, bool show_dirs)
Construct a file list with the given kind of files, for the stated purpose.
const FiosItem * FindItem(std::string_view file)
Find file information of a file by its name from the file list.
Helper for scanning for files with a given name.
uint Scan(std::string_view extension, Subdirectory sd, bool tars=true, bool recursive=true)
Scan for files with the given extension in the given search path.
Scanner to scan for a particular type of FIOS file.
FiosFileScanner(SaveLoadOperation fop, FiosGetTypeAndNameProc *callback_proc, FileList &file_list)
Create the scanner.
SaveLoadOperation fop
The kind of file we are looking for.
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
Try to add a fios item set with the given filename.
FileList & file_list
Destination of the found files.
FiosGetTypeAndNameProc * callback_proc
Callback to check whether the file may be added.
Scanner to find the unique IDs of scenarios.
bool scanned
Whether we've already scanned.
void Scan(bool rescan)
Scan, but only if it's needed.
bool AddFile(const std::string &filename, size_t, const std::string &) override
Add a file with the given filename.
ScenarioScanner()
Initialise.
Parse data from a string / buffer.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
bool include(Container &container, typename Container::const_reference &item)
Helper function to append an item to a container if it is not already contained.
bool FioRemove(const std::string &filename)
Remove a file.
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Functions for Standard In/Out file operations.
SaveLoadOperation
Operation performed on the file.
@ SLO_SAVE
File is being saved.
@ SLO_LOAD
File is being loaded.
@ DFT_FIOS_DRIVE
A drive (letter) entry.
@ DFT_FIOS_DIR
A directory entry.
@ DFT_FIOS_PARENT
A parent directory entry.
@ DFT_INVALID
Unknown or invalid file.
@ DFT_FIOS_DIRECT
Direct filename.
Searchpath
Types of searchpaths OpenTTD might use.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ NO_DIRECTORY
A path without any base directory.
@ SCREENSHOT_DIR
Subdirectory for all screenshots.
@ SCENARIO_DIR
Base directory for all scenarios.
@ SAVE_DIR
Base directory for all savegames.
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
AbstractFileType
The different abstract types of files that the system knows about.
@ FT_SCENARIO
old or new scenario
@ FT_HEIGHTMAP
heightmap file
@ FT_SAVEGAME
old or new savegame
@ FT_TOWN_DATA
town data file
std::tuple< FiosType, std::string > FiosGetScenarioListCallback(SaveLoadOperation fop, std::string_view file, std::string_view ext)
Callback for FiosGetFileList.
static std::tuple< FiosType, std::string > FiosGetTownDataListCallback(SaveLoadOperation fop, std::string_view file, std::string_view ext)
Callback for FiosGetTownDataList.
std::optional< std::string_view > FindScenario(const ContentInfo &ci, bool md5sum)
Find a given scenario based on its unique ID.
std::string FiosMakeSavegameName(std::string_view name)
Make a save game or scenario filename from a name.
std::tuple< FiosType, std::string > FiosGetSavegameListCallback(SaveLoadOperation fop, std::string_view file, std::string_view ext)
Callback for FiosGetFileList.
static std::string GetFileTitle(std::string_view file, Subdirectory subdir)
Get the title of a file, which (if exists) is stored in a file named the same as the data file but wi...
std::string FiosGetCurrentPath()
Get the current path/working directory.
static std::string FiosMakeFilename(const std::string *path, std::string_view name, std::string_view ext)
Construct a filename from its components in destination buffer buf.
bool FiosDelete(std::string_view name)
Delete a file.
void FiosGetSavegameList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of savegames.
void FiosGetHeightmapList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of heightmaps.
static void FiosGetFileList(SaveLoadOperation fop, bool show_dirs, FiosGetTypeAndNameProc *callback_proc, Subdirectory subdir, FileList &file_list)
Fill the list of the files in a directory, according to some arbitrary rule.
void ScanScenarios()
Force a (re)scan of the scenarios.
std::string FiosMakeHeightmapName(std::string_view name)
Construct a filename for a height map.
bool HasScenario(const ContentInfo &ci, bool md5sum)
Check whether we've got a given scenario based on its unique ID.
void FiosGetScenarioList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of scenarios.
std::string_view FiosGetScreenshotDir()
Get the directory for screenshots.
bool FiosBrowseTo(const FiosItem *item)
Browse to a new path based on the passed item, starting at _fios_path.
static ScenarioScanner _scanner
Scanner for scenarios.
void FiosGetTownDataList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of town data files.
Declarations for savegames operations.
void FiosGetSavegameList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of savegames.
void FiosGetHeightmapList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of heightmaps.
void FiosGetScenarioList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of scenarios.
void FiosGetTownDataList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
Get a list of town data files.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
Part of the network protocol handling content distribution.
A number of safeguards to prevent using unsafe methods.
std::string_view GetCurrentScreenshotExtension()
Get filename extension of current screenshot file format.
Functions to make screenshots.
ClientSettings _settings_client
The current settings for this game.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
int StrNaturalCompare(std::string_view s1, std::string_view s2, bool ignore_garbage_at_front)
Compares two strings using case insensitive natural sort.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Functions related to low-level strings.
std::string GetString(StringID string)
Resolve the given StringID into a std::string with formatting but no parameters.
Functions related to OTTD's strings.
GUISettings gui
settings related to the GUI
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
MD5Hash md5sum
The MD5 checksum.
Deals with finding savegames.
bool operator<(const FiosItem &other) const
Compare two FiosItem's.
std::string Filename()
Generate a savegame name and number according to _settings_client.gui.max_num_autosaves.
FiosNumberedSaveName(const std::string &prefix)
Constructs FiosNumberedSaveName.
std::string Extension()
Generate an extension for a savegame name.
Elements of a file system that are recognized.
DetailedFileType detailed
Detailed file type.
uint8_t max_num_autosaves
controls how many autosavegames are made before the game starts to overwrite (names them 0 to max_num...
Basic data to distinguish a scenario.
uint32_t scenid
ID for the scenario (generated by content).
MD5Hash md5sum
MD5 checksum of file.
std::string filename
filename of the file.
Structs, typedefs and macros used for TAR file handling.
std::wstring OTTD2FS(std::string_view name)
Convert from OpenTTD's encoding to a wide string.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.