OpenTTD Source 20260711-master-g3fb3006dff
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(sl, 3, "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
470static uint SlReadSimpleGamma()
471{
472 uint i = SlReadByte();
473 if (HasBit(i, 7)) {
474 i &= ~0x80;
475 if (HasBit(i, 6)) {
476 i &= ~0x40;
477 if (HasBit(i, 5)) {
478 i &= ~0x20;
479 if (HasBit(i, 4)) {
480 i &= ~0x10;
481 if (HasBit(i, 3)) {
482 SlErrorCorrupt("Unsupported gamma");
483 }
484 i = SlReadByte(); // 32 bits only.
485 }
486 i = (i << 8) | SlReadByte();
487 }
488 i = (i << 8) | SlReadByte();
489 }
490 i = (i << 8) | SlReadByte();
491 }
492 return i;
493}
494
511
512static void SlWriteSimpleGamma(size_t i)
513{
514 if (i >= (1 << 7)) {
515 if (i >= (1 << 14)) {
516 if (i >= (1 << 21)) {
517 if (i >= (1 << 28)) {
518 assert(i <= UINT32_MAX); // We can only support 32 bits for now.
519 SlWriteByte(static_cast<uint8_t>(0xF0));
520 SlWriteByte(static_cast<uint8_t>(i >> 24));
521 } else {
522 SlWriteByte(static_cast<uint8_t>(0xE0 | (i >> 24)));
523 }
524 SlWriteByte(static_cast<uint8_t>(i >> 16));
525 } else {
526 SlWriteByte(static_cast<uint8_t>(0xC0 | (i >> 16)));
527 }
528 SlWriteByte(static_cast<uint8_t>(i >> 8));
529 } else {
530 SlWriteByte(static_cast<uint8_t>(0x80 | (i >> 8)));
531 }
532 }
533 SlWriteByte(static_cast<uint8_t>(i));
534}
535
541static inline uint SlGetGammaLength(size_t i)
542{
543 return 1 + (i >= (1 << 7)) + (i >= (1 << 14)) + (i >= (1 << 21)) + (i >= (1 << 28));
544}
545
546static inline uint SlReadSparseIndex()
547{
548 return SlReadSimpleGamma();
549}
550
551static inline void SlWriteSparseIndex(uint index)
552{
553 SlWriteSimpleGamma(index);
554}
555
556static inline uint SlReadArrayLength()
557{
558 return SlReadSimpleGamma();
559}
560
561static inline void SlWriteArrayLength(size_t length)
562{
563 SlWriteSimpleGamma(length);
564}
565
566static inline uint SlGetArrayLength(size_t length)
567{
568 return SlGetGammaLength(length);
569}
570
576 static constexpr uint8_t HAS_FIELD_LENGTH_BIT = 4;
577 uint8_t storage{};
578
581
587 SavegameFileType(VarFileType file_type, bool has_field_length = false) : storage(to_underlying(file_type))
588 {
589 /* 0 is not allowed as it's the end-of-table marker, larger is not allowed due to the field length bit. */
590 assert(IsInsideMM(to_underlying(file_type), 1, 1 << HAS_FIELD_LENGTH_BIT));
591 AssignBit(this->storage, HAS_FIELD_LENGTH_BIT, has_field_length);
592 }
593
598 constexpr bool IsEnd() const { return storage == 0; }
599
604 constexpr bool HasFieldLength() const
605 {
606 assert(!this->IsEnd());
607 return HasBit(storage, HAS_FIELD_LENGTH_BIT);
608 }
609
614 constexpr VarFileType Type() const
615 {
616 assert(!this->IsEnd());
617 return static_cast<VarFileType>(GB(storage, 0, HAS_FIELD_LENGTH_BIT));
618 }
619};
620
627{
628 switch (sld.cmd) {
630 return sld.conv.file;
631
635 return { sld.conv.file, true };
636
639
643
645 return VarFileType::U8;
646
649 return { VarFileType::Struct, true };
650
651 default: NOT_REACHED();
652 }
653}
654
661static inline uint SlCalcConvMemLen(VarMemType conv)
662{
663 switch (conv) {
664 case VarMemType::Bool: return sizeof(bool);
665 case VarMemType::I8: return sizeof(int8_t);
666 case VarMemType::U8: return sizeof(uint8_t);
667 case VarMemType::I16: return sizeof(int16_t);
668 case VarMemType::U16: return sizeof(uint16_t);
669 case VarMemType::I32: return sizeof(int32_t);
670 case VarMemType::U32: return sizeof(uint32_t);
671 case VarMemType::I64: return sizeof(int64_t);
672 case VarMemType::U64: return sizeof(uint64_t);
673 case VarMemType::Null: return 0;
674
675 case VarMemType::Str:
676 case VarMemType::StrQ:
677 return SlReadArrayLength();
678
679 case VarMemType::Name:
680 default:
681 NOT_REACHED();
682 }
683}
684
691static inline uint8_t SlCalcConvFileLen(VarType conv)
692{
693 switch (conv.file) {
694 case VarFileType::I8: return sizeof(int8_t);
695 case VarFileType::U8: return sizeof(uint8_t);
696 case VarFileType::I16: return sizeof(int16_t);
697 case VarFileType::U16: return sizeof(uint16_t);
698 case VarFileType::I32: return sizeof(int32_t);
699 case VarFileType::U32: return sizeof(uint32_t);
700 case VarFileType::I64: return sizeof(int64_t);
701 case VarFileType::U64: return sizeof(uint64_t);
702 case VarFileType::StringID: return sizeof(uint16_t);
703
705 return SlReadArrayLength();
706
708 default:
709 NOT_REACHED();
710 }
711}
712
717static inline size_t SlCalcRefLen()
718{
720}
721
722void SlSetArrayIndex(uint index)
723{
724 _sl.need_length = NeedLength::WantLength;
725 _sl.array_index = index;
726}
727
728static size_t _next_offs;
729
735{
736 /* After reading in the whole array inside the loop
737 * we must have read in all the data, so we must be at end of current block. */
738 if (_next_offs != 0 && _sl.reader->GetSize() != _next_offs) {
739 SlErrorCorruptFmt("Invalid chunk size iterating array - expected to be at position {}, actually at {}", _next_offs, _sl.reader->GetSize());
740 }
741
742 for (;;) {
743 uint length = SlReadArrayLength();
744 if (length == 0) {
745 assert(!_sl.expect_table_header);
746 _next_offs = 0;
747 return -1;
748 }
749
750 _sl.obj_len = --length;
751 _next_offs = _sl.reader->GetSize() + length;
752
753 if (_sl.expect_table_header) {
754 _sl.expect_table_header = false;
755 return INT32_MAX;
756 }
757
758 int index;
759 switch (_sl.chunk_type) {
761 case ChunkType::SparseArray: index = static_cast<int>(SlReadSparseIndex()); break;
762 case ChunkType::Table:
763 case ChunkType::Array: index = _sl.array_index++; break;
764 default:
765 Debug(sl, 0, "SlIterateArray error");
766 return -1; // error
767 }
768
769 if (length != 0) return index;
770 }
771}
772
777{
778 while (SlIterateArray() != -1) {
779 SlSkipBytes(_next_offs - _sl.reader->GetSize());
780 }
781}
782
788void SlSetLength(size_t length)
789{
790 assert(_sl.action == SaveLoadAction::Save);
791
792 switch (_sl.need_length) {
794 _sl.need_length = NeedLength::None;
795 if ((_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable) && _sl.expect_table_header) {
796 _sl.expect_table_header = false;
797 SlWriteArrayLength(length + 1);
798 break;
799 }
800
801 switch (_sl.chunk_type) {
802 case ChunkType::Riff:
803 /* Ugly encoding of >16M RIFF chunks
804 * The lower 24 bits are normal
805 * The uppermost 4 bits are bits 24:27 */
806 assert(length < (1 << 28));
807 SlWriteUint32((uint32_t)((length & 0xFFFFFF) | ((length >> 24) << 28)));
808 break;
809 case ChunkType::Table:
810 case ChunkType::Array:
811 assert(_sl.last_array_index <= _sl.array_index);
812 while (++_sl.last_array_index <= _sl.array_index) {
813 SlWriteArrayLength(1);
814 }
815 SlWriteArrayLength(length + 1);
816 break;
819 SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index.
820 SlWriteSparseIndex(_sl.array_index);
821 break;
822 default: NOT_REACHED();
823 }
824 break;
825
827 _sl.obj_len += static_cast<int>(length);
828 break;
829
830 default: NOT_REACHED();
831 }
832}
833
840static void SlCopyBytes(void *ptr, size_t length)
841{
842 uint8_t *p = static_cast<uint8_t *>(ptr);
843
844 switch (_sl.action) {
847 for (; length != 0; length--) *p++ = SlReadByte();
848 break;
850 for (; length != 0; length--) SlWriteByte(*p++);
851 break;
852 default: NOT_REACHED();
853 }
854}
855
861{
862 return _sl.obj_len;
863}
864
872int64_t ReadValue(const void *ptr, VarMemType conv)
873{
874 switch (conv) {
875 case VarMemType::Bool: return (*static_cast<const bool *>(ptr) != 0);
876 case VarMemType::I8: return *static_cast<const int8_t *>(ptr);
877 case VarMemType::U8: return *static_cast<const uint8_t *>(ptr);
878 case VarMemType::I16: return *static_cast<const int16_t *>(ptr);
879 case VarMemType::U16: return *static_cast<const uint16_t *>(ptr);
880 case VarMemType::I32: return *static_cast<const int32_t *>(ptr);
881 case VarMemType::U32: return *static_cast<const uint32_t *>(ptr);
882 case VarMemType::I64: return *static_cast<const int64_t *>(ptr);
883 case VarMemType::U64: return *static_cast<const uint64_t *>(ptr);
884 case VarMemType::Null: return 0;
885 default: NOT_REACHED();
886 }
887}
888
896void WriteValue(void *ptr, VarMemType conv, int64_t val)
897{
898 switch (conv) {
899 case VarMemType::Bool: *static_cast<bool *>(ptr) = (val != 0); break;
900 case VarMemType::I8: *static_cast<int8_t *>(ptr) = val; break;
901 case VarMemType::U8: *static_cast<uint8_t *>(ptr) = val; break;
902 case VarMemType::I16: *static_cast<int16_t *>(ptr) = val; break;
903 case VarMemType::U16: *static_cast<uint16_t *>(ptr) = val; break;
904 case VarMemType::I32: *static_cast<int32_t *>(ptr) = val; break;
905 case VarMemType::U32: *static_cast<uint32_t *>(ptr) = val; break;
906 case VarMemType::I64: *static_cast<int64_t *>(ptr) = val; break;
907 case VarMemType::U64: *static_cast<uint64_t *>(ptr) = val; break;
908 case VarMemType::Name: *reinterpret_cast<std::string *>(ptr) = CopyFromOldName(val); break;
909 case VarMemType::Null: break;
910 default: NOT_REACHED();
911 }
912}
913
922static void SlSaveLoadConv(void *ptr, VarType conv)
923{
924 switch (_sl.action) {
926 int64_t x = ReadValue(ptr, conv.mem);
927
928 /* Write the value to the file and check if its value is in the desired range */
929 switch (conv.file) {
930 case VarFileType::I8:
931 assert(x >= -128 && x <= 127);
932 SlWriteByte(x);
933 break;
934
935 case VarFileType::U8:
936 assert(x >= 0 && x <= 255);
937 SlWriteByte(x);
938 break;
939
940 case VarFileType::I16:
941 assert(x >= -32768 && x <= 32767);
942 SlWriteUint16(x);
943 break;
944
946 case VarFileType::U16:
947 assert(x >= 0 && x <= 65535);
948 SlWriteUint16(x);
949 break;
950
951 case VarFileType::I32:
952 case VarFileType::U32:
953 SlWriteUint32(static_cast<uint32_t>(x));
954 break;
955
956 case VarFileType::I64:
957 case VarFileType::U64:
958 SlWriteUint64(x);
959 break;
960
961 default: NOT_REACHED();
962 }
963 break;
964 }
967 int64_t x;
968 /* Read a value from the file */
969 switch (conv.file) {
970 case VarFileType::I8: x = static_cast<int8_t>(SlReadByte()); break;
971 case VarFileType::U8: x = static_cast<uint8_t>(SlReadByte()); break;
972 case VarFileType::I16: x = static_cast<int16_t>(SlReadUint16()); break;
973 case VarFileType::U16: x = static_cast<uint16_t>(SlReadUint16()); break;
974 case VarFileType::I32: x = static_cast<int32_t>(SlReadUint32()); break;
975 case VarFileType::U32: x = static_cast<uint32_t>(SlReadUint32()); break;
976 case VarFileType::I64: x = static_cast<int64_t>(SlReadUint64()); break;
977 case VarFileType::U64: x = static_cast<uint64_t>(SlReadUint64()); break;
978 case VarFileType::StringID: x = RemapOldStringID(static_cast<uint16_t>(SlReadUint16())); break;
979 default: NOT_REACHED();
980 }
981
982 /* Write The value to the struct. These ARE endian safe. */
983 WriteValue(ptr, conv.mem, x);
984 break;
985 }
986 case SaveLoadAction::Ptrs: break;
987 case SaveLoadAction::Null: break;
988 default: NOT_REACHED();
989 }
990}
991
999static inline size_t SlCalcStdStringLen(const void *ptr)
1000{
1001 const std::string *str = reinterpret_cast<const std::string *>(ptr);
1002
1003 size_t len = str->length();
1004 return len + SlGetArrayLength(len); // also include the length of the index
1005}
1006
1007
1016void FixSCCEncoded(std::string &str, bool fix_code)
1017{
1018 if (str.empty()) return;
1019
1020 /* We need to convert from old escape-style encoding to record separator encoding.
1021 * Initial `<SCC_ENCODED><STRINGID>` stays the same.
1022 *
1023 * `:<SCC_ENCODED><STRINGID>` becomes `<RS><SCC_ENCODED><STRINGID>`
1024 * `:<HEX>` becomes `<RS><SCC_ENCODED_NUMERIC><HEX>`
1025 * `:"<STRING>"` becomes `<RS><SCC_ENCODED_STRING><STRING>`
1026 */
1027 std::string result;
1028 StringBuilder builder(result);
1029
1030 bool is_encoded = false; // Set if we determine by the presence of SCC_ENCODED that the string is an encoded string.
1031 bool in_string = false; // Set if we in a string, between double-quotes.
1032 bool need_type = true; // Set if a parameter type needs to be emitted.
1033
1034 StringConsumer consumer(str);
1035 while (consumer.AnyBytesLeft()) {
1036 char32_t c;
1037 if (auto r = consumer.TryReadUtf8(); r.has_value()) {
1038 c = *r;
1039 } else {
1040 break;
1041 }
1042 if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) {
1043 builder.PutUtf8(SCC_ENCODED);
1044 need_type = false;
1045 is_encoded = true;
1046 continue;
1047 }
1048
1049 /* If the first character is not SCC_ENCODED then we don't have to do any conversion. */
1050 if (!is_encoded) return;
1051
1052 if (c == '"') {
1053 in_string = !in_string;
1054 if (in_string && need_type) {
1055 /* Started a new string parameter. */
1056 builder.PutUtf8(SCC_ENCODED_STRING);
1057 need_type = false;
1058 }
1059 continue;
1060 }
1061
1062 if (!in_string && c == ':') {
1063 builder.PutUtf8(SCC_RECORD_SEPARATOR);
1064 need_type = true;
1065 continue;
1066 }
1067 if (need_type) {
1068 /* Started a new numeric parameter. */
1070 need_type = false;
1071 }
1072
1073 builder.PutUtf8(c);
1074 }
1075
1076 str = std::move(result);
1077}
1078
1083void FixSCCEncodedNegative(std::string &str)
1084{
1085 if (str.empty()) return;
1086
1087 StringConsumer consumer(str);
1088
1089 /* Check whether this is an encoded string */
1090 if (!consumer.ReadUtf8If(SCC_ENCODED)) return;
1091
1092 std::string result;
1093 StringBuilder builder(result);
1094 builder.PutUtf8(SCC_ENCODED);
1095 while (consumer.AnyBytesLeft()) {
1096 /* Copy until next record */
1097 builder.Put(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::READ_ONE_SEPARATOR));
1098
1099 /* Check whether this is a numeric parameter */
1100 if (!consumer.ReadUtf8If(SCC_ENCODED_NUMERIC)) continue;
1102
1103 /* First try unsigned */
1104 if (auto u = consumer.TryReadIntegerBase<uint64_t>(16); u.has_value()) {
1105 builder.PutIntegerBase<uint64_t>(*u, 16);
1106 } else {
1107 /* Read as signed, store as unsigned */
1108 auto s = consumer.ReadIntegerBase<int64_t>(16);
1109 builder.PutIntegerBase<uint64_t>(static_cast<uint64_t>(s), 16);
1110 }
1111 }
1112
1113 str = std::move(result);
1114}
1115
1122void SlReadString(std::string &str, size_t length)
1123{
1124 str.resize(length);
1125 SlCopyBytes(str.data(), length);
1126}
1127
1133static void SlStdString(void *ptr, VarType conv)
1134{
1135 std::string *str = reinterpret_cast<std::string *>(ptr);
1136
1137 switch (_sl.action) {
1138 case SaveLoadAction::Save: {
1139 size_t len = str->length();
1140 SlWriteArrayLength(len);
1141 SlCopyBytes(const_cast<void *>(static_cast<const void *>(str->data())), len);
1142 break;
1143 }
1144
1146 case SaveLoadAction::Load: {
1147 size_t len = SlReadArrayLength();
1148 if (conv.mem == VarMemType::Null) {
1149 SlSkipBytes(len);
1150 return;
1151 }
1152
1153 SlReadString(*str, len);
1154
1160 }
1162 }
1163
1164 case SaveLoadAction::Ptrs: break;
1165 case SaveLoadAction::Null: break;
1166 default: NOT_REACHED();
1167 }
1168}
1169
1178static void SlCopyInternal(void *object, size_t length, VarType conv)
1179{
1180 if (conv.mem == VarMemType::Null) {
1181 assert(_sl.action != SaveLoadAction::Save); // Use SaveLoadType::Null if you want to write null-bytes
1182 SlSkipBytes(length * SlCalcConvFileLen(conv));
1183 return;
1184 }
1185
1186 /* NOTICE - handle some buggy stuff, in really old versions everything was saved
1187 * as a byte-type. So detect this, and adjust object size accordingly */
1189 /* all objects except difficulty settings */
1190 if (conv == VarTypes::I16 || conv == VarTypes::U16 || conv == VarTypes::STRINGID ||
1191 conv == VarTypes::I32 || conv == VarTypes::U32) {
1192 SlCopyBytes(object, length * SlCalcConvFileLen(conv));
1193 return;
1194 }
1195 /* used for conversion of Money 32bit->64bit */
1196 if (conv == (VarFileType::I32 | VarMemType::I64)) {
1197 for (uint i = 0; i < length; i++) {
1198 static_cast<int64_t *>(object)[i] = std::byteswap(SlReadUint32());
1199 }
1200 return;
1201 }
1202 }
1203
1204 /* If the size of elements is 1 byte both in file and memory, no special
1205 * conversion is needed, use specialized copy-copy function to speed up things */
1206 if (conv == VarTypes::I8 || conv == VarTypes::U8) {
1207 SlCopyBytes(object, length);
1208 } else {
1209 uint8_t *a = static_cast<uint8_t *>(object);
1210 uint8_t mem_size = SlCalcConvMemLen(conv.mem);
1211
1212 for (; length != 0; length --) {
1213 SlSaveLoadConv(a, conv);
1214 a += mem_size; // get size
1215 }
1216 }
1217}
1218
1227void SlCopy(void *object, size_t length, VarType conv)
1228{
1229 if (_sl.action == SaveLoadAction::Ptrs || _sl.action == SaveLoadAction::Null) return;
1230
1231 /* Automatically calculate the length? */
1232 if (_sl.need_length != NeedLength::None) {
1233 SlSetLength(length * SlCalcConvFileLen(conv));
1234 /* Determine length only? */
1235 if (_sl.need_length == NeedLength::CalcLength) return;
1236 }
1237
1238 SlCopyInternal(object, length, conv);
1239}
1240
1247static inline size_t SlCalcArrayLen(size_t length, VarType conv)
1248{
1249 return SlCalcConvFileLen(conv) * length + SlGetArrayLength(length);
1250}
1251
1258static void SlArray(void *array, size_t length, VarType conv)
1259{
1260 switch (_sl.action) {
1262 SlWriteArrayLength(length);
1263 SlCopyInternal(array, length, conv);
1264 return;
1265
1267 case SaveLoadAction::Load: {
1269 size_t sv_length = SlReadArrayLength();
1270 if (conv.mem == VarMemType::Null) {
1271 /* We don't know this field, so we assume the length in the savegame is correct. */
1272 length = sv_length;
1273 } else if (sv_length != length) {
1274 /* If the SLE_ARR changes size, a savegame bump is required
1275 * and the developer should have written conversion lines.
1276 * Error out to make this more visible. */
1277 SlErrorCorrupt("Fixed-length array is of wrong length");
1278 }
1279 }
1280
1281 SlCopyInternal(array, length, conv);
1282 return;
1283 }
1284
1287 return;
1288
1289 default:
1290 NOT_REACHED();
1291 }
1292}
1293
1304static uint32_t ReferenceToInt(const void *obj, SLRefType rt)
1305{
1306 assert(_sl.action == SaveLoadAction::Save);
1307
1308 if (obj == nullptr) return 0;
1309
1310 switch (rt) {
1311 case SLRefType::OldVehicle: // Old vehicles we save as new ones
1312 case SLRefType::Vehicle: return static_cast<const Vehicle *>(obj)->index + 1;
1313 case SLRefType::Station: return static_cast<const Station *>(obj)->index + 1;
1314 case SLRefType::Town: return static_cast<const Town *>(obj)->index + 1;
1315 case SLRefType::RoadStop: return static_cast<const RoadStop *>(obj)->index + 1;
1316 case SLRefType::EngineRenew: return static_cast<const EngineRenew *>(obj)->index + 1;
1317 case SLRefType::CargoPacket: return static_cast<const CargoPacket *>(obj)->index + 1;
1318 case SLRefType::OrderList: return static_cast<const OrderList *>(obj)->index + 1;
1319 case SLRefType::Storage: return static_cast<const PersistentStorage *>(obj)->index + 1;
1320 case SLRefType::LinkGraph: return static_cast<const LinkGraph *>(obj)->index + 1;
1321 case SLRefType::LinkGraphJob: return static_cast<const LinkGraphJob *>(obj)->index + 1;
1322 default: NOT_REACHED();
1323 }
1324}
1325
1336static void *IntToReference(size_t index, SLRefType rt)
1337{
1338 static_assert(sizeof(size_t) <= sizeof(void *));
1339
1340 assert(_sl.action == SaveLoadAction::Ptrs);
1341
1342 /* After version 4.3 SLRefType::OldVehicle is saved as SLRefType::Vehicle,
1343 * and should be loaded like that */
1345 rt = SLRefType::Vehicle;
1346 }
1347
1348 /* No need to look up nullptr pointers, just return immediately */
1349 if (index == (rt == SLRefType::OldVehicle ? 0xFFFF : 0)) return nullptr;
1350
1351 /* Correct index. Old vehicles were saved differently:
1352 * invalid vehicle was 0xFFFF, now we use 0x0000 for everything invalid. */
1353 if (rt != SLRefType::OldVehicle) index--;
1354
1355 switch (rt) {
1357 if (OrderList::IsValidID(index)) return OrderList::Get(index);
1358 SlErrorCorrupt("Referencing invalid OrderList");
1359
1361 case SLRefType::Vehicle:
1362 if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
1363 SlErrorCorrupt("Referencing invalid Vehicle");
1364
1365 case SLRefType::Station:
1366 if (Station::IsValidID(index)) return Station::Get(index);
1367 SlErrorCorrupt("Referencing invalid Station");
1368
1369 case SLRefType::Town:
1370 if (Town::IsValidID(index)) return Town::Get(index);
1371 SlErrorCorrupt("Referencing invalid Town");
1372
1374 if (RoadStop::IsValidID(index)) return RoadStop::Get(index);
1375 SlErrorCorrupt("Referencing invalid RoadStop");
1376
1378 if (EngineRenew::IsValidID(index)) return EngineRenew::Get(index);
1379 SlErrorCorrupt("Referencing invalid EngineRenew");
1380
1382 if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
1383 SlErrorCorrupt("Referencing invalid CargoPacket");
1384
1385 case SLRefType::Storage:
1386 if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index);
1387 SlErrorCorrupt("Referencing invalid PersistentStorage");
1388
1390 if (LinkGraph::IsValidID(index)) return LinkGraph::Get(index);
1391 SlErrorCorrupt("Referencing invalid LinkGraph");
1392
1394 if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index);
1395 SlErrorCorrupt("Referencing invalid LinkGraphJob");
1396
1397 default: NOT_REACHED();
1398 }
1399}
1400
1406void SlSaveLoadRef(void *ptr, VarType conv)
1407{
1408 switch (_sl.action) {
1410 SlWriteUint32(ReferenceToInt(*static_cast<void **>(ptr), conv.ref));
1411 break;
1414 *static_cast<size_t *>(ptr) = IsSavegameVersionBefore(SaveLoadVersion::MoreCargoPackets) ? SlReadUint16() : SlReadUint32();
1415 break;
1417 *static_cast<void **>(ptr) = IntToReference(*static_cast<size_t *>(ptr), conv.ref);
1418 break;
1420 *static_cast<void **>(ptr) = nullptr;
1421 break;
1422 default: NOT_REACHED();
1423 }
1424}
1425
1429template <template <typename, typename> typename Tstorage, typename Tvar, typename Tallocator = std::allocator<Tvar>>
1431 typedef Tstorage<Tvar, Tallocator> SlStorageT;
1432public:
1440 static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd = SaveLoadType::Variable)
1441 {
1442 assert(cmd == SaveLoadType::Variable || cmd == SaveLoadType::Reference);
1443
1444 const SlStorageT *list = static_cast<const SlStorageT *>(storage);
1445
1446 int type_size = SlGetArrayLength(list->size());
1447 int item_size = SlCalcConvFileLen(cmd == SaveLoadType::Variable ? conv : VarType{VarFileType::U32, {}});
1448 return list->size() * item_size + type_size;
1449 }
1450
1451 static void SlSaveLoadMember(SaveLoadType cmd, Tvar *item, VarType conv)
1452 {
1453 switch (cmd) {
1454 case SaveLoadType::Variable: SlSaveLoadConv(item, conv); break;
1455 case SaveLoadType::Reference: SlSaveLoadRef(item, conv); break;
1456 case SaveLoadType::String: SlStdString(item, conv); break;
1457 default:
1458 NOT_REACHED();
1459 }
1460 }
1461
1468 static void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd = SaveLoadType::Variable)
1469 {
1470 assert(cmd == SaveLoadType::Variable || cmd == SaveLoadType::Reference || cmd == SaveLoadType::String);
1471
1472 SlStorageT *list = static_cast<SlStorageT *>(storage);
1473
1474 switch (_sl.action) {
1476 SlWriteArrayLength(list->size());
1477
1478 for (auto &item : *list) {
1479 SlSaveLoadMember(cmd, &item, conv);
1480 }
1481 break;
1482
1484 case SaveLoadAction::Load: {
1485 size_t length;
1486 switch (cmd) {
1487 case SaveLoadType::Variable: length = IsSavegameVersionBefore(SaveLoadVersion::SaveloadListLength) ? SlReadUint32() : SlReadArrayLength(); break;
1488 case SaveLoadType::Reference: length = IsSavegameVersionBefore(SaveLoadVersion::MoreCargoPackets) ? SlReadUint16() : IsSavegameVersionBefore(SaveLoadVersion::SaveloadListLength) ? SlReadUint32() : SlReadArrayLength(); break;
1489 case SaveLoadType::String: length = SlReadArrayLength(); break;
1490 default: NOT_REACHED();
1491 }
1492
1493 list->clear();
1494 if constexpr (std::is_same_v<SlStorageT, std::vector<Tvar, Tallocator>>) {
1495 list->reserve(length);
1496 }
1497
1498 /* Load each value and push to the end of the storage. */
1499 for (size_t i = 0; i < length; i++) {
1500 Tvar &data = list->emplace_back();
1501 SlSaveLoadMember(cmd, &data, conv);
1502 }
1503 break;
1504 }
1505
1507 for (auto &item : *list) {
1508 SlSaveLoadMember(cmd, &item, conv);
1509 }
1510 break;
1511
1513 list->clear();
1514 break;
1515
1516 default: NOT_REACHED();
1517 }
1518 }
1519};
1520
1527static inline size_t SlCalcRefListLen(const void *list, VarType conv)
1528{
1530}
1531
1537static void SlRefList(void *list, VarType conv)
1538{
1539 /* Automatically calculate the length? */
1540 if (_sl.need_length != NeedLength::None) {
1541 SlSetLength(SlCalcRefListLen(list, conv));
1542 /* Determine length only? */
1543 if (_sl.need_length == NeedLength::CalcLength) return;
1544 }
1545
1547}
1548
1555static size_t SlCalcRefVectorLen(const void *vector, VarType conv)
1556{
1558}
1559
1565static void SlRefVector(void *vector, VarType conv)
1566{
1567 /* Automatically calculate the length? */
1568 if (_sl.need_length != NeedLength::None) {
1569 SlSetLength(SlCalcRefVectorLen(vector, conv));
1570 /* Determine length only? */
1571 if (_sl.need_length == NeedLength::CalcLength) return;
1572 }
1573
1575}
1576
1583static inline size_t SlCalcVectorLen(const void *vector, VarType conv)
1584{
1585 switch (conv.mem) {
1586 case VarMemType::Bool: NOT_REACHED(); // Not supported
1595
1596 case VarMemType::Str:
1597 /* Strings are a length-prefixed field type in the savegame table format,
1598 * these may not be directly stored in another length-prefixed container type. */
1599 NOT_REACHED();
1600
1601 default: NOT_REACHED();
1602 }
1603}
1604
1610static void SlVector(void *vector, VarType conv)
1611{
1612 switch (conv.mem) {
1613 case VarMemType::Bool: NOT_REACHED(); // Not supported
1622
1623 case VarMemType::Str:
1624 /* Strings are a length-prefixed field type in the savegame table format,
1625 * these may not be directly stored in another length-prefixed container type.
1626 * This is permitted for load-related actions, because invalid fields of this type are present
1627 * from SaveLoadVersion::CompanyAllowList up to SaveLoadVersion::CompanyAllowListV2. */
1628 assert(_sl.action != SaveLoadAction::Save);
1630 break;
1631
1632 default: NOT_REACHED();
1633 }
1634}
1635
1641static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld)
1642{
1643 return (_sl_version >= sld.version_from && _sl_version < sld.version_to);
1644}
1645
1651static size_t SlCalcTableHeader(const SaveLoadTable &slt)
1652{
1653 size_t length = 0;
1654
1655 for (auto &sld : slt) {
1656 if (!SlIsObjectValidInSavegame(sld)) continue;
1657
1659 length += SlCalcStdStringLen(&sld.name);
1660 }
1661
1662 length += SlCalcConvFileLen(VarTypes::U8); // End-of-list entry.
1663
1664 for (auto &sld : slt) {
1665 if (!SlIsObjectValidInSavegame(sld)) continue;
1666 if (sld.cmd == SaveLoadType::StructList || sld.cmd == SaveLoadType::Struct) {
1667 length += SlCalcTableHeader(sld.handler->GetDescription());
1668 }
1669 }
1670
1671 return length;
1672}
1673
1680size_t SlCalcObjLength(const void *object, const SaveLoadTable &slt)
1681{
1682 size_t length = 0;
1683
1684 /* Need to determine the length and write a length tag. */
1685 for (auto &sld : slt) {
1686 length += SlCalcObjMemberLength(object, sld);
1687 }
1688 return length;
1689}
1690
1691size_t SlCalcObjMemberLength(const void *object, const SaveLoad &sld)
1692{
1693 assert(_sl.action == SaveLoadAction::Save);
1694
1695 if (!SlIsObjectValidInSavegame(sld)) return 0;
1696
1697 switch (sld.cmd) {
1700 case SaveLoadType::Array: return SlCalcArrayLen(sld.length, sld.conv);
1703 case SaveLoadType::Vector: return SlCalcVectorLen(GetVariableAddress(object, sld), sld.conv);
1705 case SaveLoadType::SaveByte: return 1; // a byte is logically of size 1
1706 case SaveLoadType::Null: return SlCalcConvFileLen(sld.conv) * sld.length;
1707
1710 NeedLength old_need_length = _sl.need_length;
1711 size_t old_obj_len = _sl.obj_len;
1712
1713 _sl.need_length = NeedLength::CalcLength;
1714 _sl.obj_len = 0;
1715
1716 /* Pretend that we are saving to collect the object size. Other
1717 * means are difficult, as we don't know the length of the list we
1718 * are about to store. */
1719 sld.handler->Save(const_cast<void *>(object));
1720 size_t length = _sl.obj_len;
1721
1722 _sl.obj_len = old_obj_len;
1723 _sl.need_length = old_need_length;
1724
1725 if (sld.cmd == SaveLoadType::Struct) {
1726 length += SlGetArrayLength(1);
1727 }
1728
1729 return length;
1730 }
1731
1732 default: NOT_REACHED();
1733 }
1734 return 0;
1735}
1736
1737static bool SlObjectMember(void *object, const SaveLoad &sld)
1738{
1739 if (!SlIsObjectValidInSavegame(sld)) return false;
1740
1741 switch (sld.cmd) {
1748 case SaveLoadType::String: {
1749 void *ptr = GetVariableAddress(object, sld);
1750
1751 switch (sld.cmd) {
1752 case SaveLoadType::Variable: SlSaveLoadConv(ptr, sld.conv); break;
1753 case SaveLoadType::Reference: SlSaveLoadRef(ptr, sld.conv); break;
1754 case SaveLoadType::Array: SlArray(ptr, sld.length, sld.conv); break;
1755 case SaveLoadType::ReferenceList: SlRefList(ptr, sld.conv); break;
1756 case SaveLoadType::ReferenceVector: SlRefVector(ptr, sld.conv); break;
1757 case SaveLoadType::Vector: SlVector(ptr, sld.conv); break;
1758 case SaveLoadType::String: SlStdString(ptr, sld.conv); break;
1759 default: NOT_REACHED();
1760 }
1761 break;
1762 }
1763
1764 /* SaveLoadType::SaveByte writes a value to the savegame to identify the type of an object.
1765 * When loading, the value is read explicitly with SlReadByte() to determine which
1766 * object description to use. */
1768 void *ptr = GetVariableAddress(object, sld);
1769
1770 switch (_sl.action) {
1771 case SaveLoadAction::Save: SlWriteByte(*static_cast<uint8_t *>(ptr)); break;
1775 case SaveLoadAction::Null: break;
1776 default: NOT_REACHED();
1777 }
1778 break;
1779 }
1780
1781 case SaveLoadType::Null: {
1782 assert(sld.conv.mem == VarMemType::Null);
1783
1784 switch (_sl.action) {
1787 case SaveLoadAction::Save: for (int i = 0; i < SlCalcConvFileLen(sld.conv) * sld.length; i++) SlWriteByte(0); break;
1789 case SaveLoadAction::Null: break;
1790 default: NOT_REACHED();
1791 }
1792 break;
1793 }
1794
1797 switch (_sl.action) {
1798 case SaveLoadAction::Save: {
1799 if (sld.cmd == SaveLoadType::Struct) {
1800 /* Store in the savegame if this struct was written or not. */
1801 SlSetStructListLength(SlCalcObjMemberLength(object, sld) > SlGetArrayLength(1) ? 1 : 0);
1802 }
1803 sld.handler->Save(object);
1804 break;
1805 }
1806
1810 }
1811 sld.handler->LoadCheck(object);
1812 break;
1813 }
1814
1815 case SaveLoadAction::Load: {
1818 }
1819 sld.handler->Load(object);
1820 break;
1821 }
1822
1824 sld.handler->FixPointers(object);
1825 break;
1826
1827 case SaveLoadAction::Null: break;
1828 default: NOT_REACHED();
1829 }
1830 break;
1831
1832 default: NOT_REACHED();
1833 }
1834 return true;
1835}
1836
1841void SlSetStructListLength(size_t length)
1842{
1843 /* Automatically calculate the length? */
1844 if (_sl.need_length != NeedLength::None) {
1845 SlSetLength(SlGetArrayLength(length));
1846 if (_sl.need_length == NeedLength::CalcLength) return;
1847 }
1848
1849 SlWriteArrayLength(length);
1850}
1851
1857size_t SlGetStructListLength(size_t limit)
1858{
1859 size_t length = SlReadArrayLength();
1860 if (length > limit) SlErrorCorrupt("List exceeds storage size");
1861
1862 return length;
1863}
1864
1870void SlObject(void *object, const SaveLoadTable &slt)
1871{
1872 /* Automatically calculate the length? */
1873 if (_sl.need_length != NeedLength::None) {
1874 SlSetLength(SlCalcObjLength(object, slt));
1875 if (_sl.need_length == NeedLength::CalcLength) return;
1876 }
1877
1878 for (auto &sld : slt) {
1879 SlObjectMember(object, sld);
1880 }
1881}
1882
1888 void Save(void *) const override
1889 {
1890 NOT_REACHED();
1891 }
1892
1893 void Load(void *object) const override
1894 {
1895 size_t length = SlGetStructListLength(UINT32_MAX);
1896 for (; length > 0; length--) {
1897 SlObject(object, this->GetLoadDescription());
1898 }
1899 }
1900
1901 void LoadCheck(void *object) const override
1902 {
1903 this->Load(object);
1904 }
1905
1907 {
1908 return {};
1909 }
1910
1912 {
1913 NOT_REACHED();
1914 }
1915};
1916
1923std::vector<SaveLoad> SlTableHeader(const SaveLoadTable &slt)
1924{
1925 /* You can only use SlTableHeader if you are a ChunkType::Table or ChunkType::SparseTable. */
1926 assert(_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
1927
1928 switch (_sl.action) {
1930 case SaveLoadAction::Load: {
1931 std::vector<SaveLoad> saveloads;
1932
1933 /* Build a key lookup mapping based on the available fields. */
1934 std::map<std::string, const SaveLoad *> key_lookup;
1935 for (auto &sld : slt) {
1936 if (!SlIsObjectValidInSavegame(sld)) continue;
1937
1938 /* Check that there is only one active SaveLoad for a given name. */
1939 assert(key_lookup.find(sld.name) == key_lookup.end());
1940 key_lookup[sld.name] = &sld;
1941 }
1942
1943 while (true) {
1944 SavegameFileType type{};
1946 if (type.IsEnd()) break;
1947
1948 std::string key;
1950
1951 auto sld_it = key_lookup.find(key);
1952 if (sld_it == key_lookup.end()) {
1953 /* SLA_LOADCHECK triggers this debug statement a lot and is perfectly normal. */
1954 Debug(sl, _sl.action == SaveLoadAction::Load ? 2 : 6, "Field '{}' of type 0x{:02x} not found, skipping", key, type.storage);
1955
1956 std::shared_ptr<SaveLoadHandler> handler = nullptr;
1957 SaveLoadType saveload_type;
1958 switch (type.Type()) {
1960 saveload_type = SaveLoadType::String;
1961 break;
1962
1964 saveload_type = SaveLoadType::StructList;
1965 handler = std::make_shared<SlSkipHandler>();
1966 break;
1967
1968 default:
1970 break;
1971 }
1972
1973 /* We don't know this field, so read to nothing. */
1974 saveloads.emplace_back(std::move(key), saveload_type, type.Type() | VarMemType::Null, 1, SaveLoadVersion::MinVersion, SaveLoadVersion::MaxVersion, nullptr, 0, std::move(handler));
1975 continue;
1976 }
1977
1978 /* Validate the type of the field. If it is changed, the
1979 * savegame should have been bumped so we know how to do the
1980 * conversion. If this error triggers, that clearly didn't
1981 * happen and this is a friendly poke to the developer to bump
1982 * the savegame version and add conversion code. */
1983 SavegameFileType correct_type = GetSavegameFileType(*sld_it->second);
1984 if (correct_type.storage != type.storage) {
1985 Debug(sl, 1, "Field type for '{}' was expected to be 0x{:02x} but 0x{:02x} was found", key, correct_type.storage, type.storage);
1986 SlErrorCorrupt("Field type is different than expected");
1987 }
1988 saveloads.emplace_back(*sld_it->second);
1989 }
1990
1991 for (auto &sld : saveloads) {
1993 sld.handler->load_description = SlTableHeader(sld.handler->GetDescription());
1994 }
1995 }
1996
1997 return saveloads;
1998 }
1999
2000 case SaveLoadAction::Save: {
2001 /* Automatically calculate the length? */
2002 if (_sl.need_length != NeedLength::None) {
2004 if (_sl.need_length == NeedLength::CalcLength) break;
2005 }
2006
2007 for (auto &sld : slt) {
2008 if (!SlIsObjectValidInSavegame(sld)) continue;
2009 /* Make sure we are not storing empty keys. */
2010 assert(!sld.name.empty());
2011
2013 assert(!type.IsEnd());
2014
2016 SlStdString(const_cast<std::string *>(&sld.name), VarTypes::STR);
2017 }
2018
2019 /* Add an end-of-header marker. */
2020 SavegameFileType type{};
2022
2023 /* After the table, write down any sub-tables we might have. */
2024 for (auto &sld : slt) {
2025 if (!SlIsObjectValidInSavegame(sld)) continue;
2027 /* SlCalcTableHeader already looks in sub-lists, so avoid the length being added twice. */
2028 NeedLength old_need_length = _sl.need_length;
2029 _sl.need_length = NeedLength::None;
2030
2031 SlTableHeader(sld.handler->GetDescription());
2032
2033 _sl.need_length = old_need_length;
2034 }
2035 }
2036
2037 break;
2038 }
2039
2040 default: NOT_REACHED();
2041 }
2042
2043 return std::vector<SaveLoad>();
2044}
2045
2059std::vector<SaveLoad> SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct)
2060{
2061 assert(_sl.action == SaveLoadAction::Load || _sl.action == SaveLoadAction::LoadCheck);
2062 /* ChunkType::Table / ChunkType::SparseTable always have a header. */
2063 if (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable) return SlTableHeader(slt);
2064
2065 std::vector<SaveLoad> saveloads;
2066
2067 /* Build a key lookup mapping based on the available fields. */
2068 std::map<std::string_view, std::vector<const SaveLoad *>> key_lookup;
2069 for (auto &sld : slt) {
2070 /* All entries should have a name; otherwise the entry should just be removed. */
2071 assert(!sld.name.empty());
2072
2073 key_lookup[sld.name].push_back(&sld);
2074 }
2075
2076 for (auto &slc : slct) {
2077 if (slc.name.empty()) {
2078 /* In old savegames there can be data we no longer care for. We
2079 * skip this by simply reading the amount of bytes indicated and
2080 * send those to /dev/null. */
2081 saveloads.emplace_back("", SaveLoadType::Null, VarFileType::U8 | VarMemType::Null, slc.null_length, slc.version_from, slc.version_to, nullptr, 0, nullptr);
2082 } else {
2083 auto sld_it = key_lookup.find(slc.name);
2084 /* If this branch triggers, it means that an entry in the
2085 * SaveLoadCompat list is not mentioned in the SaveLoad list. Did
2086 * you rename a field in one and not in the other? */
2087 if (sld_it == key_lookup.end()) {
2088 /* This isn't an assert, as that leaves no information what
2089 * field was to blame. This way at least we have breadcrumbs. */
2090 Debug(sl, 0, "internal error: saveload compatibility field '{}' not found", slc.name);
2091 SlErrorCorrupt("Internal error with savegame compatibility");
2092 }
2093 for (auto &sld : sld_it->second) {
2094 saveloads.push_back(*sld);
2095 }
2096 }
2097 }
2098
2099 for (auto &sld : saveloads) {
2100 if (!SlIsObjectValidInSavegame(sld)) continue;
2102 sld.handler->load_description = SlCompatTableHeader(sld.handler->GetDescription(), sld.handler->GetCompatDescription());
2103 }
2104 }
2105
2106 return saveloads;
2107}
2108
2114{
2115 SlObject(nullptr, slt);
2116}
2117
2123void SlAutolength(AutolengthProc *proc, int arg)
2124{
2125 assert(_sl.action == SaveLoadAction::Save);
2126
2127 /* Tell it to calculate the length */
2128 _sl.need_length = NeedLength::CalcLength;
2129 _sl.obj_len = 0;
2130 proc(arg);
2131
2132 /* Setup length */
2133 _sl.need_length = NeedLength::WantLength;
2134 SlSetLength(_sl.obj_len);
2135
2136 size_t start_pos = _sl.dumper->GetSize();
2137 size_t expected_offs = start_pos + _sl.obj_len;
2138
2139 /* And write the stuff */
2140 proc(arg);
2141
2142 if (expected_offs != _sl.dumper->GetSize()) {
2143 SlErrorCorruptFmt("Invalid chunk size when writing autolength block, expected {}, got {}", _sl.obj_len, _sl.dumper->GetSize() - start_pos);
2144 }
2145}
2146
2147void ChunkHandler::LoadCheck(size_t len) const
2148{
2149 switch (_sl.chunk_type) {
2150 case ChunkType::Table:
2152 SlTableHeader({});
2153 [[fallthrough]];
2154 case ChunkType::Array:
2156 SlSkipArray();
2157 break;
2158 case ChunkType::Riff:
2159 SlSkipBytes(len);
2160 break;
2161 default:
2162 NOT_REACHED();
2163 }
2164}
2165
2170static void SlLoadChunk(const ChunkHandler &ch)
2171{
2172 uint8_t m = SlReadByte();
2173
2174 _sl.chunk_type = static_cast<ChunkType>(m & to_underlying(ChunkType::FileTypeMask));
2175 _sl.obj_len = 0;
2176 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2177
2178 /* The header should always be at the start. Read the length; the
2179 * Load() should as first action process the header. */
2180 if (_sl.expect_table_header) {
2181 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2182 }
2183
2184 switch (_sl.chunk_type) {
2185 case ChunkType::Table:
2186 case ChunkType::Array:
2187 _sl.array_index = 0;
2188 ch.Load();
2189 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2190 break;
2193 ch.Load();
2194 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2195 break;
2196 case ChunkType::Riff: {
2197 /* Read length */
2198 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2199 len += SlReadUint16();
2200 _sl.obj_len = len;
2201 size_t start_pos = _sl.reader->GetSize();
2202 size_t endoffs = start_pos + len;
2203 ch.Load();
2204
2205 if (_sl.reader->GetSize() != endoffs) {
2206 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2207 }
2208 break;
2209 }
2210 default:
2211 SlErrorCorrupt("Invalid chunk type");
2212 break;
2213 }
2214
2215 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2216}
2217
2223static void SlLoadCheckChunk(const ChunkHandler &ch)
2224{
2225 uint8_t m = SlReadByte();
2226
2227 _sl.chunk_type = static_cast<ChunkType>(m & to_underlying(ChunkType::FileTypeMask));
2228 _sl.obj_len = 0;
2229 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2230
2231 /* The header should always be at the start. Read the length; the
2232 * LoadCheck() should as first action process the header. */
2233 if (_sl.expect_table_header) {
2234 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2235 }
2236
2237 switch (_sl.chunk_type) {
2238 case ChunkType::Table:
2239 case ChunkType::Array:
2240 _sl.array_index = 0;
2241 ch.LoadCheck();
2242 break;
2245 ch.LoadCheck();
2246 break;
2247 case ChunkType::Riff: {
2248 /* Read length */
2249 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2250 len += SlReadUint16();
2251 _sl.obj_len = len;
2252 size_t start_pos = _sl.reader->GetSize();
2253 size_t endoffs = start_pos + len;
2254 ch.LoadCheck(len);
2255
2256 if (_sl.reader->GetSize() != endoffs) {
2257 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2258 }
2259 break;
2260 }
2261 default:
2262 SlErrorCorrupt("Invalid chunk type");
2263 break;
2264 }
2265
2266 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2267}
2268
2274static void SlSaveChunk(const ChunkHandler &ch)
2275{
2276 if (ch.type == ChunkType::ReadOnly) return;
2277
2278 SlWriteUint32(ch.id);
2279 Debug(sl, 2, "Saving chunk {}", ch.GetName());
2280
2281 _sl.chunk_type = ch.type;
2282 _sl.expect_table_header = (_sl.chunk_type == ChunkType::Table || _sl.chunk_type == ChunkType::SparseTable);
2283
2284 _sl.need_length = (_sl.expect_table_header || _sl.chunk_type == ChunkType::Riff) ? NeedLength::WantLength : NeedLength::None;
2285
2286 switch (_sl.chunk_type) {
2287 case ChunkType::Riff:
2288 ch.Save();
2289 break;
2290 case ChunkType::Table:
2291 case ChunkType::Array:
2292 _sl.last_array_index = 0;
2293 SlWriteByte(to_underlying(_sl.chunk_type));
2294 ch.Save();
2295 SlWriteArrayLength(0); // Terminate arrays
2296 break;
2299 SlWriteByte(to_underlying(_sl.chunk_type));
2300 ch.Save();
2301 SlWriteArrayLength(0); // Terminate arrays
2302 break;
2303 default: NOT_REACHED();
2304 }
2305
2306 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2307}
2308
2310static void SlSaveChunks()
2311{
2312 for (auto &ch : ChunkHandlers()) {
2313 SlSaveChunk(ch);
2314 }
2315
2316 /* Terminator */
2317 SlWriteUint32(0);
2318}
2319
2326static const ChunkHandler *SlFindChunkHandler(uint32_t id)
2327{
2328 for (const ChunkHandler &ch : ChunkHandlers()) if (ch.id == id) return &ch;
2329 return nullptr;
2330}
2331
2333static void SlLoadChunks()
2334{
2335 uint32_t id;
2336 const ChunkHandler *ch;
2337
2338 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2339 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2340
2341 ch = SlFindChunkHandler(id);
2342 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2343 SlLoadChunk(*ch);
2344 }
2345}
2346
2349{
2350 uint32_t id;
2351 const ChunkHandler *ch;
2352
2353 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2354 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2355
2356 ch = SlFindChunkHandler(id);
2357 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2358 SlLoadCheckChunk(*ch);
2359 }
2360}
2361
2363static void SlFixPointers()
2364{
2365 _sl.action = SaveLoadAction::Ptrs;
2366
2367 for (const ChunkHandler &ch : ChunkHandlers()) {
2368 Debug(sl, 3, "Fixing pointers for {}", ch.GetName());
2369 ch.FixPointers();
2370 }
2371
2372 assert(_sl.action == SaveLoadAction::Ptrs);
2373}
2374
2375
2378 std::optional<FileHandle> file;
2379 long begin;
2380
2385 FileReader(FileHandle &&file) : LoadFilter(nullptr), file(std::move(file)), begin(ftell(*this->file))
2386 {
2387 }
2388
2390 ~FileReader() override
2391 {
2392 if (this->file.has_value()) {
2393 _game_session_stats.savegame_size = ftell(*this->file) - this->begin;
2394 }
2395 }
2396
2397 size_t Read(uint8_t *buf, size_t size) override
2398 {
2399 /* We're in the process of shutting down, i.e. in "failure" mode. */
2400 if (!this->file.has_value()) return 0;
2401
2402 return fread(buf, 1, size, *this->file);
2403 }
2404
2405 void Reset() override
2406 {
2407 clearerr(*this->file);
2408 if (fseek(*this->file, this->begin, SEEK_SET)) {
2409 Debug(sl, 1, "Could not reset the file reading");
2410 }
2411 }
2412};
2413
2416 std::optional<FileHandle> file;
2417
2422 FileWriter(FileHandle &&file) : SaveFilter(nullptr), file(std::move(file))
2423 {
2424 }
2425
2427 ~FileWriter() override
2428 {
2429 this->Finish();
2430 }
2431
2432 void Write(const uint8_t *buf, size_t size) override
2433 {
2434 /* We're in the process of shutting down, i.e. in "failure" mode. */
2435 if (!this->file.has_value()) return;
2436
2437 if (fwrite(buf, 1, size, *this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
2438 }
2439
2440 void Finish() override
2441 {
2442 if (this->file.has_value()) {
2443 _game_session_stats.savegame_size = ftell(*this->file);
2444 this->file.reset();
2445 }
2446 }
2447};
2448
2449/*******************************************
2450 ********** START OF LZO CODE **************
2451 *******************************************/
2452
2453#ifdef WITH_LZO
2454
2456static const uint LZO_BUFFER_SIZE = 8192;
2457
2464 LZOLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2465 {
2466 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2467 }
2468
2469 size_t Read(uint8_t *buf, size_t ssize) override
2470 {
2471 assert(ssize >= LZO_BUFFER_SIZE);
2472
2473 /* Buffer size is from the LZO docs plus the chunk header size. */
2474 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2475 uint32_t tmp[2];
2476 uint32_t size;
2477 lzo_uint len = ssize;
2478
2479 /* Read header*/
2480 if (this->chain->Read((uint8_t*)tmp, sizeof(tmp)) != sizeof(tmp)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE, "File read failed");
2481
2482 /* Check if size is bad */
2483 ((uint32_t*)out)[0] = size = tmp[1];
2484
2486 tmp[0] = TO_BE32(tmp[0]);
2487 size = TO_BE32(size);
2488 }
2489
2490 if (size >= sizeof(out)) SlErrorCorrupt("Inconsistent size");
2491
2492 /* Read block */
2493 if (this->chain->Read(out + sizeof(uint32_t), size) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2494
2495 /* Verify checksum */
2496 if (tmp[0] != lzo_adler32(0, out, size + sizeof(uint32_t))) SlErrorCorrupt("Bad checksum");
2497
2498 /* Decompress */
2499 int ret = lzo1x_decompress_safe(out + sizeof(uint32_t) * 1, size, buf, &len, nullptr);
2500 if (ret != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2501 return len;
2502 }
2503};
2504
2511 LZOSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2512 {
2513 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2514 }
2515
2516 void Write(const uint8_t *buf, size_t size) override
2517 {
2518 const lzo_bytep in = buf;
2519 /* Buffer size is from the LZO docs plus the chunk header size. */
2520 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2521 uint8_t wrkmem[LZO1X_1_MEM_COMPRESS];
2522 lzo_uint outlen;
2523
2524 do {
2525 /* Compress up to LZO_BUFFER_SIZE bytes at once. */
2526 lzo_uint len = size > LZO_BUFFER_SIZE ? LZO_BUFFER_SIZE : static_cast<lzo_uint>(size);
2527 lzo1x_1_compress(in, len, out + sizeof(uint32_t) * 2, &outlen, wrkmem);
2528 ((uint32_t*)out)[1] = TO_BE32(static_cast<uint32_t>(outlen));
2529 ((uint32_t*)out)[0] = TO_BE32(lzo_adler32(0, out + sizeof(uint32_t), outlen + sizeof(uint32_t)));
2530 this->chain->Write(out, outlen + sizeof(uint32_t) * 2);
2531
2532 /* Move to next data chunk. */
2533 size -= len;
2534 in += len;
2535 } while (size > 0);
2536 }
2537};
2538
2539#endif /* WITH_LZO */
2540
2541/*********************************************
2542 ******** START OF NOCOMP CODE (uncompressed)*
2543 *********************************************/
2544
2551 NoCompLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2552 {
2553 }
2554
2555 size_t Read(uint8_t *buf, size_t size) override
2556 {
2557 return this->chain->Read(buf, size);
2558 }
2559};
2560
2567 NoCompSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2568 {
2569 }
2570
2571 void Write(const uint8_t *buf, size_t size) override
2572 {
2573 this->chain->Write(buf, size);
2574 }
2575};
2576
2577/********************************************
2578 ********** START OF ZLIB CODE **************
2579 ********************************************/
2580
2581#if defined(WITH_ZLIB)
2582
2585 z_stream z{};
2587
2592 ZlibLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2593 {
2594 if (inflateInit(&this->z) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2595 }
2596
2599 {
2600 inflateEnd(&this->z);
2601 }
2602
2603 size_t Read(uint8_t *buf, size_t size) override
2604 {
2605 this->z.next_out = buf;
2606 this->z.avail_out = static_cast<uint>(size);
2607
2608 do {
2609 /* read more bytes from the file? */
2610 if (this->z.avail_in == 0) {
2611 this->z.next_in = this->fread_buf;
2612 this->z.avail_in = static_cast<uint>(this->chain->Read(this->fread_buf, sizeof(this->fread_buf)));
2613 }
2614
2615 /* inflate the data */
2616 int r = inflate(&this->z, 0);
2617 if (r == Z_STREAM_END) break;
2618
2619 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "inflate() failed");
2620 } while (this->z.avail_out != 0);
2621
2622 return size - this->z.avail_out;
2623 }
2624};
2625
2628 z_stream z{};
2630
2636 ZlibSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain))
2637 {
2638 if (deflateInit(&this->z, compression_level) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2639 }
2640
2643 {
2644 deflateEnd(&this->z);
2645 }
2646
2653 void WriteLoop(const uint8_t *p, size_t len, int mode)
2654 {
2655 uint n;
2656 this->z.next_in = const_cast<uint8_t *>(p); // zlib does not modify the data, but is non-const for legacy reasons
2657 this->z.avail_in = static_cast<uInt>(len);
2658 do {
2659 this->z.next_out = this->fwrite_buf;
2660 this->z.avail_out = sizeof(this->fwrite_buf);
2661
2669 int r = deflate(&this->z, mode);
2670
2671 /* bytes were emitted? */
2672 if ((n = sizeof(this->fwrite_buf) - this->z.avail_out) != 0) {
2673 this->chain->Write(this->fwrite_buf, n);
2674 }
2675 if (r == Z_STREAM_END) break;
2676
2677 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "zlib returned error code");
2678 } while (this->z.avail_in || !this->z.avail_out);
2679 }
2680
2681 void Write(const uint8_t *buf, size_t size) override
2682 {
2683 this->WriteLoop(buf, size, 0);
2684 }
2685
2686 void Finish() override
2687 {
2688 this->WriteLoop(nullptr, 0, Z_FINISH);
2689 this->chain->Finish();
2690 }
2691};
2692
2693#endif /* WITH_ZLIB */
2694
2695/********************************************
2696 ********** START OF LZMA CODE **************
2697 ********************************************/
2698
2699#if defined(WITH_LIBLZMA)
2700
2707static const lzma_stream _lzma_init = LZMA_STREAM_INIT;
2708
2711 lzma_stream lzma;
2713
2718 LZMALoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain)), lzma(_lzma_init)
2719 {
2720 /* Allow saves up to 256 MB uncompressed */
2721 if (lzma_auto_decoder(&this->lzma, 1 << 28, 0) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2722 }
2723
2726 {
2727 lzma_end(&this->lzma);
2728 }
2729
2730 size_t Read(uint8_t *buf, size_t size) override
2731 {
2732 this->lzma.next_out = buf;
2733 this->lzma.avail_out = size;
2734
2735 do {
2736 /* read more bytes from the file? */
2737 if (this->lzma.avail_in == 0) {
2738 this->lzma.next_in = this->fread_buf;
2739 this->lzma.avail_in = this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2740 }
2741
2742 /* inflate the data */
2743 lzma_ret r = lzma_code(&this->lzma, LZMA_RUN);
2744 if (r == LZMA_STREAM_END) break;
2745 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2746 } while (this->lzma.avail_out != 0);
2747
2748 return size - this->lzma.avail_out;
2749 }
2750};
2751
2754 lzma_stream lzma;
2756
2762 LZMASaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain)), lzma(_lzma_init)
2763 {
2764 if (lzma_easy_encoder(&this->lzma, compression_level, LZMA_CHECK_CRC32) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2765 }
2766
2769 {
2770 lzma_end(&this->lzma);
2771 }
2772
2779 void WriteLoop(const uint8_t *p, size_t len, lzma_action action)
2780 {
2781 size_t n;
2782 this->lzma.next_in = p;
2783 this->lzma.avail_in = len;
2784 do {
2785 this->lzma.next_out = this->fwrite_buf;
2786 this->lzma.avail_out = sizeof(this->fwrite_buf);
2787
2788 lzma_ret r = lzma_code(&this->lzma, action);
2789
2790 /* bytes were emitted? */
2791 if ((n = sizeof(this->fwrite_buf) - this->lzma.avail_out) != 0) {
2792 this->chain->Write(this->fwrite_buf, n);
2793 }
2794 if (r == LZMA_STREAM_END) break;
2795 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2796 } while (this->lzma.avail_in || !this->lzma.avail_out);
2797 }
2798
2799 void Write(const uint8_t *buf, size_t size) override
2800 {
2801 this->WriteLoop(buf, size, LZMA_RUN);
2802 }
2803
2804 void Finish() override
2805 {
2806 this->WriteLoop(nullptr, 0, LZMA_FINISH);
2807 this->chain->Finish();
2808 }
2809};
2810
2811#endif /* WITH_LIBLZMA */
2812
2813/*******************************************
2814 ************* END OF CODE *****************
2815 *******************************************/
2816
2819 std::shared_ptr<LoadFilter> (*init_load)(std::shared_ptr<LoadFilter> chain);
2820 std::shared_ptr<SaveFilter> (*init_write)(std::shared_ptr<SaveFilter> chain, uint8_t compression);
2821
2822 std::string_view name;
2823 uint32_t tag;
2824
2828};
2829
2830static const uint32_t SAVEGAME_TAG_LZO = TO_BE32('OTTD');
2831static const uint32_t SAVEGAME_TAG_NONE = TO_BE32('OTTN');
2832static const uint32_t SAVEGAME_TAG_ZLIB = TO_BE32('OTTZ');
2833static const uint32_t SAVEGAME_TAG_LZMA = TO_BE32('OTTX');
2834
2837#if defined(WITH_LZO)
2838 /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
2839 {CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2840#else
2841 {nullptr, nullptr, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2842#endif
2843 /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
2844 {CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, "none", SAVEGAME_TAG_NONE, 0, 0, 0},
2845#if defined(WITH_ZLIB)
2846 /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is
2847 * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level
2848 * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */
2849 {CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, "zlib", SAVEGAME_TAG_ZLIB, 0, 6, 9},
2850#else
2851 {nullptr, nullptr, "zlib", SAVEGAME_TAG_ZLIB, 0, 0, 0},
2852#endif
2853#if defined(WITH_LIBLZMA)
2854 /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
2855 * Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
2856 * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50%
2857 * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much.
2858 * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */
2859 {CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, "lzma", SAVEGAME_TAG_LZMA, 0, 2, 9},
2860#else
2861 {nullptr, nullptr, "lzma", SAVEGAME_TAG_LZMA, 0, 0, 0},
2862#endif
2863};
2864
2871static std::pair<const SaveLoadFormat &, uint8_t> GetSavegameFormat(std::string_view full_name)
2872{
2873 /* Find default savegame format, the highest one with which files can be written. */
2874 auto it = std::find_if(std::rbegin(_saveload_formats), std::rend(_saveload_formats), [](const auto &slf) { return slf.init_write != nullptr; });
2875 if (it == std::rend(_saveload_formats)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "no writeable savegame formats");
2876
2877 const SaveLoadFormat &def = *it;
2878
2879 if (!full_name.empty()) {
2880 /* Get the ":..." of the compression level out of the way */
2881 size_t separator = full_name.find(':');
2882 bool has_comp_level = separator != std::string::npos;
2883 std::string_view name = has_comp_level ? full_name.substr(0, separator) : full_name;
2884
2885 for (const auto &slf : _saveload_formats) {
2886 if (slf.init_write != nullptr && name == slf.name) {
2887 if (has_comp_level) {
2888 auto complevel = full_name.substr(separator + 1);
2889
2890 /* Get the level and determine whether all went fine. */
2891 auto level = ParseInteger<uint8_t>(complevel);
2892 if (!level.has_value() || *level != Clamp(*level, slf.min_compression, slf.max_compression)) {
2894 GetEncodedString(STR_CONFIG_ERROR),
2895 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, complevel),
2897 } else {
2898 return {slf, *level};
2899 }
2900 }
2901 return {slf, slf.default_compression};
2902 }
2903 }
2904
2906 GetEncodedString(STR_CONFIG_ERROR),
2907 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, name, def.name),
2909 }
2910 return {def, def.default_compression};
2911}
2912
2913/* actual loader/saver function */
2914void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
2915extern bool AfterLoadGame();
2916extern bool LoadOldSaveGame(std::string_view file);
2917
2923static void ResetSettings()
2924{
2925 for (auto &desc : GetSaveLoadSettingTable()) {
2926 const SettingDesc *sd = GetSettingDesc(desc);
2927 if (sd->flags.Test(SettingFlag::NotInSave)) continue;
2929
2931 }
2932}
2933
2934extern void ClearOldOrders();
2935
2940{
2942 ResetTempEngineData();
2943 ClearRailTypeLabelList();
2944 ClearRoadTypeLabelList();
2945 ResetOldWaypoints();
2946 ResetSettings();
2947}
2948
2952static inline void ClearSaveLoadState()
2953{
2954 _sl.dumper = nullptr;
2955 _sl.sf = nullptr;
2956 _sl.reader = nullptr;
2957 _sl.lf = nullptr;
2958}
2959
2961static void SaveFileStart()
2962{
2963 SetMouseCursorBusy(true);
2964
2965 InvalidateWindowData(WindowClass::Statusbar, 0, SBI_SAVELOAD_START);
2966 _sl.saveinprogress = true;
2967}
2968
2970static void SaveFileDone()
2971{
2972 SetMouseCursorBusy(false);
2973
2974 InvalidateWindowData(WindowClass::Statusbar, 0, SBI_SAVELOAD_FINISH);
2975 _sl.saveinprogress = false;
2976
2977#ifdef __EMSCRIPTEN__
2978 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
2979#endif
2980}
2981
2987{
2988 _sl.error_str = str;
2989}
2990
2996{
2997 return GetEncodedString(_sl.action == SaveLoadAction::Save ? STR_ERROR_GAME_SAVE_FAILED : STR_ERROR_GAME_LOAD_FAILED);
2998}
2999
3005{
3006 return GetEncodedString(_sl.error_str, _sl.extra_msg);
3007}
3008
3015
3022static SaveLoadResult SaveFileToDisk(bool threaded)
3023{
3024 try {
3025 auto [fmt, compression] = GetSavegameFormat(_savegame_format);
3026
3027 /* We have written our stuff to memory, now write it to file! */
3028 uint32_t hdr[2] = { fmt.tag, TO_BE32(to_underlying(SAVEGAME_VERSION) << 16) };
3029 _sl.sf->Write((uint8_t*)hdr, sizeof(hdr));
3030
3031 _sl.sf = fmt.init_write(_sl.sf, compression);
3032 _sl.dumper->Flush(_sl.sf);
3033
3035
3036 if (threaded) SetAsyncSaveFinish(SaveFileDone);
3037
3038 return SaveLoadResult::Ok;
3039 } catch (...) {
3041
3043
3044 /* We don't want to shout when saving is just
3045 * cancelled due to a client disconnecting. */
3046 if (_sl.error_str != STR_NETWORK_ERROR_LOSTCONNECTION) {
3047 Debug(sl, 0, "{} {}", GetSaveLoadErrorType().GetDecodedString(), GetSaveLoadErrorMessage().GetDecodedString());
3048 asfp = SaveFileError;
3049 }
3050
3051 if (threaded) {
3052 SetAsyncSaveFinish(asfp);
3053 } else {
3054 asfp();
3055 }
3056 return SaveLoadResult::Error;
3057 }
3058}
3059
3060void WaitTillSaved()
3061{
3062 if (!_save_thread.joinable()) return;
3063
3064 _save_thread.join();
3065
3066 /* Make sure every other state is handled properly as well. */
3068}
3069
3078static SaveLoadResult DoSave(std::shared_ptr<SaveFilter> writer, bool threaded)
3079{
3080 assert(!_sl.saveinprogress);
3081
3082 _sl.dumper = std::make_unique<MemoryDumper>();
3083 _sl.sf = std::move(writer);
3084
3086
3087 SaveViewportBeforeSaveGame();
3088 SlSaveChunks();
3089
3090 SaveFileStart();
3091
3092 if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) {
3093 if (threaded) Debug(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode...");
3094
3095 SaveLoadResult result = SaveFileToDisk(false);
3096 SaveFileDone();
3097
3098 return result;
3099 }
3100
3101 return SaveLoadResult::Ok;
3102}
3103
3110SaveLoadResult SaveWithFilter(std::shared_ptr<SaveFilter> writer, bool threaded)
3111{
3112 try {
3113 _sl.action = SaveLoadAction::Save;
3114 return DoSave(std::move(writer), threaded);
3115 } catch (...) {
3117 return SaveLoadResult::Error;
3118 }
3119}
3120
3129static const SaveLoadFormat *DetermineSaveLoadFormat(uint32_t tag, uint32_t raw_version)
3130{
3131 auto fmt = std::ranges::find(_saveload_formats, tag, &SaveLoadFormat::tag);
3132 if (fmt != std::end(_saveload_formats)) {
3133 /* Check version number */
3134 _sl_version = (SaveLoadVersion)(TO_BE32(raw_version) >> 16);
3135 /* Minor is not used anymore from version 18.0, but it is still needed
3136 * in versions before that (4 cases) which can't be removed easy.
3137 * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
3138 _sl_minor_version = (TO_BE32(raw_version) >> 8) & 0xFF;
3139
3140 Debug(sl, 1, "Loading savegame version {}", _sl_version);
3141
3142 /* Is the version higher than the current? */
3143 if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
3145 return fmt;
3146 }
3147
3148 Debug(sl, 0, "Unknown savegame type, trying to load it as the buggy format");
3149 _sl.lf->Reset();
3152
3153 /* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
3154 fmt = std::ranges::find(_saveload_formats, SAVEGAME_TAG_LZO, &SaveLoadFormat::tag);
3155 if (fmt == std::end(_saveload_formats)) {
3156 /* Who removed the LZO savegame format definition? When built without LZO support,
3157 * the formats must still list it just without a method to read the file.
3158 * The caller of this function has to check for the existence of load function. */
3159 NOT_REACHED();
3160 }
3161 return fmt;
3162}
3163
3170static SaveLoadResult DoLoad(std::shared_ptr<LoadFilter> reader, bool load_check)
3171{
3172 _sl.lf = std::move(reader);
3173
3174 if (load_check) {
3175 /* Clear previous check data */
3176 _load_check_data.Clear();
3177 /* Mark SL_LOAD_CHECK as supported for this savegame. */
3178 _load_check_data.checkable = true;
3179 }
3180
3181 uint32_t hdr[2];
3182 if (_sl.lf->Read((uint8_t*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3183
3184 /* see if we have any loader for this type. */
3185 const SaveLoadFormat *fmt = DetermineSaveLoadFormat(hdr[0], hdr[1]);
3186
3187 /* loader for this savegame type is not implemented? */
3188 if (fmt->init_load == nullptr) {
3189 SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, fmt::format("Loader for '{}' is not available.", fmt->name));
3190 }
3191
3192 _sl.lf = fmt->init_load(_sl.lf);
3193 _sl.reader = std::make_unique<ReadBuffer>(_sl.lf);
3194 _next_offs = 0;
3195
3196 if (!load_check) {
3198
3199 /* Old maps were hardcoded to 256x256 and thus did not contain
3200 * any mapsize information. Pre-initialize to 256x256 to not to
3201 * confuse old games */
3202 InitializeGame(256, 256, true, true);
3203
3204 _gamelog.Reset();
3205
3207 /*
3208 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
3209 * shared savegame version 4. Anything before that 'obviously'
3210 * does not have any NewGRFs. Between the introduction and
3211 * savegame version 41 (just before 0.5) the NewGRF settings
3212 * were not stored in the savegame and they were loaded by
3213 * using the settings from the main menu.
3214 * So, to recap:
3215 * - savegame version < 4: do not load any NewGRFs.
3216 * - savegame version >= 41: load NewGRFs from savegame, which is
3217 * already done at this stage by
3218 * overwriting the main menu settings.
3219 * - other savegame versions: use main menu settings.
3220 *
3221 * This means that users *can* crash savegame version 4..40
3222 * savegames if they set incompatible NewGRFs in the main menu,
3223 * but can't crash anymore for savegame version < 4 savegames.
3224 *
3225 * Note: this is done here because AfterLoadGame is also called
3226 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
3227 */
3229 }
3230 }
3231
3232 if (load_check) {
3233 /* Load chunks into _load_check_data.
3234 * No pools are loaded. References are not possible, and thus do not need resolving. */
3236 } else {
3237 /* Load chunks and resolve references */
3238 SlLoadChunks();
3239 SlFixPointers();
3240 }
3241
3243
3245
3246 if (load_check) {
3247 /* The only part from AfterLoadGame() we need */
3248 _load_check_data.grf_compatibility = IsGoodGRFConfigList(_load_check_data.grfconfig);
3249 } else {
3250 _gamelog.StartAction(GamelogActionType::Load);
3251
3252 /* After loading fix up savegame for any internal changes that
3253 * might have occurred since then. If it fails, load back the old game. */
3254 if (!AfterLoadGame()) {
3255 _gamelog.StopAction();
3257 }
3258
3259 _gamelog.StopAction();
3260 }
3261
3262 return SaveLoadResult::Ok;
3263}
3264
3270SaveLoadResult LoadWithFilter(std::shared_ptr<LoadFilter> reader)
3271{
3272 try {
3273 _sl.action = SaveLoadAction::Load;
3274 return DoLoad(std::move(reader), false);
3275 } catch (...) {
3278 }
3279}
3280
3291SaveLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
3292{
3293 /* An instance of saving is already active, so don't go saving again */
3294 if (_sl.saveinprogress && fop == SaveLoadOperation::Save && dft == DetailedFileType::GameFile && threaded) {
3295 /* if not an autosave, but a user action, show error message */
3296 if (!_do_autosave) ShowErrorMessage(GetEncodedString(STR_ERROR_SAVE_STILL_IN_PROGRESS), {}, WarningLevel::Error);
3297 return SaveLoadResult::Ok;
3298 }
3299 WaitTillSaved();
3300
3301 try {
3302 /* Load a TTDLX or TTDPatch game */
3305
3306 InitializeGame(256, 256, true, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused
3307
3308 /* TTD/TTO savegames have no NewGRFs, TTDP savegame have them
3309 * and if so a new NewGRF list will be made in LoadOldSaveGame.
3310 * Note: this is done here because AfterLoadGame is also called
3311 * for OTTD savegames which have their own NewGRF logic. */
3313 _gamelog.Reset();
3314 if (!LoadOldSaveGame(filename)) return SaveLoadResult::ReInit;
3317 _gamelog.StartAction(GamelogActionType::Load);
3318 if (!AfterLoadGame()) {
3319 _gamelog.StopAction();
3321 }
3322 _gamelog.StopAction();
3323 return SaveLoadResult::Ok;
3324 }
3325
3326 assert(dft == DetailedFileType::GameFile);
3327 switch (fop) {
3330 break;
3331
3333 _sl.action = SaveLoadAction::Load;
3334 break;
3335
3337 _sl.action = SaveLoadAction::Save;
3338 break;
3339
3340 default: NOT_REACHED();
3341 }
3342
3343 auto fh = (fop == SaveLoadOperation::Save) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
3344
3345 /* Make it a little easier to load savegames from the console */
3346 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Save);
3347 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Base);
3348 if (!fh.has_value() && fop != SaveLoadOperation::Save) fh = FioFOpenFile(filename, "rb", Subdirectory::Scenario);
3349
3350 if (!fh.has_value()) {
3351 SlError(fop == SaveLoadOperation::Save ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3352 }
3353
3354 if (fop == SaveLoadOperation::Save) { // SAVE game
3355 Debug(desync, 1, "save: {:08x}; {:02x}; {}", TimerGameEconomy::date, TimerGameEconomy::date_fract, filename);
3356 if (!_settings_client.gui.threaded_saves) threaded = false;
3357
3358 return DoSave(std::make_shared<FileWriter>(std::move(*fh)), threaded);
3359 }
3360
3361 /* LOAD game */
3362 assert(fop == SaveLoadOperation::Load || fop == SaveLoadOperation::Check);
3363 Debug(desync, 1, "load: {}", filename);
3364 return DoLoad(std::make_shared<FileReader>(std::move(*fh)), fop == SaveLoadOperation::Check);
3365 } catch (...) {
3366 /* This code may be executed both for old and new save games. */
3368
3369 if (fop != SaveLoadOperation::Check) Debug(sl, 0, "{} {}", GetSaveLoadErrorType().GetDecodedString(), GetSaveLoadErrorMessage().GetDecodedString());
3370
3371 /* A saver/loader exception!! reinitialize all variables to prevent crash! */
3373 }
3374}
3375
3381{
3382 std::string filename;
3383
3384 if (_settings_client.gui.keep_all_autosave) {
3385 filename = GenerateDefaultSaveName() + counter.Extension();
3386 } else {
3387 filename = counter.Filename();
3388 }
3389
3390 Debug(sl, 2, "Autosaving to '{}'", filename);
3392 ShowErrorMessage(GetEncodedString(STR_ERROR_AUTOSAVE_FAILED), {}, WarningLevel::Error);
3393 }
3394}
3395
3396
3402
3408{
3409 /* Check if we have a name for this map, which is the name of the first
3410 * available company. When there's no company available we'll use
3411 * 'Spectator' as "company" name. */
3412 CompanyID cid = _local_company;
3413 if (!Company::IsValidID(cid)) {
3414 for (const Company *c : Company::Iterate()) {
3415 cid = c->index;
3416 break;
3417 }
3418 }
3419
3420 std::array<StringParameter, 4> params{};
3421 auto it = params.begin();
3422 *it++ = cid;
3423
3424 /* We show the current game time differently depending on the timekeeping units used by this game. */
3426 /* Insert time played. */
3427 const auto play_time = TimerGameTick::counter / Ticks::TICKS_PER_SECOND;
3428 *it++ = STR_SAVEGAME_DURATION_REALTIME;
3429 *it++ = play_time / 60 / 60;
3430 *it++ = (play_time / 60) % 60;
3431 } else {
3432 /* Insert current date */
3433 switch (_settings_client.gui.date_format_in_default_names) {
3434 case 0: *it++ = STR_JUST_DATE_LONG; break;
3435 case 1: *it++ = STR_JUST_DATE_TINY; break;
3436 case 2: *it++ = STR_JUST_DATE_ISO; break;
3437 default: NOT_REACHED();
3438 }
3439 *it++ = TimerGameEconomy::date;
3440 }
3441
3442 /* Get the correct string (special string for when there's not company) */
3443 std::string filename = GetStringWithArgs(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT, params);
3444 SanitizeFilename(filename);
3445 return filename;
3446}
3447
3454{
3457 this->ftype = FIOS_TYPE_INVALID;
3458 return;
3459 }
3460
3461 this->file_op = fop;
3462 this->ftype = ft;
3463}
3464
3470{
3471 this->SetMode(item.type);
3472 this->name = item.name;
3473 this->title = item.title;
3474}
3475
3477{
3478 assert(this->load_description.has_value());
3479 return *this->load_description;
3480}
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:542
std::optional< std::vector< SaveLoad > > load_description
Description derived from savegame being loaded.
Definition saveload.h:544
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(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
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:27
@ Error
Errors (eg. saving/loading failed).
Definition error.h:26
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:41
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:19
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
static const ChunkHandler * SlFindChunkHandler(uint32_t id)
Find the ChunkHandler that will be used for processing the found chunk in the savegame or in memory.
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:626
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:512
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.
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
int SlIterateArray()
Iterate through the elements of an array and read the whole thing.
Definition saveload.cpp:734
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:860
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:717
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:776
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.
Definition saveload.cpp:999
static uint SlReadSimpleGamma()
Read in the header descriptor of an object or an array.
Definition saveload.cpp:470
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:840
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:922
void WriteValue(void *ptr, VarMemType conv, int64_t val)
Write the value of a setting.
Definition saveload.cpp:896
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:788
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:691
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:661
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
static const SaveLoadFormat * DetermineSaveLoadFormat(uint32_t tag, uint32_t raw_version)
Determines the SaveLoadFormat that is connected to the given tag.
int64_t ReadValue(const void *ptr, VarMemType conv)
Return a signed-long version of the value of a setting.
Definition saveload.cpp:872
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.
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:541
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:676
@ U64
A 64 bit unsigned int.
Definition saveload.h:686
@ Name
old custom name to be converted to a string pointer
Definition saveload.h:690
@ I8
A 8 bit signed int.
Definition saveload.h:679
@ U8
A 8 bit unsigned int.
Definition saveload.h:680
@ StrQ
string pointer enclosed in quotes
Definition saveload.h:689
@ Null
useful to write zeros in savegame.
Definition saveload.h:687
@ I16
A 16 bit signed int.
Definition saveload.h:681
@ Bool
A boolean value.
Definition saveload.h:678
@ U32
A 32 bit unsigned int.
Definition saveload.h:684
@ I32
A 32 bit signed int.
Definition saveload.h:683
@ I64
A 64 bit signed int.
Definition saveload.h:685
@ Str
string pointer
Definition saveload.h:688
@ U16
A 16 bit unsigned int.
Definition saveload.h:682
VarFileType
The types/structures of data that can be stored in the file.
Definition saveload.h:657
@ String
A string.
Definition saveload.h:670
@ U64
A 64 bit unsigned int.
Definition saveload.h:668
@ I8
A 8 bit signed int.
Definition saveload.h:661
@ U8
A 8 bit unsigned int.
Definition saveload.h:662
@ Struct
An arbitrary structure.
Definition saveload.h:671
@ I16
A 16 bit signed int.
Definition saveload.h:663
@ U32
A 32 bit unsigned int.
Definition saveload.h:666
@ StringID
StringID offset into strings-array.
Definition saveload.h:669
@ I32
A 32 bit signed int.
Definition saveload.h:665
@ I64
A 64 bit signed int.
Definition saveload.h:667
@ U16
A 16 bit unsigned int.
Definition saveload.h:664
SavegameType
Types of save games.
Definition saveload.h:443
@ OTTD
OTTD savegame.
Definition saveload.h:447
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:1365
SLRefType
Type of reference (SLE_REF, SLE_CONDREF).
Definition saveload.h:642
@ LinkGraph
Load/save a reference to a link graph.
Definition saveload.h:652
@ CargoPacket
Load/save a reference to a cargo packet.
Definition saveload.h:649
@ OrderList
Load/save a reference to an orderlist.
Definition saveload.h:650
@ Station
Load/save a reference to a station.
Definition saveload.h:644
@ OldVehicle
Load/save an old-style reference to a vehicle (for pre-4.4 savegames).
Definition saveload.h:646
@ Storage
Load/save a reference to a persistent storage.
Definition saveload.h:651
@ EngineRenew
Load/save a reference to an engine renewal (autoreplace).
Definition saveload.h:648
@ Town
Load/save a reference to a town.
Definition saveload.h:645
@ LinkGraphJob
Load/save a reference to a link graph job.
Definition saveload.h:653
@ Vehicle
Load/save a reference to a vehicle.
Definition saveload.h:643
@ RoadStop
Load/save a reference to a bus/truck stop.
Definition saveload.h:647
void * GetVariableAddress(const void *object, const SaveLoad &sld)
Get the address of the variable.
Definition saveload.h:1319
std::span< const ChunkHandlerRef > ChunkHandlerTable
A table of ChunkHandler entries.
Definition saveload.h:533
SaveLoadType
Type of data saved.
Definition saveload.h:766
@ ReferenceList
Save/load a list of SaveLoadType::Reference elements.
Definition saveload.h:775
@ String
Save/load a std::string.
Definition saveload.h:771
@ Array
Save/load a fixed-size array of SaveLoadType::Variable elements.
Definition saveload.h:773
@ Variable
Save/load a variable.
Definition saveload.h:767
@ Vector
Save/load a vector of SaveLoadType::Variable elements.
Definition saveload.h:774
@ Reference
Save/load a reference.
Definition saveload.h:768
@ StructList
Save/load a list of structs.
Definition saveload.h:776
@ Struct
Save/load a struct.
Definition saveload.h:769
@ Null
Save null-bytes and load to nowhere.
Definition saveload.h:779
@ SaveByte
Save (but not load) a byte.
Definition saveload.h:778
@ ReferenceVector
Save/load a vector of SaveLoadType::Reference elements.
Definition saveload.h:781
std::span< const struct SaveLoadCompat > SaveLoadCompatTable
A table of SaveLoadCompat entries.
Definition saveload.h:539
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1278
SaveLoadResult
Save or load result codes.
Definition saveload.h:425
@ Error
error that was caught before internal structures were modified
Definition saveload.h:427
@ Ok
completed successfully
Definition saveload.h:426
@ ReInit
error that was caught in the middle of updating game state, need to clear it. (can only happen during...
Definition saveload.h:428
SaveLoadVersion
SaveLoad versions Previous savegame versions, the trunk revision where they were introduced and the r...
Definition saveload.h:30
@ EndPatchpacks
Saveload version: 286 Last known patchpack to use a version just above ours.
Definition saveload.h:322
@ MoveSccEncoded
Saveload version: 169, SVN revision: 23816 Move SCC_ENCODED to the first StringControlCode.
Definition saveload.h:246
@ TownTolerancePauseMode
Saveload version: 4.0, SVN revision: 1 Town council tolerance and pause mode.
Definition saveload.h:38
@ MoreCargoPackets
Saveload version: 69, SVN revision: 10319 Allow more than ~65k cargo packets.
Definition saveload.h:126
@ EncodedStringFormat
Saveload version: 350, GitHub pull request: 13499 Encoded String format changed.
Definition saveload.h:400
@ FixSccEncodedNegative
Saveload version: 353, GitHub pull request: 14049 Fix encoding of negative parameters.
Definition saveload.h:403
@ MinVersion
First savegame version.
Definition saveload.h:31
@ SaveloadListLength
Saveload version: 293, GitHub pull request: 9374 Consistency in list length with SaveLoadType::Struc...
Definition saveload.h:331
@ MaxVersion
Highest possible saveload version.
Definition saveload.h:421
@ StartPatchpacks
Saveload version: 220 First known patchpack to use a version just above ours.
Definition saveload.h:321
std::vector< SaveLoad > SlTableHeader(const SaveLoadTable &slt)
Save or Load a table header.
std::span< const struct SaveLoad > SaveLoadTable
A table of SaveLoad entries.
Definition saveload.h:536
ChunkType
Type of a chunk.
Definition saveload.h:471
@ SparseTable
A SparseArray with a header describing the elements.
Definition saveload.h:476
@ ReadOnly
Chunk is never saved.
Definition saveload.h:479
@ Array
Contiguous array of elements starting at index 0.
Definition saveload.h:473
@ Table
An Array with a header describing the elements.
Definition saveload.h:475
@ FileTypeMask
All ChunkType values that are saved in the file have to be within this mask.
Definition saveload.h:478
@ Riff
4 bits store the chunk type, 28 bits the number of bytes.
Definition saveload.h:472
@ SparseArray
Array of elements with index for each element.
Definition saveload.h:474
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.
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:271
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.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
Container for cargo from the same location and time.
Definition cargopacket.h:41
Handlers and description of chunk.
Definition saveload.h:483
ChunkType type
Type of the chunk.
Definition saveload.h:485
virtual void LoadCheck(size_t len=0) const
Load the chunk for game preview.
virtual void Load() const =0
Load the chunk.
uint32_t id
Unique ID (4 letters).
Definition saveload.h:484
virtual void Save() const
Save the chunk.
Definition saveload.h:496
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.
Definition saveload.h:432
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.
Definition saveload.h:434
SaveLoadOperation file_op
File operation to perform.
Definition saveload.h:433
std::string name
Name of the file.
Definition saveload.h:435
EncodedString title
Internal name of the game.
Definition saveload.h:436
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:78
A savegame name automatically numbered.
Definition fios.h:119
std::string Filename()
Generate a savegame name and number according to _settings_client.gui.max_num_autosaves.
Definition fios.cpp:723
std::string Extension()
Generate an extension for a savegame name.
Definition fios.cpp:733
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.
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.
uint32_t tag
the 4-letter tag by which it is identified in the 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.
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:787
uint16_t length
(Conditional) length of the variable (eg. arrays) (max array size is 65536 elements).
Definition saveload.h:791
std::shared_ptr< SaveLoadHandler > handler
Custom handler for Save/Load procs.
Definition saveload.h:796
SaveLoadVersion version_to
Save/load the variable before this savegame version.
Definition saveload.h:793
SaveLoadType cmd
The action to take with the saved/loaded type, All types need different action.
Definition saveload.h:789
std::string name
Name of this field (optional, used for tables).
Definition saveload.h:788
VarType conv
Type of the variable to be saved; this field combines both FileVarType and MemVarType.
Definition saveload.h:790
SaveLoadVersion version_from
Save/load the variable starting from this savegame version.
Definition saveload.h:792
Container/wrapper for the file type that is used in tables in the save game.
Definition saveload.cpp:575
uint8_t storage
Actual storage of the file type.
Definition saveload.cpp:577
constexpr VarFileType Type() const
Get the VarType for this field.
Definition saveload.cpp:614
SavegameFileType(VarFileType file_type, bool has_field_length=false)
Create the type.
Definition saveload.cpp:587
constexpr bool HasFieldLength() const
Does this field have a length?
Definition saveload.cpp:604
constexpr bool IsEnd() const
Is this the end-of-table marker?
Definition saveload.cpp:598
static constexpr uint8_t HAS_FIELD_LENGTH_BIT
Set this bit to denote the type has a field length.
Definition saveload.cpp:576
SavegameFileType()
Create an end-of-table marker.
Definition saveload.cpp:580
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:695
SLRefType ref
The reference type.
Definition saveload.h:699
VarMemType mem
The way of storing data in memory.
Definition saveload.h:697
StringValidationSettings string_validation_settings
Any settings related to validation of the strings.
Definition saveload.h:698
VarFileType file
The way of storing data in the file.
Definition saveload.h:696
static constexpr VarType U16
Store a 16 bits unsigned int.
Definition saveload.h:754
static constexpr VarType U8
Store a 8 bits unsigned int.
Definition saveload.h:752
static constexpr VarType STR
Store string.
Definition saveload.h:760
static constexpr VarType I16
Store a 16 bits signed int.
Definition saveload.h:753
static constexpr VarType I8
Store a 8 bits signed int.
Definition saveload.h:751
static constexpr VarType U32
Store a 32 bits unsigned int.
Definition saveload.h:756
static constexpr VarType STRINGID
Store a StringID.
Definition saveload.h:759
static constexpr VarType I32
Store a 32 bits signed int.
Definition saveload.h:755
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:3315
Window functions not directly related to making/drawing windows.