changeset 15334:09680603c66b draft

(svn r19975) -Add: SL_LOAD_CHECK mode for partial reading of savegames.
author frosch <frosch@openttd.org>
date Sun, 13 Jun 2010 14:13:23 +0000
parents 72de7debf8ce
children 3d75c6abc840
files src/fios.h src/fios_gui.cpp src/saveload/saveload.cpp src/saveload/saveload.h
diffstat 4 files changed, 197 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/src/fios.h
+++ b/src/fios.h
@@ -17,6 +17,26 @@
 #include "core/enum_type.hpp"
 #include "gfx_type.h"
 
+
+/**
+ * Container for loading in mode SL_LOAD_CHECK.
+ */
+struct LoadCheckData {
+	bool checkable;     ///< True if the savegame could be checked by SL_LOAD_CHECK. (Old savegames are not checkable.)
+	StringID error;     ///< Error message from loading. INVALID_STRING_ID if no error.
+	char *error_data;   ///< Data to pass to SetDParamStr when displaying #error.
+
+	LoadCheckData() : error_data(NULL)
+	{
+		this->Clear();
+	}
+
+	void Clear();
+};
+
+extern LoadCheckData _load_check_data;
+
+
 enum FileSlots {
 	/**
 	 * Slot used for the GRF scanning and such. This slot cannot be reused
--- a/src/fios_gui.cpp
+++ b/src/fios_gui.cpp
@@ -29,11 +29,24 @@
 #include "table/strings.h"
 
 SaveLoadDialogMode _saveload_mode;
+LoadCheckData _load_check_data;    ///< Data loaded from save during SL_LOAD_CHECK.
 
 static bool _fios_path_changed;
 static bool _savegame_sort_dirty;
 
 
+/**
+ * Reset read data.
+ */
+void LoadCheckData::Clear()
+{
+	this->checkable = false;
+	this->error = INVALID_STRING_ID;
+	free(this->error_data);
+	this->error_data = NULL;
+}
+
+
 enum SaveLoadWindowWidgets {
 	SLWW_WINDOWTITLE,
 	SLWW_SORT_BYNAME,
--- a/src/saveload/saveload.cpp
+++ b/src/saveload/saveload.cpp
@@ -41,6 +41,7 @@
 #include "../string_func.h"
 #include "../engine_base.h"
 #include "../company_base.h"
+#include "../fios.h"
 
 #include "table/strings.h"
 
@@ -60,10 +61,11 @@
 
 /** 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)
+	SLA_LOAD,        ///< loading
+	SLA_SAVE,        ///< saving
+	SLA_PTRS,        ///< fixing pointers
+	SLA_NULL,        ///< null all pointers (on loading error)
+	SLA_LOAD_CHECK,  ///< partial loading into #_load_check_data
 };
 
 enum NeedLength {
@@ -194,13 +196,20 @@
  * pretty ugly, and seriously interferes with any multithreaded approaches */
 static void NORETURN SlError(StringID string, const char *extra_msg = NULL)
 {
-	_sl.error_str = string;
-	free(_sl.extra_msg);
-	_sl.extra_msg = (extra_msg == NULL) ? NULL : strdup(extra_msg);
-	/* We have to NULL all pointers here; we might be in a state where
-	 * the pointers are actually filled with indices, which means that
-	 * when we access them during cleaning the pool dereferences of
-	 * those indices will be made with segmentation faults as result. */
+	/* Distinguish between loading into _load_check_data vs. normal save/load. */
+	if (_sl.action == SLA_LOAD_CHECK) {
+		_load_check_data.error = string;
+		free(_load_check_data.error_data);
+		_load_check_data.error_data = (extra_msg == NULL) ? NULL : strdup(extra_msg);
+	} else {
+		_sl.error_str = string;
+		free(_sl.extra_msg);
+		_sl.extra_msg = (extra_msg == NULL) ? NULL : strdup(extra_msg);
+		/* We have to NULL all pointers here; we might be in a state where
+		 * the pointers are actually filled with indices, which means that
+		 * when we access them during cleaning the pool dereferences of
+		 * those indices will be made with segmentation faults as result. */
+	}
 	if (_sl.action == SLA_LOAD || _sl.action == SLA_PTRS) SlNullPointers();
 	throw std::exception();
 }
@@ -560,6 +569,7 @@
 	byte *p = (byte *)ptr;
 
 	switch (_sl.action) {
+		case SLA_LOAD_CHECK:
 		case SLA_LOAD:
 			for (; length != 0; length--) { *p++ = SlReadByteInternal(); }
 			break;
@@ -647,6 +657,7 @@
 			}
 			break;
 		}
+		case SLA_LOAD_CHECK:
 		case SLA_LOAD: {
 			int64 x;
 			/* Read a value from the file */
@@ -743,6 +754,7 @@
 			SlCopyBytes(ptr, len);
 			break;
 		}
+		case SLA_LOAD_CHECK:
 		case SLA_LOAD: {
 			size_t len = SlReadArrayLength();
 
@@ -890,6 +902,7 @@
 			}
 			break;
 		}
+		case SLA_LOAD_CHECK:
 		case SLA_LOAD: {
 			size_t length = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32();
 
@@ -1009,6 +1022,7 @@
 						case SLA_SAVE:
 							SlWriteUint32((uint32)ReferenceToInt(*(void **)ptr, (SLRefType)conv));
 							break;
+						case SLA_LOAD_CHECK:
 						case SLA_LOAD:
 							*(size_t *)ptr = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32();
 							break;
@@ -1036,6 +1050,7 @@
 		case SL_WRITEBYTE:
 			switch (_sl.action) {
 				case SLA_SAVE: SlWriteByte(sld->version_to); break;
+				case SLA_LOAD_CHECK:
 				case SLA_LOAD: *(byte *)ptr = sld->version_from; break;
 				case SLA_PTRS: break;
 				case SLA_NULL: break;
@@ -1150,6 +1165,56 @@
 	}
 }
 
+/**
+ * Load a chunk of data for checking savegames.
+ * If the chunkhandler is NULL, the chunk is skipped.
+ * @param ch The chunkhandler that will be used for the operation
+ */
+static void SlLoadCheckChunk(const ChunkHandler *ch)
+{
+	byte m = SlReadByte();
+	size_t len;
+	size_t endoffs;
+
+	_sl.block_mode = m;
+	_sl.obj_len = 0;
+
+	switch (m) {
+		case CH_ARRAY:
+			_sl.array_index = 0;
+			if (ch->load_check_proc) {
+				ch->load_check_proc();
+			} else {
+				SlSkipArray();
+			}
+			break;
+		case CH_SPARSE_ARRAY:
+			if (ch->load_check_proc) {
+				ch->load_check_proc();
+			} else {
+				SlSkipArray();
+			}
+			break;
+		default:
+			if ((m & 0xF) == CH_RIFF) {
+				/* Read length */
+				len = (SlReadByte() << 16) | ((m >> 4) << 24);
+				len += SlReadUint16();
+				_sl.obj_len = len;
+				endoffs = SlGetOffs() + len;
+				if (ch->load_check_proc) {
+					ch->load_check_proc();
+				} else {
+					SlSkipBytes(len);
+				}
+				if (SlGetOffs() != endoffs) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk size");
+			} else {
+				SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Invalid chunk type");
+			}
+			break;
+	}
+}
+
 /* Stub Chunk handlers to only calculate length and do nothing else */
 static ChunkSaveLoadProc *_tmp_proc_1;
 static inline void SlStubSaveProc2(void *arg) {_tmp_proc_1();}
@@ -1233,6 +1298,21 @@
 	}
 }
 
+/** Load all chunks for savegame checking */
+static void SlLoadCheckChunks()
+{
+	uint32 id;
+	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 == NULL) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unknown chunk type");
+		SlLoadCheckChunk(ch);
+	}
+}
+
 /** Fix all pointers (convert index -> pointer) */
 static void SlFixPointers()
 {
@@ -1850,6 +1930,8 @@
 	}
 	WaitTillSaved();
 
+	/* Clear previous check data */
+	if (mode == SL_LOAD_CHECK) _load_check_data.Clear();
 	_next_offs = 0;
 
 	/* Load a TTDLX or TTDPatch game */
@@ -1875,13 +1957,16 @@
 		return SL_OK;
 	}
 
+	/* Mark SL_LOAD_CHECK as supported for this savegame. */
+	if (mode == SL_LOAD_CHECK) _load_check_data.checkable = true;
+
 	_sl.excpt_uninit = NULL;
 	try {
 		_sl.fh = (mode == SL_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
 
 		/* Make it a little easier to load savegames from the console */
-		if (_sl.fh == NULL && mode == SL_LOAD) _sl.fh = FioFOpenFile(filename, "rb", SAVE_DIR);
-		if (_sl.fh == NULL && mode == SL_LOAD) _sl.fh = FioFOpenFile(filename, "rb", BASE_DIR);
+		if (_sl.fh == NULL && mode != SL_SAVE) _sl.fh = FioFOpenFile(filename, "rb", SAVE_DIR);
+		if (_sl.fh == NULL && mode != SL_SAVE) _sl.fh = FioFOpenFile(filename, "rb", BASE_DIR);
 
 		if (_sl.fh == NULL) {
 			SlError(mode == SL_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
@@ -1889,7 +1974,12 @@
 
 		_sl.bufe = _sl.bufp = NULL;
 		_sl.offs_base = 0;
-		_sl.action = (mode != 0) ? SLA_SAVE : SLA_LOAD;
+		switch (mode) {
+			case SL_LOAD_CHECK: _sl.action = SLA_LOAD_CHECK; break;
+			case SL_LOAD: _sl.action = SLA_LOAD; break;
+			case SL_SAVE: _sl.action = SLA_SAVE; break;
+			default: NOT_REACHED();
+		}
 
 		/* General tactic is to first save the game to memory, then use an available writer
 		 * to write it to file, either in threaded mode if possible, or single-threaded */
@@ -1917,7 +2007,7 @@
 				return result;
 			}
 		} else { // LOAD game
-			assert(mode == SL_LOAD);
+			assert(mode == SL_LOAD || mode == SL_LOAD_CHECK);
 			DEBUG(desync, 1, "load: %s", filename);
 
 			/* Can't fseek to 0 as in tar files that is not correct */
@@ -1983,57 +2073,68 @@
 				SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, err_str);
 			}
 
-			_engine_mngr.ResetToDefaultMapping();
+			if (mode != SL_LOAD_CHECK) {
+				_engine_mngr.ResetToDefaultMapping();
 
-			/* Old maps were hardcoded to 256x256 and thus did not contain
-			 * any mapsize information. Pre-initialize to 256x256 to not to
-			 * confuse old games */
-			InitializeGame(256, 256, true, true);
+				/* Old maps were hardcoded to 256x256 and thus did not contain
+				 * any mapsize information. Pre-initialize to 256x256 to not to
+				 * confuse old games */
+				InitializeGame(256, 256, true, true);
 
-			GamelogReset();
+				GamelogReset();
 
-			if (CheckSavegameVersion(4)) {
-				/*
-				 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
-				 * shared savegame version 4. Anything before that 'obviously'
-				 * does not have any NewGRFs. Between the introduction and
-				 * savegame version 41 (just before 0.5) the NewGRF settings
-				 * were not stored in the savegame and they were loaded by
-				 * using the settings from the main menu.
-				 * So, to recap:
-				 * - savegame version  <  4:  do not load any NewGRFs.
-				 * - savegame version >= 41:  load NewGRFs from savegame, which is
-				 *                            already done at this stage by
-				 *                            overwriting the main menu settings.
-				 * - other savegame versions: use main menu settings.
-				 *
-				 * This means that users *can* crash savegame version 4..40
-				 * savegames if they set incompatible NewGRFs in the main menu,
-				 * but can't crash anymore for savegame version < 4 savegames.
-				 *
-				 * Note: this is done here because AfterLoadGame is also called
-				 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
-				 */
-				ClearGRFConfigList(&_grfconfig);
+				if (CheckSavegameVersion(4)) {
+					/*
+					 * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
+					 * shared savegame version 4. Anything before that 'obviously'
+					 * does not have any NewGRFs. Between the introduction and
+					 * savegame version 41 (just before 0.5) the NewGRF settings
+					 * were not stored in the savegame and they were loaded by
+					 * using the settings from the main menu.
+					 * So, to recap:
+					 * - savegame version  <  4:  do not load any NewGRFs.
+					 * - savegame version >= 41:  load NewGRFs from savegame, which is
+					 *                            already done at this stage by
+					 *                            overwriting the main menu settings.
+					 * - other savegame versions: use main menu settings.
+					 *
+					 * This means that users *can* crash savegame version 4..40
+					 * savegames if they set incompatible NewGRFs in the main menu,
+					 * but can't crash anymore for savegame version < 4 savegames.
+					 *
+					 * Note: this is done here because AfterLoadGame is also called
+					 * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
+					 */
+					ClearGRFConfigList(&_grfconfig);
+				}
 			}
 
-			SlLoadChunks();
-			SlFixPointers();
+			if (mode == SL_LOAD_CHECK) {
+				/* Load chunks into _load_check_data.
+				 * No pools are loaded. References are not possible, and thus do not need resolving. */
+				SlLoadCheckChunks();
+			} else {
+				/* Load chunks and resolve references */
+				SlLoadChunks();
+				SlFixPointers();
+			}
 			fmt->uninit_read();
 			fclose(_sl.fh);
 
-			GamelogStartAction(GLAT_LOAD);
-
 			_savegame_type = SGT_OTTD;
 
-			/* After loading fix up savegame for any internal changes that
-			 * might've occured since then. If it fails, load back the old game */
-			if (!AfterLoadGame()) {
+			if (mode != SL_LOAD_CHECK) {
+				GamelogStartAction(GLAT_LOAD);
+
+				/* After loading fix up savegame for any internal changes that
+				 * might've occured since then. If it fails, load back the old game */
+				if (!AfterLoadGame()) {
+					GamelogStopAction();
+					return SL_REINIT;
+				}
+
 				GamelogStopAction();
-				return SL_REINIT;
 			}
-
-			GamelogStopAction();
 		}
 
 		return SL_OK;
@@ -2045,7 +2146,7 @@
 		if (_sl.excpt_uninit != NULL) _sl.excpt_uninit();
 
 		/* Skip the "colour" character */
-		DEBUG(sl, 0, "%s", GetSaveLoadErrorString() + 3);
+		if (mode != SL_LOAD_CHECK) DEBUG(sl, 0, "%s", GetSaveLoadErrorString() + 3);
 
 		/* A saver/loader exception!! reinitialize all variables to prevent crash! */
 		return (mode == SL_LOAD) ? SL_REINIT : SL_ERROR;
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -27,12 +27,13 @@
 };
 
 enum SaveOrLoadMode {
-	SL_INVALID  = -1,
-	SL_LOAD     =  0,
-	SL_SAVE     =  1,
-	SL_OLD_LOAD =  2,
-	SL_PNG      =  3,
-	SL_BMP      =  4,
+	SL_INVALID    = -1,
+	SL_LOAD       =  0,
+	SL_SAVE       =  1,
+	SL_OLD_LOAD   =  2,
+	SL_PNG        =  3,
+	SL_BMP        =  4,
+	SL_LOAD_CHECK =  5,
 };
 
 enum SavegameType {
@@ -216,6 +217,7 @@
 #define SLE_ARR(base, variable, type, length) SLE_CONDARR(base, variable, type, length, 0, SL_MAX_VERSION)
 #define SLE_STR(base, variable, type, length) SLE_CONDSTR(base, variable, type, length, 0, SL_MAX_VERSION)
 #define SLE_LST(base, variable, type) SLE_CONDLST(base, variable, type, 0, SL_MAX_VERSION)
+#define SLE_NULL(length) SLE_CONDNULL(length, 0, SL_MAX_VERSION)
 
 #define SLE_CONDNULL(length, from, to) SLE_CONDARR(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_CONFIG_NO, length, from, to)