(svn r19975) -Add: SL_LOAD_CHECK mode for partial reading of savegames.

This commit is contained in:
frosch 2010-06-13 14:13:23 +00:00
parent e8248cf113
commit 8d9b573f8f
4 changed files with 198 additions and 62 deletions

View File

@ -17,6 +17,26 @@
#include "core/enum_type.hpp" #include "core/enum_type.hpp"
#include "gfx_type.h" #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 { enum FileSlots {
/** /**
* Slot used for the GRF scanning and such. This slot cannot be reused * Slot used for the GRF scanning and such. This slot cannot be reused

View File

@ -29,11 +29,24 @@
#include "table/strings.h" #include "table/strings.h"
SaveLoadDialogMode _saveload_mode; SaveLoadDialogMode _saveload_mode;
LoadCheckData _load_check_data; ///< Data loaded from save during SL_LOAD_CHECK.
static bool _fios_path_changed; static bool _fios_path_changed;
static bool _savegame_sort_dirty; 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 { enum SaveLoadWindowWidgets {
SLWW_WINDOWTITLE, SLWW_WINDOWTITLE,
SLWW_SORT_BYNAME, SLWW_SORT_BYNAME,

View File

@ -41,6 +41,7 @@
#include "../string_func.h" #include "../string_func.h"
#include "../engine_base.h" #include "../engine_base.h"
#include "../company_base.h" #include "../company_base.h"
#include "../fios.h"
#include "table/strings.h" #include "table/strings.h"
@ -60,10 +61,11 @@ typedef size_t ReaderProc();
/** What are we currently doing? */ /** What are we currently doing? */
enum SaveLoadAction { enum SaveLoadAction {
SLA_LOAD, ///< loading SLA_LOAD, ///< loading
SLA_SAVE, ///< saving SLA_SAVE, ///< saving
SLA_PTRS, ///< fixing pointers SLA_PTRS, ///< fixing pointers
SLA_NULL, ///< null all pointers (on loading error) SLA_NULL, ///< null all pointers (on loading error)
SLA_LOAD_CHECK, ///< partial loading into #_load_check_data
}; };
enum NeedLength { enum NeedLength {
@ -194,13 +196,20 @@ static void SlNullPointers()
* pretty ugly, and seriously interferes with any multithreaded approaches */ * pretty ugly, and seriously interferes with any multithreaded approaches */
static void NORETURN SlError(StringID string, const char *extra_msg = NULL) static void NORETURN SlError(StringID string, const char *extra_msg = NULL)
{ {
_sl.error_str = string; /* Distinguish between loading into _load_check_data vs. normal save/load. */
free(_sl.extra_msg); if (_sl.action == SLA_LOAD_CHECK) {
_sl.extra_msg = (extra_msg == NULL) ? NULL : strdup(extra_msg); _load_check_data.error = string;
/* We have to NULL all pointers here; we might be in a state where free(_load_check_data.error_data);
* the pointers are actually filled with indices, which means that _load_check_data.error_data = (extra_msg == NULL) ? NULL : strdup(extra_msg);
* when we access them during cleaning the pool dereferences of } else {
* those indices will be made with segmentation faults as result. */ _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(); if (_sl.action == SLA_LOAD || _sl.action == SLA_PTRS) SlNullPointers();
throw std::exception(); throw std::exception();
} }
@ -560,6 +569,7 @@ static void SlCopyBytes(void *ptr, size_t length)
byte *p = (byte *)ptr; byte *p = (byte *)ptr;
switch (_sl.action) { switch (_sl.action) {
case SLA_LOAD_CHECK:
case SLA_LOAD: case SLA_LOAD:
for (; length != 0; length--) { *p++ = SlReadByteInternal(); } for (; length != 0; length--) { *p++ = SlReadByteInternal(); }
break; break;
@ -647,6 +657,7 @@ static void SlSaveLoadConv(void *ptr, VarType conv)
} }
break; break;
} }
case SLA_LOAD_CHECK:
case SLA_LOAD: { case SLA_LOAD: {
int64 x; int64 x;
/* Read a value from the file */ /* Read a value from the file */
@ -743,6 +754,7 @@ static void SlString(void *ptr, size_t length, VarType conv)
SlCopyBytes(ptr, len); SlCopyBytes(ptr, len);
break; break;
} }
case SLA_LOAD_CHECK:
case SLA_LOAD: { case SLA_LOAD: {
size_t len = SlReadArrayLength(); size_t len = SlReadArrayLength();
@ -890,6 +902,7 @@ static void SlList(void *list, SLRefType conv)
} }
break; break;
} }
case SLA_LOAD_CHECK:
case SLA_LOAD: { case SLA_LOAD: {
size_t length = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32(); size_t length = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32();
@ -1009,6 +1022,7 @@ bool SlObjectMember(void *ptr, const SaveLoad *sld)
case SLA_SAVE: case SLA_SAVE:
SlWriteUint32((uint32)ReferenceToInt(*(void **)ptr, (SLRefType)conv)); SlWriteUint32((uint32)ReferenceToInt(*(void **)ptr, (SLRefType)conv));
break; break;
case SLA_LOAD_CHECK:
case SLA_LOAD: case SLA_LOAD:
*(size_t *)ptr = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32(); *(size_t *)ptr = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32();
break; break;
@ -1036,6 +1050,7 @@ bool SlObjectMember(void *ptr, const SaveLoad *sld)
case SL_WRITEBYTE: case SL_WRITEBYTE:
switch (_sl.action) { switch (_sl.action) {
case SLA_SAVE: SlWriteByte(sld->version_to); break; case SLA_SAVE: SlWriteByte(sld->version_to); break;
case SLA_LOAD_CHECK:
case SLA_LOAD: *(byte *)ptr = sld->version_from; break; case SLA_LOAD: *(byte *)ptr = sld->version_from; break;
case SLA_PTRS: break; case SLA_PTRS: break;
case SLA_NULL: break; case SLA_NULL: break;
@ -1150,6 +1165,56 @@ static void SlLoadChunk(const ChunkHandler *ch)
} }
} }
/**
* 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 */ /* Stub Chunk handlers to only calculate length and do nothing else */
static ChunkSaveLoadProc *_tmp_proc_1; static ChunkSaveLoadProc *_tmp_proc_1;
static inline void SlStubSaveProc2(void *arg) {_tmp_proc_1();} static inline void SlStubSaveProc2(void *arg) {_tmp_proc_1();}
@ -1233,6 +1298,21 @@ static void SlLoadChunks()
} }
} }
/** 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) */ /** Fix all pointers (convert index -> pointer) */
static void SlFixPointers() static void SlFixPointers()
{ {
@ -1850,6 +1930,8 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
} }
WaitTillSaved(); WaitTillSaved();
/* Clear previous check data */
if (mode == SL_LOAD_CHECK) _load_check_data.Clear();
_next_offs = 0; _next_offs = 0;
/* Load a TTDLX or TTDPatch game */ /* Load a TTDLX or TTDPatch game */
@ -1875,13 +1957,16 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
return SL_OK; 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; _sl.excpt_uninit = NULL;
try { try {
_sl.fh = (mode == SL_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb); _sl.fh = (mode == SL_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
/* Make it a little easier to load savegames from the console */ /* 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_SAVE) _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", BASE_DIR);
if (_sl.fh == NULL) { if (_sl.fh == NULL) {
SlError(mode == SL_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE); SlError(mode == SL_SAVE ? STR_GAME_SAVELOAD_ERROR_FILE_NOT_WRITEABLE : STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
@ -1889,7 +1974,12 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
_sl.bufe = _sl.bufp = NULL; _sl.bufe = _sl.bufp = NULL;
_sl.offs_base = 0; _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 /* 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 */ * to write it to file, either in threaded mode if possible, or single-threaded */
@ -1917,7 +2007,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
return result; return result;
} }
} else { // LOAD game } else { // LOAD game
assert(mode == SL_LOAD); assert(mode == SL_LOAD || mode == SL_LOAD_CHECK);
DEBUG(desync, 1, "load: %s", filename); DEBUG(desync, 1, "load: %s", filename);
/* Can't fseek to 0 as in tar files that is not correct */ /* Can't fseek to 0 as in tar files that is not correct */
@ -1983,57 +2073,68 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_INTERNAL_ERROR, err_str); 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 /* Old maps were hardcoded to 256x256 and thus did not contain
* any mapsize information. Pre-initialize to 256x256 to not to * any mapsize information. Pre-initialize to 256x256 to not to
* confuse old games */ * confuse old games */
InitializeGame(256, 256, true, true); InitializeGame(256, 256, true, true);
GamelogReset(); GamelogReset();
if (CheckSavegameVersion(4)) { if (CheckSavegameVersion(4)) {
/* /*
* NewGRFs were introduced between 0.3,4 and 0.3.5, which both * NewGRFs were introduced between 0.3,4 and 0.3.5, which both
* shared savegame version 4. Anything before that 'obviously' * shared savegame version 4. Anything before that 'obviously'
* does not have any NewGRFs. Between the introduction and * does not have any NewGRFs. Between the introduction and
* savegame version 41 (just before 0.5) the NewGRF settings * savegame version 41 (just before 0.5) the NewGRF settings
* were not stored in the savegame and they were loaded by * were not stored in the savegame and they were loaded by
* using the settings from the main menu. * using the settings from the main menu.
* So, to recap: * So, to recap:
* - savegame version < 4: do not load any NewGRFs. * - savegame version < 4: do not load any NewGRFs.
* - savegame version >= 41: load NewGRFs from savegame, which is * - savegame version >= 41: load NewGRFs from savegame, which is
* already done at this stage by * already done at this stage by
* overwriting the main menu settings. * overwriting the main menu settings.
* - other savegame versions: use main menu settings. * - other savegame versions: use main menu settings.
* *
* This means that users *can* crash savegame version 4..40 * This means that users *can* crash savegame version 4..40
* savegames if they set incompatible NewGRFs in the main menu, * savegames if they set incompatible NewGRFs in the main menu,
* but can't crash anymore for savegame version < 4 savegames. * but can't crash anymore for savegame version < 4 savegames.
* *
* Note: this is done here because AfterLoadGame is also called * Note: this is done here because AfterLoadGame is also called
* for TTO/TTD/TTDP savegames which have their own NewGRF logic. * for TTO/TTD/TTDP savegames which have their own NewGRF logic.
*/ */
ClearGRFConfigList(&_grfconfig); ClearGRFConfigList(&_grfconfig);
}
} }
SlLoadChunks(); if (mode == SL_LOAD_CHECK) {
SlFixPointers(); /* 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(); fmt->uninit_read();
fclose(_sl.fh); fclose(_sl.fh);
GamelogStartAction(GLAT_LOAD);
_savegame_type = SGT_OTTD; _savegame_type = SGT_OTTD;
/* After loading fix up savegame for any internal changes that if (mode != SL_LOAD_CHECK) {
* might've occured since then. If it fails, load back the old game */ GamelogStartAction(GLAT_LOAD);
if (!AfterLoadGame()) {
GamelogStopAction();
return SL_REINIT;
}
GamelogStopAction(); /* 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_OK; return SL_OK;
@ -2045,7 +2146,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, int mode, Subdirectory sb, boo
if (_sl.excpt_uninit != NULL) _sl.excpt_uninit(); if (_sl.excpt_uninit != NULL) _sl.excpt_uninit();
/* Skip the "colour" character */ /* 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! */ /* A saver/loader exception!! reinitialize all variables to prevent crash! */
return (mode == SL_LOAD) ? SL_REINIT : SL_ERROR; return (mode == SL_LOAD) ? SL_REINIT : SL_ERROR;

View File

@ -27,12 +27,13 @@ enum SaveOrLoadResult {
}; };
enum SaveOrLoadMode { enum SaveOrLoadMode {
SL_INVALID = -1, SL_INVALID = -1,
SL_LOAD = 0, SL_LOAD = 0,
SL_SAVE = 1, SL_SAVE = 1,
SL_OLD_LOAD = 2, SL_OLD_LOAD = 2,
SL_PNG = 3, SL_PNG = 3,
SL_BMP = 4, SL_BMP = 4,
SL_LOAD_CHECK = 5,
}; };
enum SavegameType { enum SavegameType {
@ -216,6 +217,7 @@ typedef SaveLoad SaveLoadGlobVarList;
#define SLE_ARR(base, variable, type, length) SLE_CONDARR(base, variable, type, length, 0, SL_MAX_VERSION) #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_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_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) #define SLE_CONDNULL(length, from, to) SLE_CONDARR(NullStruct, null, SLE_FILE_U8 | SLE_VAR_NULL | SLF_CONFIG_NO, length, from, to)