|
@@ -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:
|