File diff r26193:4bc7915a2156 → r26194:f7347205838e
src/saveload/saveload.cpp
Show inline comments
 
@@ -49,28 +49,36 @@
 
#ifdef __EMSCRIPTEN__
 
#	include <emscripten.h>
 
#endif
 

	
 
#include "table/strings.h"
 

	
 
#include "saveload_buffer.h"
 
#include "saveload_internal.h"
 
#include "saveload_filter.h"
 

	
 
#include "../safeguards.h"
 

	
 
extern const SaveLoadVersion SAVEGAME_VERSION = (SaveLoadVersion)(SL_MAX_VERSION - 1); ///< Current savegame version of OpenTTD.
 

	
 
const SaveLoadVersion SAVEGAME_VERSION_EXT = (SaveLoadVersion)(0x8000); ///< Savegame extension indicator mask
 

	
 
SavegameType _savegame_type; ///< type of savegame we are loading
 
FileToSaveLoad _file_to_saveload; ///< File to save or load in the openttd loop.
 

	
 
uint32 _ttdp_version;         ///< version of TTDP savegame (if applicable)
 
SaveLoadVersion _sl_version;  ///< the major savegame version identifier
 
byte   _sl_minor_version;     ///< the minor savegame version, DO NOT USE!
 
std::string _savegame_format; ///< how to compress savegames
 
bool _do_autosave;            ///< are we doing an autosave at the moment?
 

	
 
extern bool _sl_is_ext_version;
 
extern bool _sl_might_be_legacy_patchpack_save;
 
extern SaveLoadVersion _sl_is_faked_ext;
 
extern std::string _sl_xv_version_label;
 

	
 
/** What are we currently doing? */
 
enum SaveLoadAction {
 
	SLA_LOAD,        ///< loading
 
	SLA_SAVE,        ///< saving
 
	SLA_PTRS,        ///< fixing pointers
 
	SLA_NULL,        ///< null all pointers (on loading error)
 
@@ -80,119 +88,12 @@ enum SaveLoadAction {
 
enum NeedLength {
 
	NL_NONE = 0,       ///< not working in NeedLength mode
 
	NL_WANTLENGTH = 1, ///< writing length and data
 
	NL_CALCLENGTH = 2, ///< need to calculate the length
 
};
 

	
 
/** Save in chunks of 128 KiB. */
 
static const size_t MEMORY_CHUNK_SIZE = 128 * 1024;
 

	
 
/** A buffer for reading (and buffering) savegame data. */
 
struct ReadBuffer {
 
	byte buf[MEMORY_CHUNK_SIZE]; ///< Buffer we're going to read from.
 
	byte *bufp;                  ///< Location we're at reading the buffer.
 
	byte *bufe;                  ///< End of the buffer we can read from.
 
	LoadFilter *reader;          ///< The filter used to actually read.
 
	size_t read;                 ///< The amount of read bytes so far from the filter.
 

	
 
	/**
 
	 * Initialise our variables.
 
	 * @param reader The filter to actually read data.
 
	 */
 
	ReadBuffer(LoadFilter *reader) : bufp(nullptr), bufe(nullptr), reader(reader), read(0)
 
	{
 
	}
 

	
 
	inline byte ReadByte()
 
	{
 
		if (this->bufp == this->bufe) {
 
			size_t len = this->reader->Read(this->buf, lengthof(this->buf));
 
			if (len == 0) SlErrorCorrupt("Unexpected end of chunk");
 

	
 
			this->read += len;
 
			this->bufp = this->buf;
 
			this->bufe = this->buf + len;
 
		}
 

	
 
		return *this->bufp++;
 
	}
 

	
 
	/**
 
	 * Get the size of the memory dump made so far.
 
	 * @return The size.
 
	 */
 
	size_t GetSize() const
 
	{
 
		return this->read - (this->bufe - this->bufp);
 
	}
 
};
 

	
 

	
 
/** Container for dumping the savegame (quickly) to memory. */
 
struct MemoryDumper {
 
	std::vector<byte *> blocks; ///< Buffer with blocks of allocated memory.
 
	byte *buf;                  ///< Buffer we're going to write to.
 
	byte *bufe;                 ///< End of the buffer we write to.
 

	
 
	/** Initialise our variables. */
 
	MemoryDumper() : buf(nullptr), bufe(nullptr)
 
	{
 
	}
 

	
 
	~MemoryDumper()
 
	{
 
		for (auto p : this->blocks) {
 
			free(p);
 
		}
 
	}
 

	
 
	/**
 
	 * Write a single byte into the dumper.
 
	 * @param b The byte to write.
 
	 */
 
	inline void WriteByte(byte b)
 
	{
 
		/* Are we at the end of this chunk? */
 
		if (this->buf == this->bufe) {
 
			this->buf = CallocT<byte>(MEMORY_CHUNK_SIZE);
 
			this->blocks.push_back(this->buf);
 
			this->bufe = this->buf + MEMORY_CHUNK_SIZE;
 
		}
 

	
 
		*this->buf++ = b;
 
	}
 

	
 
	/**
 
	 * Flush this dumper into a writer.
 
	 * @param writer The filter we want to use.
 
	 */
 
	void Flush(SaveFilter *writer)
 
	{
 
		uint i = 0;
 
		size_t t = this->GetSize();
 

	
 
		while (t > 0) {
 
			size_t to_write = std::min(MEMORY_CHUNK_SIZE, t);
 

	
 
			writer->Write(this->blocks[i++], to_write);
 
			t -= to_write;
 
		}
 

	
 
		writer->Finish();
 
	}
 

	
 
	/**
 
	 * Get the size of the memory dump made so far.
 
	 * @return The size.
 
	 */
 
	size_t GetSize() const
 
	{
 
		return this->blocks.size() * MEMORY_CHUNK_SIZE - (this->bufe - this->buf);
 
	}
 
};
 

	
 
/** The saveload struct, containing reader-writer functions, buffer, version, etc. */
 
struct SaveLoadParams {
 
	SaveLoadAction action;               ///< are we doing a save or a load atm.
 
	NeedLength need_length;              ///< working in NeedLength (Autolength) mode?
 
	byte block_mode;                     ///< ???
 
	bool error;                          ///< did an error occur or not
 
@@ -213,15 +114,133 @@ struct SaveLoadParams {
 
	uint16 game_speed;                   ///< The game speed when saving started.
 
	bool saveinprogress;                 ///< Whether there is currently a save in progress.
 
};
 

	
 
static SaveLoadParams _sl; ///< Parameters used for/at saveload.
 

	
 
void ReadBuffer::SkipBytesSlowPath(size_t bytes)
 
{
 
	bytes -= (this->bufe - this->bufp);
 
	while (true) {
 
		size_t len = this->reader->Read(this->buf, lengthof(this->buf));
 
		if (len == 0) SlErrorCorrupt("Unexpected end of chunk");
 
		this->read += len;
 
		if (len >= bytes) {
 
			this->bufp = this->buf + bytes;
 
			this->bufe = this->buf + len;
 
			return;
 
		}
 
		else {
 
			bytes -= len;
 
		}
 
	}
 
}
 

	
 
void ReadBuffer::AcquireBytes()
 
{
 
	size_t remainder = this->bufe - this->bufp;
 
	if (remainder) {
 
		memmove(this->buf, this->bufp, remainder);
 
	}
 
	size_t len = this->reader->Read(this->buf + remainder, lengthof(this->buf) - remainder);
 
	if (len == 0) SlErrorCorrupt("Unexpected end of chunk");
 

	
 
	this->read += len;
 
	this->bufp = this->buf;
 
	this->bufe = this->buf + remainder + len;
 
}
 

	
 
void MemoryDumper::FinaliseBlock()
 
{
 
	assert(this->saved_buf == nullptr);
 
	if (!this->blocks.empty()) {
 
		size_t s = MEMORY_CHUNK_SIZE - (this->bufe - this->buf);
 
		this->blocks.back().size = s;
 
		this->completed_block_bytes += s;
 
	}
 
	this->buf = this->bufe = nullptr;
 
}
 

	
 
void MemoryDumper::AllocateBuffer()
 
{
 
	if (this->saved_buf) {
 
		const size_t offset = this->buf - this->autolen_buf;
 
		const size_t size = (this->autolen_buf_end - this->autolen_buf) * 2;
 
		this->autolen_buf = ReallocT<byte>(this->autolen_buf, size);
 
		this->autolen_buf_end = this->autolen_buf + size;
 
		this->buf = this->autolen_buf + offset;
 
		this->bufe = this->autolen_buf_end;
 
		return;
 
	}
 
	this->FinaliseBlock();
 
	this->buf = MallocT<byte>(MEMORY_CHUNK_SIZE);
 
	this->blocks.emplace_back(this->buf);
 
	this->bufe = this->buf + MEMORY_CHUNK_SIZE;
 
}
 

	
 
/**
 
 * Flush this dumper into a writer.
 
 * @param writer The filter we want to use.
 
 */
 
void MemoryDumper::Flush(SaveFilter* writer)
 
{
 
	this->FinaliseBlock();
 

	
 
	size_t block_count = this->blocks.size();
 
	for (size_t i = 0; i < block_count; i++) {
 
		writer->Write(this->blocks[i].data, this->blocks[i].size);
 
	}
 

	
 
	writer->Finish();
 
}
 

	
 
void MemoryDumper::StartAutoLength()
 
{
 
	assert(this->saved_buf == nullptr);
 

	
 
	this->saved_buf = this->buf;
 
	this->saved_bufe = this->bufe;
 
	this->buf = this->autolen_buf;
 
	this->bufe = this->autolen_buf_end;
 
}
 

	
 
std::pair<byte*, size_t> MemoryDumper::StopAutoLength()
 
{
 
	assert(this->saved_buf != nullptr);
 
	auto res = std::make_pair(this->autolen_buf, this->buf - this->autolen_buf);
 

	
 
	this->buf = this->saved_buf;
 
	this->bufe = this->saved_bufe;
 
	this->saved_buf = this->saved_bufe = nullptr;
 
	return res;
 
}
 

	
 
/**
 
 * Get the size of the memory dump made so far.
 
 * @return The size.
 
 */
 
size_t MemoryDumper::GetSize() const
 
{
 
	assert(this->saved_buf == nullptr);
 
	return this->completed_block_bytes + (this->bufe ? (MEMORY_CHUNK_SIZE - (this->bufe - this->buf)) : 0);
 
}
 

	
 
ReadBuffer* ReadBuffer::GetCurrent()
 
{
 
	return _sl.reader;
 
}
 

	
 
MemoryDumper* MemoryDumper::GetCurrent()
 
{
 
	return _sl.dumper;
 
}
 

	
 
static const std::vector<ChunkHandlerRef> &ChunkHandlers()
 
{
 
	/* These define the chunks */
 
	extern const ChunkHandlerTable _version_ext_chunk_handlers;
 
	extern const ChunkHandlerTable _gamelog_chunk_handlers;
 
	extern const ChunkHandlerTable _map_chunk_handlers;
 
	extern const ChunkHandlerTable _misc_chunk_handlers;
 
	extern const ChunkHandlerTable _name_chunk_handlers;
 
	extern const ChunkHandlerTable _cheat_chunk_handlers;
 
	extern const ChunkHandlerTable _setting_chunk_handlers;
 
@@ -252,12 +271,13 @@ static const std::vector<ChunkHandlerRef
 
	extern const ChunkHandlerTable _airport_chunk_handlers;
 
	extern const ChunkHandlerTable _object_chunk_handlers;
 
	extern const ChunkHandlerTable _persistent_storage_chunk_handlers;
 

	
 
	/** List of all chunks in a savegame. */
 
	static const ChunkHandlerTable _chunk_handler_tables[] = {
 
		_version_ext_chunk_handlers,
 
		_gamelog_chunk_handlers,
 
		_map_chunk_handlers,
 
		_misc_chunk_handlers,
 
		_name_chunk_handlers,
 
		_cheat_chunk_handlers,
 
		_setting_chunk_handlers,
 
@@ -309,12 +329,13 @@ static void SlNullPointers()
 
	_sl.action = SLA_NULL;
 

	
 
	/* We don't want any savegame conversion code to run
 
	 * during NULLing; especially those that try to get
 
	 * pointers from other pools. */
 
	_sl_version = SAVEGAME_VERSION;
 
	SlXvSetCurrentState();
 

	
 
	for (const ChunkHandler &ch : ChunkHandlers()) {
 
		Debug(sl, 3, "Nulling pointers for {:c}{:c}{:c}{:c}", ch.id >> 24, ch.id >> 16, ch.id >> 8, ch.id);
 
		ch.FixPointers();
 
	}
 

	
 
@@ -432,47 +453,66 @@ byte SlReadByte()
 
 */
 
void SlWriteByte(byte b)
 
{
 
	_sl.dumper->WriteByte(b);
 
}
 

	
 
static inline int SlReadUint16()
 
int SlReadUint16()
 
{
 
	int x = SlReadByte() << 8;
 
	return x | SlReadByte();
 
	_sl.reader->CheckBytes(2);
 
	return _sl.reader->RawReadUint16();
 
}
 

	
 
static inline uint32 SlReadUint32()
 
uint32 SlReadUint32()
 
{
 
	uint32 x = SlReadUint16() << 16;
 
	return x | SlReadUint16();
 
	_sl.reader->CheckBytes(4);
 
	return _sl.reader->RawReadUint32();
 
}
 

	
 
static inline uint64 SlReadUint64()
 
uint64 SlReadUint64()
 
{
 
	uint32 x = SlReadUint32();
 
	uint32 y = SlReadUint32();
 
	return (uint64)x << 32 | y;
 
	_sl.reader->CheckBytes(8);
 
	return _sl.reader->RawReadUint64();
 
}
 

	
 
void SlWriteUint16(uint16 v)
 
{
 
	_sl.dumper->CheckBytes(2);
 
	_sl.dumper->RawWriteUint16(v);
 
}
 

	
 
static inline void SlWriteUint16(uint16 v)
 
void SlWriteUint32(uint32 v)
 
{
 
	SlWriteByte(GB(v, 8, 8));
 
	SlWriteByte(GB(v, 0, 8));
 
	_sl.dumper->CheckBytes(4);
 
	_sl.dumper->RawWriteUint32(v);
 
}
 

	
 
static inline void SlWriteUint32(uint32 v)
 
void SlWriteUint64(uint64 x)
 
{
 
	SlWriteUint16(GB(v, 16, 16));
 
	SlWriteUint16(GB(v,  0, 16));
 
	_sl.dumper->CheckBytes(8);
 
	_sl.dumper->RawWriteUint64(x);
 
}
 

	
 
static inline void SlWriteUint64(uint64 x)
 
/**
 
 * Returns number of bytes read so far
 
 * May only be called during a load/load check action
 
 */
 
size_t SlGetBytesRead()
 
{
 
	SlWriteUint32((uint32)(x >> 32));
 
	SlWriteUint32((uint32)x);
 
	assert(_sl.action == SLA_LOAD || _sl.action == SLA_LOAD_CHECK);
 
	return _sl.reader->GetSize();
 
}
 

	
 
/**
 
 * Returns number of bytes written so far
 
 * May only be called during a save action
 
 */
 
size_t SlGetBytesWritten()
 
{
 
	assert(_sl.action == SLA_SAVE);
 
	return _sl.dumper->GetSize();
 
}
 

	
 
/**
 
 * Read in the header descriptor of an object or an array.
 
 * If the highest bit is set (7), then the index is bigger than 127
 
 * elements, so use the next byte to read in the real value.
 
@@ -1057,12 +1097,15 @@ static void SlStdString(void *ptr, VarTy
 
			break;
 
		}
 

	
 
		case SLA_LOAD_CHECK:
 
		case SLA_LOAD: {
 
			size_t len = SlReadArrayLength();
 
			if (len > 65535) {
 
				SlErrorCorrupt("String too long");
 
			}
 
			if (GetVarMemType(conv) == SLE_VAR_NULL) {
 
				SlSkipBytes(len);
 
				return;
 
			}
 

	
 
			char *buf = AllocaM(char, len + 1);
 
@@ -1551,13 +1594,13 @@ static void SlVector(void *vector, VarTy
 
	}
 
}
 

	
 
/** Are we going to save this object or not? */
 
static inline bool SlIsObjectValidInSavegame(const SaveLoad &sld)
 
{
 
	return (_sl_version >= sld.version_from && _sl_version < sld.version_to);
 
	return sld.ext_feature_test.IsFeaturePresent(_sl_version, sld.version_from, sld.version_to);
 
}
 

	
 
/**
 
 * Calculate the size of the table header.
 
 * @param slt The SaveLoad table with objects to save/load.
 
 * @return size of given object.
 
@@ -2045,25 +2088,26 @@ std::vector<SaveLoad> SlCompatTableHeade
 

	
 
	for (auto &slc : slct) {
 
		if (slc.name.empty()) {
 
			/* In old savegames there can be data we no longer care for. We
 
			 * skip this by simply reading the amount of bytes indicated and
 
			 * send those to /dev/null. */
 
			saveloads.push_back({"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, slc.length, slc.version_from, slc.version_to, 0, nullptr, 0, nullptr});
 
			saveloads.push_back({"", SL_NULL, SLE_FILE_U8 | SLE_VAR_NULL, slc.length, slc.version_from, slc.version_to, 0, nullptr, 0, nullptr, slc.ext_feature_test});
 
		} else {
 
			auto sld_it = key_lookup.find(slc.name);
 
			/* If this branch triggers, it means that an entry in the
 
			 * SaveLoadCompat list is not mentioned in the SaveLoad list. Did
 
			 * you rename a field in one and not in the other? */
 
			if (sld_it == key_lookup.end()) {
 
				/* This isn't an assert, as that leaves no information what
 
				 * field was to blame. This way at least we have breadcrumbs. */
 
				Debug(sl, 0, "internal error: saveload compatibility field '{}' not found", slc.name);
 
				SlErrorCorrupt("Internal error with savegame compatibility");
 
			}
 
			for (auto &sld : sld_it->second) {
 
				if (!slc.ext_feature_test.IsFeaturePresent(_sl_version, SL_MIN_VERSION, SL_MAX_VERSION)) continue;
 
				saveloads.push_back(*sld);
 
			}
 
		}
 
	}
 

	
 
	for (auto &sld : saveloads) {
 
@@ -2183,13 +2227,13 @@ static void SlLoadChunk(const ChunkHandl
 

	
 
/**
 
 * Load a chunk of data for checking savegames.
 
 * If the chunkhandler is nullptr, the chunk is skipped.
 
 * @param ch The chunkhandler that will be used for the operation
 
 */
 
static void SlLoadCheckChunk(const ChunkHandler &ch)
 
static void SlLoadCheckChunk(const ChunkHandler* ch)
 
{
 
	byte m = SlReadByte();
 
	size_t len;
 
	size_t endoffs;
 

	
 
	_sl.block_mode = m & CH_TYPE_MASK;
 
@@ -2203,36 +2247,75 @@ static void SlLoadCheckChunk(const Chunk
 
	}
 

	
 
	switch (_sl.block_mode) {
 
		case CH_TABLE:
 
		case CH_ARRAY:
 
			_sl.array_index = 0;
 
			ch.LoadCheck();
 
			if (ch)
 
			{
 
				ch->LoadCheck();
 
			}
 
			else
 
			{
 
				SlSkipArray();
 
			}
 
			break;
 
		case CH_SPARSE_TABLE:
 
		case CH_SPARSE_ARRAY:
 
			ch.LoadCheck();
 
			if (ch)
 
			{
 
				ch->LoadCheck();
 
			}
 
			else
 
			{
 
				SlSkipArray();
 
			}
 
			break;
 
		case CH_RIFF:
 
			/* Read length */
 
			len = (SlReadByte() << 16) | ((m >> 4) << 24);
 
			len += SlReadUint16();
 
			_sl.obj_len = len;
 
			endoffs = _sl.reader->GetSize() + len;
 
			ch.LoadCheck(len);
 
			if (ch) {
 
				ch->LoadCheck(len);
 
			}
 
			else
 
			{
 
				SlSkipBytes(len);
 
			}
 
			if (_sl.reader->GetSize() != endoffs) SlErrorCorrupt("Invalid chunk size");
 
			break;
 
		default:
 
			SlErrorCorrupt("Invalid chunk type");
 
			break;
 
	}
 

	
 
	if (_sl.expect_table_header) SlErrorCorrupt("Table chunk without header");
 
}
 

	
 
/**
 
 * Load a chunk of data for checking savegames.
 
 * @param ch The chunkhandler that will be used for the operation
 
 */
 
static void SlLoadCheckChunk(const ChunkHandler &ch)
 
{
 
	SlLoadCheckChunk(&ch);
 
}
 

	
 
/**
 
 * Skip unwanted chunk
 
 * @param id Chunk ID
 
 */
 
static void SlLoadSkipChunk(uint32 id)
 
{
 
	Debug(sl, 1, "Discarding chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
 
	SlLoadCheckChunk(nullptr);
 
}
 

	
 
/**
 
 * Save a chunk of data (eg. vehicles, stations, etc.). Each chunk is
 
 * prefixed by an ID identifying it, followed by data, and terminator where appropriate
 
 * @param ch The chunkhandler that will be used for the operation
 
 */
 
static void SlSaveChunk(const ChunkHandler &ch)
 
{
 
@@ -2299,14 +2382,23 @@ static void SlLoadChunks()
 
	const ChunkHandler *ch;
 

	
 
	for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
 
		Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
 

	
 
		ch = SlFindChunkHandler(id);
 
		if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
 
		SlLoadChunk(*ch);
 
		if (ch == nullptr) {
 
			if (SlXvIsChunkDiscardable(id)) {
 
				SlLoadSkipChunk(id);
 
			}
 
			else {
 
				SlErrorCorrupt("Unknown chunk type");
 
			}
 
		}
 
		else {
 
			SlLoadChunk(*ch);
 
		}
 
	}
 
}
 

	
 
/** Load all chunks for savegame checking */
 
static void SlLoadCheckChunks()
 
{
 
@@ -2314,14 +2406,23 @@ static void SlLoadCheckChunks()
 
	const ChunkHandler *ch;
 

	
 
	for (id = SlReadUint32(); id != 0; id = SlReadUint32()) {
 
		Debug(sl, 2, "Loading chunk {:c}{:c}{:c}{:c}", id >> 24, id >> 16, id >> 8, id);
 

	
 
		ch = SlFindChunkHandler(id);
 
		if (ch == nullptr) SlErrorCorrupt("Unknown chunk type");
 
		SlLoadCheckChunk(*ch);
 
		if (ch == nullptr) {
 
			if (SlXvIsChunkDiscardable(id)) {
 
				SlLoadSkipChunk(id);
 
			}
 
			else {
 
				SlErrorCorruptFmt("Unknown chunk type %c%c%c%c", id >> 24, id >> 16, id >> 8, id);
 
			}
 
		}
 
		else {
 
			SlLoadCheckChunk(*ch);
 
		}
 
	}
 
}
 

	
 
/** Fix all pointers (convert index -> pointer) */
 
static void SlFixPointers()
 
{
 
@@ -2973,13 +3074,13 @@ static SaveOrLoadResult SaveFileToDisk(b
 
{
 
	try {
 
		byte compression;
 
		const SaveLoadFormat *fmt = GetSavegameFormat(_savegame_format, &compression);
 

	
 
		/* We have written our stuff to memory, now write it to file! */
 
		uint32 hdr[2] = { fmt->tag, TO_BE32(SAVEGAME_VERSION << 16) };
 
		uint32 hdr[2] = { fmt->tag, TO_BE32((uint32)(SAVEGAME_VERSION | SAVEGAME_VERSION_EXT) << 16) };
 
		_sl.sf->Write((byte*)hdr, sizeof(hdr));
 

	
 
		_sl.sf = fmt->init_write(_sl.sf, compression);
 
		_sl.dumper->Flush(_sl.sf);
 

	
 
		ClearSaveLoadState();
 
@@ -3032,12 +3133,13 @@ static SaveOrLoadResult DoSave(SaveFilte
 
	assert(!_sl.saveinprogress);
 

	
 
	_sl.dumper = new MemoryDumper();
 
	_sl.sf = writer;
 

	
 
	_sl_version = SAVEGAME_VERSION;
 
	SlXvSetCurrentState();
 

	
 
	SaveViewportBeforeSaveGame();
 
	SlSaveChunks();
 

	
 
	SaveFileStart();
 

	
 
@@ -3084,24 +3186,27 @@ static SaveOrLoadResult DoLoad(LoadFilte
 
		/* Clear previous check data */
 
		_load_check_data.Clear();
 
		/* Mark SL_LOAD_CHECK as supported for this savegame. */
 
		_load_check_data.checkable = true;
 
	}
 

	
 
	SlXvResetState();
 

	
 
	uint32 hdr[2];
 
	if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
 

	
 
	/* see if we have any loader for this type. */
 
	const SaveLoadFormat *fmt = _saveload_formats;
 
	for (;;) {
 
		/* No loader found, treat as version 0 and use LZO format */
 
		if (fmt == endof(_saveload_formats)) {
 
			Debug(sl, 0, "Unknown savegame type, trying to load it as the buggy format");
 
			_sl.lf->Reset();
 
			_sl_version = SL_MIN_VERSION;
 
			_sl_minor_version = 0;
 
			SlXvResetState();
 

	
 
			/* Try to find the LZO savegame format; it uses 'OTTD' as tag. */
 
			fmt = _saveload_formats;
 
			for (;;) {
 
				if (fmt == endof(_saveload_formats)) {
 
					/* Who removed LZO support? */
 
@@ -3118,17 +3223,24 @@ static SaveOrLoadResult DoLoad(LoadFilte
 
			_sl_version = (SaveLoadVersion)(TO_BE32(hdr[1]) >> 16);
 
			/* Minor is not used anymore from version 18.0, but it is still needed
 
			 * in versions before that (4 cases) which can't be removed easy.
 
			 * Therefore it is loaded, but never saved (or, it saves a 0 in any scenario). */
 
			_sl_minor_version = (TO_BE32(hdr[1]) >> 8) & 0xFF;
 

	
 
			Debug(sl, 1, "Loading savegame version {}", _sl_version);
 
			if (_sl_version & SAVEGAME_VERSION_EXT) {
 
				_sl_version = (SaveLoadVersion)(_sl_version & ~SAVEGAME_VERSION_EXT);
 
				_sl_is_ext_version = true;
 
			} else if (_settings_client.gui.load_legacy_patchpack_savedata ) {
 
				SlXvCheckSpecialSavegameVersionsA();
 
			}
 

	
 
			Debug(sl, 1, "Loading savegame version {}{}", _sl_version, _sl_is_ext_version ? " (extended)" : "");
 

	
 
			/* Is the version higher than the current? */
 
			if (_sl_version > SAVEGAME_VERSION) SlError(STR_GAME_SAVELOAD_ERROR_TOO_NEW_SAVEGAME);
 
			if (_sl_version >= SLV_START_PATCHPACKS && _sl_version <= SLV_END_PATCHPACKS) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK);
 
			if (_sl_version >= SLV_START_PATCHPACKS && _sl_version < SLV_END_PATCHPACKS && !_sl_might_be_legacy_patchpack_save) SlError(STR_GAME_SAVELOAD_ERROR_PATCHPACK);
 
			break;
 
		}
 

	
 
		fmt++;
 
	}
 

	
 
@@ -3179,12 +3291,14 @@ static SaveOrLoadResult DoLoad(LoadFilte
 
		}
 
	}
 

	
 
	if (load_check) {
 
		/* Load chunks into _load_check_data.
 
		 * No pools are loaded. References are not possible, and thus do not need resolving. */
 
		_load_check_data.save_version = _sl_is_faked_ext != SL_MIN_VERSION ? _sl_is_faked_ext : _sl_version;
 
		_load_check_data.save_ext_type = _sl_is_ext_version ? PSXT_EXTENDED : PSXT_NONE;
 
		SlLoadCheckChunks();
 
	} else {
 
		/* Load chunks and resolve references */
 
		SlLoadChunks();
 
		SlFixPointers();
 
	}
 
@@ -3204,12 +3318,13 @@ static SaveOrLoadResult DoLoad(LoadFilte
 
		if (!AfterLoadGame()) {
 
			GamelogStopAction();
 
			return SL_REINIT;
 
		}
 

	
 
		GamelogStopAction();
 
		SlXvSetCurrentState();
 
	}
 

	
 
	return SL_OK;
 
}
 

	
 
/**
 
@@ -3260,18 +3375,20 @@ SaveOrLoadResult SaveOrLoad(const std::s
 
			 * for OTTD savegames which have their own NewGRF logic. */
 
			ClearGRFConfigList(&_grfconfig);
 
			GamelogReset();
 
			if (!LoadOldSaveGame(filename)) return SL_REINIT;
 
			_sl_version = SL_MIN_VERSION;
 
			_sl_minor_version = 0;
 
			SlXvResetState();
 
			GamelogStartAction(GLAT_LOAD);
 
			if (!AfterLoadGame()) {
 
				GamelogStopAction();
 
				return SL_REINIT;
 
			}
 
			GamelogStopAction();
 
			SlXvSetCurrentState();
 
			return SL_OK;
 
		}
 

	
 
		assert(dft == DFT_GAME_FILE);
 
		switch (fop) {
 
			case SLO_CHECK: