OpenTTD Source 20260311-master-g511d3794ce
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
541static inline uint SlGetGammaLength(size_t i)
542{
543 return 1 + (i >= (1 << 7)) + (i >= (1 << 14)) + (i >= (1 << 21)) + (i >= (1 << 28));
544}
545
546static inline uint SlReadSparseIndex()
547{
548 return SlReadSimpleGamma();
549}
550
551static inline void SlWriteSparseIndex(uint index)
552{
553 SlWriteSimpleGamma(index);
554}
555
556static inline uint SlReadArrayLength()
557{
558 return SlReadSimpleGamma();
559}
560
561static inline void SlWriteArrayLength(size_t length)
562{
563 SlWriteSimpleGamma(length);
564}
565
566static inline uint SlGetArrayLength(size_t length)
567{
568 return SlGetGammaLength(length);
569}
570
576static uint8_t GetSavegameFileType(const SaveLoad &sld)
577{
578 switch (sld.cmd) {
579 case SL_VAR:
580 return GetVarFileType(sld.conv); break;
581
582 case SL_STDSTR:
583 case SL_ARR:
584 case SL_VECTOR:
585 case SL_DEQUE:
586 return GetVarFileType(sld.conv) | SLE_FILE_HAS_LENGTH_FIELD; break;
587
588 case SL_REF:
589 return IsSavegameVersionBefore(SLV_69) ? SLE_FILE_U16 : SLE_FILE_U32;
590
591 case SL_REFLIST:
592 case SL_REFVECTOR:
593 return (IsSavegameVersionBefore(SLV_69) ? SLE_FILE_U16 : SLE_FILE_U32) | SLE_FILE_HAS_LENGTH_FIELD;
594
595 case SL_SAVEBYTE:
596 return SLE_FILE_U8;
597
598 case SL_STRUCT:
599 case SL_STRUCTLIST:
600 return SLE_FILE_STRUCT | SLE_FILE_HAS_LENGTH_FIELD;
601
602 default: NOT_REACHED();
603 }
604}
605
612static inline uint SlCalcConvMemLen(VarType conv)
613{
614 switch (GetVarMemType(conv)) {
615 case SLE_VAR_BL: return sizeof(bool);
616 case SLE_VAR_I8: return sizeof(int8_t);
617 case SLE_VAR_U8: return sizeof(uint8_t);
618 case SLE_VAR_I16: return sizeof(int16_t);
619 case SLE_VAR_U16: return sizeof(uint16_t);
620 case SLE_VAR_I32: return sizeof(int32_t);
621 case SLE_VAR_U32: return sizeof(uint32_t);
622 case SLE_VAR_I64: return sizeof(int64_t);
623 case SLE_VAR_U64: return sizeof(uint64_t);
624 case SLE_VAR_NULL: return 0;
625
626 case SLE_VAR_STR:
627 case SLE_VAR_STRQ:
628 return SlReadArrayLength();
629
630 case SLE_VAR_NAME:
631 default:
632 NOT_REACHED();
633 }
634}
635
642static inline uint8_t SlCalcConvFileLen(VarType conv)
643{
644 switch (GetVarFileType(conv)) {
645 case SLE_FILE_END: return 0;
646 case SLE_FILE_I8: return sizeof(int8_t);
647 case SLE_FILE_U8: return sizeof(uint8_t);
648 case SLE_FILE_I16: return sizeof(int16_t);
649 case SLE_FILE_U16: return sizeof(uint16_t);
650 case SLE_FILE_I32: return sizeof(int32_t);
651 case SLE_FILE_U32: return sizeof(uint32_t);
652 case SLE_FILE_I64: return sizeof(int64_t);
653 case SLE_FILE_U64: return sizeof(uint64_t);
654 case SLE_FILE_STRINGID: return sizeof(uint16_t);
655
656 case SLE_FILE_STRING:
657 return SlReadArrayLength();
658
659 case SLE_FILE_STRUCT:
660 default:
661 NOT_REACHED();
662 }
663}
664
669static inline size_t SlCalcRefLen()
670{
671 return IsSavegameVersionBefore(SLV_69) ? 2 : 4;
672}
673
674void SlSetArrayIndex(uint index)
675{
676 _sl.need_length = NL_WANTLENGTH;
677 _sl.array_index = index;
678}
679
680static size_t _next_offs;
681
687{
688 /* After reading in the whole array inside the loop
689 * we must have read in all the data, so we must be at end of current block. */
690 if (_next_offs != 0 && _sl.reader->GetSize() != _next_offs) {
691 SlErrorCorruptFmt("Invalid chunk size iterating array - expected to be at position {}, actually at {}", _next_offs, _sl.reader->GetSize());
692 }
693
694 for (;;) {
695 uint length = SlReadArrayLength();
696 if (length == 0) {
697 assert(!_sl.expect_table_header);
698 _next_offs = 0;
699 return -1;
700 }
701
702 _sl.obj_len = --length;
703 _next_offs = _sl.reader->GetSize() + length;
704
705 if (_sl.expect_table_header) {
706 _sl.expect_table_header = false;
707 return INT32_MAX;
708 }
709
710 int index;
711 switch (_sl.block_mode) {
712 case CH_SPARSE_TABLE:
713 case CH_SPARSE_ARRAY: index = (int)SlReadSparseIndex(); break;
714 case CH_TABLE:
715 case CH_ARRAY: index = _sl.array_index++; break;
716 default:
717 Debug(sl, 0, "SlIterateArray error");
718 return -1; // error
719 }
720
721 if (length != 0) return index;
722 }
723}
724
729{
730 while (SlIterateArray() != -1) {
731 SlSkipBytes(_next_offs - _sl.reader->GetSize());
732 }
733}
734
740void SlSetLength(size_t length)
741{
742 assert(_sl.action == SLA_SAVE);
743
744 switch (_sl.need_length) {
745 case NL_WANTLENGTH:
746 _sl.need_length = NL_NONE;
747 if ((_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) && _sl.expect_table_header) {
748 _sl.expect_table_header = false;
749 SlWriteArrayLength(length + 1);
750 break;
751 }
752
753 switch (_sl.block_mode) {
754 case CH_RIFF:
755 /* Ugly encoding of >16M RIFF chunks
756 * The lower 24 bits are normal
757 * The uppermost 4 bits are bits 24:27 */
758 assert(length < (1 << 28));
759 SlWriteUint32((uint32_t)((length & 0xFFFFFF) | ((length >> 24) << 28)));
760 break;
761 case CH_TABLE:
762 case CH_ARRAY:
763 assert(_sl.last_array_index <= _sl.array_index);
764 while (++_sl.last_array_index <= _sl.array_index) {
765 SlWriteArrayLength(1);
766 }
767 SlWriteArrayLength(length + 1);
768 break;
769 case CH_SPARSE_TABLE:
770 case CH_SPARSE_ARRAY:
771 SlWriteArrayLength(length + 1 + SlGetArrayLength(_sl.array_index)); // Also include length of sparse index.
772 SlWriteSparseIndex(_sl.array_index);
773 break;
774 default: NOT_REACHED();
775 }
776 break;
777
778 case NL_CALCLENGTH:
779 _sl.obj_len += (int)length;
780 break;
781
782 default: NOT_REACHED();
783 }
784}
785
792static void SlCopyBytes(void *ptr, size_t length)
793{
794 uint8_t *p = (uint8_t *)ptr;
795
796 switch (_sl.action) {
797 case SLA_LOAD_CHECK:
798 case SLA_LOAD:
799 for (; length != 0; length--) *p++ = SlReadByte();
800 break;
801 case SLA_SAVE:
802 for (; length != 0; length--) SlWriteByte(*p++);
803 break;
804 default: NOT_REACHED();
805 }
806}
807
813{
814 return _sl.obj_len;
815}
816
824int64_t ReadValue(const void *ptr, VarType conv)
825{
826 switch (GetVarMemType(conv)) {
827 case SLE_VAR_BL: return (*(const bool *)ptr != 0);
828 case SLE_VAR_I8: return *(const int8_t *)ptr;
829 case SLE_VAR_U8: return *(const uint8_t *)ptr;
830 case SLE_VAR_I16: return *(const int16_t *)ptr;
831 case SLE_VAR_U16: return *(const uint16_t*)ptr;
832 case SLE_VAR_I32: return *(const int32_t *)ptr;
833 case SLE_VAR_U32: return *(const uint32_t*)ptr;
834 case SLE_VAR_I64: return *(const int64_t *)ptr;
835 case SLE_VAR_U64: return *(const uint64_t*)ptr;
836 case SLE_VAR_NULL:return 0;
837 default: NOT_REACHED();
838 }
839}
840
848void WriteValue(void *ptr, VarType conv, int64_t val)
849{
850 switch (GetVarMemType(conv)) {
851 case SLE_VAR_BL: *(bool *)ptr = (val != 0); break;
852 case SLE_VAR_I8: *(int8_t *)ptr = val; break;
853 case SLE_VAR_U8: *(uint8_t *)ptr = val; break;
854 case SLE_VAR_I16: *(int16_t *)ptr = val; break;
855 case SLE_VAR_U16: *(uint16_t*)ptr = val; break;
856 case SLE_VAR_I32: *(int32_t *)ptr = val; break;
857 case SLE_VAR_U32: *(uint32_t*)ptr = val; break;
858 case SLE_VAR_I64: *(int64_t *)ptr = val; break;
859 case SLE_VAR_U64: *(uint64_t*)ptr = val; break;
860 case SLE_VAR_NAME: *reinterpret_cast<std::string *>(ptr) = CopyFromOldName(val); break;
861 case SLE_VAR_NULL: break;
862 default: NOT_REACHED();
863 }
864}
865
874static void SlSaveLoadConv(void *ptr, VarType conv)
875{
876 switch (_sl.action) {
877 case SLA_SAVE: {
878 int64_t x = ReadValue(ptr, conv);
879
880 /* Write the value to the file and check if its value is in the desired range */
881 switch (GetVarFileType(conv)) {
882 case SLE_FILE_I8: assert(x >= -128 && x <= 127); SlWriteByte(x);break;
883 case SLE_FILE_U8: assert(x >= 0 && x <= 255); SlWriteByte(x);break;
884 case SLE_FILE_I16:assert(x >= -32768 && x <= 32767); SlWriteUint16(x);break;
886 case SLE_FILE_U16:assert(x >= 0 && x <= 65535); SlWriteUint16(x);break;
887 case SLE_FILE_I32:
888 case SLE_FILE_U32: SlWriteUint32((uint32_t)x);break;
889 case SLE_FILE_I64:
890 case SLE_FILE_U64: SlWriteUint64(x);break;
891 default: NOT_REACHED();
892 }
893 break;
894 }
895 case SLA_LOAD_CHECK:
896 case SLA_LOAD: {
897 int64_t x;
898 /* Read a value from the file */
899 switch (GetVarFileType(conv)) {
900 case SLE_FILE_I8: x = (int8_t )SlReadByte(); break;
901 case SLE_FILE_U8: x = (uint8_t )SlReadByte(); break;
902 case SLE_FILE_I16: x = (int16_t )SlReadUint16(); break;
903 case SLE_FILE_U16: x = (uint16_t)SlReadUint16(); break;
904 case SLE_FILE_I32: x = (int32_t )SlReadUint32(); break;
905 case SLE_FILE_U32: x = (uint32_t)SlReadUint32(); break;
906 case SLE_FILE_I64: x = (int64_t )SlReadUint64(); break;
907 case SLE_FILE_U64: x = (uint64_t)SlReadUint64(); break;
908 case SLE_FILE_STRINGID: x = RemapOldStringID((uint16_t)SlReadUint16()); break;
909 default: NOT_REACHED();
910 }
911
912 /* Write The value to the struct. These ARE endian safe. */
913 WriteValue(ptr, conv, x);
914 break;
915 }
916 case SLA_PTRS: break;
917 case SLA_NULL: break;
918 default: NOT_REACHED();
919 }
920}
921
929static inline size_t SlCalcStdStringLen(const void *ptr)
930{
931 const std::string *str = reinterpret_cast<const std::string *>(ptr);
932
933 size_t len = str->length();
934 return len + SlGetArrayLength(len); // also include the length of the index
935}
936
937
946void FixSCCEncoded(std::string &str, bool fix_code)
947{
948 if (str.empty()) return;
949
950 /* We need to convert from old escape-style encoding to record separator encoding.
951 * Initial `<SCC_ENCODED><STRINGID>` stays the same.
952 *
953 * `:<SCC_ENCODED><STRINGID>` becomes `<RS><SCC_ENCODED><STRINGID>`
954 * `:<HEX>` becomes `<RS><SCC_ENCODED_NUMERIC><HEX>`
955 * `:"<STRING>"` becomes `<RS><SCC_ENCODED_STRING><STRING>`
956 */
957 std::string result;
958 StringBuilder builder(result);
959
960 bool is_encoded = false; // Set if we determine by the presence of SCC_ENCODED that the string is an encoded string.
961 bool in_string = false; // Set if we in a string, between double-quotes.
962 bool need_type = true; // Set if a parameter type needs to be emitted.
963
964 StringConsumer consumer(str);
965 while (consumer.AnyBytesLeft()) {
966 char32_t c;
967 if (auto r = consumer.TryReadUtf8(); r.has_value()) {
968 c = *r;
969 } else {
970 break;
971 }
972 if (c == SCC_ENCODED || (fix_code && (c == 0xE028 || c == 0xE02A))) {
973 builder.PutUtf8(SCC_ENCODED);
974 need_type = false;
975 is_encoded = true;
976 continue;
977 }
978
979 /* If the first character is not SCC_ENCODED then we don't have to do any conversion. */
980 if (!is_encoded) return;
981
982 if (c == '"') {
983 in_string = !in_string;
984 if (in_string && need_type) {
985 /* Started a new string parameter. */
987 need_type = false;
988 }
989 continue;
990 }
991
992 if (!in_string && c == ':') {
993 builder.PutUtf8(SCC_RECORD_SEPARATOR);
994 need_type = true;
995 continue;
996 }
997 if (need_type) {
998 /* Started a new numeric parameter. */
1000 need_type = false;
1001 }
1002
1003 builder.PutUtf8(c);
1004 }
1005
1006 str = std::move(result);
1007}
1008
1013void FixSCCEncodedNegative(std::string &str)
1014{
1015 if (str.empty()) return;
1016
1017 StringConsumer consumer(str);
1018
1019 /* Check whether this is an encoded string */
1020 if (!consumer.ReadUtf8If(SCC_ENCODED)) return;
1021
1022 std::string result;
1023 StringBuilder builder(result);
1024 builder.PutUtf8(SCC_ENCODED);
1025 while (consumer.AnyBytesLeft()) {
1026 /* Copy until next record */
1027 builder.Put(consumer.ReadUntilUtf8(SCC_RECORD_SEPARATOR, StringConsumer::READ_ONE_SEPARATOR));
1028
1029 /* Check whether this is a numeric parameter */
1030 if (!consumer.ReadUtf8If(SCC_ENCODED_NUMERIC)) continue;
1032
1033 /* First try unsigned */
1034 if (auto u = consumer.TryReadIntegerBase<uint64_t>(16); u.has_value()) {
1035 builder.PutIntegerBase<uint64_t>(*u, 16);
1036 } else {
1037 /* Read as signed, store as unsigned */
1038 auto s = consumer.ReadIntegerBase<int64_t>(16);
1039 builder.PutIntegerBase<uint64_t>(static_cast<uint64_t>(s), 16);
1040 }
1041 }
1042
1043 str = std::move(result);
1044}
1045
1052void SlReadString(std::string &str, size_t length)
1053{
1054 str.resize(length);
1055 SlCopyBytes(str.data(), length);
1056}
1057
1063static void SlStdString(void *ptr, VarType conv)
1064{
1065 std::string *str = reinterpret_cast<std::string *>(ptr);
1066
1067 switch (_sl.action) {
1068 case SLA_SAVE: {
1069 size_t len = str->length();
1070 SlWriteArrayLength(len);
1071 SlCopyBytes(const_cast<void *>(static_cast<const void *>(str->data())), len);
1072 break;
1073 }
1074
1075 case SLA_LOAD_CHECK:
1076 case SLA_LOAD: {
1077 size_t len = SlReadArrayLength();
1078 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1079 SlSkipBytes(len);
1080 return;
1081 }
1082
1083 SlReadString(*str, len);
1084
1086 if ((conv & SLF_ALLOW_CONTROL) != 0) {
1090 }
1091 if ((conv & SLF_ALLOW_NEWLINE) != 0) {
1093 }
1094 if ((conv & SLF_REPLACE_TABCRLF) != 0) {
1096 }
1098 }
1099
1100 case SLA_PTRS: break;
1101 case SLA_NULL: break;
1102 default: NOT_REACHED();
1103 }
1104}
1105
1114static void SlCopyInternal(void *object, size_t length, VarType conv)
1115{
1116 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1117 assert(_sl.action != SLA_SAVE); // Use SL_NULL if you want to write null-bytes
1118 SlSkipBytes(length * SlCalcConvFileLen(conv));
1119 return;
1120 }
1121
1122 /* NOTICE - handle some buggy stuff, in really old versions everything was saved
1123 * as a byte-type. So detect this, and adjust object size accordingly */
1124 if (_sl.action != SLA_SAVE && _sl_version == 0) {
1125 /* all objects except difficulty settings */
1126 if (conv == SLE_INT16 || conv == SLE_UINT16 || conv == SLE_STRINGID ||
1127 conv == SLE_INT32 || conv == SLE_UINT32) {
1128 SlCopyBytes(object, length * SlCalcConvFileLen(conv));
1129 return;
1130 }
1131 /* used for conversion of Money 32bit->64bit */
1132 if (conv == (SLE_FILE_I32 | SLE_VAR_I64)) {
1133 for (uint i = 0; i < length; i++) {
1134 ((int64_t*)object)[i] = (int32_t)std::byteswap(SlReadUint32());
1135 }
1136 return;
1137 }
1138 }
1139
1140 /* If the size of elements is 1 byte both in file and memory, no special
1141 * conversion is needed, use specialized copy-copy function to speed up things */
1142 if (conv == SLE_INT8 || conv == SLE_UINT8) {
1143 SlCopyBytes(object, length);
1144 } else {
1145 uint8_t *a = (uint8_t*)object;
1146 uint8_t mem_size = SlCalcConvMemLen(conv);
1147
1148 for (; length != 0; length --) {
1149 SlSaveLoadConv(a, conv);
1150 a += mem_size; // get size
1151 }
1152 }
1153}
1154
1163void SlCopy(void *object, size_t length, VarType conv)
1164{
1165 if (_sl.action == SLA_PTRS || _sl.action == SLA_NULL) return;
1166
1167 /* Automatically calculate the length? */
1168 if (_sl.need_length != NL_NONE) {
1169 SlSetLength(length * SlCalcConvFileLen(conv));
1170 /* Determine length only? */
1171 if (_sl.need_length == NL_CALCLENGTH) return;
1172 }
1173
1174 SlCopyInternal(object, length, conv);
1175}
1176
1183static inline size_t SlCalcArrayLen(size_t length, VarType conv)
1184{
1185 return SlCalcConvFileLen(conv) * length + SlGetArrayLength(length);
1186}
1187
1194static void SlArray(void *array, size_t length, VarType conv)
1195{
1196 switch (_sl.action) {
1197 case SLA_SAVE:
1198 SlWriteArrayLength(length);
1199 SlCopyInternal(array, length, conv);
1200 return;
1201
1202 case SLA_LOAD_CHECK:
1203 case SLA_LOAD: {
1205 size_t sv_length = SlReadArrayLength();
1206 if (GetVarMemType(conv) == SLE_VAR_NULL) {
1207 /* We don't know this field, so we assume the length in the savegame is correct. */
1208 length = sv_length;
1209 } else if (sv_length != length) {
1210 /* If the SLE_ARR changes size, a savegame bump is required
1211 * and the developer should have written conversion lines.
1212 * Error out to make this more visible. */
1213 SlErrorCorrupt("Fixed-length array is of wrong length");
1214 }
1215 }
1216
1217 SlCopyInternal(array, length, conv);
1218 return;
1219 }
1220
1221 case SLA_PTRS:
1222 case SLA_NULL:
1223 return;
1224
1225 default:
1226 NOT_REACHED();
1227 }
1228}
1229
1240static size_t ReferenceToInt(const void *obj, SLRefType rt)
1241{
1242 assert(_sl.action == SLA_SAVE);
1243
1244 if (obj == nullptr) return 0;
1245
1246 switch (rt) {
1247 case REF_VEHICLE_OLD: // Old vehicles we save as new ones
1248 case REF_VEHICLE: return ((const Vehicle*)obj)->index + 1;
1249 case REF_STATION: return ((const Station*)obj)->index + 1;
1250 case REF_TOWN: return ((const Town*)obj)->index + 1;
1251 case REF_ROADSTOPS: return ((const RoadStop*)obj)->index + 1;
1252 case REF_ENGINE_RENEWS: return ((const EngineRenew*)obj)->index + 1;
1253 case REF_CARGO_PACKET: return ((const CargoPacket*)obj)->index + 1;
1254 case REF_ORDERLIST: return ((const OrderList*)obj)->index + 1;
1255 case REF_STORAGE: return ((const PersistentStorage*)obj)->index + 1;
1256 case REF_LINK_GRAPH: return ((const LinkGraph*)obj)->index + 1;
1257 case REF_LINK_GRAPH_JOB: return ((const LinkGraphJob*)obj)->index + 1;
1258 default: NOT_REACHED();
1259 }
1260}
1261
1272static void *IntToReference(size_t index, SLRefType rt)
1273{
1274 static_assert(sizeof(size_t) <= sizeof(void *));
1275
1276 assert(_sl.action == SLA_PTRS);
1277
1278 /* After version 4.3 REF_VEHICLE_OLD is saved as REF_VEHICLE,
1279 * and should be loaded like that */
1280 if (rt == REF_VEHICLE_OLD && !IsSavegameVersionBefore(SLV_4, 4)) {
1281 rt = REF_VEHICLE;
1282 }
1283
1284 /* No need to look up nullptr pointers, just return immediately */
1285 if (index == (rt == REF_VEHICLE_OLD ? 0xFFFF : 0)) return nullptr;
1286
1287 /* Correct index. Old vehicles were saved differently:
1288 * invalid vehicle was 0xFFFF, now we use 0x0000 for everything invalid. */
1289 if (rt != REF_VEHICLE_OLD) index--;
1290
1291 switch (rt) {
1292 case REF_ORDERLIST:
1293 if (OrderList::IsValidID(index)) return OrderList::Get(index);
1294 SlErrorCorrupt("Referencing invalid OrderList");
1295
1296 case REF_VEHICLE_OLD:
1297 case REF_VEHICLE:
1298 if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
1299 SlErrorCorrupt("Referencing invalid Vehicle");
1300
1301 case REF_STATION:
1302 if (Station::IsValidID(index)) return Station::Get(index);
1303 SlErrorCorrupt("Referencing invalid Station");
1304
1305 case REF_TOWN:
1306 if (Town::IsValidID(index)) return Town::Get(index);
1307 SlErrorCorrupt("Referencing invalid Town");
1308
1309 case REF_ROADSTOPS:
1310 if (RoadStop::IsValidID(index)) return RoadStop::Get(index);
1311 SlErrorCorrupt("Referencing invalid RoadStop");
1312
1313 case REF_ENGINE_RENEWS:
1314 if (EngineRenew::IsValidID(index)) return EngineRenew::Get(index);
1315 SlErrorCorrupt("Referencing invalid EngineRenew");
1316
1317 case REF_CARGO_PACKET:
1318 if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
1319 SlErrorCorrupt("Referencing invalid CargoPacket");
1320
1321 case REF_STORAGE:
1322 if (PersistentStorage::IsValidID(index)) return PersistentStorage::Get(index);
1323 SlErrorCorrupt("Referencing invalid PersistentStorage");
1324
1325 case REF_LINK_GRAPH:
1326 if (LinkGraph::IsValidID(index)) return LinkGraph::Get(index);
1327 SlErrorCorrupt("Referencing invalid LinkGraph");
1328
1329 case REF_LINK_GRAPH_JOB:
1330 if (LinkGraphJob::IsValidID(index)) return LinkGraphJob::Get(index);
1331 SlErrorCorrupt("Referencing invalid LinkGraphJob");
1332
1333 default: NOT_REACHED();
1334 }
1335}
1336
1342void SlSaveLoadRef(void *ptr, VarType conv)
1343{
1344 switch (_sl.action) {
1345 case SLA_SAVE:
1346 SlWriteUint32((uint32_t)ReferenceToInt(*(void **)ptr, (SLRefType)conv));
1347 break;
1348 case SLA_LOAD_CHECK:
1349 case SLA_LOAD:
1350 *(size_t *)ptr = IsSavegameVersionBefore(SLV_69) ? SlReadUint16() : SlReadUint32();
1351 break;
1352 case SLA_PTRS:
1353 *(void **)ptr = IntToReference(*(size_t *)ptr, (SLRefType)conv);
1354 break;
1355 case SLA_NULL:
1356 *(void **)ptr = nullptr;
1357 break;
1358 default: NOT_REACHED();
1359 }
1360}
1361
1365template <template <typename, typename> typename Tstorage, typename Tvar, typename Tallocator = std::allocator<Tvar>>
1367 typedef Tstorage<Tvar, Tallocator> SlStorageT;
1368public:
1376 static size_t SlCalcLen(const void *storage, VarType conv, SaveLoadType cmd = SL_VAR)
1377 {
1378 assert(cmd == SL_VAR || cmd == SL_REF);
1379
1380 const SlStorageT *list = static_cast<const SlStorageT *>(storage);
1381
1382 int type_size = SlGetArrayLength(list->size());
1383 int item_size = SlCalcConvFileLen(cmd == SL_VAR ? conv : (VarType)SLE_FILE_U32);
1384 return list->size() * item_size + type_size;
1385 }
1386
1387 static void SlSaveLoadMember(SaveLoadType cmd, Tvar *item, VarType conv)
1388 {
1389 switch (cmd) {
1390 case SL_VAR: SlSaveLoadConv(item, conv); break;
1391 case SL_REF: SlSaveLoadRef(item, conv); break;
1392 case SL_STDSTR: SlStdString(item, conv); break;
1393 default:
1394 NOT_REACHED();
1395 }
1396 }
1397
1404 static void SlSaveLoad(void *storage, VarType conv, SaveLoadType cmd = SL_VAR)
1405 {
1406 assert(cmd == SL_VAR || cmd == SL_REF || cmd == SL_STDSTR);
1407
1408 SlStorageT *list = static_cast<SlStorageT *>(storage);
1409
1410 switch (_sl.action) {
1411 case SLA_SAVE:
1412 SlWriteArrayLength(list->size());
1413
1414 for (auto &item : *list) {
1415 SlSaveLoadMember(cmd, &item, conv);
1416 }
1417 break;
1418
1419 case SLA_LOAD_CHECK:
1420 case SLA_LOAD: {
1421 size_t length;
1422 switch (cmd) {
1423 case SL_VAR: length = IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? SlReadUint32() : SlReadArrayLength(); break;
1424 case SL_REF: length = IsSavegameVersionBefore(SLV_69) ? SlReadUint16() : IsSavegameVersionBefore(SLV_SAVELOAD_LIST_LENGTH) ? SlReadUint32() : SlReadArrayLength(); break;
1425 case SL_STDSTR: length = SlReadArrayLength(); break;
1426 default: NOT_REACHED();
1427 }
1428
1429 list->clear();
1430 if constexpr (std::is_same_v<SlStorageT, std::vector<Tvar, Tallocator>>) {
1431 list->reserve(length);
1432 }
1433
1434 /* Load each value and push to the end of the storage. */
1435 for (size_t i = 0; i < length; i++) {
1436 Tvar &data = list->emplace_back();
1437 SlSaveLoadMember(cmd, &data, conv);
1438 }
1439 break;
1440 }
1441
1442 case SLA_PTRS:
1443 for (auto &item : *list) {
1444 SlSaveLoadMember(cmd, &item, conv);
1445 }
1446 break;
1447
1448 case SLA_NULL:
1449 list->clear();
1450 break;
1451
1452 default: NOT_REACHED();
1453 }
1454 }
1455};
1456
1463static inline size_t SlCalcRefListLen(const void *list, VarType conv)
1464{
1466}
1467
1473static void SlRefList(void *list, VarType conv)
1474{
1475 /* Automatically calculate the length? */
1476 if (_sl.need_length != NL_NONE) {
1477 SlSetLength(SlCalcRefListLen(list, conv));
1478 /* Determine length only? */
1479 if (_sl.need_length == NL_CALCLENGTH) return;
1480 }
1481
1483}
1484
1491static size_t SlCalcRefVectorLen(const void *vector, VarType conv)
1492{
1494}
1495
1501static void SlRefVector(void *vector, VarType conv)
1502{
1503 /* Automatically calculate the length? */
1504 if (_sl.need_length != NL_NONE) {
1505 SlSetLength(SlCalcRefVectorLen(vector, conv));
1506 /* Determine length only? */
1507 if (_sl.need_length == NL_CALCLENGTH) return;
1508 }
1509
1511}
1512
1519static inline size_t SlCalcDequeLen(const void *deque, VarType conv)
1520{
1521 switch (GetVarMemType(conv)) {
1522 case SLE_VAR_BL: return SlStorageHelper<std::deque, bool>::SlCalcLen(deque, conv);
1523 case SLE_VAR_I8: return SlStorageHelper<std::deque, int8_t>::SlCalcLen(deque, conv);
1524 case SLE_VAR_U8: return SlStorageHelper<std::deque, uint8_t>::SlCalcLen(deque, conv);
1525 case SLE_VAR_I16: return SlStorageHelper<std::deque, int16_t>::SlCalcLen(deque, conv);
1526 case SLE_VAR_U16: return SlStorageHelper<std::deque, uint16_t>::SlCalcLen(deque, conv);
1527 case SLE_VAR_I32: return SlStorageHelper<std::deque, int32_t>::SlCalcLen(deque, conv);
1528 case SLE_VAR_U32: return SlStorageHelper<std::deque, uint32_t>::SlCalcLen(deque, conv);
1529 case SLE_VAR_I64: return SlStorageHelper<std::deque, int64_t>::SlCalcLen(deque, conv);
1530 case SLE_VAR_U64: return SlStorageHelper<std::deque, uint64_t>::SlCalcLen(deque, conv);
1531
1532 case SLE_VAR_STR:
1533 /* Strings are a length-prefixed field type in the savegame table format,
1534 * these may not be directly stored in another length-prefixed container type. */
1535 NOT_REACHED();
1536
1537 default: NOT_REACHED();
1538 }
1539}
1540
1546static void SlDeque(void *deque, VarType conv)
1547{
1548 switch (GetVarMemType(conv)) {
1549 case SLE_VAR_BL: SlStorageHelper<std::deque, bool>::SlSaveLoad(deque, conv); break;
1550 case SLE_VAR_I8: SlStorageHelper<std::deque, int8_t>::SlSaveLoad(deque, conv); break;
1551 case SLE_VAR_U8: SlStorageHelper<std::deque, uint8_t>::SlSaveLoad(deque, conv); break;
1552 case SLE_VAR_I16: SlStorageHelper<std::deque, int16_t>::SlSaveLoad(deque, conv); break;
1553 case SLE_VAR_U16: SlStorageHelper<std::deque, uint16_t>::SlSaveLoad(deque, conv); break;
1554 case SLE_VAR_I32: SlStorageHelper<std::deque, int32_t>::SlSaveLoad(deque, conv); break;
1555 case SLE_VAR_U32: SlStorageHelper<std::deque, uint32_t>::SlSaveLoad(deque, conv); break;
1556 case SLE_VAR_I64: SlStorageHelper<std::deque, int64_t>::SlSaveLoad(deque, conv); break;
1557 case SLE_VAR_U64: SlStorageHelper<std::deque, uint64_t>::SlSaveLoad(deque, conv); break;
1558
1559 case SLE_VAR_STR:
1560 /* Strings are a length-prefixed field type in the savegame table format,
1561 * these may not be directly stored in another length-prefixed container type.
1562 * This is permitted for load-related actions, because invalid fields of this type are present
1563 * from SLV_COMPANY_ALLOW_LIST up to SLV_COMPANY_ALLOW_LIST_V2. */
1564 assert(_sl.action != SLA_SAVE);
1566 break;
1567
1568 default: NOT_REACHED();
1569 }
1570}
1571
1578static inline size_t SlCalcVectorLen(const void *vector, VarType conv)
1579{
1580 switch (GetVarMemType(conv)) {
1581 case SLE_VAR_BL: NOT_REACHED(); // Not supported
1582 case SLE_VAR_I8: return SlStorageHelper<std::vector, int8_t>::SlCalcLen(vector, conv);
1583 case SLE_VAR_U8: return SlStorageHelper<std::vector, uint8_t>::SlCalcLen(vector, conv);
1584 case SLE_VAR_I16: return SlStorageHelper<std::vector, int16_t>::SlCalcLen(vector, conv);
1585 case SLE_VAR_U16: return SlStorageHelper<std::vector, uint16_t>::SlCalcLen(vector, conv);
1586 case SLE_VAR_I32: return SlStorageHelper<std::vector, int32_t>::SlCalcLen(vector, conv);
1587 case SLE_VAR_U32: return SlStorageHelper<std::vector, uint32_t>::SlCalcLen(vector, conv);
1588 case SLE_VAR_I64: return SlStorageHelper<std::vector, int64_t>::SlCalcLen(vector, conv);
1589 case SLE_VAR_U64: return SlStorageHelper<std::vector, uint64_t>::SlCalcLen(vector, conv);
1590
1591 case SLE_VAR_STR:
1592 /* Strings are a length-prefixed field type in the savegame table format,
1593 * these may not be directly stored in another length-prefixed container type. */
1594 NOT_REACHED();
1595
1596 default: NOT_REACHED();
1597 }
1598}
1599
1605static void SlVector(void *vector, VarType conv)
1606{
1607 switch (GetVarMemType(conv)) {
1608 case SLE_VAR_BL: NOT_REACHED(); // Not supported
1609 case SLE_VAR_I8: SlStorageHelper<std::vector, int8_t>::SlSaveLoad(vector, conv); break;
1610 case SLE_VAR_U8: SlStorageHelper<std::vector, uint8_t>::SlSaveLoad(vector, conv); break;
1611 case SLE_VAR_I16: SlStorageHelper<std::vector, int16_t>::SlSaveLoad(vector, conv); break;
1612 case SLE_VAR_U16: SlStorageHelper<std::vector, uint16_t>::SlSaveLoad(vector, conv); break;
1613 case SLE_VAR_I32: SlStorageHelper<std::vector, int32_t>::SlSaveLoad(vector, conv); break;
1614 case SLE_VAR_U32: SlStorageHelper<std::vector, uint32_t>::SlSaveLoad(vector, conv); break;
1615 case SLE_VAR_I64: SlStorageHelper<std::vector, int64_t>::SlSaveLoad(vector, conv); break;
1616 case SLE_VAR_U64: SlStorageHelper<std::vector, uint64_t>::SlSaveLoad(vector, conv); break;
1617
1618 case SLE_VAR_STR:
1619 /* Strings are a length-prefixed field type in the savegame table format,
1620 * these may not be directly stored in another length-prefixed container type.
1621 * This is permitted for load-related actions, because invalid fields of this type are present
1622 * from SLV_COMPANY_ALLOW_LIST up to SLV_COMPANY_ALLOW_LIST_V2. */
1623 assert(_sl.action != SLA_SAVE);
1625 break;
1626
1627 default: NOT_REACHED();
1628 }
1629}
1630
1636static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld)
1637{
1638 return (_sl_version >= sld.version_from && _sl_version < sld.version_to);
1639}
1640
1646static size_t SlCalcTableHeader(const SaveLoadTable &slt)
1647{
1648 size_t length = 0;
1649
1650 for (auto &sld : slt) {
1651 if (!SlIsObjectValidInSavegame(sld)) continue;
1652
1653 length += SlCalcConvFileLen(SLE_UINT8);
1654 length += SlCalcStdStringLen(&sld.name);
1655 }
1656
1657 length += SlCalcConvFileLen(SLE_UINT8); // End-of-list entry.
1658
1659 for (auto &sld : slt) {
1660 if (!SlIsObjectValidInSavegame(sld)) continue;
1661 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
1662 length += SlCalcTableHeader(sld.handler->GetDescription());
1663 }
1664 }
1665
1666 return length;
1667}
1668
1675size_t SlCalcObjLength(const void *object, const SaveLoadTable &slt)
1676{
1677 size_t length = 0;
1678
1679 /* Need to determine the length and write a length tag. */
1680 for (auto &sld : slt) {
1681 length += SlCalcObjMemberLength(object, sld);
1682 }
1683 return length;
1684}
1685
1686size_t SlCalcObjMemberLength(const void *object, const SaveLoad &sld)
1687{
1688 assert(_sl.action == SLA_SAVE);
1689
1690 if (!SlIsObjectValidInSavegame(sld)) return 0;
1691
1692 switch (sld.cmd) {
1693 case SL_VAR: return SlCalcConvFileLen(sld.conv);
1694 case SL_REF: return SlCalcRefLen();
1695 case SL_ARR: return SlCalcArrayLen(sld.length, sld.conv);
1696 case SL_REFLIST: return SlCalcRefListLen(GetVariableAddress(object, sld), sld.conv);
1697 case SL_REFVECTOR: return SlCalcRefVectorLen(GetVariableAddress(object, sld), sld.conv);
1698 case SL_DEQUE: return SlCalcDequeLen(GetVariableAddress(object, sld), sld.conv);
1699 case SL_VECTOR: return SlCalcVectorLen(GetVariableAddress(object, sld), sld.conv);
1700 case SL_STDSTR: return SlCalcStdStringLen(GetVariableAddress(object, sld));
1701 case SL_SAVEBYTE: return 1; // a byte is logically of size 1
1702 case SL_NULL: return SlCalcConvFileLen(sld.conv) * sld.length;
1703
1704 case SL_STRUCT:
1705 case SL_STRUCTLIST: {
1706 NeedLength old_need_length = _sl.need_length;
1707 size_t old_obj_len = _sl.obj_len;
1708
1709 _sl.need_length = NL_CALCLENGTH;
1710 _sl.obj_len = 0;
1711
1712 /* Pretend that we are saving to collect the object size. Other
1713 * means are difficult, as we don't know the length of the list we
1714 * are about to store. */
1715 sld.handler->Save(const_cast<void *>(object));
1716 size_t length = _sl.obj_len;
1717
1718 _sl.obj_len = old_obj_len;
1719 _sl.need_length = old_need_length;
1720
1721 if (sld.cmd == SL_STRUCT) {
1722 length += SlGetArrayLength(1);
1723 }
1724
1725 return length;
1726 }
1727
1728 default: NOT_REACHED();
1729 }
1730 return 0;
1731}
1732
1733static bool SlObjectMember(void *object, const SaveLoad &sld)
1734{
1735 if (!SlIsObjectValidInSavegame(sld)) return false;
1736
1737 VarType conv = GB(sld.conv, 0, 8);
1738 switch (sld.cmd) {
1739 case SL_VAR:
1740 case SL_REF:
1741 case SL_ARR:
1742 case SL_REFLIST:
1743 case SL_REFVECTOR:
1744 case SL_DEQUE:
1745 case SL_VECTOR:
1746 case SL_STDSTR: {
1747 void *ptr = GetVariableAddress(object, sld);
1748
1749 switch (sld.cmd) {
1750 case SL_VAR: SlSaveLoadConv(ptr, conv); break;
1751 case SL_REF: SlSaveLoadRef(ptr, conv); break;
1752 case SL_ARR: SlArray(ptr, sld.length, conv); break;
1753 case SL_REFLIST: SlRefList(ptr, conv); break;
1754 case SL_REFVECTOR: SlRefVector(ptr, conv); break;
1755 case SL_DEQUE: SlDeque(ptr, conv); break;
1756 case SL_VECTOR: SlVector(ptr, conv); break;
1757 case SL_STDSTR: SlStdString(ptr, sld.conv); break;
1758 default: NOT_REACHED();
1759 }
1760 break;
1761 }
1762
1763 /* SL_SAVEBYTE writes a value to the savegame to identify the type of an object.
1764 * When loading, the value is read explicitly with SlReadByte() to determine which
1765 * object description to use. */
1766 case SL_SAVEBYTE: {
1767 void *ptr = GetVariableAddress(object, sld);
1768
1769 switch (_sl.action) {
1770 case SLA_SAVE: SlWriteByte(*(uint8_t *)ptr); break;
1771 case SLA_LOAD_CHECK:
1772 case SLA_LOAD:
1773 case SLA_PTRS:
1774 case SLA_NULL: break;
1775 default: NOT_REACHED();
1776 }
1777 break;
1778 }
1779
1780 case SL_NULL: {
1781 assert(GetVarMemType(sld.conv) == SLE_VAR_NULL);
1782
1783 switch (_sl.action) {
1784 case SLA_LOAD_CHECK:
1785 case SLA_LOAD: SlSkipBytes(SlCalcConvFileLen(sld.conv) * sld.length); break;
1786 case SLA_SAVE: for (int i = 0; i < SlCalcConvFileLen(sld.conv) * sld.length; i++) SlWriteByte(0); break;
1787 case SLA_PTRS:
1788 case SLA_NULL: break;
1789 default: NOT_REACHED();
1790 }
1791 break;
1792 }
1793
1794 case SL_STRUCT:
1795 case SL_STRUCTLIST:
1796 switch (_sl.action) {
1797 case SLA_SAVE: {
1798 if (sld.cmd == SL_STRUCT) {
1799 /* Store in the savegame if this struct was written or not. */
1800 SlSetStructListLength(SlCalcObjMemberLength(object, sld) > SlGetArrayLength(1) ? 1 : 0);
1801 }
1802 sld.handler->Save(object);
1803 break;
1804 }
1805
1806 case SLA_LOAD_CHECK: {
1809 }
1810 sld.handler->LoadCheck(object);
1811 break;
1812 }
1813
1814 case SLA_LOAD: {
1817 }
1818 sld.handler->Load(object);
1819 break;
1820 }
1821
1822 case SLA_PTRS:
1823 sld.handler->FixPointers(object);
1824 break;
1825
1826 case SLA_NULL: break;
1827 default: NOT_REACHED();
1828 }
1829 break;
1830
1831 default: NOT_REACHED();
1832 }
1833 return true;
1834}
1835
1840void SlSetStructListLength(size_t length)
1841{
1842 /* Automatically calculate the length? */
1843 if (_sl.need_length != NL_NONE) {
1844 SlSetLength(SlGetArrayLength(length));
1845 if (_sl.need_length == NL_CALCLENGTH) return;
1846 }
1847
1848 SlWriteArrayLength(length);
1849}
1850
1856size_t SlGetStructListLength(size_t limit)
1857{
1858 size_t length = SlReadArrayLength();
1859 if (length > limit) SlErrorCorrupt("List exceeds storage size");
1860
1861 return length;
1862}
1863
1869void SlObject(void *object, const SaveLoadTable &slt)
1870{
1871 /* Automatically calculate the length? */
1872 if (_sl.need_length != NL_NONE) {
1873 SlSetLength(SlCalcObjLength(object, slt));
1874 if (_sl.need_length == NL_CALCLENGTH) return;
1875 }
1876
1877 for (auto &sld : slt) {
1878 SlObjectMember(object, sld);
1879 }
1880}
1881
1887 void Save(void *) const override
1888 {
1889 NOT_REACHED();
1890 }
1891
1892 void Load(void *object) const override
1893 {
1894 size_t length = SlGetStructListLength(UINT32_MAX);
1895 for (; length > 0; length--) {
1896 SlObject(object, this->GetLoadDescription());
1897 }
1898 }
1899
1900 void LoadCheck(void *object) const override
1901 {
1902 this->Load(object);
1903 }
1904
1906 {
1907 return {};
1908 }
1909
1911 {
1912 NOT_REACHED();
1913 }
1914};
1915
1922std::vector<SaveLoad> SlTableHeader(const SaveLoadTable &slt)
1923{
1924 /* You can only use SlTableHeader if you are a CH_TABLE. */
1925 assert(_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
1926
1927 switch (_sl.action) {
1928 case SLA_LOAD_CHECK:
1929 case SLA_LOAD: {
1930 std::vector<SaveLoad> saveloads;
1931
1932 /* Build a key lookup mapping based on the available fields. */
1933 std::map<std::string, const SaveLoad *> key_lookup;
1934 for (auto &sld : slt) {
1935 if (!SlIsObjectValidInSavegame(sld)) continue;
1936
1937 /* Check that there is only one active SaveLoad for a given name. */
1938 assert(key_lookup.find(sld.name) == key_lookup.end());
1939 key_lookup[sld.name] = &sld;
1940 }
1941
1942 while (true) {
1943 uint8_t type = 0;
1944 SlSaveLoadConv(&type, SLE_UINT8);
1945 if (type == SLE_FILE_END) break;
1946
1947 std::string key;
1948 SlStdString(&key, SLE_STR);
1949
1950 auto sld_it = key_lookup.find(key);
1951 if (sld_it == key_lookup.end()) {
1952 /* SLA_LOADCHECK triggers this debug statement a lot and is perfectly normal. */
1953 Debug(sl, _sl.action == SLA_LOAD ? 2 : 6, "Field '{}' of type 0x{:02x} not found, skipping", key, type);
1954
1955 std::shared_ptr<SaveLoadHandler> handler = nullptr;
1956 SaveLoadType saveload_type;
1957 switch (type & SLE_FILE_TYPE_MASK) {
1958 case SLE_FILE_STRING:
1959 /* Strings are always marked with SLE_FILE_HAS_LENGTH_FIELD, as they are a list of chars. */
1960 saveload_type = SL_STDSTR;
1961 break;
1962
1963 case SLE_FILE_STRUCT:
1964 /* Structs are always marked with SLE_FILE_HAS_LENGTH_FIELD as SL_STRUCT is seen as a list of 0/1 in length. */
1965 saveload_type = SL_STRUCTLIST;
1966 handler = std::make_shared<SlSkipHandler>();
1967 break;
1968
1969 default:
1970 saveload_type = (type & SLE_FILE_HAS_LENGTH_FIELD) ? SL_ARR : SL_VAR;
1971 break;
1972 }
1973
1974 /* We don't know this field, so read to nothing. */
1975 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));
1976 continue;
1977 }
1978
1979 /* Validate the type of the field. If it is changed, the
1980 * savegame should have been bumped so we know how to do the
1981 * conversion. If this error triggers, that clearly didn't
1982 * happen and this is a friendly poke to the developer to bump
1983 * the savegame version and add conversion code. */
1984 uint8_t correct_type = GetSavegameFileType(*sld_it->second);
1985 if (correct_type != type) {
1986 Debug(sl, 1, "Field type for '{}' was expected to be 0x{:02x} but 0x{:02x} was found", key, correct_type, type);
1987 SlErrorCorrupt("Field type is different than expected");
1988 }
1989 saveloads.emplace_back(*sld_it->second);
1990 }
1991
1992 for (auto &sld : saveloads) {
1993 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
1994 sld.handler->load_description = SlTableHeader(sld.handler->GetDescription());
1995 }
1996 }
1997
1998 return saveloads;
1999 }
2000
2001 case SLA_SAVE: {
2002 /* Automatically calculate the length? */
2003 if (_sl.need_length != NL_NONE) {
2005 if (_sl.need_length == NL_CALCLENGTH) break;
2006 }
2007
2008 for (auto &sld : slt) {
2009 if (!SlIsObjectValidInSavegame(sld)) continue;
2010 /* Make sure we are not storing empty keys. */
2011 assert(!sld.name.empty());
2012
2013 uint8_t type = GetSavegameFileType(sld);
2014 assert(type != SLE_FILE_END);
2015
2016 SlSaveLoadConv(&type, SLE_UINT8);
2017 SlStdString(const_cast<std::string *>(&sld.name), SLE_STR);
2018 }
2019
2020 /* Add an end-of-header marker. */
2021 uint8_t type = SLE_FILE_END;
2022 SlSaveLoadConv(&type, SLE_UINT8);
2023
2024 /* After the table, write down any sub-tables we might have. */
2025 for (auto &sld : slt) {
2026 if (!SlIsObjectValidInSavegame(sld)) continue;
2027 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
2028 /* SlCalcTableHeader already looks in sub-lists, so avoid the length being added twice. */
2029 NeedLength old_need_length = _sl.need_length;
2030 _sl.need_length = NL_NONE;
2031
2032 SlTableHeader(sld.handler->GetDescription());
2033
2034 _sl.need_length = old_need_length;
2035 }
2036 }
2037
2038 break;
2039 }
2040
2041 default: NOT_REACHED();
2042 }
2043
2044 return std::vector<SaveLoad>();
2045}
2046
2060std::vector<SaveLoad> SlCompatTableHeader(const SaveLoadTable &slt, const SaveLoadCompatTable &slct)
2061{
2062 assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK);
2063 /* CH_TABLE / CH_SPARSE_TABLE always have a header. */
2064 if (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE) return SlTableHeader(slt);
2065
2066 std::vector<SaveLoad> saveloads;
2067
2068 /* Build a key lookup mapping based on the available fields. */
2069 std::map<std::string, std::vector<const SaveLoad *>> key_lookup;
2070 for (auto &sld : slt) {
2071 /* All entries should have a name; otherwise the entry should just be removed. */
2072 assert(!sld.name.empty());
2073
2074 key_lookup[sld.name].push_back(&sld);
2075 }
2076
2077 for (auto &slc : slct) {
2078 if (slc.name.empty()) {
2079 /* In old savegames there can be data we no longer care for. We
2080 * skip this by simply reading the amount of bytes indicated and
2081 * send those to /dev/null. */
2082 saveloads.emplace_back("", SL_NULL, GetVarFileType(slc.null_type) | SLE_VAR_NULL, slc.null_length, slc.version_from, slc.version_to, nullptr, 0, nullptr);
2083 } else {
2084 auto sld_it = key_lookup.find(slc.name);
2085 /* If this branch triggers, it means that an entry in the
2086 * SaveLoadCompat list is not mentioned in the SaveLoad list. Did
2087 * you rename a field in one and not in the other? */
2088 if (sld_it == key_lookup.end()) {
2089 /* This isn't an assert, as that leaves no information what
2090 * field was to blame. This way at least we have breadcrumbs. */
2091 Debug(sl, 0, "internal error: saveload compatibility field '{}' not found", slc.name);
2092 SlErrorCorrupt("Internal error with savegame compatibility");
2093 }
2094 for (auto &sld : sld_it->second) {
2095 saveloads.push_back(*sld);
2096 }
2097 }
2098 }
2099
2100 for (auto &sld : saveloads) {
2101 if (!SlIsObjectValidInSavegame(sld)) continue;
2102 if (sld.cmd == SL_STRUCTLIST || sld.cmd == SL_STRUCT) {
2103 sld.handler->load_description = SlCompatTableHeader(sld.handler->GetDescription(), sld.handler->GetCompatDescription());
2104 }
2105 }
2106
2107 return saveloads;
2108}
2109
2115{
2116 SlObject(nullptr, slt);
2117}
2118
2124void SlAutolength(AutolengthProc *proc, int arg)
2125{
2126 assert(_sl.action == SLA_SAVE);
2127
2128 /* Tell it to calculate the length */
2129 _sl.need_length = NL_CALCLENGTH;
2130 _sl.obj_len = 0;
2131 proc(arg);
2132
2133 /* Setup length */
2134 _sl.need_length = NL_WANTLENGTH;
2135 SlSetLength(_sl.obj_len);
2136
2137 size_t start_pos = _sl.dumper->GetSize();
2138 size_t expected_offs = start_pos + _sl.obj_len;
2139
2140 /* And write the stuff */
2141 proc(arg);
2142
2143 if (expected_offs != _sl.dumper->GetSize()) {
2144 SlErrorCorruptFmt("Invalid chunk size when writing autolength block, expected {}, got {}", _sl.obj_len, _sl.dumper->GetSize() - start_pos);
2145 }
2146}
2147
2148void ChunkHandler::LoadCheck(size_t len) const
2149{
2150 switch (_sl.block_mode) {
2151 case CH_TABLE:
2152 case CH_SPARSE_TABLE:
2153 SlTableHeader({});
2154 [[fallthrough]];
2155 case CH_ARRAY:
2156 case CH_SPARSE_ARRAY:
2157 SlSkipArray();
2158 break;
2159 case CH_RIFF:
2160 SlSkipBytes(len);
2161 break;
2162 default:
2163 NOT_REACHED();
2164 }
2165}
2166
2171static void SlLoadChunk(const ChunkHandler &ch)
2172{
2173 uint8_t m = SlReadByte();
2174
2175 _sl.block_mode = m & CH_TYPE_MASK;
2176 _sl.obj_len = 0;
2177 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2178
2179 /* The header should always be at the start. Read the length; the
2180 * Load() should as first action process the header. */
2181 if (_sl.expect_table_header) {
2182 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2183 }
2184
2185 switch (_sl.block_mode) {
2186 case CH_TABLE:
2187 case CH_ARRAY:
2188 _sl.array_index = 0;
2189 ch.Load();
2190 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2191 break;
2192 case CH_SPARSE_TABLE:
2193 case CH_SPARSE_ARRAY:
2194 ch.Load();
2195 if (_next_offs != 0) SlErrorCorrupt("Invalid array length");
2196 break;
2197 case CH_RIFF: {
2198 /* Read length */
2199 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2200 len += SlReadUint16();
2201 _sl.obj_len = len;
2202 size_t start_pos = _sl.reader->GetSize();
2203 size_t endoffs = start_pos + len;
2204 ch.Load();
2205
2206 if (_sl.reader->GetSize() != endoffs) {
2207 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2208 }
2209 break;
2210 }
2211 default:
2212 SlErrorCorrupt("Invalid chunk type");
2213 break;
2214 }
2215
2216 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2217}
2218
2224static void SlLoadCheckChunk(const ChunkHandler &ch)
2225{
2226 uint8_t m = SlReadByte();
2227
2228 _sl.block_mode = m & CH_TYPE_MASK;
2229 _sl.obj_len = 0;
2230 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2231
2232 /* The header should always be at the start. Read the length; the
2233 * LoadCheck() should as first action process the header. */
2234 if (_sl.expect_table_header) {
2235 if (SlIterateArray() != INT32_MAX) SlErrorCorrupt("Table chunk without header");
2236 }
2237
2238 switch (_sl.block_mode) {
2239 case CH_TABLE:
2240 case CH_ARRAY:
2241 _sl.array_index = 0;
2242 ch.LoadCheck();
2243 break;
2244 case CH_SPARSE_TABLE:
2245 case CH_SPARSE_ARRAY:
2246 ch.LoadCheck();
2247 break;
2248 case CH_RIFF: {
2249 /* Read length */
2250 size_t len = (SlReadByte() << 16) | ((m >> 4) << 24);
2251 len += SlReadUint16();
2252 _sl.obj_len = len;
2253 size_t start_pos = _sl.reader->GetSize();
2254 size_t endoffs = start_pos + len;
2255 ch.LoadCheck(len);
2256
2257 if (_sl.reader->GetSize() != endoffs) {
2258 SlErrorCorruptFmt("Invalid chunk size in RIFF in {} - expected {}, got {}", ch.GetName(), len, _sl.reader->GetSize() - start_pos);
2259 }
2260 break;
2261 }
2262 default:
2263 SlErrorCorrupt("Invalid chunk type");
2264 break;
2265 }
2266
2267 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2268}
2269
2275static void SlSaveChunk(const ChunkHandler &ch)
2276{
2277 if (ch.type == CH_READONLY) return;
2278
2279 SlWriteUint32(ch.id);
2280 Debug(sl, 2, "Saving chunk {}", ch.GetName());
2281
2282 _sl.block_mode = ch.type;
2283 _sl.expect_table_header = (_sl.block_mode == CH_TABLE || _sl.block_mode == CH_SPARSE_TABLE);
2284
2285 _sl.need_length = (_sl.expect_table_header || _sl.block_mode == CH_RIFF) ? NL_WANTLENGTH : NL_NONE;
2286
2287 switch (_sl.block_mode) {
2288 case CH_RIFF:
2289 ch.Save();
2290 break;
2291 case CH_TABLE:
2292 case CH_ARRAY:
2293 _sl.last_array_index = 0;
2294 SlWriteByte(_sl.block_mode);
2295 ch.Save();
2296 SlWriteArrayLength(0); // Terminate arrays
2297 break;
2298 case CH_SPARSE_TABLE:
2299 case CH_SPARSE_ARRAY:
2300 SlWriteByte(_sl.block_mode);
2301 ch.Save();
2302 SlWriteArrayLength(0); // Terminate arrays
2303 break;
2304 default: NOT_REACHED();
2305 }
2306
2307 if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
2308}
2309
2311static void SlSaveChunks()
2312{
2313 for (auto &ch : ChunkHandlers()) {
2314 SlSaveChunk(ch);
2315 }
2316
2317 /* Terminator */
2318 SlWriteUint32(0);
2319}
2320
2327static const ChunkHandler *SlFindChunkHandler(uint32_t id)
2328{
2329 for (const ChunkHandler &ch : ChunkHandlers()) if (ch.id == id) return &ch;
2330 return nullptr;
2331}
2332
2334static void SlLoadChunks()
2335{
2336 uint32_t id;
2337 const ChunkHandler *ch;
2338
2339 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2340 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2341
2342 ch = SlFindChunkHandler(id);
2343 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2344 SlLoadChunk(*ch);
2345 }
2346}
2347
2350{
2351 uint32_t id;
2352 const ChunkHandler *ch;
2353
2354 for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
2355 Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
2356
2357 ch = SlFindChunkHandler(id);
2358 if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
2359 SlLoadCheckChunk(*ch);
2360 }
2361}
2362
2364static void SlFixPointers()
2365{
2366 _sl.action = SLA_PTRS;
2367
2368 for (const ChunkHandler &ch : ChunkHandlers()) {
2369 Debug(sl, 3, "Fixing pointers for {}", ch.GetName());
2370 ch.FixPointers();
2371 }
2372
2373 assert(_sl.action == SLA_PTRS);
2374}
2375
2376
2379 std::optional<FileHandle> file;
2380 long begin;
2381
2386 FileReader(FileHandle &&file) : LoadFilter(nullptr), file(std::move(file)), begin(ftell(*this->file))
2387 {
2388 }
2389
2391 ~FileReader() override
2392 {
2393 if (this->file.has_value()) {
2394 _game_session_stats.savegame_size = ftell(*this->file) - this->begin;
2395 }
2396 }
2397
2398 size_t Read(uint8_t *buf, size_t size) override
2399 {
2400 /* We're in the process of shutting down, i.e. in "failure" mode. */
2401 if (!this->file.has_value()) return 0;
2402
2403 return fread(buf, 1, size, *this->file);
2404 }
2405
2406 void Reset() override
2407 {
2408 clearerr(*this->file);
2409 if (fseek(*this->file, this->begin, SEEK_SET)) {
2410 Debug(sl, 1, "Could not reset the file reading");
2411 }
2412 }
2413};
2414
2417 std::optional<FileHandle> file;
2418
2423 FileWriter(FileHandle &&file) : SaveFilter(nullptr), file(std::move(file))
2424 {
2425 }
2426
2428 ~FileWriter() override
2429 {
2430 this->Finish();
2431 }
2432
2433 void Write(uint8_t *buf, size_t size) override
2434 {
2435 /* We're in the process of shutting down, i.e. in "failure" mode. */
2436 if (!this->file.has_value()) return;
2437
2438 if (fwrite(buf, 1, size, *this->file) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE);
2439 }
2440
2441 void Finish() override
2442 {
2443 if (this->file.has_value()) {
2444 _game_session_stats.savegame_size = ftell(*this->file);
2445 this->file.reset();
2446 }
2447 }
2448};
2449
2450/*******************************************
2451 ********** START OF LZO CODE **************
2452 *******************************************/
2453
2454#ifdef WITH_LZO
2455
2457static const uint LZO_BUFFER_SIZE = 8192;
2458
2465 LZOLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2466 {
2467 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2468 }
2469
2470 size_t Read(uint8_t *buf, size_t ssize) override
2471 {
2472 assert(ssize >= LZO_BUFFER_SIZE);
2473
2474 /* Buffer size is from the LZO docs plus the chunk header size. */
2475 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2476 uint32_t tmp[2];
2477 uint32_t size;
2478 lzo_uint len = ssize;
2479
2480 /* Read header*/
2481 if (this->chain->Read((uint8_t*)tmp, sizeof(tmp)) != sizeof(tmp)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE, "File read failed");
2482
2483 /* Check if size is bad */
2484 ((uint32_t*)out)[0] = size = tmp[1];
2485
2486 if (_sl_version != SL_MIN_VERSION) {
2487 tmp[0] = TO_BE32(tmp[0]);
2488 size = TO_BE32(size);
2489 }
2490
2491 if (size >= sizeof(out)) SlErrorCorrupt("Inconsistent size");
2492
2493 /* Read block */
2494 if (this->chain->Read(out + sizeof(uint32_t), size) != size) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2495
2496 /* Verify checksum */
2497 if (tmp[0] != lzo_adler32(0, out, size + sizeof(uint32_t))) SlErrorCorrupt("Bad checksum");
2498
2499 /* Decompress */
2500 int ret = lzo1x_decompress_safe(out + sizeof(uint32_t) * 1, size, buf, &len, nullptr);
2501 if (ret != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
2502 return len;
2503 }
2504};
2505
2512 LZOSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2513 {
2514 if (lzo_init() != LZO_E_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2515 }
2516
2517 void Write(uint8_t *buf, size_t size) override
2518 {
2519 const lzo_bytep in = buf;
2520 /* Buffer size is from the LZO docs plus the chunk header size. */
2521 uint8_t out[LZO_BUFFER_SIZE + LZO_BUFFER_SIZE / 16 + 64 + 3 + sizeof(uint32_t) * 2];
2522 uint8_t wrkmem[LZO1X_1_MEM_COMPRESS];
2523 lzo_uint outlen;
2524
2525 do {
2526 /* Compress up to LZO_BUFFER_SIZE bytes at once. */
2527 lzo_uint len = size > LZO_BUFFER_SIZE ? LZO_BUFFER_SIZE : (lzo_uint)size;
2528 lzo1x_1_compress(in, len, out + sizeof(uint32_t) * 2, &outlen, wrkmem);
2529 ((uint32_t*)out)[1] = TO_BE32((uint32_t)outlen);
2530 ((uint32_t*)out)[0] = TO_BE32(lzo_adler32(0, out + sizeof(uint32_t), outlen + sizeof(uint32_t)));
2531 this->chain->Write(out, outlen + sizeof(uint32_t) * 2);
2532
2533 /* Move to next data chunk. */
2534 size -= len;
2535 in += len;
2536 } while (size > 0);
2537 }
2538};
2539
2540#endif /* WITH_LZO */
2541
2542/*********************************************
2543 ******** START OF NOCOMP CODE (uncompressed)*
2544 *********************************************/
2545
2552 NoCompLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2553 {
2554 }
2555
2556 size_t Read(uint8_t *buf, size_t size) override
2557 {
2558 return this->chain->Read(buf, size);
2559 }
2560};
2561
2568 NoCompSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t) : SaveFilter(std::move(chain))
2569 {
2570 }
2571
2572 void Write(uint8_t *buf, size_t size) override
2573 {
2574 this->chain->Write(buf, size);
2575 }
2576};
2577
2578/********************************************
2579 ********** START OF ZLIB CODE **************
2580 ********************************************/
2581
2582#if defined(WITH_ZLIB)
2583
2586 z_stream z{};
2588
2593 ZlibLoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain))
2594 {
2595 if (inflateInit(&this->z) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2596 }
2597
2600 {
2601 inflateEnd(&this->z);
2602 }
2603
2604 size_t Read(uint8_t *buf, size_t size) override
2605 {
2606 this->z.next_out = buf;
2607 this->z.avail_out = (uint)size;
2608
2609 do {
2610 /* read more bytes from the file? */
2611 if (this->z.avail_in == 0) {
2612 this->z.next_in = this->fread_buf;
2613 this->z.avail_in = (uint)this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2614 }
2615
2616 /* inflate the data */
2617 int r = inflate(&this->z, 0);
2618 if (r == Z_STREAM_END) break;
2619
2620 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "inflate() failed");
2621 } while (this->z.avail_out != 0);
2622
2623 return size - this->z.avail_out;
2624 }
2625};
2626
2629 z_stream z{};
2631
2637 ZlibSaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain))
2638 {
2639 if (deflateInit(&this->z, compression_level) != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2640 }
2641
2644 {
2645 deflateEnd(&this->z);
2646 }
2647
2654 void WriteLoop(uint8_t *p, size_t len, int mode)
2655 {
2656 uint n;
2657 this->z.next_in = p;
2658 this->z.avail_in = (uInt)len;
2659 do {
2660 this->z.next_out = this->fwrite_buf;
2661 this->z.avail_out = sizeof(this->fwrite_buf);
2662
2670 int r = deflate(&this->z, mode);
2671
2672 /* bytes were emitted? */
2673 if ((n = sizeof(this->fwrite_buf) - this->z.avail_out) != 0) {
2674 this->chain->Write(this->fwrite_buf, n);
2675 }
2676 if (r == Z_STREAM_END) break;
2677
2678 if (r != Z_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "zlib returned error code");
2679 } while (this->z.avail_in || !this->z.avail_out);
2680 }
2681
2682 void Write(uint8_t *buf, size_t size) override
2683 {
2684 this->WriteLoop(buf, size, 0);
2685 }
2686
2687 void Finish() override
2688 {
2689 this->WriteLoop(nullptr, 0, Z_FINISH);
2690 this->chain->Finish();
2691 }
2692};
2693
2694#endif /* WITH_ZLIB */
2695
2696/********************************************
2697 ********** START OF LZMA CODE **************
2698 ********************************************/
2699
2700#if defined(WITH_LIBLZMA)
2701
2708static const lzma_stream _lzma_init = LZMA_STREAM_INIT;
2709
2712 lzma_stream lzma;
2714
2719 LZMALoadFilter(std::shared_ptr<LoadFilter> chain) : LoadFilter(std::move(chain)), lzma(_lzma_init)
2720 {
2721 /* Allow saves up to 256 MB uncompressed */
2722 if (lzma_auto_decoder(&this->lzma, 1 << 28, 0) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize decompressor");
2723 }
2724
2727 {
2728 lzma_end(&this->lzma);
2729 }
2730
2731 size_t Read(uint8_t *buf, size_t size) override
2732 {
2733 this->lzma.next_out = buf;
2734 this->lzma.avail_out = size;
2735
2736 do {
2737 /* read more bytes from the file? */
2738 if (this->lzma.avail_in == 0) {
2739 this->lzma.next_in = this->fread_buf;
2740 this->lzma.avail_in = this->chain->Read(this->fread_buf, sizeof(this->fread_buf));
2741 }
2742
2743 /* inflate the data */
2744 lzma_ret r = lzma_code(&this->lzma, LZMA_RUN);
2745 if (r == LZMA_STREAM_END) break;
2746 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2747 } while (this->lzma.avail_out != 0);
2748
2749 return size - this->lzma.avail_out;
2750 }
2751};
2752
2755 lzma_stream lzma;
2757
2763 LZMASaveFilter(std::shared_ptr<SaveFilter> chain, uint8_t compression_level) : SaveFilter(std::move(chain)), lzma(_lzma_init)
2764 {
2765 if (lzma_easy_encoder(&this->lzma, compression_level, LZMA_CHECK_CRC32) != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "cannot initialize compressor");
2766 }
2767
2770 {
2771 lzma_end(&this->lzma);
2772 }
2773
2780 void WriteLoop(uint8_t *p, size_t len, lzma_action action)
2781 {
2782 size_t n;
2783 this->lzma.next_in = p;
2784 this->lzma.avail_in = len;
2785 do {
2786 this->lzma.next_out = this->fwrite_buf;
2787 this->lzma.avail_out = sizeof(this->fwrite_buf);
2788
2789 lzma_ret r = lzma_code(&this->lzma, action);
2790
2791 /* bytes were emitted? */
2792 if ((n = sizeof(this->fwrite_buf) - this->lzma.avail_out) != 0) {
2793 this->chain->Write(this->fwrite_buf, n);
2794 }
2795 if (r == LZMA_STREAM_END) break;
2796 if (r != LZMA_OK) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "liblzma returned error code");
2797 } while (this->lzma.avail_in || !this->lzma.avail_out);
2798 }
2799
2800 void Write(uint8_t *buf, size_t size) override
2801 {
2802 this->WriteLoop(buf, size, LZMA_RUN);
2803 }
2804
2805 void Finish() override
2806 {
2807 this->WriteLoop(nullptr, 0, LZMA_FINISH);
2808 this->chain->Finish();
2809 }
2810};
2811
2812#endif /* WITH_LIBLZMA */
2813
2814/*******************************************
2815 ************* END OF CODE *****************
2816 *******************************************/
2817
2820 std::shared_ptr<LoadFilter> (*init_load)(std::shared_ptr<LoadFilter> chain);
2821 std::shared_ptr<SaveFilter> (*init_write)(std::shared_ptr<SaveFilter> chain, uint8_t compression);
2822
2823 std::string_view name;
2824 uint32_t tag;
2825
2829};
2830
2831static const uint32_t SAVEGAME_TAG_LZO = TO_BE32('OTTD');
2832static const uint32_t SAVEGAME_TAG_NONE = TO_BE32('OTTN');
2833static const uint32_t SAVEGAME_TAG_ZLIB = TO_BE32('OTTZ');
2834static const uint32_t SAVEGAME_TAG_LZMA = TO_BE32('OTTX');
2835
2838#if defined(WITH_LZO)
2839 /* Roughly 75% larger than zlib level 6 at only ~7% of the CPU usage. */
2840 {CreateLoadFilter<LZOLoadFilter>, CreateSaveFilter<LZOSaveFilter>, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2841#else
2842 {nullptr, nullptr, "lzo", SAVEGAME_TAG_LZO, 0, 0, 0},
2843#endif
2844 /* Roughly 5 times larger at only 1% of the CPU usage over zlib level 6. */
2845 {CreateLoadFilter<NoCompLoadFilter>, CreateSaveFilter<NoCompSaveFilter>, "none", SAVEGAME_TAG_NONE, 0, 0, 0},
2846#if defined(WITH_ZLIB)
2847 /* After level 6 the speed reduction is significant (1.5x to 2.5x slower per level), but the reduction in filesize is
2848 * fairly insignificant (~1% for each step). Lower levels become ~5-10% bigger by each level than level 6 while level
2849 * 1 is "only" 3 times as fast. Level 0 results in uncompressed savegames at about 8 times the cost of "none". */
2850 {CreateLoadFilter<ZlibLoadFilter>, CreateSaveFilter<ZlibSaveFilter>, "zlib", SAVEGAME_TAG_ZLIB, 0, 6, 9},
2851#else
2852 {nullptr, nullptr, "zlib", SAVEGAME_TAG_ZLIB, 0, 0, 0},
2853#endif
2854#if defined(WITH_LIBLZMA)
2855 /* Level 2 compression is speed wise as fast as zlib level 6 compression (old default), but results in ~10% smaller saves.
2856 * Higher compression levels are possible, and might improve savegame size by up to 25%, but are also up to 10 times slower.
2857 * The next significant reduction in file size is at level 4, but that is already 4 times slower. Level 3 is primarily 50%
2858 * slower while not improving the filesize, while level 0 and 1 are faster, but don't reduce savegame size much.
2859 * It's OTTX and not e.g. OTTL because liblzma is part of xz-utils and .tar.xz is preferred over .tar.lzma. */
2860 {CreateLoadFilter<LZMALoadFilter>, CreateSaveFilter<LZMASaveFilter>, "lzma", SAVEGAME_TAG_LZMA, 0, 2, 9},
2861#else
2862 {nullptr, nullptr, "lzma", SAVEGAME_TAG_LZMA, 0, 0, 0},
2863#endif
2864};
2865
2872static std::pair<const SaveLoadFormat &, uint8_t> GetSavegameFormat(std::string_view full_name)
2873{
2874 /* Find default savegame format, the highest one with which files can be written. */
2875 auto it = std::find_if(std::rbegin(_saveload_formats), std::rend(_saveload_formats), [](const auto &slf) { return slf.init_write != nullptr; });
2876 if (it == std::rend(_saveload_formats)) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, "no writeable savegame formats");
2877
2878 const SaveLoadFormat &def = *it;
2879
2880 if (!full_name.empty()) {
2881 /* Get the ":..." of the compression level out of the way */
2882 size_t separator = full_name.find(':');
2883 bool has_comp_level = separator != std::string::npos;
2884 std::string_view name = has_comp_level ? full_name.substr(0, separator) : full_name;
2885
2886 for (const auto &slf : _saveload_formats) {
2887 if (slf.init_write != nullptr && name == slf.name) {
2888 if (has_comp_level) {
2889 auto complevel = full_name.substr(separator + 1);
2890
2891 /* Get the level and determine whether all went fine. */
2892 auto level = ParseInteger<uint8_t>(complevel);
2893 if (!level.has_value() || *level != Clamp(*level, slf.min_compression, slf.max_compression)) {
2895 GetEncodedString(STR_CONFIG_ERROR),
2896 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_LEVEL, complevel),
2897 WL_CRITICAL);
2898 } else {
2899 return {slf, *level};
2900 }
2901 }
2902 return {slf, slf.default_compression};
2903 }
2904 }
2905
2907 GetEncodedString(STR_CONFIG_ERROR),
2908 GetEncodedString(STR_CONFIG_ERROR_INVALID_SAVEGAME_COMPRESSION_ALGORITHM, name, def.name),
2909 WL_CRITICAL);
2910 }
2911 return {def, def.default_compression};
2912}
2913
2914/* actual loader/saver function */
2915void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings);
2916extern bool AfterLoadGame();
2917extern bool LoadOldSaveGame(std::string_view file);
2918
2924static void ResetSettings()
2925{
2926 for (auto &desc : GetSaveLoadSettingTable()) {
2927 const SettingDesc *sd = GetSettingDesc(desc);
2928 if (sd->flags.Test(SettingFlag::NotInSave)) continue;
2930
2932 }
2933}
2934
2935extern void ClearOldOrders();
2936
2941{
2943 ResetTempEngineData();
2944 ClearRailTypeLabelList();
2945 ClearRoadTypeLabelList();
2946 ResetOldWaypoints();
2947 ResetSettings();
2948}
2949
2953static inline void ClearSaveLoadState()
2954{
2955 _sl.dumper = nullptr;
2956 _sl.sf = nullptr;
2957 _sl.reader = nullptr;
2958 _sl.lf = nullptr;
2959}
2960
2962static void SaveFileStart()
2963{
2964 SetMouseCursorBusy(true);
2965
2967 _sl.saveinprogress = true;
2968}
2969
2971static void SaveFileDone()
2972{
2973 SetMouseCursorBusy(false);
2974
2976 _sl.saveinprogress = false;
2977
2978#ifdef __EMSCRIPTEN__
2979 EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs());
2980#endif
2981}
2982
2988{
2989 _sl.error_str = str;
2990}
2991
2997{
2998 return GetEncodedString(_sl.action == SLA_SAVE ? STR_ERROR_GAME_SAVE_FAILED : STR_ERROR_GAME_LOAD_FAILED);
2999}
3000
3006{
3007 return GetEncodedString(_sl.error_str, _sl.extra_msg);
3008}
3009
3016
3024{
3025 try {
3026 auto [fmt, compression] = GetSavegameFormat(_savegame_format);
3027
3028 /* We have written our stuff to memory, now write it to file! */
3029 uint32_t hdr[2] = { fmt.tag, TO_BE32(SAVEGAME_VERSION << 16) };
3030 _sl.sf->Write((uint8_t*)hdr, sizeof(hdr));
3031
3032 _sl.sf = fmt.init_write(_sl.sf, compression);
3033 _sl.dumper->Flush(_sl.sf);
3034
3036
3037 if (threaded) SetAsyncSaveFinish(SaveFileDone);
3038
3039 return SL_OK;
3040 } catch (...) {
3042
3044
3045 /* We don't want to shout when saving is just
3046 * cancelled due to a client disconnecting. */
3047 if (_sl.error_str != STR_NETWORK_ERROR_LOSTCONNECTION) {
3048 /* Skip the "colour" character */
3049 Debug(sl, 0, "{}", GetSaveLoadErrorType().GetDecodedString().substr(3) + GetSaveLoadErrorMessage().GetDecodedString());
3050 asfp = SaveFileError;
3051 }
3052
3053 if (threaded) {
3054 SetAsyncSaveFinish(asfp);
3055 } else {
3056 asfp();
3057 }
3058 return SL_ERROR;
3059 }
3060}
3061
3062void WaitTillSaved()
3063{
3064 if (!_save_thread.joinable()) return;
3065
3066 _save_thread.join();
3067
3068 /* Make sure every other state is handled properly as well. */
3070}
3071
3080static SaveOrLoadResult DoSave(std::shared_ptr<SaveFilter> writer, bool threaded)
3081{
3082 assert(!_sl.saveinprogress);
3083
3084 _sl.dumper = std::make_unique<MemoryDumper>();
3085 _sl.sf = std::move(writer);
3086
3088
3089 SaveViewportBeforeSaveGame();
3090 SlSaveChunks();
3091
3092 SaveFileStart();
3093
3094 if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) {
3095 if (threaded) Debug(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode...");
3096
3097 SaveOrLoadResult result = SaveFileToDisk(false);
3098 SaveFileDone();
3099
3100 return result;
3101 }
3102
3103 return SL_OK;
3104}
3105
3112SaveOrLoadResult SaveWithFilter(std::shared_ptr<SaveFilter> writer, bool threaded)
3113{
3114 try {
3115 _sl.action = SLA_SAVE;
3116 return DoSave(std::move(writer), threaded);
3117 } catch (...) {
3119 return SL_ERROR;
3120 }
3121}
3122
3131static const SaveLoadFormat *DetermineSaveLoadFormat(uint32_t tag, uint32_t raw_version)
3132{
3133 auto fmt = std::ranges::find(_saveload_formats, tag, &SaveLoadFormat::tag);
3134 if (fmt != std::end(_saveload_formats)) {
3135 /* Check version number */
3136 _sl_version = (SaveLoadVersion)(TO_BE32(raw_version) >> 16);
3137 /* Minor is not used anymore from version 18.0, but it is still needed
3138 * in versions before that (4 cases) which can't be removed easy.
3139 * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
3140 _sl_minor_version = (TO_BE32(raw_version) >> 8) & 0xFF;
3141
3142 Debug(sl, 1, "Loading savegame version {}", _sl_version);
3143
3144 /* Is the version higher than the current? */
3145 if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
3146 if (_sl_version >= SLV_START_PATCHPACKS && _sl_version <= SLV_END_PATCHPACKS) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK);
3147 return fmt;
3148 }
3149
3150 Debug(sl, 0, "Unknown savegame type, trying to load it as the buggy format");
3151 _sl.lf->Reset();
3154
3155 /* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
3156 fmt = std::ranges::find(_saveload_formats, SAVEGAME_TAG_LZO, &SaveLoadFormat::tag);
3157 if (fmt == std::end(_saveload_formats)) {
3158 /* Who removed the LZO savegame format definition? When built without LZO support,
3159 * the formats must still list it just without a method to read the file.
3160 * The caller of this function has to check for the existence of load function. */
3161 NOT_REACHED();
3162 }
3163 return fmt;
3164}
3165
3172static SaveOrLoadResult DoLoad(std::shared_ptr<LoadFilter> reader, bool load_check)
3173{
3174 _sl.lf = std::move(reader);
3175
3176 if (load_check) {
3177 /* Clear previous check data */
3178 _load_check_data.Clear();
3179 /* Mark SL_LOAD_CHECK as supported for this savegame. */
3180 _load_check_data.checkable = true;
3181 }
3182
3183 uint32_t hdr[2];
3184 if (_sl.lf->Read((uint8_t*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3185
3186 /* see if we have any loader for this type. */
3187 const SaveLoadFormat *fmt = DetermineSaveLoadFormat(hdr[0], hdr[1]);
3188
3189 /* loader for this savegame type is not implemented? */
3190 if (fmt->init_load == nullptr) {
3191 SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, fmt::format("Loader for '{}' is not available.", fmt->name));
3192 }
3193
3194 _sl.lf = fmt->init_load(_sl.lf);
3195 _sl.reader = std::make_unique<ReadBuffer>(_sl.lf);
3196 _next_offs = 0;
3197
3198 if (!load_check) {
3200
3201 /* Old maps were hardcoded to 256x256 and thus did not contain
3202 * any mapsize information. Pre-initialize to 256x256 to not to
3203 * confuse old games */
3204 InitializeGame(256, 256, true, true);
3205
3206 _gamelog.Reset();
3207
3209 /*
3210 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
3211 * shared savegame version 4. Anything before that 'obviously'
3212 * does not have any NewGRFs. Between the introduction and
3213 * savegame version 41 (just before 0.5) the NewGRF settings
3214 * were not stored in the savegame and they were loaded by
3215 * using the settings from the main menu.
3216 * So, to recap:
3217 * - savegame version < 4: do not load any NewGRFs.
3218 * - savegame version >= 41: load NewGRFs from savegame, which is
3219 * already done at this stage by
3220 * overwriting the main menu settings.
3221 * - other savegame versions: use main menu settings.
3222 *
3223 * This means that users *can* crash savegame version 4..40
3224 * savegames if they set incompatible NewGRFs in the main menu,
3225 * but can't crash anymore for savegame version < 4 savegames.
3226 *
3227 * Note: this is done here because AfterLoadGame is also called
3228 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
3229 */
3231 }
3232 }
3233
3234 if (load_check) {
3235 /* Load chunks into _load_check_data.
3236 * No pools are loaded. References are not possible, and thus do not need resolving. */
3238 } else {
3239 /* Load chunks and resolve references */
3240 SlLoadChunks();
3241 SlFixPointers();
3242 }
3243
3245
3247
3248 if (load_check) {
3249 /* The only part from AfterLoadGame() we need */
3250 _load_check_data.grf_compatibility = IsGoodGRFConfigList(_load_check_data.grfconfig);
3251 } else {
3252 _gamelog.StartAction(GLAT_LOAD);
3253
3254 /* After loading fix up savegame for any internal changes that
3255 * might have occurred since then. If it fails, load back the old game. */
3256 if (!AfterLoadGame()) {
3257 _gamelog.StopAction();
3258 return SL_REINIT;
3259 }
3260
3261 _gamelog.StopAction();
3262 }
3263
3264 return SL_OK;
3265}
3266
3272SaveOrLoadResult LoadWithFilter(std::shared_ptr<LoadFilter> reader)
3273{
3274 try {
3275 _sl.action = SLA_LOAD;
3276 return DoLoad(std::move(reader), false);
3277 } catch (...) {
3279 return SL_REINIT;
3280 }
3281}
3282
3293SaveOrLoadResult SaveOrLoad(std::string_view filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
3294{
3295 /* An instance of saving is already active, so don't go saving again */
3296 if (_sl.saveinprogress && fop == SLO_SAVE && dft == DFT_GAME_FILE && threaded) {
3297 /* if not an autosave, but a user action, show error message */
3298 if (!_do_autosave) ShowErrorMessage(GetEncodedString(STR_ERROR_SAVE_STILL_IN_PROGRESS), {}, WL_ERROR);
3299 return SL_OK;
3300 }
3301 WaitTillSaved();
3302
3303 try {
3304 /* Load a TTDLX or TTDPatch game */
3305 if (fop == SLO_LOAD && dft == DFT_OLD_GAME_FILE) {
3307
3308 InitializeGame(256, 256, true, true); // set a mapsize of 256x256 for TTDPatch games or it might get confused
3309
3310 /* TTD/TTO savegames have no NewGRFs, TTDP savegame have them
3311 * and if so a new NewGRF list will be made in LoadOldSaveGame.
3312 * Note: this is done here because AfterLoadGame is also called
3313 * for OTTD savegames which have their own NewGRF logic. */
3315 _gamelog.Reset();
3316 if (!LoadOldSaveGame(filename)) return SL_REINIT;
3319 _gamelog.StartAction(GLAT_LOAD);
3320 if (!AfterLoadGame()) {
3321 _gamelog.StopAction();
3322 return SL_REINIT;
3323 }
3324 _gamelog.StopAction();
3325 return SL_OK;
3326 }
3327
3328 assert(dft == DFT_GAME_FILE);
3329 switch (fop) {
3330 case SLO_CHECK:
3331 _sl.action = SLA_LOAD_CHECK;
3332 break;
3333
3334 case SLO_LOAD:
3335 _sl.action = SLA_LOAD;
3336 break;
3337
3338 case SLO_SAVE:
3339 _sl.action = SLA_SAVE;
3340 break;
3341
3342 default: NOT_REACHED();
3343 }
3344
3345 auto fh = (fop == SLO_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
3346
3347 /* Make it a little easier to load savegames from the console */
3348 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SAVE_DIR);
3349 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", BASE_DIR);
3350 if (!fh.has_value() && fop != SLO_SAVE) fh = FioFOpenFile(filename, "rb", SCENARIO_DIR);
3351
3352 if (!fh.has_value()) {
3353 SlError(fop == SLO_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
3354 }
3355
3356 if (fop == SLO_SAVE) { // SAVE game
3357 Debug(desync, 1, "save: {:08x}; {:02x}; {}", TimerGameEconomy::date, TimerGameEconomy::date_fract, filename);
3358 if (!_settings_client.gui.threaded_saves) threaded = false;
3359
3360 return DoSave(std::make_shared<FileWriter>(std::move(*fh)), threaded);
3361 }
3362
3363 /* LOAD game */
3364 assert(fop == SLO_LOAD || fop == SLO_CHECK);
3365 Debug(desync, 1, "load: {}", filename);
3366 return DoLoad(std::make_shared<FileReader>(std::move(*fh)), fop == SLO_CHECK);
3367 } catch (...) {
3368 /* This code may be executed both for old and new save games. */
3370
3371 /* Skip the "colour" character */
3372 if (fop != SLO_CHECK) Debug(sl, 0, "{}", GetSaveLoadErrorType().GetDecodedString().substr(3) + GetSaveLoadErrorMessage().GetDecodedString());
3373
3374 /* A saver/loader exception!! reinitialize all variables to prevent crash! */
3375 return (fop == SLO_LOAD) ? SL_REINIT : SL_ERROR;
3376 }
3377}
3378
3384{
3385 std::string filename;
3386
3387 if (_settings_client.gui.keep_all_autosave) {
3388 filename = GenerateDefaultSaveName() + counter.Extension();
3389 } else {
3390 filename = counter.Filename();
3391 }
3392
3393 Debug(sl, 2, "Autosaving to '{}'", filename);
3394 if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR) != SL_OK) {
3395 ShowErrorMessage(GetEncodedString(STR_ERROR_AUTOSAVE_FAILED), {}, WL_ERROR);
3396 }
3397}
3398
3399
3402{
3404}
3405
3411{
3412 /* Check if we have a name for this map, which is the name of the first
3413 * available company. When there's no company available we'll use
3414 * 'Spectator' as "company" name. */
3415 CompanyID cid = _local_company;
3416 if (!Company::IsValidID(cid)) {
3417 for (const Company *c : Company::Iterate()) {
3418 cid = c->index;
3419 break;
3420 }
3421 }
3422
3423 std::array<StringParameter, 4> params{};
3424 auto it = params.begin();
3425 *it++ = cid;
3426
3427 /* We show the current game time differently depending on the timekeeping units used by this game. */
3429 /* Insert time played. */
3430 const auto play_time = TimerGameTick::counter / Ticks::TICKS_PER_SECOND;
3431 *it++ = STR_SAVEGAME_DURATION_REALTIME;
3432 *it++ = play_time / 60 / 60;
3433 *it++ = (play_time / 60) % 60;
3434 } else {
3435 /* Insert current date */
3436 switch (_settings_client.gui.date_format_in_default_names) {
3437 case 0: *it++ = STR_JUST_DATE_LONG; break;
3438 case 1: *it++ = STR_JUST_DATE_TINY; break;
3439 case 2: *it++ = STR_JUST_DATE_ISO; break;
3440 default: NOT_REACHED();
3441 }
3442 *it++ = TimerGameEconomy::date;
3443 }
3444
3445 /* Get the correct string (special string for when there's not company) */
3446 std::string filename = GetStringWithArgs(!Company::IsValidID(cid) ? STR_SAVEGAME_NAME_SPECTATOR : STR_SAVEGAME_NAME_DEFAULT, params);
3447 SanitizeFilename(filename);
3448 return filename;
3449}
3450
3457{
3458 if (ft.abstract == FT_INVALID || ft.abstract == FT_NONE) {
3459 this->file_op = SLO_INVALID;
3460 this->ftype = FIOS_TYPE_INVALID;
3461 return;
3462 }
3463
3464 this->file_op = fop;
3465 this->ftype = ft;
3466}
3467
3473{
3474 this->SetMode(item.type);
3475 this->name = item.name;
3476 this->title = item.title;
3477}
3478
3480{
3481 assert(this->load_description.has_value());
3482 return *this->load_description;
3483}
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:539
std::optional< std::vector< SaveLoad > > load_description
Description derived from savegame being loaded.
Definition saveload.h:541
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:1011
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:67
bool _network_server
network-server is active
Definition network.cpp:68
Basic functions/variables used all over the place.
GRFConfigList _grfconfig
First item in list of current GRF set up.
GRFListCompatibility IsGoodGRFConfigList(GRFConfigList &grfconfig)
Check if all GRFs in the GRF config from a savegame can be loaded.
void ClearGRFConfigList(GRFConfigList &config)
Clear a GRF Config list, freeing all nodes.
NewGRF handling of rail types.
NewGRF handling of road types.
uint8_t ReadByte(LoadgameState &ls)
Reads a byte from the buffer and decompress if needed.
Definition oldloader.cpp:86
Base class for roadstops.
A number of safeguards to prevent using unsafe methods.
static void SlRefVector(void *vector, VarType conv)
Save/Load a vector.
static const uint LZO_BUFFER_SIZE
Buffer size for the LZO compressor.
void SlError(StringID string, const std::string &extra_msg)
Error handler.
Definition saveload.cpp:339
static const ChunkHandler * SlFindChunkHandler(uint32_t id)
Find the ChunkHandler that will be used for processing the found chunk in the savegame or in memory.
static uint8_t GetSavegameFileType(const SaveLoad &sld)
Return the type as saved/loaded inside the savegame.
Definition saveload.cpp:576
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:612
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:848
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:686
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:812
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:669
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:728
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:929
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:792
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:874
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:740
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:642
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:824
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:946
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:541
static size_t SlCalcVectorLen(const void *vector, VarType conv)
Return the size in bytes of a std::vector.
static void SlRefList(void *list, VarType conv)
Save/Load a list.
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:691
@ SLE_FILE_END
Used to mark end-of-header in tables.
Definition saveload.h:664
@ SLE_FILE_TYPE_MASK
Mask to get the file-type (and not any flags).
Definition saveload.h:678
@ SLE_FILE_HAS_LENGTH_FIELD
Bit stored in savegame to indicate field has a length field for each entry.
Definition saveload.h:679
@ SLF_REPLACE_TABCRLF
Replace tabs, cr and lf in the string with spaces.
Definition saveload.h:728
@ SLF_ALLOW_NEWLINE
Allow new lines in the strings.
Definition saveload.h:727
@ SLF_ALLOW_CONTROL
Allow control codes in the strings.
Definition saveload.h:726
@ SLE_VAR_STR
string pointer
Definition saveload.h:692
@ SLE_VAR_NAME
old custom name to be converted to a string pointer
Definition saveload.h:694
@ SLE_VAR_STRQ
string pointer enclosed in quotes
Definition saveload.h:693
@ SLE_FILE_STRINGID
StringID offset into strings-array.
Definition saveload.h:673
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:1389
constexpr VarType GetVarFileType(VarType type)
Get the FileType of a setting.
Definition saveload.h:801
SLRefType
Type of reference (SLE_REF, SLE_CONDREF).
Definition saveload.h:639
@ REF_VEHICLE_OLD
Load/save an old-style reference to a vehicle (for pre-4.4 savegames).
Definition saveload.h:643
@ REF_LINK_GRAPH_JOB
Load/save a reference to a link graph job.
Definition saveload.h:650
@ REF_TOWN
Load/save a reference to a town.
Definition saveload.h:642
@ REF_LINK_GRAPH
Load/save a reference to a link graph.
Definition saveload.h:649
@ REF_CARGO_PACKET
Load/save a reference to a cargo packet.
Definition saveload.h:646
@ REF_ENGINE_RENEWS
Load/save a reference to an engine renewal (autoreplace).
Definition saveload.h:645
@ REF_STATION
Load/save a reference to a station.
Definition saveload.h:641
@ REF_ORDERLIST
Load/save a reference to an orderlist.
Definition saveload.h:647
@ REF_STORAGE
Load/save a reference to a persistent storage.
Definition saveload.h:648
@ REF_VEHICLE
Load/save a reference to a vehicle.
Definition saveload.h:640
@ REF_ROADSTOPS
Load/save a reference to a bus/truck stop.
Definition saveload.h:644
void * GetVariableAddress(const void *object, const SaveLoad &sld)
Get the address of the variable.
Definition saveload.h:1343
std::span< const ChunkHandlerRef > ChunkHandlerTable
A table of ChunkHandler entries.
Definition saveload.h:530
SaveLoadType
Type of data saved.
Definition saveload.h:734
@ SL_NULL
Save null-bytes and load to nowhere.
Definition saveload.h:748
@ SL_STRUCTLIST
Save/load a list of structs.
Definition saveload.h:745
@ SL_STDSTR
Save/load a std::string.
Definition saveload.h:739
@ SL_REF
Save/load a reference.
Definition saveload.h:736
@ SL_SAVEBYTE
Save (but not load) a byte.
Definition saveload.h:747
@ SL_DEQUE
Save/load a deque of SL_VAR elements.
Definition saveload.h:742
@ SL_STRUCT
Save/load a struct.
Definition saveload.h:737
@ SL_VECTOR
Save/load a vector of SL_VAR elements.
Definition saveload.h:743
@ SL_REFVECTOR
Save/load a vector of SL_REF elements.
Definition saveload.h:750
@ SL_REFLIST
Save/load a list of SL_REF elements.
Definition saveload.h:744
@ SL_ARR
Save/load a fixed-size array of SL_VAR elements.
Definition saveload.h:741
@ SL_VAR
Save/load a variable.
Definition saveload.h:735
std::span< const struct SaveLoadCompat > SaveLoadCompatTable
A table of SaveLoadCompat entries.
Definition saveload.h:536
bool IsSavegameVersionBefore(SaveLoadVersion major, uint8_t minor=0)
Checks whether the savegame is below major.
Definition saveload.h:1302
constexpr VarType GetVarMemType(VarType type)
Get the NumberType of a setting.
Definition saveload.h:790
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:533
@ 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:493
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:127
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:384
Class for pooled persistent storage of data.
static Pool::IterateWrapper< Company > Iterate(size_t from=0)
static OrderList * Get(auto index)
uint8_t * bufp
Location we're at reading the buffer.
Definition saveload.cpp:107
ReadBuffer(std::shared_ptr< LoadFilter > reader)
Initialise our variables.
Definition saveload.cpp:116
size_t read
The amount of read bytes so far from the filter.
Definition saveload.cpp:110
size_t GetSize() const
Get the size of the memory dump made so far.
Definition saveload.cpp:138
std::shared_ptr< LoadFilter > reader
The filter used to actually read.
Definition saveload.cpp:109
uint8_t buf[MEMORY_CHUNK_SIZE]
Buffer we're going to read from.
Definition saveload.cpp:106
uint8_t * bufe
End of the buffer we can read from.
Definition saveload.cpp:108
A Stop for a Road Vehicle.
SaveFilter(std::shared_ptr< SaveFilter > chain)
Initialise this filter.
std::shared_ptr< SaveFilter > chain
Chained to the (savegame) filters.
The format for a reader/writer type of a savegame.
uint32_t tag
the 4-letter tag by which it is identified in the savegame
uint8_t min_compression
the minimum compression level of this format
std::shared_ptr< SaveFilter >(* init_write)(std::shared_ptr< SaveFilter > chain, uint8_t compression)
Constructor for the save filter.
uint8_t default_compression
the default compression level of this format
std::shared_ptr< LoadFilter >(* init_load)(std::shared_ptr< LoadFilter > chain)
Constructor for the load filter.
std::string_view name
name of the compressor/decompressor (debug-only)
uint8_t max_compression
the maximum compression level of this format
The saveload struct, containing reader-writer functions, buffer, version, etc.
Definition saveload.cpp:196
std::unique_ptr< ReadBuffer > reader
Savegame reading buffer.
Definition saveload.cpp:209
std::shared_ptr< SaveFilter > sf
Filter to write the savegame to.
Definition saveload.cpp:207
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:756
uint16_t length
(Conditional) length of the variable (eg. arrays) (max array size is 65536 elements).
Definition saveload.h:760
std::shared_ptr< SaveLoadHandler > handler
Custom handler for Save/Load procs.
Definition saveload.h:765
SaveLoadVersion version_to
Save/load the variable before this savegame version.
Definition saveload.h:762
SaveLoadType cmd
The action to take with the saved/loaded type, All types need different action.
Definition saveload.h:758
std::string name
Name of this field (optional, used for tables).
Definition saveload.h:757
VarType conv
Type of the variable to be saved; this field combines both FileVarType and MemVarType.
Definition saveload.h:759
SaveLoadVersion version_from
Save/load the variable starting from this savegame version.
Definition saveload.h:761
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:3321
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