OpenTTD Source 20260910-master-gd1af18d1b6
saveload.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
22
23#include "../stdafx.h"
24#include "../debug.h"
25#include "../station_base.h"
26#include "../thread.h"
27#include "../town.h"
28#include "../network/network.h"
29#include "../window_func.h"
30#include "../strings_func.h"
34#include "../vehicle_base.h"
35#include "../company_func.h"
37#include "../autoreplace_base.h"
38#include "../roadstop_base.h"
41#include "../statusbar_gui.h"
42#include "../fileio_func.h"
43#include "../gamelog.h"
44#include "../string_func.h"
45#include "../fios.h"
46#include "../error.h"
47#include "../strings_type.h"
48#include "../newgrf_railtype.h"
49#include "../newgrf_roadtype.h"
51#include "saveload_internal.h"
52#include "saveload_filter.h"
53
54#include <atomic>
55#ifdef __EMSCRIPTEN__
56# include <emscripten.h>
57#endif
58
59#ifdef WITH_LZO
60#include <lzo/lzo1x.h>
61#endif
62
63#if defined(WITH_ZLIB)
64#include <zlib.h>
65#endif /* WITH_ZLIB */
66
67#if defined(WITH_LIBLZMA)
68#include <lzma.h>
69#endif /* WITH_LIBLZMA */
70
71#include "table/strings.h"
72
73#include "../safeguards.h"
74
76
79
80uint32_t _ttdp_version;
83std::string _savegame_format;
85
87enum class SaveLoadAction : uint8_t {
93};
94
95enum class NeedLength : uint8_t {
99};
100
102static const size_t MEMORY_CHUNK_SIZE = 128 * 1024;
103
107 uint8_t *bufp = nullptr;
108 uint8_t *bufe = nullptr;
109 std::shared_ptr<LoadFilter> reader{};
110 size_t read = 0;
111
116 ReadBuffer(std::shared_ptr<LoadFilter> reader) : reader(std::move(reader))
117 {
118 }
119
120 inline uint8_t ReadByte()
121 {
122 if (this->bufp == this->bufe) {
123 size_t len = this->reader->Read(this->buf, lengthof(this->buf));
124 if (len == 0) SlErrorCorrupt("Unexpected end of chunk");
125
126 this->read += len;
127 this->bufp = this->buf;
128 this->bufe = this->buf + len;
129 }
130
131 return *this->bufp++;
132 }
133
138 size_t GetSize() const
139 {
140 return this->read - (this->bufe - this->bufp);
141 }
142};
143
144
147 std::vector<std::unique_ptr<uint8_t[]>> blocks{};
148 uint8_t *buf = nullptr;
149 uint8_t *bufe = nullptr;
150
155 inline void WriteByte(uint8_t b)
156 {
157 /* Are we at the end of this chunk? */
158 if (this->buf == this->bufe) {
159 this->buf = this->blocks.emplace_back(std::make_unique<uint8_t[]>(MEMORY_CHUNK_SIZE)).get();
160 this->bufe = this->buf + MEMORY_CHUNK_SIZE;
161 }
162
163 *this->buf++ = b;
164 }
165
170 void Flush(std::shared_ptr<SaveFilter> writer)
171 {
172 uint i = 0;
173 size_t t = this->GetSize();
174
175 while (t > 0) {
176 size_t to_write = std::min(MEMORY_CHUNK_SIZE, t);
177
178 writer->Write(this->blocks[i++].get(), to_write);
179 t -= to_write;
180 }
181
182 writer->Finish();
183 }
184
189 size_t GetSize() const
190 {
191 return this->blocks.size() * MEMORY_CHUNK_SIZE - (this->bufe - this->buf);
192 }
193};
194
200 bool error;
201
202 size_t obj_len;
203 int array_index, last_array_index;
205
206 std::unique_ptr<MemoryDumper> dumper;
207 std::shared_ptr<SaveFilter> sf;
208
209 std::unique_ptr<ReadBuffer> reader;
210 std::shared_ptr<LoadFilter> lf;
211
213 std::string extra_msg;
214
216};
217
219
220static const std::vector<ChunkHandlerRef> &ChunkHandlers()
221{
222 /* These define the chunks */
223 extern const ChunkHandlerTable _gamelog_chunk_handlers;
224 extern const ChunkHandlerTable _map_chunk_handlers;
225 extern const ChunkHandlerTable _misc_chunk_handlers;
226 extern const ChunkHandlerTable _name_chunk_handlers;
227 extern const ChunkHandlerTable _cheat_chunk_handlers;
228 extern const ChunkHandlerTable _setting_chunk_handlers;
229 extern const ChunkHandlerTable _company_chunk_handlers;
230 extern const ChunkHandlerTable _engine_chunk_handlers;
231 extern const ChunkHandlerTable _veh_chunk_handlers;
232 extern const ChunkHandlerTable _waypoint_chunk_handlers;
233 extern const ChunkHandlerTable _depot_chunk_handlers;
234 extern const ChunkHandlerTable _order_chunk_handlers;
235 extern const ChunkHandlerTable _town_chunk_handlers;
236 extern const ChunkHandlerTable _sign_chunk_handlers;
237 extern const ChunkHandlerTable _station_chunk_handlers;
238 extern const ChunkHandlerTable _industry_chunk_handlers;
239 extern const ChunkHandlerTable _economy_chunk_handlers;
240 extern const ChunkHandlerTable _subsidy_chunk_handlers;
241 extern const ChunkHandlerTable _cargomonitor_chunk_handlers;
242 extern const ChunkHandlerTable _goal_chunk_handlers;
243 extern const ChunkHandlerTable _story_page_chunk_handlers;
244 extern const ChunkHandlerTable _league_chunk_handlers;
245 extern const ChunkHandlerTable _ai_chunk_handlers;
246 extern const ChunkHandlerTable _game_chunk_handlers;
247 extern const ChunkHandlerTable _animated_tile_chunk_handlers;
248 extern const ChunkHandlerTable _newgrf_chunk_handlers;
249 extern const ChunkHandlerTable _group_chunk_handlers;
250 extern const ChunkHandlerTable _cargopacket_chunk_handlers;
251 extern const ChunkHandlerTable _autoreplace_chunk_handlers;
252 extern const ChunkHandlerTable _labelmaps_chunk_handlers;
253 extern const ChunkHandlerTable _linkgraph_chunk_handlers;
254 extern const ChunkHandlerTable _airport_chunk_handlers;
255 extern const ChunkHandlerTable _object_chunk_handlers;
256 extern const ChunkHandlerTable _persistent_storage_chunk_handlers;
257 extern const ChunkHandlerTable _water_region_chunk_handlers;
258 extern const ChunkHandlerTable _randomizer_chunk_handlers;
259
261 static const ChunkHandlerTable _chunk_handler_tables[] = {
262 _gamelog_chunk_handlers,
263 _map_chunk_handlers,
264 _misc_chunk_handlers,
265 _name_chunk_handlers,
266 _cheat_chunk_handlers,
267 _setting_chunk_handlers,
268 _veh_chunk_handlers,
269 _waypoint_chunk_handlers,
270 _depot_chunk_handlers,
271 _order_chunk_handlers,
272 _industry_chunk_handlers,
273 _economy_chunk_handlers,
274 _subsidy_chunk_handlers,
275 _cargomonitor_chunk_handlers,
276 _goal_chunk_handlers,
277 _story_page_chunk_handlers,
278 _league_chunk_handlers,
279 _engine_chunk_handlers,
280 _town_chunk_handlers,
281 _sign_chunk_handlers,
282 _station_chunk_handlers,
283 _company_chunk_handlers,
284 _ai_chunk_handlers,
285 _game_chunk_handlers,
286 _animated_tile_chunk_handlers,
287 _newgrf_chunk_handlers,
288 _group_chunk_handlers,
289 _cargopacket_chunk_handlers,
290 _autoreplace_chunk_handlers,
291 _labelmaps_chunk_handlers,
292 _linkgraph_chunk_handlers,
293 _airport_chunk_handlers,
294 _object_chunk_handlers,
295 _persistent_storage_chunk_handlers,
296 _water_region_chunk_handlers,
297 _randomizer_chunk_handlers,
298 };
299
300 static std::vector<ChunkHandlerRef> _chunk_handlers;
301
302 if (_chunk_handlers.empty()) {
303 for (auto &chunk_handler_table : _chunk_handler_tables) {
304 for (auto &chunk_handler : chunk_handler_table) {
305 _chunk_handlers.push_back(chunk_handler);
306 }
307 }
308 }
309
310 return _chunk_handlers;
311}
312
314static void SlNullPointers()
315{
316 _sl.action = SaveLoadAction::Null;
317
318 /* We don't want any savegame conversion code to run
319 * during NULLing; especially those that try to get
320 * pointers from other pools. */
322
323 for (const ChunkHandler &ch : ChunkHandlers()) {
324 Debug(Facility::Sl, Severity::Notice, "Nulling pointers for {}", ch.GetName());
325 ch.FixPointers();
326 }
327
328 assert(_sl.action == SaveLoadAction::Null);
329}
330
339[[noreturn]] void SlError(StringID string, const std::string &extra_msg)
340{
341 /* Distinguish between loading into _load_check_data vs. normal save/load. */
342 if (_sl.action == SaveLoadAction::LoadCheck) {
343 _load_check_data.error = string;
344 _load_check_data.error_msg = extra_msg;
345 } else {
346 _sl.error_str = string;
347 _sl.extra_msg = extra_msg;
348 }
349
350 /* We have to nullptr all pointers here; we might be in a state where
351 * the pointers are actually filled with indices, which means that
352 * when we access them during cleaning the pool dereferences of
353 * those indices will be made with segmentation faults as result. */
354 if (_sl.action == SaveLoadAction::Load || _sl.action == SaveLoadAction::Ptrs) SlNullPointers();
355
356 /* Logging could be active. */
357 _gamelog.StopAnyAction();
358
359 throw std::exception();
360}
361
369[[noreturn]] void SlErrorCorrupt(const std::string &msg)
370{
371 SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, msg);
372}
373
374
375typedef void (*AsyncSaveFinishProc)();
376static std::atomic<AsyncSaveFinishProc> _async_save_finish;
377static std::thread _save_thread;
378
384{
385 if (_exit_game) return;
386 while (_async_save_finish.load(std::memory_order_acquire) != nullptr) CSleep(10);
387
388 _async_save_finish.store(proc, std::memory_order_release);
389}
390
395{
396 AsyncSaveFinishProc proc = _async_save_finish.exchange(nullptr, std::memory_order_acq_rel);
397 if (proc == nullptr) return;
398
399 proc();
400
401 if (_save_thread.joinable()) {
402 _save_thread.join();
403 }
404}
405
410uint8_t SlReadByte()
411{
412 return _sl.reader->ReadByte();
413}
414
419void SlWriteByte(uint8_t b)
420{
421 _sl.dumper->WriteByte(b);
422}
423
424static inline int SlReadUint16()
425{
426 int x = SlReadByte() << 8;
427 return x | SlReadByte();
428}
429
430static inline uint32_t SlReadUint32()
431{
432 uint32_t x = SlReadUint16() << 16;
433 return x | SlReadUint16();
434}
435
436static inline uint64_t SlReadUint64()
437{
438 uint32_t x = SlReadUint32();
439 uint32_t y = SlReadUint32();
440 return static_cast<uint64_t>(x) << 32 | y;
441}
442
443static inline void SlWriteUint16(uint16_t v)
444{
445 SlWriteByte(GB(v, 8, 8));
446 SlWriteByte(GB(v, 0, 8));
447}
448
449static inline void SlWriteUint32(uint32_t v)
450{
451 SlWriteUint16(GB(v, 16, 16));
452 SlWriteUint16(GB(v, 0, 16));
453}
454
455static inline void SlWriteUint64(uint64_t x)
456{
457 SlWriteUint32(static_cast<uint32_t>(x >> 32));
458 SlWriteUint32(static_cast<uint32_t>(x));
459}
460
465static inline ChunkId SlReadChunkId()
466{
467 ChunkId label{};
468 for (uint8_t &b : label) b = SlReadByte();
469 return label;
470}
471
481static uint SlReadSimpleGamma()
482{
483 uint i = SlReadByte();
484 if (HasBit(i, 7)) {
485 i &= ~0x80;
486 if (HasBit(i, 6)) {
487 i &= ~0x40;
488 if (HasBit(i, 5)) {
489 i &= ~0x20;
490 if (HasBit(i, 4)) {
491 i &= ~0x10;
492 if (HasBit(i, 3)) {
493 SlErrorCorrupt("Unsupported gamma");
494 }
495 i = SlReadByte(); // 32 bits only.
496 }
497 i = (i << 8) | SlReadByte();
498 }
499 i = (i << 8) | SlReadByte();
500 }
501 i = (i << 8) | SlReadByte();
502 }
503 return i;
504}
505
522
523static void SlWriteSimpleGamma(size_t i)
524{
525 if (i >= (1 << 7)) {
526 if (i >= (1 << 14)) {
527 if (i >= (1 << 21)) {
528 if (i >= (1 << 28)) {
529 assert(i <= UINT32_MAX); // We can only support 32 bits for now.
530 SlWriteByte(static_cast<uint8_t>(0xF0));
531 SlWriteByte(static_cast<uint8_t>(i >> 24));
532 } else {
533 SlWriteByte(static_cast<uint8_t>(0xE0 | (i >> 24)));
534 }
535 SlWriteByte(static_cast<uint8_t>(i >> 16));
536 } else {
537 SlWriteByte(static_cast<uint8_t>(0xC0 | (i >> 16)));
538 }
539 SlWriteByte(static_cast<uint8_t>(i >> 8));
540 } else {
541 SlWriteByte(static_cast<uint8_t>(0x80 | (i >> 8)));
542 }
543 }
544 SlWriteByte(static_cast<uint8_t>(i));
545}
546
552static inline uint SlGetGammaLength(size_t i)
553{
554 return 1 + (i >= (1 << 7)) + (i >= (1 << 14)) + (i >= (1 << 21)) + (i >= (1 << 28));
555}
556
557static inline uint SlReadSparseIndex()
558{
559 return SlReadSimpleGamma();
560}
561
562static inline void SlWriteSparseIndex(uint index)
563{
564 SlWriteSimpleGamma(index);
565}
566
567static inline uint SlReadArrayLength()
568{
569 return SlReadSimpleGamma();
570}
571
572static inline void SlWriteArrayLength(size_t length)
573{
574 SlWriteSimpleGamma(length);
575}
576
577static inline uint SlGetArrayLength(size_t length)
578{
579 return SlGetGammaLength(length);
580}
581
587 static constexpr uint8_t HAS_FIELD_LENGTH_BIT = 4;
588 uint8_t storage{};
589
592
598 SavegameFileType(VarFileType file_type, bool has_field_length = false) : storage(to_underlying(file_type))
599 {
600 /* 0 is not allowed as it's the end-of-table marker, larger is not allowed due to the field length bit. */
601 assert(IsInsideMM(to_underlying(file_type), 1, 1 << HAS_FIELD_LENGTH_BIT));
602 AssignBit(this->storage, HAS_FIELD_LENGTH_BIT, has_field_length);
603 }
604
609 constexpr bool IsEnd() const { return storage == 0; }
610
615 constexpr bool HasFieldLength() const
616 {
617 assert(!this->IsEnd());
618 return HasBit(storage, HAS_FIELD_LENGTH_BIT);
619 }
620
625 constexpr VarFileType Type() const
626 {
627 assert(!this->IsEnd());
628 return static_cast<VarFileType>(GB(storage, 0, HAS_FIELD_LENGTH_BIT));
629 }
630};
631
638{
639 switch (sld.cmd) {
641 return sld.conv.file;
642
646 return { sld.conv.file, true };
647
650
654
656 return VarFileType::U8;
657
660 return { VarFileType::Struct, true };
661
662 default: NOT_REACHED();
663 }
664}
665
672static inline uint SlCalcConvMemLen(VarMemType conv)
673{
674 switch (conv) {
675 case VarMemType::Bool: return sizeof(bool);
676 case VarMemType::I8: return sizeof(int8_t);
677 case VarMemType::U8: return sizeof(uint8_t);
678 case VarMemType::I16: return sizeof(int16_t);
679 case VarMemType::U16: return sizeof(uint16_t);
680 case VarMemType::I32: return sizeof(int32_t);
681 case VarMemType::U32: return sizeof(uint32_t);
682 case VarMemType::I64: return sizeof(int64_t);
683 case VarMemType::U64: return sizeof(uint64_t);
684 case VarMemType::Null: return 0;
685 case VarMemType::Label: return sizeof(BaseLabel);
686
687 case VarMemType::Str:
688 return SlReadArrayLength();
689
690 case VarMemType::Name:
691 default:
692 NOT_REACHED();
693 }
694}
695
702static inline uint8_t SlCalcConvFileLen(VarType conv)
703{
704 switch (conv.file) {
705 case VarFileType::I8: return sizeof(int8_t);
706 case VarFileType::U8: return sizeof(uint8_t);
707 case VarFileType::I16: return sizeof(int16_t);
708 case VarFileType::U16: return sizeof(uint16_t);
709 case VarFileType::I32: return sizeof(int32_t);
710 case VarFileType::U32: return sizeof(uint32_t);
711 case VarFileType::I64: return sizeof(int64_t);
712 case VarFileType::U64: return sizeof(uint64_t);
713 case VarFileType::StringID: return sizeof(uint16_t);
714
716 return SlReadArrayLength();
717
719 default:
720 NOT_REACHED();
721 }
722}
723
728static inline size_t SlCalcRefLen()
729{
731}
732
733void SlSetArrayIndex(uint index)
734{
735 _sl.need_length = NeedLength::WantLength;
736 _sl.array_index = index;
737}
738
739static size_t _next_offs;
740
746{
747 /* After reading in the whole array inside the loop
748 * we must have read in all the data, so we must be at end of current block. */
749 if (_next_offs != 0 && _sl.reader->GetSize() != _next_offs) {
750 SlErrorCorruptFmt("Invalid chunk size iterating array - expected to be at position {}, actually at {}", _next_offs, _sl.reader->GetSize());
751 }
752
753 for (;;) {
754 uint length = SlReadArrayLength();
755 if (length == 0) {
756 assert(!_sl.expect_table_header);
757 _next_offs = 0;
758 return -1;
759 }
760
761 _sl.obj_len = --length;
762 _next_offs = _sl.reader->GetSize() + length;
763
764 if (_sl.expect_table_header) {
765 _sl.expect_table_header = false;
766 return INT32_MAX;
767 }
768
769 int index;
770 switch (_sl.chunk_type) {
772 case ChunkType::SparseArray: index = static_cast<int>(SlReadSparseIndex()); break;
773 case ChunkType::Table:
774 case ChunkType::Array: index = _sl.array_index++; break;
775 default:
776 Debug(Facility::Sl, Severity::Critical, "SlIterateArray error");
777 return -1; // error
778 }
779
780 if (length != 0) return index;
781 }
782}
783
788{
789 while (SlIterateArray() != -1) {
790 SlSkipBytes(_next_offs - _sl.reader->GetSize());
791 }
792}
793
799void SlSetLength(size_t length)
800{
801 assert(_sl.action == SaveLoadAction::Save);
802
803 switch (_sl.need_length) {
805 _sl.need_length = NeedLength::None;
806 if ((_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable) && _sl.expect_table_header) {
807 _sl.expect_table_header = false;
808 SlWriteArrayLength(length + 1);
809 break;
810 }
811
812 switch (_sl.chunk_type) {
813 case ChunkType::Riff:
814 /* Ugly encoding of >16M RIFF chunks
815 * The lower 24 bits are normal
816 * The uppermost 4 bits are bits 24:27 */
817 assert(length < (1 << 28));
818 SlWriteUint32((uint32_t)((length & 0xFFFFFF) | ((length >> 24) << 28)));
819 break;
820 case ChunkType::Table:
821 case ChunkType::Array:
822 assert(_sl.last_array_index <= _sl.array_index);
823 while (++_sl.last_array_index <= _sl.array_index) {
824 SlWriteArrayLength(1);
825 }
826 SlWriteArrayLength(length + 1);
827 break;
830 SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index.
831 SlWriteSparseIndex(_sl.array_index);
832 break;
833 default: NOT_REACHED();
834 }
835 break;
836
838 _sl.obj_len += static_cast<int>(length);
839 break;
840
841 default: NOT_REACHED();
842 }
843}
844
851static void SlCopyBytes(void *ptr, size_t length)
852{
853 uint8_t *p = static_cast<uint8_t *>(ptr);
854
855 switch (_sl.action) {
858 for (; length != 0; length--) *p++ = SlReadByte();
859 break;
861 for (; length != 0; length--) SlWriteByte(*p++);
862 break;
863 default: NOT_REACHED();
864 }
865}
866
872{
873 return _sl.obj_len;
874}
875
883int64_t ReadValue(const void *ptr, VarMemType conv)
884{
885 switch (conv) {
886 case VarMemType::Bool: return (*static_cast<const bool *>(ptr) != 0);
887 case VarMemType::I8: return *static_cast<const int8_t *>(ptr);
888 case VarMemType::U8: return *static_cast<const uint8_t *>(ptr);
889 case VarMemType::I16: return *static_cast<const int16_t *>(ptr);
890 case VarMemType::U16: return *static_cast<const uint16_t *>(ptr);
891 case VarMemType::I32: return *static_cast<const int32_t *>(ptr);
892 case VarMemType::U32: return *static_cast<const uint32_t *>(ptr);
893 case VarMemType::I64: return *static_cast<const int64_t *>(ptr);
894 case VarMemType::U64: return *static_cast<const uint64_t *>(ptr);
895 case VarMemType::Null: return 0;
896 default: NOT_REACHED();
897 }
898}
899
907void WriteValue(void *ptr, VarMemType conv, int64_t val)
908{
909 switch (conv) {
910 case VarMemType::Bool: *static_cast<bool *>(ptr) = (val != 0); break;
911 case VarMemType::I8: *static_cast<int8_t *>(ptr) = val; break;
912 case VarMemType::U8: *static_cast<uint8_t *>(ptr) = val; break;
913 case VarMemType::I16: *static_cast<int16_t *>(ptr) = val; break;
914 case VarMemType::U16: *static_cast<uint16_t *>(ptr) = val; break;
915 case VarMemType::I32: *static_cast<int32_t *>(ptr) = val; break;
916 case VarMemType::U32: *static_cast<uint32_t *>(ptr) = val; break;
917 case VarMemType::I64: *static_cast<int64_t *>(ptr) = val; break;
918 case VarMemType::U64: *static_cast<uint64_t *>(ptr) = val; break;
919 case VarMemType::Name: *reinterpret_cast<std::string *>(ptr) = CopyFromOldName(static_cast<StringID>(val)); break;
920 case VarMemType::Null: break;
921 default: NOT_REACHED();
922 }
923}
924
933static void SlSaveLoadConv(void *ptr, VarType conv)
934{
935 switch (_sl.action) {
937 if (conv == VarTypes::LABEL) {
938 /* Labels are written in reverse order as that is the way GrfIDs used to be written.
939 * Changing the order means changing external applications that extract this data. */
940 BaseLabel *label = static_cast<BaseLabel *>(ptr);
941 for (auto it = label->rbegin(); it != label->rend(); it++) SlWriteByte(*it);
942 break;
943 }
944
945 int64_t x = ReadValue(ptr, conv.mem);
946
947 /* Write the value to the file and check if its value is in the desired range */
948 switch (conv.file) {
949 case VarFileType::I8:
950 assert(x >= -128 && x <= 127);
951 SlWriteByte(x);
952 break;
953
954 case VarFileType::U8:
955 assert(x >= 0 && x <= 255);
956 SlWriteByte(x);
957 break;
958
959 case VarFileType::I16:
960 assert(x >= -32768 && x <= 32767);
961 SlWriteUint16(x);
962 break;
963
965 case VarFileType::U16:
966 assert(x >= 0 && x <= 65535);
967 SlWriteUint16(x);
968 break;
969
970 case VarFileType::I32:
971 case VarFileType::U32:
972 SlWriteUint32(static_cast<uint32_t>(x));
973 break;
974
975 case VarFileType::I64:
976 case VarFileType::U64:
977 SlWriteUint64(x);
978 break;
979
980 default: NOT_REACHED();
981 }
982 break;
983 }
986 if (conv == VarTypes::LABEL) {
987 /* Labels are written in reverse order as that is the way GrfIDs used to be written.
988 * Changing the order means changing external applications that extract this data.
989 * The road/rail type labels were in forward order. They are fixed when needed in
990 * their respective loaders. */
991 BaseLabel *label = static_cast<BaseLabel *>(ptr);
992 for (auto it = label->rbegin(); it != label->rend(); it++) *it = SlReadByte();
993 break;
994 }
995
996 int64_t x;
997 /* Read a value from the file */
998 switch (conv.file) {
999 case VarFileType::I8: x = static_cast<int8_t>(SlReadByte()); break;
1000 case VarFileType::U8: x = static_cast<uint8_t>(SlReadByte()); break;
1001 case VarFileType::I16: x = static_cast<int16_t>(SlReadUint16()); break;
1002 case VarFileType::U16: x = static_cast<uint16_t>(SlReadUint16()); break;
1003 case VarFileType::I32: x = static_cast<int32_t>(SlReadUint32()); break;
1004 case VarFileType::U32: x = static_cast<uint32_t>(SlReadUint32()); break;
1005 case VarFileType::I64: x = static_cast<int64_t>(SlReadUint64()); break;
1006 case VarFileType::U64: x = static_cast<uint64_t>(SlReadUint64()); break;
1007 case VarFileType::StringID: x = RemapOldStringID(static_cast<StringID>(SlReadUint16())).base(); break;
1008 default: NOT_REACHED();
1009 }
1010
1011 /* Write The value to the struct. These ARE endian safe. */
1012 WriteValue(ptr, conv.mem, x);
1013 break;
1014 }
1015 case SaveLoadAction::Ptrs: break;
1016 case SaveLoadAction::Null: break;
1017 default: NOT_REACHED();
1018 }
1019}
1020
1028static inline size_t SlCalcStdStringLen(const void *ptr)
1029{
1030 const std::string *str = reinterpret_cast<const std::string *>(ptr);
1031
1032 size_t len = str->length();
1033 return len + SlGetArrayLength(len); // also include the length of the index
1034}
1035
1036
1045void FixSCCEncoded(std::string &str, bool fix_code)
1046{
1047 if (str.empty()) return;
1048
1049 /* We need to convert from old escape-style encoding to record separator encoding.
1050 * Initial `<SCC_ENCODED><STRINGID>` stays the same.
1051 *
1052 * `:<SCC_ENCODED><STRINGID>` becomes `<RS><SCC_ENCODED><STRINGID>`
1053 * `:<HEX>` becomes `<RS><SCC_ENCODED_NUMERIC><HEX>`
1054 * `:"<STRING>"` becomes `<RS><SCC_ENCODED_STRING><STRING>`
1055 */
1056 std::string result;
1057 StringBuilder builder(result);
1058
1059 bool is_encoded = false; // Set if we determine by the presence of SCC_ENCODED that the string is an encoded string.
1060 bool in_string = false; // Set if we in a string, between double-quotes.
1061 bool need_type = true; // Set if a parameter type needs to be emitted.
1062
1063 StringConsumer consumer(str);
1064 while (consumer.AnyBytesLeft()) {
1065 char32_t c;
1066 if (auto r = consumer.TryReadUtf8(); r.has_value()) {
1067 c = *r;
1068 } else {
1069 break;
1070 }
1071 if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) {
1072 builder.PutUtf8(SCC_ENCODED);
1073 need_type = false;
1074 is_encoded = true;
1075 continue;
1076 }
1077
1078 /* If the first character is not SCC_ENCODED then we don't have to do any conversion. */
1079 if (!is_encoded) return;
1080
1081 if (c == '"') {
1082 in_string = !in_string;
1083 if (in_string && need_type) {
1084 /* Started a new string parameter. */
1085 builder.PutUtf8(SCC_ENCODED_STRING);
1086 need_type = false;
1087 }
1088 continue;
1089 }
1090
1091 if (!in_string && c == ':') {
1092 builder.PutUtf8(SCC_RECORD_SEPARATOR);
1093 need_type = true;
1094 continue;
1095 }
1096 if (need_type) {
1097 /* Started a new numeric parameter. */
1099 need_type = false;
1100 }
1101
1102 builder.PutUtf8(c);
1103 }
1104
1105 str = std::move(result);
1106}
1107
1112void FixSCCEncodedNegative(std::string &str)
1113{
1114 if (str.empty()) return;
1115
1116 StringConsumer consumer(str);
1117
1118 /* Check whether this is an encoded string */
1119 if (!consumer.ReadUtf8If(SCC_ENCODED)) return;
1120
1121 std::string result;
1122 StringBuilder builder(result);
1123 builder.PutUtf8(SCC_ENCODED);
1124 while (consumer.AnyBytesLeft()) {
1125 /* Copy until next record */
1126 builder.Put(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::READ_ONE_SEPARATOR));
1127
1128 /* Check whether this is a numeric parameter */
1129 if (!consumer.ReadUtf8If(SCC_ENCODED_NUMERIC)) continue;
1131
1132 /* First try unsigned */
1133 if (auto u = consumer.TryReadIntegerBase<uint64_t>(16); u.has_value()) {
1134 builder.PutIntegerBase<uint64_t>(*u, 16);
1135 } else {
1136 /* Read as signed, store as unsigned */
1137 auto s = consumer.ReadIntegerBase<int64_t>(16);
1138 builder.PutIntegerBase<uint64_t>(static_cast<uint64_t>(s), 16);
1139 }
1140 }
1141
1142 str = std::move(result);
1143}
1144
1151void SlReadString(std::string &str, size_t length)
1152{
1153 str.resize(length);
1154 SlCopyBytes(str.data(), length);
1155}
1156
1162static void SlStdString(void *ptr, VarType conv)
1163{
1164 std::string *str = reinterpret_cast<std::string *>(ptr);
1165
1166 switch (_sl.action) {
1167 case SaveLoadAction::Save: {
1168 size_t len = str->length();
1169 SlWriteArrayLength(len);
1170 SlCopyBytes(const_cast<void *>(static_cast<const void *>(str->data())), len);
1171 break;
1172 }
1173
1175 case SaveLoadAction::Load: {
1176 size_t len = SlReadArrayLength();
1177 if (conv.mem == VarMemType::Null) {
1178 SlSkipBytes(len);
1179 return;
1180 }
1181
1182 SlReadString(*str, len);
1183
1189 }
1191 }
1192
1193 case SaveLoadAction::Ptrs: break;
1194 case SaveLoadAction::Null: break;
1195 default: NOT_REACHED();
1196 }
1197}
1198
1207static void SlCopyInternal(void *object, size_t length, VarType conv)
1208{
1209 if (conv.mem == VarMemType::Null) {
1210 assert(_sl.action != SaveLoadAction::Save); // Use SaveLoadType::Null if you want to write null-bytes
1211 SlSkipBytes(length * SlCalcConvFileLen(conv));
1212 return;
1213 }
1214
1215 /* NOTICE - handle some buggy stuff, in really old versions everything was saved
1216 * as a byte-type. So detect this, and adjust object size accordingly */
1218 /* all objects except difficulty settings */
1219 if (conv == VarTypes::I16 || conv == VarTypes::U16 || conv == VarTypes::STRINGID ||
1220 conv == VarTypes::I32 || conv == VarTypes::U32) {
1221 SlCopyBytes(object, length * SlCalcConvFileLen(conv));
1222 return;
1223 }
1224 /* used for conversion of Money 32bit->64bit */
1225 if (conv == (VarFileType::I32 | VarMemType::I64)) {
1226 for (uint i = 0; i < length; i++) {
1227 static_cast<int64_t *>(object)[i] = std::byteswap(SlReadUint32());
1228 }
1229 return;
1230 }
1231 }
1232
1233 /* If the size of elements is 1 byte both in file and memory, no special
1234 * conversion is needed, use specialized copy-copy function to speed up things */
1235 if (conv == VarTypes::I8 || conv == VarTypes::U8) {
1236 SlCopyBytes(object, length);
1237 } else {
1238 uint8_t *a = static_cast<uint8_t *>(object);
1239 uint8_t mem_size = SlCalcConvMemLen(conv.mem);
1240
1241 for (; length != 0; length --) {
1242 SlSaveLoadConv(a, conv);
1243 a += mem_size; // get size
1244 }
1245 }
1246}
1247
1256void SlCopy(void *object, size_t length, VarType conv)
1257{
1258 assert(object != nullptr); // Use SlSkipBytes instead
1259 if (_sl.action == SaveLoadAction::Ptrs || _sl.action == SaveLoadAction::Null) return;
1260
1261 /* Automatically calculate the length? */
1262 if (_sl.need_length != NeedLength::None) {
1263 SlSetLength(length * SlCalcConvFileLen(conv));
1264 /* Determine length only? */
1265 if (_sl.need_length == NeedLength::CalcLength) return;
1266 }
1267
1268 SlCopyInternal(object, length, conv);
1269}
1270
1277static inline size_t SlCalcArrayLen(size_t length, VarType conv)
1278{
1279 return SlCalcConvFileLen(conv) * length + SlGetArrayLength(length);
1280}
1281
1288static void SlArray(void *array, size_t length, VarType conv)
1289{
1290 switch (_sl.action) {
1292 SlWriteArrayLength(length);
1293 SlCopyInternal(array, length, conv);
1294 return;
1295
1297 case SaveLoadAction::Load: {
1299 size_t sv_length = SlReadArrayLength();
1300 if (conv.mem == VarMemType::Null) {
1301 /* We don't know this field, so we assume the length in the savegame is correct. */
1302 length = sv_length;
1303 } else if (sv_length != length) {
1304 /* If the SLE_ARR changes size, a savegame bump is required
1305 * and the developer should have written conversion lines.
1306 * Error out to make this more visible. */
1307 SlErrorCorrupt("Fixed-length array is of wrong length");
1308 }
1309 }
1310
1311 SlCopyInternal(array, length, conv);
1312 return;
1313 }
1314
1317 return;
1318
1319 default:
1320 NOT_REACHED();
1321 }
1322}
1323
1334static uint32_t ReferenceToInt(const void *obj, SLRefType rt)
1335{
1336 assert(_sl.action == SaveLoadAction::Save);
1337
1338 if (obj == nullptr) return 0;
1339
1340 switch (rt) {
1341 case SLRefType::OldVehicle: // Old vehicles we save as new ones
1342 case SLRefType::Vehicle: return static_cast<const Vehicle *>(obj)->index + 1;
1343 case SLRefType::Station: return static_cast<const Station *>(obj)->index + 1;
1344 case SLRefType::Town: return static_cast<const Town *>(obj)->index + 1;
1345 case SLRefType::RoadStop: return static_cast<const RoadStop *>(obj)->index + 1;
1346 case SLRefType::EngineRenew: return static_cast<const EngineRenew *>(obj)->index + 1;
1347 case SLRefType::CargoPacket: return static_cast<const CargoPacket *>(obj)->index + 1;
1348 case SLRefType::OrderList: return static_cast<const OrderList *>(obj)->index + 1;
1349 case SLRefType::Storage: return static_cast<const PersistentStorage *>(obj)->index + 1;
1350 case SLRefType::LinkGraph: return static_cast<const LinkGraph *>(obj)->index + 1;
1351 case SLRefType::LinkGraphJob: return static_cast<const LinkGraphJob *>(obj)->index + 1;
1352 default: NOT_REACHED();
1353 }
1354}
1355
1366static void *IntToReference(size_t index, SLRefType rt)
1367{
1368 static_assert(sizeof(size_t) <= sizeof(void *));
1369
1370 assert(_sl.action == SaveLoadAction::Ptrs);
1371
1372 /* After version 4.3 SLRefType::OldVehicle is saved as SLRefType::Vehicle,
1373 * and should be loaded like that */
1375 rt = SLRefType::Vehicle;
1376 }
1377
1378 /* No need to look up nullptr pointers, just return immediately */
1379 if (index == (rt == SLRefType::OldVehicle ? 0xFFFF : 0)) return nullptr;
1380
1381 /* Correct index. Old vehicles were saved differently:
1382 * invalid vehicle was 0xFFFF, now we use 0x0000 for everything invalid. */
1383 if (rt != SLRefType::OldVehicle) index--;
1384
1385 switch (rt) {
1387 if (OrderList::IsValidID(index)) return OrderList::Get(index);
1388 SlErrorCorrupt("Referencing invalid OrderList");
1389
1391 case SLRefType::Vehicle:
1392 if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
1393 SlErrorCorrupt("Referencing invalid Vehicle");
1394
1395 case SLRefType::Station:
1396 if (Station::IsValidID(index)) return Station::Get(index);
1397 SlErrorCorrupt("Referencing invalid Station");
1398
1399 case SLRefType::Town:
1400 if (Town::IsValidID(index)) return Town::Get(index);
1401 SlErrorCorrupt("Referencing invalid Town");
1402
1404 if (RoadStop::IsValidID(index)) return RoadStop::Get(index);
1405 SlErrorCorrupt("Referencing invalid RoadStop");
1406
1408 if (EngineRenew::IsValidID(index)) return EngineRenew::Get(index);
1409 SlErrorCorrupt("Referencing invalid EngineRenew");
1410
1412 if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
1413 SlErrorCorrupt("Referencing invalid CargoPacket");
1414
1415 case SLRefType::Storage:
1416 if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index);
1417 SlErrorCorrupt("Referencing invalid PersistentStorage");
1418
1420 if (LinkGraph::IsValidID(index)) return LinkGraph::Get(index);
1421 SlErrorCorrupt("Referencing invalid LinkGraph");
1422
1424 if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index);
1425 SlErrorCorrupt("Referencing invalid LinkGraphJob");
1426
1427 default: NOT_REACHED();
1428 }
1429}
1430
1436void SlSaveLoadRef(void *ptr, VarType conv)
1437{
1438 switch (_sl.action) {
1440 SlWriteUint32(ReferenceToInt(*static_cast<void **>(ptr), conv.ref));
1441 break;
1444 *static_cast<size_t *>(ptr) = IsSavegameVersionBefore(SaveLoadVersion::MoreCargoPackets) ? SlReadUint16() : SlReadUint32();
1445 break;
1447 *static_cast<void **>(ptr) = IntToReference(*static_cast<size_t *>(ptr), conv.ref);
1448 break;
1450 *static_cast<void **>(ptr) = nullptr;
1451 break;
1452 default: NOT_REACHED();
1453 }
1454}
1455
1459template <template <typename, typename> typename Tstorage, typename Tvar, typename Tallocator = std::allocator<Tvar>>
1461 typedef Tstorage<Tvar, Tallocator> SlStorageT;
1462public:
1470 static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd = SaveLoadType::Variable)
1471 {
1472 assert(cmd == SaveLoadType::Variable || cmd == SaveLoadType::Reference);
1473
1474 const SlStorageT *list = static_cast<const SlStorageT *>(storage);
1475
1476 int type_size = SlGetArrayLength(list->size());
1477 int item_size = SlCalcConvFileLen(cmd == SaveLoadType::Variable ? conv : VarType{VarFileType::U32, {}});
1478 return list->size() * item_size + type_size;
1479 }
1480
1481 static void SlSaveLoadMember(SaveLoadType cmd, Tvar *item, VarType conv)
1482 {
1483 switch (cmd) {
1484 case SaveLoadType::Variable: SlSaveLoadConv(item, conv); break;
1485 case SaveLoadType::Reference: SlSaveLoadRef(item, conv); break;
1486 case SaveLoadType::String: SlStdString(item, conv); break;
1487 default:
1488 NOT_REACHED();
1489 }
1490 }
1491
1498 static void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd = SaveLoadType::Variable)
1499 {
1500 assert(cmd == SaveLoadType::Variable || cmd == SaveLoadType::Reference || cmd == SaveLoadType::String);
1501
1502 SlStorageT *list = static_cast<SlStorageT *>(storage);
1503
1504 switch (_sl.action) {
1506 SlWriteArrayLength(list->size());
1507
1508 for (auto &item : *list) {
1509 SlSaveLoadMember(cmd, &item, conv);
1510 }
1511 break;
1512
1514 case SaveLoadAction::Load: {
1515 size_t length;
1516 switch (cmd) {
1517 case SaveLoadType::Variable: length = IsSavegameVersionBefore(SaveLoadVersion::SaveloadListLength) ? SlReadUint32() : SlReadArrayLength(); break;
1518 case SaveLoadType::Reference: length = IsSavegameVersionBefore(SaveLoadVersion::MoreCargoPackets) ? SlReadUint16() : IsSavegameVersionBefore(SaveLoadVersion::SaveloadListLength) ? SlReadUint32() : SlReadArrayLength(); break;
1519 case SaveLoadType::String: length = SlReadArrayLength(); break;
1520 default: NOT_REACHED();
1521 }
1522
1523 list->clear();
1524 if constexpr (std::is_same_v<SlStorageT, std::vector<Tvar, Tallocator>>) {
1525 list->reserve(length);
1526 }
1527
1528 /* Load each value and push to the end of the storage. */
1529 for (size_t i = 0; i < length; i++) {
1530 Tvar &data = list->emplace_back();
1531 SlSaveLoadMember(cmd, &data, conv);
1532 }
1533 break;
1534 }
1535
1537 for (auto &item : *list) {
1538 SlSaveLoadMember(cmd, &item, conv);
1539 }
1540 break;
1541
1543 list->clear();
1544 break;
1545
1546 default: NOT_REACHED();
1547 }
1548 }
1549};
1550
1557static inline size_t SlCalcRefListLen(const void *list, VarType conv)
1558{
1560}
1561
1567static void SlRefList(void *list, VarType conv)
1568{
1569 /* Automatically calculate the length? */
1570 if (_sl.need_length != NeedLength::None) {
1571 SlSetLength(SlCalcRefListLen(list, conv));
1572 /* Determine length only? */
1573 if (_sl.need_length == NeedLength::CalcLength) return;
1574 }
1575
1577}
1578
1585static size_t SlCalcRefVectorLen(const void *vector, VarType conv)
1586{
1588}
1589
1595static void SlRefVector(void *vector, VarType conv)
1596{
1597 /* Automatically calculate the length? */
1598 if (_sl.need_length != NeedLength::None) {
1599 SlSetLength(SlCalcRefVectorLen(vector, conv));
1600 /* Determine length only? */
1601 if (_sl.need_length == NeedLength::CalcLength) return;
1602 }
1603
1605}
1606
1613static inline size_t SlCalcVectorLen(const void *vector, VarType conv)
1614{
1615 switch (conv.mem) {
1616 case VarMemType::Bool: NOT_REACHED(); // Not supported
1625
1626 case VarMemType::Str:
1627 /* Strings are a length-prefixed field type in the savegame table format,
1628 * these may not be directly stored in another length-prefixed container type. */
1629 NOT_REACHED();
1630
1631 default: NOT_REACHED();
1632 }
1633}
1634
1640static void SlVector(void *vector, VarType conv)
1641{
1642 switch (conv.mem) {
1643 case VarMemType::Bool: NOT_REACHED(); // Not supported
1652
1653 case VarMemType::Str:
1654 /* Strings are a length-prefixed field type in the savegame table format,
1655 * these may not be directly stored in another length-prefixed container type.
1656 * This is permitted for load-related actions, because invalid fields of this type are present
1657 * from SaveLoadVersion::CompanyAllowList up to SaveLoadVersion::CompanyAllowListV2. */
1658 assert(_sl.action != SaveLoadAction::Save);
1660 break;
1661
1662 default: NOT_REACHED();
1663 }
1664}
1665
1671static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld)
1672{
1673 return (_sl_version >= sld.version_from && _sl_version < sld.version_to);
1674}
1675
1681static size_t SlCalcTableHeader(const SaveLoadTable &slt)
1682{
1683 size_t length = 0;
1684
1685 for (auto &sld : slt) {
1686 if (!SlIsObjectValidInSavegame(sld)) continue;
1687
1689 length += SlCalcStdStringLen(&sld.name);
1690 }
1691
1692 length += SlCalcConvFileLen(VarTypes::U8); // End-of-list entry.
1693
1694 for (auto &sld : slt) {
1695 if (!SlIsObjectValidInSavegame(sld)) continue;
1696 if (sld.cmd == SaveLoadType::StructList || sld.cmd == SaveLoadType::Struct) {
1697 length += SlCalcTableHeader(sld.handler->GetDescription());
1698 }
1699 }
1700
1701 return length;
1702}
1703
1710size_t SlCalcObjLength(const void *object, const SaveLoadTable &slt)
1711{
1712 size_t length = 0;
1713
1714 /* Need to determine the length and write a length tag. */
1715 for (auto &sld : slt) {
1716 length += SlCalcObjMemberLength(object, sld);
1717 }
1718 return length;
1719}
1720
1721size_t SlCalcObjMemberLength(const void *object, const SaveLoad &sld)
1722{
1723 assert(_sl.action == SaveLoadAction::Save);
1724
1725 if (!SlIsObjectValidInSavegame(sld)) return 0;
1726
1727 switch (sld.cmd) {
1730 case SaveLoadType::Array: return SlCalcArrayLen(sld.length, sld.conv);
1733 case SaveLoadType::Vector: return SlCalcVectorLen(GetVariableAddress(object, sld), sld.conv);
1735 case SaveLoadType::SaveByte: return 1; // a byte is logically of size 1
1736 case SaveLoadType::Null: return SlCalcConvFileLen(sld.conv) * sld.length;
1737
1740 NeedLength old_need_length = _sl.need_length;
1741 size_t old_obj_len = _sl.obj_len;
1742
1743 _sl.need_length = NeedLength::CalcLength;
1744 _sl.obj_len = 0;
1745
1746 /* Pretend that we are saving to collect the object size. Other
1747 * means are difficult, as we don't know the length of the list we
1748 * are about to store. */
1749 sld.handler->Save(const_cast<void *>(object));
1750 size_t length = _sl.obj_len;
1751
1752 _sl.obj_len = old_obj_len;
1753 _sl.need_length = old_need_length;
1754
1755 if (sld.cmd == SaveLoadType::Struct) {
1756 length += SlGetArrayLength(1);
1757 }
1758
1759 return length;
1760 }
1761
1762 default: NOT_REACHED();
1763 }
1764 return 0;
1765}
1766
1767static bool SlObjectMember(void *object, const SaveLoad &sld)
1768{
1769 if (!SlIsObjectValidInSavegame(sld)) return false;
1770
1771 switch (sld.cmd) {
1778 case SaveLoadType::String: {
1779 void *ptr = GetVariableAddress(object, sld);
1780
1781 switch (sld.cmd) {
1782 case SaveLoadType::Variable: SlSaveLoadConv(ptr, sld.conv); break;
1783 case SaveLoadType::Reference: SlSaveLoadRef(ptr, sld.conv); break;
1784 case SaveLoadType::Array: SlArray(ptr, sld.length, sld.conv); break;
1785 case SaveLoadType::ReferenceList: SlRefList(ptr, sld.conv); break;
1786 case SaveLoadType::ReferenceVector: SlRefVector(ptr, sld.conv); break;
1787 case SaveLoadType::Vector: SlVector(ptr, sld.conv); break;
1788 case SaveLoadType::String: SlStdString(ptr, sld.conv); break;
1789 default: NOT_REACHED();
1790 }
1791 break;
1792 }
1793
1794 /* SaveLoadType::SaveByte writes a value to the savegame to identify the type of an object.
1795 * When loading, the value is read explicitly with SlReadByte() to determine which
1796 * object description to use. */
1798 void *ptr = GetVariableAddress(object, sld);
1799
1800 switch (_sl.action) {
1801 case SaveLoadAction::Save: SlWriteByte(*static_cast<uint8_t *>(ptr)); break;
1805 case SaveLoadAction::Null: break;
1806 default: NOT_REACHED();
1807 }
1808 break;
1809 }
1810
1811 case SaveLoadType::Null: {
1812 assert(sld.conv.mem == VarMemType::Null);
1813
1814 switch (_sl.action) {
1817 case SaveLoadAction::Save: for (int i = 0; i < SlCalcConvFileLen(sld.conv) * sld.length; i++) SlWriteByte(0); break;
1819 case SaveLoadAction::Null: break;
1820 default: NOT_REACHED();
1821 }
1822 break;
1823 }
1824
1827 switch (_sl.action) {
1828 case SaveLoadAction::Save: {
1829 if (sld.cmd == SaveLoadType::Struct) {
1830 /* Store in the savegame if this struct was written or not. */
1831 SlSetStructListLength(SlCalcObjMemberLength(object, sld) > SlGetArrayLength(1) ? 1 : 0);
1832 }
1833 sld.handler->Save(object);
1834 break;
1835 }
1836
1840 }
1841 sld.handler->LoadCheck(object);
1842 break;
1843 }
1844
1845 case SaveLoadAction::Load: {
1848 }
1849 sld.handler->Load(object);
1850 break;
1851 }
1852
1854 sld.handler->FixPointers(object);
1855 break;
1856
1857 case SaveLoadAction::Null: break;
1858 default: NOT_REACHED();
1859 }
1860 break;
1861
1862 default: NOT_REACHED();
1863 }
1864 return true;
1865}
1866
1871void SlSetStructListLength(size_t length)
1872{
1873 /* Automatically calculate the length? */
1874 if (_sl.need_length != NeedLength::None) {
1875 SlSetLength(SlGetArrayLength(length));
1876 if (_sl.need_length == NeedLength::CalcLength) return;
1877 }
1878
1879 SlWriteArrayLength(length);
1880}
1881
1887size_t SlGetStructListLength(size_t limit)
1888{
1889 size_t length = SlReadArrayLength();
1890 if (length > limit) SlErrorCorrupt("List exceeds storage size");
1891
1892 return length;
1893}
1894
1900void SlObject(void *object, const SaveLoadTable &slt)
1901{
1902 /* Automatically calculate the length? */
1903 if (_sl.need_length != NeedLength::None) {
1904 SlSetLength(SlCalcObjLength(object, slt));
1905 if (_sl.need_length == NeedLength::CalcLength) return;
1906 }
1907
1908 for (auto &sld : slt) {
1909 SlObjectMember(object, sld);
1910 }
1911}
1912
1918 void Save(void *) const override
1919 {
1920 NOT_REACHED();
1921 }
1922
1923 void Load(void *object) const override
1924 {
1925 size_t length = SlGetStructListLength(UINT32_MAX);
1926 for (; length > 0; length--) {
1927 SlObject(object, this->GetLoadDescription());
1928 }
1929 }
1930
1931 void LoadCheck(void *object) const override
1932 {
1933 this->Load(object);
1934 }
1935
1937 {
1938 return {};
1939 }
1940
1942 {
1943 NOT_REACHED();
1944 }
1945};
1946
1953std::vector<SaveLoad> SlTableHeader(const SaveLoadTable &slt)
1954{
1955 /* You can only use SlTableHeader if you are a ChunkType::Table or ChunkType::SparseTable. */
1956 assert(_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
1957
1958 switch (_sl.action) {
1960 case SaveLoadAction::Load: {
1961 std::vector<SaveLoad> saveloads;
1962
1963 /* Build a key lookup mapping based on the available fields. */
1964 std::map<std::string, const SaveLoad *> key_lookup;
1965 for (auto &sld : slt) {
1966 if (!SlIsObjectValidInSavegame(sld)) continue;
1967
1968 /* Check that there is only one active SaveLoad for a given name. */
1969 assert(key_lookup.find(sld.name) == key_lookup.end());
1970 key_lookup[sld.name] = &sld;
1971 }
1972
1973 while (true) {
1974 SavegameFileType type{};
1976 if (type.IsEnd()) break;
1977
1978 std::string key;
1980
1981 auto sld_it = key_lookup.find(key);
1982 if (sld_it == key_lookup.end()) {
1983 /* SLA_LOADCHECK triggers this debug statement a lot and is perfectly normal. */
1984 Debug(Facility::Sl, _sl.action == SaveLoadAction::Load ? Severity::Warning : Severity::Debug2, "Field '{}' of type 0x{:02x} not found, skipping", key, type.storage);
1985
1986 std::shared_ptr<SaveLoadHandler> handler = nullptr;
1987 SaveLoadType saveload_type;
1988 switch (type.Type()) {
1990 saveload_type = SaveLoadType::String;
1991 break;
1992
1994 saveload_type = SaveLoadType::StructList;
1995 handler = std::make_shared<SlSkipHandler>();
1996 break;
1997
1998 default:
2000 break;
2001 }
2002
2003 /* We don't know this field, so read to nothing. */
2004 saveloads.emplace_back(std::move(key), saveload_type, type.Type() | VarMemType::Null, 1, SaveLoadVersion::MinVersion, SaveLoadVersion::MaxVersion, nullptr, 0, std::move(handler));
2005 continue;
2006 }
2007
2008 /* Validate the type of the field. If it is changed, the
2009 * savegame should have been bumped so we know how to do the
2010 * conversion. If this error triggers, that clearly didn't
2011 * happen and this is a friendly poke to the developer to bump
2012 * the savegame version and add conversion code. */
2013 SavegameFileType correct_type = GetSavegameFileType(*sld_it->second);
2014 if (correct_type.storage != type.storage) {
2015 Debug(Facility::Sl, Severity::Error, "Field type for '{}' was expected to be 0x{:02x} but 0x{:02x} was found", key, correct_type.storage, type.storage);
2016 SlErrorCorrupt("Field type is different than expected");
2017 }
2018 saveloads.emplace_back(*sld_it->second);
2019 }
2020
2021 for (auto &sld : saveloads) {
2023 sld.handler->load_description = SlTableHeader(sld.handler->GetDescription());
2024 }
2025 }
2026
2027 return saveloads;
2028 }
2029
2030 case SaveLoadAction::Save: {
2031 /* Automatically calculate the length? */
2032 if (_sl.need_length != NeedLength::None) {
2034 if (_sl.need_length == NeedLength::CalcLength) break;
2035 }
2036
2037 for (auto &sld : slt) {
2038 if (!SlIsObjectValidInSavegame(sld)) continue;
2039 /* Make sure we are not storing empty keys. */
2040 assert(!sld.name.empty());
2041
2043 assert(!type.IsEnd());
2044
2046 SlStdString(const_cast<std::string *>(&sld.name), VarTypes::STR);
2047 }
2048
2049 /* Add an end-of-header marker. */
2050 SavegameFileType type{};
2052
2053 /* After the table, write down any sub-tables we might have. */
2054 for (auto &sld : slt) {
2055 if (!SlIsObjectValidInSavegame(sld)) continue;
2057 /* SlCalcTableHeader already looks in sub-lists, so avoid the length being added twice. */
2058 NeedLength old_need_length = _sl.need_length;
2059 _sl.need_length = NeedLength::None;
2060
2061 SlTableHeader(sld.handler->GetDescription());
2062
2063 _sl.need_length = old_need_length;
2064 }
2065 }
2066
2067 break;
2068 }
2069
2070 default: NOT_REACHED();
2071 }
2072
2073 return std::vector<SaveLoad>();
2074}
2075
2089std::vector<SaveLoad> SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct)
2090{
2091 assert(_sl.action == SaveLoadAction::Load || _sl.action == SaveLoadAction::LoadCheck);
2092 /* ChunkType::Table / ChunkType::SparseTable always have a header. */
2093 if (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable) return SlTableHeader(slt);
2094
2095 std::vector<SaveLoad> saveloads;
2096
2097 /* Build a key lookup mapping based on the available fields. */
2098 std::map<std::string_view, std::vector<const SaveLoad *>> key_lookup;
2099 for (auto &sld : slt) {
2100 /* All entries should have a name; otherwise the entry should just be removed. */
2101 assert(!sld.name.empty());
2102
2103 key_lookup[sld.name].push_back(&sld);
2104 }
2105
2106 for (auto &slc : slct) {
2107 if (slc.name.empty()) {
2108 /* In old savegames there can be data we no longer care for. We
2109 * skip this by simply reading the amount of bytes indicated and
2110 * send those to /dev/null. */
2111 saveloads.emplace_back("", SaveLoadType::Null, VarFileType::U8 | VarMemType::Null, slc.null_length, slc.version_from, slc.version_to, nullptr, 0, nullptr);
2112 } else {
2113 auto sld_it = key_lookup.find(slc.name);
2114 /* If this branch triggers, it means that an entry in the
2115 * SaveLoadCompat list is not mentioned in the SaveLoad list. Did
2116 * you rename a field in one and not in the other? */
2117 if (sld_it == key_lookup.end()) {
2118 /* This isn't an assert, as that leaves no information what
2119 * field was to blame. This way at least we have breadcrumbs. */
2120 Debug(Facility::Sl, Severity::Critical, "internal error: saveload compatibility field '{}' not found", slc.name);
2121 SlErrorCorrupt("Internal error with savegame compatibility");
2122 }
2123 for (auto &sld : sld_it->second) {
2124 saveloads.push_back(*sld);
2125 }
2126 }
2127 }
2128
2129 for (auto &sld : saveloads) {
2130 if (!SlIsObjectValidInSavegame(sld)) continue;
2132 sld.handler->load_description = SlCompatTableHeader(sld.handler->GetDescription(), sld.handler->GetCompatDescription());
2133 }
2134 }
2135
2136 return saveloads;
2137}
2138
2144{
2145 SlObject(nullptr, slt);
2146}
2147
2153void SlAutolength(AutolengthProc *proc, int arg)
2154{
2155 assert(_sl.action == SaveLoadAction::Save);
2156
2157 /* Tell it to calculate the length */
2158 _sl.need_length = NeedLength::CalcLength;
2159 _sl.obj_len = 0;
2160 proc(arg);
2161
2162 /* Setup length */
2163 _sl.need_length = NeedLength::WantLength;
2164 SlSetLength(_sl.obj_len);
2165
2166 size_t start_pos = _sl.dumper->GetSize();
2167 size_t expected_offs = start_pos + _sl.obj_len;
2168
2169 /* And write the stuff */
2170 proc(arg);
2171
2172 if (expected_offs != _sl.dumper->GetSize()) {
2173 SlErrorCorruptFmt("Invalid chunk size when writing autolength block, expected {}, got {}", _sl.obj_len, _sl.dumper->GetSize() - start_pos);
2174 }
2175}
2176
2177void ChunkHandler::LoadCheck(size_t len) const
2178{
2179 switch (_sl.chunk_type) {
2180 case ChunkType::Table:
2182 SlTableHeader({});
2183 [[fallthrough]];
2184 case ChunkType::Array:
2186 SlSkipArray();
2187 break;
2188 case ChunkType::Riff:
2189 SlSkipBytes(len);
2190 break;
2191 default:
2192 NOT_REACHED();
2193 }
2194}
2195
2200static void SlLoadChunk(const ChunkHandler &ch)
2201{
2202 uint8_t m = SlReadByte();
2203
2204 _sl.chunk_type = static_cast<ChunkType>(m & to_underlying(ChunkType::FileTypeMask));
2205 _sl.obj_len = 0;
2206 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2207
2208 /* The header should always be at the start. Read the length; the
2209 * Load() should as first action process the header. */
2210 if (_sl.expect_table_header) {
2211 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2212 }
2213
2214 switch (_sl.chunk_type) {
2215 case ChunkType::Table:
2216 case ChunkType::Array:
2217 _sl.array_index = 0;
2218 ch.Load();
2219 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2220 break;
2223 ch.Load();
2224 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2225 break;
2226 case ChunkType::Riff: {
2227 /* Read length */
2228 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2229 len += SlReadUint16();
2230 _sl.obj_len = len;
2231 size_t start_pos = _sl.reader->GetSize();
2232 size_t endoffs = start_pos + len;
2233 ch.Load();
2234
2235 if (_sl.reader->GetSize() != endoffs) {
2236 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2237 }
2238 break;
2239 }
2240 default:
2241 SlErrorCorrupt("Invalid chunk type");
2242 break;
2243 }
2244
2245 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2246}
2247
2253static void SlLoadCheckChunk(const ChunkHandler &ch)
2254{
2255 uint8_t m = SlReadByte();
2256
2257 _sl.chunk_type = static_cast<ChunkType>(m & to_underlying(ChunkType::FileTypeMask));
2258 _sl.obj_len = 0;
2259 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2260
2261 /* The header should always be at the start. Read the length; the
2262 * LoadCheck() should as first action process the header. */
2263 if (_sl.expect_table_header) {
2264 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2265 }
2266
2267 switch (_sl.chunk_type) {
2268 case ChunkType::Table:
2269 case ChunkType::Array:
2270 _sl.array_index = 0;
2271 ch.LoadCheck();
2272 break;
2275 ch.LoadCheck();
2276 break;
2277 case ChunkType::Riff: {
2278 /* Read length */
2279 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2280 len += SlReadUint16();
2281 _sl.obj_len = len;
2282 size_t start_pos = _sl.reader->GetSize();
2283 size_t endoffs = start_pos + len;
2284 ch.LoadCheck(len);
2285
2286 if (_sl.reader->GetSize() != endoffs) {
2287 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2288 }
2289 break;
2290 }
2291 default:
2292 SlErrorCorrupt("Invalid chunk type");
2293 break;
2294 }
2295
2296 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2297}
2298
2304static void SlSaveChunk(const ChunkHandler &ch)
2305{
2306 if (ch.type == ChunkType::ReadOnly) return;
2307
2308 for (uint8_t b : ch.id) SlWriteByte(b);
2309 Debug(Facility::Sl, Severity::Warning, "Saving chunk {}", ch.GetName());
2310
2311 _sl.chunk_type = ch.type;
2312 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2313
2314 _sl.need_length = (_sl.expect_table_header || _sl.chunk_type == ChunkType::Riff) ? NeedLength::WantLength : NeedLength::None;
2315
2316 switch (_sl.chunk_type) {
2317 case ChunkType::Riff:
2318 ch.Save();
2319 break;
2320 case ChunkType::Table:
2321 case ChunkType::Array:
2322 _sl.last_array_index = 0;
2323 SlWriteByte(to_underlying(_sl.chunk_type));
2324 ch.Save();
2325 SlWriteArrayLength(0); // Terminate arrays
2326 break;
2329 SlWriteByte(to_underlying(_sl.chunk_type));
2330 ch.Save();
2331 SlWriteArrayLength(0); // Terminate arrays
2332 break;
2333 default: NOT_REACHED();
2334 }
2335
2336 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2337}
2338
2340static void SlSaveChunks()
2341{
2342 for (auto &ch : ChunkHandlers()) {
2343 SlSaveChunk(ch);
2344 }
2345
2346 /* Terminator */
2347 SlWriteUint32(0);
2348}
2349
2357{
2358 for (const ChunkHandler &ch : ChunkHandlers()) if (ch.id == id) return &ch;
2359 return nullptr;
2360}
2361
2363static void SlLoadChunks()
2364{
2365 for (ChunkId id = SlReadChunkId(); !id.Empty(); id = SlReadChunkId()) {
2366 Debug(Facility::Sl, Severity::Warning, "Loading chunk {}", id.AsString());
2367
2368 const ChunkHandler *ch = SlFindChunkHandler(id);
2369 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2370 SlLoadChunk(*ch);
2371 }
2372}
2373
2376{
2377 for (ChunkId id = SlReadChunkId(); !id.Empty(); id = SlReadChunkId()) {
2378 Debug(Facility::Sl, Severity::Warning, "Loading chunk {}", id.AsString());
2379
2380 const ChunkHandler *ch = SlFindChunkHandler(id);
2381 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2382 SlLoadCheckChunk(*ch);
2383 }
2384}
2385
2387static void SlFixPointers()
2388{
2389 _sl.action = SaveLoadAction::Ptrs;
2390
2391 for (const ChunkHandler &ch : ChunkHandlers()) {
2392 Debug(Facility::Sl, Severity::Notice, "Fixing pointers for {}", ch.GetName());
2393 ch.FixPointers();
2394 }
2395
2396 assert(_sl.action == SaveLoadAction::Ptrs);
2397}
2398
2399
2402 std::optional<FileHandle> file;
2403 long begin;
2404
2409 FileReader(FileHandle &&file) : LoadFilter(nullptr), file(std::move(file)), begin(ftell(*this->file))
2410 {
2411 }
2412
2414 ~FileReader() override
2415 {
2416 if (this->file.has_value()) {
2417 _game_session_stats.savegame_size = ftell(*this->file) - this->begin;
2418 }
2419 }
2420
2421 size_t Read(uint8_t *buf, size_t size) override
2422 {
2423 /* We're in the process of shutting down, i.e. in "failure" mode. */
2424 if (!this->file.has_value()) return 0;
2425
2426 return fread(buf, 1, size, *this->file);
2427 }
2428
2429 void Reset() override
2430 {
2431 clearerr(*this->file);
2432 if (fseek(*this->file, this->begin, SEEK_SET)) {
2433 Debug(Facility::Sl, Severity::Error, "Could not reset the file reading");
2434 }
2435 }
2436};
2437
2440 std::optional<FileHandle> file;
2441
2446 FileWriter(FileHandle &&file) : SaveFilter(nullptr), file(std::move(file))
2447 {
2448 }
2449
2451 ~FileWriter() override
2452 {
2453 this->Finish();
2454 }
2455
2456 void Write(const uint8_t *buf, size_t size) override
2457 {
2458 /* We're in the process of shutting down, i.e. in "failure" mode. */
2459 if (!this->file.has_value()) return;
2460
2461 if (fwrite(buf, 1, size, *this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
2462 }
2463
2464 void Finish() override
2465 {
2466 if (this->file.has_value()) {
2467 _game_session_stats.savegame_size = ftell(*this->file);
2468 this->file.reset();
2469 }
2470 }
2471};
2472
2473/*******************************************
2474 ********** START OF LZO CODE **************
2475 *******************************************/
2476
2477#ifdef WITH_LZO
2478
2480static const uint LZO_BUFFER_SIZE = 8192;
2481
2488 LZOLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2489 {
2490 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2491 }
2492
2493 size_t Read(uint8_t *buf, size_t ssize) override
2494 {
2495 assert(ssize >= LZO_BUFFER_SIZE);
2496
2497 /* Buffer size is from the LZO docs plus the chunk header size. */
2498 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2499 uint32_t tmp[2];
2500 uint32_t size;
2501 lzo_uint len = ssize;
2502
2503 /* Read header*/
2504 if (this->chain->Read((uint8_t*)tmp, sizeof(tmp)) != sizeof(tmp)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE, "File read failed");
2505
2506 /* Check if size is bad */
2507 ((uint32_t*)out)[0] = size = tmp[1];
2508
2510 tmp[0] = TO_BE32(tmp[0]);
2511 size = TO_BE32(size);
2512 }
2513
2514 if (size >= sizeof(out)) SlErrorCorrupt("Inconsistent size");
2515
2516 /* Read block */
2517 if (this->chain->Read(out + sizeof(uint32_t), size) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2518
2519 /* Verify checksum */
2520 if (tmp[0] != lzo_adler32(0, out, size + sizeof(uint32_t))) SlErrorCorrupt("Bad checksum");
2521
2522 /* Decompress */
2523 int ret = lzo1x_decompress_safe(out + sizeof(uint32_t) * 1, size, buf, &len, nullptr);
2524 if (ret != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2525 return len;
2526 }
2527};
2528
2535 LZOSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2536 {
2537 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2538 }
2539
2540 void Write(const uint8_t *buf, size_t size) override
2541 {
2542 const lzo_bytep in = buf;
2543 /* Buffer size is from the LZO docs plus the chunk header size. */
2544 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2545 uint8_t wrkmem[LZO1X_1_MEM_COMPRESS];
2546 lzo_uint outlen;
2547
2548 do {
2549 /* Compress up to LZO_BUFFER_SIZE bytes at once. */
2550 lzo_uint len = size > LZO_BUFFER_SIZE ? LZO_BUFFER_SIZE : static_cast<lzo_uint>(size);
2551 lzo1x_1_compress(in, len, out + sizeof(uint32_t) * 2, &outlen, wrkmem);
2552 ((uint32_t*)out)[1] = TO_BE32(static_cast<uint32_t>(outlen));
2553 ((uint32_t*)out)[0] = TO_BE32(lzo_adler32(0, out + sizeof(uint32_t), outlen + sizeof(uint32_t)));
2554 this->chain->Write(out, outlen + sizeof(uint32_t) * 2);
2555
2556 /* Move to next data chunk. */
2557 size -= len;
2558 in += len;
2559 } while (size > 0);
2560 }
2561};
2562
2563#endif /* WITH_LZO */
2564
2565/*********************************************
2566 ******** START OF NOCOMP CODE (uncompressed)*
2567 *********************************************/
2568
2575 NoCompLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2576 {
2577 }
2578
2579 size_t Read(uint8_t *buf, size_t size) override
2580 {
2581 return this->chain->Read(buf, size);
2582 }
2583};
2584
2591 NoCompSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2592 {
2593 }
2594
2595 void Write(const uint8_t *buf, size_t size) override
2596 {
2597 this->chain->Write(buf, size);
2598 }
2599};
2600
2601/********************************************
2602 ********** START OF ZLIB CODE **************
2603 ********************************************/
2604
2605#if defined(WITH_ZLIB)
2606
2609 z_stream z{};
2611
2616 ZlibLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2617 {
2618 if (inflateInit(&this->z) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2619 }
2620
2623 {
2624 inflateEnd(&this->z);
2625 }
2626
2627 size_t Read(uint8_t *buf, size_t size) override
2628 {
2629 this->z.next_out = buf;
2630 this->z.avail_out = static_cast<uint>(size);
2631
2632 do {
2633 /* read more bytes from the file? */
2634 if (this->z.avail_in == 0) {
2635 this->z.next_in = this->fread_buf;
2636 this->z.avail_in = static_cast<uint>(this->chain->Read(this->fread_buf, sizeof(this->fread_buf)));
2637 }
2638
2639 /* inflate the data */
2640 int r = inflate(&this->z, 0);
2641 if (r == Z_STREAM_END) break;
2642
2643 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "inflate() failed");
2644 } while (this->z.avail_out != 0);
2645
2646 return size - this->z.avail_out;
2647 }
2648};
2649
2652 z_stream z{};
2654
2660 ZlibSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain))
2661 {
2662 if (deflateInit(&this->z, compression_level) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2663 }
2664
2667 {
2668 deflateEnd(&this->z);
2669 }
2670
2677 void WriteLoop(const uint8_t *p, size_t len, int mode)
2678 {
2679 uint n;
2680 this->z.next_in = const_cast<uint8_t *>(p); // zlib does not modify the data, but is non-const for legacy reasons
2681 this->z.avail_in = static_cast<uInt>(len);
2682 do {
2683 this->z.next_out = this->fwrite_buf;
2684 this->z.avail_out = sizeof(this->fwrite_buf);
2685
2693 int r = deflate(&this->z, mode);
2694
2695 /* bytes were emitted? */
2696 if ((n = sizeof(this->fwrite_buf) - this->z.avail_out) != 0) {
2697 this->chain->Write(this->fwrite_buf, n);
2698 }
2699 if (r == Z_STREAM_END) break;
2700
2701 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "zlib returned error code");
2702 } while (this->z.avail_in || !this->z.avail_out);
2703 }
2704
2705 void Write(const uint8_t *buf, size_t size) override
2706 {
2707 this->WriteLoop(buf, size, 0);
2708 }
2709
2710 void Finish() override
2711 {
2712 this->WriteLoop(nullptr, 0, Z_FINISH);
2713 this->chain->Finish();
2714 }
2715};
2716
2717#endif /* WITH_ZLIB */
2718
2719/********************************************
2720 ********** START OF LZMA CODE **************
2721 ********************************************/
2722
2723#if defined(WITH_LIBLZMA)
2724
2731static const lzma_stream _lzma_init = LZMA_STREAM_INIT;
2732
2735 lzma_stream lzma;
2737
2742 LZMALoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain)), lzma(_lzma_init)
2743 {
2744 /* Allow saves up to 256 MB uncompressed */
2745 if (lzma_auto_decoder(&this->lzma, 1 << 28, 0) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2746 }
2747
2750 {
2751 lzma_end(&this->lzma);
2752 }
2753
2754 size_t Read(uint8_t *buf, size_t size) override
2755 {
2756 this->lzma.next_out = buf;
2757 this->lzma.avail_out = size;
2758
2759 do {
2760 /* read more bytes from the file? */
2761 if (this->lzma.avail_in == 0) {
2762 this->lzma.next_in = this->fread_buf;
2763 this->lzma.avail_in = this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2764 }
2765
2766 /* inflate the data */
2767 lzma_ret r = lzma_code(&this->lzma, LZMA_RUN);
2768 if (r == LZMA_STREAM_END) break;
2769 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2770 } while (this->lzma.avail_out != 0);
2771
2772 return size - this->lzma.avail_out;
2773 }
2774};
2775
2778 lzma_stream lzma;
2780
2786 LZMASaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain)), lzma(_lzma_init)
2787 {
2788 if (lzma_easy_encoder(&this->lzma, compression_level, LZMA_CHECK_CRC32) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2789 }
2790
2793 {
2794 lzma_end(&this->lzma);
2795 }
2796
2803 void WriteLoop(const uint8_t *p, size_t len, lzma_action action)
2804 {
2805 size_t n;
2806 this->lzma.next_in = p;
2807 this->lzma.avail_in = len;
2808 do {
2809 this->lzma.next_out = this->fwrite_buf;
2810 this->lzma.avail_out = sizeof(this->fwrite_buf);
2811
2812 lzma_ret r = lzma_code(&this->lzma, action);
2813
2814 /* bytes were emitted? */
2815 if ((n = sizeof(this->fwrite_buf) - this->lzma.avail_out) != 0) {
2816 this->chain->Write(this->fwrite_buf, n);
2817 }
2818 if (r == LZMA_STREAM_END) break;
2819 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2820 } while (this->lzma.avail_in || !this->lzma.avail_out);
2821 }
2822
2823 void Write(const uint8_t *buf, size_t size) override
2824 {
2825 this->WriteLoop(buf, size, LZMA_RUN);
2826 }
2827
2828 void Finish() override
2829 {
2830 this->WriteLoop(nullptr, 0, LZMA_FINISH);
2831 this->chain->Finish();
2832 }
2833};
2834
2835#endif /* WITH_LIBLZMA */
2836
2837/*******************************************
2838 ************* END OF CODE *****************
2839 *******************************************/
2840
2843
2846 std::shared_ptr<LoadFilter> (*init_load)(std::shared_ptr<LoadFilter> chain);
2847 std::shared_ptr<SaveFilter> (*init_write)(std::shared_ptr<SaveFilter> chain, uint8_t compression);
2848
2849 std::string_view name;
2851
2855};
2856
2861
2864#if defined(WITH_LZO)
2865 /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
2867#else
2868 {nullptr, nullptr, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2869#endif
2870 /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
2872#if defined(WITH_ZLIB)
2873 /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is
2874 * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level
2875 * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */
2877#else
2878 {nullptr, nullptr, "zlib", SAVEGAME_TAG_ZLIB, 0, 0, 0},
2879#endif
2880#if defined(WITH_LIBLZMA)
2881 /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
2882 * Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
2883 * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50%
2884 * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much.
2885 * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */
2887#else
2888 {nullptr, nullptr, "lzma", SAVEGAME_TAG_LZMA, 0, 0, 0},
2889#endif
2890};
2891
2898static std::pair<const SaveLoadFormat &, uint8_t> GetSavegameFormat(std::string_view full_name)
2899{
2900 /* Find default savegame format, the highest one with which files can be written. */
2901 auto it = std::find_if(std::rbegin(_saveload_formats), std::rend(_saveload_formats), [](const auto &slf) { return slf.init_write != nullptr; });
2902 if (it == std::rend(_saveload_formats)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "no writeable savegame formats");
2903
2904 const SaveLoadFormat &def = *it;
2905
2906 if (!full_name.empty()) {
2907 /* Get the ":..." of the compression level out of the way */
2908 size_t separator = full_name.find(':');
2909 bool has_comp_level = separator != std::string::npos;
2910 std::string_view name = has_comp_level ? full_name.substr(0, separator) : full_name;
2911
2912 for (const auto &slf : _saveload_formats) {
2913 if (slf.init_write != nullptr && name == slf.name) {
2914 if (has_comp_level) {
2915 auto complevel = full_name.substr(separator + 1);
2916
2917 /* Get the level and determine whether all went fine. */
2918 auto level = ParseInteger<uint8_t>(complevel);
2919 if (!level.has_value() || *level != Clamp(*level, slf.min_compression, slf.max_compression)) {
2921 GetEncodedString(STR_CONFIG_ERROR),
2922 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, complevel),
2924 } else {
2925 return {slf, *level};
2926 }
2927 }
2928 return {slf, slf.default_compression};
2929 }
2930 }
2931
2933 GetEncodedString(STR_CONFIG_ERROR),
2934 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, name, def.name),
2936 }
2937 return {def, def.default_compression};
2938}
2939
2940/* actual loader/saver function */
2941void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
2942extern bool AfterLoadGame();
2943extern bool LoadOldSaveGame(std::string_view file);
2944
2950static void ResetSettings()
2951{
2952 for (auto &desc : GetSaveLoadSettingTable()) {
2953 const SettingDesc *sd = GetSettingDesc(desc);
2954 if (sd->flags.Test(SettingFlag::NotInSave)) continue;
2956
2958 }
2959}
2960
2961extern void ClearOldOrders();
2962
2967{
2969 ResetTempEngineData();
2970 ClearRailTypeLabelList();
2971 ClearRoadTypeLabelList();
2972 ResetOldWaypoints();
2973 ResetSettings();
2974}
2975
2979static inline void ClearSaveLoadState()
2980{
2981 _sl.dumper = nullptr;
2982 _sl.sf = nullptr;
2983 _sl.reader = nullptr;
2984 _sl.lf = nullptr;
2985}
2986
2988static void SaveFileStart()
2989{
2990 SetMouseCursorBusy(true);
2991
2992 InvalidateWindowData(WindowClass::Statusbar, 0, SBI_SAVELOAD_START);
2993 _sl.saveinprogress = true;
2994}
2995
2997static void SaveFileDone()
2998{
2999 SetMouseCursorBusy(false);
3000
3001 InvalidateWindowData(WindowClass::Statusbar, 0, SBI_SAVELOAD_FINISH);
3002 _sl.saveinprogress = false;
3003
3004#ifdef __EMSCRIPTEN__
3005 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
3006#endif
3007}
3008
3014{
3015 _sl.error_str = str;
3016}
3017
3023{
3024 return GetEncodedString(_sl.action == SaveLoadAction::Save ? STR_ERROR_GAME_SAVE_FAILED : STR_ERROR_GAME_LOAD_FAILED);
3025}
3026
3032{
3033 return GetEncodedString(_sl.error_str, _sl.extra_msg);
3034}
3035
3042
3049static SaveLoadResult SaveFileToDisk(bool threaded)
3050{
3051 try {
3052 auto [fmt, compression] = GetSavegameFormat(_savegame_format);
3053
3054 /* We have written our stuff to memory, now write it to file! */
3055 _sl.sf->Write(fmt.tag.data(), fmt.tag.size());
3056
3057 uint32_t version = TO_BE32(to_underlying(SAVEGAME_VERSION) << 16);
3058 _sl.sf->Write(reinterpret_cast<uint8_t *>(&version), sizeof(version));
3059
3060 _sl.sf = fmt.init_write(_sl.sf, compression);
3061 _sl.dumper->Flush(_sl.sf);
3062
3064
3065 if (threaded) SetAsyncSaveFinish(SaveFileDone);
3066
3067 return SaveLoadResult::Ok;
3068 } catch (...) {
3070
3072
3073 /* We don't want to shout when saving is just
3074 * cancelled due to a client disconnecting. */
3075 if (_sl.error_str != STR_NETWORK_ERROR_LOSTCONNECTION) {
3076 Debug(Facility::Sl, Severity::Critical, "{} {}", GetSaveLoadErrorType().GetDecodedString(), GetSaveLoadErrorMessage().GetDecodedString());
3077 asfp = SaveFileError;
3078 }
3079
3080 if (threaded) {
3081 SetAsyncSaveFinish(asfp);
3082 } else {
3083 asfp();
3084 }
3085 return SaveLoadResult::Error;
3086 }
3087}
3088
3089void WaitTillSaved()
3090{
3091 if (!_save_thread.joinable()) return;
3092
3093 _save_thread.join();
3094
3095 /* Make sure every other state is handled properly as well. */
3097}
3098
3107static SaveLoadResult DoSave(std::shared_ptr<SaveFilter> writer, bool threaded)
3108{
3109 assert(!_sl.saveinprogress);
3110
3111 _sl.dumper = std::make_unique<MemoryDumper>();
3112 _sl.sf = std::move(writer);
3113
3115
3116 SaveViewportBeforeSaveGame();
3117 SlSaveChunks();
3118
3119 SaveFileStart();
3120
3121 if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) {
3122 if (threaded) Debug(Facility::Sl, Severity::Error, "Cannot create savegame thread, reverting to single-threaded mode...");
3123
3124 SaveLoadResult result = SaveFileToDisk(false);
3125 SaveFileDone();
3126
3127 return result;
3128 }
3129
3130 return SaveLoadResult::Ok;
3131}
3132
3139SaveLoadResult SaveWithFilter(std::shared_ptr<SaveFilter> writer, bool threaded)
3140{
3141 try {
3142 _sl.action = SaveLoadAction::Save;
3143 return DoSave(std::move(writer), threaded);
3144 } catch (...) {
3146 return SaveLoadResult::Error;
3147 }
3148}
3149
3158static const SaveLoadFormat *DetermineSaveLoadFormat(SaveLoadFormatTag tag, uint32_t raw_version)
3159{
3160 auto fmt = std::ranges::find(_saveload_formats, tag, &SaveLoadFormat::tag);
3161 if (fmt != std::end(_saveload_formats)) {
3162 /* Check version number */
3163 _sl_version = (SaveLoadVersion)(TO_BE32(raw_version) >> 16);
3164 /* Minor is not used anymore from version 18.0, but it is still needed
3165 * in versions before that (4 cases) which can't be removed easy.
3166 * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
3167 _sl_minor_version = (TO_BE32(raw_version) >> 8) & 0xFF;
3168
3169 Debug(Facility::Sl, Severity::Error, "Loading savegame version {}", _sl_version);
3170
3171 /* Is the version higher than the current? */
3172 if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
3174 return fmt;
3175 }
3176
3177 Debug(Facility::Sl, Severity::Critical, "Unknown savegame type, trying to load it as the buggy format");
3178 _sl.lf->Reset();
3181
3182 /* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
3183 fmt = std::ranges::find(_saveload_formats, SAVEGAME_TAG_LZO, &SaveLoadFormat::tag);
3184 if (fmt == std::end(_saveload_formats)) {
3185 /* Who removed the LZO savegame format definition? When built without LZO support,
3186 * the formats must still list it just without a method to read the file.
3187 * The caller of this function has to check for the existence of load function. */
3188 NOT_REACHED();
3189 }
3190 return fmt;
3191}
3192
3199static SaveLoadResult DoLoad(std::shared_ptr<LoadFilter> reader, bool load_check)
3200{
3201 _sl.lf = std::move(reader);
3202
3203 if (load_check) {
3204 /* Clear previous check data */
3205 _load_check_data.Clear();
3206 /* Mark SL_LOAD_CHECK as supported for this savegame. */
3207 _load_check_data.checkable = true;
3208 }
3209
3210 SaveLoadFormatTag tag{};
3211 if (_sl.lf->Read(tag.data(), tag.size()) != tag.size()) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3212
3213 uint32_t version;
3214 if (_sl.lf->Read(reinterpret_cast<uint8_t*>(&version), sizeof(version)) != sizeof(version)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3215
3216 /* see if we have any loader for this type. */
3217 const SaveLoadFormat *fmt = DetermineSaveLoadFormat(tag, version);
3218
3219 /* loader for this savegame type is not implemented? */
3220 if (fmt->init_load == nullptr) {
3221 SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, fmt::format("Loader for '{}' is not available.", fmt->name));
3222 }
3223
3224 _sl.lf = fmt->init_load(_sl.lf);
3225 _sl.reader = std::make_unique<ReadBuffer>(_sl.lf);
3226 _next_offs = 0;
3227
3228 if (!load_check) {
3230
3231 /* Old maps were hardcoded to 256x256 and thus did not contain
3232 * any mapsize information. Pre-initialize to 256x256 to not to
3233 * confuse old games */
3234 InitializeGame(256, 256, true, true);
3235
3236 _gamelog.Reset();
3237
3239 /*
3240 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
3241 * shared savegame version 4. Anything before that 'obviously'
3242 * does not have any NewGRFs. Between the introduction and
3243 * savegame version 41 (just before 0.5) the NewGRF settings
3244 * were not stored in the savegame and they were loaded by
3245 * using the settings from the main menu.
3246 * So, to recap:
3247 * - savegame version < 4: do not load any NewGRFs.
3248 * - savegame version >= 41: load NewGRFs from savegame, which is
3249 * already done at this stage by
3250 * overwriting the main menu settings.
3251 * - other savegame versions: use main menu settings.
3252 *
3253 * This means that users *can* crash savegame version 4..40
3254 * savegames if they set incompatible NewGRFs in the main menu,
3255 * but can't crash anymore for savegame version < 4 savegames.
3256 *
3257 * Note: this is done here because AfterLoadGame is also called
3258 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
3259 */
3261 }
3262 }
3263
3264 if (load_check) {
3265 /* Load chunks into _load_check_data.
3266 * No pools are loaded. References are not possible, and thus do not need resolving. */
3268 } else {
3269 /* Load chunks and resolve references */
3270 SlLoadChunks();
3271 SlFixPointers();
3272 }
3273
3275
3277
3278 if (load_check) {
3279 /* The only part from AfterLoadGame() we need */
3280 _load_check_data.grf_compatibility = IsGoodGRFConfigList(_load_check_data.grfconfig);
3281 } else {
3282 _gamelog.StartAction(GamelogActionType::Load);
3283
3284 /* After loading fix up savegame for any internal changes that
3285 * might have occurred since then. If it fails, load back the old game. */
3286 if (!AfterLoadGame()) {
3287 _gamelog.StopAction();
3289 }
3290
3291 _gamelog.StopAction();
3292 }
3293
3294 return SaveLoadResult::Ok;
3295}
3296
3302SaveLoadResult LoadWithFilter(std::shared_ptr<LoadFilter> reader)
3303{
3304 try {
3305 _sl.action = SaveLoadAction::Load;
3306 return DoLoad(std::move(reader), false);
3307 } catch (...) {
3310 }
3311}
3312
3323SaveLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
3324{
3325 /* An instance of saving is already active, so don't go saving again */
3326 if (_sl.saveinprogress && fop == SaveLoadOperation::Save && dft == DetailedFileType::GameFile && threaded) {
3327 /* if not an autosave, but a user action, show error message */
3328 if (!_do_autosave) ShowErrorMessage(GetEncodedString(STR_ERROR_SAVE_STILL_IN_PROGRESS), {}, WarningLevel::Error);
3329 return SaveLoadResult::Ok;
3330 }
3331 WaitTillSaved();
3332
3333 try {
3334 /* Load a TTDLX or TTDPatch game */
3337
3338 InitializeGame(256, 256, true, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused
3339
3340 /* TTD/TTO savegames have no NewGRFs, TTDP savegame have them
3341 * and if so a new NewGRF list will be made in LoadOldSaveGame.
3342 * Note: this is done here because AfterLoadGame is also called
3343 * for OTTD savegames which have their own NewGRF logic. */
3345 _gamelog.Reset();
3346 if (!LoadOldSaveGame(filename)) return SaveLoadResult::ReInit;
3349 _gamelog.StartAction(GamelogActionType::Load);
3350 if (!AfterLoadGame()) {
3351 _gamelog.StopAction();
3353 }
3354 _gamelog.StopAction();
3355 return SaveLoadResult::Ok;
3356 }
3357
3358 assert(dft == DetailedFileType::GameFile);
3359 switch (fop) {
3362 break;
3363
3365 _sl.action = SaveLoadAction::Load;
3366 break;
3367
3369 _sl.action = SaveLoadAction::Save;
3370 break;
3371
3372 default: NOT_REACHED();
3373 }
3374
3375 auto fh = (fop == SaveLoadOperation::Save) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
3376
3377 /* Make it a little easier to load savegames from the console */
3378 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Save);
3379 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Base);
3380 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Scenario);
3381
3382 if (!fh.has_value()) {
3383 SlError(fop == SaveLoadOperation::Save ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3384 }
3385
3386 if (fop == SaveLoadOperation::Save) { // SAVE game
3388 if (!_settings_client.gui.threaded_saves) threaded = false;
3389
3390 return DoSave(std::make_shared<FileWriter>(std::move(*fh)), threaded);
3391 }
3392
3393 /* LOAD game */
3394 assert(fop == SaveLoadOperation::Load || fop == SaveLoadOperation::Check);
3395 Debug(Facility::Desync, Severity::Error, "load: {}", filename);
3396 return DoLoad(std::make_shared<FileReader>(std::move(*fh)), fop == SaveLoadOperation::Check);
3397 } catch (...) {
3398 /* This code may be executed both for old and new save games. */
3400
3401 if (fop != SaveLoadOperation::Check) Debug(Facility::Sl, Severity::Critical, "{} {}", GetSaveLoadErrorType().GetDecodedString(), GetSaveLoadErrorMessage().GetDecodedString());
3402
3403 /* A saver/loader exception!! reinitialize all variables to prevent crash! */
3405 }
3406}
3407
3413{
3414 std::string filename;
3415
3416 if (_settings_client.gui.keep_all_autosave) {
3417 filename = GenerateDefaultSaveName() + counter.Extension();
3418 } else {
3419 filename = counter.Filename();
3420 }
3421
3422 Debug(Facility::Sl, Severity::Warning, "Autosaving to '{}'", filename);
3424 ShowErrorMessage(GetEncodedString(STR_ERROR_AUTOSAVE_FAILED), {}, WarningLevel::Error);
3425 }
3426}
3427
3428
3434
3440{
3441 /* Check if we have a name for this map, which is the name of the first
3442 * available company. When there's no company available we'll use
3443 * 'Spectator' as "company" name. */
3444 CompanyID cid = _local_company;
3445 if (!Company::IsValidID(cid)) {
3446 for (const Company *c : Company::Iterate()) {
3447 cid = c->index;
3448 break;
3449 }
3450 }
3451
3452 std::array<StringParameter, 4> params{};
3453 auto it = params.begin();
3454 *it++ = cid;
3455
3456 /* We show the current game time differently depending on the timekeeping units used by this game. */
3458 /* Insert time played. */
3459 const auto play_time = TimerGameTick::counter / Ticks::TICKS_PER_SECOND;
3460 *it++ = STR_SAVEGAME_DURATION_REALTIME;
3461 *it++ = play_time / 60 / 60;
3462 *it++ = (play_time / 60) % 60;
3463 } else {
3464 /* Insert current date */
3465 switch (_settings_client.gui.date_format_in_default_names) {
3466 case 0: *it++ = STR_JUST_DATE_LONG; break;
3467 case 1: *it++ = STR_JUST_DATE_TINY; break;
3468 case 2: *it++ = STR_JUST_DATE_ISO; break;
3469 default: NOT_REACHED();
3470 }
3471 *it++ = TimerGameEconomy::date;
3472 }
3473
3474 /* Get the correct string (special string for when there's not company) */
3475 std::string filename = GetStringWithArgs(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT, params);
3476 SanitizeFilename(filename);
3477 return filename;
3478}
3479
3486{
3489 this->ftype = FIOS_TYPE_INVALID;
3490 return;
3491 }
3492
3493 this->file_op = fop;
3494 this->ftype = ft;
3495}
3496
3502{
3503 this->SetMode(item.type);
3504 this->name = item.name;
3505 this->title = item.title;
3506}
3507
3509{
3510 assert(this->load_description.has_value());
3511 return *this->load_description;
3512}
Base class for autoreplaces/autorenews.
constexpr T AssignBit(T &x, const uint8_t y, bool value)
Assigns a bit in a variable.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
constexpr bool HasBit(const T x, const uint8_t y)
Checks if a bit in a value is set.
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
void PutUtf8(char32_t c)
Append UTF-8 char.
void Put(std::string_view str)
Append string.
void PutIntegerBase(T value, int base)
Append integer 'value' in given number 'base'.
Container for an encoded string, created by GetEncodedString.
Class for calculation jobs to be run on link graphs.
A connected component of a link graph.
Definition linkgraph.h:37
Handler for saving/loading an object to/from disk.
Definition saveload.h:518
std::optional< std::vector< SaveLoad > > load_description
Description derived from savegame being loaded.
Definition saveload.h:520
SaveLoadTable GetLoadDescription() const
Get the description for how to load the chunk.
Handler that is assigned when there is a struct read in the savegame which is not known to the code.
SaveLoadCompatTable GetCompatDescription() const override
Get the pre-header description of the fields in the savegame.
SaveLoadTable GetDescription() const override
Get the description of the fields in the savegame.
void LoadCheck(void *object) const override
Similar to load, but used only to validate savegames.
void Load(void *object) const override
Load the object from disk.
void Save(void *) const override
Save the object to disk.
Template class to help with list-like types.
static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd=SaveLoadType::Variable)
Internal templated helper to return the size in bytes of a list-like type.
static void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd=SaveLoadType::Variable)
Internal templated helper to save/load a list-like type.
Compose data into a growing std::string.
Parse data from a string / buffer.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
@ READ_ONE_SEPARATOR
Read one separator, and include it in the result.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
T ReadIntegerBase(int base, T def=0, bool clamp=false)
Read and parse an integer in number 'base', and advance the reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
std::string_view ReadUntilUtf8(char32_t c, SeparatorUsage sep)
Read data until the first occurrence of UTF-8 char 'c', and advance reader.
static constexpr TimerGameTick::Ticks TICKS_PER_SECOND
Estimation of how many ticks fit in a single second.
static Date date
Current date in days (day counter).
static bool UsingWallclockUnits(bool newgame=false)
Check if we are using wallclock units.
static DateFract date_fract
Fractional part of the day.
static TickCounter counter
Monotonic counter, in ticks, since start of game.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
Functions related to companies.
@ SCC_ENCODED
Encoded string marker and sub-string parameter.
@ SCC_ENCODED_NUMERIC
Encoded numeric parameter.
@ SCC_ENCODED_STRING
Encoded string parameter.
Functions related to debugging.
#define Debug(facility, severity, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ Desync
Desync message facility.
Definition debug_type.h:41
@ Sl
Saveload message facility.
Definition debug_type.h:39
@ Warning
Warning, wrong but okay if you don't know.
Definition debug_type.h:17
@ Notice
Notice.
Definition debug_type.h:18
@ Critical
Critical, user should know about this.
Definition debug_type.h:15
@ Debug2
Debug #2 - Low level debug messages.
Definition debug_type.h:21
@ Error
Error, but we are recovering.
Definition debug_type.h:16
Function to handling different endian machines.
constexpr std::underlying_type_t< enum_type > to_underlying(enum_type e)
Implementation of std::to_underlying (from C++23).
Definition enum_type.hpp:21
Functions related to errors.
@ Critical
Critical errors, the MessageBox is shown in all cases.
Definition error.h:25
@ Error
Errors (eg. saving/loading failed).
Definition error.h:24
void ShowErrorMessage(EncodedString &&summary_msg, int x, int y, CommandCost &cc)
Display an error message in a window.
void SanitizeFilename(std::string &filename)
Sanitizes a filename, i.e.
Definition fileio.cpp:1057
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:249
Functions for standard in/out file operations.
SaveLoadOperation
Operation performed on the file.
Definition fileio_type.h:52
@ Check
Load file for checking and/or preview.
Definition fileio_type.h:53
@ Invalid
Unknown file operation.
Definition fileio_type.h:57
@ Save
File is being saved.
Definition fileio_type.h:55
@ Load
File is being loaded.
Definition fileio_type.h:54
DetailedFileType
Kinds of files in each AbstractFileType.
Definition fileio_type.h:28
@ OldGameFile
Old save game or scenario file.
Definition fileio_type.h:30
@ GameFile
Save game or scenario file.
Definition fileio_type.h:31
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ Base
Base directory for all subdirectories.
Definition fileio_type.h:89
@ Autosave
Subdirectory of save for autosaves.
Definition fileio_type.h:91
@ Scenario
Base directory for all scenarios.
Definition fileio_type.h:92
@ Save
Base directory for all savegames.
Definition fileio_type.h:90
@ Invalid
Invalid or unknown file type.
Definition fileio_type.h:24
@ None
nothing to do
Definition fileio_type.h:18
Declarations for savegames operations.
LoadCheckData _load_check_data
Data loaded from save during SL_LOAD_CHECK.
Definition fios_gui.cpp:42
fluid_settings_t * settings
FluidSynth settings handle.
uint32_t _ttdp_version
version of TTDP savegame (if applicable)
Definition saveload.cpp:80
SaveLoadVersion _sl_version
the major savegame version identifier
Definition saveload.cpp:81
uint8_t _sl_minor_version
the minor savegame version, DO NOT USE!
Definition saveload.cpp:82
Gamelog _gamelog
Gamelog instance.
Definition gamelog.cpp:31
SavegameType _savegame_type
type of savegame we are loading
Definition saveload.cpp:77
const SaveLoadVersion SAVEGAME_VERSION
current savegame version
Functions to be called to log fundamental changes to the game.
@ Load
Game loaded.
Definition gamelog.h:20
void SetMouseCursorBusy(bool busy)
Set or unset the ZZZ cursor.
Definition gfx.cpp:1694
GameSessionStats _game_session_stats
Statistics about the current session.
Definition gfx.cpp:52
Declaration of link graph classes used for cargo distribution.
Declaration of link graph job classes used for cargo distribution.
constexpr bool IsInsideMM(const size_t x, const size_t min, const size_t max) noexcept
Checks if a value is in an interval.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
bool _networking
are we in networking mode?
Definition network.cpp:67
bool _network_server
network-server is active
Definition network.cpp:68
Basic functions/variables used all over the place.
GRFConfigList _grfconfig
First item in list of current GRF set up.
GRFListCompatibility IsGoodGRFConfigList(GRFConfigList &grfconfig)
Check if all GRFs in the GRF config from a savegame can be loaded.
void ClearGRFConfigList(GRFConfigList &config)
Clear a GRF Config list, freeing all nodes.
NewGRF handling of rail types.
NewGRF handling of road types.
uint8_t ReadByte(LoadgameState &ls)
Reads a byte from the buffer and decompress if needed.
Definition oldloader.cpp:86
Base class for roadstops.
A number of safeguards to prevent using unsafe methods.
static void SlRefVector(void *vector, VarType conv)
Save/Load a vector.
static const uint LZO_BUFFER_SIZE
Buffer size for the LZO compressor.
void SlError(StringID string, const std::string &extra_msg)
Error handler.
Definition saveload.cpp:339
void ProcessAsyncSaveFinish()
Handle async save finishes.
Definition saveload.cpp:394
void FixSCCEncodedNegative(std::string &str)
Scan the string for SCC_ENCODED_NUMERIC with negative values, and reencode them as uint64_t.
static const lzma_stream _lzma_init
Have a copy of an initialised LZMA stream.
static void * IntToReference(size_t index, SLRefType rt)
Pointers cannot be loaded from a savegame, so this function gets the index from the savegame and retu...
static SaveLoadResult DoSave(std::shared_ptr< SaveFilter > writer, bool threaded)
Actually perform the saving of the savegame.
static const SaveLoadFormat _saveload_formats[]
The different saveload formats known/understood by OpenTTD.
std::string _savegame_format
how to compress savegames
Definition saveload.cpp:83
static void SaveFileDone()
Update the gui accordingly when saving is done and release locks on saveload.
SaveLoadVersion _sl_version
the major savegame version identifier
Definition saveload.cpp:81
static SavegameFileType GetSavegameFileType(const SaveLoad &sld)
Return the type as saved/loaded inside the savegame.
Definition saveload.cpp:637
SaveLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
Main Save or Load function where the high-level saveload functions are handled.
static uint32_t ReferenceToInt(const void *obj, SLRefType rt)
Pointers cannot be saved to a savegame, so this functions gets the index of the item,...
static const std::vector< ChunkHandlerRef > & ChunkHandlers()
Definition saveload.cpp:220
static size_t SlCalcRefVectorLen(const void *vector, VarType conv)
Return the size in bytes of a vector.
static void ResetSaveloadData()
Clear temporary data that is passed between various saveload phases.
static void SlWriteSimpleGamma(size_t i)
Write the header descriptor of an object or an array.
Definition saveload.cpp:523
static size_t SlCalcTableHeader(const SaveLoadTable &slt)
Calculate the size of the table header.
static void ClearSaveLoadState()
Clear/free saveload state.
bool _do_autosave
are we doing an autosave at the moment?
Definition saveload.cpp:84
static std::atomic< AsyncSaveFinishProc > _async_save_finish
Callback to call when the savegame loading is finished.
Definition saveload.cpp:376
static std::thread _save_thread
The thread we're using to compress and write a savegame.
Definition saveload.cpp:377
std::vector< SaveLoad > SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct)
Load a table header in a savegame compatible way.
static void ResetSettings()
Reset all settings to their default, so any settings missing in the savegame are their default,...
void SlWriteByte(uint8_t b)
Wrapper for writing a byte to the dumper.
Definition saveload.cpp:419
size_t SlGetStructListLength(size_t limit)
Get the length of this list; if it exceeds the limit, error out.
Label< struct SaveLoadFormatLabelTag > SaveLoadFormatTag
Unique 4-letter tag for the different saveload formats.
static SaveLoadResult DoLoad(std::shared_ptr< LoadFilter > reader, bool load_check)
Actually perform the loading of a "non-old" savegame.
SaveLoadResult SaveWithFilter(std::shared_ptr< SaveFilter > writer, bool threaded)
Save the game using a (writer) filter.
static size_t SlCalcArrayLen(size_t length, VarType conv)
Return the size in bytes of a certain type of atomic array.
void(* AsyncSaveFinishProc)()
Callback for when the savegame loading is finished.
Definition saveload.cpp:375
static const SaveLoadFormatTag SAVEGAME_TAG_LZMA
Tag for a game with lzma compression.
int SlIterateArray()
Iterate through the elements of an array and read the whole thing.
Definition saveload.cpp:745
static const SaveLoadFormatTag SAVEGAME_TAG_LZO
Tag for a game compressed with LZO.
static void SetAsyncSaveFinish(AsyncSaveFinishProc proc)
Called by save thread to tell we finished saving.
Definition saveload.cpp:383
void SetSaveLoadError(StringID str)
Set the error message from outside of the actual loading/saving of the game (AfterLoadGame and friend...
void SlCopy(void *object, size_t length, VarType conv)
Copy a list of SaveLoadType::Variables to/from a savegame.
size_t SlGetFieldLength()
Get the length of the current object.
Definition saveload.cpp:871
void DoAutoOrNetsave(FiosNumberedSaveName &counter)
Create an autosave or netsave.
static size_t SlCalcRefLen()
Return the size in bytes of a reference (pointer).
Definition saveload.cpp:728
NeedLength
Definition saveload.cpp:95
@ WantLength
writing length and data
Definition saveload.cpp:97
@ None
not working in NeedLength mode
Definition saveload.cpp:96
@ CalcLength
need to calculate the length
Definition saveload.cpp:98
static void SaveFileStart()
Update the gui accordingly when starting saving and set locks on saveload.
static void SlNullPointers()
Null all pointers (convert index -> nullptr).
Definition saveload.cpp:314
static void SlStdString(void *ptr, VarType conv)
Save/Load a std::string.
static size_t SlCalcRefListLen(const void *list, VarType conv)
Return the size in bytes of a list.
static bool SlIsObjectValidInSavegame(const SaveLoad &sld)
Are we going to save this object or not?
EncodedString GetSaveLoadErrorType()
Return the appropriate initial string for an error depending on whether we are saving or loading.
void SlSaveLoadRef(void *ptr, VarType conv)
Handle conversion for references.
static void SlFixPointers()
Fix all pointers (convert index -> pointer).
void SlErrorCorrupt(const std::string &msg)
Error handler for corrupt savegames.
Definition saveload.cpp:369
void SlSkipArray()
Skip an array or sparse array.
Definition saveload.cpp:787
static void SlLoadChunk(const ChunkHandler &ch)
Load a chunk of data (eg vehicles, stations, etc.).
static void SlLoadCheckChunks()
Load all chunks for savegame checking.
static size_t SlCalcStdStringLen(const void *ptr)
Calculate the gross length of the string that it will occupy in the savegame.
static uint SlReadSimpleGamma()
Read in the header descriptor of an object or an array.
Definition saveload.cpp:481
SaveLoadAction
What are we currently doing?
Definition saveload.cpp:87
@ Ptrs
fixing pointers
Definition saveload.cpp:90
@ Null
null all pointers (on loading error)
Definition saveload.cpp:91
@ LoadCheck
partial loading into _load_check_data
Definition saveload.cpp:92
@ Load
loading
Definition saveload.cpp:88
static void SlCopyBytes(void *ptr, size_t length)
Save/Load bytes.
Definition saveload.cpp:851
static ChunkId SlReadChunkId()
Read the ChunkId.
Definition saveload.cpp:465
static void SlCopyInternal(void *object, size_t length, VarType conv)
Internal function to save/Load a list of SaveLoadType::Variables.
static void SlArray(void *array, size_t length, VarType conv)
Save/Load the length of the array followed by the array of SaveLoadType::Variable elements.
static void SlSaveLoadConv(void *ptr, VarType conv)
Handle all conversion and typechecking of variables here.
Definition saveload.cpp:933
static const SaveLoadFormatTag SAVEGAME_TAG_NONE
Tag for a game without compression.
void WriteValue(void *ptr, VarMemType conv, int64_t val)
Write the value of a setting.
Definition saveload.cpp:907
FileToSaveLoad _file_to_saveload
File to save or load in the openttd loop.
Definition saveload.cpp:78
static void SlLoadCheckChunk(const ChunkHandler &ch)
Load a chunk of data for checking savegames.
void SlSetLength(size_t length)
Sets the length of either a RIFF object or the number of items in an array.
Definition saveload.cpp:799
uint8_t SlReadByte()
Wrapper for reading a byte from the buffer.
Definition saveload.cpp:410
void ClearOldOrders()
Clear all old orders.
Definition order_sl.cpp:114
static SaveLoadParams _sl
Parameters used for/at saveload.
Definition saveload.cpp:218
void DoExitSave()
Do a save when exiting the game (_settings_client.gui.autosave_on_exit).
static void SlLoadChunks()
Load all chunks.
static uint8_t SlCalcConvFileLen(VarType conv)
Return the size in bytes of a certain type of normal/atomic variable as it appears in a saved game.
Definition saveload.cpp:702
static void SlSaveChunk(const ChunkHandler &ch)
Save a chunk of data (eg.
size_t SlCalcObjLength(const void *object, const SaveLoadTable &slt)
Calculate the size of an object.
void SlObject(void *object, const SaveLoadTable &slt)
Main SaveLoad function.
EncodedString GetSaveLoadErrorMessage()
Return the description of the error.
std::vector< SaveLoad > SlTableHeader(const SaveLoadTable &slt)
Save or Load a table header.
bool AfterLoadGame()
Perform a (large) amount of savegame conversion magic in order to load older savegames and to fill th...
SaveLoadResult LoadWithFilter(std::shared_ptr< LoadFilter > reader)
Load the game using a (reader) filter.
static void SlVector(void *vector, VarType conv)
Save/load a std::vector.
static void SaveFileError()
Show a gui message when saving has failed.
static SaveLoadResult SaveFileToDisk(bool threaded)
We have written the whole game into memory, _memory_savegame, now find and appropriate compressor and...
void SlGlobList(const SaveLoadTable &slt)
Save or Load (a list of) global variables.
static std::pair< const SaveLoadFormat &, uint8_t > GetSavegameFormat(std::string_view full_name)
Return the savegameformat of the game.
static uint SlCalcConvMemLen(VarMemType conv)
Return the size in bytes of a certain type of normal/atomic variable as it appears in memory.
Definition saveload.cpp:672
void FixSCCEncoded(std::string &str, bool fix_code)
Scan the string for old values of SCC_ENCODED and fix it to it's new, value.
static void SlSaveChunks()
Save all chunks.
std::string GenerateDefaultSaveName()
Get the default name for a savegame or screenshot.
static const size_t MEMORY_CHUNK_SIZE
Save in chunks of 128 KiB.
Definition saveload.cpp:102
int64_t ReadValue(const void *ptr, VarMemType conv)
Return a signed-long version of the value of a setting.
Definition saveload.cpp:883
void SlAutolength(AutolengthProc *proc, int arg)
Do something of which I have no idea what it is :P.
void SlReadString(std::string &str, size_t length)
Read the given amount of bytes from the buffer into the string.
static const SaveLoadFormatTag SAVEGAME_TAG_ZLIB
Tag for a game with zlib compression.
static const ChunkHandler * SlFindChunkHandler(ChunkId id)
Find the ChunkHandler that will be used for processing the found chunk in the savegame or in memory.
void SlSetStructListLength(size_t length)
Set the length of this list.
static uint SlGetGammaLength(size_t i)
Return how many bytes used to encode a gamma value.
Definition saveload.cpp:552
static const SaveLoadFormat * DetermineSaveLoadFormat(SaveLoadFormatTag tag, uint32_t raw_version)
Determines the SaveLoadFormat that is connected to the given tag.
static size_t SlCalcVectorLen(const void *vector, VarType conv)
Return the size in bytes of a std::vector.
static void SlRefList(void *list, VarType conv)
Save/Load a list.
VarMemType
The types/structures of data we have in memory.
Definition saveload.h:654
@ U64
A 64 bit unsigned int.
Definition saveload.h:663
@ Name
old custom name to be converted to a string pointer
Definition saveload.h:666
@ I8
A 8 bit signed int.
Definition saveload.h:656
@ U8
A 8 bit unsigned int.
Definition saveload.h:657
@ Label
A 4 character Label, stored as-is.
Definition saveload.h:667
@ Null
useful to write zeros in savegame.
Definition saveload.h:664
@ I16
A 16 bit signed int.
Definition saveload.h:658
@ Bool
A boolean value.
Definition saveload.h:655
@ U32
A 32 bit unsigned int.
Definition saveload.h:661
@ I32
A 32 bit signed int.
Definition saveload.h:660
@ I64
A 64 bit signed int.
Definition saveload.h:662
@ Str
string pointer
Definition saveload.h:665
@ U16
A 16 bit unsigned int.
Definition saveload.h:659
VarFileType
The types/structures of data that can be stored in the file.
Definition saveload.h:633
@ String
A string.
Definition saveload.h:648
@ U64
A 64 bit unsigned int.
Definition saveload.h:646
@ I8
A 8 bit signed int.
Definition saveload.h:637
@ U8
A 8 bit unsigned int.
Definition saveload.h:639
@ Struct
An arbitrary structure.
Definition saveload.h:649
@ I16
A 16 bit signed int.
Definition saveload.h:640
@ U32
A 32 bit unsigned int.
Definition saveload.h:643
@ StringID
StringID offset into strings-array.
Definition saveload.h:647
@ I32
A 32 bit signed int.
Definition saveload.h:642
@ I64
A 64 bit signed int.
Definition saveload.h:645
@ U16
A 16 bit unsigned int.
Definition saveload.h:641
SavegameType
Types of save games.
Definition saveload.h:429
@ OTTD
OTTD savegame.
Definition saveload.h:433
void SlSkipBytes(size_t length)
Read in bytes from the file/data structure but don't do anything with them, discarding them in effect...
Definition saveload.h:1430
SLRefType
Type of reference (SLE_REF, SLE_CONDREF).
Definition saveload.h:618
@ LinkGraph
Load/save a reference to a link graph.
Definition saveload.h:628
@ CargoPacket
Load/save a reference to a cargo packet.
Definition saveload.h:625
@ OrderList
Load/save a reference to an orderlist.
Definition saveload.h:626
@ Station
Load/save a reference to a station.
Definition saveload.h:620
@ OldVehicle
Load/save an old-style reference to a vehicle (for pre-4.4 savegames).
Definition saveload.h:622
@ Storage
Load/save a reference to a persistent storage.
Definition saveload.h:627
@ EngineRenew
Load/save a reference to an engine renewal (autoreplace).
Definition saveload.h:624
@ Town
Load/save a reference to a town.
Definition saveload.h:621
@ LinkGraphJob
Load/save a reference to a link graph job.
Definition saveload.h:629
@ Vehicle
Load/save a reference to a vehicle.
Definition saveload.h:619
@ RoadStop
Load/save a reference to a bus/truck stop.
Definition saveload.h:623
void * GetVariableAddress(const void *object, const SaveLoad &sld)
Get the address of the variable.
Definition saveload.h:1373
std::span< const ChunkHandlerRef > ChunkHandlerTable
A table of ChunkHandler entries.
Definition saveload.h:512
SaveLoadType
Type of data saved.
Definition saveload.h:742
@ ReferenceList
Save/load a list of SaveLoadType::Reference elements.
Definition saveload.h:751
@ String
Save/load a std::string.
Definition saveload.h:747
@ Array
Save/load a fixed-size array of SaveLoadType::Variable elements.
Definition saveload.h:749
@ Variable
Save/load a variable.
Definition saveload.h:743
@ Vector
Save/load a vector of SaveLoadType::Variable elements.
Definition saveload.h:750
@ Reference
Save/load a reference.
Definition saveload.h:744
@ StructList
Save/load a list of structs.
Definition saveload.h:752
@ Struct
Save/load a struct.
Definition saveload.h:745
@ Null
Save null-bytes and load to nowhere.
Definition saveload.h:755
@ SaveByte
Save (but not load) a byte.
Definition saveload.h:754
@ ReferenceVector
Save/load a vector of SaveLoadType::Reference elements.
Definition saveload.h:757
std::span< const struct SaveLoadCompat > SaveLoadCompatTable
A table of SaveLoadCompat entries.
Definition saveload.h:515
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1332
SaveLoadVersion
SaveLoad versions Previous savegame versions, the trunk revision where they were introduced and the r...
Definition saveload.h:33
@ EndPatchpacks
Saveload version: 286 Last known patchpack to use a version just above ours.
Definition saveload.h:325
@ MoveSccEncoded
Saveload version: 169, SVN revision: 23816 Move SCC_ENCODED to the first StringControlCode.
Definition saveload.h:249
@ TownTolerancePauseMode
Saveload version: 4.0, SVN revision: 1 Town council tolerance and pause mode.
Definition saveload.h:41
@ MoreCargoPackets
Saveload version: 69, SVN revision: 10319 Allow more than ~65k cargo packets.
Definition saveload.h:129
@ EncodedStringFormat
Saveload version: 350, GitHub pull request: 13499 Encoded String format changed.
Definition saveload.h:403
@ FixSccEncodedNegative
Saveload version: 353, GitHub pull request: 14049 Fix encoding of negative parameters.
Definition saveload.h:406
@ MinVersion
First savegame version.
Definition saveload.h:34
@ SaveloadListLength
Saveload version: 293, GitHub pull request: 9374 Consistency in list length with SaveLoadType::Struc...
Definition saveload.h:334
@ MaxVersion
Highest possible saveload version.
Definition saveload.h:425
@ StartPatchpacks
Saveload version: 220 First known patchpack to use a version just above ours.
Definition saveload.h:324
std::vector< SaveLoad > SlTableHeader(const SaveLoadTable &slt)
Save or Load a table header.
ChunkType
Type of a chunk.
Definition saveload.h:442
@ SparseTable
A SparseArray with a header describing the elements.
Definition saveload.h:447
@ ReadOnly
Chunk is never saved.
Definition saveload.h:450
@ Array
Contiguous array of elements starting at index 0.
Definition saveload.h:444
@ Table
An Array with a header describing the elements.
Definition saveload.h:446
@ FileTypeMask
All ChunkType values that are saved in the file have to be within this mask.
Definition saveload.h:449
@ Riff
4 bits store the chunk type, 28 bits the number of bytes.
Definition saveload.h:443
@ SparseArray
Array of elements with index for each element.
Definition saveload.h:445
Label< struct ChunkIdTag > ChunkId
Label/unique identifier for each of the chunks in the savegame.
Definition saveload.h:454
void SlErrorCorruptFmt(const fmt::format_string< Args... > format, Args &&... fmt_args)
Issue an SlErrorCorrupt with a format string.
Declaration of filters used for saving and loading savegames.
std::shared_ptr< SaveFilter > CreateSaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t compression_level)
Instantiator for a save filter.
std::shared_ptr< LoadFilter > CreateLoadFilter(std::shared_ptr< LoadFilter > chain)
Instantiator for a load filter.
Declaration of functions used in more save/load files.
StringID RemapOldStringID(StringID s)
Remap a string ID from the old format to the new format.
std::string CopyFromOldName(StringID id)
Copy and convert old custom names to UTF-8.
SaveLoadResult
Save or load result codes.
@ Error
error that was caught before internal structures were modified
@ Ok
completed successfully
@ ReInit
error that was caught in the middle of updating game state, need to clear it. (can only happen during...
std::span< const struct SaveLoad > SaveLoadTable
A table of SaveLoad entries.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
SettingTable GetSaveLoadSettingTable()
Create a single table with all settings that should be stored/loaded in the savegame.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Functions and types used internally for the settings configurations.
@ NotInSave
Do not save with savegame, basically client-based.
@ NoNetworkSync
Do not synchronize over network (but it is saved if SettingFlag::NotInSave is not set).
static constexpr const SettingDesc * GetSettingDesc(const SettingVariant &desc)
Helper to convert the type of the iterated settings description to a pointer to it.
Base classes/functions for stations.
Functions, definitions and such used only by the GUI.
@ SBI_SAVELOAD_FINISH
finished saving
@ SBI_SAVELOAD_START
started saving
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:261
void StrMakeValidInPlace(char *str, StringValidationSettings settings)
Scans the string for invalid characters and replaces them with a question mark '?
Definition string.cpp:157
Compose strings from textual and binary data.
Parse strings.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
Functions related to low-level strings.
@ ReplaceWithQuestionMark
Replace the unknown/bad bits with question marks.
Definition string_type.h:45
@ AllowControlCode
Allow the special control codes.
Definition string_type.h:47
EnumBitSet< StringValidationSetting, uint8_t > StringValidationSettings
Bitset of StringValidationSetting elements.
Definition string_type.h:57
void GetStringWithArgs(StringBuilder &builder, StringID string, StringParameters &args, uint case_index, bool game_script)
Get a parsed string with most special stringcodes replaced by the string parameters.
Definition strings.cpp:336
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:90
Functions related to OTTD's strings.
Types related to strings.
StrongType::Typedef< uint32_t, struct StringIDTag, StrongType::Compare, StrongType::Integer > StringID
Numeric value that represents a string, independent of the selected language.
Base for a four character label/tag/id.
constexpr bool Empty() const
Check whether the label is empty.
Container for cargo from the same location and time.
Definition cargopacket.h:41
Handlers and description of chunk.
Definition saveload.h:457
ChunkType type
Type of the chunk.
Definition saveload.h:459
virtual void LoadCheck(size_t len=0) const
Load the chunk for game preview.
ChunkId id
Unique ID (4 letters).
Definition saveload.h:458
std::string GetName() const
Get the name of this chunk.
Definition saveload.h:502
virtual void Load() const =0
Load the chunk.
virtual void Save() const
Save the chunk.
Definition saveload.h:475
Struct to store engine replacements.
size_t Read(uint8_t *buf, size_t size) override
Read a given number of bytes from the savegame.
void Reset() override
Reset this filter to read from the beginning of the file.
~FileReader() override
Make sure everything is cleaned up.
FileReader(FileHandle &&file)
Create the file reader, so it reads from a specific file.
long begin
The begin of the file.
std::optional< FileHandle > file
The file to read from.
Deals with the type of the savegame, independent of extension.
void SetMode(const FiosType &ft, SaveLoadOperation fop=SaveLoadOperation::Load)
Set the mode and file type of the file to save or load.
FiosType ftype
File type.
SaveLoadOperation file_op
File operation to perform.
std::string name
Name of the file.
EncodedString title
Internal name of the game.
void Set(const FiosItem &item)
Set the mode, title and name of the file.
std::optional< FileHandle > file
The file to write to.
void Write(const uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
~FileWriter() override
Make sure everything is cleaned up.
FileWriter(FileHandle &&file)
Create the file writer, so it writes to a specific file.
void Finish() override
Prepare everything to finish writing the savegame.
Deals with finding savegames.
Definition fios.h:86
A savegame name automatically numbered.
Definition fios.h:128
std::string Filename()
Generate a savegame name and number according to _settings_client.gui.max_num_autosaves.
Definition fios.cpp:755
std::string Extension()
Generate an extension for a savegame name.
Definition fios.cpp:765
Elements of a file system that are recognized.
Definition fileio_type.h:63
AbstractFileType abstract
Abstract file type.
Definition fileio_type.h:64
lzma_stream lzma
Stream state that we are reading from.
size_t Read(uint8_t *buf, size_t size) override
Read a given number of bytes from the savegame.
~LZMALoadFilter() override
Clean everything up.
uint8_t fread_buf[MEMORY_CHUNK_SIZE]
Buffer for reading from the file.
LZMALoadFilter(std::shared_ptr< LoadFilter > chain)
Initialise this filter.
~LZMASaveFilter() override
Clean up what we allocated.
void WriteLoop(const uint8_t *p, size_t len, lzma_action action)
Helper loop for writing the data.
void Write(const uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
void Finish() override
Prepare everything to finish writing the savegame.
LZMASaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t compression_level)
Initialise this filter.
lzma_stream lzma
Stream state that we are writing to.
uint8_t fwrite_buf[MEMORY_CHUNK_SIZE]
Buffer for writing to the file.
LZOLoadFilter(std::shared_ptr< LoadFilter > chain)
Initialise this filter.
size_t Read(uint8_t *buf, size_t ssize) override
Read a given number of bytes from the savegame.
void Write(const uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
LZOSaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t)
Initialise this filter.
A four character label/tag/id.
std::shared_ptr< LoadFilter > chain
Chained to the (savegame) filters.
LoadFilter(std::shared_ptr< LoadFilter > chain)
Initialise this filter.
Container for dumping the savegame (quickly) to memory.
Definition saveload.cpp:146
uint8_t * buf
Buffer we're going to write to.
Definition saveload.cpp:148
void WriteByte(uint8_t b)
Write a single byte into the dumper.
Definition saveload.cpp:155
std::vector< std::unique_ptr< uint8_t[]> > blocks
Buffer with blocks of allocated memory.
Definition saveload.cpp:147
uint8_t * bufe
End of the buffer we write to.
Definition saveload.cpp:149
size_t GetSize() const
Get the size of the memory dump made so far.
Definition saveload.cpp:189
void Flush(std::shared_ptr< SaveFilter > writer)
Flush this dumper into a writer.
Definition saveload.cpp:170
NoCompLoadFilter(std::shared_ptr< LoadFilter > chain)
Initialise this filter.
size_t Read(uint8_t *buf, size_t size) override
Read a given number of bytes from the savegame.
void Write(const uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
NoCompSaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t)
Initialise this filter.
Shared order list linking together the linked list of orders and the list of vehicles sharing this or...
Definition order_base.h:384
Class for pooled persistent storage of data.
static Pool::IterateWrapper< Company > Iterate(size_t from=0)
static OrderList * Get(auto index)
uint8_t * bufp
Location we're at reading the buffer.
Definition saveload.cpp:107
ReadBuffer(std::shared_ptr< LoadFilter > reader)
Initialise our variables.
Definition saveload.cpp:116
size_t read
The amount of read bytes so far from the filter.
Definition saveload.cpp:110
size_t GetSize() const
Get the size of the memory dump made so far.
Definition saveload.cpp:138
std::shared_ptr< LoadFilter > reader
The filter used to actually read.
Definition saveload.cpp:109
uint8_t buf[MEMORY_CHUNK_SIZE]
Buffer we're going to read from.
Definition saveload.cpp:106
uint8_t * bufe
End of the buffer we can read from.
Definition saveload.cpp:108
A Stop for a Road Vehicle.
SaveFilter(std::shared_ptr< SaveFilter > chain)
Initialise this filter.
std::shared_ptr< SaveFilter > chain
Chained to the (savegame) filters.
The format for a reader/writer type of a savegame.
uint8_t min_compression
the minimum compression level of this format
std::shared_ptr< SaveFilter >(* init_write)(std::shared_ptr< SaveFilter > chain, uint8_t compression)
Constructor for the save filter.
uint8_t default_compression
the default compression level of this format
std::shared_ptr< LoadFilter >(* init_load)(std::shared_ptr< LoadFilter > chain)
Constructor for the load filter.
SaveLoadFormatTag tag
the 4-letter tag by which it is identified in the savegame
std::string_view name
name of the compressor/decompressor (debug-only)
uint8_t max_compression
the maximum compression level of this format
The saveload struct, containing reader-writer functions, buffer, version, etc.
Definition saveload.cpp:196
std::unique_ptr< ReadBuffer > reader
Savegame reading buffer.
Definition saveload.cpp:209
std::shared_ptr< SaveFilter > sf
Filter to write the savegame to.
Definition saveload.cpp:207
ChunkType chunk_type
The type of chunk we are reading or writing.
Definition saveload.cpp:199
std::unique_ptr< MemoryDumper > dumper
Memory dumper to write the savegame to.
Definition saveload.cpp:206
StringID error_str
the translatable error message to show
Definition saveload.cpp:212
SaveLoadAction action
are we doing a save or a load atm.
Definition saveload.cpp:197
std::string extra_msg
the error message
Definition saveload.cpp:213
NeedLength need_length
working in NeedLength (Autolength) mode?
Definition saveload.cpp:198
bool saveinprogress
Whether there is currently a save in progress.
Definition saveload.cpp:215
std::shared_ptr< LoadFilter > lf
Filter to read the savegame from.
Definition saveload.cpp:210
bool expect_table_header
In the case of a table, if the header is saved/loaded.
Definition saveload.cpp:204
size_t obj_len
the length of the current object we are busy with
Definition saveload.cpp:202
bool error
did an error occur or not
Definition saveload.cpp:200
int last_array_index
in the case of an array, the current and last positions
Definition saveload.cpp:203
SaveLoad type struct.
Definition saveload.h:762
uint16_t length
(Conditional) length of the variable (eg. arrays) (max array size is 65536 elements).
Definition saveload.h:775
std::shared_ptr< SaveLoadHandler > handler
Custom handler for Save/Load procs.
Definition saveload.h:780
SaveLoadVersion version_to
Save/load the variable before this savegame version.
Definition saveload.h:777
SaveLoadType cmd
The action to take with the saved/loaded type, All types need different action.
Definition saveload.h:773
std::string name
Name of this field (optional, used for tables).
Definition saveload.h:772
VarType conv
Type of the variable to be saved; this field combines both FileVarType and MemVarType.
Definition saveload.h:774
SaveLoadVersion version_from
Save/load the variable starting from this savegame version.
Definition saveload.h:776
Container/wrapper for the file type that is used in tables in the save game.
Definition saveload.cpp:586
uint8_t storage
Actual storage of the file type.
Definition saveload.cpp:588
constexpr VarFileType Type() const
Get the VarType for this field.
Definition saveload.cpp:625
SavegameFileType(VarFileType file_type, bool has_field_length=false)
Create the type.
Definition saveload.cpp:598
constexpr bool HasFieldLength() const
Does this field have a length?
Definition saveload.cpp:615
constexpr bool IsEnd() const
Is this the end-of-table marker?
Definition saveload.cpp:609
static constexpr uint8_t HAS_FIELD_LENGTH_BIT
Set this bit to denote the type has a field length.
Definition saveload.cpp:587
SavegameFileType()
Create an end-of-table marker.
Definition saveload.cpp:591
Properties of config file settings.
SettingFlags flags
Handles how a setting would show up in the GUI (text/currency, etc.).
virtual void ResetToDefault(void *object) const =0
Reset the setting to its default value.
static Station * Get(auto index)
Station data structure.
Town data structure.
Definition town.h:64
Container of a variable's characteristics about a variable's storage.
Definition saveload.h:671
SLRefType ref
The reference type.
Definition saveload.h:675
VarMemType mem
The way of storing data in memory.
Definition saveload.h:673
StringValidationSettings string_validation_settings
Any settings related to validation of the strings.
Definition saveload.h:674
VarFileType file
The way of storing data in the file.
Definition saveload.h:672
static constexpr VarType U16
Store a 16 bits unsigned int.
Definition saveload.h:730
static constexpr VarType U8
Store a 8 bits unsigned int.
Definition saveload.h:728
static constexpr VarType STR
Store string.
Definition saveload.h:736
static constexpr VarType I16
Store a 16 bits signed int.
Definition saveload.h:729
static constexpr VarType I8
Store a 8 bits signed int.
Definition saveload.h:727
static constexpr VarType LABEL
Store a Label as-is.
Definition saveload.h:738
static constexpr VarType U32
Store a 32 bits unsigned int.
Definition saveload.h:732
static constexpr VarType STRINGID
Store a StringID.
Definition saveload.h:735
static constexpr VarType I32
Store a 32 bits signed int.
Definition saveload.h:731
Vehicle data structure.
size_t Read(uint8_t *buf, size_t size) override
Read a given number of bytes from the savegame.
ZlibLoadFilter(std::shared_ptr< LoadFilter > chain)
Initialise this filter.
uint8_t fread_buf[MEMORY_CHUNK_SIZE]
Buffer for reading from the file.
~ZlibLoadFilter() override
Clean everything up.
z_stream z
Stream state we are reading from.
z_stream z
Stream state we are writing to.
void Write(const uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
uint8_t fwrite_buf[MEMORY_CHUNK_SIZE]
Buffer for writing to the file.
~ZlibSaveFilter() override
Clean up what we allocated.
void Finish() override
Prepare everything to finish writing the savegame.
ZlibSaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t compression_level)
Initialise this filter.
void WriteLoop(const uint8_t *p, size_t len, int mode)
Helper loop for writing the data.
Base of all threads.
void CSleep(int milliseconds)
Sleep on the current thread for a defined time.
Definition thread.h:24
bool StartNewThread(std::thread *thr, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition thread.h:47
Definition of the game-economy-timer.
Base of the town class.
Base class for all vehicles.
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3334
Window functions not directly related to making/drawing windows.