Allow client and server negotiate on savegame format if both are patched

This commit is contained in:
dP
2021-04-01 14:07:12 +03:00
parent de00ea9fe0
commit da3cfd2821
7 changed files with 197 additions and 43 deletions

View File

@@ -1499,8 +1499,8 @@ make_cflags_and_ldflags() {
CFLAGS="$CFLAGS -D$os" CFLAGS="$CFLAGS -D$os"
CFLAGS_BUILD="$CFLAGS_BUILD -D$os" CFLAGS_BUILD="$CFLAGS_BUILD -D$os"
CXXFLAGS="$CXXFLAGS -std=c++11" CXXFLAGS="$CXXFLAGS -std=c++17"
CXXFLAGS_BUILD="$CXXFLAGS_BUILD -std=c++11" CXXFLAGS_BUILD="$CXXFLAGS_BUILD -std=c++17"
if [ "$enable_debug" = "0" ]; then if [ "$enable_debug" = "0" ]; then
# No debug, add default stuff # No debug, add default stuff

View File

@@ -355,6 +355,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
p->Send_string(_settings_client.network.client_name); // Client name p->Send_string(_settings_client.network.client_name); // Client name
p->Send_uint8 (_network_join_as); // PlayAs p->Send_uint8 (_network_join_as); // PlayAs
p->Send_uint8 (NETLANG_ANY); // Language p->Send_uint8 (NETLANG_ANY); // Language
p->Send_uint8 (citymania::GetAvailableLoadFormats()); // Compressnion formats that we can decompress
my_client->SendPacket(p); my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY; return NETWORK_RECV_STATUS_OKAY;
} }

View File

@@ -592,7 +592,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap()
sent_packets = 4; // We start with trying 4 packets sent_packets = 4; // We start with trying 4 packets
/* Make a dump of the current game */ /* Make a dump of the current game */
if (SaveWithFilter(this->savegame, true) != SL_OK) usererror("network savedump failed"); if (SaveWithFilter(this->savegame, true, this->cm_preset) != SL_OK) usererror("network savedump failed");
} }
if (this->status == STATUS_MAP) { if (this->status == STATUS_MAP) {
@@ -908,9 +908,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet *p)
p->Recv_string(name, sizeof(name)); p->Recv_string(name, sizeof(name));
playas = (Owner)p->Recv_uint8(); playas = (Owner)p->Recv_uint8();
client_lang = (NetworkLanguage)p->Recv_uint8(); client_lang = (NetworkLanguage)p->Recv_uint8();
uint8 savegame_formats = p->CanReadFromPacket(1) ? p->Recv_uint8() : 23u /* assume non-modded has everything but zstd */;
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CONN_LOST; if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CONN_LOST;
/* Find common savegame compression format to use */
auto preset = citymania::FindCompatibleSavePreset("", savegame_formats);
if (!preset) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
this->cm_preset = *preset;
/* join another company does not affect these values */ /* join another company does not affect these values */
switch (playas) { switch (playas) {
case COMPANY_NEW_COMPANY: // New company case COMPANY_NEW_COMPANY: // New company

View File

@@ -12,6 +12,7 @@
#include "network_internal.h" #include "network_internal.h"
#include "core/tcp_listen.h" #include "core/tcp_listen.h"
#include "../saveload/saveload.h"
class ServerNetworkGameSocketHandler; class ServerNetworkGameSocketHandler;
/** Make the code look slightly nicer/simpler. */ /** Make the code look slightly nicer/simpler. */
@@ -72,6 +73,7 @@ public:
struct PacketWriter *savegame; ///< Writer used to write the savegame. struct PacketWriter *savegame; ///< Writer used to write the savegame.
NetworkAddress client_address; ///< IP-address of the client (so he can be banned) NetworkAddress client_address; ///< IP-address of the client (so he can be banned)
citymania::SavePreset cm_preset; ///< Preset to use for the savegame
ServerNetworkGameSocketHandler(SOCKET s); ServerNetworkGameSocketHandler(SOCKET s);
~ServerNetworkGameSocketHandler(); ~ServerNetworkGameSocketHandler();

View File

@@ -19,7 +19,7 @@
*/ */
bool IsReleasedVersion() bool IsReleasedVersion()
{ {
return HasBit(_openttd_newgrf_version, 19); return HasBit(_openttd_newgrf_version, 19);
} }
/** /**
@@ -35,7 +35,7 @@ bool IsReleasedVersion()
* *
* <modified> shows a "M", if the binary is made from modified source code. * <modified> shows a "M", if the binary is made from modified source code.
*/ */
const char _openttd_revision[] = "!!VERSION!!"; const char _openttd_revision[] = "1.10.3";
/** /**
* The text version of OpenTTD's build date. * The text version of OpenTTD's build date.
@@ -48,12 +48,12 @@ const char _openttd_build_date[] = __DATE__ " " __TIME__;
/** /**
* The git revision hash of this version. * The git revision hash of this version.
*/ */
const char _openttd_revision_hash[] = "!!GITHASH!!"; const char _openttd_revision_hash[] = "baf5bf29fa68c908e7033e58465562d22ec97a07";
/** /**
* The year of this version. * The year of this version.
*/ */
const char _openttd_revision_year[] = "!!YEAR!!"; const char _openttd_revision_year[] = "2020";
/** /**
* Let us know if current build was modified. This detection * Let us know if current build was modified. This detection
@@ -63,14 +63,14 @@ const char _openttd_revision_year[] = "!!YEAR!!";
* (compiling from sources without any version control software) * (compiling from sources without any version control software)
* and 2 is for modified revision. * and 2 is for modified revision.
*/ */
const byte _openttd_revision_modified = !!MODIFIED!!; const byte _openttd_revision_modified = 0;
/** /**
* Indicate whether this is a tagged version. * Indicate whether this is a tagged version.
* If this is non-0, then _openttd_revision is the name of the tag, * If this is non-0, then _openttd_revision is the name of the tag,
* and the version is likely a beta, release candidate, or real release. * and the version is likely a beta, release candidate, or real release.
*/ */
const byte _openttd_revision_tagged = !!ISTAG!!; const byte _openttd_revision_tagged = 1;
/** /**
* The NewGRF revision of OTTD: * The NewGRF revision of OTTD:
@@ -85,4 +85,6 @@ const byte _openttd_revision_tagged = !!ISTAG!!;
* final release will always have a lower version number than the released * final release will always have a lower version number than the released
* version, thus making comparisons on specific revisions easy. * version, thus making comparisons on specific revisions easy.
*/ */
const uint32 _openttd_newgrf_version = 1 << 28 | 10 << 24 | 0 << 20 | !!ISSTABLETAG!! << 19 | 28004; const uint32 _openttd_newgrf_version = 1 << 28 | 10 << 24 | 0 << 20 | 1 << 19 | 28004;
const char _citymania_version[] = "!!VERSION!! !!DATE!!";

View File

@@ -44,6 +44,7 @@
#include "../fios.h" #include "../fios.h"
#include "../error.h" #include "../error.h"
#include <atomic> #include <atomic>
#include <sstream>
#include "table/strings.h" #include "table/strings.h"
@@ -2392,35 +2393,35 @@ struct ZSTDSaveFilter : SaveFilter {
*******************************************/ *******************************************/
/** The format for a reader/writer type of a savegame */ /** The format for a reader/writer type of a savegame */
struct SaveLoadFormat { // struct SaveLoadFormat {
const char *name; ///< name of the compressor/decompressor (debug-only) // const char *name; ///< name of the compressor/decompressor (debug-only)
uint32 tag; ///< the 4-letter tag by which it is identified in the savegame // uint32 tag; ///< the 4-letter tag by which it is identified in the savegame
LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter. // LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter.
SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter. // SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter.
byte min_compression; ///< the minimum compression level of this format // byte min_compression; ///< the minimum compression level of this format
byte default_compression; ///< the default compression level of this format // byte default_compression; ///< the default compression level of this format
byte max_compression; ///< the maximum compression level of this format // byte max_compression; ///< the maximum compression level of this format
}; // };
/** The different saveload formats known/understood by OpenTTD. */ /** The different saveload formats known/understood by OpenTTD. */
static const SaveLoadFormat _saveload_formats[] = { static const citymania::SaveLoadFormat _saveload_formats[] = {
/* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
{0, "none", TO_BE32X('OTTN'), CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, citymania::CompressionMethod::None, 0, 0, 0},
#if defined(WITH_LZO) #if defined(WITH_LZO)
/* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */ /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
{"lzo", TO_BE32X('OTTD'), CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, 0, 0, 0}, {1, "lzo", TO_BE32X('OTTD'), CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, citymania::CompressionMethod::LZO, 0, 0, 0},
#else #else
{"lzo", TO_BE32X('OTTD'), nullptr, nullptr, 0, 0, 0}, {1, "lzo", TO_BE32X('OTTD'), nullptr, nullptr, citymania::CompressionMethod::LZO, 0, 0, 0},
#endif #endif
/* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
{"none", TO_BE32X('OTTN'), CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, 0, 0, 0},
#if defined(WITH_ZLIB) #if defined(WITH_ZLIB)
/* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is
* fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level
* 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */ * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */
{"zlib", TO_BE32X('OTTZ'), CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, 0, 6, 9}, {2, "zlib", TO_BE32X('OTTZ'), CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, citymania::CompressionMethod::Zlib, 0, 6, 9},
#else #else
{"zlib", TO_BE32X('OTTZ'), nullptr, nullptr, 0, 0, 0}, {2, "zlib", TO_BE32X('OTTZ'), nullptr, nullptr, citymania::CompressionMethod::Zlib, 0, 0, 0},
#endif #endif
#if defined(WITH_ZSTD) #if defined(WITH_ZSTD)
/* Zstd provides a decent compression rate at a very high compression/decompression speed. Compared to lzma level 2 /* Zstd provides a decent compression rate at a very high compression/decompression speed. Compared to lzma level 2
@@ -2429,9 +2430,9 @@ static const SaveLoadFormat _saveload_formats[] = {
* (compress + 10 MB/s download + decompress time), about 3x faster than lzma:2 and 1.5x than zlib:2 and lzo. * (compress + 10 MB/s download + decompress time), about 3x faster than lzma:2 and 1.5x than zlib:2 and lzo.
* As zstd has negative compression levels the values were increased by 100 moving zstd level range -100..22 into * As zstd has negative compression levels the values were increased by 100 moving zstd level range -100..22 into
* openttd 0..122. Also note that value 100 mathes zstd level 0 which is a special value for default level 3 (openttd 103) */ * openttd 0..122. Also note that value 100 mathes zstd level 0 which is a special value for default level 3 (openttd 103) */
{"zstd", TO_BE32X('OTTS'), CreateLoadFilter<ZSTDLoadFilter>, CreateSaveFilter<ZSTDSaveFilter>, 0, 101, 122}, {3, "zstd", TO_BE32X('OTTS'), CreateLoadFilter<ZSTDLoadFilter>, CreateSaveFilter<ZSTDSaveFilter>, citymania::CompressionMethod::ZSTD, 0, 101, 122},
#else #else
{"zstd", TO_BE32X('OTTS'), nullptr, nullptr, 0, 0, 0}, {3, "zstd", TO_BE32X('OTTS'), nullptr, nullptr, citymania::CompressionMethod::ZSTD, 0, 0, 0},
#endif #endif
#if defined(WITH_LIBLZMA) #if defined(WITH_LIBLZMA)
/* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves. /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
@@ -2439,12 +2440,111 @@ static const SaveLoadFormat _saveload_formats[] = {
* The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50% * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50%
* slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much. * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much.
* It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */ * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */
{"lzma", TO_BE32X('OTTX'), CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, 0, 2, 9}, {4, "lzma", TO_BE32X('OTTX'), CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, citymania::CompressionMethod::LZMA, 0, 2, 9},
#else #else
{"lzma", TO_BE32X('OTTX'), nullptr, nullptr, 0, 0, 0}, {4, "lzma", TO_BE32X('OTTX'), nullptr, nullptr, citymania::CompressionMethod::LZMA, 0, 0, 0},
#endif #endif
}; };
namespace citymania { // citymania savegame format handling
static const std::string DEFAULT_NETWORK_SAVEGAME_COMPRESSION = "zstd:1 zlib:2 lzma:0 lzo:0";
/**
* Parses the savegame format and compression level string ("format:[compression_level]").
* @param str String to parse
* @return Parsest SavePreset or std::nullopt
*/
static std::optional<SavePreset> ParseSavePreset(const std::string &str)
{
auto delimiter_pos = str.find(':');
auto format = (delimiter_pos != std::string::npos ? str.substr(0, delimiter_pos) : str);
for (auto &slf : _saveload_formats) {
if (slf.init_write != nullptr && format == slf.name) {
/* If compression level wasn't specified use the default one */
if (delimiter_pos == std::string::npos) return SavePreset{&slf, slf.default_compression};
auto level_str = str.substr(delimiter_pos + 1);
int level;
try{
level = stoi(level_str);
} catch(const std::exception &e) {
/* Can't parse compression level, set it out ouf bounds to fail later */
level = (int)slf.max_compression + 1;
}
if (level != Clamp<int>(level, slf.min_compression, slf.max_compression)) {
/* Invalid compression level, show the error and use default level */
SetDParamStr(0, level_str.c_str());
ShowErrorMessage(STR_CONFIG_ERROR, STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, WL_CRITICAL);
return SavePreset{&slf, slf.default_compression};
}
return SavePreset{&slf, (byte)level};
}
}
SetDParamStr(0, str.c_str());
ShowErrorMessage(STR_CONFIG_ERROR, STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, WL_CRITICAL);
return {};
}
static_assert(lengthof(_saveload_formats) <= 8); // uint8 is used for the bitset of format ids
/**
* Finds the best savegame preset to use in network game based on server settings and client capabilies.
* @param server_formats String of space-separated format descriptions in form format[:compression_level] acceptable for the server (listed first take priority).
* @param client_formats Bitset of savegame formats available to the client (as returned by GetAvailableLoadFormats)
* @return SavePreset that satisfies both server and client or std::nullopt
*/
std::optional<SavePreset> FindCompatibleSavePreset(const std::string &server_formats, uint8 client_formats)
{
std::istringstream iss(server_formats.empty() ? DEFAULT_NETWORK_SAVEGAME_COMPRESSION : server_formats);
std::string preset_str;
while (std::getline(iss, preset_str, ' ')) {
auto preset = ParseSavePreset(preset_str);
if (!preset) continue;
if ((client_formats & (1 << preset->format->id)) != 0) return preset;
}
return {};
}
/**
* Return the bitset of savegame formats that this game instance can load
* @return bitset of available savegame formats
*/
uint8 GetAvailableLoadFormats()
{
return 3;
uint8 res = 0;
for(auto &slf : _saveload_formats) {
if (slf.init_load != nullptr) {
res &= (1 << slf.id);
}
}
return res;
}
/**
* Return the save preset to use for local game saves.
* @return SavePreset to use
*/
static SavePreset GetLocalSavePreset()
{
if (!StrEmpty(_savegame_format)) {
auto config = ParseSavePreset(_savegame_format);
if (config) return *config;
}
const citymania::SaveLoadFormat *def = lastof(_saveload_formats);
/* find default savegame format, the highest one with which files can be written */
while (!def->init_write) def--;
return {def, def->default_compression};
}
} // namespace citymania
/** /**
* Return the savegameformat of the game. Whether it was created with ZLIB compression * Return the savegameformat of the game. Whether it was created with ZLIB compression
* uncompressed, or another type * uncompressed, or another type
@@ -2452,6 +2552,8 @@ static const SaveLoadFormat _saveload_formats[] = {
* @param compression_level Output for telling what compression level we want. * @param compression_level Output for telling what compression level we want.
* @return Pointer to SaveLoadFormat struct giving all characteristics of this type of savegame * @return Pointer to SaveLoadFormat struct giving all characteristics of this type of savegame
*/ */
#if 0
Citymania uses other way
static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level) static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level)
{ {
const SaveLoadFormat *def = lastof(_saveload_formats); const SaveLoadFormat *def = lastof(_saveload_formats);
@@ -2499,6 +2601,8 @@ static const SaveLoadFormat *GetSavegameFormat(char *s, byte *compression_level)
return def; return def;
} }
#endif
/* actual loader/saver function */ /* actual loader/saver function */
void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings); void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
extern bool AfterLoadGame(); extern bool AfterLoadGame();
@@ -2586,17 +2690,17 @@ static void SaveFileError()
* We have written the whole game into memory, _memory_savegame, now find * We have written the whole game into memory, _memory_savegame, now find
* and appropriate compressor and start writing to file. * and appropriate compressor and start writing to file.
*/ */
static SaveOrLoadResult SaveFileToDisk(bool threaded) static SaveOrLoadResult SaveFileToDisk(bool threaded, citymania::SavePreset preset)
{ {
try { try {
byte compression; // byte compression;
const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression); // const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression);
/* We have written our stuff to memory, now write it to file! */ /* We have written our stuff to memory, now write it to file! */
uint32 hdr[2] = { fmt->tag, TO_BE32(SAVEGAME_VERSION << 16) }; uint32 hdr[2] = { preset.format->tag, TO_BE32(SAVEGAME_VERSION << 16) };
_sl.sf->Write((byte*)hdr, sizeof(hdr)); _sl.sf->Write((byte*)hdr, sizeof(hdr));
_sl.sf = fmt->init_write(_sl.sf, compression); _sl.sf = preset.format->init_write(_sl.sf, preset.compression_level);
_sl.dumper->Flush(_sl.sf); _sl.dumper->Flush(_sl.sf);
ClearSaveLoadState(); ClearSaveLoadState();
@@ -2644,7 +2748,7 @@ void WaitTillSaved()
* @param threaded Whether to try to perform the saving asynchronously. * @param threaded Whether to try to perform the saving asynchronously.
* @return Return the result of the action. #SL_OK or #SL_ERROR * @return Return the result of the action. #SL_OK or #SL_ERROR
*/ */
static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded) static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded, citymania::SavePreset preset)
{ {
assert(!_sl.saveinprogress); assert(!_sl.saveinprogress);
@@ -2658,10 +2762,10 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded)
SaveFileStart(); SaveFileStart();
if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) { if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true, std::move(preset))) {
if (threaded) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode..."); if (threaded) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode...");
SaveOrLoadResult result = SaveFileToDisk(false); SaveOrLoadResult result = SaveFileToDisk(false, preset);
SaveFileDone(); SaveFileDone();
return result; return result;
@@ -2676,11 +2780,11 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded)
* @param threaded Whether to try to perform the saving asynchronously. * @param threaded Whether to try to perform the saving asynchronously.
* @return Return the result of the action. #SL_OK or #SL_ERROR * @return Return the result of the action. #SL_OK or #SL_ERROR
*/ */
SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded) SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded, citymania::SavePreset preset)
{ {
try { try {
_sl.action = SLA_SAVE; _sl.action = SLA_SAVE;
return DoSave(writer, threaded); return DoSave(writer, threaded, preset);
} catch (...) { } catch (...) {
ClearSaveLoadState(); ClearSaveLoadState();
return SL_ERROR; return SL_ERROR;
@@ -2708,7 +2812,7 @@ static SaveOrLoadResult DoLoad(LoadFilter *reader, bool load_check)
if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
/* see if we have any loader for this type. */ /* see if we have any loader for this type. */
const SaveLoadFormat *fmt = _saveload_formats; const citymania::SaveLoadFormat *fmt = _saveload_formats;
for (;;) { for (;;) {
/* No loader found, treat as version 0 and use LZO format */ /* No loader found, treat as version 0 and use LZO format */
if (fmt == endof(_saveload_formats)) { if (fmt == endof(_saveload_formats)) {
@@ -2920,7 +3024,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, SaveLoadOperation fop, Detaile
DEBUG(desync, 1, "save: %08x; %02x; %s", _date, _date_fract, filename); DEBUG(desync, 1, "save: %08x; %02x; %s", _date, _date_fract, filename);
if (_network_server || !_settings_client.gui.threaded_saves) threaded = false; if (_network_server || !_settings_client.gui.threaded_saves) threaded = false;
return DoSave(new FileWriter(fh), threaded); return DoSave(new FileWriter(fh), threaded, citymania::GetLocalSavePreset());
} }
/* LOAD game */ /* LOAD game */

View File

@@ -12,6 +12,9 @@
#include "../fileio_type.h" #include "../fileio_type.h"
#include "../strings_type.h" #include "../strings_type.h"
#include "saveload_filter.h"
#include <optional>
#include <string>
/** SaveLoad versions /** SaveLoad versions
* Previous savegame versions, the trunk revision where they were * Previous savegame versions, the trunk revision where they were
@@ -337,6 +340,42 @@ enum SavegameType {
SGT_INVALID = 0xFF, ///< broken savegame (used internally) SGT_INVALID = 0xFF, ///< broken savegame (used internally)
}; };
namespace citymania {
enum class CompressionMethod : uint8 {
None = 0u,
LZO = 1u,
Zlib = 2u,
ZSTD = 3u,
LZMA = 4u,
};
/** The format for a reader/writer type of a savegame */
struct SaveLoadFormat {
uint8 id; ///< unique integer id of this savegame format (olny used for networkking so is not guaranteed to be preserved between versions)
const char *name; ///< name of the compressor/decompressor (debug-only)
uint32 tag; ///< the 4-letter tag by which it is identified in the savegame
LoadFilter *(*init_load)(LoadFilter *chain); ///< Constructor for the load filter.
SaveFilter *(*init_write)(SaveFilter *chain, byte compression); ///< Constructor for the save filter.
CompressionMethod method; ///< compression method used in this format
byte min_compression; ///< the minimum compression level of this format
byte default_compression; ///< the default compression level of this format
byte max_compression; ///< the maximum compression level of this format
};
/** The preset to use for generating savegames */
struct SavePreset {
const SaveLoadFormat *format; ///< savegame format to use
byte compression_level; ///< compression level to use
};
std::optional<SavePreset> FindCompatibleSavePreset(const std::string &server_formats, uint8 client_format_flags);
uint8 GetAvailableLoadFormats();
} // namespace citymania
extern FileToSaveLoad _file_to_saveload; extern FileToSaveLoad _file_to_saveload;
void GenerateDefaultSaveName(char *buf, const char *last); void GenerateDefaultSaveName(char *buf, const char *last);
@@ -347,7 +386,7 @@ void WaitTillSaved();
void ProcessAsyncSaveFinish(); void ProcessAsyncSaveFinish();
void DoExitSave(); void DoExitSave();
SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded); SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded, citymania::SavePreset preset);
SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader); SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader);
typedef void ChunkSaveLoadProc(); typedef void ChunkSaveLoadProc();