OpenTTD Source 20260206-master-g4d4e37dbf1
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
94
95enum NeedLength : uint8_t {
96 NL_NONE = 0,
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
199 uint8_t block_mode;
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 = SLA_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 == SLA_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 == SLA_LOAD_CHECK) {
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 == SLA_LOAD || _sl.action == SLA_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 (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((uint32_t)(x >> 32));
458 SlWriteUint32((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((uint8_t)(0xF0));
520 SlWriteByte((uint8_t)(i >> 24));
521 } else {
522 SlWriteByte((uint8_t)(0xE0 | (i >> 24)));
523 }
524 SlWriteByte((uint8_t)(i >> 16));
525 } else {
526 SlWriteByte((uint8_t)(0xC0 | (i >> 16)));
527 }
528 SlWriteByte((uint8_t)(i >> 8));
529 } else {
530 SlWriteByte((uint8_t)(0x80 | (i >> 8)));
531 }
532 }
533 SlWriteByte((uint8_t)i);
534}
535
537static inline uint SlGetGammaLength(size_t i)
538{
539 return 1 + (i >= (1 << 7)) + (i >= (1 << 14)) + (i >= (1 << 21)) + (i >= (1 << 28));
540}
541
542static inline uint SlReadSparseIndex()
543{
544 return SlReadSimpleGamma();
545}
546
547static inline void SlWriteSparseIndex(uint index)
548{
549 SlWriteSimpleGamma(index);
550}
551
552static inline uint SlReadArrayLength()
553{
554 return SlReadSimpleGamma();
555}
556
557static inline void SlWriteArrayLength(size_t length)
558{
559 SlWriteSimpleGamma(length);
560}
561
562static inline uint SlGetArrayLength(size_t length)
563{
564 return SlGetGammaLength(length);
565}
566
570static uint8_t GetSavegameFileType(const SaveLoad &sld)
571{
572 switch (sld.cmd) {
573 case SL_VAR:
574 return GetVarFileType(sld.conv); break;
575
576 case SL_STDSTR:
577 case SL_ARR:
578 case SL_VECTOR:
579 case SL_DEQUE:
580 return GetVarFileType(sld.conv) | SLE_FILE_HAS_LENGTH_FIELD; break;
581
582 case SL_REF:
583 return IsSavegameVersionBefore(SLV_69) ? SLE_FILE_U16 : SLE_FILE_U32;
584
585 case SL_REFLIST:
586 case SL_REFVECTOR:
587 return (IsSavegameVersionBefore(SLV_69) ? SLE_FILE_U16 : SLE_FILE_U32) | SLE_FILE_HAS_LENGTH_FIELD;
588
589 case SL_SAVEBYTE:
590 return SLE_FILE_U8;
591
592 case SL_STRUCT:
593 case SL_STRUCTLIST:
594 return SLE_FILE_STRUCT | SLE_FILE_HAS_LENGTH_FIELD;
595
596 default: NOT_REACHED();
597 }
598}
599
606static inline uint SlCalcConvMemLen(VarType conv)
607{
608 switch (GetVarMemType(conv)) {
609 case SLE_VAR_BL: return sizeof(bool);
610 case SLE_VAR_I8: return sizeof(int8_t);
611 case SLE_VAR_U8: return sizeof(uint8_t);
612 case SLE_VAR_I16: return sizeof(int16_t);
613 case SLE_VAR_U16: return sizeof(uint16_t);
614 case SLE_VAR_I32: return sizeof(int32_t);
615 case SLE_VAR_U32: return sizeof(uint32_t);
616 case SLE_VAR_I64: return sizeof(int64_t);
617 case SLE_VAR_U64: return sizeof(uint64_t);
618 case SLE_VAR_NULL: return 0;
619
620 case SLE_VAR_STR:
621 case SLE_VAR_STRQ:
622 return SlReadArrayLength();
623
624 case SLE_VAR_NAME:
625 default:
626 NOT_REACHED();
627 }
628}
629
636static inline uint8_t SlCalcConvFileLen(VarType conv)
637{
638 switch (GetVarFileType(conv)) {
639 case SLE_FILE_END: return 0;
640 case SLE_FILE_I8: return sizeof(int8_t);
641 case SLE_FILE_U8: return sizeof(uint8_t);
642 case SLE_FILE_I16: return sizeof(int16_t);
643 case SLE_FILE_U16: return sizeof(uint16_t);
644 case SLE_FILE_I32: return sizeof(int32_t);
645 case SLE_FILE_U32: return sizeof(uint32_t);
646 case SLE_FILE_I64: return sizeof(int64_t);
647 case SLE_FILE_U64: return sizeof(uint64_t);
648 case SLE_FILE_STRINGID: return sizeof(uint16_t);
649
650 case SLE_FILE_STRING:
651 return SlReadArrayLength();
652
653 case SLE_FILE_STRUCT:
654 default:
655 NOT_REACHED();
656 }
657}
658
660static inline size_t SlCalcRefLen()
661{
662 return IsSavegameVersionBefore(SLV_69) ? 2 : 4;
663}
664
665void SlSetArrayIndex(uint index)
666{
667 _sl.need_length = NL_WANTLENGTH;
668 _sl.array_index = index;
669}
670
671static size_t _next_offs;
672
678{
679 /* After reading in the whole array inside the loop
680 * we must have read in all the data, so we must be at end of current block. */
681 if (_next_offs != 0 && _sl.reader->GetSize() != _next_offs) {
682 SlErrorCorruptFmt("Invalid chunk size iterating array - expected to be at position {}, actually at {}", _next_offs, _sl.reader->GetSize());
683 }
684
685 for (;;) {
686 uint length = SlReadArrayLength();
687 if (length == 0) {
688 assert(!_sl.expect_table_header);
689 _next_offs = 0;
690 return -1;
691 }
692
693 _sl.obj_len = --length;
694 _next_offs = _sl.reader->GetSize() + length;
695
696 if (_sl.expect_table_header) {
697 _sl.expect_table_header = false;
698 return INT32_MAX;
699 }
700
701 int index;
702 switch (_sl.block_mode) {
703 case CH_SPARSE_TABLE:
704 case CH_SPARSE_ARRAY: index = (int)SlReadSparseIndex(); break;
705 case CH_TABLE:
706 case CH_ARRAY: index = _sl.array_index++; break;
707 default:
708 Debug(sl, 0, "SlIterateArray error");
709 return -1; // error
710 }
711
712 if (length != 0) return index;
713 }
714}
715
720{
721 while (SlIterateArray() != -1) {
722 SlSkipBytes(_next_offs - _sl.reader->GetSize());
723 }
724}
725
731void SlSetLength(size_t length)
732{
733 assert(_sl.action == SLA_SAVE);
734
735 switch (_sl.need_length) {
736 case NL_WANTLENGTH:
737 _sl.need_length = NL_NONE;
738 if ((_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) && _sl.expect_table_header) {
739 _sl.expect_table_header = false;
740 SlWriteArrayLength(length + 1);
741 break;
742 }
743
744 switch (_sl.block_mode) {
745 case CH_RIFF:
746 /* Ugly encoding of >16M RIFF chunks
747 * The lower 24 bits are normal
748 * The uppermost 4 bits are bits 24:27 */
749 assert(length < (1 << 28));
750 SlWriteUint32((uint32_t)((length & 0xFFFFFF) | ((length >> 24) << 28)));
751 break;
752 case CH_TABLE:
753 case CH_ARRAY:
754 assert(_sl.last_array_index <= _sl.array_index);
755 while (++_sl.last_array_index <= _sl.array_index) {
756 SlWriteArrayLength(1);
757 }
758 SlWriteArrayLength(length + 1);
759 break;
760 case CH_SPARSE_TABLE:
761 case CH_SPARSE_ARRAY:
762 SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index.
763 SlWriteSparseIndex(_sl.array_index);
764 break;
765 default: NOT_REACHED();
766 }
767 break;
768
769 case NL_CALCLENGTH:
770 _sl.obj_len += (int)length;
771 break;
772
773 default: NOT_REACHED();
774 }
775}
776
783static void SlCopyBytes(void *ptr, size_t length)
784{
785 uint8_t *p = (uint8_t *)ptr;
786
787 switch (_sl.action) {
788 case SLA_LOAD_CHECK:
789 case SLA_LOAD:
790 for (; length != 0; length--) *p++ = SlReadByte();
791 break;
792 case SLA_SAVE:
793 for (; length != 0; length--) SlWriteByte(*p++);
794 break;
795 default: NOT_REACHED();
796 }
797}
798
801{
802 return _sl.obj_len;
803}
804
812int64_t ReadValue(const void *ptr, VarType conv)
813{
814 switch (GetVarMemType(conv)) {
815 case SLE_VAR_BL: return (*(const bool *)ptr != 0);
816 case SLE_VAR_I8: return *(const int8_t *)ptr;
817 case SLE_VAR_U8: return *(const uint8_t *)ptr;
818 case SLE_VAR_I16: return *(const int16_t *)ptr;
819 case SLE_VAR_U16: return *(const uint16_t*)ptr;
820 case SLE_VAR_I32: return *(const int32_t *)ptr;
821 case SLE_VAR_U32: return *(const uint32_t*)ptr;
822 case SLE_VAR_I64: return *(const int64_t *)ptr;
823 case SLE_VAR_U64: return *(const uint64_t*)ptr;
824 case SLE_VAR_NULL:return 0;
825 default: NOT_REACHED();
826 }
827}
828
836void WriteValue(void *ptr, VarType conv, int64_t val)
837{
838 switch (GetVarMemType(conv)) {
839 case SLE_VAR_BL: *(bool *)ptr = (val != 0); break;
840 case SLE_VAR_I8: *(int8_t *)ptr = val; break;
841 case SLE_VAR_U8: *(uint8_t *)ptr = val; break;
842 case SLE_VAR_I16: *(int16_t *)ptr = val; break;
843 case SLE_VAR_U16: *(uint16_t*)ptr = val; break;
844 case SLE_VAR_I32: *(int32_t *)ptr = val; break;
845 case SLE_VAR_U32: *(uint32_t*)ptr = val; break;
846 case SLE_VAR_I64: *(int64_t *)ptr = val; break;
847 case SLE_VAR_U64: *(uint64_t*)ptr = val; break;
848 case SLE_VAR_NAME: *reinterpret_cast<std::string *>(ptr) = CopyFromOldName(val); break;
849 case SLE_VAR_NULL: break;
850 default: NOT_REACHED();
851 }
852}
853
862static void SlSaveLoadConv(void *ptr, VarType conv)
863{
864 switch (_sl.action) {
865 case SLA_SAVE: {
866 int64_t x = ReadValue(ptr, conv);
867
868 /* Write the value to the file and check if its value is in the desired range */
869 switch (GetVarFileType(conv)) {
870 case SLE_FILE_I8: assert(x >= -128 && x <= 127); SlWriteByte(x);break;
871 case SLE_FILE_U8: assert(x >= 0 && x <= 255); SlWriteByte(x);break;
872 case SLE_FILE_I16:assert(x >= -32768 && x <= 32767); SlWriteUint16(x);break;
874 case SLE_FILE_U16:assert(x >= 0 && x <= 65535); SlWriteUint16(x);break;
875 case SLE_FILE_I32:
876 case SLE_FILE_U32: SlWriteUint32((uint32_t)x);break;
877 case SLE_FILE_I64:
878 case SLE_FILE_U64: SlWriteUint64(x);break;
879 default: NOT_REACHED();
880 }
881 break;
882 }
883 case SLA_LOAD_CHECK:
884 case SLA_LOAD: {
885 int64_t x;
886 /* Read a value from the file */
887 switch (GetVarFileType(conv)) {
888 case SLE_FILE_I8: x = (int8_t )SlReadByte(); break;
889 case SLE_FILE_U8: x = (uint8_t )SlReadByte(); break;
890 case SLE_FILE_I16: x = (int16_t )SlReadUint16(); break;
891 case SLE_FILE_U16: x = (uint16_t)SlReadUint16(); break;
892 case SLE_FILE_I32: x = (int32_t )SlReadUint32(); break;
893 case SLE_FILE_U32: x = (uint32_t)SlReadUint32(); break;
894 case SLE_FILE_I64: x = (int64_t )SlReadUint64(); break;
895 case SLE_FILE_U64: x = (uint64_t)SlReadUint64(); break;
896 case SLE_FILE_STRINGID: x = RemapOldStringID((uint16_t)SlReadUint16()); break;
897 default: NOT_REACHED();
898 }
899
900 /* Write The value to the struct. These ARE endian safe. */
901 WriteValue(ptr, conv, x);
902 break;
903 }
904 case SLA_PTRS: break;
905 case SLA_NULL: break;
906 default: NOT_REACHED();
907 }
908}
909
917static inline size_t SlCalcStdStringLen(const void *ptr)
918{
919 const std::string *str = reinterpret_cast<const std::string *>(ptr);
920
921 size_t len = str->length();
922 return len + SlGetArrayLength(len); // also include the length of the index
923}
924
925
934void FixSCCEncoded(std::string &str, bool fix_code)
935{
936 if (str.empty()) return;
937
938 /* We need to convert from old escape-style encoding to record separator encoding.
939 * Initial `<SCC_ENCODED><STRINGID>` stays the same.
940 *
941 * `:<SCC_ENCODED><STRINGID>` becomes `<RS><SCC_ENCODED><STRINGID>`
942 * `:<HEX>` becomes `<RS><SCC_ENCODED_NUMERIC><HEX>`
943 * `:"<STRING>"` becomes `<RS><SCC_ENCODED_STRING><STRING>`
944 */
945 std::string result;
946 StringBuilder builder(result);
947
948 bool is_encoded = false; // Set if we determine by the presence of SCC_ENCODED that the string is an encoded string.
949 bool in_string = false; // Set if we in a string, between double-quotes.
950 bool need_type = true; // Set if a parameter type needs to be emitted.
951
952 StringConsumer consumer(str);
953 while (consumer.AnyBytesLeft()) {
954 char32_t c;
955 if (auto r = consumer.TryReadUtf8(); r.has_value()) {
956 c = *r;
957 } else {
958 break;
959 }
960 if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) {
961 builder.PutUtf8(SCC_ENCODED);
962 need_type = false;
963 is_encoded = true;
964 continue;
965 }
966
967 /* If the first character is not SCC_ENCODED then we don't have to do any conversion. */
968 if (!is_encoded) return;
969
970 if (c == '"') {
971 in_string = !in_string;
972 if (in_string && need_type) {
973 /* Started a new string parameter. */
975 need_type = false;
976 }
977 continue;
978 }
979
980 if (!in_string && c == ':') {
981 builder.PutUtf8(SCC_RECORD_SEPARATOR);
982 need_type = true;
983 continue;
984 }
985 if (need_type) {
986 /* Started a new numeric parameter. */
988 need_type = false;
989 }
990
991 builder.PutUtf8(c);
992 }
993
994 str = std::move(result);
995}
996
1001void FixSCCEncodedNegative(std::string &str)
1002{
1003 if (str.empty()) return;
1004
1005 StringConsumer consumer(str);
1006
1007 /* Check whether this is an encoded string */
1008 if (!consumer.ReadUtf8If(SCC_ENCODED)) return;
1009
1010 std::string result;
1011 StringBuilder builder(result);
1012 builder.PutUtf8(SCC_ENCODED);
1013 while (consumer.AnyBytesLeft()) {
1014 /* Copy until next record */
1015 builder.Put(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::READ_ONE_SEPARATOR));
1016
1017 /* Check whether this is a numeric parameter */
1018 if (!consumer.ReadUtf8If(SCC_ENCODED_NUMERIC)) continue;
1020
1021 /* First try unsigned */
1022 if (auto u = consumer.TryReadIntegerBase<uint64_t>(16); u.has_value()) {
1023 builder.PutIntegerBase<uint64_t>(*u, 16);
1024 } else {
1025 /* Read as signed, store as unsigned */
1026 auto s = consumer.ReadIntegerBase<int64_t>(16);
1027 builder.PutIntegerBase<uint64_t>(static_cast<uint64_t>(s), 16);
1028 }
1029 }
1030
1031 str = std::move(result);
1032}
1033
1040void SlReadString(std::string &str, size_t length)
1041{
1042 str.resize(length);
1043 SlCopyBytes(str.data(), length);
1044}
1045
1051static void SlStdString(void *ptr, VarType conv)
1052{
1053 std::string *str = reinterpret_cast<std::string *>(ptr);
1054
1055 switch (_sl.action) {
1056 case SLA_SAVE: {
1057 size_t len = str->length();
1058 SlWriteArrayLength(len);
1059 SlCopyBytes(const_cast<void *>(static_cast<const void *>(str->data())), len);
1060 break;
1061 }
1062
1063 case SLA_LOAD_CHECK:
1064 case SLA_LOAD: {
1065 size_t len = SlReadArrayLength();
1066 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1067 SlSkipBytes(len);
1068 return;
1069 }
1070
1071 SlReadString(*str, len);
1072
1074 if ((conv & SLF_ALLOW_CONTROL) != 0) {
1078 }
1079 if ((conv & SLF_ALLOW_NEWLINE) != 0) {
1081 }
1082 if ((conv & SLF_REPLACE_TABCRLF) != 0) {
1084 }
1086 }
1087
1088 case SLA_PTRS: break;
1089 case SLA_NULL: break;
1090 default: NOT_REACHED();
1091 }
1092}
1093
1102static void SlCopyInternal(void *object, size_t length, VarType conv)
1103{
1104 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1105 assert(_sl.action != SLA_SAVE); // Use SL_NULL if you want to write null-bytes
1106 SlSkipBytes(length * SlCalcConvFileLen(conv));
1107 return;
1108 }
1109
1110 /* NOTICE - handle some buggy stuff, in really old versions everything was saved
1111 * as a byte-type. So detect this, and adjust object size accordingly */
1112 if (_sl.action != SLA_SAVE && _sl_version == 0) {
1113 /* all objects except difficulty settings */
1114 if (conv == SLE_INT16 || conv == SLE_UINT16 || conv == SLE_STRINGID ||
1115 conv == SLE_INT32 || conv == SLE_UINT32) {
1116 SlCopyBytes(object, length * SlCalcConvFileLen(conv));
1117 return;
1118 }
1119 /* used for conversion of Money 32bit->64bit */
1120 if (conv == (SLE_FILE_I32 | SLE_VAR_I64)) {
1121 for (uint i = 0; i < length; i++) {
1122 ((int64_t*)object)[i] = (int32_t)std::byteswap(SlReadUint32());
1123 }
1124 return;
1125 }
1126 }
1127
1128 /* If the size of elements is 1 byte both in file and memory, no special
1129 * conversion is needed, use specialized copy-copy function to speed up things */
1130 if (conv == SLE_INT8 || conv == SLE_UINT8) {
1131 SlCopyBytes(object, length);
1132 } else {
1133 uint8_t *a = (uint8_t*)object;
1134 uint8_t mem_size = SlCalcConvMemLen(conv);
1135
1136 for (; length != 0; length --) {
1137 SlSaveLoadConv(a, conv);
1138 a += mem_size; // get size
1139 }
1140 }
1141}
1142
1151void SlCopy(void *object, size_t length, VarType conv)
1152{
1153 if (_sl.action == SLA_PTRS || _sl.action == SLA_NULL) return;
1154
1155 /* Automatically calculate the length? */
1156 if (_sl.need_length != NL_NONE) {
1157 SlSetLength(length * SlCalcConvFileLen(conv));
1158 /* Determine length only? */
1159 if (_sl.need_length == NL_CALCLENGTH) return;
1160 }
1161
1162 SlCopyInternal(object, length, conv);
1163}
1164
1170static inline size_t SlCalcArrayLen(size_t length, VarType conv)
1171{
1172 return SlCalcConvFileLen(conv) * length + SlGetArrayLength(length);
1173}
1174
1181static void SlArray(void *array, size_t length, VarType conv)
1182{
1183 switch (_sl.action) {
1184 case SLA_SAVE:
1185 SlWriteArrayLength(length);
1186 SlCopyInternal(array, length, conv);
1187 return;
1188
1189 case SLA_LOAD_CHECK:
1190 case SLA_LOAD: {
1192 size_t sv_length = SlReadArrayLength();
1193 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1194 /* We don't know this field, so we assume the length in the savegame is correct. */
1195 length = sv_length;
1196 } else if (sv_length != length) {
1197 /* If the SLE_ARR changes size, a savegame bump is required
1198 * and the developer should have written conversion lines.
1199 * Error out to make this more visible. */
1200 SlErrorCorrupt("Fixed-length array is of wrong length");
1201 }
1202 }
1203
1204 SlCopyInternal(array, length, conv);
1205 return;
1206 }
1207
1208 case SLA_PTRS:
1209 case SLA_NULL:
1210 return;
1211
1212 default:
1213 NOT_REACHED();
1214 }
1215}
1216
1227static size_t ReferenceToInt(const void *obj, SLRefType rt)
1228{
1229 assert(_sl.action == SLA_SAVE);
1230
1231 if (obj == nullptr) return 0;
1232
1233 switch (rt) {
1234 case REF_VEHICLE_OLD: // Old vehicles we save as new ones
1235 case REF_VEHICLE: return ((const Vehicle*)obj)->index + 1;
1236 case REF_STATION: return ((const Station*)obj)->index + 1;
1237 case REF_TOWN: return ((const Town*)obj)->index + 1;
1238 case REF_ROADSTOPS: return ((const RoadStop*)obj)->index + 1;
1239 case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1;
1240 case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1;
1241 case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1;
1242 case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1;
1243 case REF_LINK_GRAPH: return ((const LinkGraph*)obj)->index + 1;
1244 case REF_LINK_GRAPH_JOB: return ((const LinkGraphJob*)obj)->index + 1;
1245 default: NOT_REACHED();
1246 }
1247}
1248
1259static void *IntToReference(size_t index, SLRefType rt)
1260{
1261 static_assert(sizeof(size_t) <= sizeof(void *));
1262
1263 assert(_sl.action == SLA_PTRS);
1264
1265 /* After version 4.3 REF_VEHICLE_OLD is saved as REF_VEHICLE,
1266 * and should be loaded like that */
1267 if (rt == REF_VEHICLE_OLD && !IsSavegameVersionBefore(SLV_4, 4)) {
1268 rt = REF_VEHICLE;
1269 }
1270
1271 /* No need to look up nullptr pointers, just return immediately */
1272 if (index == (rt == REF_VEHICLE_OLD ? 0xFFFF : 0)) return nullptr;
1273
1274 /* Correct index. Old vehicles were saved differently:
1275 * invalid vehicle was 0xFFFF, now we use 0x0000 for everything invalid. */
1276 if (rt != REF_VEHICLE_OLD) index--;
1277
1278 switch (rt) {
1279 case REF_ORDERLIST:
1280 if (OrderList::IsValidID(index)) return OrderList::Get(index);
1281 SlErrorCorrupt("Referencing invalid OrderList");
1282
1283 case REF_VEHICLE_OLD:
1284 case REF_VEHICLE:
1285 if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
1286 SlErrorCorrupt("Referencing invalid Vehicle");
1287
1288 case REF_STATION:
1289 if (Station::IsValidID(index)) return Station::Get(index);
1290 SlErrorCorrupt("Referencing invalid Station");
1291
1292 case REF_TOWN:
1293 if (Town::IsValidID(index)) return Town::Get(index);
1294 SlErrorCorrupt("Referencing invalid Town");
1295
1296 case REF_ROADSTOPS:
1297 if (RoadStop::IsValidID(index)) return RoadStop::Get(index);
1298 SlErrorCorrupt("Referencing invalid RoadStop");
1299
1300 case REF_ENGINE_RENEWS:
1301 if (EngineRenew::IsValidID(index)) return EngineRenew::Get(index);
1302 SlErrorCorrupt("Referencing invalid EngineRenew");
1303
1304 case REF_CARGO_PACKET:
1305 if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
1306 SlErrorCorrupt("Referencing invalid CargoPacket");
1307
1308 case REF_STORAGE:
1309 if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index);
1310 SlErrorCorrupt("Referencing invalid PersistentStorage");
1311
1312 case REF_LINK_GRAPH:
1313 if (LinkGraph::IsValidID(index)) return LinkGraph::Get(index);
1314 SlErrorCorrupt("Referencing invalid LinkGraph");
1315
1316 case REF_LINK_GRAPH_JOB:
1317 if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index);
1318 SlErrorCorrupt("Referencing invalid LinkGraphJob");
1319
1320 default: NOT_REACHED();
1321 }
1322}
1323
1329void SlSaveLoadRef(void *ptr, VarType conv)
1330{
1331 switch (_sl.action) {
1332 case SLA_SAVE:
1333 SlWriteUint32((uint32_t)ReferenceToInt(*(void **)ptr, (SLRefType)conv));
1334 break;
1335 case SLA_LOAD_CHECK:
1336 case SLA_LOAD:
1337 *(size_t *)ptr = IsSavegameVersionBefore(SLV_69) ? SlReadUint16() : SlReadUint32();
1338 break;
1339 case SLA_PTRS:
1340 *(void **)ptr = IntToReference(*(size_t *)ptr, (SLRefType)conv);
1341 break;
1342 case SLA_NULL:
1343 *(void **)ptr = nullptr;
1344 break;
1345 default: NOT_REACHED();
1346 }
1347}
1348
1352template <template <typename, typename> typename Tstorage, typename Tvar, typename Tallocator = std::allocator<Tvar>>
1354 typedef Tstorage<Tvar, Tallocator> SlStorageT;
1355public:
1362 static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd = SL_VAR)
1363 {
1364 assert(cmd == SL_VAR || cmd == SL_REF);
1365
1366 const SlStorageT *list = static_cast<const SlStorageT *>(storage);
1367
1368 int type_size = SlGetArrayLength(list->size());
1369 int item_size = SlCalcConvFileLen(cmd == SL_VAR ? conv : (VarType)SLE_FILE_U32);
1370 return list->size() * item_size + type_size;
1371 }
1372
1373 static void SlSaveLoadMember(SaveLoadType cmd, Tvar *item, VarType conv)
1374 {
1375 switch (cmd) {
1376 case SL_VAR: SlSaveLoadConv(item, conv); break;
1377 case SL_REF: SlSaveLoadRef(item, conv); break;
1378 case SL_STDSTR: SlStdString(item, conv); break;
1379 default:
1380 NOT_REACHED();
1381 }
1382 }
1383
1390 static void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd = SL_VAR)
1391 {
1392 assert(cmd == SL_VAR || cmd == SL_REF || cmd == SL_STDSTR);
1393
1394 SlStorageT *list = static_cast<SlStorageT *>(storage);
1395
1396 switch (_sl.action) {
1397 case SLA_SAVE:
1398 SlWriteArrayLength(list->size());
1399
1400 for (auto &item : *list) {
1401 SlSaveLoadMember(cmd, &item, conv);
1402 }
1403 break;
1404
1405 case SLA_LOAD_CHECK:
1406 case SLA_LOAD: {
1407 size_t length;
1408 switch (cmd) {
1409 case SL_VAR: length = IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? SlReadUint32() : SlReadArrayLength(); break;
1410 case SL_REF: length = IsSavegameVersionBefore(SLV_69) ? SlReadUint16() : IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? SlReadUint32() : SlReadArrayLength(); break;
1411 case SL_STDSTR: length = SlReadArrayLength(); break;
1412 default: NOT_REACHED();
1413 }
1414
1415 list->clear();
1416 if constexpr (std::is_same_v<SlStorageT, std::vector<Tvar, Tallocator>>) {
1417 list->reserve(length);
1418 }
1419
1420 /* Load each value and push to the end of the storage. */
1421 for (size_t i = 0; i < length; i++) {
1422 Tvar &data = list->emplace_back();
1423 SlSaveLoadMember(cmd, &data, conv);
1424 }
1425 break;
1426 }
1427
1428 case SLA_PTRS:
1429 for (auto &item : *list) {
1430 SlSaveLoadMember(cmd, &item, conv);
1431 }
1432 break;
1433
1434 case SLA_NULL:
1435 list->clear();
1436 break;
1437
1438 default: NOT_REACHED();
1439 }
1440 }
1441};
1442
1448static inline size_t SlCalcRefListLen(const void *list, VarType conv)
1449{
1451}
1452
1458static void SlRefList(void *list, VarType conv)
1459{
1460 /* Automatically calculate the length? */
1461 if (_sl.need_length != NL_NONE) {
1462 SlSetLength(SlCalcRefListLen(list, conv));
1463 /* Determine length only? */
1464 if (_sl.need_length == NL_CALCLENGTH) return;
1465 }
1466
1468}
1469
1475static size_t SlCalcRefVectorLen(const void *vector, VarType conv)
1476{
1478}
1479
1485static void SlRefVector(void *vector, VarType conv)
1486{
1487 /* Automatically calculate the length? */
1488 if (_sl.need_length != NL_NONE) {
1489 SlSetLength(SlCalcRefVectorLen(vector, conv));
1490 /* Determine length only? */
1491 if (_sl.need_length == NL_CALCLENGTH) return;
1492 }
1493
1495}
1496
1502static inline size_t SlCalcDequeLen(const void *deque, VarType conv)
1503{
1504 switch (GetVarMemType(conv)) {
1505 case SLE_VAR_BL: return SlStorageHelper<std::deque, bool>::SlCalcLen(deque, conv);
1506 case SLE_VAR_I8: return SlStorageHelper<std::deque, int8_t>::SlCalcLen(deque, conv);
1507 case SLE_VAR_U8: return SlStorageHelper<std::deque, uint8_t>::SlCalcLen(deque, conv);
1508 case SLE_VAR_I16: return SlStorageHelper<std::deque, int16_t>::SlCalcLen(deque, conv);
1509 case SLE_VAR_U16: return SlStorageHelper<std::deque, uint16_t>::SlCalcLen(deque, conv);
1510 case SLE_VAR_I32: return SlStorageHelper<std::deque, int32_t>::SlCalcLen(deque, conv);
1511 case SLE_VAR_U32: return SlStorageHelper<std::deque, uint32_t>::SlCalcLen(deque, conv);
1512 case SLE_VAR_I64: return SlStorageHelper<std::deque, int64_t>::SlCalcLen(deque, conv);
1513 case SLE_VAR_U64: return SlStorageHelper<std::deque, uint64_t>::SlCalcLen(deque, conv);
1514
1515 case SLE_VAR_STR:
1516 /* Strings are a length-prefixed field type in the savegame table format,
1517 * these may not be directly stored in another length-prefixed container type. */
1518 NOT_REACHED();
1519
1520 default: NOT_REACHED();
1521 }
1522}
1523
1529static void SlDeque(void *deque, VarType conv)
1530{
1531 switch (GetVarMemType(conv)) {
1532 case SLE_VAR_BL: SlStorageHelper<std::deque, bool>::SlSaveLoad(deque, conv); break;
1533 case SLE_VAR_I8: SlStorageHelper<std::deque, int8_t>::SlSaveLoad(deque, conv); break;
1534 case SLE_VAR_U8: SlStorageHelper<std::deque, uint8_t>::SlSaveLoad(deque, conv); break;
1535 case SLE_VAR_I16: SlStorageHelper<std::deque, int16_t>::SlSaveLoad(deque, conv); break;
1536 case SLE_VAR_U16: SlStorageHelper<std::deque, uint16_t>::SlSaveLoad(deque, conv); break;
1537 case SLE_VAR_I32: SlStorageHelper<std::deque, int32_t>::SlSaveLoad(deque, conv); break;
1538 case SLE_VAR_U32: SlStorageHelper<std::deque, uint32_t>::SlSaveLoad(deque, conv); break;
1539 case SLE_VAR_I64: SlStorageHelper<std::deque, int64_t>::SlSaveLoad(deque, conv); break;
1540 case SLE_VAR_U64: SlStorageHelper<std::deque, uint64_t>::SlSaveLoad(deque, conv); break;
1541
1542 case SLE_VAR_STR:
1543 /* Strings are a length-prefixed field type in the savegame table format,
1544 * these may not be directly stored in another length-prefixed container type.
1545 * This is permitted for load-related actions, because invalid fields of this type are present
1546 * from SLV_COMPANY_ALLOW_LIST up to SLV_COMPANY_ALLOW_LIST_V2. */
1547 assert(_sl.action != SLA_SAVE);
1549 break;
1550
1551 default: NOT_REACHED();
1552 }
1553}
1554
1560static inline size_t SlCalcVectorLen(const void *vector, VarType conv)
1561{
1562 switch (GetVarMemType(conv)) {
1563 case SLE_VAR_BL: NOT_REACHED(); // Not supported
1564 case SLE_VAR_I8: return SlStorageHelper<std::vector, int8_t>::SlCalcLen(vector, conv);
1565 case SLE_VAR_U8: return SlStorageHelper<std::vector, uint8_t>::SlCalcLen(vector, conv);
1566 case SLE_VAR_I16: return SlStorageHelper<std::vector, int16_t>::SlCalcLen(vector, conv);
1567 case SLE_VAR_U16: return SlStorageHelper<std::vector, uint16_t>::SlCalcLen(vector, conv);
1568 case SLE_VAR_I32: return SlStorageHelper<std::vector, int32_t>::SlCalcLen(vector, conv);
1569 case SLE_VAR_U32: return SlStorageHelper<std::vector, uint32_t>::SlCalcLen(vector, conv);
1570 case SLE_VAR_I64: return SlStorageHelper<std::vector, int64_t>::SlCalcLen(vector, conv);
1571 case SLE_VAR_U64: return SlStorageHelper<std::vector, uint64_t>::SlCalcLen(vector, conv);
1572
1573 case SLE_VAR_STR:
1574 /* Strings are a length-prefixed field type in the savegame table format,
1575 * these may not be directly stored in another length-prefixed container type. */
1576 NOT_REACHED();
1577
1578 default: NOT_REACHED();
1579 }
1580}
1581
1587static void SlVector(void *vector, VarType conv)
1588{
1589 switch (GetVarMemType(conv)) {
1590 case SLE_VAR_BL: NOT_REACHED(); // Not supported
1591 case SLE_VAR_I8: SlStorageHelper<std::vector, int8_t>::SlSaveLoad(vector, conv); break;
1592 case SLE_VAR_U8: SlStorageHelper<std::vector, uint8_t>::SlSaveLoad(vector, conv); break;
1593 case SLE_VAR_I16: SlStorageHelper<std::vector, int16_t>::SlSaveLoad(vector, conv); break;
1594 case SLE_VAR_U16: SlStorageHelper<std::vector, uint16_t>::SlSaveLoad(vector, conv); break;
1595 case SLE_VAR_I32: SlStorageHelper<std::vector, int32_t>::SlSaveLoad(vector, conv); break;
1596 case SLE_VAR_U32: SlStorageHelper<std::vector, uint32_t>::SlSaveLoad(vector, conv); break;
1597 case SLE_VAR_I64: SlStorageHelper<std::vector, int64_t>::SlSaveLoad(vector, conv); break;
1598 case SLE_VAR_U64: SlStorageHelper<std::vector, uint64_t>::SlSaveLoad(vector, conv); break;
1599
1600 case SLE_VAR_STR:
1601 /* Strings are a length-prefixed field type in the savegame table format,
1602 * these may not be directly stored in another length-prefixed container type.
1603 * This is permitted for load-related actions, because invalid fields of this type are present
1604 * from SLV_COMPANY_ALLOW_LIST up to SLV_COMPANY_ALLOW_LIST_V2. */
1605 assert(_sl.action != SLA_SAVE);
1607 break;
1608
1609 default: NOT_REACHED();
1610 }
1611}
1612
1614static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld)
1615{
1616 return (_sl_version >= sld.version_from && _sl_version < sld.version_to);
1617}
1618
1624static size_t SlCalcTableHeader(const SaveLoadTable &slt)
1625{
1626 size_t length = 0;
1627
1628 for (auto &sld : slt) {
1629 if (!SlIsObjectValidInSavegame(sld)) continue;
1630
1631 length += SlCalcConvFileLen(SLE_UINT8);
1632 length += SlCalcStdStringLen(&sld.name);
1633 }
1634
1635 length += SlCalcConvFileLen(SLE_UINT8); // End-of-list entry.
1636
1637 for (auto &sld : slt) {
1638 if (!SlIsObjectValidInSavegame(sld)) continue;
1639 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
1640 length += SlCalcTableHeader(sld.handler->GetDescription());
1641 }
1642 }
1643
1644 return length;
1645}
1646
1653size_t SlCalcObjLength(const void *object, const SaveLoadTable &slt)
1654{
1655 size_t length = 0;
1656
1657 /* Need to determine the length and write a length tag. */
1658 for (auto &sld : slt) {
1659 length += SlCalcObjMemberLength(object, sld);
1660 }
1661 return length;
1662}
1663
1664size_t SlCalcObjMemberLength(const void *object, const SaveLoad &sld)
1665{
1666 assert(_sl.action == SLA_SAVE);
1667
1668 if (!SlIsObjectValidInSavegame(sld)) return 0;
1669
1670 switch (sld.cmd) {
1671 case SL_VAR: return SlCalcConvFileLen(sld.conv);
1672 case SL_REF: return SlCalcRefLen();
1673 case SL_ARR: return SlCalcArrayLen(sld.length, sld.conv);
1674 case SL_REFLIST: return SlCalcRefListLen(GetVariableAddress(object, sld), sld.conv);
1675 case SL_REFVECTOR: return SlCalcRefVectorLen(GetVariableAddress(object, sld), sld.conv);
1676 case SL_DEQUE: return SlCalcDequeLen(GetVariableAddress(object, sld), sld.conv);
1677 case SL_VECTOR: return SlCalcVectorLen(GetVariableAddress(object, sld), sld.conv);
1678 case SL_STDSTR: return SlCalcStdStringLen(GetVariableAddress(object, sld));
1679 case SL_SAVEBYTE: return 1; // a byte is logically of size 1
1680 case SL_NULL: return SlCalcConvFileLen(sld.conv) * sld.length;
1681
1682 case SL_STRUCT:
1683 case SL_STRUCTLIST: {
1684 NeedLength old_need_length = _sl.need_length;
1685 size_t old_obj_len = _sl.obj_len;
1686
1687 _sl.need_length = NL_CALCLENGTH;
1688 _sl.obj_len = 0;
1689
1690 /* Pretend that we are saving to collect the object size. Other
1691 * means are difficult, as we don't know the length of the list we
1692 * are about to store. */
1693 sld.handler->Save(const_cast<void *>(object));
1694 size_t length = _sl.obj_len;
1695
1696 _sl.obj_len = old_obj_len;
1697 _sl.need_length = old_need_length;
1698
1699 if (sld.cmd == SL_STRUCT) {
1700 length += SlGetArrayLength(1);
1701 }
1702
1703 return length;
1704 }
1705
1706 default: NOT_REACHED();
1707 }
1708 return 0;
1709}
1710
1711static bool SlObjectMember(void *object, const SaveLoad &sld)
1712{
1713 if (!SlIsObjectValidInSavegame(sld)) return false;
1714
1715 VarType conv = GB(sld.conv, 0, 8);
1716 switch (sld.cmd) {
1717 case SL_VAR:
1718 case SL_REF:
1719 case SL_ARR:
1720 case SL_REFLIST:
1721 case SL_REFVECTOR:
1722 case SL_DEQUE:
1723 case SL_VECTOR:
1724 case SL_STDSTR: {
1725 void *ptr = GetVariableAddress(object, sld);
1726
1727 switch (sld.cmd) {
1728 case SL_VAR: SlSaveLoadConv(ptr, conv); break;
1729 case SL_REF: SlSaveLoadRef(ptr, conv); break;
1730 case SL_ARR: SlArray(ptr, sld.length, conv); break;
1731 case SL_REFLIST: SlRefList(ptr, conv); break;
1732 case SL_REFVECTOR: SlRefVector(ptr, conv); break;
1733 case SL_DEQUE: SlDeque(ptr, conv); break;
1734 case SL_VECTOR: SlVector(ptr, conv); break;
1735 case SL_STDSTR: SlStdString(ptr, sld.conv); break;
1736 default: NOT_REACHED();
1737 }
1738 break;
1739 }
1740
1741 /* SL_SAVEBYTE writes a value to the savegame to identify the type of an object.
1742 * When loading, the value is read explicitly with SlReadByte() to determine which
1743 * object description to use. */
1744 case SL_SAVEBYTE: {
1745 void *ptr = GetVariableAddress(object, sld);
1746
1747 switch (_sl.action) {
1748 case SLA_SAVE: SlWriteByte(*(uint8_t *)ptr); break;
1749 case SLA_LOAD_CHECK:
1750 case SLA_LOAD:
1751 case SLA_PTRS:
1752 case SLA_NULL: break;
1753 default: NOT_REACHED();
1754 }
1755 break;
1756 }
1757
1758 case SL_NULL: {
1759 assert(GetVarMemType(sld.conv) == SLE_VAR_NULL);
1760
1761 switch (_sl.action) {
1762 case SLA_LOAD_CHECK:
1763 case SLA_LOAD: SlSkipBytes(SlCalcConvFileLen(sld.conv) * sld.length); break;
1764 case SLA_SAVE: for (int i = 0; i < SlCalcConvFileLen(sld.conv) * sld.length; i++) SlWriteByte(0); break;
1765 case SLA_PTRS:
1766 case SLA_NULL: break;
1767 default: NOT_REACHED();
1768 }
1769 break;
1770 }
1771
1772 case SL_STRUCT:
1773 case SL_STRUCTLIST:
1774 switch (_sl.action) {
1775 case SLA_SAVE: {
1776 if (sld.cmd == SL_STRUCT) {
1777 /* Store in the savegame if this struct was written or not. */
1778 SlSetStructListLength(SlCalcObjMemberLength(object, sld) > SlGetArrayLength(1) ? 1 : 0);
1779 }
1780 sld.handler->Save(object);
1781 break;
1782 }
1783
1784 case SLA_LOAD_CHECK: {
1787 }
1788 sld.handler->LoadCheck(object);
1789 break;
1790 }
1791
1792 case SLA_LOAD: {
1795 }
1796 sld.handler->Load(object);
1797 break;
1798 }
1799
1800 case SLA_PTRS:
1801 sld.handler->FixPointers(object);
1802 break;
1803
1804 case SLA_NULL: break;
1805 default: NOT_REACHED();
1806 }
1807 break;
1808
1809 default: NOT_REACHED();
1810 }
1811 return true;
1812}
1813
1818void SlSetStructListLength(size_t length)
1819{
1820 /* Automatically calculate the length? */
1821 if (_sl.need_length != NL_NONE) {
1822 SlSetLength(SlGetArrayLength(length));
1823 if (_sl.need_length == NL_CALCLENGTH) return;
1824 }
1825
1826 SlWriteArrayLength(length);
1827}
1828
1834size_t SlGetStructListLength(size_t limit)
1835{
1836 size_t length = SlReadArrayLength();
1837 if (length > limit) SlErrorCorrupt("List exceeds storage size");
1838
1839 return length;
1840}
1841
1847void SlObject(void *object, const SaveLoadTable &slt)
1848{
1849 /* Automatically calculate the length? */
1850 if (_sl.need_length != NL_NONE) {
1851 SlSetLength(SlCalcObjLength(object, slt));
1852 if (_sl.need_length == NL_CALCLENGTH) return;
1853 }
1854
1855 for (auto &sld : slt) {
1856 SlObjectMember(object, sld);
1857 }
1858}
1859
1865 void Save(void *) const override
1866 {
1867 NOT_REACHED();
1868 }
1869
1870 void Load(void *object) const override
1871 {
1872 size_t length = SlGetStructListLength(UINT32_MAX);
1873 for (; length > 0; length--) {
1874 SlObject(object, this->GetLoadDescription());
1875 }
1876 }
1877
1878 void LoadCheck(void *object) const override
1879 {
1880 this->Load(object);
1881 }
1882
1884 {
1885 return {};
1886 }
1887
1889 {
1890 NOT_REACHED();
1891 }
1892};
1893
1900std::vector<SaveLoad> SlTableHeader(const SaveLoadTable &slt)
1901{
1902 /* You can only use SlTableHeader if you are a CH_TABLE. */
1903 assert(_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
1904
1905 switch (_sl.action) {
1906 case SLA_LOAD_CHECK:
1907 case SLA_LOAD: {
1908 std::vector<SaveLoad> saveloads;
1909
1910 /* Build a key lookup mapping based on the available fields. */
1911 std::map<std::string, const SaveLoad *> key_lookup;
1912 for (auto &sld : slt) {
1913 if (!SlIsObjectValidInSavegame(sld)) continue;
1914
1915 /* Check that there is only one active SaveLoad for a given name. */
1916 assert(key_lookup.find(sld.name) == key_lookup.end());
1917 key_lookup[sld.name] = &sld;
1918 }
1919
1920 while (true) {
1921 uint8_t type = 0;
1922 SlSaveLoadConv(&type, SLE_UINT8);
1923 if (type == SLE_FILE_END) break;
1924
1925 std::string key;
1926 SlStdString(&key, SLE_STR);
1927
1928 auto sld_it = key_lookup.find(key);
1929 if (sld_it == key_lookup.end()) {
1930 /* SLA_LOADCHECK triggers this debug statement a lot and is perfectly normal. */
1931 Debug(sl, _sl.action == SLA_LOAD ? 2 : 6, "Field '{}' of type 0x{:02x} not found, skipping", key, type);
1932
1933 std::shared_ptr<SaveLoadHandler> handler = nullptr;
1934 SaveLoadType saveload_type;
1935 switch (type & SLE_FILE_TYPE_MASK) {
1936 case SLE_FILE_STRING:
1937 /* Strings are always marked with SLE_FILE_HAS_LENGTH_FIELD, as they are a list of chars. */
1938 saveload_type = SL_STDSTR;
1939 break;
1940
1941 case SLE_FILE_STRUCT:
1942 /* Structs are always marked with SLE_FILE_HAS_LENGTH_FIELD as SL_STRUCT is seen as a list of 0/1 in length. */
1943 saveload_type = SL_STRUCTLIST;
1944 handler = std::make_shared<SlSkipHandler>();
1945 break;
1946
1947 default:
1948 saveload_type = (type & SLE_FILE_HAS_LENGTH_FIELD) ? SL_ARR : SL_VAR;
1949 break;
1950 }
1951
1952 /* We don't know this field, so read to nothing. */
1953 saveloads.emplace_back(std::move(key), saveload_type, ((VarType)type & SLE_FILE_TYPE_MASK) | SLE_VAR_NULL, 1, SL_MIN_VERSION, SL_MAX_VERSION, nullptr, 0, std::move(handler));
1954 continue;
1955 }
1956
1957 /* Validate the type of the field. If it is changed, the
1958 * savegame should have been bumped so we know how to do the
1959 * conversion. If this error triggers, that clearly didn't
1960 * happen and this is a friendly poke to the developer to bump
1961 * the savegame version and add conversion code. */
1962 uint8_t correct_type = GetSavegameFileType(*sld_it->second);
1963 if (correct_type != type) {
1964 Debug(sl, 1, "Field type for '{}' was expected to be 0x{:02x} but 0x{:02x} was found", key, correct_type, type);
1965 SlErrorCorrupt("Field type is different than expected");
1966 }
1967 saveloads.emplace_back(*sld_it->second);
1968 }
1969
1970 for (auto &sld : saveloads) {
1971 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
1972 sld.handler->load_description = SlTableHeader(sld.handler->GetDescription());
1973 }
1974 }
1975
1976 return saveloads;
1977 }
1978
1979 case SLA_SAVE: {
1980 /* Automatically calculate the length? */
1981 if (_sl.need_length != NL_NONE) {
1983 if (_sl.need_length == NL_CALCLENGTH) break;
1984 }
1985
1986 for (auto &sld : slt) {
1987 if (!SlIsObjectValidInSavegame(sld)) continue;
1988 /* Make sure we are not storing empty keys. */
1989 assert(!sld.name.empty());
1990
1991 uint8_t type = GetSavegameFileType(sld);
1992 assert(type != SLE_FILE_END);
1993
1994 SlSaveLoadConv(&type, SLE_UINT8);
1995 SlStdString(const_cast<std::string *>(&sld.name), SLE_STR);
1996 }
1997
1998 /* Add an end-of-header marker. */
1999 uint8_t type = SLE_FILE_END;
2000 SlSaveLoadConv(&type, SLE_UINT8);
2001
2002 /* After the table, write down any sub-tables we might have. */
2003 for (auto &sld : slt) {
2004 if (!SlIsObjectValidInSavegame(sld)) continue;
2005 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
2006 /* SlCalcTableHeader already looks in sub-lists, so avoid the length being added twice. */
2007 NeedLength old_need_length = _sl.need_length;
2008 _sl.need_length = NL_NONE;
2009
2010 SlTableHeader(sld.handler->GetDescription());
2011
2012 _sl.need_length = old_need_length;
2013 }
2014 }
2015
2016 break;
2017 }
2018
2019 default: NOT_REACHED();
2020 }
2021
2022 return std::vector<SaveLoad>();
2023}
2024
2038std::vector<SaveLoad> SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct)
2039{
2040 assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK);
2041 /* CH_TABLE / CH_SPARSE_TABLE always have a header. */
2042 if (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) return SlTableHeader(slt);
2043
2044 std::vector<SaveLoad> saveloads;
2045
2046 /* Build a key lookup mapping based on the available fields. */
2047 std::map<std::string, std::vector<const SaveLoad *>> key_lookup;
2048 for (auto &sld : slt) {
2049 /* All entries should have a name; otherwise the entry should just be removed. */
2050 assert(!sld.name.empty());
2051
2052 key_lookup[sld.name].push_back(&sld);
2053 }
2054
2055 for (auto &slc : slct) {
2056 if (slc.name.empty()) {
2057 /* In old savegames there can be data we no longer care for. We
2058 * skip this by simply reading the amount of bytes indicated and
2059 * send those to /dev/null. */
2060 saveloads.emplace_back("", SL_NULL, GetVarFileType(slc.null_type) | SLE_VAR_NULL, slc.null_length, slc.version_from, slc.version_to, nullptr, 0, nullptr);
2061 } else {
2062 auto sld_it = key_lookup.find(slc.name);
2063 /* If this branch triggers, it means that an entry in the
2064 * SaveLoadCompat list is not mentioned in the SaveLoad list. Did
2065 * you rename a field in one and not in the other? */
2066 if (sld_it == key_lookup.end()) {
2067 /* This isn't an assert, as that leaves no information what
2068 * field was to blame. This way at least we have breadcrumbs. */
2069 Debug(sl, 0, "internal error: saveload compatibility field '{}' not found", slc.name);
2070 SlErrorCorrupt("Internal error with savegame compatibility");
2071 }
2072 for (auto &sld : sld_it->second) {
2073 saveloads.push_back(*sld);
2074 }
2075 }
2076 }
2077
2078 for (auto &sld : saveloads) {
2079 if (!SlIsObjectValidInSavegame(sld)) continue;
2080 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
2081 sld.handler->load_description = SlCompatTableHeader(sld.handler->GetDescription(), sld.handler->GetCompatDescription());
2082 }
2083 }
2084
2085 return saveloads;
2086}
2087
2093{
2094 SlObject(nullptr, slt);
2095}
2096
2102void SlAutolength(AutolengthProc *proc, int arg)
2103{
2104 assert(_sl.action == SLA_SAVE);
2105
2106 /* Tell it to calculate the length */
2107 _sl.need_length = NL_CALCLENGTH;
2108 _sl.obj_len = 0;
2109 proc(arg);
2110
2111 /* Setup length */
2112 _sl.need_length = NL_WANTLENGTH;
2113 SlSetLength(_sl.obj_len);
2114
2115 size_t start_pos = _sl.dumper->GetSize();
2116 size_t expected_offs = start_pos + _sl.obj_len;
2117
2118 /* And write the stuff */
2119 proc(arg);
2120
2121 if (expected_offs != _sl.dumper->GetSize()) {
2122 SlErrorCorruptFmt("Invalid chunk size when writing autolength block, expected {}, got {}", _sl.obj_len, _sl.dumper->GetSize() - start_pos);
2123 }
2124}
2125
2126void ChunkHandler::LoadCheck(size_t len) const
2127{
2128 switch (_sl.block_mode) {
2129 case CH_TABLE:
2130 case CH_SPARSE_TABLE:
2131 SlTableHeader({});
2132 [[fallthrough]];
2133 case CH_ARRAY:
2134 case CH_SPARSE_ARRAY:
2135 SlSkipArray();
2136 break;
2137 case CH_RIFF:
2138 SlSkipBytes(len);
2139 break;
2140 default:
2141 NOT_REACHED();
2142 }
2143}
2144
2149static void SlLoadChunk(const ChunkHandler &ch)
2150{
2151 uint8_t m = SlReadByte();
2152
2153 _sl.block_mode = m & CH_TYPE_MASK;
2154 _sl.obj_len = 0;
2155 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2156
2157 /* The header should always be at the start. Read the length; the
2158 * Load() should as first action process the header. */
2159 if (_sl.expect_table_header) {
2160 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2161 }
2162
2163 switch (_sl.block_mode) {
2164 case CH_TABLE:
2165 case CH_ARRAY:
2166 _sl.array_index = 0;
2167 ch.Load();
2168 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2169 break;
2170 case CH_SPARSE_TABLE:
2171 case CH_SPARSE_ARRAY:
2172 ch.Load();
2173 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2174 break;
2175 case CH_RIFF: {
2176 /* Read length */
2177 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2178 len += SlReadUint16();
2179 _sl.obj_len = len;
2180 size_t start_pos = _sl.reader->GetSize();
2181 size_t endoffs = start_pos + len;
2182 ch.Load();
2183
2184 if (_sl.reader->GetSize() != endoffs) {
2185 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2186 }
2187 break;
2188 }
2189 default:
2190 SlErrorCorrupt("Invalid chunk type");
2191 break;
2192 }
2193
2194 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2195}
2196
2202static void SlLoadCheckChunk(const ChunkHandler &ch)
2203{
2204 uint8_t m = SlReadByte();
2205
2206 _sl.block_mode = m & CH_TYPE_MASK;
2207 _sl.obj_len = 0;
2208 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2209
2210 /* The header should always be at the start. Read the length; the
2211 * LoadCheck() should as first action process the header. */
2212 if (_sl.expect_table_header) {
2213 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2214 }
2215
2216 switch (_sl.block_mode) {
2217 case CH_TABLE:
2218 case CH_ARRAY:
2219 _sl.array_index = 0;
2220 ch.LoadCheck();
2221 break;
2222 case CH_SPARSE_TABLE:
2223 case CH_SPARSE_ARRAY:
2224 ch.LoadCheck();
2225 break;
2226 case CH_RIFF: {
2227 /* Read length */
2228 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2229 len += SlReadUint16();
2230 _sl.obj_len = len;
2231 size_t start_pos = _sl.reader->GetSize();
2232 size_t endoffs = start_pos + len;
2233 ch.LoadCheck(len);
2234
2235 if (_sl.reader->GetSize() != endoffs) {
2236 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2237 }
2238 break;
2239 }
2240 default:
2241 SlErrorCorrupt("Invalid chunk type");
2242 break;
2243 }
2244
2245 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2246}
2247
2253static void SlSaveChunk(const ChunkHandler &ch)
2254{
2255 if (ch.type == CH_READONLY) return;
2256
2257 SlWriteUint32(ch.id);
2258 Debug(sl, 2, "Saving chunk {}", ch.GetName());
2259
2260 _sl.block_mode = ch.type;
2261 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2262
2263 _sl.need_length = (_sl.expect_table_header || _sl.block_mode == CH_RIFF) ? NL_WANTLENGTH : NL_NONE;
2264
2265 switch (_sl.block_mode) {
2266 case CH_RIFF:
2267 ch.Save();
2268 break;
2269 case CH_TABLE:
2270 case CH_ARRAY:
2271 _sl.last_array_index = 0;
2272 SlWriteByte(_sl.block_mode);
2273 ch.Save();
2274 SlWriteArrayLength(0); // Terminate arrays
2275 break;
2276 case CH_SPARSE_TABLE:
2277 case CH_SPARSE_ARRAY:
2278 SlWriteByte(_sl.block_mode);
2279 ch.Save();
2280 SlWriteArrayLength(0); // Terminate arrays
2281 break;
2282 default: NOT_REACHED();
2283 }
2284
2285 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2286}
2287
2289static void SlSaveChunks()
2290{
2291 for (auto &ch : ChunkHandlers()) {
2292 SlSaveChunk(ch);
2293 }
2294
2295 /* Terminator */
2296 SlWriteUint32(0);
2297}
2298
2305static const ChunkHandler *SlFindChunkHandler(uint32_t id)
2306{
2307 for (const ChunkHandler &ch : ChunkHandlers()) if (ch.id == id) return &ch;
2308 return nullptr;
2309}
2310
2312static void SlLoadChunks()
2313{
2314 uint32_t id;
2315 const ChunkHandler *ch;
2316
2317 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2318 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2319
2320 ch = SlFindChunkHandler(id);
2321 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2322 SlLoadChunk(*ch);
2323 }
2324}
2325
2328{
2329 uint32_t id;
2330 const ChunkHandler *ch;
2331
2332 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2333 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2334
2335 ch = SlFindChunkHandler(id);
2336 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2337 SlLoadCheckChunk(*ch);
2338 }
2339}
2340
2342static void SlFixPointers()
2343{
2344 _sl.action = SLA_PTRS;
2345
2346 for (const ChunkHandler &ch : ChunkHandlers()) {
2347 Debug(sl, 3, "Fixing pointers for {}", ch.GetName());
2348 ch.FixPointers();
2349 }
2350
2351 assert(_sl.action == SLA_PTRS);
2352}
2353
2354
2357 std::optional<FileHandle> file;
2358 long begin;
2359
2364 FileReader(FileHandle &&file) : LoadFilter(nullptr), file(std::move(file)), begin(ftell(*this->file))
2365 {
2366 }
2367
2369 ~FileReader() override
2370 {
2371 if (this->file.has_value()) {
2372 _game_session_stats.savegame_size = ftell(*this->file) - this->begin;
2373 }
2374 }
2375
2376 size_t Read(uint8_t *buf, size_t size) override
2377 {
2378 /* We're in the process of shutting down, i.e. in "failure" mode. */
2379 if (!this->file.has_value()) return 0;
2380
2381 return fread(buf, 1, size, *this->file);
2382 }
2383
2384 void Reset() override
2385 {
2386 clearerr(*this->file);
2387 if (fseek(*this->file, this->begin, SEEK_SET)) {
2388 Debug(sl, 1, "Could not reset the file reading");
2389 }
2390 }
2391};
2392
2395 std::optional<FileHandle> file;
2396
2401 FileWriter(FileHandle &&file) : SaveFilter(nullptr), file(std::move(file))
2402 {
2403 }
2404
2406 ~FileWriter() override
2407 {
2408 this->Finish();
2409 }
2410
2411 void Write(uint8_t *buf, size_t size) override
2412 {
2413 /* We're in the process of shutting down, i.e. in "failure" mode. */
2414 if (!this->file.has_value()) return;
2415
2416 if (fwrite(buf, 1, size, *this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
2417 }
2418
2419 void Finish() override
2420 {
2421 if (this->file.has_value()) {
2422 _game_session_stats.savegame_size = ftell(*this->file);
2423 this->file.reset();
2424 }
2425 }
2426};
2427
2428/*******************************************
2429 ********** START OF LZO CODE **************
2430 *******************************************/
2431
2432#ifdef WITH_LZO
2433
2435static const uint LZO_BUFFER_SIZE = 8192;
2436
2443 LZOLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2444 {
2445 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2446 }
2447
2448 size_t Read(uint8_t *buf, size_t ssize) override
2449 {
2450 assert(ssize >= LZO_BUFFER_SIZE);
2451
2452 /* Buffer size is from the LZO docs plus the chunk header size. */
2453 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2454 uint32_t tmp[2];
2455 uint32_t size;
2456 lzo_uint len = ssize;
2457
2458 /* Read header*/
2459 if (this->chain->Read((uint8_t*)tmp, sizeof(tmp)) != sizeof(tmp)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE, "File read failed");
2460
2461 /* Check if size is bad */
2462 ((uint32_t*)out)[0] = size = tmp[1];
2463
2464 if (_sl_version != SL_MIN_VERSION) {
2465 tmp[0] = TO_BE32(tmp[0]);
2466 size = TO_BE32(size);
2467 }
2468
2469 if (size >= sizeof(out)) SlErrorCorrupt("Inconsistent size");
2470
2471 /* Read block */
2472 if (this->chain->Read(out + sizeof(uint32_t), size) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2473
2474 /* Verify checksum */
2475 if (tmp[0] != lzo_adler32(0, out, size + sizeof(uint32_t))) SlErrorCorrupt("Bad checksum");
2476
2477 /* Decompress */
2478 int ret = lzo1x_decompress_safe(out + sizeof(uint32_t) * 1, size, buf, &len, nullptr);
2479 if (ret != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2480 return len;
2481 }
2482};
2483
2490 LZOSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2491 {
2492 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2493 }
2494
2495 void Write(uint8_t *buf, size_t size) override
2496 {
2497 const lzo_bytep in = buf;
2498 /* Buffer size is from the LZO docs plus the chunk header size. */
2499 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2500 uint8_t wrkmem[LZO1X_1_MEM_COMPRESS];
2501 lzo_uint outlen;
2502
2503 do {
2504 /* Compress up to LZO_BUFFER_SIZE bytes at once. */
2505 lzo_uint len = size > LZO_BUFFER_SIZE ? LZO_BUFFER_SIZE : (lzo_uint)size;
2506 lzo1x_1_compress(in, len, out + sizeof(uint32_t) * 2, &outlen, wrkmem);
2507 ((uint32_t*)out)[1] = TO_BE32((uint32_t)outlen);
2508 ((uint32_t*)out)[0] = TO_BE32(lzo_adler32(0, out + sizeof(uint32_t), outlen + sizeof(uint32_t)));
2509 this->chain->Write(out, outlen + sizeof(uint32_t) * 2);
2510
2511 /* Move to next data chunk. */
2512 size -= len;
2513 in += len;
2514 } while (size > 0);
2515 }
2516};
2517
2518#endif /* WITH_LZO */
2519
2520/*********************************************
2521 ******** START OF NOCOMP CODE (uncompressed)*
2522 *********************************************/
2523
2530 NoCompLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2531 {
2532 }
2533
2534 size_t Read(uint8_t *buf, size_t size) override
2535 {
2536 return this->chain->Read(buf, size);
2537 }
2538};
2539
2546 NoCompSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2547 {
2548 }
2549
2550 void Write(uint8_t *buf, size_t size) override
2551 {
2552 this->chain->Write(buf, size);
2553 }
2554};
2555
2556/********************************************
2557 ********** START OF ZLIB CODE **************
2558 ********************************************/
2559
2560#if defined(WITH_ZLIB)
2561
2564 z_stream z{};
2566
2571 ZlibLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2572 {
2573 if (inflateInit(&this->z) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2574 }
2575
2578 {
2579 inflateEnd(&this->z);
2580 }
2581
2582 size_t Read(uint8_t *buf, size_t size) override
2583 {
2584 this->z.next_out = buf;
2585 this->z.avail_out = (uint)size;
2586
2587 do {
2588 /* read more bytes from the file? */
2589 if (this->z.avail_in == 0) {
2590 this->z.next_in = this->fread_buf;
2591 this->z.avail_in = (uint)this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2592 }
2593
2594 /* inflate the data */
2595 int r = inflate(&this->z, 0);
2596 if (r == Z_STREAM_END) break;
2597
2598 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "inflate() failed");
2599 } while (this->z.avail_out != 0);
2600
2601 return size - this->z.avail_out;
2602 }
2603};
2604
2607 z_stream z{};
2609
2615 ZlibSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain))
2616 {
2617 if (deflateInit(&this->z, compression_level) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2618 }
2619
2622 {
2623 deflateEnd(&this->z);
2624 }
2625
2632 void WriteLoop(uint8_t *p, size_t len, int mode)
2633 {
2634 uint n;
2635 this->z.next_in = p;
2636 this->z.avail_in = (uInt)len;
2637 do {
2638 this->z.next_out = this->fwrite_buf;
2639 this->z.avail_out = sizeof(this->fwrite_buf);
2640
2648 int r = deflate(&this->z, mode);
2649
2650 /* bytes were emitted? */
2651 if ((n = sizeof(this->fwrite_buf) - this->z.avail_out) != 0) {
2652 this->chain->Write(this->fwrite_buf, n);
2653 }
2654 if (r == Z_STREAM_END) break;
2655
2656 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "zlib returned error code");
2657 } while (this->z.avail_in || !this->z.avail_out);
2658 }
2659
2660 void Write(uint8_t *buf, size_t size) override
2661 {
2662 this->WriteLoop(buf, size, 0);
2663 }
2664
2665 void Finish() override
2666 {
2667 this->WriteLoop(nullptr, 0, Z_FINISH);
2668 this->chain->Finish();
2669 }
2670};
2671
2672#endif /* WITH_ZLIB */
2673
2674/********************************************
2675 ********** START OF LZMA CODE **************
2676 ********************************************/
2677
2678#if defined(WITH_LIBLZMA)
2679
2686static const lzma_stream _lzma_init = LZMA_STREAM_INIT;
2687
2690 lzma_stream lzma;
2692
2697 LZMALoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain)), lzma(_lzma_init)
2698 {
2699 /* Allow saves up to 256 MB uncompressed */
2700 if (lzma_auto_decoder(&this->lzma, 1 << 28, 0) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2701 }
2702
2705 {
2706 lzma_end(&this->lzma);
2707 }
2708
2709 size_t Read(uint8_t *buf, size_t size) override
2710 {
2711 this->lzma.next_out = buf;
2712 this->lzma.avail_out = size;
2713
2714 do {
2715 /* read more bytes from the file? */
2716 if (this->lzma.avail_in == 0) {
2717 this->lzma.next_in = this->fread_buf;
2718 this->lzma.avail_in = this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2719 }
2720
2721 /* inflate the data */
2722 lzma_ret r = lzma_code(&this->lzma, LZMA_RUN);
2723 if (r == LZMA_STREAM_END) break;
2724 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2725 } while (this->lzma.avail_out != 0);
2726
2727 return size - this->lzma.avail_out;
2728 }
2729};
2730
2733 lzma_stream lzma;
2735
2741 LZMASaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain)), lzma(_lzma_init)
2742 {
2743 if (lzma_easy_encoder(&this->lzma, compression_level, LZMA_CHECK_CRC32) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2744 }
2745
2748 {
2749 lzma_end(&this->lzma);
2750 }
2751
2758 void WriteLoop(uint8_t *p, size_t len, lzma_action action)
2759 {
2760 size_t n;
2761 this->lzma.next_in = p;
2762 this->lzma.avail_in = len;
2763 do {
2764 this->lzma.next_out = this->fwrite_buf;
2765 this->lzma.avail_out = sizeof(this->fwrite_buf);
2766
2767 lzma_ret r = lzma_code(&this->lzma, action);
2768
2769 /* bytes were emitted? */
2770 if ((n = sizeof(this->fwrite_buf) - this->lzma.avail_out) != 0) {
2771 this->chain->Write(this->fwrite_buf, n);
2772 }
2773 if (r == LZMA_STREAM_END) break;
2774 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2775 } while (this->lzma.avail_in || !this->lzma.avail_out);
2776 }
2777
2778 void Write(uint8_t *buf, size_t size) override
2779 {
2780 this->WriteLoop(buf, size, LZMA_RUN);
2781 }
2782
2783 void Finish() override
2784 {
2785 this->WriteLoop(nullptr, 0, LZMA_FINISH);
2786 this->chain->Finish();
2787 }
2788};
2789
2790#endif /* WITH_LIBLZMA */
2791
2792/*******************************************
2793 ************* END OF CODE *****************
2794 *******************************************/
2795
2798 std::shared_ptr<LoadFilter> (*init_load)(std::shared_ptr<LoadFilter> chain);
2799 std::shared_ptr<SaveFilter> (*init_write)(std::shared_ptr<SaveFilter> chain, uint8_t compression);
2800
2801 std::string_view name;
2802 uint32_t tag;
2803
2807};
2808
2809static const uint32_t SAVEGAME_TAG_LZO = TO_BE32('OTTD');
2810static const uint32_t SAVEGAME_TAG_NONE = TO_BE32('OTTN');
2811static const uint32_t SAVEGAME_TAG_ZLIB = TO_BE32('OTTZ');
2812static const uint32_t SAVEGAME_TAG_LZMA = TO_BE32('OTTX');
2813
2816#if defined(WITH_LZO)
2817 /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
2818 {CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2819#else
2820 {nullptr, nullptr, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2821#endif
2822 /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
2823 {CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, "none", SAVEGAME_TAG_NONE, 0, 0, 0},
2824#if defined(WITH_ZLIB)
2825 /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is
2826 * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level
2827 * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */
2828 {CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, "zlib", SAVEGAME_TAG_ZLIB, 0, 6, 9},
2829#else
2830 {nullptr, nullptr, "zlib", SAVEGAME_TAG_ZLIB, 0, 0, 0},
2831#endif
2832#if defined(WITH_LIBLZMA)
2833 /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
2834 * Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
2835 * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50%
2836 * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much.
2837 * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */
2838 {CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, "lzma", SAVEGAME_TAG_LZMA, 0, 2, 9},
2839#else
2840 {nullptr, nullptr, "lzma", SAVEGAME_TAG_LZMA, 0, 0, 0},
2841#endif
2842};
2843
2850static std::pair<const SaveLoadFormat &, uint8_t> GetSavegameFormat(std::string_view full_name)
2851{
2852 /* Find default savegame format, the highest one with which files can be written. */
2853 auto it = std::find_if(std::rbegin(_saveload_formats), std::rend(_saveload_formats), [](const auto &slf) { return slf.init_write != nullptr; });
2854 if (it == std::rend(_saveload_formats)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "no writeable savegame formats");
2855
2856 const SaveLoadFormat &def = *it;
2857
2858 if (!full_name.empty()) {
2859 /* Get the ":..." of the compression level out of the way */
2860 size_t separator = full_name.find(':');
2861 bool has_comp_level = separator != std::string::npos;
2862 std::string_view name = has_comp_level ? full_name.substr(0, separator) : full_name;
2863
2864 for (const auto &slf : _saveload_formats) {
2865 if (slf.init_write != nullptr && name == slf.name) {
2866 if (has_comp_level) {
2867 auto complevel = full_name.substr(separator + 1);
2868
2869 /* Get the level and determine whether all went fine. */
2870 auto level = ParseInteger<uint8_t>(complevel);
2871 if (!level.has_value() || *level != Clamp(*level, slf.min_compression, slf.max_compression)) {
2873 GetEncodedString(STR_CONFIG_ERROR),
2874 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, complevel),
2875 WL_CRITICAL);
2876 } else {
2877 return {slf, *level};
2878 }
2879 }
2880 return {slf, slf.default_compression};
2881 }
2882 }
2883
2885 GetEncodedString(STR_CONFIG_ERROR),
2886 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, name, def.name),
2887 WL_CRITICAL);
2888 }
2889 return {def, def.default_compression};
2890}
2891
2892/* actual loader/saver function */
2893void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
2894extern bool AfterLoadGame();
2895extern bool LoadOldSaveGame(std::string_view file);
2896
2902static void ResetSettings()
2903{
2904 for (auto &desc : GetSaveLoadSettingTable()) {
2905 const SettingDesc *sd = GetSettingDesc(desc);
2906 if (sd->flags.Test(SettingFlag::NotInSave)) continue;
2908
2910 }
2911}
2912
2913extern void ClearOldOrders();
2914
2919{
2921 ResetTempEngineData();
2922 ClearRailTypeLabelList();
2923 ClearRoadTypeLabelList();
2924 ResetOldWaypoints();
2925 ResetSettings();
2926}
2927
2931static inline void ClearSaveLoadState()
2932{
2933 _sl.dumper = nullptr;
2934 _sl.sf = nullptr;
2935 _sl.reader = nullptr;
2936 _sl.lf = nullptr;
2937}
2938
2940static void SaveFileStart()
2941{
2942 SetMouseCursorBusy(true);
2943
2945 _sl.saveinprogress = true;
2946}
2947
2949static void SaveFileDone()
2950{
2951 SetMouseCursorBusy(false);
2952
2954 _sl.saveinprogress = false;
2955
2956#ifdef __EMSCRIPTEN__
2957 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
2958#endif
2959}
2960
2963{
2964 _sl.error_str = str;
2965}
2966
2969{
2970 return GetEncodedString(_sl.action == SLA_SAVE ? STR_ERROR_GAME_SAVE_FAILED : STR_ERROR_GAME_LOAD_FAILED);
2971}
2972
2975{
2976 return GetEncodedString(_sl.error_str, _sl.extra_msg);
2977}
2978
2985
2991{
2992 try {
2993 auto [fmt, compression] = GetSavegameFormat(_savegame_format);
2994
2995 /* We have written our stuff to memory, now write it to file! */
2996 uint32_t hdr[2] = { fmt.tag, TO_BE32(SAVEGAME_VERSION << 16) };
2997 _sl.sf->Write((uint8_t*)hdr, sizeof(hdr));
2998
2999 _sl.sf = fmt.init_write(_sl.sf, compression);
3000 _sl.dumper->Flush(_sl.sf);
3001
3003
3004 if (threaded) SetAsyncSaveFinish(SaveFileDone);
3005
3006 return SL_OK;
3007 } catch (...) {
3009
3011
3012 /* We don't want to shout when saving is just
3013 * cancelled due to a client disconnecting. */
3014 if (_sl.error_str != STR_NETWORK_ERROR_LOSTCONNECTION) {
3015 /* Skip the "colour" character */
3016 Debug(sl, 0, "{}", GetSaveLoadErrorType().GetDecodedString().substr(3) + GetSaveLoadErrorMessage().GetDecodedString());
3017 asfp = SaveFileError;
3018 }
3019
3020 if (threaded) {
3021 SetAsyncSaveFinish(asfp);
3022 } else {
3023 asfp();
3024 }
3025 return SL_ERROR;
3026 }
3027}
3028
3029void WaitTillSaved()
3030{
3031 if (!_save_thread.joinable()) return;
3032
3033 _save_thread.join();
3034
3035 /* Make sure every other state is handled properly as well. */
3037}
3038
3047static SaveOrLoadResult DoSave(std::shared_ptr<SaveFilter> writer, bool threaded)
3048{
3049 assert(!_sl.saveinprogress);
3050
3051 _sl.dumper = std::make_unique<MemoryDumper>();
3052 _sl.sf = std::move(writer);
3053
3055
3056 SaveViewportBeforeSaveGame();
3057 SlSaveChunks();
3058
3059 SaveFileStart();
3060
3061 if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) {
3062 if (threaded) Debug(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode...");
3063
3064 SaveOrLoadResult result = SaveFileToDisk(false);
3065 SaveFileDone();
3066
3067 return result;
3068 }
3069
3070 return SL_OK;
3071}
3072
3079SaveOrLoadResult SaveWithFilter(std::shared_ptr<SaveFilter> writer, bool threaded)
3080{
3081 try {
3082 _sl.action = SLA_SAVE;
3083 return DoSave(std::move(writer), threaded);
3084 } catch (...) {
3086 return SL_ERROR;
3087 }
3088}
3089
3098static const SaveLoadFormat *DetermineSaveLoadFormat(uint32_t tag, uint32_t raw_version)
3099{
3100 auto fmt = std::ranges::find(_saveload_formats, tag, &SaveLoadFormat::tag);
3101 if (fmt != std::end(_saveload_formats)) {
3102 /* Check version number */
3103 _sl_version = (SaveLoadVersion)(TO_BE32(raw_version) >> 16);
3104 /* Minor is not used anymore from version 18.0, but it is still needed
3105 * in versions before that (4 cases) which can't be removed easy.
3106 * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
3107 _sl_minor_version = (TO_BE32(raw_version) >> 8) & 0xFF;
3108
3109 Debug(sl, 1, "Loading savegame version {}", _sl_version);
3110
3111 /* Is the version higher than the current? */
3112 if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
3113 if (_sl_version >= SLV_START_PATCHPACKS && _sl_version <= SLV_END_PATCHPACKS) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK);
3114 return fmt;
3115 }
3116
3117 Debug(sl, 0, "Unknown savegame type, trying to load it as the buggy format");
3118 _sl.lf->Reset();
3121
3122 /* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
3123 fmt = std::ranges::find(_saveload_formats, SAVEGAME_TAG_LZO, &SaveLoadFormat::tag);
3124 if (fmt == std::end(_saveload_formats)) {
3125 /* Who removed the LZO savegame format definition? When built without LZO support,
3126 * the formats must still list it just without a method to read the file.
3127 * The caller of this function has to check for the existence of load function. */
3128 NOT_REACHED();
3129 }
3130 return fmt;
3131}
3132
3139static SaveOrLoadResult DoLoad(std::shared_ptr<LoadFilter> reader, bool load_check)
3140{
3141 _sl.lf = std::move(reader);
3142
3143 if (load_check) {
3144 /* Clear previous check data */
3145 _load_check_data.Clear();
3146 /* Mark SL_LOAD_CHECK as supported for this savegame. */
3147 _load_check_data.checkable = true;
3148 }
3149
3150 uint32_t hdr[2];
3151 if (_sl.lf->Read((uint8_t*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3152
3153 /* see if we have any loader for this type. */
3154 const SaveLoadFormat *fmt = DetermineSaveLoadFormat(hdr[0], hdr[1]);
3155
3156 /* loader for this savegame type is not implemented? */
3157 if (fmt->init_load == nullptr) {
3158 SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, fmt::format("Loader for '{}' is not available.", fmt->name));
3159 }
3160
3161 _sl.lf = fmt->init_load(_sl.lf);
3162 _sl.reader = std::make_unique<ReadBuffer>(_sl.lf);
3163 _next_offs = 0;
3164
3165 if (!load_check) {
3167
3168 /* Old maps were hardcoded to 256x256 and thus did not contain
3169 * any mapsize information. Pre-initialize to 256x256 to not to
3170 * confuse old games */
3171 InitializeGame(256, 256, true, true);
3172
3173 _gamelog.Reset();
3174
3176 /*
3177 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
3178 * shared savegame version 4. Anything before that 'obviously'
3179 * does not have any NewGRFs. Between the introduction and
3180 * savegame version 41 (just before 0.5) the NewGRF settings
3181 * were not stored in the savegame and they were loaded by
3182 * using the settings from the main menu.
3183 * So, to recap:
3184 * - savegame version < 4: do not load any NewGRFs.
3185 * - savegame version >= 41: load NewGRFs from savegame, which is
3186 * already done at this stage by
3187 * overwriting the main menu settings.
3188 * - other savegame versions: use main menu settings.
3189 *
3190 * This means that users *can* crash savegame version 4..40
3191 * savegames if they set incompatible NewGRFs in the main menu,
3192 * but can't crash anymore for savegame version < 4 savegames.
3193 *
3194 * Note: this is done here because AfterLoadGame is also called
3195 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
3196 */
3198 }
3199 }
3200
3201 if (load_check) {
3202 /* Load chunks into _load_check_data.
3203 * No pools are loaded. References are not possible, and thus do not need resolving. */
3205 } else {
3206 /* Load chunks and resolve references */
3207 SlLoadChunks();
3208 SlFixPointers();
3209 }
3210
3212
3214
3215 if (load_check) {
3216 /* The only part from AfterLoadGame() we need */
3217 _load_check_data.grf_compatibility = IsGoodGRFConfigList(_load_check_data.grfconfig);
3218 } else {
3219 _gamelog.StartAction(GLAT_LOAD);
3220
3221 /* After loading fix up savegame for any internal changes that
3222 * might have occurred since then. If it fails, load back the old game. */
3223 if (!AfterLoadGame()) {
3224 _gamelog.StopAction();
3225 return SL_REINIT;
3226 }
3227
3228 _gamelog.StopAction();
3229 }
3230
3231 return SL_OK;
3232}
3233
3239SaveOrLoadResult LoadWithFilter(std::shared_ptr<LoadFilter> reader)
3240{
3241 try {
3242 _sl.action = SLA_LOAD;
3243 return DoLoad(std::move(reader), false);
3244 } catch (...) {
3246 return SL_REINIT;
3247 }
3248}
3249
3260SaveOrLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
3261{
3262 /* An instance of saving is already active, so don't go saving again */
3263 if (_sl.saveinprogress && fop == SLO_SAVE && dft == DFT_GAME_FILE && threaded) {
3264 /* if not an autosave, but a user action, show error message */
3265 if (!_do_autosave) ShowErrorMessage(GetEncodedString(STR_ERROR_SAVE_STILL_IN_PROGRESS), {}, WL_ERROR);
3266 return SL_OK;
3267 }
3268 WaitTillSaved();
3269
3270 try {
3271 /* Load a TTDLX or TTDPatch game */
3272 if (fop == SLO_LOAD && dft == DFT_OLD_GAME_FILE) {
3274
3275 InitializeGame(256, 256, true, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused
3276
3277 /* TTD/TTO savegames have no NewGRFs, TTDP savegame have them
3278 * and if so a new NewGRF list will be made in LoadOldSaveGame.
3279 * Note: this is done here because AfterLoadGame is also called
3280 * for OTTD savegames which have their own NewGRF logic. */
3282 _gamelog.Reset();
3283 if (!LoadOldSaveGame(filename)) return SL_REINIT;
3286 _gamelog.StartAction(GLAT_LOAD);
3287 if (!AfterLoadGame()) {
3288 _gamelog.StopAction();
3289 return SL_REINIT;
3290 }
3291 _gamelog.StopAction();
3292 return SL_OK;
3293 }
3294
3295 assert(dft == DFT_GAME_FILE);
3296 switch (fop) {
3297 case SLO_CHECK:
3298 _sl.action = SLA_LOAD_CHECK;
3299 break;
3300
3301 case SLO_LOAD:
3302 _sl.action = SLA_LOAD;
3303 break;
3304
3305 case SLO_SAVE:
3306 _sl.action = SLA_SAVE;
3307 break;
3308
3309 default: NOT_REACHED();
3310 }
3311
3312 auto fh = (fop == SLO_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
3313
3314 /* Make it a little easier to load savegames from the console */
3315 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SAVE_DIR);
3316 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", BASE_DIR);
3317 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SCENARIO_DIR);
3318
3319 if (!fh.has_value()) {
3320 SlError(fop == SLO_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3321 }
3322
3323 if (fop == SLO_SAVE) { // SAVE game
3324 Debug(desync, 1, "save: {:08x}; {:02x}; {}", TimerGameEconomy::date, TimerGameEconomy::date_fract, filename);
3325 if (!_settings_client.gui.threaded_saves) threaded = false;
3326
3327 return DoSave(std::make_shared<FileWriter>(std::move(*fh)), threaded);
3328 }
3329
3330 /* LOAD game */
3331 assert(fop == SLO_LOAD || fop == SLO_CHECK);
3332 Debug(desync, 1, "load: {}", filename);
3333 return DoLoad(std::make_shared<FileReader>(std::move(*fh)), fop == SLO_CHECK);
3334 } catch (...) {
3335 /* This code may be executed both for old and new save games. */
3337
3338 /* Skip the "colour" character */
3339 if (fop != SLO_CHECK) Debug(sl, 0, "{}", GetSaveLoadErrorType().GetDecodedString().substr(3) + GetSaveLoadErrorMessage().GetDecodedString());
3340
3341 /* A saver/loader exception!! reinitialize all variables to prevent crash! */
3342 return (fop == SLO_LOAD) ? SL_REINIT : SL_ERROR;
3343 }
3344}
3345
3351{
3352 std::string filename;
3353
3354 if (_settings_client.gui.keep_all_autosave) {
3355 filename = GenerateDefaultSaveName() + counter.Extension();
3356 } else {
3357 filename = counter.Filename();
3358 }
3359
3360 Debug(sl, 2, "Autosaving to '{}'", filename);
3361 if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR) != SL_OK) {
3362 ShowErrorMessage(GetEncodedString(STR_ERROR_AUTOSAVE_FAILED), {}, WL_ERROR);
3363 }
3364}
3365
3366
3369{
3371}
3372
3377{
3378 /* Check if we have a name for this map, which is the name of the first
3379 * available company. When there's no company available we'll use
3380 * 'Spectator' as "company" name. */
3381 CompanyID cid = _local_company;
3382 if (!Company::IsValidID(cid)) {
3383 for (const Company *c : Company::Iterate()) {
3384 cid = c->index;
3385 break;
3386 }
3387 }
3388
3389 std::array<StringParameter, 4> params{};
3390 auto it = params.begin();
3391 *it++ = cid;
3392
3393 /* We show the current game time differently depending on the timekeeping units used by this game. */
3395 /* Insert time played. */
3396 const auto play_time = TimerGameTick::counter / Ticks::TICKS_PER_SECOND;
3397 *it++ = STR_SAVEGAME_DURATION_REALTIME;
3398 *it++ = play_time / 60 / 60;
3399 *it++ = (play_time / 60) % 60;
3400 } else {
3401 /* Insert current date */
3402 switch (_settings_client.gui.date_format_in_default_names) {
3403 case 0: *it++ = STR_JUST_DATE_LONG; break;
3404 case 1: *it++ = STR_JUST_DATE_TINY; break;
3405 case 2: *it++ = STR_JUST_DATE_ISO; break;
3406 default: NOT_REACHED();
3407 }
3408 *it++ = TimerGameEconomy::date;
3409 }
3410
3411 /* Get the correct string (special string for when there's not company) */
3412 std::string filename = GetStringWithArgs(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT, params);
3413 SanitizeFilename(filename);
3414 return filename;
3415}
3416
3423{
3424 if (ft.abstract == FT_INVALID || ft.abstract == FT_NONE) {
3425 this->file_op = SLO_INVALID;
3426 this->ftype = FIOS_TYPE_INVALID;
3427 return;
3428 }
3429
3430 this->file_op = fop;
3431 this->ftype = ft;
3432}
3433
3439{
3440 this->SetMode(item.type);
3441 this->name = item.name;
3442 this->title = item.title;
3443}
3444
3446{
3447 assert(this->load_description.has_value());
3448 return *this->load_description;
3449}
Base class for autoreplaces/autorenews.
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:538
std::optional< std::vector< SaveLoad > > load_description
Description derived from savegame being loaded.
Definition saveload.h:540
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 void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd=SL_VAR)
Internal templated helper to save/load a list-like type.
static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd=SL_VAR)
Internal templated helper to return the size in bytes of 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.
Functions related to errors.
@ WL_ERROR
Errors (eg. saving/loading failed).
Definition error.h:26
@ WL_CRITICAL
Critical errors, the MessageBox is shown in all cases.
Definition error.h:27
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:1006
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:244
Functions for standard in/out file operations.
SaveLoadOperation
Operation performed on the file.
Definition fileio_type.h:52
@ SLO_CHECK
Load file for checking and/or preview.
Definition fileio_type.h:53
@ SLO_SAVE
File is being saved.
Definition fileio_type.h:55
@ SLO_LOAD
File is being loaded.
Definition fileio_type.h:54
@ SLO_INVALID
Unknown file operation.
Definition fileio_type.h:57
DetailedFileType
Kinds of files in each AbstractFileType.
Definition fileio_type.h:28
@ DFT_GAME_FILE
Save game or scenario file.
Definition fileio_type.h:31
@ DFT_OLD_GAME_FILE
Old save game or scenario file.
Definition fileio_type.h:30
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
@ SCENARIO_DIR
Base directory for all scenarios.
Definition fileio_type.h:92
@ BASE_DIR
Base directory for all subdirectories.
Definition fileio_type.h:89
@ SAVE_DIR
Base directory for all savegames.
Definition fileio_type.h:90
@ AUTOSAVE_DIR
Subdirectory of save for autosaves.
Definition fileio_type.h:91
@ FT_NONE
nothing to do
Definition fileio_type.h:18
@ FT_INVALID
Invalid or unknown file type.
Definition fileio_type.h:24
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.
@ GLAT_LOAD
Game loaded.
Definition gamelog.h:18
void SetMouseCursorBusy(bool busy)
Set or unset the ZZZ cursor.
Definition gfx.cpp:1695
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 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:66
bool _network_server
network-server is active
Definition network.cpp:67
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.
static uint8_t GetSavegameFileType(const SaveLoad &sld)
Return the type as saved/loaded inside the savegame.
Definition saveload.cpp:570
static SaveOrLoadResult DoSave(std::shared_ptr< SaveFilter > writer, bool threaded)
Actually perform the saving of the savegame.
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 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 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.
SaveOrLoadResult LoadWithFilter(std::shared_ptr< LoadFilter > reader)
Load the game using a (reader) filter.
static SaveOrLoadResult SaveFileToDisk(bool threaded)
We have written the whole game into memory, _memory_savegame, now find and appropriate compressor and...
static void ResetSaveloadData()
Clear temporary data that is passed between various saveload phases.
static size_t ReferenceToInt(const void *obj, SLRefType rt)
Pointers cannot be saved to a savegame, so this functions gets the index of the item,...
SaveOrLoadResult SaveWithFilter(std::shared_ptr< SaveFilter > writer, bool threaded)
Save the game using a (writer) filter.
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
static uint SlCalcConvMemLen(VarType conv)
Return the size in bytes of a certain type of normal/atomic variable as it appears in memory.
Definition saveload.cpp:606
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 size_t SlCalcArrayLen(size_t length, VarType conv)
Return the size in bytes of a certain type of atomic array.
void WriteValue(void *ptr, VarType conv, int64_t val)
Write the value of a setting.
Definition saveload.cpp:836
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:677
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 SL_VARs to/from a savegame.
size_t SlGetFieldLength()
Get the length of the current object.
Definition saveload.cpp:800
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:660
NeedLength
Definition saveload.cpp:95
@ NL_WANTLENGTH
writing length and data
Definition saveload.cpp:97
@ NL_NONE
not working in NeedLength mode
Definition saveload.cpp:96
@ NL_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 SaveOrLoadResult DoLoad(std::shared_ptr< LoadFilter > reader, bool load_check)
Actually perform the loading of a "non-old" savegame.
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:719
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:917
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
@ SLA_LOAD
loading
Definition saveload.cpp:88
@ SLA_NULL
null all pointers (on loading error)
Definition saveload.cpp:91
@ SLA_SAVE
saving
Definition saveload.cpp:89
@ SLA_LOAD_CHECK
partial loading into _load_check_data
Definition saveload.cpp:92
@ SLA_PTRS
fixing pointers
Definition saveload.cpp:90
static void SlCopyBytes(void *ptr, size_t length)
Save/Load bytes.
Definition saveload.cpp:783
static void SlCopyInternal(void *object, size_t length, VarType conv)
Internal function to save/Load a list of SL_VARs.
SaveOrLoadResult 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 void SlArray(void *array, size_t length, VarType conv)
Save/Load the length of the array followed by the array of SL_VAR elements.
static void SlSaveLoadConv(void *ptr, VarType conv)
Handle all conversion and typechecking of variables here.
Definition saveload.cpp:862
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:731
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 void SlDeque(void *deque, VarType conv)
Save/load a std::deque.
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:636
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...
int64_t ReadValue(const void *ptr, VarType conv)
Return a signed-long version of the value of a setting.
Definition saveload.cpp:812
static void SlVector(void *vector, VarType conv)
Save/load a std::vector.
static void SaveFileError()
Show a gui message when saving has failed.
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.
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.
Definition saveload.cpp:934
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.
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:537
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.
static size_t SlCalcDequeLen(const void *deque, VarType conv)
Return the size in bytes of a std::deque.
SavegameType
Types of save games.
Definition saveload.h:440
@ SGT_OTTD
OTTD savegame.
Definition saveload.h:444
SaveOrLoadResult
Save or load result codes.
Definition saveload.h:422
@ SL_OK
completed successfully
Definition saveload.h:423
@ SL_REINIT
error that was caught in the middle of updating game state, need to clear it. (can only happen during...
Definition saveload.h:425
@ SLE_VAR_NULL
useful to write zeros in savegame.
Definition saveload.h:688
@ SLE_FILE_END
Used to mark end-of-header in tables.
Definition saveload.h:661
@ SLE_FILE_TYPE_MASK
Mask to get the file-type (and not any flags).
Definition saveload.h:675
@ SLE_FILE_HAS_LENGTH_FIELD
Bit stored in savegame to indicate field has a length field for each entry.
Definition saveload.h:676
@ SLF_REPLACE_TABCRLF
Replace tabs, cr and lf in the string with spaces.
Definition saveload.h:725
@ SLF_ALLOW_NEWLINE
Allow new lines in the strings.
Definition saveload.h:724
@ SLF_ALLOW_CONTROL
Allow control codes in the strings.
Definition saveload.h:723
@ SLE_VAR_STR
string pointer
Definition saveload.h:689
@ SLE_VAR_NAME
old custom name to be converted to a string pointer
Definition saveload.h:691
@ SLE_VAR_STRQ
string pointer enclosed in quotes
Definition saveload.h:690
@ SLE_FILE_STRINGID
StringID offset into strings-array.
Definition saveload.h:670
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:1383
constexpr VarType GetVarFileType(VarType type)
Get the FileType of a setting.
Definition saveload.h:798
SLRefType
Type of reference (SLE_REF, SLE_CONDREF).
Definition saveload.h:636
@ REF_VEHICLE_OLD
Load/save an old-style reference to a vehicle (for pre-4.4 savegames).
Definition saveload.h:640
@ REF_LINK_GRAPH_JOB
Load/save a reference to a link graph job.
Definition saveload.h:647
@ REF_TOWN
Load/save a reference to a town.
Definition saveload.h:639
@ REF_LINK_GRAPH
Load/save a reference to a link graph.
Definition saveload.h:646
@ REF_CARGO_PACKET
Load/save a reference to a cargo packet.
Definition saveload.h:643
@ REF_ENGINE_RENEWS
Load/save a reference to an engine renewal (autoreplace).
Definition saveload.h:642
@ REF_STATION
Load/save a reference to a station.
Definition saveload.h:638
@ REF_ORDERLIST
Load/save a reference to an orderlist.
Definition saveload.h:644
@ REF_STORAGE
Load/save a reference to a persistent storage.
Definition saveload.h:645
@ REF_VEHICLE
Load/save a reference to a vehicle.
Definition saveload.h:637
@ REF_ROADSTOPS
Load/save a reference to a bus/truck stop.
Definition saveload.h:641
void * GetVariableAddress(const void *object, const SaveLoad &sld)
Get the address of the variable.
Definition saveload.h:1337
std::span< const ChunkHandlerRef > ChunkHandlerTable
A table of ChunkHandler entries.
Definition saveload.h:529
SaveLoadType
Type of data saved.
Definition saveload.h:731
@ SL_NULL
Save null-bytes and load to nowhere.
Definition saveload.h:745
@ SL_STRUCTLIST
Save/load a list of structs.
Definition saveload.h:742
@ SL_STDSTR
Save/load a std::string.
Definition saveload.h:736
@ SL_REF
Save/load a reference.
Definition saveload.h:733
@ SL_SAVEBYTE
Save (but not load) a byte.
Definition saveload.h:744
@ SL_DEQUE
Save/load a deque of SL_VAR elements.
Definition saveload.h:739
@ SL_STRUCT
Save/load a struct.
Definition saveload.h:734
@ SL_VECTOR
Save/load a vector of SL_VAR elements.
Definition saveload.h:740
@ SL_REFVECTOR
Save/load a vector of SL_REF elements.
Definition saveload.h:747
@ SL_REFLIST
Save/load a list of SL_REF elements.
Definition saveload.h:741
@ SL_ARR
Save/load a fixed-size array of SL_VAR elements.
Definition saveload.h:738
@ SL_VAR
Save/load a variable.
Definition saveload.h:732
std::span< const struct SaveLoadCompat > SaveLoadCompatTable
A table of SaveLoadCompat entries.
Definition saveload.h:535
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1299
constexpr VarType GetVarMemType(VarType type)
Get the NumberType of a setting.
Definition saveload.h:787
SaveLoadVersion
SaveLoad versions Previous savegame versions, the trunk revision where they were introduced and the r...
Definition saveload.h:30
@ SLV_69
69 10319
Definition saveload.h:125
@ SLV_FIX_SCC_ENCODED_NEGATIVE
353 PR#14049 Fix encoding of negative parameters.
Definition saveload.h:403
@ SLV_4
4.0 1 4.1 122 0.3.3, 0.3.4 4.2 1222 0.3.5 4.3 1417 4.4 1426
Definition saveload.h:37
@ SLV_SAVELOAD_LIST_LENGTH
293 PR#9374 Consistency in list length with SL_STRUCT / SL_STRUCTLIST / SL_DEQUE / SL_REFLIST.
Definition saveload.h:331
@ SLV_START_PATCHPACKS
220 First known patchpack to use a version just above ours.
Definition saveload.h:321
@ SL_MAX_VERSION
Highest possible saveload version.
Definition saveload.h:418
@ SL_MIN_VERSION
First savegame version.
Definition saveload.h:31
@ SLV_END_PATCHPACKS
286 Last known patchpack to use a version just above ours.
Definition saveload.h:322
@ SLV_ENCODED_STRING_FORMAT
350 PR#13499 Encoded String format changed.
Definition saveload.h:400
@ SLV_169
169 23816
Definition saveload.h:245
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:532
@ CH_TYPE_MASK
All ChunkType values have to be within this mask.
Definition saveload.h:475
@ CH_READONLY
Chunk is never saved.
Definition saveload.h:476
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
@ AllowNewline
Allow newlines; replaces '\r ' with ' ' during processing.
Definition string_type.h:46
@ ReplaceTabCrNlWithSpace
Replace tabs ('\t'), carriage returns ('\r') and newlines (' ') with spaces.
Definition string_type.h:53
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:480
ChunkType type
Type of the chunk.
Definition saveload.h:482
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:481
virtual void Save() const
Save the chunk.
Definition saveload.h:492
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:429
void SetMode(const FiosType &ft, SaveLoadOperation fop=SLO_LOAD)
Set the mode and file type of the file to save or load.
FiosType ftype
File type.
Definition saveload.h:431
SaveLoadOperation file_op
File operation to perform.
Definition saveload.h:430
std::string name
Name of the file.
Definition saveload.h:432
EncodedString title
Internal name of the game.
Definition saveload.h:433
void Set(const FiosItem &item)
Set the mode, title and name of the file.
std::optional< FileHandle > file
The file to write to.
~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.
void Write(uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
Deals with finding savegames.
Definition fios.h:78
A savegame name automatically numbered.
Definition fios.h:128
std::string Filename()
Generate a savegame name and number according to _settings_client.gui.max_num_autosaves.
Definition fios.cpp:736
std::string Extension()
Generate an extension for a savegame name.
Definition fios.cpp:746
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 Write(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.
void WriteLoop(uint8_t *p, size_t len, lzma_action action)
Helper loop for writing the data.
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(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.
NoCompSaveFilter(std::shared_ptr< SaveFilter > chain, uint8_t)
Initialise this filter.
void Write(uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
Shared order list linking together the linked list of orders and the list of vehicles sharing this or...
Definition order_base.h:274
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
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
uint8_t block_mode
???
Definition saveload.cpp:199
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:753
uint16_t length
(Conditional) length of the variable (eg. arrays) (max array size is 65536 elements).
Definition saveload.h:757
std::shared_ptr< SaveLoadHandler > handler
Custom handler for Save/Load procs.
Definition saveload.h:762
SaveLoadVersion version_to
Save/load the variable before this savegame version.
Definition saveload.h:759
SaveLoadType cmd
The action to take with the saved/loaded type, All types need different action.
Definition saveload.h:755
std::string name
Name of this field (optional, used for tables).
Definition saveload.h:754
VarType conv
Type of the variable to be saved; this field combines both FileVarType and MemVarType.
Definition saveload.h:756
SaveLoadVersion version_from
Save/load the variable starting from this savegame version.
Definition saveload.h:758
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:63
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.
void WriteLoop(uint8_t *p, size_t len, int mode)
Helper loop for writing the data.
z_stream z
Stream state we are writing to.
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 Write(uint8_t *buf, size_t size) override
Write a given number of bytes into the savegame.
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:3310
Window functions not directly related to making/drawing windows.
@ WC_STATUS_BAR
Statusbar (at the bottom of your screen); Window numbers:
Definition window_type.h:69