From 7dd5fd6ed497e1da40c13075d6e37b54ab12a082 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 14 Jun 2021 10:05:30 +0200 Subject: [PATCH] Feature: framework to make savegames self-descriptive We won't be able to make it fully self-descriptive (looking at you MAP-chunks), but anything else can. With this framework, we can add headers for each chunk explaining how each chunk looks like in detail. They also will all be tables, making it a lot easier to read in external tooling, and opening the way to consider a database (like SQLite) to use as savegame format. Lastly, with the headers in the savegame, you can freely add fields without needing a savegame version bump; older versions of OpenTTD will simply ignore the new field. This also means we can remove all the SLE_CONDNULL, as they are irrelevant. The next few commits will start using this framework. --- src/core/span_type.hpp | 2 + src/saveload/ai_sl.cpp | 8 +- src/saveload/animated_tile_sl.cpp | 2 +- src/saveload/cheat_sl.cpp | 1 - src/saveload/company_sl.cpp | 12 +- src/saveload/depot_sl.cpp | 2 +- src/saveload/game_sl.cpp | 14 +- src/saveload/gamelog_sl.cpp | 26 +- src/saveload/industry_sl.cpp | 4 +- src/saveload/linkgraph_sl.cpp | 8 +- src/saveload/map_sl.cpp | 4 +- src/saveload/misc_sl.cpp | 44 ++-- src/saveload/saveload.cpp | 423 +++++++++++++++++++++++++++--- src/saveload/saveload.h | 154 ++++++++--- src/saveload/station_sl.cpp | 42 +-- src/saveload/town_sl.cpp | 6 +- src/saveload/vehicle_sl.cpp | 40 +-- src/script/script_instance.cpp | 2 +- src/settings.cpp | 2 +- src/table/settings.h.preamble | 12 +- 20 files changed, 620 insertions(+), 188 deletions(-) diff --git a/src/core/span_type.hpp b/src/core/span_type.hpp index 394b9ef38c..03bc678b7e 100644 --- a/src/core/span_type.hpp +++ b/src/core/span_type.hpp @@ -73,6 +73,8 @@ public: typedef size_t size_type; typedef std::ptrdiff_t difference_type; + constexpr span() noexcept : first(nullptr), last(nullptr) {} + constexpr span(pointer data_in, size_t size_in) : first(data_in), last(data_in + size_in) {} template::value), int>::type = 0> diff --git a/src/saveload/ai_sl.cpp b/src/saveload/ai_sl.cpp index 49221ef300..1c44ef7323 100644 --- a/src/saveload/ai_sl.cpp +++ b/src/saveload/ai_sl.cpp @@ -26,10 +26,10 @@ static std::string _ai_saveload_settings; static bool _ai_saveload_is_random; static const SaveLoad _ai_company[] = { - SLEG_SSTR(_ai_saveload_name, SLE_STR), - SLEG_SSTR(_ai_saveload_settings, SLE_STR), - SLEG_CONDVAR(_ai_saveload_version, SLE_UINT32, SLV_108, SL_MAX_VERSION), - SLEG_CONDVAR(_ai_saveload_is_random, SLE_BOOL, SLV_136, SL_MAX_VERSION), + SLEG_SSTR("name", _ai_saveload_name, SLE_STR), + SLEG_SSTR("settings", _ai_saveload_settings, SLE_STR), + SLEG_CONDVAR("version", _ai_saveload_version, SLE_UINT32, SLV_108, SL_MAX_VERSION), + SLEG_CONDVAR("is_random", _ai_saveload_is_random, SLE_BOOL, SLV_136, SL_MAX_VERSION), }; static void SaveReal_AIPL(int *index_ptr) diff --git a/src/saveload/animated_tile_sl.cpp b/src/saveload/animated_tile_sl.cpp index 7f05eaeeb7..2e0666d4a2 100644 --- a/src/saveload/animated_tile_sl.cpp +++ b/src/saveload/animated_tile_sl.cpp @@ -19,7 +19,7 @@ extern std::vector _animated_tiles; static const SaveLoad _animated_tile_desc[] = { - SLEG_VECTOR(_animated_tiles, SLE_UINT32), + SLEG_VECTOR("tiles", _animated_tiles, SLE_UINT32), }; /** diff --git a/src/saveload/cheat_sl.cpp b/src/saveload/cheat_sl.cpp index 51df50314e..6c6bab4ddb 100644 --- a/src/saveload/cheat_sl.cpp +++ b/src/saveload/cheat_sl.cpp @@ -74,7 +74,6 @@ static void Load_CHTS() if (!IsSavegameVersionBefore(SLV_RIFF_TO_ARRAY) && SlIterateArray() != -1) SlErrorCorrupt("Too many CHTS entries"); } -/** Chunk handlers related to cheats. */ static const ChunkHandler cheat_chunk_handlers[] = { { 'CHTS', Save_CHTS, Load_CHTS, nullptr, nullptr, CH_ARRAY }, }; diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp index 880fc9e891..7abec80418 100644 --- a/src/saveload/company_sl.cpp +++ b/src/saveload/company_sl.cpp @@ -294,7 +294,7 @@ public: SLE_CONDNULL(32, SL_MIN_VERSION, SLV_107), SLE_CONDNULL(64, SLV_2, SLV_107), - SLEG_STRUCTLIST(SlCompanyOldAIBuildRec), + SLEG_STRUCTLIST("build_rec", SlCompanyOldAIBuildRec), }; void GenericSaveLoad(CompanyProperties *c) const @@ -513,11 +513,11 @@ static const SaveLoad _company_desc[] = { SLE_CONDVAR(CompanyProperties, terraform_limit, SLE_UINT32, SLV_156, SL_MAX_VERSION), SLE_CONDVAR(CompanyProperties, clear_limit, SLE_UINT32, SLV_156, SL_MAX_VERSION), SLE_CONDVAR(CompanyProperties, tree_limit, SLE_UINT32, SLV_175, SL_MAX_VERSION), - SLEG_STRUCT(SlCompanySettings), - SLEG_CONDSTRUCT(SlCompanyOldAI, SL_MIN_VERSION, SLV_107), - SLEG_STRUCT(SlCompanyEconomy), - SLEG_STRUCTLIST(SlCompanyOldEconomy), - SLEG_CONDSTRUCTLIST(SlCompanyLiveries, SLV_34, SL_MAX_VERSION), + SLEG_STRUCT("settings", SlCompanySettings), + SLEG_CONDSTRUCT("old_ai", SlCompanyOldAI, SL_MIN_VERSION, SLV_107), + SLEG_STRUCT("cur_economy", SlCompanyEconomy), + SLEG_STRUCTLIST("old_economy", SlCompanyOldEconomy), + SLEG_CONDSTRUCTLIST("liveries", SlCompanyLiveries, SLV_34, SL_MAX_VERSION), }; static void Save_PLYR() diff --git a/src/saveload/depot_sl.cpp b/src/saveload/depot_sl.cpp index da195416bd..6af08e718d 100644 --- a/src/saveload/depot_sl.cpp +++ b/src/saveload/depot_sl.cpp @@ -20,7 +20,7 @@ static TownID _town_index; static const SaveLoad _depot_desc[] = { SLE_CONDVAR(Depot, xy, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_6), SLE_CONDVAR(Depot, xy, SLE_UINT32, SLV_6, SL_MAX_VERSION), - SLEG_CONDVAR(_town_index, SLE_UINT16, SL_MIN_VERSION, SLV_141), + SLEG_CONDVAR("town_index", _town_index, SLE_UINT16, SL_MIN_VERSION, SLV_141), SLE_CONDREF(Depot, town, REF_TOWN, SLV_141, SL_MAX_VERSION), SLE_CONDVAR(Depot, town_cn, SLE_UINT16, SLV_141, SL_MAX_VERSION), SLE_CONDSSTR(Depot, name, SLE_STR, SLV_141, SL_MAX_VERSION), diff --git a/src/saveload/game_sl.cpp b/src/saveload/game_sl.cpp index 48281ed665..1ef513c635 100644 --- a/src/saveload/game_sl.cpp +++ b/src/saveload/game_sl.cpp @@ -26,10 +26,10 @@ static std::string _game_saveload_settings; static bool _game_saveload_is_random; static const SaveLoad _game_script[] = { - SLEG_SSTR(_game_saveload_name, SLE_STR), - SLEG_SSTR(_game_saveload_settings, SLE_STR), - SLEG_VAR(_game_saveload_version, SLE_UINT32), - SLEG_VAR(_game_saveload_is_random, SLE_BOOL), + SLEG_SSTR("name", _game_saveload_name, SLE_STR), + SLEG_SSTR("settings", _game_saveload_settings, SLE_STR), + SLEG_VAR("version", _game_saveload_version, SLE_UINT32), + SLEG_VAR("is_random", _game_saveload_is_random, SLE_BOOL), }; static void SaveReal_GSDT(int *index_ptr) @@ -116,7 +116,7 @@ static uint32 _game_saveload_strings; class SlGameLanguageString : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_SSTR(_game_saveload_string, SLE_STR | SLF_ALLOW_CONTROL), + SLEG_SSTR("string", _game_saveload_string, SLE_STR | SLF_ALLOW_CONTROL), }; void Save(LanguageStrings *ls) const override @@ -142,8 +142,8 @@ public: static const SaveLoad _game_language_desc[] = { SLE_SSTR(LanguageStrings, language, SLE_STR), - SLEG_CONDVAR(_game_saveload_strings, SLE_UINT32, SL_MIN_VERSION, SLV_SAVELOAD_LIST_LENGTH), - SLEG_STRUCTLIST(SlGameLanguageString), + SLEG_CONDVAR("count", _game_saveload_strings, SLE_UINT32, SL_MIN_VERSION, SLV_SAVELOAD_LIST_LENGTH), + SLEG_STRUCTLIST("strings", SlGameLanguageString), }; static void Load_GSTR() diff --git a/src/saveload/gamelog_sl.cpp b/src/saveload/gamelog_sl.cpp index 9f7f4f140d..f76718d42a 100644 --- a/src/saveload/gamelog_sl.cpp +++ b/src/saveload/gamelog_sl.cpp @@ -204,7 +204,7 @@ class SlGamelogEmergency : public DefaultSaveLoadHandlerGetSize() + length; + if (_sl.expect_table_header) { + _sl.expect_table_header = false; + return INT32_MAX; + } + switch (_sl.block_mode) { + case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: index = (int)SlReadSparseIndex(); break; + case CH_TABLE: case CH_ARRAY: index = _sl.array_index++; break; default: Debug(sl, 0, "SlIterateArray error"); @@ -687,6 +729,12 @@ void SlSetLength(size_t length) switch (_sl.need_length) { case NL_WANTLENGTH: _sl.need_length = NL_NONE; + if ((_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) && _sl.expect_table_header) { + _sl.expect_table_header = false; + SlWriteArrayLength(length + 1); + break; + } + switch (_sl.block_mode) { case CH_RIFF: /* Ugly encoding of >16M RIFF chunks @@ -695,6 +743,7 @@ void SlSetLength(size_t length) assert(length < (1 << 28)); SlWriteUint32((uint32)((length & 0xFFFFFF) | ((length >> 24) << 28))); break; + case CH_TABLE: case CH_ARRAY: assert(_sl.last_array_index <= _sl.array_index); while (++_sl.last_array_index <= _sl.array_index) { @@ -702,6 +751,7 @@ void SlSetLength(size_t length) } SlWriteArrayLength(length + 1); break; + case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index. SlWriteSparseIndex(_sl.array_index); @@ -1142,7 +1192,15 @@ static void SlArray(void *array, size_t length, VarType conv) case SLA_LOAD: { if (!IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH)) { size_t sv_length = SlReadArrayLength(); - if (sv_length != length) SlErrorCorrupt("Fixed-length array is of wrong length"); + if (GetVarMemType(conv) == SLE_VAR_NULL) { + /* We don't know this field, so we assume the length in the savegame is correct. */ + length = sv_length; + } else if (sv_length != length) { + /* If the SLE_ARR changes size, a savegame bump is required + * and the developer should have written conversion lines. + * Error out to make this more visible. */ + SlErrorCorrupt("Fixed-length array is of wrong length"); + } } SlCopyInternal(array, length, conv); @@ -1501,6 +1559,34 @@ static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld) return (_sl_version >= sld.version_from && _sl_version < sld.version_to); } +/** + * Calculate the size of the table header. + * @param slt The SaveLoad table with objects to save/load. + * @return size of given object. + */ +static size_t SlCalcTableHeader(const SaveLoadTable &slt) +{ + size_t length = 0; + + for (auto &sld : slt) { + if (!SlIsObjectValidInSavegame(sld)) continue; + + length += SlCalcConvFileLen(SLE_UINT8); + length += SlCalcStdStringLen(&sld.name); + } + + length += SlCalcConvFileLen(SLE_UINT8); // End-of-list entry. + + for (auto &sld : slt) { + if (!SlIsObjectValidInSavegame(sld)) continue; + if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) { + length += SlCalcTableHeader(sld.handler->GetDescription()); + } + } + + return length; +} + /** * Calculate the size of an object. * @param object to be measured. @@ -1764,6 +1850,233 @@ void SlObject(void *object, const SaveLoadTable &slt) } } +/** + * Handler that is assigned when there is a struct read in the savegame which + * is not known to the code. This means we are going to skip it. + */ +class SlSkipHandler : public SaveLoadHandler { + void Save(void *object) const override + { + NOT_REACHED(); + } + + void Load(void *object) const override + { + size_t length = SlGetStructListLength(UINT32_MAX); + for (; length > 0; length--) { + SlObject(object, this->GetLoadDescription()); + } + } + + void LoadCheck(void *object) const override + { + this->Load(object); + } + + virtual SaveLoadTable GetDescription() const override + { + return {}; + } + + virtual SaveLoadCompatTable GetCompatDescription() const override + { + NOT_REACHED(); + } +}; + +/** + * Save or Load a table header. + * @note a table-header can never contain more than 65535 fields. + * @param slt The SaveLoad table with objects to save/load. + * @return When loading, the ordered SaveLoad array to use; otherwise an empty list. + */ +std::vector SlTableHeader(const SaveLoadTable &slt) +{ + /* You can only use SlTableHeader if you are a CH_TABLE. */ + assert(_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE); + + switch (_sl.action) { + case SLA_LOAD_CHECK: + case SLA_LOAD: { + std::vector saveloads; + + /* Build a key lookup mapping based on the available fields. */ + std::map key_lookup; + for (auto &sld : slt) { + if (!SlIsObjectValidInSavegame(sld)) continue; + + /* Check that there is only one active SaveLoad for a given name. */ + assert(key_lookup.find(sld.name) == key_lookup.end()); + key_lookup[sld.name] = &sld; + } + + while (true) { + uint8 type; + SlSaveLoadConv(&type, SLE_UINT8); + if (type == SLE_FILE_END) break; + + std::string key; + SlStdString(&key, SLE_STR); + + auto sld_it = key_lookup.find(key); + if (sld_it == key_lookup.end()) { + Debug(sl, 2, "Field '{}' of type 0x{:02x} not found, skipping", key, type); + + std::shared_ptr handler = nullptr; + SaveLoadType slt; + switch (type & SLE_FILE_TYPE_MASK) { + case SLE_FILE_STRING: + /* Strings are always marked with SLE_FILE_HAS_LENGTH_FIELD, as they are a list of chars. */ + slt = SL_STR; + break; + + case SLE_FILE_STRUCT: + /* Structs are always marked with SLE_FILE_HAS_LENGTH_FIELD as SL_STRUCT is seen as a list of 0/1 in length. */ + slt = SL_STRUCTLIST; + handler = std::make_shared(); + break; + + default: + slt = (type & SLE_FILE_HAS_LENGTH_FIELD) ? SL_ARR : SL_VAR; + break; + } + + /* We don't know this field, so read to nothing. */ + saveloads.push_back({key, slt, ((VarType)type & SLE_FILE_TYPE_MASK) | SLE_VAR_NULL, 1, SL_MIN_VERSION, SL_MAX_VERSION, 0, nullptr, 0, handler}); + continue; + } + + /* Validate the type of the field. If it is changed, the + * savegame should have been bumped so we know how to do the + * conversion. If this error triggers, that clearly didn't + * happen and this is a friendly poke to the developer to bump + * the savegame version and add conversion code. */ + uint8 correct_type = GetSavegameFileType(*sld_it->second); + if (correct_type != type) { + Debug(sl, 1, "Field type for '{}' was expected to be 0x{:02x} but 0x{:02x} was found", key, correct_type, type); + SlErrorCorrupt("Field type is different than expected"); + } + saveloads.push_back(*sld_it->second); + } + + for (auto &sld : saveloads) { + if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) { + sld.handler->load_description = SlTableHeader(sld.handler->GetDescription()); + } + } + + return saveloads; + } + + case SLA_SAVE: { + /* Automatically calculate the length? */ + if (_sl.need_length != NL_NONE) { + SlSetLength(SlCalcTableHeader(slt)); + if (_sl.need_length == NL_CALCLENGTH) break; + } + + for (auto &sld : slt) { + if (!SlIsObjectValidInSavegame(sld)) continue; + /* Make sure we are not storing empty keys. */ + assert(!sld.name.empty()); + + uint8 type = GetSavegameFileType(sld); + assert(type != SLE_FILE_END); + + SlSaveLoadConv(&type, SLE_UINT8); + SlStdString(const_cast(&sld.name), SLE_STR); + } + + /* Add an end-of-header marker. */ + uint8 type = SLE_FILE_END; + SlSaveLoadConv(&type, SLE_UINT8); + + /* After the table, write down any sub-tables we might have. */ + for (auto &sld : slt) { + if (!SlIsObjectValidInSavegame(sld)) continue; + if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) { + /* SlCalcTableHeader already looks in sub-lists, so avoid the length being added twice. */ + NeedLength old_need_length = _sl.need_length; + _sl.need_length = NL_NONE; + + SlTableHeader(sld.handler->GetDescription()); + + _sl.need_length = old_need_length; + } + } + + break; + } + + default: NOT_REACHED(); + } + + return std::vector(); +} + +/** + * Load a table header in a savegame compatible way. If the savegame was made + * before table headers were added, it will fall back to the + * SaveLoadCompatTable for the order of fields while loading. + * + * @note You only have to call this function if the chunk existed as a + * non-table type before converting it to a table. New chunks created as + * table can call SlTableHeader() directly. + * + * @param slt The SaveLoad table with objects to save/load. + * @param slct The SaveLoadCompat table the original order of the fields. + * @return When loading, the ordered SaveLoad array to use; otherwise an empty list. + */ +std::vector SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct) +{ + assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK); + /* CH_TABLE / CH_SPARSE_TABLE always have a header. */ + if (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) return SlTableHeader(slt); + + std::vector saveloads; + + /* Build a key lookup mapping based on the available fields. */ + std::map> key_lookup; + for (auto &sld : slt) { + /* All entries should have a name; otherwise the entry should just be removed. */ + assert(!sld.name.empty()); + + key_lookup[sld.name].push_back(&sld); + } + + for (auto &slc : slct) { + if (slc.name.empty()) { + /* In old savegames there can be data we no longer care for. We + * skip this by simply reading the amount of bytes indicated and + * send those to /dev/null. */ + saveloads.push_back({"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, slc.length, slc.version_from, slc.version_to, 0, nullptr, 0, nullptr}); + } else { + auto sld_it = key_lookup.find(slc.name); + /* If this branch triggers, it means that an entry in the + * SaveLoadCompat list is not mentioned in the SaveLoad list. Did + * you rename a field in one and not in the other? */ + if (sld_it == key_lookup.end()) { + /* This isn't an assert, as that leaves no information what + * field was to blame. This way at least we have breadcrumbs. */ + Debug(sl, 0, "internal error: saveload compatibility field '{}' not found", slc.name); + SlErrorCorrupt("Internal error with savegame compatibility"); + } + for (auto &sld : sld_it->second) { + saveloads.push_back(*sld); + } + } + } + + for (auto &sld : saveloads) { + if (!SlIsObjectValidInSavegame(sld)) continue; + if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) { + sld.handler->load_description = SlCompatTableHeader(sld.handler->GetDescription(), sld.handler->GetCompatDescription()); + } + } + + return saveloads; +} + /** * Save or Load (a list of) global variables. * @param slt The SaveLoad table with objects to save/load. @@ -1811,33 +2124,43 @@ static void SlLoadChunk(const ChunkHandler &ch) size_t len; size_t endoffs; - _sl.block_mode = m; + _sl.block_mode = m & CH_TYPE_MASK; _sl.obj_len = 0; + _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE); - switch (m) { + /* The header should always be at the start. Read the length; the + * load_proc() should as first action process the header. */ + if (_sl.expect_table_header) { + SlIterateArray(); + } + + switch (_sl.block_mode) { + case CH_TABLE: case CH_ARRAY: _sl.array_index = 0; ch.load_proc(); if (_next_offs != 0) SlErrorCorrupt("Invalid array length"); break; + case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: ch.load_proc(); if (_next_offs != 0) SlErrorCorrupt("Invalid array length"); break; + case CH_RIFF: + /* Read length */ + len = (SlReadByte() << 16) | ((m >> 4) << 24); + len += SlReadUint16(); + _sl.obj_len = len; + endoffs = _sl.reader->GetSize() + len; + ch.load_proc(); + if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size"); + break; default: - if ((m & 0xF) == CH_RIFF) { - /* Read length */ - len = (SlReadByte() << 16) | ((m >> 4) << 24); - len += SlReadUint16(); - _sl.obj_len = len; - endoffs = _sl.reader->GetSize() + len; - ch.load_proc(); - if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size"); - } else { - SlErrorCorrupt("Invalid chunk type"); - } + SlErrorCorrupt("Invalid chunk type"); break; } + + if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header"); } /** @@ -1851,43 +2174,54 @@ static void SlLoadCheckChunk(const ChunkHandler &ch) size_t len; size_t endoffs; - _sl.block_mode = m; + _sl.block_mode = m & CH_TYPE_MASK; _sl.obj_len = 0; + _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE); - switch (m) { + /* The header should always be at the start. Read the length; the + * load_check_proc() should as first action process the header. */ + if (_sl.expect_table_header && ch.load_check_proc != nullptr) { + /* If load_check_proc() is nullptr, SlSkipArray() will already skip the header. */ + SlIterateArray(); + } + + switch (_sl.block_mode) { + case CH_TABLE: case CH_ARRAY: _sl.array_index = 0; - if (ch.load_check_proc) { + if (ch.load_check_proc != nullptr) { ch.load_check_proc(); } else { SlSkipArray(); } break; + case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: - if (ch.load_check_proc) { + if (ch.load_check_proc != nullptr) { ch.load_check_proc(); } else { SlSkipArray(); } break; + case CH_RIFF: + /* Read length */ + len = (SlReadByte() << 16) | ((m >> 4) << 24); + len += SlReadUint16(); + _sl.obj_len = len; + endoffs = _sl.reader->GetSize() + len; + if (ch.load_check_proc) { + ch.load_check_proc(); + } else { + SlSkipBytes(len); + } + if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size"); + break; default: - if ((m & 0xF) == CH_RIFF) { - /* Read length */ - len = (SlReadByte() << 16) | ((m >> 4) << 24); - len += SlReadUint16(); - _sl.obj_len = len; - endoffs = _sl.reader->GetSize() + len; - if (ch.load_check_proc) { - ch.load_check_proc(); - } else { - SlSkipBytes(len); - } - if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size"); - } else { - SlErrorCorrupt("Invalid chunk type"); - } + SlErrorCorrupt("Invalid chunk type"); break; } + + if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header"); } /** @@ -1906,24 +2240,31 @@ static void SlSaveChunk(const ChunkHandler &ch) Debug(sl, 2, "Saving chunk {:c}{:c}{:c}{:c}", ch.id >> 24, ch.id >> 16, ch.id >> 8, ch.id); _sl.block_mode = ch.type; - switch (ch.type) { + _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE); + + _sl.need_length = (_sl.expect_table_header || _sl.block_mode == CH_RIFF) ? NL_WANTLENGTH : NL_NONE; + + switch (_sl.block_mode) { case CH_RIFF: - _sl.need_length = NL_WANTLENGTH; proc(); break; + case CH_TABLE: case CH_ARRAY: _sl.last_array_index = 0; - SlWriteByte(CH_ARRAY); + SlWriteByte(_sl.block_mode); proc(); SlWriteArrayLength(0); // Terminate arrays break; + case CH_SPARSE_TABLE: case CH_SPARSE_ARRAY: - SlWriteByte(CH_SPARSE_ARRAY); + SlWriteByte(_sl.block_mode); proc(); SlWriteArrayLength(0); // Terminate arrays break; default: NOT_REACHED(); } + + if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header"); } /** Save all chunks */ @@ -3068,3 +3409,9 @@ void FileToSaveLoad::SetTitle(const char *title) { strecpy(this->title, title, lastof(this->title)); } + +SaveLoadTable SaveLoadHandler::GetLoadDescription() const +{ + assert(this->load_description.has_value()); + return *this->load_description; +} diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 0ce46cbe98..b6e843c991 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -13,6 +13,7 @@ #include "../fileio_type.h" #include "../strings_type.h" #include "../core/span_type.hpp" +#include #include #include @@ -333,6 +334,8 @@ enum SaveLoadVersion : uint16 { SLV_SAVELOAD_LIST_LENGTH, ///< 293 PR#9374 Consistency in list length with SL_STRUCT / SL_STRUCTLIST / SL_DEQUE / SL_REFLIST. SLV_RIFF_TO_ARRAY, ///< 294 PR#9375 Changed many CH_RIFF chunks to CH_ARRAY chunks. + SLV_TABLE_CHUNKS, ///< 295 PR#9322 Introduction of CH_TABLE and CH_SPARSE_TABLE. + SL_MAX_VERSION, ///< Highest possible saveload version }; @@ -388,6 +391,10 @@ enum ChunkType { CH_RIFF = 0, CH_ARRAY = 1, CH_SPARSE_ARRAY = 2, + CH_TABLE = 3, + CH_SPARSE_TABLE = 4, + + CH_TYPE_MASK = 0xf, ///< All ChunkType values have to be within this mask. CH_READONLY, ///< Chunk is never saved. }; @@ -407,9 +414,14 @@ using ChunkHandlerTable = span; /** A table of SaveLoad entries. */ using SaveLoadTable = span; +/** A table of SaveLoadCompat entries. */ +using SaveLoadCompatTable = span; + /** Handler for saving/loading an object to/from disk. */ class SaveLoadHandler { public: + std::optional> load_description; + virtual ~SaveLoadHandler() {} /** @@ -440,6 +452,18 @@ public: * Get the description of the fields in the savegame. */ virtual SaveLoadTable GetDescription() const = 0; + + /** + * Get the pre-header description of the fields in the savegame. + */ + virtual SaveLoadCompatTable GetCompatDescription() const { return {}; } + + /** + * Get the description for how to load the chunk. Depending on the + * savegame version this can either use the headers in the savegame or + * fall back to backwards compatibility and uses hard-coded headers. + */ + SaveLoadTable GetLoadDescription() const; }; /** @@ -496,18 +520,24 @@ enum SLRefType { * Bits 8-15 are reserved for various flags as explained below */ enum VarTypes { - /* 4 bits allocated a maximum of 16 types for NumberType */ - SLE_FILE_I8 = 0, - SLE_FILE_U8 = 1, - SLE_FILE_I16 = 2, - SLE_FILE_U16 = 3, - SLE_FILE_I32 = 4, - SLE_FILE_U32 = 5, - SLE_FILE_I64 = 6, - SLE_FILE_U64 = 7, - SLE_FILE_STRINGID = 8, ///< StringID offset into strings-array - SLE_FILE_STRING = 9, - /* 6 more possible file-primitives */ + /* 4 bits allocated a maximum of 16 types for NumberType. + * NOTE: the SLE_FILE_NNN values are stored in the savegame! */ + SLE_FILE_END = 0, ///< Used to mark end-of-header in tables. + SLE_FILE_I8 = 1, + SLE_FILE_U8 = 2, + SLE_FILE_I16 = 3, + SLE_FILE_U16 = 4, + SLE_FILE_I32 = 5, + SLE_FILE_U32 = 6, + SLE_FILE_I64 = 7, + SLE_FILE_U64 = 8, + SLE_FILE_STRINGID = 9, ///< StringID offset into strings-array + SLE_FILE_STRING = 10, + SLE_FILE_STRUCT = 11, + /* 4 more possible file-primitives */ + + SLE_FILE_TYPE_MASK = 0xf, ///< Mask to get the file-type (and not any flags). + SLE_FILE_HAS_LENGTH_FIELD = 1 << 4, ///< Bit stored in savegame to indicate field has a length field for each entry. /* 4 bits allocated a maximum of 16 types for NumberType */ SLE_VAR_BL = 0 << 4, @@ -586,6 +616,7 @@ typedef void *SaveLoadAddrProc(void *base, size_t extra); /** SaveLoad type struct. Do NOT use this directly but use the SLE_ macros defined just below! */ struct SaveLoad { + std::string name; ///< Name of this field (optional, used for tables). SaveLoadType cmd; ///< the action to take with the saved/loaded type, All types need different action VarType conv; ///< type of the variable to be saved, int uint16 length; ///< (conditional) length of the variable (eg. arrays) (max array size is 65536 elements) @@ -594,7 +625,22 @@ struct SaveLoad { size_t size; ///< the sizeof size. SaveLoadAddrProc *address_proc; ///< callback proc the get the actual variable address in memory size_t extra_data; ///< extra data for the callback proc - SaveLoadHandler *handler; ///< Custom handler for Save/Load procs. + std::shared_ptr handler; ///< Custom handler for Save/Load procs. +}; + +/** + * SaveLoad information for backwards compatibility. + * + * At SLV_SETTINGS_NAME a new method of keeping track of fields in a savegame + * was added, where the order of fields is no longer important. For older + * savegames we still need to know the correct order. This struct is the glue + * to make that happen. + */ +struct SaveLoadCompat { + std::string name; ///< Name of the field. + uint16 length; ///< Length of the NULL field. + SaveLoadVersion version_from; ///< Save/load the variable starting from this savegame version. + SaveLoadVersion version_to; ///< Save/load the variable until this savegame version. }; /** @@ -608,7 +654,7 @@ struct SaveLoad { * @param extra Extra data to pass to the address callback function. * @note In general, it is better to use one of the SLE_* macros below. */ -#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) SaveLoad {cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra, nullptr} +#define SLE_GENERAL(cmd, base, variable, type, length, from, to, extra) SaveLoad {#variable, cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast(static_cast(std::addressof(static_cast(b)->variable))); }, extra, nullptr} /** * Storage of a variable in some savegame versions. @@ -744,7 +790,7 @@ struct SaveLoad { * @param from First savegame version that has the empty space. * @param to Last savegame version that has the empty space. */ -#define SLE_CONDNULL(length, from, to) SaveLoad {SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, length, from, to, 0, nullptr, 0, nullptr} +#define SLE_CONDNULL(length, from, to) SaveLoad {"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, length, from, to, 0, nullptr, 0, nullptr} /** * Only write byte during saving; never read it during loading. @@ -760,6 +806,7 @@ struct SaveLoad { /** * Storage of global simple variables, references (pointers), and arrays. + * @param name The name of the field. * @param cmd Load/save type. @see SaveLoadType * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. @@ -768,149 +815,167 @@ struct SaveLoad { * @param extra Extra data to pass to the address callback function. * @note In general, it is better to use one of the SLEG_* macros below. */ -#define SLEG_GENERAL(cmd, variable, type, length, from, to, extra) SaveLoad {cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra, nullptr} +#define SLEG_GENERAL(name, cmd, variable, type, length, from, to, extra) SaveLoad {name, cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast(std::addressof(variable)); }, extra, nullptr} /** * Storage of a global variable in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLEG_CONDVAR(variable, type, from, to) SLEG_GENERAL(SL_VAR, variable, type, 0, from, to, 0) +#define SLEG_CONDVAR(name, variable, type, from, to) SLEG_GENERAL(name, SL_VAR, variable, type, 0, from, to, 0) /** * Storage of a global reference in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the field. * @param to Last savegame version that has the field. */ -#define SLEG_CONDREF(variable, type, from, to) SLEG_GENERAL(SL_REF, variable, type, 0, from, to, 0) +#define SLEG_CONDREF(name, variable, type, from, to) SLEG_GENERAL(name, SL_REF, variable, type, 0, from, to, 0) /** * Storage of a global fixed-size array of #SL_VAR elements in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param length Number of elements in the array. * @param from First savegame version that has the array. * @param to Last savegame version that has the array. */ -#define SLEG_CONDARR(variable, type, length, from, to) SLEG_GENERAL(SL_ARR, variable, type, length, from, to, 0) +#define SLEG_CONDARR(name, variable, type, length, from, to) SLEG_GENERAL(name, SL_ARR, variable, type, length, from, to, 0) /** * Storage of a global string in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param length Number of elements in the string (only used for fixed size buffers). * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLEG_CONDSTR(variable, type, length, from, to) SLEG_GENERAL(SL_STR, variable, type, length, from, to, 0) +#define SLEG_CONDSTR(name, variable, type, length, from, to) SLEG_GENERAL(name, SL_STR, variable, type, length, from, to, 0) /** * Storage of a global \c std::string in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the string. * @param to Last savegame version that has the string. */ -#define SLEG_CONDSSTR(variable, type, from, to) SLEG_GENERAL(SL_STDSTR, variable, type, 0, from, to, 0) +#define SLEG_CONDSSTR(name, variable, type, from, to) SLEG_GENERAL(name, SL_STDSTR, variable, type, 0, from, to, 0) /** * Storage of a structs in some savegame versions. + * @param name The name of the field. * @param handler SaveLoadHandler for the structs. * @param from First savegame version that has the struct. * @param to Last savegame version that has the struct. */ -#define SLEG_CONDSTRUCT(handler, from, to) SaveLoad {SL_STRUCT, 0, 0, from, to, 0, nullptr, 0, new handler()} +#define SLEG_CONDSTRUCT(name, handler, from, to) SaveLoad {name, SL_STRUCT, 0, 0, from, to, 0, nullptr, 0, std::make_shared()} /** * Storage of a global reference list in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLEG_CONDREFLIST(variable, type, from, to) SLEG_GENERAL(SL_REFLIST, variable, type, 0, from, to, 0) +#define SLEG_CONDREFLIST(name, variable, type, from, to) SLEG_GENERAL(name, SL_REFLIST, variable, type, 0, from, to, 0) /** * Storage of a global vector of #SL_VAR elements in some savegame versions. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLEG_CONDVECTOR(variable, type, from, to) SLEG_GENERAL(SL_VECTOR, variable, type, 0, from, to, 0) +#define SLEG_CONDVECTOR(name, variable, type, from, to) SLEG_GENERAL(name, SL_VECTOR, variable, type, 0, from, to, 0) /** * Storage of a list of structs in some savegame versions. + * @param name The name of the field. * @param handler SaveLoadHandler for the list of structs. * @param from First savegame version that has the list. * @param to Last savegame version that has the list. */ -#define SLEG_CONDSTRUCTLIST(handler, from, to) SaveLoad {SL_STRUCTLIST, 0, 0, from, to, 0, nullptr, 0, new handler()} +#define SLEG_CONDSTRUCTLIST(name, handler, from, to) SaveLoad {name, SL_STRUCTLIST, 0, 0, from, to, 0, nullptr, 0, std::make_shared()} /** * Storage of a global variable in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_VAR(variable, type) SLEG_CONDVAR(variable, type, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_VAR(name, variable, type) SLEG_CONDVAR(name, variable, type, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global reference in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_REF(variable, type) SLEG_CONDREF(variable, type, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_REF(name, variable, type) SLEG_CONDREF(name, variable, type, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global fixed-size array of #SL_VAR elements in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_ARR(variable, type) SLEG_CONDARR(variable, type, lengthof(variable), SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_ARR(name, variable, type) SLEG_CONDARR(name, variable, type, lengthof(variable), SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global string in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_STR(variable, type) SLEG_CONDSTR(variable, type, sizeof(variable), SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_STR(name, variable, type) SLEG_CONDSTR(name, variable, type, sizeof(variable), SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global \c std::string in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_SSTR(variable, type) SLEG_CONDSSTR(variable, type, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_SSTR(name, variable, type) SLEG_CONDSSTR(name, variable, type, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a structs in every savegame version. + * @param name The name of the field. * @param handler SaveLoadHandler for the structs. */ -#define SLEG_STRUCT(handler) SLEG_CONDSTRUCT(handler, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_STRUCT(name, handler) SLEG_CONDSTRUCT(name, handler, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global reference list in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_REFLIST(variable, type) SLEG_CONDREFLIST(variable, type, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_REFLIST(name, variable, type) SLEG_CONDREFLIST(name, variable, type, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a global vector of #SL_VAR elements in every savegame version. + * @param name The name of the field. * @param variable Name of the global variable. * @param type Storage of the data in memory and in the savegame. */ -#define SLEG_VECTOR(variable, type) SLEG_CONDVECTOR(variable, type, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_VECTOR(name, variable, type) SLEG_CONDVECTOR(name, variable, type, SL_MIN_VERSION, SL_MAX_VERSION) /** * Storage of a list of structs in every savegame version. + * @param name The name of the field. * @param handler SaveLoadHandler for the list of structs. */ -#define SLEG_STRUCTLIST(handler) SLEG_CONDSTRUCTLIST(handler, SL_MIN_VERSION, SL_MAX_VERSION) +#define SLEG_STRUCTLIST(name, handler) SLEG_CONDSTRUCTLIST(name, handler, SL_MIN_VERSION, SL_MAX_VERSION) /** * Empty global space in some savegame versions. @@ -918,7 +983,24 @@ struct SaveLoad { * @param from First savegame version that has the empty space. * @param to Last savegame version that has the empty space. */ -#define SLEG_CONDNULL(length, from, to) SaveLoad {SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, length, from, to, 0, nullptr, 0, nullptr} +#define SLEG_CONDNULL(length, from, to) SaveLoad {"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, length, from, to, 0, nullptr, 0, nullptr} + +/** + * Field name where the real SaveLoad can be located. + * @param name The name of the field. + */ +#define SLC_VAR(name) {name, 0, SL_MIN_VERSION, SL_MAX_VERSION} + +/** + * Empty space in every savegame version. + * @param length Length of the empty space. + * @param from First savegame version that has the empty space. + * @param to Last savegame version that has the empty space. + */ +#define SLC_NULL(length, from, to) {{}, length, from, to} + +/** End marker of compat variables save or load. */ +#define SLC_END() {{}, 0, SL_MIN_VERSION, SL_MIN_VERSION} /** * Checks whether the savegame is below \a major.\a minor. @@ -1029,6 +1111,8 @@ void SlWriteByte(byte b); void SlGlobList(const SaveLoadTable &slt); void SlCopy(void *object, size_t length, VarType conv); +std::vector SlTableHeader(const SaveLoadTable &slt); +std::vector SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct); void SlObject(void *object, const SaveLoadTable &slt); void NORETURN SlError(StringID string, const char *extra_msg = nullptr); void NORETURN SlErrorCorrupt(const char *msg); diff --git a/src/saveload/station_sl.cpp b/src/saveload/station_sl.cpp index a536c02db4..5b99b09f7d 100644 --- a/src/saveload/station_sl.cpp +++ b/src/saveload/station_sl.cpp @@ -331,29 +331,29 @@ public: inline #endif static const SaveLoad description[] = { - SLEG_CONDVAR( _waiting_acceptance, SLE_UINT16, SL_MIN_VERSION, SLV_68), + SLEG_CONDVAR("waiting_acceptance", _waiting_acceptance, SLE_UINT16, SL_MIN_VERSION, SLV_68), SLE_CONDVAR(GoodsEntry, status, SLE_UINT8, SLV_68, SL_MAX_VERSION), SLE_CONDNULL(2, SLV_51, SLV_68), SLE_VAR(GoodsEntry, time_since_pickup, SLE_UINT8), SLE_VAR(GoodsEntry, rating, SLE_UINT8), - SLEG_CONDVAR( _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_7), - SLEG_CONDVAR( _cargo_source, SLE_UINT16, SLV_7, SLV_68), - SLEG_CONDVAR( _cargo_source_xy, SLE_UINT32, SLV_44, SLV_68), - SLEG_CONDVAR( _cargo_days, SLE_UINT8, SL_MIN_VERSION, SLV_68), + SLEG_CONDVAR("cargo_source", _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_7), + SLEG_CONDVAR("cargo_source", _cargo_source, SLE_UINT16, SLV_7, SLV_68), + SLEG_CONDVAR("cargo_source_xy", _cargo_source_xy, SLE_UINT32, SLV_44, SLV_68), + SLEG_CONDVAR("cargo_days", _cargo_days, SLE_UINT8, SL_MIN_VERSION, SLV_68), SLE_VAR(GoodsEntry, last_speed, SLE_UINT8), SLE_VAR(GoodsEntry, last_age, SLE_UINT8), - SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, SLV_14, SLV_65), - SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, SLV_65, SLV_68), + SLEG_CONDVAR("cargo_feeder_share", _cargo_feeder_share, SLE_FILE_U32 | SLE_VAR_I64, SLV_14, SLV_65), + SLEG_CONDVAR("cargo_feeder_share", _cargo_feeder_share, SLE_INT64, SLV_65, SLV_68), SLE_CONDVAR(GoodsEntry, amount_fract, SLE_UINT8, SLV_150, SL_MAX_VERSION), - SLEG_CONDREFLIST( _packets, REF_CARGO_PACKET, SLV_68, SLV_183), - SLEG_CONDVAR( _old_num_dests, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), + SLEG_CONDREFLIST("packets", _packets, REF_CARGO_PACKET, SLV_68, SLV_183), + SLEG_CONDVAR("old_num_dests", _old_num_dests, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), SLE_CONDVAR(GoodsEntry, cargo.reserved_count, SLE_UINT, SLV_181, SL_MAX_VERSION), SLE_CONDVAR(GoodsEntry, link_graph, SLE_UINT16, SLV_183, SL_MAX_VERSION), SLE_CONDVAR(GoodsEntry, node, SLE_UINT16, SLV_183, SL_MAX_VERSION), - SLEG_CONDVAR( _old_num_flows, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), + SLEG_CONDVAR("old_num_flows", _old_num_flows, SLE_UINT32, SLV_183, SLV_SAVELOAD_LIST_LENGTH), SLE_CONDVAR(GoodsEntry, max_waiting_cargo, SLE_UINT32, SLV_183, SL_MAX_VERSION), - SLEG_CONDSTRUCTLIST(SlStationFlow, SLV_183, SL_MAX_VERSION), - SLEG_CONDSTRUCTLIST(SlStationCargo, SLV_183, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("flow", SlStationFlow, SLV_183, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("cargo", SlStationCargo, SLV_183, SL_MAX_VERSION), }; #if defined(_MSC_VER) && (_MSC_VER == 1915 || _MSC_VER == 1916) return description; @@ -497,8 +497,8 @@ static const SaveLoad _old_station_desc[] = { /* reserve extra space in savegame here. (currently 32 bytes) */ SLE_CONDNULL(32, SLV_2, SL_MAX_VERSION), - SLEG_STRUCTLIST(SlStationGoods), - SLEG_CONDSTRUCTLIST(SlStationSpecList, SLV_27, SL_MAX_VERSION), + SLEG_STRUCTLIST("goods", SlStationGoods), + SLEG_CONDSTRUCTLIST("speclist", SlStationSpecList, SLV_27, SL_MAX_VERSION), }; static void Load_STNS() @@ -566,7 +566,7 @@ public: class SlStationNormal : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlStationBase), + SLEG_STRUCT("base", SlStationBase), SLE_VAR(Station, train_station.tile, SLE_UINT32), SLE_VAR(Station, train_station.w, SLE_FILE_U8 | SLE_VAR_U16), SLE_VAR(Station, train_station.h, SLE_FILE_U8 | SLE_VAR_U16), @@ -587,7 +587,7 @@ public: SLE_CONDVAR(Station, airport.layout, SLE_UINT8, SLV_145, SL_MAX_VERSION), SLE_VAR(Station, airport.flags, SLE_UINT64), SLE_CONDVAR(Station, airport.rotation, SLE_UINT8, SLV_145, SL_MAX_VERSION), - SLEG_CONDARR(_old_st_persistent_storage.storage, SLE_UINT32, 16, SLV_145, SLV_161), + SLEG_CONDARR("storage", _old_st_persistent_storage.storage, SLE_UINT32, 16, SLV_145, SLV_161), SLE_CONDREF(Station, airport.psa, REF_STORAGE, SLV_161, SL_MAX_VERSION), SLE_VAR(Station, indtype, SLE_UINT8), @@ -599,7 +599,7 @@ public: SLE_REFLIST(Station, loading_vehicles, REF_VEHICLE), SLE_CONDVAR(Station, always_accepted, SLE_FILE_U32 | SLE_VAR_U64, SLV_127, SLV_EXTEND_CARGOTYPES), SLE_CONDVAR(Station, always_accepted, SLE_UINT64, SLV_EXTEND_CARGOTYPES, SL_MAX_VERSION), - SLEG_STRUCTLIST(SlStationGoods), + SLEG_STRUCTLIST("goods", SlStationGoods), }; void GenericSaveLoad(BaseStation *bst) const @@ -616,7 +616,7 @@ public: class SlStationWaypoint : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlStationBase), + SLEG_STRUCT("base", SlStationBase), SLE_VAR(Waypoint, town_cn, SLE_UINT16), SLE_CONDVAR(Waypoint, train_station.tile, SLE_UINT32, SLV_124, SL_MAX_VERSION), @@ -637,9 +637,9 @@ public: static const SaveLoad _station_desc[] = { SLE_SAVEBYTE(BaseStation, facilities), - SLEG_STRUCT(SlStationNormal), - SLEG_STRUCT(SlStationWaypoint), - SLEG_CONDSTRUCTLIST(SlStationSpecList, SLV_27, SL_MAX_VERSION), + SLEG_STRUCT("normal", SlStationNormal), + SLEG_STRUCT("waypoint", SlStationWaypoint), + SLEG_CONDSTRUCTLIST("speclist", SlStationSpecList, SLV_27, SL_MAX_VERSION), }; static void Save_STNN() diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index 38c000e3b9..c653db53a7 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -278,9 +278,9 @@ static const SaveLoad _town_desc[] = { SLE_CONDNULL(8, SLV_EXTEND_CARGOTYPES, SLV_REMOVE_TOWN_CARGO_CACHE), ///< cargo_produced, no longer in use SLE_CONDNULL(30, SLV_2, SLV_REMOVE_TOWN_CARGO_CACHE), ///< old reserved space - SLEG_CONDSTRUCTLIST(SlTownSupplied, SLV_165, SL_MAX_VERSION), - SLEG_CONDSTRUCTLIST(SlTownReceived, SLV_165, SL_MAX_VERSION), - SLEG_CONDSTRUCTLIST(SlTownAcceptanceMatrix, SLV_166, SLV_REMOVE_TOWN_CARGO_CACHE), + SLEG_CONDSTRUCTLIST("supplied", SlTownSupplied, SLV_165, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("received", SlTownReceived, SLV_165, SL_MAX_VERSION), + SLEG_CONDSTRUCTLIST("acceptance_matrix", SlTownAcceptanceMatrix, SLV_166, SLV_REMOVE_TOWN_CARGO_CACHE), }; static void Save_HIDS() diff --git a/src/saveload/vehicle_sl.cpp b/src/saveload/vehicle_sl.cpp index ffa0206771..b2d9972fee 100644 --- a/src/saveload/vehicle_sl.cpp +++ b/src/saveload/vehicle_sl.cpp @@ -626,14 +626,14 @@ public: SLE_VAR(Vehicle, cargo_type, SLE_UINT8), SLE_CONDVAR(Vehicle, cargo_subtype, SLE_UINT8, SLV_35, SL_MAX_VERSION), - SLEG_CONDVAR( _cargo_days, SLE_UINT8, SL_MIN_VERSION, SLV_68), - SLEG_CONDVAR( _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_7), - SLEG_CONDVAR( _cargo_source, SLE_UINT16, SLV_7, SLV_68), - SLEG_CONDVAR( _cargo_source_xy, SLE_UINT32, SLV_44, SLV_68), + SLEG_CONDVAR("cargo_days", _cargo_days, SLE_UINT8, SL_MIN_VERSION, SLV_68), + SLEG_CONDVAR("cargo_source", _cargo_source, SLE_FILE_U8 | SLE_VAR_U16, SL_MIN_VERSION, SLV_7), + SLEG_CONDVAR("cargo_source", _cargo_source, SLE_UINT16, SLV_7, SLV_68), + SLEG_CONDVAR("cargo_source_xy", _cargo_source_xy, SLE_UINT32, SLV_44, SLV_68), SLE_VAR(Vehicle, cargo_cap, SLE_UINT16), SLE_CONDVAR(Vehicle, refit_cap, SLE_UINT16, SLV_182, SL_MAX_VERSION), - SLEG_CONDVAR( _cargo_count, SLE_UINT16, SL_MIN_VERSION, SLV_68), - SLE_CONDREFLIST(Vehicle, cargo.packets, REF_CARGO_PACKET, SLV_68, SL_MAX_VERSION), + SLEG_CONDVAR("cargo_count", _cargo_count, SLE_UINT16, SL_MIN_VERSION, SLV_68), + SLE_CONDREFLIST(Vehicle, cargo.packets, REF_CARGO_PACKET, SLV_68, SL_MAX_VERSION), SLE_CONDARR(Vehicle, cargo.action_counts, SLE_UINT, VehicleCargoList::NUM_MOVE_TO_ACTION, SLV_181, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, cargo_age_counter, SLE_UINT16, SLV_162, SL_MAX_VERSION), @@ -689,7 +689,7 @@ public: SLE_CONDVAR(Vehicle, build_year, SLE_INT32, SLV_31, SL_MAX_VERSION), SLE_VAR(Vehicle, load_unload_ticks, SLE_UINT16), - SLEG_CONDVAR( _cargo_paid_for, SLE_UINT16, SLV_45, SL_MAX_VERSION), + SLEG_CONDVAR("cargo_paid_for", _cargo_paid_for, SLE_UINT16, SLV_45, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, vehicle_flags, SLE_FILE_U8 | SLE_VAR_U16, SLV_40, SLV_180), SLE_CONDVAR(Vehicle, vehicle_flags, SLE_UINT16, SLV_180, SL_MAX_VERSION), @@ -697,9 +697,9 @@ public: SLE_CONDVAR(Vehicle, profit_this_year, SLE_INT64, SLV_65, SL_MAX_VERSION), SLE_CONDVAR(Vehicle, profit_last_year, SLE_FILE_I32 | SLE_VAR_I64, SL_MIN_VERSION, SLV_65), SLE_CONDVAR(Vehicle, profit_last_year, SLE_INT64, SLV_65, SL_MAX_VERSION), - SLEG_CONDVAR( _cargo_feeder_share, SLE_FILE_I32 | SLE_VAR_I64, SLV_51, SLV_65), - SLEG_CONDVAR( _cargo_feeder_share, SLE_INT64, SLV_65, SLV_68), - SLEG_CONDVAR( _cargo_loaded_at_xy, SLE_UINT32, SLV_51, SLV_68), + SLEG_CONDVAR("cargo_feeder_share", _cargo_feeder_share, SLE_FILE_I32 | SLE_VAR_I64, SLV_51, SLV_65), + SLEG_CONDVAR("cargo_feeder_share", _cargo_feeder_share, SLE_INT64, SLV_65, SLV_68), + SLEG_CONDVAR("cargo_loaded_at_xy", _cargo_loaded_at_xy, SLE_UINT32, SLV_51, SLV_68), SLE_CONDVAR(Vehicle, value, SLE_FILE_I32 | SLE_VAR_I64, SL_MIN_VERSION, SLV_65), SLE_CONDVAR(Vehicle, value, SLE_INT64, SLV_65, SL_MAX_VERSION), @@ -735,7 +735,7 @@ public: class SlVehicleTrain : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlVehicleCommon), + SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(Train, crash_anim_pos, SLE_UINT16), SLE_VAR(Train, force_proceed, SLE_UINT8), SLE_VAR(Train, railtype, SLE_UINT8), @@ -766,7 +766,7 @@ public: class SlVehicleRoadVeh : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlVehicleCommon), + SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(RoadVehicle, state, SLE_UINT8), SLE_VAR(RoadVehicle, frame, SLE_UINT8), SLE_VAR(RoadVehicle, blocked_ctr, SLE_UINT16), @@ -798,7 +798,7 @@ public: class SlVehicleShip : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlVehicleCommon), + SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(Ship, state, SLE_UINT8), SLE_CONDDEQUE(Ship, path, SLE_UINT8, SLV_SHIP_PATH_CACHE, SL_MAX_VERSION), SLE_CONDVAR(Ship, rotation, SLE_UINT8, SLV_SHIP_ROTATION, SL_MAX_VERSION), @@ -820,7 +820,7 @@ public: class SlVehicleAircraft : public DefaultSaveLoadHandler { public: inline static const SaveLoad description[] = { - SLEG_STRUCT(SlVehicleCommon), + SLEG_STRUCT("common", SlVehicleCommon), SLE_VAR(Aircraft, crashed_counter, SLE_UINT16), SLE_VAR(Aircraft, pos, SLE_UINT8), @@ -955,12 +955,12 @@ public: const static SaveLoad _vehicle_desc[] = { SLE_SAVEBYTE(Vehicle, type), - SLEG_STRUCT(SlVehicleTrain), - SLEG_STRUCT(SlVehicleRoadVeh), - SLEG_STRUCT(SlVehicleShip), - SLEG_STRUCT(SlVehicleAircraft), - SLEG_STRUCT(SlVehicleEffect), - SLEG_STRUCT(SlVehicleDisaster), + SLEG_STRUCT("train", SlVehicleTrain), + SLEG_STRUCT("roadveh", SlVehicleRoadVeh), + SLEG_STRUCT("ship", SlVehicleShip), + SLEG_STRUCT("aircraft", SlVehicleAircraft), + SLEG_STRUCT("effect", SlVehicleEffect), + SLEG_STRUCT("disaster", SlVehicleDisaster), }; /** Will be called when the vehicles need to be saved. */ diff --git a/src/script/script_instance.cpp b/src/script/script_instance.cpp index 2665a86846..1d57383a9f 100644 --- a/src/script/script_instance.cpp +++ b/src/script/script_instance.cpp @@ -345,7 +345,7 @@ static byte _script_sl_byte; ///< Used as source/target by the script saveload c /** SaveLoad array that saves/loads exactly one byte. */ static const SaveLoad _script_byte[] = { - SLEG_VAR(_script_sl_byte, SLE_UINT8), + SLEG_VAR("type", _script_sl_byte, SLE_UINT8), }; /* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test) diff --git a/src/settings.cpp b/src/settings.cpp index 9847a0ff93..6368dea113 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2194,7 +2194,7 @@ static std::vector GetSettingsDesc(const SettingTable &settings, bool if (is_loading && (sd->flags & SF_NO_NETWORK_SYNC) && _networking && !_network_server) { /* We don't want to read this setting, so we do need to skip over it. */ - saveloads.push_back({sd->save.cmd, GetVarFileType(sd->save.conv) | SLE_VAR_NULL, sd->save.length, sd->save.version_from, sd->save.version_to, 0, nullptr, 0, nullptr}); + saveloads.push_back({sd->name, sd->save.cmd, GetVarFileType(sd->save.conv) | SLE_VAR_NULL, sd->save.length, sd->save.version_from, sd->save.version_to, 0, nullptr, 0, nullptr}); continue; } diff --git a/src/table/settings.h.preamble b/src/table/settings.h.preamble index 5fd1a9bac6..9641c24cea 100644 --- a/src/table/settings.h.preamble +++ b/src/table/settings.h.preamble @@ -59,22 +59,22 @@ static size_t ConvertLandscape(const char *value); /* Macros for various objects to go in the configuration file. * This section is for global variables */ #define SDTG_VAR(name, type, flags, var, def, min, max, interval, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Int, SLEG_GENERAL(SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) + NSD(Int, SLEG_GENERAL(#var, SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, min, max, interval, str, strhelp, strval, cat, pre_check, post_callback) #define SDTG_BOOL(name, flags, var, def, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(Bool, SLEG_GENERAL(SL_VAR, var, SLE_BOOL, 1, from, to, extra), name, flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) + NSD(Bool, SLEG_GENERAL(#var, SL_VAR, var, SLE_BOOL, 1, from, to, extra), name, flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback) #define SDTG_LIST(name, type, flags, var, def, length, from, to, cat, extra, startup)\ - NSD(List, SLEG_GENERAL(SL_ARR, var, type, length, from, to, extra), name, flags, startup, def) + NSD(List, SLEG_GENERAL(#var, SL_ARR, var, type, length, from, to, extra), name, flags, startup, def) #define SDTG_SSTR(name, type, flags, var, def, max_length, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(String, SLEG_GENERAL(SL_STDSTR, var, type, sizeof(var), from, to, extra), name, flags, startup, def, max_length, pre_check, post_callback) + NSD(String, SLEG_GENERAL(#var, SL_STDSTR, var, type, sizeof(var), from, to, extra), name, flags, startup, def, max_length, pre_check, post_callback) #define SDTG_OMANY(name, type, flags, var, def, max, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(OneOfMany, SLEG_GENERAL(SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) + NSD(OneOfMany, SLEG_GENERAL(#var, SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, max, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) #define SDTG_MMANY(name, type, flags, var, def, full, str, strhelp, strval, pre_check, post_callback, from, to, cat, extra, startup)\ - NSD(ManyOfMany, SLEG_GENERAL(SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) + NSD(ManyOfMany, SLEG_GENERAL(#var, SL_VAR, var, type, 1, from, to, extra), name, flags, startup, def, str, strhelp, strval, cat, pre_check, post_callback, full, nullptr) #define SDTG_NULL(length, from, to)\ NSD(Null, SLEG_NULL(length, from, to))