mirror of https://github.com/OpenTTD/OpenTTD
Merge branch 'OpenTTD:master' into maxMoneyFix
commit
80f46d141d
|
@ -0,0 +1,97 @@
|
|||
# Importing Town Data into OpenTTD
|
||||
|
||||
To aid players in scenario creation, OpenTTD's Scenario Editor can import town data from external JSON files. This enables players to use an image editing program to align town coordinates with a real-world heightmap using a map underlay, instead of guessing at the correct locations in Scenario Editor itself.
|
||||
|
||||
This town data consists of a JSON file storing an array of town data objects, each containing a name, location, target OpenTTD population, and whether it is marked as a city in the game.
|
||||
|
||||
This document describes the standard format for this JSON file and outlines a workflow for creating this data effectively.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- Why load external data?
|
||||
- How to use this feature
|
||||
- Creating geodata
|
||||
- Town data format standards
|
||||
- Town data values
|
||||
- Loading geodata into OpenTTD
|
||||
- Tutorial: Creating town data
|
||||
|
||||
## Why load external data?
|
||||
|
||||
There are three benefits to using an image editing program to create towns instead of the OpenTTD Scenario Editor.
|
||||
|
||||
1. Placing towns accurately is much easier using a map underlay such as OpenStreetMap to match town locations with the corresponding heightmap.
|
||||
2. Storing town data in a JSON file instead of as an OpenTTD Scenario (.scn) doesn't require choosing your NewGRF house set before placing towns.
|
||||
3. Town coordinates are scaled by the map size, so you can load the data onto whatever size map you like.
|
||||
|
||||
## How to use this feature
|
||||
|
||||
### Creating geodata
|
||||
|
||||
Town data is a text file in the JSON format, with a list of towns, each containing a coordinate location and properties: name, population, and whether or not it should be a city in OpenTTD.
|
||||
|
||||
The format of this file is standardized for importing into OpenTTD and must be followed for OpenTTD to properly parse the data.
|
||||
|
||||
For use in OpenTTD, you will also need a matching heightmap of the terrain features, as a PNG.
|
||||
|
||||
#### Town data format standards
|
||||
|
||||
The following code sample is complete and can be used in OpenTTD.
|
||||
- The list of towns is enclosed in an array marked with square brackets `[]`
|
||||
- Each town is enclosed in curly braces `{}`, with a comma after each town except for the last in the list.
|
||||
- The properties separated by commas except for the last.
|
||||
- Property names are enclosed in double quotes `""` with a colon `:` separating it from the property value.
|
||||
- The name property value is enclosed in double quotes `"London"`, while all other property values `44910`, `true`, etc., are not.
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"name": "London",
|
||||
"population": 44910,
|
||||
"city": true,
|
||||
"x": 0.7998046875,
|
||||
"y": 0.708984375
|
||||
},
|
||||
{
|
||||
"name": "Canterbury",
|
||||
"population": 217.16,
|
||||
"city": false,
|
||||
"x": 0.83251953125,
|
||||
"y": 0.828125
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Town data values
|
||||
|
||||
- Population is scaled down for use in OpenTTD. It is possible to generate huge cities by using a large number, but there is a practical limit to town size. The larger the town, the longer it will take to import town data, since towns are placed at a relatively small size and then expanded until the population is greater than the player-defined target.
|
||||
- X and Y coordinates are a proportion of the total map dimension, between 0 and 1. Just take the pixel coordinates of the town's location in the corresponding heightmap (more detail in the tutorial below) and divide each by the maximum value.
|
||||
- For example, London is at `726, 1638` in a 1024 px by 2048 px heightmap, so `726 / 1024 = 0.7998046875` and `1638 / 2048 = 0.708984375` gives the correct coordinates for OpenTTD.
|
||||
- The reason for these proportional coordinates is so the data can be used for any map size.
|
||||
- 0,0 is (approximately) the very top tile in OpenTTD. You can see tile coordinates in-game with the Land Info Tool.
|
||||
- In most image editing programs, 0,0 is the top-left corner of the image. You can rotate the image however you want relative to compass north to orient the map to your liking. Make sure you crop and resize the image before recording town locations.
|
||||
- In OpenTTD, X and Y axis are swapped compared to most image editing programs and the standard Cartesian coordinate system. From the 0,0 origin at top left, X is the axis along the left side and Y is the axis along the right side. You can still measure X and Y coordinates in your image editing program, just swap them before importing into OpenTTD or towns won't line up with your heightmap.
|
||||
|
||||
### Loading geodata into OpenTTD
|
||||
Using geodata to create a real-world location in OpenTTD is done in the Scenario Editor.
|
||||
|
||||
1. Choose the NewGRFs you want to use in the game.
|
||||
2. Load the heightmap which you created in the geodata workflow. Either rotation will work, but the clockwise rotation is considered "correct" and the coordinates in the Land Info Tool will match your data; counter clockwise maps will align properly but the coordinates won't match your data.
|
||||
3. In the Town Generation window, click `Load from file` and choose the .json file containing town data. The default directory to search for town data is `OpenTTD\scenario\heightmap`.
|
||||
4. (Optional) Manually add industries, rivers, trees, and objects.
|
||||
5. Save the game as a Scenario and exit to the main menu.
|
||||
6. Load the game with Play Scenario and enjoy.
|
||||
|
||||
Sometimes it's not possible to place a town, such as when the heightmap is very rough and a flat tile can't be found with a 16-tile radius of the target tile. In such cases, a sign will be placed on the target tile with the name of the town. The player can then place the town manually or change the heightmap settings and try again. This fallback also helps debug errors with data creation, such as if towns end up in the ocean.
|
||||
|
||||
## Tutorial: Creating town data
|
||||
|
||||
1. Load both your heightmap and a labeled map like OpenStreetMap as layers in an image editing program. You can use a free/open-source program like QGIS to acquire, align, and export these map images, if you like.
|
||||
2. Crop the image to your desired bounds, ensuring the aspect ratio is supported in OpenTTD (1:1, 1:2, 1:4, etc.).
|
||||
3. Resize the image to one of OpenTTD's supported map sizes, such as 512 px by 1024 px. Some image editors let you do this part of step 2. You can always load heightmaps and town data at a reduced size, so you may want to make this larger than your intended use in case you want it later.
|
||||
4. Use the labeled map layer to find the pixel coordinates of each town you'd like to include in your map. In GIMP this is displayed in the bottom left corner of the image window, and in Photoshop you need to enable the Info panel (F8) and switch to pixel units of measurement if not already.
|
||||
5. Some spreadsheets including Google Sheets can export data as JSON, so you may want to record it there, to export after step 8. Or you can build the JSON file manually.
|
||||
6. Adjust population numbers for OpenTTD.
|
||||
7. Change coordinates from pixels to proportion (0-1) of the total dimension: `x / maximum_x` and `y / maximum_y`, as described in "Town data values" above.
|
||||
8. Swap X and Y coordinates before importing to OpenTTD, since OpenTTD uses a reverse X and Y system than most image editors.
|
||||
9. Save the heightmap and town data files in your `OpenTTD\scenario\heightmap` folder.
|
|
@ -34,11 +34,11 @@ static const uint ICON_MAX_RECURSE = 10; ///< Maximum number of recursion
|
|||
return aliases;
|
||||
}
|
||||
|
||||
FILE *_iconsole_output_file;
|
||||
std::optional<FileHandle> _iconsole_output_file;
|
||||
|
||||
void IConsoleInit()
|
||||
{
|
||||
_iconsole_output_file = nullptr;
|
||||
_iconsole_output_file = std::nullopt;
|
||||
_redirect_console_to_client = INVALID_CLIENT_ID;
|
||||
_redirect_console_to_admin = INVALID_ADMIN_ID;
|
||||
|
||||
|
@ -49,13 +49,12 @@ void IConsoleInit()
|
|||
|
||||
static void IConsoleWriteToLogFile(const std::string &string)
|
||||
{
|
||||
if (_iconsole_output_file != nullptr) {
|
||||
if (_iconsole_output_file.has_value()) {
|
||||
/* if there is an console output file ... also print it there */
|
||||
try {
|
||||
fmt::print(_iconsole_output_file, "{}{}\n", GetLogPrefix(), string);
|
||||
fmt::print(*_iconsole_output_file, "{}{}\n", GetLogPrefix(), string);
|
||||
} catch (const std::system_error &) {
|
||||
fclose(_iconsole_output_file);
|
||||
_iconsole_output_file = nullptr;
|
||||
_iconsole_output_file.reset();
|
||||
IConsolePrint(CC_ERROR, "Cannot write to console log file; closing the log file.");
|
||||
}
|
||||
}
|
||||
|
@ -63,10 +62,9 @@ static void IConsoleWriteToLogFile(const std::string &string)
|
|||
|
||||
bool CloseConsoleLogIfActive()
|
||||
{
|
||||
if (_iconsole_output_file != nullptr) {
|
||||
if (_iconsole_output_file.has_value()) {
|
||||
IConsolePrint(CC_INFO, "Console log file closed.");
|
||||
fclose(_iconsole_output_file);
|
||||
_iconsole_output_file = nullptr;
|
||||
_iconsole_output_file.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1130,15 +1130,14 @@ DEF_CONSOLE_CMD(ConExec)
|
|||
|
||||
if (argc < 2) return false;
|
||||
|
||||
FILE *script_file = FioFOpenFile(argv[1], "r", BASE_DIR);
|
||||
auto script_file = FioFOpenFile(argv[1], "r", BASE_DIR);
|
||||
|
||||
if (script_file == nullptr) {
|
||||
if (!script_file.has_value()) {
|
||||
if (argc == 2 || atoi(argv[2]) != 0) IConsolePrint(CC_ERROR, "Script file '{}' not found.", argv[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_script_current_depth == 11) {
|
||||
FioFCloseFile(script_file);
|
||||
IConsolePrint(CC_ERROR, "Maximum 'exec' depth reached; script A is calling script B is calling script C ... more than 10 times.");
|
||||
return true;
|
||||
}
|
||||
|
@ -1147,7 +1146,7 @@ DEF_CONSOLE_CMD(ConExec)
|
|||
uint script_depth = _script_current_depth;
|
||||
|
||||
char cmdline[ICON_CMDLN_SIZE];
|
||||
while (fgets(cmdline, sizeof(cmdline), script_file) != nullptr) {
|
||||
while (fgets(cmdline, sizeof(cmdline), *script_file) != nullptr) {
|
||||
/* Remove newline characters from the executing script */
|
||||
for (char *cmdptr = cmdline; *cmdptr != '\0'; cmdptr++) {
|
||||
if (*cmdptr == '\n' || *cmdptr == '\r') {
|
||||
|
@ -1163,12 +1162,11 @@ DEF_CONSOLE_CMD(ConExec)
|
|||
if (_script_current_depth == script_depth - 1) break;
|
||||
}
|
||||
|
||||
if (ferror(script_file)) {
|
||||
if (ferror(*script_file) != 0) {
|
||||
IConsolePrint(CC_ERROR, "Encountered error while trying to read from script file '{}'.", argv[1]);
|
||||
}
|
||||
|
||||
if (_script_current_depth == script_depth) _script_current_depth--;
|
||||
FioFCloseFile(script_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1222,7 +1220,7 @@ extern void ShowFramerateWindow();
|
|||
|
||||
DEF_CONSOLE_CMD(ConScript)
|
||||
{
|
||||
extern FILE *_iconsole_output_file;
|
||||
extern std::optional<FileHandle> _iconsole_output_file;
|
||||
|
||||
if (argc == 0) {
|
||||
IConsolePrint(CC_HELP, "Start or stop logging console output to a file. Usage: 'script <filename>'.");
|
||||
|
@ -1233,8 +1231,8 @@ DEF_CONSOLE_CMD(ConScript)
|
|||
if (!CloseConsoleLogIfActive()) {
|
||||
if (argc < 2) return false;
|
||||
|
||||
_iconsole_output_file = fopen(argv[1], "ab");
|
||||
if (_iconsole_output_file == nullptr) {
|
||||
_iconsole_output_file = FileHandle::Open(argv[1], "ab");
|
||||
if (!_iconsole_output_file.has_value()) {
|
||||
IConsolePrint(CC_ERROR, "Could not open console log file '{}'.", argv[1]);
|
||||
} else {
|
||||
IConsolePrint(CC_INFO, "Console log output started to '{}'.", argv[1]);
|
||||
|
|
|
@ -190,15 +190,14 @@ bool CrashLog::WriteCrashLog()
|
|||
{
|
||||
this->crashlog_filename = this->CreateFileName(".json.log");
|
||||
|
||||
FILE *file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
|
||||
if (file == nullptr) return false;
|
||||
auto file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
|
||||
if (!file.has_value()) return false;
|
||||
|
||||
std::string survey_json = this->survey.dump(4);
|
||||
|
||||
size_t len = survey_json.size();
|
||||
size_t written = fwrite(survey_json.data(), 1, len, file);
|
||||
size_t written = fwrite(survey_json.data(), 1, len, *file);
|
||||
|
||||
FioFCloseFile(file);
|
||||
return len == written;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,18 +109,18 @@ void DumpDebugFacilityNames(std::back_insert_iterator<std::string> &output_itera
|
|||
void DebugPrint(const char *category, int level, const std::string &message)
|
||||
{
|
||||
if (strcmp(category, "desync") == 0 && level != 0) {
|
||||
static FILE *f = FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR);
|
||||
if (f == nullptr) return;
|
||||
static auto f = FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR);
|
||||
if (!f.has_value()) return;
|
||||
|
||||
fmt::print(f, "{}{}\n", GetLogPrefix(true), message);
|
||||
fflush(f);
|
||||
fmt::print(*f, "{}{}\n", GetLogPrefix(true), message);
|
||||
fflush(*f);
|
||||
#ifdef RANDOM_DEBUG
|
||||
} else if (strcmp(category, "random") == 0) {
|
||||
static FILE *f = FioFOpenFile("random-out.log", "wb", AUTOSAVE_DIR);
|
||||
if (f == nullptr) return;
|
||||
static auto f = FioFOpenFile("random-out.log", "wb", AUTOSAVE_DIR);
|
||||
if (!f.has_value()) return;
|
||||
|
||||
fmt::print(f, "{}\n", message);
|
||||
fflush(f);
|
||||
fmt::print(*f, "{}\n", message);
|
||||
fflush(*f);
|
||||
#endif
|
||||
} else {
|
||||
fmt::print(stderr, "{}dbg: [{}:{}] {}\n", GetLogPrefix(true), category, level, message);
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
#include "fileio_func.h"
|
||||
#include "debug.h"
|
||||
|
||||
std::string _log_file; ///< File to reroute output of a forked OpenTTD to
|
||||
std::unique_ptr<FILE, FileDeleter> _log_fd; ///< File to reroute output of a forked OpenTTD to
|
||||
std::string _log_file; ///< Filename to reroute output of a forked OpenTTD to
|
||||
std::optional<FileHandle> _log_fd; ///< File to reroute output of a forked OpenTTD to
|
||||
|
||||
#if defined(UNIX)
|
||||
|
||||
|
@ -31,17 +31,17 @@ void DedicatedFork()
|
|||
|
||||
case 0: { // We're the child
|
||||
/* Open the log-file to log all stuff too */
|
||||
_log_fd.reset(fopen(_log_file.c_str(), "a"));
|
||||
if (!_log_fd) {
|
||||
_log_fd = FileHandle::Open(_log_file, "a");
|
||||
if (!_log_fd.has_value()) {
|
||||
perror("Unable to open logfile");
|
||||
exit(1);
|
||||
}
|
||||
/* Redirect stdout and stderr to log-file */
|
||||
if (dup2(fileno(_log_fd.get()), fileno(stdout)) == -1) {
|
||||
if (dup2(fileno(*_log_fd), fileno(stdout)) == -1) {
|
||||
perror("Rerouting stdout");
|
||||
exit(1);
|
||||
}
|
||||
if (dup2(fileno(_log_fd.get()), fileno(stderr)) == -1) {
|
||||
if (dup2(fileno(*_log_fd), fileno(stderr)) == -1) {
|
||||
perror("Rerouting stderr");
|
||||
exit(1);
|
||||
}
|
||||
|
|
|
@ -134,8 +134,7 @@ bool DriverFactoryBase::SelectDriverImpl(const std::string &name, Driver::Type t
|
|||
}
|
||||
|
||||
/* Write empty file to note we are attempting hardware acceleration. */
|
||||
auto f = FioFOpenFile(HWACCELERATION_TEST_FILE, "w", BASE_DIR);
|
||||
FioFCloseFile(f);
|
||||
FioFOpenFile(HWACCELERATION_TEST_FILE, "w", BASE_DIR);
|
||||
}
|
||||
|
||||
Driver *oldd = *GetActiveDriver(type);
|
||||
|
|
112
src/fileio.cpp
112
src/fileio.cpp
|
@ -120,11 +120,8 @@ static void FillValidSearchPaths(bool only_local_path)
|
|||
*/
|
||||
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
|
||||
{
|
||||
FILE *f = FioFOpenFile(filename, "rb", subdir);
|
||||
if (f == nullptr) return false;
|
||||
|
||||
FioFCloseFile(f);
|
||||
return true;
|
||||
auto f = FioFOpenFile(filename, "rb", subdir);
|
||||
return f.has_value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,14 +135,6 @@ bool FileExists(const std::string &filename)
|
|||
return std::filesystem::exists(OTTD2FS(filename), ec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a file in a safe way.
|
||||
*/
|
||||
void FioFCloseFile(FILE *f)
|
||||
{
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a path to the filename in one of the search directories.
|
||||
* @param subdir Subdirectory to try.
|
||||
|
@ -191,7 +180,7 @@ std::string FioFindDirectory(Subdirectory subdir)
|
|||
return _personal_dir;
|
||||
}
|
||||
|
||||
static FILE *FioFOpenFileSp(const std::string &filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
|
||||
static std::optional<FileHandle> FioFOpenFileSp(const std::string &filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* fopen is implemented as a define with ellipses for
|
||||
|
@ -201,7 +190,6 @@ static FILE *FioFOpenFileSp(const std::string &filename, const char *mode, Searc
|
|||
wchar_t Lmode[5];
|
||||
MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, static_cast<int>(std::size(Lmode)));
|
||||
#endif
|
||||
FILE *f = nullptr;
|
||||
std::string buf;
|
||||
|
||||
if (subdir == NO_DIRECTORY) {
|
||||
|
@ -210,17 +198,17 @@ static FILE *FioFOpenFileSp(const std::string &filename, const char *mode, Searc
|
|||
buf = _searchpaths[sp] + _subdirs[subdir] + filename;
|
||||
}
|
||||
|
||||
f = fopen(buf.c_str(), mode);
|
||||
auto f = FileHandle::Open(buf, mode);
|
||||
#if !defined(_WIN32)
|
||||
if (f == nullptr && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
|
||||
f = fopen(buf.c_str(), mode);
|
||||
if (!f.has_value() && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
|
||||
f = FileHandle::Open(buf, mode);
|
||||
}
|
||||
#endif
|
||||
if (f != nullptr && filesize != nullptr) {
|
||||
if (f.has_value() && filesize != nullptr) {
|
||||
/* Find the size of the file */
|
||||
fseek(f, 0, SEEK_END);
|
||||
*filesize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fseek(*f, 0, SEEK_END);
|
||||
*filesize = ftell(*f);
|
||||
fseek(*f, 0, SEEK_SET);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
@ -232,14 +220,13 @@ static FILE *FioFOpenFileSp(const std::string &filename, const char *mode, Searc
|
|||
* @return File handle of the opened file, or \c nullptr if the file is not available.
|
||||
* @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
|
||||
*/
|
||||
FILE *FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
|
||||
static std::optional<FileHandle> FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
|
||||
{
|
||||
FILE *f = fopen(entry.tar_filename.c_str(), "rb");
|
||||
if (f == nullptr) return f;
|
||||
auto f = FileHandle::Open(entry.tar_filename, "rb");
|
||||
if (!f.has_value()) return std::nullopt;
|
||||
|
||||
if (fseek(f, entry.position, SEEK_SET) < 0) {
|
||||
fclose(f);
|
||||
return nullptr;
|
||||
if (fseek(*f, entry.position, SEEK_SET) < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (filesize != nullptr) *filesize = entry.size;
|
||||
|
@ -252,19 +239,18 @@ FILE *FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
|
|||
* @param subdir Subdirectory to open.
|
||||
* @return File handle of the opened file, or \c nullptr if the file is not available.
|
||||
*/
|
||||
FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
|
||||
std::optional<FileHandle> FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
|
||||
{
|
||||
FILE *f = nullptr;
|
||||
|
||||
std::optional<FileHandle> f = std::nullopt;
|
||||
assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
|
||||
|
||||
for (Searchpath sp : _valid_searchpaths) {
|
||||
f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
|
||||
if (f != nullptr || subdir == NO_DIRECTORY) break;
|
||||
if (f.has_value() || subdir == NO_DIRECTORY) break;
|
||||
}
|
||||
|
||||
/* We can only use .tar in case of data-dir, and read-mode */
|
||||
if (f == nullptr && mode[0] == 'r' && subdir != NO_DIRECTORY) {
|
||||
if (!f.has_value() && mode[0] == 'r' && subdir != NO_DIRECTORY) {
|
||||
/* Filenames in tars are always forced to be lowercase */
|
||||
std::string resolved_name = filename;
|
||||
strtolower(resolved_name);
|
||||
|
@ -275,7 +261,7 @@ FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory s
|
|||
std::string token;
|
||||
while (std::getline(ss, token, PATHSEPCHAR)) {
|
||||
if (token == "..") {
|
||||
if (tokens.size() < 2) return nullptr;
|
||||
if (tokens.size() < 2) return std::nullopt;
|
||||
tokens.pop_back();
|
||||
} else if (token == ".") {
|
||||
/* Do nothing. "." means current folder, but you can create tar files with "." in the path.
|
||||
|
@ -303,11 +289,11 @@ FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory s
|
|||
|
||||
/* Sometimes a full path is given. To support
|
||||
* the 'subdirectory' must be 'removed'. */
|
||||
if (f == nullptr && subdir != NO_DIRECTORY) {
|
||||
if (!f.has_value() && subdir != NO_DIRECTORY) {
|
||||
switch (subdir) {
|
||||
case BASESET_DIR:
|
||||
f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
|
||||
if (f != nullptr) break;
|
||||
if (f.has_value()) break;
|
||||
[[fallthrough]];
|
||||
case NEWGRF_DIR:
|
||||
f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
|
||||
|
@ -480,12 +466,13 @@ bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] c
|
|||
TarList::iterator it = _tar_list[this->subdir].find(filename);
|
||||
if (it != _tar_list[this->subdir].end()) return false;
|
||||
|
||||
FILE *f = fopen(filename.c_str(), "rb");
|
||||
auto of = FileHandle::Open(filename, "rb");
|
||||
/* Although the file has been found there can be
|
||||
* a number of reasons we cannot open the file.
|
||||
* Most common case is when we simply have not
|
||||
* been given read access. */
|
||||
if (f == nullptr) return false;
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
_tar_list[this->subdir][filename] = std::string{};
|
||||
|
||||
|
@ -510,7 +497,6 @@ bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] c
|
|||
if (memcmp(&th, &empty[0], 512) == 0) continue;
|
||||
|
||||
Debug(misc, 0, "The file '{}' isn't a valid tar-file", filename);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -584,14 +570,12 @@ bool TarScanner::AddFile(const std::string &filename, size_t, [[maybe_unused]] c
|
|||
skip = Align(skip, 512);
|
||||
if (fseek(f, skip, SEEK_CUR) < 0) {
|
||||
Debug(misc, 0, "The file '{}' can't be read as a valid tar-file", filename);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
pos += skip;
|
||||
}
|
||||
|
||||
Debug(misc, 4, "Found tar '{}' with {} new files", filename, num);
|
||||
fclose(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -638,15 +622,15 @@ bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
|
|||
|
||||
/* First open the file in the .tar. */
|
||||
size_t to_copy = 0;
|
||||
std::unique_ptr<FILE, FileDeleter> in(FioFOpenFileTar(it2.second, &to_copy));
|
||||
if (!in) {
|
||||
auto in = FioFOpenFileTar(it2.second, &to_copy);
|
||||
if (!in.has_value()) {
|
||||
Debug(misc, 6, "Extracting {} failed; could not open {}", filename, tar_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Now open the 'output' file. */
|
||||
std::unique_ptr<FILE, FileDeleter> out(fopen(filename.c_str(), "wb"));
|
||||
if (!out) {
|
||||
auto out = FileHandle::Open(filename, "wb");
|
||||
if (!out.has_value()) {
|
||||
Debug(misc, 6, "Extracting {} failed; could not open {}", filename, filename);
|
||||
return false;
|
||||
}
|
||||
|
@ -655,8 +639,8 @@ bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
|
|||
char buffer[4096];
|
||||
size_t read;
|
||||
for (; to_copy != 0; to_copy -= read) {
|
||||
read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), in.get());
|
||||
if (read <= 0 || fwrite(buffer, 1, read, out.get()) != read) break;
|
||||
read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), *in);
|
||||
if (read <= 0 || fwrite(buffer, 1, read, *out) != read) break;
|
||||
}
|
||||
|
||||
if (to_copy != 0) {
|
||||
|
@ -1040,20 +1024,18 @@ void SanitizeFilename(std::string &filename)
|
|||
*/
|
||||
std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
|
||||
{
|
||||
FILE *in = fopen(filename.c_str(), "rb");
|
||||
if (in == nullptr) return nullptr;
|
||||
auto in = FileHandle::Open(filename, "rb");
|
||||
if (!in.has_value()) return nullptr;
|
||||
|
||||
FileCloser fc(in);
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
size_t len = ftell(in);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
fseek(*in, 0, SEEK_END);
|
||||
size_t len = ftell(*in);
|
||||
fseek(*in, 0, SEEK_SET);
|
||||
if (len > maxsize) return nullptr;
|
||||
|
||||
std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
|
||||
|
||||
mem.get()[len] = 0;
|
||||
if (fread(mem.get(), len, 1, in) != 1) return nullptr;
|
||||
if (fread(mem.get(), len, 1, *in) != 1) return nullptr;
|
||||
|
||||
lenp = len;
|
||||
return mem;
|
||||
|
@ -1177,3 +1159,23 @@ uint FileScanner::Scan(const std::string_view extension, const std::string &dire
|
|||
AppendPathSeparator(path);
|
||||
return ScanPath(this, extension, OTTD2FS(path), path.size(), recursive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an RAII file handle if possible.
|
||||
* The canonical RAII-way is for FileHandle to open the file and throw an exception on failure, but we don't want that.
|
||||
* @param filename UTF-8 encoded filename to open.
|
||||
* @param mode Mode to open file.
|
||||
* @return FileHandle, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<FileHandle> FileHandle::Open(const std::string &filename, const std::string &mode)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* Windows also requires mode to be wchar_t. */
|
||||
auto f = _wfopen(OTTD2FS(filename).c_str(), OTTD2FS(mode).c_str());
|
||||
#else
|
||||
auto f = fopen(filename.c_str(), mode.c_str());
|
||||
#endif /* _WIN32 */
|
||||
|
||||
if (f == nullptr) return std::nullopt;
|
||||
return FileHandle(f);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
#include "core/enum_type.hpp"
|
||||
#include "fileio_type.h"
|
||||
|
||||
void FioFCloseFile(FILE *f);
|
||||
FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize = nullptr);
|
||||
std::optional<FileHandle> FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize = nullptr);
|
||||
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir);
|
||||
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename);
|
||||
std::string FioGetDirectory(Searchpath sp, Subdirectory subdir);
|
||||
|
@ -81,24 +80,4 @@ public:
|
|||
|
||||
DECLARE_ENUM_AS_BIT_SET(TarScanner::Mode)
|
||||
|
||||
/** Auto-close a file upon scope exit. */
|
||||
class FileCloser {
|
||||
FILE *f;
|
||||
|
||||
public:
|
||||
FileCloser(FILE *_f) : f(_f) {}
|
||||
~FileCloser()
|
||||
{
|
||||
fclose(f);
|
||||
}
|
||||
};
|
||||
|
||||
/** Helper to manage a FILE with a \c std::unique_ptr. */
|
||||
struct FileDeleter {
|
||||
void operator()(FILE *f)
|
||||
{
|
||||
if (f) fclose(f);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* FILEIO_FUNC_H */
|
||||
|
|
|
@ -18,6 +18,7 @@ enum AbstractFileType {
|
|||
FT_SAVEGAME, ///< old or new savegame
|
||||
FT_SCENARIO, ///< old or new scenario
|
||||
FT_HEIGHTMAP, ///< heightmap file
|
||||
FT_TOWN_DATA, ///< town data file
|
||||
|
||||
FT_INVALID = 7, ///< Invalid or unknown file type.
|
||||
FT_NUMBITS = 3, ///< Number of bits required for storing a #AbstractFileType value.
|
||||
|
@ -34,6 +35,9 @@ enum DetailedFileType {
|
|||
DFT_HEIGHTMAP_BMP, ///< BMP file.
|
||||
DFT_HEIGHTMAP_PNG, ///< PNG file.
|
||||
|
||||
/* Town data files. */
|
||||
DFT_TOWN_DATA_JSON, ///< JSON file.
|
||||
|
||||
/* fios 'files' */
|
||||
DFT_FIOS_DRIVE, ///< A drive (letter) entry.
|
||||
DFT_FIOS_PARENT, ///< A parent directory entry.
|
||||
|
@ -78,6 +82,7 @@ enum FiosType {
|
|||
FIOS_TYPE_OLD_SCENARIO = MAKE_FIOS_TYPE(FT_SCENARIO, DFT_OLD_GAME_FILE),
|
||||
FIOS_TYPE_PNG = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_PNG),
|
||||
FIOS_TYPE_BMP = MAKE_FIOS_TYPE(FT_HEIGHTMAP, DFT_HEIGHTMAP_BMP),
|
||||
FIOS_TYPE_JSON = MAKE_FIOS_TYPE(FT_TOWN_DATA, DFT_TOWN_DATA_JSON),
|
||||
|
||||
FIOS_TYPE_INVALID = MAKE_FIOS_TYPE(FT_INVALID, DFT_INVALID),
|
||||
};
|
||||
|
@ -150,4 +155,33 @@ enum Searchpath : unsigned {
|
|||
|
||||
DECLARE_POSTFIX_INCREMENT(Searchpath)
|
||||
|
||||
class FileHandle {
|
||||
public:
|
||||
static std::optional<FileHandle> Open(const std::string &filename, const std::string &mode);
|
||||
|
||||
inline void Close() { this->f.reset(); }
|
||||
|
||||
inline operator FILE *()
|
||||
{
|
||||
assert(this->f != nullptr);
|
||||
return this->f.get();
|
||||
}
|
||||
|
||||
private:
|
||||
/** Helper to close a FILE * with a \c std::unique_ptr. */
|
||||
struct FileDeleter {
|
||||
void operator ()(FILE *f)
|
||||
{
|
||||
if (f != nullptr) fclose(f);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<FILE, FileDeleter> f;
|
||||
|
||||
FileHandle(FILE *f) : f(f) { assert(this->f != nullptr); }
|
||||
};
|
||||
|
||||
/* Ensure has_value() is used consistently. */
|
||||
template <> constexpr std::optional<FileHandle>::operator bool() const noexcept = delete;
|
||||
|
||||
#endif /* FILEIO_TYPE_H */
|
||||
|
|
61
src/fios.cpp
61
src/fios.cpp
|
@ -84,6 +84,10 @@ void FileList::BuildFileList(AbstractFileType abstract_filetype, SaveLoadOperati
|
|||
FiosGetHeightmapList(fop, show_dirs, *this);
|
||||
break;
|
||||
|
||||
case FT_TOWN_DATA:
|
||||
FiosGetTownDataList(fop, show_dirs, *this);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
@ -180,6 +184,7 @@ bool FiosBrowseTo(const FiosItem *item)
|
|||
case FIOS_TYPE_OLD_SCENARIO:
|
||||
case FIOS_TYPE_PNG:
|
||||
case FIOS_TYPE_BMP:
|
||||
case FIOS_TYPE_JSON:
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -385,12 +390,11 @@ static void FiosGetFileList(SaveLoadOperation fop, bool show_dirs, FiosGetTypeAn
|
|||
*/
|
||||
static std::string GetFileTitle(const std::string &file, Subdirectory subdir)
|
||||
{
|
||||
FILE *f = FioFOpenFile(file + ".title", "r", subdir);
|
||||
if (f == nullptr) return {};
|
||||
auto f = FioFOpenFile(file + ".title", "r", subdir);
|
||||
if (!f.has_value()) return {};
|
||||
|
||||
char title[80];
|
||||
size_t read = fread(title, 1, lengthof(title), f);
|
||||
FioFCloseFile(f);
|
||||
size_t read = fread(title, 1, lengthof(title), *f);
|
||||
|
||||
assert(read <= lengthof(title));
|
||||
return StrMakeValid({title, read});
|
||||
|
@ -554,6 +558,42 @@ void FiosGetHeightmapList(SaveLoadOperation fop, bool show_dirs, FileList &file_
|
|||
FiosGetFileList(fop, show_dirs, &FiosGetHeightmapListCallback, subdir, file_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for FiosGetTownDataList.
|
||||
* @param fop Purpose of collecting the list.
|
||||
* @param file Name of the file to check.
|
||||
* @return a FIOS_TYPE_JSON type of the found file, FIOS_TYPE_INVALID if not a valid JSON file, and the title of the file (if any).
|
||||
*/
|
||||
static std::tuple<FiosType, std::string> FiosGetTownDataListCallback(SaveLoadOperation fop, const std::string &file, const std::string_view ext)
|
||||
{
|
||||
if (fop == SLO_LOAD) {
|
||||
if (StrEqualsIgnoreCase(ext, ".json")) {
|
||||
return { FIOS_TYPE_JSON, GetFileTitle(file, SAVE_DIR) };
|
||||
}
|
||||
}
|
||||
|
||||
return { FIOS_TYPE_INVALID, {} };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of town data files.
|
||||
* @param fop Purpose of collecting the list.
|
||||
* @param show_dirs Whether to show directories.
|
||||
* @param file_list Destination of the found files.
|
||||
*/
|
||||
void FiosGetTownDataList(SaveLoadOperation fop, bool show_dirs, FileList &file_list)
|
||||
{
|
||||
static std::optional<std::string> fios_town_data_path;
|
||||
|
||||
if (!fios_town_data_path) fios_town_data_path = FioFindDirectory(HEIGHTMAP_DIR);
|
||||
|
||||
_fios_path = &(*fios_town_data_path);
|
||||
|
||||
std::string base_path = FioFindDirectory(HEIGHTMAP_DIR);
|
||||
Subdirectory subdir = base_path == *_fios_path ? HEIGHTMAP_DIR : NO_DIRECTORY;
|
||||
FiosGetFileList(fop, show_dirs, &FiosGetTownDataListCallback, subdir, file_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directory for screenshots.
|
||||
* @return path to screenshots
|
||||
|
@ -607,12 +647,11 @@ public:
|
|||
|
||||
bool AddFile(const std::string &filename, size_t, const std::string &) override
|
||||
{
|
||||
FILE *f = FioFOpenFile(filename, "r", SCENARIO_DIR);
|
||||
if (f == nullptr) return false;
|
||||
auto f = FioFOpenFile(filename, "r", SCENARIO_DIR);
|
||||
if (!f.has_value()) return false;
|
||||
|
||||
ScenarioIdentifier id;
|
||||
int fret = fscanf(f, "%u", &id.scenid);
|
||||
FioFCloseFile(f);
|
||||
int fret = fscanf(*f, "%u", &id.scenid);
|
||||
if (fret != 1) return false;
|
||||
id.filename = filename;
|
||||
|
||||
|
@ -624,17 +663,15 @@ public:
|
|||
* This is safe as we check on extension which
|
||||
* must always exist. */
|
||||
f = FioFOpenFile(filename.substr(0, filename.rfind('.')), "rb", SCENARIO_DIR, &size);
|
||||
if (f == nullptr) return false;
|
||||
if (!f.has_value()) return false;
|
||||
|
||||
/* calculate md5sum */
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
|
||||
size -= len;
|
||||
checksum.Append(buffer, len);
|
||||
}
|
||||
checksum.Finish(id.md5sum);
|
||||
|
||||
FioFCloseFile(f);
|
||||
|
||||
include(*this, id);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fo
|
|||
void FiosGetSavegameList(SaveLoadOperation fop, bool show_dirs, FileList &file_list);
|
||||
void FiosGetScenarioList(SaveLoadOperation fop, bool show_dirs, FileList &file_list);
|
||||
void FiosGetHeightmapList(SaveLoadOperation fop, bool show_dirs, FileList &file_list);
|
||||
void FiosGetTownDataList(SaveLoadOperation fop, bool show_dirs, FileList &file_list);
|
||||
|
||||
bool FiosBrowseTo(const FiosItem *item);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "querystring_gui.h"
|
||||
#include "engine_func.h"
|
||||
#include "landscape_type.h"
|
||||
#include "genworld.h"
|
||||
#include "timer/timer_game_calendar.h"
|
||||
#include "core/geometry_func.hpp"
|
||||
#include "gamelog.h"
|
||||
|
@ -172,6 +173,48 @@ static constexpr NWidgetPart _nested_load_heightmap_dialog_widgets[] = {
|
|||
EndContainer(),
|
||||
};
|
||||
|
||||
/** Load town data */
|
||||
static constexpr NWidgetPart _nested_load_town_data_dialog_widgets[] = {
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_CLOSEBOX, COLOUR_GREY),
|
||||
NWidget(WWT_CAPTION, COLOUR_GREY, WID_SL_CAPTION),
|
||||
NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
|
||||
EndContainer(),
|
||||
/* Current directory and free space */
|
||||
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_BACKGROUND), SetFill(1, 0), SetResize(1, 0), EndContainer(),
|
||||
|
||||
/* Filter box with label */
|
||||
NWidget(WWT_PANEL, COLOUR_GREY), SetFill(1, 1), SetResize(1, 1),
|
||||
NWidget(NWID_HORIZONTAL), SetPadding(WidgetDimensions::unscaled.framerect.top, 0, WidgetDimensions::unscaled.framerect.bottom, 0),
|
||||
SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.frametext.right, 0),
|
||||
NWidget(WWT_TEXT, COLOUR_GREY), SetFill(0, 1), SetDataTip(STR_SAVELOAD_FILTER_TITLE , STR_NULL),
|
||||
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_SL_FILTER), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
/* Sort Buttons */
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYNAME), SetDataTip(STR_SORT_BY_CAPTION_NAME, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_SORT_BYDATE), SetDataTip(STR_SORT_BY_CAPTION_DATE, STR_TOOLTIP_SORT_ORDER), SetFill(1, 0), SetResize(1, 0),
|
||||
EndContainer(),
|
||||
NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SL_HOME_BUTTON), SetAspect(1), SetDataTip(SPR_HOUSE_ICON, STR_SAVELOAD_HOME_BUTTON),
|
||||
EndContainer(),
|
||||
/* Files */
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_PANEL, COLOUR_GREY, WID_SL_FILE_BACKGROUND),
|
||||
NWidget(WWT_INSET, COLOUR_GREY, WID_SL_DRIVES_DIRECTORIES_LIST), SetFill(1, 1), SetPadding(2, 2, 2, 2),
|
||||
SetDataTip(0x0, STR_SAVELOAD_LIST_TOOLTIP), SetResize(1, 10), SetScrollbar(WID_SL_SCROLLBAR), EndContainer(),
|
||||
EndContainer(),
|
||||
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SL_SCROLLBAR),
|
||||
EndContainer(),
|
||||
/* Load button */
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SL_LOAD_BUTTON), SetResize(1, 0), SetFill(1, 0),
|
||||
SetDataTip(STR_SAVELOAD_LOAD_BUTTON, STR_SAVELOAD_LOAD_TOWN_DATA_TOOLTIP),
|
||||
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
|
||||
EndContainer(),
|
||||
};
|
||||
|
||||
/** Save game/scenario */
|
||||
static constexpr NWidgetPart _nested_save_dialog_widgets[] = {
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
|
@ -242,6 +285,7 @@ static const TextColour _fios_colours[] = {
|
|||
TC_ORANGE, // DFT_GAME_FILE
|
||||
TC_YELLOW, // DFT_HEIGHTMAP_BMP
|
||||
TC_ORANGE, // DFT_HEIGHTMAP_PNG
|
||||
TC_LIGHT_BROWN, // DFT_TOWN_DATA_JSON
|
||||
TC_LIGHT_BLUE, // DFT_FIOS_DRIVE
|
||||
TC_DARK_GREEN, // DFT_FIOS_PARENT
|
||||
TC_DARK_GREEN, // DFT_FIOS_DIR
|
||||
|
@ -330,6 +374,7 @@ public:
|
|||
break;
|
||||
|
||||
default:
|
||||
/* It's not currently possible to save town data. */
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
@ -356,6 +401,10 @@ public:
|
|||
caption_string = (this->fop == SLO_SAVE) ? STR_SAVELOAD_SAVE_HEIGHTMAP : STR_SAVELOAD_LOAD_HEIGHTMAP;
|
||||
break;
|
||||
|
||||
case FT_TOWN_DATA:
|
||||
caption_string = STR_SAVELOAD_LOAD_TOWN_DATA; // It's not currently possible to save town data.
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
@ -391,6 +440,7 @@ public:
|
|||
break;
|
||||
|
||||
case FT_HEIGHTMAP:
|
||||
case FT_TOWN_DATA:
|
||||
o_dir.name = FioFindDirectory(HEIGHTMAP_DIR);
|
||||
break;
|
||||
|
||||
|
@ -634,6 +684,9 @@ public:
|
|||
if (this->abstract_filetype == FT_HEIGHTMAP) {
|
||||
this->Close();
|
||||
ShowHeightmapLoad();
|
||||
} else if (this->abstract_filetype == FT_TOWN_DATA) {
|
||||
this->Close();
|
||||
LoadTownData();
|
||||
} else if (!_load_check_data.HasNewGrfs() || _load_check_data.grf_compatibility != GLC_NOT_FOUND || _settings_client.gui.UserIsAllowedToChangeNewGRFs()) {
|
||||
_switch_mode = (_game_mode == GM_EDITOR) ? SM_LOAD_SCENARIO : SM_LOAD_GAME;
|
||||
ClearErrorMessages();
|
||||
|
@ -689,7 +742,7 @@ public:
|
|||
} else if (!_load_check_data.HasErrors()) {
|
||||
this->selected = file;
|
||||
if (this->fop == SLO_LOAD) {
|
||||
if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO) {
|
||||
if (this->abstract_filetype == FT_SAVEGAME || this->abstract_filetype == FT_SCENARIO || this->abstract_filetype == FT_TOWN_DATA) {
|
||||
this->OnClick(pt, WID_SL_LOAD_BUTTON, 1);
|
||||
} else {
|
||||
assert(this->abstract_filetype == FT_HEIGHTMAP);
|
||||
|
@ -856,6 +909,7 @@ public:
|
|||
|
||||
switch (this->abstract_filetype) {
|
||||
case FT_HEIGHTMAP:
|
||||
case FT_TOWN_DATA:
|
||||
this->SetWidgetDisabledState(WID_SL_LOAD_BUTTON, this->selected == nullptr || _load_check_data.HasErrors());
|
||||
break;
|
||||
|
||||
|
@ -908,6 +962,14 @@ static WindowDesc _load_heightmap_dialog_desc(
|
|||
_nested_load_heightmap_dialog_widgets
|
||||
);
|
||||
|
||||
/** Load town data */
|
||||
static WindowDesc _load_town_data_dialog_desc(
|
||||
WDP_CENTER, "load_town_data", 257, 320,
|
||||
WC_SAVELOAD, WC_NONE,
|
||||
0,
|
||||
_nested_load_town_data_dialog_widgets
|
||||
);
|
||||
|
||||
/** Save game/scenario */
|
||||
static WindowDesc _save_dialog_desc(
|
||||
WDP_CENTER, "save_game", 500, 294,
|
||||
|
@ -929,6 +991,17 @@ void ShowSaveLoadDialog(AbstractFileType abstract_filetype, SaveLoadOperation fo
|
|||
new SaveLoadWindow(_save_dialog_desc, abstract_filetype, fop);
|
||||
} else {
|
||||
/* Dialogue for loading a file. */
|
||||
new SaveLoadWindow((abstract_filetype == FT_HEIGHTMAP) ? _load_heightmap_dialog_desc : _load_dialog_desc, abstract_filetype, fop);
|
||||
switch (abstract_filetype) {
|
||||
case FT_HEIGHTMAP:
|
||||
new SaveLoadWindow(_load_heightmap_dialog_desc, abstract_filetype, fop);
|
||||
break;
|
||||
|
||||
case FT_TOWN_DATA:
|
||||
new SaveLoadWindow(_load_town_data_dialog_desc, abstract_filetype, fop);
|
||||
break;
|
||||
|
||||
default:
|
||||
new SaveLoadWindow(_load_dialog_desc, abstract_filetype, fop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,8 @@ void CDECL StrgenFatalI(const std::string &msg)
|
|||
LanguageStrings ReadRawLanguageStrings(const std::string &file)
|
||||
{
|
||||
size_t to_read;
|
||||
FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
|
||||
if (fh == nullptr) return LanguageStrings();
|
||||
|
||||
FileCloser fhClose(fh);
|
||||
auto fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
|
||||
if (!fh.has_value()) return LanguageStrings();
|
||||
|
||||
auto pos = file.rfind(PATHSEPCHAR);
|
||||
if (pos == std::string::npos) return LanguageStrings();
|
||||
|
@ -64,7 +62,7 @@ LanguageStrings ReadRawLanguageStrings(const std::string &file)
|
|||
LanguageStrings ret(langname.substr(0, langname.find('.')));
|
||||
|
||||
char buffer[2048];
|
||||
while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != nullptr) {
|
||||
while (to_read != 0 && fgets(buffer, sizeof(buffer), *fh) != nullptr) {
|
||||
size_t len = strlen(buffer);
|
||||
|
||||
/* Remove trailing spaces/newlines from the string. */
|
||||
|
|
179
src/genworld.cpp
179
src/genworld.cpp
|
@ -10,6 +10,10 @@
|
|||
#include "stdafx.h"
|
||||
#include "landscape.h"
|
||||
#include "company_func.h"
|
||||
#include "town_cmd.h"
|
||||
#include "signs_cmd.h"
|
||||
#include "3rdparty/nlohmann/json.hpp"
|
||||
#include "strings_func.h"
|
||||
#include "genworld.h"
|
||||
#include "gfxinit.h"
|
||||
#include "window_func.h"
|
||||
|
@ -337,3 +341,178 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
|
|||
|
||||
_GenerateWorld();
|
||||
}
|
||||
|
||||
/** Town data imported from JSON files and used to place towns. */
|
||||
struct ExternalTownData {
|
||||
TownID town_id; ///< The TownID of the town in OpenTTD. Not imported, but set during the founding proceess and stored here for convenience.
|
||||
std::string name; ///< The name of the town.
|
||||
uint population; ///< The target population of the town when created in OpenTTD. If input is blank, defaults to 0.
|
||||
bool is_city; ///< Should it be created as a city in OpenTTD? If input is blank, defaults to false.
|
||||
float x_proportion; ///< The X coordinate of the town, as a proportion 0..1 of the maximum X coordinate.
|
||||
float y_proportion; ///< The Y coordinate of the town, as a proportion 0..1 of the maximum Y coordinate.
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for CircularTileSearch to found a town on or near a given tile.
|
||||
* @param tile The tile to try founding the town upon.
|
||||
* @param user_data The ExternalTownData to attempt to found.
|
||||
* @return True if the town was founded successfully.
|
||||
*/
|
||||
static bool TryFoundTownNearby(TileIndex tile, void *user_data)
|
||||
{
|
||||
ExternalTownData &town = *static_cast<ExternalTownData *>(user_data);
|
||||
std::tuple<CommandCost, Money, TownID> result = Command<CMD_FOUND_TOWN>::Do(DC_EXEC, tile, TSZ_SMALL, town.is_city, _settings_game.economy.town_layout, false, 0, town.name);
|
||||
|
||||
TownID id = std::get<TownID>(result);
|
||||
|
||||
/* Check if the command failed. */
|
||||
if (id == INVALID_TOWN) return false;
|
||||
|
||||
/* The command succeeded, send the ID back through user_data. */
|
||||
town.town_id = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load town data from _file_to_saveload, place towns at the appropriate locations, and expand them to their target populations.
|
||||
*/
|
||||
void LoadTownData()
|
||||
{
|
||||
/* Load the JSON file as a string initially. We'll parse it soon. */
|
||||
size_t filesize;
|
||||
auto f = FioFOpenFile(_file_to_saveload.name, "rb", HEIGHTMAP_DIR, &filesize);
|
||||
|
||||
if (!f.has_value()) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string text(filesize, '\0');
|
||||
size_t len = fread(text.data(), filesize, 1, *f);
|
||||
f.reset();
|
||||
if (len != 1) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now parse the JSON. */
|
||||
nlohmann::json town_data;
|
||||
try {
|
||||
town_data = nlohmann::json::parse(text);
|
||||
} catch (nlohmann::json::exception &) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for JSON formatting errors with the array of towns. */
|
||||
if (!town_data.is_array()) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<Town *, uint> > towns;
|
||||
uint failed_towns = 0;
|
||||
|
||||
/* Iterate through towns and attempt to found them. */
|
||||
for (auto &feature : town_data) {
|
||||
ExternalTownData town;
|
||||
|
||||
/* Ensure JSON is formatted properly. */
|
||||
if (!feature.is_object()) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check to ensure all fields exist and are of the correct type.
|
||||
* If the town name is formatted wrong, all we can do is give a general warning. */
|
||||
if (!feature.contains("name") || !feature.at("name").is_string()) {
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If other fields are formatted wrong, we can actually inform the player which town is the problem. */
|
||||
if (!feature.contains("population") || !feature.at("population").is_number() ||
|
||||
!feature.contains("city") || !feature.at("city").is_boolean() ||
|
||||
!feature.contains("x") || !feature.at("x").is_number() ||
|
||||
!feature.contains("y") || !feature.at("y").is_number()) {
|
||||
feature.at("name").get_to(town.name);
|
||||
SetDParamStr(0, town.name);
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_TOWN_FORMATTED_INCORRECTLY, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set town properties. */
|
||||
feature.at("name").get_to(town.name);
|
||||
feature.at("population").get_to(town.population);
|
||||
feature.at("city").get_to(town.is_city);
|
||||
|
||||
/* Set town coordinates. */
|
||||
feature.at("x").get_to(town.x_proportion);
|
||||
feature.at("y").get_to(town.y_proportion);
|
||||
|
||||
/* Check for improper coordinates and warn the player. */
|
||||
if (town.x_proportion <= 0.0f || town.y_proportion <= 0.0f || town.x_proportion >= 1.0f || town.y_proportion >= 1.0f) {
|
||||
SetDParamStr(0, town.name);
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_LOAD_FAILED, STR_TOWN_DATA_ERROR_BAD_COORDINATE, WL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the target tile for the town. */
|
||||
TileIndex tile;
|
||||
switch (_settings_game.game_creation.heightmap_rotation) {
|
||||
case HM_CLOCKWISE:
|
||||
/* Tile coordinates align with what we expect. */
|
||||
tile = TileXY(town.x_proportion * Map::MaxX(), town.y_proportion * Map::MaxY());
|
||||
break;
|
||||
case HM_COUNTER_CLOCKWISE:
|
||||
/* Tile coordinates are rotated and must be adjusted. */
|
||||
tile = TileXY((1 - town.y_proportion * Map::MaxX()), town.x_proportion * Map::MaxY());
|
||||
break;
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
/* Try founding on the target tile, and if that doesn't work, find the nearest suitable tile up to 16 tiles away.
|
||||
* The target might be on water, blocked somehow, or on a steep slope that can't be terraformed by the founding command. */
|
||||
TileIndex search_tile = tile;
|
||||
bool success = CircularTileSearch(&search_tile, 16, 0, 0, TryFoundTownNearby, &town);
|
||||
|
||||
/* If we still fail to found the town, we'll create a sign at the intended location and tell the player how many towns we failed to create in an error message.
|
||||
* This allows the player to diagnose a heightmap misalignment, if towns end up in the sea, or place towns manually, if in rough terrain. */
|
||||
if (!success) {
|
||||
Command<CMD_PLACE_SIGN>::Post(tile, town.name);
|
||||
failed_towns++;
|
||||
continue;
|
||||
}
|
||||
|
||||
towns.emplace_back(std::make_pair(Town::Get(town.town_id), town.population));
|
||||
}
|
||||
|
||||
/* If we couldn't found a town (or multiple), display a message to the player with the number of failed towns. */
|
||||
if (failed_towns > 0) {
|
||||
SetDParam(0, failed_towns);
|
||||
ShowErrorMessage(STR_TOWN_DATA_ERROR_FAILED_TO_FOUND_TOWN, INVALID_STRING_ID, WL_WARNING);
|
||||
}
|
||||
|
||||
/* Now that we've created the towns, let's grow them to their target populations. */
|
||||
for (const auto &item : towns) {
|
||||
Town *t = item.first;
|
||||
uint population = item.second;
|
||||
|
||||
/* Grid towns can grow almost forever, but the town growth algorithm gets less and less efficient as it wanders roads randomly,
|
||||
* so we set an arbitrary limit. With a flat map and a 3x3 grid layout this results in about 4900 houses, or 2800 houses with "Better roads." */
|
||||
int try_limit = 1000;
|
||||
|
||||
/* If a town repeatedly fails to grow, continuing to try only wastes time. */
|
||||
int fail_limit = 10;
|
||||
|
||||
/* Grow by a constant number of houses each time, instead of growth based on current town size.
|
||||
* We want our try limit to apply in a predictable way, no matter the road layout and other geography. */
|
||||
const int HOUSES_TO_GROW = 10;
|
||||
|
||||
do {
|
||||
uint before = t->cache.num_houses;
|
||||
Command<CMD_EXPAND_TOWN>::Post(t->index, HOUSES_TO_GROW);
|
||||
if (t->cache.num_houses <= before) fail_limit--;
|
||||
} while (fail_limit > 0 && try_limit-- > 0 && t->cache.population < population);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
|
|||
void AbortGeneratingWorld();
|
||||
bool IsGeneratingWorldAborted();
|
||||
void HandleGeneratingWorldAbortion();
|
||||
void LoadTownData();
|
||||
|
||||
/* genworld_gui.cpp */
|
||||
void SetNewLandscapeType(uint8_t landscape);
|
||||
|
|
|
@ -418,12 +418,10 @@ void GraphicsSet::CopyCompatibleConfig(const GraphicsSet &src)
|
|||
/* static */ MD5File::ChecksumResult GraphicsSet::CheckMD5(const MD5File *file, Subdirectory subdir)
|
||||
{
|
||||
size_t size = 0;
|
||||
FILE *f = FioFOpenFile(file->filename, "rb", subdir, &size);
|
||||
if (f == nullptr) return MD5File::CR_NO_FILE;
|
||||
auto f = FioFOpenFile(file->filename, "rb", subdir, &size);
|
||||
if (!f.has_value()) return MD5File::CR_NO_FILE;
|
||||
|
||||
size_t max = GRFGetSizeOfDataSection(f);
|
||||
|
||||
FioFCloseFile(f);
|
||||
size_t max = GRFGetSizeOfDataSection(*f);
|
||||
|
||||
return file->CheckMD5(subdir, max);
|
||||
}
|
||||
|
@ -441,9 +439,8 @@ void GraphicsSet::CopyCompatibleConfig(const GraphicsSet &src)
|
|||
MD5File::ChecksumResult MD5File::CheckMD5(Subdirectory subdir, size_t max_size) const
|
||||
{
|
||||
size_t size;
|
||||
FILE *f = FioFOpenFile(this->filename, "rb", subdir, &size);
|
||||
|
||||
if (f == nullptr) return CR_NO_FILE;
|
||||
auto f = FioFOpenFile(this->filename, "rb", subdir, &size);
|
||||
if (!f.has_value()) return CR_NO_FILE;
|
||||
|
||||
size = std::min(size, max_size);
|
||||
|
||||
|
@ -452,13 +449,11 @@ MD5File::ChecksumResult MD5File::CheckMD5(Subdirectory subdir, size_t max_size)
|
|||
MD5Hash digest;
|
||||
size_t len;
|
||||
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
|
||||
size -= len;
|
||||
checksum.Append(buffer, len);
|
||||
}
|
||||
|
||||
FioFCloseFile(f);
|
||||
|
||||
checksum.Finish(digest);
|
||||
return this->hash == digest ? CR_MATCH : CR_MISMATCH;
|
||||
}
|
||||
|
|
|
@ -136,12 +136,11 @@ static void ReadHeightmapPNGImageData(std::span<uint8_t> map, png_structp png_pt
|
|||
*/
|
||||
static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, std::vector<uint8_t> *map)
|
||||
{
|
||||
FILE *fp;
|
||||
png_structp png_ptr = nullptr;
|
||||
png_infop info_ptr = nullptr;
|
||||
|
||||
fp = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
|
||||
if (fp == nullptr) {
|
||||
auto fp = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
|
||||
if (!fp.has_value()) {
|
||||
ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
@ -149,19 +148,17 @@ static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, std::vector
|
|||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (png_ptr == nullptr) {
|
||||
ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (info_ptr == nullptr || setjmp(png_jmpbuf(png_ptr))) {
|
||||
ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
|
||||
fclose(fp);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
png_init_io(png_ptr, *fp);
|
||||
|
||||
/* Allocate memory and read image, without alpha or 16-bit samples
|
||||
* (result is either 8-bit indexed/grayscale or 24-bit RGB) */
|
||||
|
@ -172,7 +169,6 @@ static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, std::vector
|
|||
* (this should have been taken care of by stripping alpha and 16-bit samples on load) */
|
||||
if ((png_get_channels(png_ptr, info_ptr) != 1) && (png_get_channels(png_ptr, info_ptr) != 3) && (png_get_bit_depth(png_ptr, info_ptr) != 8)) {
|
||||
ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_IMAGE_TYPE, WL_ERROR);
|
||||
fclose(fp);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
@ -182,7 +178,6 @@ static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, std::vector
|
|||
|
||||
if (!IsValidHeightmapDimension(width, height)) {
|
||||
ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
|
||||
fclose(fp);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
@ -195,7 +190,6 @@ static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, std::vector
|
|||
*x = width;
|
||||
*y = height;
|
||||
|
||||
fclose(fp);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
@ -263,15 +257,15 @@ static void ReadHeightmapBMPImageData(std::span<uint8_t> map, const BmpInfo &inf
|
|||
*/
|
||||
static bool ReadHeightmapBMP(const char *filename, uint *x, uint *y, std::vector<uint8_t> *map)
|
||||
{
|
||||
BmpInfo info{};
|
||||
BmpData data{};
|
||||
|
||||
if (!FioCheckFileExists(filename, HEIGHTMAP_DIR)) {
|
||||
auto f = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
|
||||
if (!f.has_value()) {
|
||||
ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
RandomAccessFile file(filename, HEIGHTMAP_DIR);
|
||||
BmpInfo info{};
|
||||
BmpData data{};
|
||||
|
||||
if (!BmpReadHeader(file, info, data)) {
|
||||
ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
|
||||
|
|
|
@ -122,18 +122,19 @@ int8_t SaveHighScoreValueNetwork()
|
|||
/** Save HighScore table to file */
|
||||
void SaveToHighScore()
|
||||
{
|
||||
std::unique_ptr<FILE, FileDeleter> fp(fopen(_highscore_file.c_str(), "wb"));
|
||||
if (fp == nullptr) return;
|
||||
auto ofp = FileHandle::Open(_highscore_file, "wb");
|
||||
if (!ofp.has_value()) return;
|
||||
auto &fp = *ofp;
|
||||
|
||||
/* Does not iterate through the complete array!. */
|
||||
for (int i = 0; i < SP_SAVED_HIGHSCORE_END; i++) {
|
||||
for (HighScore &hs : _highscore_table[i]) {
|
||||
/* This code is weird and old fashioned to keep compatibility with the old high score files. */
|
||||
uint8_t name_length = ClampTo<uint8_t>(hs.name.size());
|
||||
if (fwrite(&name_length, sizeof(name_length), 1, fp.get()) != 1 || // Write the string length of the name
|
||||
fwrite(hs.name.data(), name_length, 1, fp.get()) > 1 || // Yes... could be 0 bytes too
|
||||
fwrite(&hs.score, sizeof(hs.score), 1, fp.get()) != 1 ||
|
||||
fwrite(" ", 2, 1, fp.get()) != 1) { // Used to be hs.title, not saved anymore; compatibility
|
||||
if (fwrite(&name_length, sizeof(name_length), 1, fp) != 1 || // Write the string length of the name
|
||||
fwrite(hs.name.data(), name_length, 1, fp) > 1 || // Yes... could be 0 bytes too
|
||||
fwrite(&hs.score, sizeof(hs.score), 1, fp) != 1 ||
|
||||
fwrite(" ", 2, 1, fp) != 1) { // Used to be hs.title, not saved anymore; compatibility
|
||||
Debug(misc, 1, "Could not save highscore.");
|
||||
return;
|
||||
}
|
||||
|
@ -146,8 +147,9 @@ void LoadFromHighScore()
|
|||
{
|
||||
std::fill(_highscore_table.begin(), _highscore_table.end(), HighScores{});
|
||||
|
||||
std::unique_ptr<FILE, FileDeleter> fp(fopen(_highscore_file.c_str(), "rb"));
|
||||
if (fp == nullptr) return;
|
||||
auto ofp = FileHandle::Open(_highscore_file, "rb");
|
||||
if (!ofp.has_value()) return;
|
||||
auto &fp = *ofp;
|
||||
|
||||
/* Does not iterate through the complete array!. */
|
||||
for (int i = 0; i < SP_SAVED_HIGHSCORE_END; i++) {
|
||||
|
@ -156,10 +158,10 @@ void LoadFromHighScore()
|
|||
uint8_t name_length;
|
||||
char buffer[std::numeric_limits<decltype(name_length)>::max() + 1];
|
||||
|
||||
if (fread(&name_length, sizeof(name_length), 1, fp.get()) != 1 ||
|
||||
fread(buffer, name_length, 1, fp.get()) > 1 || // Yes... could be 0 bytes too
|
||||
fread(&hs.score, sizeof(hs.score), 1, fp.get()) != 1 ||
|
||||
fseek(fp.get(), 2, SEEK_CUR) == -1) { // Used to be hs.title, not saved anymore; compatibility
|
||||
if (fread(&name_length, sizeof(name_length), 1, fp) != 1 ||
|
||||
fread(buffer, name_length, 1, fp) > 1 || // Yes... could be 0 bytes too
|
||||
fread(&hs.score, sizeof(hs.score), 1, fp) != 1 ||
|
||||
fseek(fp, 2, SEEK_CUR) == -1) { // Used to be hs.title, not saved anymore; compatibility
|
||||
Debug(misc, 1, "Highscore corrupted");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ bool IniFile::SaveToDisk(const std::string &filename)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* virtual */ FILE *IniFile::OpenFile(const std::string &filename, Subdirectory subdir, size_t *size)
|
||||
/* virtual */ std::optional<FileHandle> IniFile::OpenFile(const std::string &filename, Subdirectory subdir, size_t *size)
|
||||
{
|
||||
/* Open the text file in binary mode to prevent end-of-line translations
|
||||
* done by ftell() and friends, as defined by K&R. */
|
||||
|
|
|
@ -194,13 +194,13 @@ void IniLoadFile::LoadFromDisk(const std::string &filename, Subdirectory subdir)
|
|||
std::string comment;
|
||||
|
||||
size_t end;
|
||||
FILE *in = this->OpenFile(filename, subdir, &end);
|
||||
if (in == nullptr) return;
|
||||
auto in = this->OpenFile(filename, subdir, &end);
|
||||
if (!in.has_value()) return;
|
||||
|
||||
end += ftell(in);
|
||||
end += ftell(*in);
|
||||
|
||||
/* for each line in the file */
|
||||
while (static_cast<size_t>(ftell(in)) < end && fgets(buffer, sizeof(buffer), in)) {
|
||||
while (static_cast<size_t>(ftell(*in)) < end && fgets(buffer, sizeof(buffer), *in)) {
|
||||
char c, *s;
|
||||
/* trim whitespace from the left side */
|
||||
for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
|
||||
|
@ -272,7 +272,5 @@ void IniLoadFile::LoadFromDisk(const std::string &filename, Subdirectory subdir)
|
|||
}
|
||||
|
||||
this->comment = std::move(comment);
|
||||
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,9 +71,9 @@ struct IniLoadFile {
|
|||
* @param filename Name of the INI file.
|
||||
* @param subdir The subdir to load the file from.
|
||||
* @param[out] size Size of the opened file.
|
||||
* @return File handle of the opened file, or \c nullptr.
|
||||
* @return File handle of the opened file, or \c std::nullopt.
|
||||
*/
|
||||
virtual FILE *OpenFile(const std::string &filename, Subdirectory subdir, size_t *size) = 0;
|
||||
virtual std::optional<FileHandle> OpenFile(const std::string &filename, Subdirectory subdir, size_t *size) = 0;
|
||||
|
||||
/**
|
||||
* Report an error about the file contents.
|
||||
|
@ -90,7 +90,7 @@ struct IniFile : IniLoadFile {
|
|||
|
||||
bool SaveToDisk(const std::string &filename);
|
||||
|
||||
FILE *OpenFile(const std::string &filename, Subdirectory subdir, size_t *size) override;
|
||||
std::optional<FileHandle> OpenFile(const std::string &filename, Subdirectory subdir, size_t *size) override;
|
||||
void ReportFileError(const char * const pre, const char * const buffer, const char * const post) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -3019,6 +3019,8 @@ STR_FOUND_TOWN_RANDOM_TOWN_BUTTON :{BLACK}Random T
|
|||
STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP :{BLACK}Found town in random location
|
||||
STR_FOUND_TOWN_MANY_RANDOM_TOWNS :{BLACK}Many random towns
|
||||
STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP :{BLACK}Cover the map with randomly placed towns
|
||||
STR_FOUND_TOWN_LOAD_FROM_FILE :{BLACK}Load from file
|
||||
STR_FOUND_TOWN_LOAD_FROM_FILE_TOOLTIP :{BLACK}Import town data from a JSON file
|
||||
STR_FOUND_TOWN_EXPAND_ALL_TOWNS :{BLACK}Expand all towns
|
||||
STR_FOUND_TOWN_EXPAND_ALL_TOWNS_TOOLTIP :{BLACK}Make all towns grow slightly
|
||||
|
||||
|
@ -3271,6 +3273,7 @@ STR_SAVELOAD_SAVE_SCENARIO :{WHITE}Save Sce
|
|||
STR_SAVELOAD_LOAD_SCENARIO :{WHITE}Load Scenario
|
||||
STR_SAVELOAD_LOAD_HEIGHTMAP :{WHITE}Load Heightmap
|
||||
STR_SAVELOAD_SAVE_HEIGHTMAP :{WHITE}Save Heightmap
|
||||
STR_SAVELOAD_LOAD_TOWN_DATA :{WHITE}Load Town Data
|
||||
STR_SAVELOAD_HOME_BUTTON :{BLACK}Click here to jump to the current default save/load directory
|
||||
STR_SAVELOAD_BYTES_FREE :{BLACK}{BYTES} free
|
||||
STR_SAVELOAD_LIST_TOOLTIP :{BLACK}List of drives, directories and saved-game files
|
||||
|
@ -3282,6 +3285,7 @@ STR_SAVELOAD_SAVE_TOOLTIP :{BLACK}Save the
|
|||
STR_SAVELOAD_LOAD_BUTTON :{BLACK}Load
|
||||
STR_SAVELOAD_LOAD_TOOLTIP :{BLACK}Load the selected game
|
||||
STR_SAVELOAD_LOAD_HEIGHTMAP_TOOLTIP :{BLACK}Load the selected heightmap
|
||||
STR_SAVELOAD_LOAD_TOWN_DATA_TOOLTIP :{BLACK}Load the selected town data
|
||||
STR_SAVELOAD_DETAIL_CAPTION :{BLACK}Game Details
|
||||
STR_SAVELOAD_DETAIL_NOT_AVAILABLE :{BLACK}No information available
|
||||
STR_SAVELOAD_DETAIL_COMPANY_INDEX :{SILVER}{COMMA}: {WHITE}{STRING1}
|
||||
|
@ -3415,6 +3419,13 @@ STR_GENERATION_PREPARING_TILELOOP :{BLACK}Running
|
|||
STR_GENERATION_PREPARING_SCRIPT :{BLACK}Running script
|
||||
STR_GENERATION_PREPARING_GAME :{BLACK}Preparing game
|
||||
|
||||
STR_TOWN_DATA_ERROR_LOAD_FAILED :{WHITE}Town data load failed
|
||||
STR_TOWN_DATA_ERROR_JSON_FORMATTED_INCORRECTLY :{WHITE}JSON file formatted incorrectly
|
||||
STR_TOWN_DATA_ERROR_TOWN_FORMATTED_INCORRECTLY :{WHITE}{RAW_STRING} data formatted incorrectly
|
||||
STR_TOWN_DATA_ERROR_BAD_COORDINATE :{WHITE}{RAW_STRING} coordinates formatted incorrectly, must be 0..1 as a percentage of the total heightmap dimension
|
||||
|
||||
STR_TOWN_DATA_ERROR_FAILED_TO_FOUND_TOWN :{WHITE}Could not find valid location to found {NUM} town{P "" s}. Created {P "a " ""}sign{P "" s} at the intended location{P "" s} instead
|
||||
|
||||
# NewGRF settings
|
||||
STR_NEWGRF_SETTINGS_CAPTION :{WHITE}NewGRF Settings
|
||||
STR_NEWGRF_SETTINGS_INFO_TITLE :{WHITE}Detailed NewGRF information
|
||||
|
|
|
@ -82,23 +82,23 @@ struct DLSFile {
|
|||
std::vector<DLSWave> waves;
|
||||
|
||||
/** Try loading a DLS file into memory. */
|
||||
bool LoadFile(const wchar_t *file);
|
||||
bool LoadFile(const std::string &file);
|
||||
|
||||
private:
|
||||
/** Load an articulation structure from a DLS file. */
|
||||
bool ReadDLSArticulation(FILE *f, DWORD list_length, std::vector<CONNECTION> &out);
|
||||
bool ReadDLSArticulation(FileHandle &f, DWORD list_length, std::vector<CONNECTION> &out);
|
||||
/** Load a list of regions from a DLS file. */
|
||||
bool ReadDLSRegionList(FILE *f, DWORD list_length, DLSInstrument &instrument);
|
||||
bool ReadDLSRegionList(FileHandle &f, DWORD list_length, DLSInstrument &instrument);
|
||||
/** Load a single region from a DLS file. */
|
||||
bool ReadDLSRegion(FILE *f, DWORD list_length, std::vector<DLSRegion> &out);
|
||||
bool ReadDLSRegion(FileHandle &f, DWORD list_length, std::vector<DLSRegion> &out);
|
||||
/** Load a list of instruments from a DLS file. */
|
||||
bool ReadDLSInstrumentList(FILE *f, DWORD list_length);
|
||||
bool ReadDLSInstrumentList(FileHandle &f, DWORD list_length);
|
||||
/** Load a single instrument from a DLS file. */
|
||||
bool ReadDLSInstrument(FILE *f, DWORD list_length);
|
||||
bool ReadDLSInstrument(FileHandle &f, DWORD list_length);
|
||||
/** Load a list of waves from a DLS file. */
|
||||
bool ReadDLSWaveList(FILE *f, DWORD list_length);
|
||||
bool ReadDLSWaveList(FileHandle &f, DWORD list_length);
|
||||
/** Load a single wave from a DLS file. */
|
||||
bool ReadDLSWave(FILE *f, DWORD list_length, long offset);
|
||||
bool ReadDLSWave(FileHandle &f, DWORD list_length, long offset);
|
||||
};
|
||||
|
||||
/** A RIFF chunk header. */
|
||||
|
@ -154,7 +154,7 @@ static std::vector<IDirectMusicDownload *> _dls_downloads;
|
|||
static FMusicDriver_DMusic iFMusicDriver_DMusic;
|
||||
|
||||
|
||||
bool DLSFile::ReadDLSArticulation(FILE *f, DWORD list_length, std::vector<CONNECTION> &out)
|
||||
bool DLSFile::ReadDLSArticulation(FileHandle &f, DWORD list_length, std::vector<CONNECTION> &out)
|
||||
{
|
||||
while (list_length > 0) {
|
||||
ChunkHeader chunk;
|
||||
|
@ -180,7 +180,7 @@ bool DLSFile::ReadDLSArticulation(FILE *f, DWORD list_length, std::vector<CONNEC
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSRegion(FILE *f, DWORD list_length, std::vector<DLSRegion> &out)
|
||||
bool DLSFile::ReadDLSRegion(FileHandle &f, DWORD list_length, std::vector<DLSRegion> &out)
|
||||
{
|
||||
out.push_back(DLSRegion());
|
||||
DLSRegion ®ion = out.back();
|
||||
|
@ -239,7 +239,7 @@ bool DLSFile::ReadDLSRegion(FILE *f, DWORD list_length, std::vector<DLSRegion> &
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSRegionList(FILE *f, DWORD list_length, DLSInstrument &instrument)
|
||||
bool DLSFile::ReadDLSRegionList(FileHandle &f, DWORD list_length, DLSInstrument &instrument)
|
||||
{
|
||||
while (list_length > 0) {
|
||||
ChunkHeader chunk;
|
||||
|
@ -265,7 +265,7 @@ bool DLSFile::ReadDLSRegionList(FILE *f, DWORD list_length, DLSInstrument &instr
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSInstrument(FILE *f, DWORD list_length)
|
||||
bool DLSFile::ReadDLSInstrument(FileHandle &f, DWORD list_length)
|
||||
{
|
||||
this->instruments.push_back(DLSInstrument());
|
||||
DLSInstrument &instrument = this->instruments.back();
|
||||
|
@ -309,7 +309,7 @@ bool DLSFile::ReadDLSInstrument(FILE *f, DWORD list_length)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSInstrumentList(FILE *f, DWORD list_length)
|
||||
bool DLSFile::ReadDLSInstrumentList(FileHandle &f, DWORD list_length)
|
||||
{
|
||||
while (list_length > 0) {
|
||||
ChunkHeader chunk;
|
||||
|
@ -337,7 +337,7 @@ bool DLSFile::ReadDLSInstrumentList(FILE *f, DWORD list_length)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSWave(FILE *f, DWORD list_length, long offset)
|
||||
bool DLSFile::ReadDLSWave(FileHandle &f, DWORD list_length, long offset)
|
||||
{
|
||||
this->waves.push_back(DLSWave());
|
||||
DLSWave &wave = this->waves.back();
|
||||
|
@ -397,7 +397,7 @@ bool DLSFile::ReadDLSWave(FILE *f, DWORD list_length, long offset)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::ReadDLSWaveList(FILE *f, DWORD list_length)
|
||||
bool DLSFile::ReadDLSWaveList(FileHandle &f, DWORD list_length)
|
||||
{
|
||||
long base_offset = ftell(f);
|
||||
|
||||
|
@ -429,14 +429,13 @@ bool DLSFile::ReadDLSWaveList(FILE *f, DWORD list_length)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DLSFile::LoadFile(const wchar_t *file)
|
||||
bool DLSFile::LoadFile(const std::string &file)
|
||||
{
|
||||
Debug(driver, 2, "DMusic: Try to load DLS file {}", FS2OTTD(file));
|
||||
Debug(driver, 2, "DMusic: Try to load DLS file {}", file);
|
||||
|
||||
FILE *f = _wfopen(file, L"rb");
|
||||
if (f == nullptr) return false;
|
||||
|
||||
FileCloser f_scope(f);
|
||||
auto of = FileHandle::Open(file, "rb");
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
/* Check DLS file header. */
|
||||
ChunkHeader hdr;
|
||||
|
@ -871,7 +870,7 @@ static const char *LoadDefaultDLSFile(const char *user_dls)
|
|||
if (SUCCEEDED(RegQueryValueEx(hkDM, L"GMFilePath", nullptr, nullptr, (LPBYTE)dls_path, &buf_size))) {
|
||||
wchar_t expand_path[MAX_PATH * 2];
|
||||
ExpandEnvironmentStrings(dls_path, expand_path, static_cast<DWORD>(std::size(expand_path)));
|
||||
if (!dls_file.LoadFile(expand_path)) Debug(driver, 1, "Failed to load default GM DLS file from registry");
|
||||
if (!dls_file.LoadFile(FS2OTTD(expand_path))) Debug(driver, 1, "Failed to load default GM DLS file from registry");
|
||||
}
|
||||
RegCloseKey(hkDM);
|
||||
}
|
||||
|
@ -882,10 +881,10 @@ static const char *LoadDefaultDLSFile(const char *user_dls)
|
|||
wchar_t path[MAX_PATH];
|
||||
ExpandEnvironmentStrings(DLS_GM_FILE, path, static_cast<DWORD>(std::size(path)));
|
||||
|
||||
if (!dls_file.LoadFile(path)) return "Can't load GM DLS collection";
|
||||
if (!dls_file.LoadFile(FS2OTTD(path))) return "Can't load GM DLS collection";
|
||||
}
|
||||
} else {
|
||||
if (!dls_file.LoadFile(OTTD2FS(user_dls).c_str())) return "Can't load GM DLS collection";
|
||||
if (!dls_file.LoadFile(user_dls)) return "Can't load GM DLS collection";
|
||||
}
|
||||
|
||||
/* Get download port and allocate download IDs. */
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
* @param file file to read from at current position
|
||||
* @param len number of bytes to read
|
||||
*/
|
||||
ByteBuffer(FILE *file, size_t len)
|
||||
ByteBuffer(FileHandle &file, size_t len)
|
||||
{
|
||||
this->buf.resize(len);
|
||||
if (fread(this->buf.data(), 1, len, file) == len) {
|
||||
|
@ -186,7 +186,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static bool ReadTrackChunk(FILE *file, MidiFile &target)
|
||||
static bool ReadTrackChunk(FileHandle &file, MidiFile &target)
|
||||
{
|
||||
uint8_t buf[4];
|
||||
|
||||
|
@ -405,10 +405,9 @@ static bool FixupMidiData(MidiFile &target)
|
|||
*/
|
||||
bool MidiFile::ReadSMFHeader(const std::string &filename, SMFHeader &header)
|
||||
{
|
||||
FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
|
||||
if (!file) return false;
|
||||
bool result = ReadSMFHeader(file, header);
|
||||
FioFCloseFile(file);
|
||||
auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
|
||||
if (!file.has_value()) return false;
|
||||
bool result = ReadSMFHeader(*file, header);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -419,7 +418,7 @@ bool MidiFile::ReadSMFHeader(const std::string &filename, SMFHeader &header)
|
|||
* @param[out] header filled with data read
|
||||
* @return true if a header in correct format could be read from the file
|
||||
*/
|
||||
bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
|
||||
bool MidiFile::ReadSMFHeader(FileHandle &file, SMFHeader &header)
|
||||
{
|
||||
/* Try to read header, fixed size */
|
||||
uint8_t buffer[14];
|
||||
|
@ -453,31 +452,26 @@ bool MidiFile::LoadFile(const std::string &filename)
|
|||
this->tempos.clear();
|
||||
this->tickdiv = 0;
|
||||
|
||||
bool success = false;
|
||||
FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
|
||||
if (file == nullptr) return false;
|
||||
auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
|
||||
if (!file.has_value()) return false;
|
||||
|
||||
SMFHeader header;
|
||||
if (!ReadSMFHeader(file, header)) goto cleanup;
|
||||
if (!ReadSMFHeader(*file, header)) return false;
|
||||
|
||||
/* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
|
||||
if (header.format != 0 && header.format != 1) goto cleanup;
|
||||
if (header.format != 0 && header.format != 1) return false;
|
||||
/* Doesn't support SMPTE timecode files */
|
||||
if ((header.tickdiv & 0x8000) != 0) goto cleanup;
|
||||
if ((header.tickdiv & 0x8000) != 0) return false;
|
||||
|
||||
this->tickdiv = header.tickdiv;
|
||||
|
||||
for (; header.tracks > 0; header.tracks--) {
|
||||
if (!ReadTrackChunk(file, *this)) {
|
||||
goto cleanup;
|
||||
if (!ReadTrackChunk(*file, *this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
success = FixupMidiData(*this);
|
||||
|
||||
cleanup:
|
||||
FioFCloseFile(file);
|
||||
return success;
|
||||
return FixupMidiData(*this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -873,7 +867,7 @@ void MidiFile::MoveFrom(MidiFile &other)
|
|||
other.tickdiv = 0;
|
||||
}
|
||||
|
||||
static void WriteVariableLen(FILE *f, uint32_t value)
|
||||
static void WriteVariableLen(FileHandle &f, uint32_t value)
|
||||
{
|
||||
if (value <= 0x7F) {
|
||||
uint8_t tb = value;
|
||||
|
@ -906,10 +900,9 @@ static void WriteVariableLen(FILE *f, uint32_t value)
|
|||
*/
|
||||
bool MidiFile::WriteSMF(const std::string &filename)
|
||||
{
|
||||
FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
auto of = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
/* SMF header */
|
||||
const uint8_t fileheader[] = {
|
||||
|
@ -1007,7 +1000,6 @@ bool MidiFile::WriteSMF(const std::string &filename)
|
|||
}
|
||||
|
||||
/* Fail for any other commands */
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1023,7 +1015,6 @@ bool MidiFile::WriteSMF(const std::string &filename)
|
|||
tracksize = TO_BE32(tracksize);
|
||||
fwrite(&tracksize, 4, 1, f);
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define MUSIC_MIDIFILE_HPP
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../fileio_type.h"
|
||||
#include "midi.h"
|
||||
|
||||
struct MusicSongInfo;
|
||||
|
@ -44,7 +45,7 @@ struct MidiFile {
|
|||
|
||||
static std::string GetSMFFile(const MusicSongInfo &song);
|
||||
static bool ReadSMFHeader(const std::string &filename, SMFHeader &header);
|
||||
static bool ReadSMFHeader(FILE *file, SMFHeader &header);
|
||||
static bool ReadSMFHeader(FileHandle &file, SMFHeader &header);
|
||||
};
|
||||
|
||||
#endif /* MUSIC_MIDIFILE_HPP */
|
||||
|
|
|
@ -1112,18 +1112,18 @@ void NetworkGameLoop()
|
|||
|
||||
#ifdef DEBUG_DUMP_COMMANDS
|
||||
/* Loading of the debug commands from -ddesync>=1 */
|
||||
static FILE *f = FioFOpenFile("commands.log", "rb", SAVE_DIR);
|
||||
static auto f = FioFOpenFile("commands.log", "rb", SAVE_DIR);
|
||||
static TimerGameEconomy::Date next_date(0);
|
||||
static uint32_t next_date_fract;
|
||||
static CommandPacket *cp = nullptr;
|
||||
static bool check_sync_state = false;
|
||||
static uint32_t sync_state[2];
|
||||
if (f == nullptr && next_date == 0) {
|
||||
if (!f.has_value() && next_date == 0) {
|
||||
Debug(desync, 0, "Cannot open commands.log");
|
||||
next_date = TimerGameEconomy::Date(1);
|
||||
}
|
||||
|
||||
while (f != nullptr && !feof(f)) {
|
||||
while (f.has_value() && !feof(*f)) {
|
||||
if (TimerGameEconomy::date == next_date && TimerGameEconomy::date_fract == next_date_fract) {
|
||||
if (cp != nullptr) {
|
||||
NetworkSendCommand(cp->cmd, cp->err_msg, nullptr, cp->company, cp->data);
|
||||
|
@ -1156,7 +1156,7 @@ void NetworkGameLoop()
|
|||
if (cp != nullptr || check_sync_state) break;
|
||||
|
||||
char buff[4096];
|
||||
if (fgets(buff, lengthof(buff), f) == nullptr) break;
|
||||
if (fgets(buff, lengthof(buff), *f) == nullptr) break;
|
||||
|
||||
char *p = buff;
|
||||
/* Ignore the "[date time] " part of the message */
|
||||
|
@ -1225,10 +1225,9 @@ void NetworkGameLoop()
|
|||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
if (f != nullptr && feof(f)) {
|
||||
if (f.has_value() && feof(*f)) {
|
||||
Debug(desync, 0, "End of commands.log");
|
||||
fclose(f);
|
||||
f = nullptr;
|
||||
f.reset();
|
||||
}
|
||||
#endif /* DEBUG_DUMP_COMMANDS */
|
||||
if (_frame_counter >= _frame_counter_max) {
|
||||
|
|
|
@ -404,16 +404,16 @@ static bool GunzipFile(const ContentInfo *ci)
|
|||
bool ret = true;
|
||||
|
||||
/* Need to open the file with fopen() to support non-ASCII on Windows. */
|
||||
FILE *ftmp = fopen(GetFullFilename(ci, true).c_str(), "rb");
|
||||
if (ftmp == nullptr) return false;
|
||||
auto ftmp = FileHandle::Open(GetFullFilename(ci, true), "rb");
|
||||
if (!ftmp.has_value()) return false;
|
||||
/* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
|
||||
int fdup = dup(fileno(ftmp));
|
||||
int fdup = dup(fileno(*ftmp));
|
||||
gzFile fin = gzdopen(fdup, "rb");
|
||||
fclose(ftmp);
|
||||
ftmp.reset();
|
||||
|
||||
FILE *fout = fopen(GetFullFilename(ci, false).c_str(), "wb");
|
||||
auto fout = FileHandle::Open(GetFullFilename(ci, false), "wb");
|
||||
|
||||
if (fin == nullptr || fout == nullptr) {
|
||||
if (fin == nullptr || !fout.has_value()) {
|
||||
ret = false;
|
||||
} else {
|
||||
uint8_t buff[8192];
|
||||
|
@ -437,7 +437,7 @@ static bool GunzipFile(const ContentInfo *ci)
|
|||
if (errnum != 0 && errnum != Z_STREAM_END) ret = false;
|
||||
break;
|
||||
}
|
||||
if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, fout)) {
|
||||
if (read < 0 || static_cast<size_t>(read) != fwrite(buff, 1, read, *fout)) {
|
||||
/* If gzread() returns -1, there was an error in archive */
|
||||
ret = false;
|
||||
break;
|
||||
|
@ -453,7 +453,6 @@ static bool GunzipFile(const ContentInfo *ci)
|
|||
/* Failing gzdopen does not close the passed file descriptor. */
|
||||
close(fdup);
|
||||
}
|
||||
if (fout != nullptr) fclose(fout);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
|
@ -468,14 +467,14 @@ static bool GunzipFile(const ContentInfo *ci)
|
|||
* @param amount The number of bytes to write.
|
||||
* @return The number of bytes that were written.
|
||||
*/
|
||||
static inline ssize_t TransferOutFWrite(FILE *file, const char *buffer, size_t amount)
|
||||
static inline ssize_t TransferOutFWrite(std::optional<FileHandle> &file, const char *buffer, size_t amount)
|
||||
{
|
||||
return fwrite(buffer, 1, amount, file);
|
||||
return fwrite(buffer, 1, amount, *file);
|
||||
}
|
||||
|
||||
bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet &p)
|
||||
{
|
||||
if (this->curFile == nullptr) {
|
||||
if (!this->curFile.has_value()) {
|
||||
delete this->curInfo;
|
||||
/* When we haven't opened a file this must be our first packet with metadata. */
|
||||
this->curInfo = new ContentInfo;
|
||||
|
@ -491,12 +490,11 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet &p)
|
|||
} else {
|
||||
/* We have a file opened, thus are downloading internal content */
|
||||
size_t toRead = p.RemainingBytesToTransfer();
|
||||
if (toRead != 0 && static_cast<size_t>(p.TransferOut(TransferOutFWrite, this->curFile)) != toRead) {
|
||||
if (toRead != 0 && static_cast<size_t>(p.TransferOut(TransferOutFWrite, std::ref(this->curFile))) != toRead) {
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
|
||||
this->CloseConnection();
|
||||
fclose(this->curFile);
|
||||
this->curFile = nullptr;
|
||||
this->curFile.reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -524,7 +522,7 @@ bool ClientNetworkContentSocketHandler::BeforeDownload()
|
|||
if (this->curInfo->filesize != 0) {
|
||||
/* The filesize is > 0, so we are going to download it */
|
||||
std::string filename = GetFullFilename(this->curInfo, true);
|
||||
if (filename.empty() || (this->curFile = fopen(filename.c_str(), "wb")) == nullptr) {
|
||||
if (filename.empty() || !(this->curFile = FileHandle::Open(filename, "wb")).has_value()) {
|
||||
/* Unless that fails of course... */
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
|
||||
|
@ -542,8 +540,7 @@ void ClientNetworkContentSocketHandler::AfterDownload()
|
|||
{
|
||||
/* We read nothing; that's our marker for end-of-stream.
|
||||
* Now gunzip the tar and make it known. */
|
||||
fclose(this->curFile);
|
||||
this->curFile = nullptr;
|
||||
this->curFile.reset();
|
||||
|
||||
if (GunzipFile(this->curInfo)) {
|
||||
FioRemove(GetFullFilename(this->curInfo, true));
|
||||
|
@ -583,11 +580,10 @@ void ClientNetworkContentSocketHandler::OnFailure()
|
|||
this->http_response.shrink_to_fit();
|
||||
this->http_response_index = -2;
|
||||
|
||||
if (this->curFile != nullptr) {
|
||||
if (this->curFile.has_value()) {
|
||||
this->OnDownloadProgress(this->curInfo, -1);
|
||||
|
||||
fclose(this->curFile);
|
||||
this->curFile = nullptr;
|
||||
this->curFile.reset();
|
||||
}
|
||||
|
||||
/* If we fail, download the rest via the 'old' system. */
|
||||
|
@ -623,7 +619,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
|
|||
|
||||
if (data != nullptr) {
|
||||
/* We have data, so write it to the file. */
|
||||
if (fwrite(data.get(), 1, length, this->curFile) != length) {
|
||||
if (fwrite(data.get(), 1, length, *this->curFile) != length) {
|
||||
/* Writing failed somehow, let try via the old method. */
|
||||
this->OnFailure();
|
||||
} else {
|
||||
|
@ -635,7 +631,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
|
|||
return;
|
||||
}
|
||||
|
||||
if (this->curFile != nullptr) {
|
||||
if (this->curFile.has_value()) {
|
||||
/* We've finished downloading a file. */
|
||||
this->AfterDownload();
|
||||
}
|
||||
|
@ -732,7 +728,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(std::unique_ptr<char[]> da
|
|||
ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
|
||||
NetworkContentSocketHandler(),
|
||||
http_response_index(-2),
|
||||
curFile(nullptr),
|
||||
curFile(std::nullopt),
|
||||
curInfo(nullptr),
|
||||
isConnecting(false),
|
||||
isCancelled(false)
|
||||
|
@ -744,7 +740,6 @@ ClientNetworkContentSocketHandler::ClientNetworkContentSocketHandler() :
|
|||
ClientNetworkContentSocketHandler::~ClientNetworkContentSocketHandler()
|
||||
{
|
||||
delete this->curInfo;
|
||||
if (this->curFile != nullptr) fclose(this->curFile);
|
||||
|
||||
for (ContentInfo *ci : this->infos) delete ci;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ protected:
|
|||
std::vector<char> http_response; ///< The HTTP response to the requests we've been doing
|
||||
int http_response_index; ///< Where we are, in the response, with handling it
|
||||
|
||||
FILE *curFile; ///< Currently downloaded file
|
||||
std::optional<FileHandle> curFile; ///< Currently downloaded file
|
||||
ContentInfo *curInfo; ///< Information about the currently downloaded file
|
||||
bool isConnecting; ///< Whether we're connecting
|
||||
bool isCancelled; ///< Whether the download has been cancelled
|
||||
|
|
|
@ -252,7 +252,7 @@ void UpdateNewGRFConfigPalette(int32_t)
|
|||
* @param f GRF.
|
||||
* @return Size of the data section or SIZE_MAX if the file has no separate data section.
|
||||
*/
|
||||
size_t GRFGetSizeOfDataSection(FILE *f)
|
||||
size_t GRFGetSizeOfDataSection(FileHandle &f)
|
||||
{
|
||||
extern const uint8_t _grf_cont_v2_sig[];
|
||||
static const uint header_len = 14;
|
||||
|
@ -284,32 +284,28 @@ size_t GRFGetSizeOfDataSection(FILE *f)
|
|||
*/
|
||||
static bool CalcGRFMD5Sum(GRFConfig *config, Subdirectory subdir)
|
||||
{
|
||||
FILE *f;
|
||||
Md5 checksum;
|
||||
uint8_t buffer[1024];
|
||||
size_t len, size;
|
||||
|
||||
/* open the file */
|
||||
f = FioFOpenFile(config->filename, "rb", subdir, &size);
|
||||
if (f == nullptr) return false;
|
||||
auto f = FioFOpenFile(config->filename, "rb", subdir, &size);
|
||||
if (!f.has_value()) return false;
|
||||
|
||||
long start = ftell(f);
|
||||
size = std::min(size, GRFGetSizeOfDataSection(f));
|
||||
long start = ftell(*f);
|
||||
size = std::min(size, GRFGetSizeOfDataSection(*f));
|
||||
|
||||
if (start < 0 || fseek(f, start, SEEK_SET) < 0) {
|
||||
FioFCloseFile(f);
|
||||
if (start < 0 || fseek(*f, start, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* calculate md5sum */
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
|
||||
size -= len;
|
||||
checksum.Append(buffer, len);
|
||||
}
|
||||
checksum.Finish(config->ident.md5sum);
|
||||
|
||||
FioFCloseFile(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ struct NewGRFScanCallback {
|
|||
virtual void OnNewGRFsScanned() = 0;
|
||||
};
|
||||
|
||||
size_t GRFGetSizeOfDataSection(FILE *f);
|
||||
size_t GRFGetSizeOfDataSection(FileHandle &f);
|
||||
|
||||
void ScanNewGRFFiles(NewGRFScanCallback *callback);
|
||||
const GRFConfig *FindGRFConfig(uint32_t grfid, FindGRFConfigMode mode, const MD5Hash *md5sum = nullptr, uint32_t desired_version = 0);
|
||||
|
|
|
@ -104,15 +104,18 @@ uint32_t NewGRFProfiler::Finish()
|
|||
std::string filename = this->GetOutputFilename();
|
||||
IConsolePrint(CC_DEBUG, "Finished profile of NewGRF [{:08X}], writing {} events to '{}'.", BSWAP32(this->grffile->grfid), this->calls.size(), filename);
|
||||
|
||||
FILE *f = FioFOpenFile(filename, "wt", Subdirectory::NO_DIRECTORY);
|
||||
FileCloser fcloser(f);
|
||||
|
||||
uint32_t total_microseconds = 0;
|
||||
|
||||
fmt::print(f, "Tick,Sprite,Feature,Item,CallbackID,Microseconds,Depth,Result\n");
|
||||
for (const Call &c : this->calls) {
|
||||
fmt::print(f, "{},{},{:#X},{},{:#X},{},{},{}\n", c.tick, c.root_sprite, c.feat, c.item, (uint)c.cb, c.time, c.subs, c.result);
|
||||
total_microseconds += c.time;
|
||||
auto f = FioFOpenFile(filename, "wt", Subdirectory::NO_DIRECTORY);
|
||||
|
||||
if (!f.has_value()) {
|
||||
IConsolePrint(CC_ERROR, "Failed to open '{}' for writing.", filename);
|
||||
} else {
|
||||
fmt::print(*f, "Tick,Sprite,Feature,Item,CallbackID,Microseconds,Depth,Result\n");
|
||||
for (const Call &c : this->calls) {
|
||||
fmt::print(*f, "{},{},{:#X},{},{:#X},{},{},{}\n", c.tick, c.root_sprite, c.feat, c.item, (uint)c.cb, c.time, c.subs, c.result);
|
||||
total_microseconds += c.time;
|
||||
}
|
||||
}
|
||||
|
||||
this->Abort();
|
||||
|
|
|
@ -24,14 +24,12 @@ template <typename Tpf> void DumpState(Tpf &pf1, Tpf &pf2)
|
|||
DumpTarget dmp1, dmp2;
|
||||
pf1.DumpBase(dmp1);
|
||||
pf2.DumpBase(dmp2);
|
||||
FILE *f1 = fopen("yapf1.txt", "wt");
|
||||
FILE *f2 = fopen("yapf2.txt", "wt");
|
||||
assert(f1 != nullptr);
|
||||
assert(f2 != nullptr);
|
||||
fwrite(dmp1.m_out.c_str(), 1, dmp1.m_out.size(), f1);
|
||||
fwrite(dmp2.m_out.c_str(), 1, dmp2.m_out.size(), f2);
|
||||
fclose(f1);
|
||||
fclose(f2);
|
||||
auto f1 = FileHandle::Open("yapf1.txt", "wt");
|
||||
auto f2 = FileHandle::Open("yapf2.txt", "wt");
|
||||
assert(f1.has_value());
|
||||
assert(f2.has_value());
|
||||
fwrite(dmp1.m_out.c_str(), 1, dmp1.m_out.size(), *f1);
|
||||
fwrite(dmp2.m_out.c_str(), 1, dmp2.m_out.size(), *f2);
|
||||
}
|
||||
|
||||
template <class Types>
|
||||
|
|
|
@ -26,10 +26,10 @@ RandomAccessFile::RandomAccessFile(const std::string &filename, Subdirectory sub
|
|||
{
|
||||
size_t file_size;
|
||||
this->file_handle = FioFOpenFile(filename, "rb", subdir, &file_size);
|
||||
if (this->file_handle == nullptr) UserError("Cannot open file '{}'", filename);
|
||||
if (!this->file_handle.has_value()) UserError("Cannot open file '{}'", filename);
|
||||
|
||||
/* When files are in a tar-file, the begin of the file might not be at 0. */
|
||||
long pos = ftell(this->file_handle);
|
||||
long pos = ftell(*this->file_handle);
|
||||
if (pos < 0) UserError("Cannot read file '{}'", filename);
|
||||
|
||||
/* Make a note of start and end position for readers who check bounds. */
|
||||
|
@ -45,14 +45,6 @@ RandomAccessFile::RandomAccessFile(const std::string &filename, Subdirectory sub
|
|||
this->SeekTo(static_cast<size_t>(pos), SEEK_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the file's file handle.
|
||||
*/
|
||||
RandomAccessFile::~RandomAccessFile()
|
||||
{
|
||||
fclose(this->file_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename of the opened file with the path from the SubDirectory and the extension.
|
||||
* @return Name of the file.
|
||||
|
@ -100,7 +92,7 @@ void RandomAccessFile::SeekTo(size_t pos, int mode)
|
|||
if (mode == SEEK_CUR) pos += this->GetPos();
|
||||
|
||||
this->pos = pos;
|
||||
if (fseek(this->file_handle, this->pos, SEEK_SET) < 0) {
|
||||
if (fseek(*this->file_handle, this->pos, SEEK_SET) < 0) {
|
||||
Debug(misc, 0, "Seeking in {} failed", this->filename);
|
||||
}
|
||||
|
||||
|
@ -116,7 +108,7 @@ uint8_t RandomAccessFile::ReadByte()
|
|||
{
|
||||
if (this->buffer == this->buffer_end) {
|
||||
this->buffer = this->buffer_start;
|
||||
size_t size = fread(this->buffer, 1, RandomAccessFile::BUFFER_SIZE, this->file_handle);
|
||||
size_t size = fread(this->buffer, 1, RandomAccessFile::BUFFER_SIZE, *this->file_handle);
|
||||
this->pos += size;
|
||||
this->buffer_end = this->buffer_start + size;
|
||||
|
||||
|
@ -153,7 +145,7 @@ uint32_t RandomAccessFile::ReadDword()
|
|||
void RandomAccessFile::ReadBlock(void *ptr, size_t size)
|
||||
{
|
||||
this->SeekTo(this->GetPos(), SEEK_SET);
|
||||
this->pos += fread(ptr, 1, size, this->file_handle);
|
||||
this->pos += fread(ptr, 1, size, *this->file_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,7 @@ class RandomAccessFile {
|
|||
std::string filename; ///< Full name of the file; relative path to subdir plus the extension of the file.
|
||||
std::string simplified_filename; ///< Simplified lowecase name of the file; only the name, no path or extension.
|
||||
|
||||
FILE *file_handle; ///< File handle of the open file.
|
||||
std::optional<FileHandle> file_handle; ///< File handle of the open file.
|
||||
size_t pos; ///< Position in the file of the end of the read buffer.
|
||||
size_t start_pos; ///< Start position of file. May be non-zero if file is within a tar file.
|
||||
size_t end_pos; ///< End position of file.
|
||||
|
@ -40,7 +40,7 @@ public:
|
|||
RandomAccessFile(const RandomAccessFile&) = delete;
|
||||
void operator=(const RandomAccessFile&) = delete;
|
||||
|
||||
virtual ~RandomAccessFile();
|
||||
virtual ~RandomAccessFile() {}
|
||||
|
||||
const std::string &GetFilename() const;
|
||||
const std::string &GetSimplifiedFilename() const;
|
||||
|
|
|
@ -65,7 +65,7 @@ static uint8_t ReadByteFromFile(LoadgameState *ls)
|
|||
if (ls->buffer_cur >= ls->buffer_count) {
|
||||
|
||||
/* Read some new bytes from the file */
|
||||
int count = (int)fread(ls->buffer, 1, BUFFER_SIZE, ls->file);
|
||||
int count = static_cast<int>(fread(ls->buffer, 1, BUFFER_SIZE, *ls->file));
|
||||
|
||||
/* We tried to read, but there is nothing in the file anymore.. */
|
||||
if (count == 0) {
|
||||
|
@ -235,7 +235,7 @@ static bool VerifyOldNameChecksum(char *title, uint len)
|
|||
return sum == sum2;
|
||||
}
|
||||
|
||||
static std::tuple<SavegameType, std::string> DetermineOldSavegameTypeAndName(FILE *f)
|
||||
static std::tuple<SavegameType, std::string> DetermineOldSavegameTypeAndName(FileHandle &f)
|
||||
{
|
||||
long pos = ftell(f);
|
||||
char buffer[std::max(TTO_HEADER_SIZE, TTD_HEADER_SIZE)];
|
||||
|
@ -267,13 +267,13 @@ bool LoadOldSaveGame(const std::string &file)
|
|||
/* Open file */
|
||||
ls.file = FioFOpenFile(file, "rb", NO_DIRECTORY);
|
||||
|
||||
if (ls.file == nullptr) {
|
||||
if (!ls.file.has_value()) {
|
||||
Debug(oldloader, 0, "Cannot open file '{}'", file);
|
||||
return false;
|
||||
}
|
||||
|
||||
SavegameType type;
|
||||
std::tie(type, std::ignore) = DetermineOldSavegameTypeAndName(ls.file);
|
||||
std::tie(type, std::ignore) = DetermineOldSavegameTypeAndName(*ls.file);
|
||||
|
||||
LoadOldMainProc *proc = nullptr;
|
||||
|
||||
|
@ -296,7 +296,7 @@ bool LoadOldSaveGame(const std::string &file)
|
|||
|
||||
if (!game_loaded) {
|
||||
SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
|
||||
fclose(ls.file);
|
||||
ls.file.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -307,11 +307,10 @@ bool LoadOldSaveGame(const std::string &file)
|
|||
|
||||
std::string GetOldSaveGameName(const std::string &file)
|
||||
{
|
||||
FILE *f = FioFOpenFile(file, "rb", NO_DIRECTORY);
|
||||
if (f == nullptr) return {};
|
||||
auto f = FioFOpenFile(file, "rb", NO_DIRECTORY);
|
||||
if (!f.has_value()) return {};
|
||||
|
||||
std::string name;
|
||||
std::tie(std::ignore, name) = DetermineOldSavegameTypeAndName(f);
|
||||
fclose(f);
|
||||
std::tie(std::ignore, name) = DetermineOldSavegameTypeAndName(*f);
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ static const uint BUFFER_SIZE = 4096;
|
|||
static const uint OLD_MAP_SIZE = 256;
|
||||
|
||||
struct LoadgameState {
|
||||
FILE *file;
|
||||
std::optional<FileHandle> file;
|
||||
|
||||
uint chunk_size;
|
||||
|
||||
|
|
|
@ -2204,39 +2204,37 @@ static void SlFixPointers()
|
|||
|
||||
/** Yes, simply reading from a file. */
|
||||
struct FileReader : LoadFilter {
|
||||
FILE *file; ///< The file to read from.
|
||||
std::optional<FileHandle> file; ///< The file to read from.
|
||||
long begin; ///< The begin of the file.
|
||||
|
||||
/**
|
||||
* Create the file reader, so it reads from a specific file.
|
||||
* @param file The file to read from.
|
||||
*/
|
||||
FileReader(FILE *file) : LoadFilter(nullptr), file(file), begin(ftell(file))
|
||||
FileReader(FileHandle &&file) : LoadFilter(nullptr), file(std::move(file)), begin(ftell(*this->file))
|
||||
{
|
||||
}
|
||||
|
||||
/** Make sure everything is cleaned up. */
|
||||
~FileReader()
|
||||
{
|
||||
if (this->file != nullptr) {
|
||||
_game_session_stats.savegame_size = ftell(this->file) - this->begin;
|
||||
fclose(this->file);
|
||||
if (this->file.has_value()) {
|
||||
_game_session_stats.savegame_size = ftell(*this->file) - this->begin;
|
||||
}
|
||||
this->file = nullptr;
|
||||
}
|
||||
|
||||
size_t Read(uint8_t *buf, size_t size) override
|
||||
{
|
||||
/* We're in the process of shutting down, i.e. in "failure" mode. */
|
||||
if (this->file == nullptr) return 0;
|
||||
if (!this->file.has_value()) return 0;
|
||||
|
||||
return fread(buf, 1, size, this->file);
|
||||
return fread(buf, 1, size, *this->file);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
clearerr(this->file);
|
||||
if (fseek(this->file, this->begin, SEEK_SET)) {
|
||||
clearerr(*this->file);
|
||||
if (fseek(*this->file, this->begin, SEEK_SET)) {
|
||||
Debug(sl, 1, "Could not reset the file reading");
|
||||
}
|
||||
}
|
||||
|
@ -2244,13 +2242,13 @@ struct FileReader : LoadFilter {
|
|||
|
||||
/** Yes, simply writing to a file. */
|
||||
struct FileWriter : SaveFilter {
|
||||
FILE *file; ///< The file to write to.
|
||||
std::optional<FileHandle> file; ///< The file to write to.
|
||||
|
||||
/**
|
||||
* Create the file writer, so it writes to a specific file.
|
||||
* @param file The file to write to.
|
||||
*/
|
||||
FileWriter(FILE *file) : SaveFilter(nullptr), file(file)
|
||||
FileWriter(FileHandle &&file) : SaveFilter(nullptr), file(std::move(file))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2263,18 +2261,17 @@ struct FileWriter : SaveFilter {
|
|||
void Write(uint8_t *buf, size_t size) override
|
||||
{
|
||||
/* We're in the process of shutting down, i.e. in "failure" mode. */
|
||||
if (this->file == nullptr) return;
|
||||
if (!this->file.has_value()) return;
|
||||
|
||||
if (fwrite(buf, 1, size, this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
|
||||
if (fwrite(buf, 1, size, *this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
|
||||
}
|
||||
|
||||
void Finish() override
|
||||
{
|
||||
if (this->file != nullptr) {
|
||||
_game_session_stats.savegame_size = ftell(this->file);
|
||||
fclose(this->file);
|
||||
if (this->file.has_value()) {
|
||||
_game_session_stats.savegame_size = ftell(*this->file);
|
||||
this->file.reset();
|
||||
}
|
||||
this->file = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3144,14 +3141,14 @@ SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop,
|
|||
default: NOT_REACHED();
|
||||
}
|
||||
|
||||
FILE *fh = (fop == SLO_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
|
||||
auto fh = (fop == SLO_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
|
||||
|
||||
/* Make it a little easier to load savegames from the console */
|
||||
if (fh == nullptr && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SAVE_DIR);
|
||||
if (fh == nullptr && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", BASE_DIR);
|
||||
if (fh == nullptr && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SCENARIO_DIR);
|
||||
if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SAVE_DIR);
|
||||
if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", BASE_DIR);
|
||||
if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SCENARIO_DIR);
|
||||
|
||||
if (fh == nullptr) {
|
||||
if (!fh.has_value()) {
|
||||
SlError(fop == SLO_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
|
||||
}
|
||||
|
||||
|
@ -3159,13 +3156,13 @@ SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop,
|
|||
Debug(desync, 1, "save: {:08x}; {:02x}; {}", TimerGameEconomy::date, TimerGameEconomy::date_fract, filename);
|
||||
if (!_settings_client.gui.threaded_saves) threaded = false;
|
||||
|
||||
return DoSave(std::make_shared<FileWriter>(fh), threaded);
|
||||
return DoSave(std::make_shared<FileWriter>(std::move(*fh)), threaded);
|
||||
}
|
||||
|
||||
/* LOAD game */
|
||||
assert(fop == SLO_LOAD || fop == SLO_CHECK);
|
||||
Debug(desync, 1, "load: {}", filename);
|
||||
return DoLoad(std::make_shared<FileReader>(fh), fop == SLO_CHECK);
|
||||
return DoLoad(std::make_shared<FileReader>(std::move(*fh)), fop == SLO_CHECK);
|
||||
} catch (...) {
|
||||
/* This code may be executed both for old and new save games. */
|
||||
ClearSaveLoadState();
|
||||
|
|
|
@ -124,8 +124,9 @@ static bool MakeBMPImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
default: return false;
|
||||
}
|
||||
|
||||
FILE *f = fopen(name, "wb");
|
||||
if (f == nullptr) return false;
|
||||
auto of = FileHandle::Open(name, "wb");
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
/* Each scanline must be aligned on a 32bit boundary */
|
||||
uint bytewidth = Align(w * bpp, 4); // bytes per line in file
|
||||
|
@ -156,7 +157,6 @@ static bool MakeBMPImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
|
||||
/* Write file header and info header */
|
||||
if (fwrite(&bfh, sizeof(bfh), 1, f) != 1 || fwrite(&bih, sizeof(bih), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,6 @@ static bool MakeBMPImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
}
|
||||
/* Write the palette */
|
||||
if (fwrite(rq, sizeof(rq), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -208,13 +207,11 @@ static bool MakeBMPImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
}
|
||||
/* Write to file */
|
||||
if (fwrite(line.data(), bytewidth, 1, f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (h != 0);
|
||||
|
||||
fclose(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -259,7 +256,6 @@ static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message)
|
|||
static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
|
||||
{
|
||||
png_color rq[256];
|
||||
FILE *f;
|
||||
uint i, y, n;
|
||||
uint maxlines;
|
||||
uint bpp = pixelformat / 8;
|
||||
|
@ -269,26 +265,24 @@ static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
/* only implemented for 8bit and 32bit images so far. */
|
||||
if (pixelformat != 8 && pixelformat != 32) return false;
|
||||
|
||||
f = fopen(name, "wb");
|
||||
if (f == nullptr) return false;
|
||||
auto of = FileHandle::Open(name, "wb");
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, const_cast<char *>(name), png_my_error, png_my_warning);
|
||||
|
||||
if (png_ptr == nullptr) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (info_ptr == nullptr) {
|
||||
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -388,7 +382,6 @@ static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
png_write_end(png_ptr, info_ptr);
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
#endif /* WITH_PNG */
|
||||
|
@ -432,7 +425,6 @@ static_assert(sizeof(PcxHeader) == 128);
|
|||
*/
|
||||
static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
|
||||
{
|
||||
FILE *f;
|
||||
uint maxlines;
|
||||
uint y;
|
||||
PcxHeader pcx;
|
||||
|
@ -444,8 +436,9 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
}
|
||||
if (pixelformat != 8 || w == 0) return false;
|
||||
|
||||
f = fopen(name, "wb");
|
||||
if (f == nullptr) return false;
|
||||
auto of = FileHandle::Open(name, "wb");
|
||||
if (!of.has_value()) return false;
|
||||
auto &f = *of;
|
||||
|
||||
memset(&pcx, 0, sizeof(pcx));
|
||||
|
||||
|
@ -466,7 +459,6 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
|
||||
/* write pcx header */
|
||||
if (fwrite(&pcx, sizeof(pcx), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -500,12 +492,10 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
if (ch != runchar || runcount >= 0x3f) {
|
||||
if (runcount > 1 || (runchar & 0xC0) == 0xC0) {
|
||||
if (fputc(0xC0 | runcount, f) == EOF) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fputc(runchar, f) == EOF) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
runcount = 0;
|
||||
|
@ -517,12 +507,10 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
/* write remaining bytes.. */
|
||||
if (runcount > 1 || (runchar & 0xC0) == 0xC0) {
|
||||
if (fputc(0xC0 | runcount, f) == EOF) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (fputc(runchar, f) == EOF) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -530,7 +518,6 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
|
||||
/* write 8-bit colour palette */
|
||||
if (fputc(12, f) == EOF) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -544,8 +531,6 @@ static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *user
|
|||
}
|
||||
success = fwrite(tmp, sizeof(tmp), 1, f) == 1;
|
||||
|
||||
fclose(f);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
|
@ -168,11 +168,11 @@ struct ScriptFileChecksumCreator : FileScanner {
|
|||
size_t len, size;
|
||||
|
||||
/* Open the file ... */
|
||||
FILE *f = FioFOpenFile(filename, "rb", this->dir, &size);
|
||||
if (f == nullptr) return false;
|
||||
auto f = FioFOpenFile(filename, "rb", this->dir, &size);
|
||||
if (!f.has_value()) return false;
|
||||
|
||||
/* ... calculate md5sum... */
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, f)) != 0 && size != 0) {
|
||||
while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
|
||||
size -= len;
|
||||
checksum.Append(buffer, len);
|
||||
}
|
||||
|
@ -180,8 +180,6 @@ struct ScriptFileChecksumCreator : FileScanner {
|
|||
MD5Hash tmp_md5sum;
|
||||
checksum.Finish(tmp_md5sum);
|
||||
|
||||
FioFCloseFile(f);
|
||||
|
||||
/* ... and xor it to the overall md5sum. */
|
||||
this->md5sum ^= tmp_md5sum;
|
||||
|
||||
|
|
|
@ -548,12 +548,12 @@ void Squirrel::Initialize()
|
|||
|
||||
class SQFile {
|
||||
private:
|
||||
FILE *file;
|
||||
FileHandle file;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
|
||||
public:
|
||||
SQFile(FILE *file, size_t size) : file(file), size(size), pos(0) {}
|
||||
SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0) {}
|
||||
|
||||
size_t Read(void *buf, size_t elemsize, size_t count)
|
||||
{
|
||||
|
@ -622,40 +622,37 @@ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool
|
|||
{
|
||||
ScriptAllocatorScope alloc_scope(this);
|
||||
|
||||
FILE *file;
|
||||
std::optional<FileHandle> file = std::nullopt;
|
||||
size_t size;
|
||||
if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
|
||||
file = FioFOpenFile(filename, "rb", AI_DIR, &size);
|
||||
if (file == nullptr) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
|
||||
if (!file.has_value()) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
|
||||
} else if (strncmp(this->GetAPIName(), "GS", 2) == 0) {
|
||||
file = FioFOpenFile(filename, "rb", GAME_DIR, &size);
|
||||
if (file == nullptr) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
|
||||
if (!file.has_value()) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
|
||||
} else {
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
if (file == nullptr) {
|
||||
if (!file.has_value()) {
|
||||
return sq_throwerror(vm, "cannot open the file");
|
||||
}
|
||||
unsigned short bom = 0;
|
||||
if (size >= 2) {
|
||||
[[maybe_unused]] size_t sr = fread(&bom, 1, sizeof(bom), file);
|
||||
if (fread(&bom, 1, sizeof(bom), *file) != sizeof(bom)) return sq_throwerror(vm, "cannot read the file");;
|
||||
}
|
||||
|
||||
SQLEXREADFUNC func;
|
||||
switch (bom) {
|
||||
case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
|
||||
if (fseek(file, -2, SEEK_CUR) < 0) {
|
||||
FioFCloseFile(file);
|
||||
if (fseek(*file, -2, SEEK_CUR) < 0) {
|
||||
return sq_throwerror(vm, "cannot seek the file");
|
||||
}
|
||||
|
||||
SQFile f(file, size);
|
||||
SQFile f(std::move(*file), size);
|
||||
if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
|
||||
FioFCloseFile(file);
|
||||
return SQ_OK;
|
||||
}
|
||||
FioFCloseFile(file);
|
||||
return sq_throwerror(vm, "Couldn't read bytecode");
|
||||
}
|
||||
case 0xFFFE:
|
||||
|
@ -673,12 +670,10 @@ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool
|
|||
case 0xEFBB: { // UTF-8 on big-endian machine
|
||||
/* Similarly, check the file is actually big enough to finish checking BOM */
|
||||
if (size < 3) {
|
||||
FioFCloseFile(file);
|
||||
return sq_throwerror(vm, "I/O error");
|
||||
}
|
||||
unsigned char uc;
|
||||
if (fread(&uc, 1, sizeof(uc), file) != sizeof(uc) || uc != 0xBF) {
|
||||
FioFCloseFile(file);
|
||||
if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
|
||||
return sq_throwerror(vm, "Unrecognized encoding");
|
||||
}
|
||||
func = _io_file_lexfeed_UTF8;
|
||||
|
@ -688,19 +683,16 @@ SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool
|
|||
default: // ASCII
|
||||
func = _io_file_lexfeed_ASCII;
|
||||
/* Account for when we might not have fread'd earlier */
|
||||
if (size >= 2 && fseek(file, -2, SEEK_CUR) < 0) {
|
||||
FioFCloseFile(file);
|
||||
if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
|
||||
return sq_throwerror(vm, "cannot seek the file");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SQFile f(file, size);
|
||||
SQFile f(std::move(*file), size);
|
||||
if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
|
||||
FioFCloseFile(file);
|
||||
return SQ_OK;
|
||||
}
|
||||
FioFCloseFile(file);
|
||||
return SQ_ERROR;
|
||||
}
|
||||
|
||||
|
|
|
@ -156,17 +156,17 @@ struct SettingsIniFile : IniLoadFile {
|
|||
{
|
||||
}
|
||||
|
||||
FILE *OpenFile(const std::string &filename, Subdirectory, size_t *size) override
|
||||
std::optional<FileHandle> OpenFile(const std::string &filename, Subdirectory, size_t *size) override
|
||||
{
|
||||
/* Open the text file in binary mode to prevent end-of-line translations
|
||||
* done by ftell() and friends, as defined by K&R. */
|
||||
FILE *in = fopen(filename.c_str(), "rb");
|
||||
if (in == nullptr) return nullptr;
|
||||
auto in = FileHandle::Open(filename, "rb");
|
||||
if (!in.has_value()) return in;
|
||||
|
||||
fseek(in, 0L, SEEK_END);
|
||||
*size = ftell(in);
|
||||
fseek(*in, 0L, SEEK_END);
|
||||
*size = ftell(*in);
|
||||
fseek(*in, 0L, SEEK_SET); // Seek back to the start of the file.
|
||||
|
||||
fseek(in, 0L, SEEK_SET); // Seek back to the start of the file.
|
||||
return in;
|
||||
}
|
||||
|
||||
|
@ -327,21 +327,19 @@ static void AppendFile(const char *fname, FILE *out_fp)
|
|||
{
|
||||
if (fname == nullptr) return;
|
||||
|
||||
FILE *in_fp = fopen(fname, "r");
|
||||
if (in_fp == nullptr) {
|
||||
auto in_fp = FileHandle::Open(fname, "r");
|
||||
if (!in_fp.has_value()) {
|
||||
FatalError("Cannot open file {} for copying", fname);
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
size_t length;
|
||||
do {
|
||||
length = fread(buffer, 1, lengthof(buffer), in_fp);
|
||||
length = fread(buffer, 1, lengthof(buffer), *in_fp);
|
||||
if (fwrite(buffer, 1, length, out_fp) != length) {
|
||||
FatalError("Cannot copy file");
|
||||
}
|
||||
} while (length == lengthof(buffer));
|
||||
|
||||
fclose(in_fp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -352,12 +350,11 @@ static void AppendFile(const char *fname, FILE *out_fp)
|
|||
*/
|
||||
static bool CompareFiles(const char *n1, const char *n2)
|
||||
{
|
||||
FILE *f2 = fopen(n2, "rb");
|
||||
if (f2 == nullptr) return false;
|
||||
auto f2 = FileHandle::Open(n2, "rb");
|
||||
if (!f2.has_value()) return false;
|
||||
|
||||
FILE *f1 = fopen(n1, "rb");
|
||||
if (f1 == nullptr) {
|
||||
fclose(f2);
|
||||
auto f1 = FileHandle::Open(n1, "rb");
|
||||
if (!f1.has_value()) {
|
||||
FatalError("can't open {}", n1);
|
||||
}
|
||||
|
||||
|
@ -365,18 +362,14 @@ static bool CompareFiles(const char *n1, const char *n2)
|
|||
do {
|
||||
char b1[4096];
|
||||
char b2[4096];
|
||||
l1 = fread(b1, 1, sizeof(b1), f1);
|
||||
l2 = fread(b2, 1, sizeof(b2), f2);
|
||||
l1 = fread(b1, 1, sizeof(b1), *f1);
|
||||
l2 = fread(b2, 1, sizeof(b2), *f2);
|
||||
|
||||
if (l1 != l2 || memcmp(b1, b2, l1) != 0) {
|
||||
fclose(f2);
|
||||
fclose(f1);
|
||||
return false;
|
||||
}
|
||||
} while (l1 != 0);
|
||||
|
||||
fclose(f2);
|
||||
fclose(f1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -480,15 +473,15 @@ int CDECL main(int argc, char *argv[])
|
|||
} else {
|
||||
static const char * const tmp_output = "tmp2.xxx";
|
||||
|
||||
FILE *fp = fopen(tmp_output, "w");
|
||||
if (fp == nullptr) {
|
||||
auto fp = FileHandle::Open(tmp_output, "w");
|
||||
if (!fp.has_value()) {
|
||||
FatalError("Cannot open file {}", tmp_output);
|
||||
}
|
||||
AppendFile(before_file, fp);
|
||||
_stored_output.Write(fp);
|
||||
_post_amble_output.Write(fp);
|
||||
AppendFile(after_file, fp);
|
||||
fclose(fp);
|
||||
AppendFile(before_file, *fp);
|
||||
_stored_output.Write(*fp);
|
||||
_post_amble_output.Write(*fp);
|
||||
AppendFile(after_file, *fp);
|
||||
fp.reset();
|
||||
|
||||
std::error_code error_code;
|
||||
if (CompareFiles(tmp_output, output_file)) {
|
||||
|
@ -502,3 +495,16 @@ int CDECL main(int argc, char *argv[])
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified FileHandle::Open which ignores OTTD2FS. Required as settingsgen does not include all of the fileio system.
|
||||
* @param filename UTF-8 encoded filename to open.
|
||||
* @param mode Mode to open file.
|
||||
* @return FileHandle, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<FileHandle> FileHandle::Open(const std::string &filename, const std::string &mode)
|
||||
{
|
||||
auto f = fopen(filename.c_str(), mode.c_str());
|
||||
if (f == nullptr) return std::nullopt;
|
||||
return FileHandle(f);
|
||||
}
|
||||
|
|
|
@ -35,22 +35,19 @@ static const std::initializer_list<std::array<uint8_t, 32>> _public_keys_v1 = {
|
|||
*/
|
||||
static std::string CalculateHashV1(const std::string &filename)
|
||||
{
|
||||
FILE *f = FioFOpenFile(filename, "rb", NO_DIRECTORY);
|
||||
if (f == nullptr) {
|
||||
return "";
|
||||
}
|
||||
auto f = FioFOpenFile(filename, "rb", NO_DIRECTORY);
|
||||
if (!f.has_value()) return {};
|
||||
|
||||
std::array<uint8_t, 32> digest;
|
||||
crypto_blake2b_ctx ctx;
|
||||
crypto_blake2b_init(&ctx, digest.size());
|
||||
|
||||
while (!feof(f)) {
|
||||
while (!feof(*f)) {
|
||||
std::array<uint8_t, 1024> buf;
|
||||
size_t len = fread(buf.data(), 1, buf.size(), f);
|
||||
size_t len = fread(buf.data(), 1, buf.size(), *f);
|
||||
|
||||
crypto_blake2b_update(&ctx, buf.data(), len);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
crypto_blake2b_final(&ctx, digest.data());
|
||||
return FormatArrayAsHex(digest);
|
||||
|
@ -197,15 +194,14 @@ static bool ValidateSchema(const nlohmann::json &signatures, const std::string &
|
|||
static bool _ValidateSignatureFile(const std::string &filename)
|
||||
{
|
||||
size_t filesize;
|
||||
FILE *f = FioFOpenFile(filename, "rb", NO_DIRECTORY, &filesize);
|
||||
if (f == nullptr) {
|
||||
auto f = FioFOpenFile(filename, "rb", NO_DIRECTORY, &filesize);
|
||||
if (!f.has_value()) {
|
||||
Debug(misc, 0, "Failed to validate signature: file not found: {}", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string text(filesize, '\0');
|
||||
size_t len = fread(text.data(), filesize, 1, f);
|
||||
FioFCloseFile(f);
|
||||
size_t len = fread(text.data(), filesize, 1, *f);
|
||||
if (len != 1) {
|
||||
Debug(misc, 0, "Failed to validate signature: failed to read file: {}", filename);
|
||||
return false;
|
||||
|
|
|
@ -165,19 +165,13 @@
|
|||
#if !defined(STRGEN) && !defined(SETTINGSGEN)
|
||||
# if defined(_WIN32)
|
||||
char *getcwd(char *buf, size_t size);
|
||||
# include <io.h>
|
||||
# include <tchar.h>
|
||||
|
||||
# define fopen(file, mode) _wfopen(OTTD2FS(file).c_str(), _T(mode))
|
||||
|
||||
std::string FS2OTTD(const std::wstring &name);
|
||||
std::wstring OTTD2FS(const std::string &name);
|
||||
# elif defined(WITH_ICONV)
|
||||
# define fopen(file, mode) fopen(OTTD2FS(file).c_str(), mode)
|
||||
std::string FS2OTTD(const std::string &name);
|
||||
std::string OTTD2FS(const std::string &name);
|
||||
# else
|
||||
// no override of fopen() since no transformation is required of the filename
|
||||
template <typename T> std::string FS2OTTD(T name) { return name; }
|
||||
template <typename T> std::string OTTD2FS(T name) { return name; }
|
||||
# endif /* _WIN32 or WITH_ICONV */
|
||||
|
|
|
@ -2043,11 +2043,10 @@ const LanguageMetadata *GetLanguage(uint8_t newgrflangid)
|
|||
*/
|
||||
static bool GetLanguageFileHeader(const std::string &file, LanguagePackHeader *hdr)
|
||||
{
|
||||
FILE *f = fopen(file.c_str(), "rb");
|
||||
if (f == nullptr) return false;
|
||||
auto f = FileHandle::Open(file, "rb");
|
||||
if (!f.has_value()) return false;
|
||||
|
||||
size_t read = fread(hdr, sizeof(*hdr), 1, f);
|
||||
fclose(f);
|
||||
size_t read = fread(hdr, sizeof(*hdr), 1, *f);
|
||||
|
||||
bool ret = read == 1 && hdr->IsValid();
|
||||
|
||||
|
|
|
@ -742,15 +742,14 @@ static std::vector<char> Xunzip(std::span<char> input)
|
|||
|
||||
/* Get text from file */
|
||||
size_t filesize;
|
||||
FILE *handle = FioFOpenFile(textfile, "rb", dir, &filesize);
|
||||
if (handle == nullptr) return;
|
||||
auto handle = FioFOpenFile(textfile, "rb", dir, &filesize);
|
||||
if (!handle.has_value()) return;
|
||||
/* Early return on empty files. */
|
||||
if (filesize == 0) return;
|
||||
|
||||
std::vector<char> buf;
|
||||
buf.resize(filesize);
|
||||
size_t read = fread(buf.data(), 1, buf.size(), handle);
|
||||
fclose(handle);
|
||||
size_t read = fread(buf.data(), 1, buf.size(), *handle);
|
||||
|
||||
if (read != buf.size()) return;
|
||||
|
||||
|
|
|
@ -2168,15 +2168,13 @@ std::tuple<CommandCost, Money, TownID> CmdFoundTown(DoCommandFlag flags, TileInd
|
|||
Town *t;
|
||||
if (random_location) {
|
||||
t = CreateRandomTown(20, townnameparts, size, city, layout);
|
||||
if (t == nullptr) {
|
||||
cost = CommandCost(STR_ERROR_NO_SPACE_FOR_TOWN);
|
||||
} else {
|
||||
new_town = t->index;
|
||||
}
|
||||
if (t == nullptr) return { CommandCost(STR_ERROR_NO_SPACE_FOR_TOWN), 0, INVALID_TOWN };
|
||||
} else {
|
||||
t = new Town(tile);
|
||||
DoCreateTown(t, tile, townnameparts, size, city, layout, true);
|
||||
}
|
||||
|
||||
new_town = t->index;
|
||||
UpdateNearestTownForRoadTiles(false);
|
||||
old_generating_world.Restore();
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "core/backup_type.hpp"
|
||||
#include "core/geometry_func.hpp"
|
||||
#include "genworld.h"
|
||||
#include "fios.h"
|
||||
#include "stringfilter_type.h"
|
||||
#include "dropdown_func.h"
|
||||
#include "town_kdtree.h"
|
||||
|
@ -1108,6 +1109,7 @@ static constexpr NWidgetPart _nested_found_town_widgets[] = {
|
|||
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_TF_NEW_TOWN), SetDataTip(STR_FOUND_TOWN_NEW_TOWN_BUTTON, STR_FOUND_TOWN_NEW_TOWN_TOOLTIP), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_TF_RANDOM_TOWN), SetDataTip(STR_FOUND_TOWN_RANDOM_TOWN_BUTTON, STR_FOUND_TOWN_RANDOM_TOWN_TOOLTIP), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_TF_MANY_RANDOM_TOWNS), SetDataTip(STR_FOUND_TOWN_MANY_RANDOM_TOWNS, STR_FOUND_TOWN_RANDOM_TOWNS_TOOLTIP), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_TF_LOAD_FROM_FILE), SetDataTip(STR_FOUND_TOWN_LOAD_FROM_FILE, STR_FOUND_TOWN_LOAD_FROM_FILE_TOOLTIP), SetFill(1, 0),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_TF_EXPAND_ALL_TOWNS), SetDataTip(STR_FOUND_TOWN_EXPAND_ALL_TOWNS, STR_FOUND_TOWN_EXPAND_ALL_TOWNS_TOOLTIP), SetFill(1, 0),
|
||||
|
||||
/* Town name selection. */
|
||||
|
@ -1188,7 +1190,7 @@ public:
|
|||
void UpdateButtons(bool check_availability)
|
||||
{
|
||||
if (check_availability && _game_mode != GM_EDITOR) {
|
||||
this->SetWidgetsDisabledState(true, WID_TF_RANDOM_TOWN, WID_TF_MANY_RANDOM_TOWNS, WID_TF_EXPAND_ALL_TOWNS, WID_TF_SIZE_LARGE);
|
||||
this->SetWidgetsDisabledState(true, WID_TF_RANDOM_TOWN, WID_TF_MANY_RANDOM_TOWNS, WID_TF_LOAD_FROM_FILE, WID_TF_EXPAND_ALL_TOWNS, WID_TF_SIZE_LARGE);
|
||||
this->SetWidgetsDisabledState(_settings_game.economy.found_town != TF_CUSTOM_LAYOUT,
|
||||
WID_TF_LAYOUT_ORIGINAL, WID_TF_LAYOUT_BETTER, WID_TF_LAYOUT_GRID2, WID_TF_LAYOUT_GRID3, WID_TF_LAYOUT_RANDOM);
|
||||
if (_settings_game.economy.found_town != TF_CUSTOM_LAYOUT) town_layout = _settings_game.economy.town_layout;
|
||||
|
@ -1254,6 +1256,10 @@ public:
|
|||
break;
|
||||
}
|
||||
|
||||
case WID_TF_LOAD_FROM_FILE:
|
||||
ShowSaveLoadDialog(FT_TOWN_DATA, SLO_LOAD);
|
||||
break;
|
||||
|
||||
case WID_TF_EXPAND_ALL_TOWNS:
|
||||
for (Town *t : Town::Iterate()) {
|
||||
Command<CMD_EXPAND_TOWN>::Do(DC_EXEC, t->index, 0);
|
||||
|
@ -1274,6 +1280,11 @@ public:
|
|||
case WID_TF_LAYOUT_ORIGINAL: case WID_TF_LAYOUT_BETTER: case WID_TF_LAYOUT_GRID2:
|
||||
case WID_TF_LAYOUT_GRID3: case WID_TF_LAYOUT_RANDOM:
|
||||
this->town_layout = (TownLayout)(widget - WID_TF_LAYOUT_ORIGINAL);
|
||||
|
||||
/* If we are in the editor, sync the settings of the current game to the chosen layout,
|
||||
* so that importing towns from file uses the selected layout. */
|
||||
if (_game_mode == GM_EDITOR) _settings_game.economy.town_layout = this->town_layout;
|
||||
|
||||
this->UpdateButtons(false);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ enum TownFoundingWidgets : WidgetID {
|
|||
WID_TF_NEW_TOWN, ///< Create a new town.
|
||||
WID_TF_RANDOM_TOWN, ///< Randomly place a town.
|
||||
WID_TF_MANY_RANDOM_TOWNS, ///< Randomly place many towns.
|
||||
WID_TF_LOAD_FROM_FILE, ///< Load town data from file.
|
||||
WID_TF_EXPAND_ALL_TOWNS, ///< Make all towns grow slightly.
|
||||
WID_TF_TOWN_NAME_EDITBOX, ///< Editor for the town name.
|
||||
WID_TF_TOWN_NAME_RANDOM, ///< Generate a random town name.
|
||||
|
|
Loading…
Reference in New Issue