2005-07-24 14:12:37 +00:00
|
|
|
/* $Id$ */
|
|
|
|
|
2009-01-21 02:07:56 +00:00
|
|
|
/** @file oldloader.cpp Functions for handling of TTO/TTD/TTDP savegames. */
|
2007-03-21 15:19:33 +00:00
|
|
|
|
2009-01-04 15:32:25 +00:00
|
|
|
#include "../stdafx.h"
|
|
|
|
#include "../openttd.h"
|
2009-01-21 02:07:56 +00:00
|
|
|
#include "../tile_type.h"
|
2009-01-04 15:32:25 +00:00
|
|
|
#include "../debug.h"
|
2009-01-21 02:07:56 +00:00
|
|
|
#include "../strings_type.h"
|
2009-01-18 22:44:53 +00:00
|
|
|
#include "../string_func.h"
|
2009-01-22 22:02:05 +00:00
|
|
|
#include "../settings_type.h"
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2008-01-13 01:21:35 +00:00
|
|
|
#include "table/strings.h"
|
|
|
|
|
2009-01-04 15:32:25 +00:00
|
|
|
#include "saveload_internal.h"
|
2009-01-20 16:47:42 +00:00
|
|
|
#include "oldloader.h"
|
2009-01-04 15:32:25 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
enum {
|
2009-01-23 02:35:17 +00:00
|
|
|
TTO_HEADER_SIZE = 41,
|
|
|
|
TTD_HEADER_SIZE = 49,
|
2007-03-07 12:11:48 +00:00
|
|
|
};
|
2006-04-22 12:55:57 +00:00
|
|
|
|
2009-01-20 16:47:42 +00:00
|
|
|
uint32 _bump_assert_value;
|
2008-05-05 22:03:01 +00:00
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
static inline OldChunkType GetOldChunkType(OldChunkType type) {return (OldChunkType)GB(type, 0, 4);}
|
2009-01-20 16:47:42 +00:00
|
|
|
static inline OldChunkType GetOldChunkVarType(OldChunkType type) {return (OldChunkType)(GB(type, 8, 8) << 8);}
|
|
|
|
static inline OldChunkType GetOldChunkFileType(OldChunkType type) {return (OldChunkType)(GB(type, 16, 8) << 16);}
|
2006-04-22 12:55:57 +00:00
|
|
|
|
|
|
|
static inline byte CalcOldVarLen(OldChunkType type)
|
|
|
|
{
|
|
|
|
static const byte type_mem_size[] = {0, 1, 1, 2, 2, 4, 4, 8};
|
|
|
|
byte length = GB(type, 8, 8);
|
|
|
|
assert(length != 0 && length < lengthof(type_mem_size));
|
|
|
|
return type_mem_size[length];
|
|
|
|
}
|
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Reads a byte from a file (do not call yourself, use ReadByte())
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static byte ReadByteFromFile(LoadgameState *ls)
|
|
|
|
{
|
|
|
|
/* To avoid slow reads, we read BUFFER_SIZE of bytes per time
|
|
|
|
and just return a byte per time */
|
|
|
|
if (ls->buffer_cur >= ls->buffer_count) {
|
|
|
|
/* Read some new bytes from the file */
|
2006-08-20 12:09:32 +00:00
|
|
|
int count = (int)fread(ls->buffer, 1, BUFFER_SIZE, ls->file);
|
2005-02-06 18:28:35 +00:00
|
|
|
|
|
|
|
/* We tried to read, but there is nothing in the file anymore.. */
|
|
|
|
if (count == 0) {
|
2006-12-26 17:36:18 +00:00
|
|
|
DEBUG(oldloader, 0, "Read past end of file, loading failed");
|
2005-02-06 18:28:35 +00:00
|
|
|
ls->failed = true;
|
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
ls->buffer_count = count;
|
|
|
|
ls->buffer_cur = 0;
|
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
return ls->buffer[ls->buffer_cur++];
|
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Reads a byte from the buffer and decompress if needed
|
|
|
|
*
|
|
|
|
*/
|
2009-01-20 16:47:42 +00:00
|
|
|
byte ReadByte(LoadgameState *ls)
|
2005-02-06 18:28:35 +00:00
|
|
|
{
|
|
|
|
/* Old savegames have a nice compression algorithm (RLE)
|
|
|
|
which means that we have a chunk, which starts with a length
|
|
|
|
byte. If that byte is negative, we have to repeat the next byte
|
2007-04-18 22:10:36 +00:00
|
|
|
that many times ( + 1). Else, we need to read that amount of bytes.
|
2005-02-06 18:28:35 +00:00
|
|
|
Works pretty good if you have many zero's behind eachother */
|
|
|
|
|
2005-09-23 20:20:08 +00:00
|
|
|
if (ls->chunk_size == 0) {
|
|
|
|
/* Read new chunk */
|
|
|
|
int8 new_byte = ReadByteFromFile(ls);
|
|
|
|
|
|
|
|
if (new_byte < 0) {
|
|
|
|
/* Repeat next char for new_byte times */
|
|
|
|
ls->decoding = true;
|
|
|
|
ls->decode_char = ReadByteFromFile(ls);
|
|
|
|
ls->chunk_size = -new_byte + 1;
|
|
|
|
} else {
|
|
|
|
ls->decoding = false;
|
|
|
|
ls->chunk_size = new_byte + 1;
|
|
|
|
}
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-09-23 20:20:08 +00:00
|
|
|
ls->total_read++;
|
|
|
|
ls->chunk_size--;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-09-23 20:20:08 +00:00
|
|
|
return ls->decoding ? ls->decode_char : ReadByteFromFile(ls);
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Loads a chunk from the old savegame
|
|
|
|
*
|
|
|
|
*/
|
2009-01-20 16:47:42 +00:00
|
|
|
bool LoadChunk(LoadgameState *ls, void *base, const OldChunks *chunks)
|
2005-02-06 18:28:35 +00:00
|
|
|
{
|
2007-01-10 18:56:51 +00:00
|
|
|
byte *base_ptr = (byte*)base;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
for (const OldChunks *chunk = chunks; chunk->type != OC_END; chunk++) {
|
|
|
|
if (((chunk->type & OC_TTD) && (_savegame_type == SGT_TTO)) ||
|
|
|
|
((chunk->type & OC_TTO) && (_savegame_type != SGT_TTO))) {
|
|
|
|
/* TTD(P)-only chunk, but TTO savegame || TTO-only chunk, but TTD/TTDP savegame */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-04-19 08:21:55 +00:00
|
|
|
byte *ptr = (byte*)chunk->ptr;
|
2009-01-23 02:35:17 +00:00
|
|
|
if (chunk->type & OC_DEREFERENCE_POINTER) ptr = *(byte**)ptr;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2008-04-19 08:21:55 +00:00
|
|
|
for (uint i = 0; i < chunk->amount; i++) {
|
2006-04-22 12:53:35 +00:00
|
|
|
if (ls->failed) return false;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
/* Handle simple types */
|
2006-04-22 12:55:57 +00:00
|
|
|
if (GetOldChunkType(chunk->type) != 0) {
|
|
|
|
switch (GetOldChunkType(chunk->type)) {
|
2006-04-22 12:53:35 +00:00
|
|
|
/* Just read the byte and forget about it */
|
|
|
|
case OC_NULL: ReadByte(ls); break;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
case OC_CHUNK:
|
|
|
|
/* Call function, with 'i' as parameter to tell which item we
|
2006-02-01 06:32:03 +00:00
|
|
|
* are going to read */
|
|
|
|
if (!chunk->proc(ls, i)) return false;
|
2005-02-06 18:28:35 +00:00
|
|
|
break;
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
case OC_ASSERT:
|
2006-12-26 17:36:18 +00:00
|
|
|
DEBUG(oldloader, 4, "Assert point: 0x%X / 0x%X", ls->total_read, chunk->offset + _bump_assert_value);
|
2006-04-22 12:53:35 +00:00
|
|
|
if (ls->total_read != chunk->offset + _bump_assert_value) ls->failed = true;
|
2006-04-22 13:42:09 +00:00
|
|
|
default: break;
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
|
|
|
} else {
|
2007-10-16 21:16:30 +00:00
|
|
|
uint64 res = 0;
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2006-04-22 12:55:57 +00:00
|
|
|
/* Reading from the file: bits 16 to 23 have the FILE type */
|
|
|
|
switch (GetOldChunkFileType(chunk->type)) {
|
|
|
|
case OC_FILE_I8: res = (int8)ReadByte(ls); break;
|
|
|
|
case OC_FILE_U8: res = ReadByte(ls); break;
|
|
|
|
case OC_FILE_I16: res = (int16)ReadUint16(ls); break;
|
|
|
|
case OC_FILE_U16: res = ReadUint16(ls); break;
|
|
|
|
case OC_FILE_I32: res = (int32)ReadUint32(ls); break;
|
|
|
|
case OC_FILE_U32: res = ReadUint32(ls); break;
|
|
|
|
default: NOT_REACHED();
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Sanity check */
|
2005-02-06 18:41:15 +00:00
|
|
|
assert(base_ptr != NULL || chunk->ptr != NULL);
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2006-04-22 12:55:57 +00:00
|
|
|
/* Writing to the var: bits 8 to 15 have the VAR type */
|
|
|
|
if (chunk->ptr == NULL) ptr = base_ptr + chunk->offset;
|
|
|
|
|
|
|
|
/* Write the data */
|
|
|
|
switch (GetOldChunkVarType(chunk->type)) {
|
|
|
|
case OC_VAR_I8: *(int8 *)ptr = GB(res, 0, 8); break;
|
|
|
|
case OC_VAR_U8: *(uint8 *)ptr = GB(res, 0, 8); break;
|
|
|
|
case OC_VAR_I16:*(int16 *)ptr = GB(res, 0, 16); break;
|
|
|
|
case OC_VAR_U16:*(uint16*)ptr = GB(res, 0, 16); break;
|
|
|
|
case OC_VAR_I32:*(int32 *)ptr = res; break;
|
|
|
|
case OC_VAR_U32:*(uint32*)ptr = res; break;
|
|
|
|
case OC_VAR_I64:*(int64 *)ptr = res; break;
|
2009-01-25 22:50:00 +00:00
|
|
|
case OC_VAR_U64:*(uint64*)ptr = res; break;
|
2006-04-22 12:55:57 +00:00
|
|
|
default: NOT_REACHED();
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
2006-04-22 12:55:57 +00:00
|
|
|
|
|
|
|
/* Increase pointer base for arrays when looping */
|
|
|
|
if (chunk->amount > 1 && chunk->ptr != NULL) ptr += CalcOldVarLen(chunk->type);
|
2005-02-06 18:28:35 +00:00
|
|
|
}
|
2004-08-09 17:04:08 +00:00
|
|
|
}
|
2004-09-10 19:02:27 +00:00
|
|
|
}
|
2005-02-06 18:28:35 +00:00
|
|
|
|
|
|
|
return true;
|
2004-08-09 17:04:08 +00:00
|
|
|
}
|
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Initialize some data before reading
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void InitLoading(LoadgameState *ls)
|
2004-08-09 17:04:08 +00:00
|
|
|
{
|
2005-02-06 18:28:35 +00:00
|
|
|
ls->chunk_size = 0;
|
|
|
|
ls->total_read = 0;
|
|
|
|
ls->failed = false;
|
|
|
|
|
|
|
|
ls->decoding = false;
|
|
|
|
ls->decode_char = 0;
|
|
|
|
|
|
|
|
ls->buffer_cur = 0;
|
|
|
|
ls->buffer_count = 0;
|
|
|
|
memset(ls->buffer, 0, BUFFER_SIZE);
|
|
|
|
|
|
|
|
_bump_assert_value = 0;
|
2009-01-22 22:02:05 +00:00
|
|
|
|
|
|
|
_settings_game.construction.freeform_edges = false; // disable so we can convert map array (SetTileType is still used)
|
2007-02-18 22:50:51 +00:00
|
|
|
}
|
|
|
|
|
2009-01-18 23:26:38 +00:00
|
|
|
/**
|
|
|
|
* Verifies the title has a valid checksum
|
|
|
|
* @param title title and checksum
|
|
|
|
* @return true iff the title is valid
|
2009-01-23 02:35:17 +00:00
|
|
|
* @note the title (incl. checksum) has to be at least 41/49 (HEADER_SIZE) bytes long!
|
2009-01-18 23:26:38 +00:00
|
|
|
*/
|
2009-01-23 02:35:17 +00:00
|
|
|
static bool VerifyOldNameChecksum(char *title, uint len)
|
2009-01-18 23:26:38 +00:00
|
|
|
{
|
|
|
|
uint16 sum = 0;
|
2009-01-23 02:35:17 +00:00
|
|
|
for (uint i = 0; i < len - 2; i++) {
|
2009-01-18 23:26:38 +00:00
|
|
|
sum += title[i];
|
|
|
|
sum = ROL(sum, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
sum ^= 0xAAAA; // computed checksum
|
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
uint16 sum2 = title[len - 2]; // checksum in file
|
|
|
|
SB(sum2, 8, 8, title[len - 1]);
|
2009-01-18 23:26:38 +00:00
|
|
|
|
|
|
|
return sum == sum2;
|
|
|
|
}
|
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
static inline bool CheckOldSavegameType(FILE *f, char *temp, const char *last, uint len)
|
|
|
|
{
|
2009-01-23 09:47:46 +00:00
|
|
|
assert(last - temp + 1 >= (int)len);
|
2009-01-23 02:35:17 +00:00
|
|
|
|
|
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
if (fread(temp, 1, len, f) != len) {
|
|
|
|
temp[0] = '\0'; // if reading failed, make the name empty
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ret = VerifyOldNameChecksum(temp, len);
|
|
|
|
temp[len - 2] = '\0'; // name is nul-terminated in savegame, but it's better to be sure
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_compile(TTD_HEADER_SIZE >= TTO_HEADER_SIZE);
|
|
|
|
static SavegameType DetermineOldSavegameType(FILE *f, char *title, const char *last)
|
|
|
|
{
|
|
|
|
char temp[TTD_HEADER_SIZE];
|
|
|
|
|
|
|
|
SavegameType type = SGT_TTO;
|
|
|
|
|
|
|
|
if (!CheckOldSavegameType(f, temp, lastof(temp), TTO_HEADER_SIZE)) {
|
|
|
|
type = SGT_TTD;
|
|
|
|
if (!CheckOldSavegameType(f, temp, lastof(temp), TTD_HEADER_SIZE)) {
|
|
|
|
type = SGT_INVALID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (title != NULL) {
|
|
|
|
switch (type) {
|
|
|
|
case SGT_TTO: title = strecpy(title, "(TTO) ", last); break;
|
|
|
|
case SGT_TTD: title = strecpy(title, "(TTD) ", last); break;
|
|
|
|
default: title = strecpy(title, "(broken) ", last); break;
|
|
|
|
}
|
|
|
|
title = strecpy(title, temp, last);
|
|
|
|
}
|
|
|
|
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef bool LoadOldMainProc(LoadgameState *ls);
|
|
|
|
|
2005-02-06 18:28:35 +00:00
|
|
|
bool LoadOldSaveGame(const char *file)
|
|
|
|
{
|
2005-09-23 20:13:48 +00:00
|
|
|
LoadgameState ls;
|
|
|
|
|
2007-02-14 20:19:07 +00:00
|
|
|
DEBUG(oldloader, 3, "Trying to load a TTD(Patch) savegame");
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2005-09-23 20:13:48 +00:00
|
|
|
InitLoading(&ls);
|
2005-02-06 18:28:35 +00:00
|
|
|
|
|
|
|
/* Open file */
|
2005-09-23 20:13:48 +00:00
|
|
|
ls.file = fopen(file, "rb");
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2005-09-23 20:13:48 +00:00
|
|
|
if (ls.file == NULL) {
|
2006-12-26 17:36:18 +00:00
|
|
|
DEBUG(oldloader, 0, "Cannot open file '%s'", file);
|
2005-02-06 18:28:35 +00:00
|
|
|
return false;
|
2004-08-09 17:04:08 +00:00
|
|
|
}
|
2004-09-10 19:02:27 +00:00
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
SavegameType type = DetermineOldSavegameType(ls.file, NULL, NULL);
|
|
|
|
|
|
|
|
LoadOldMainProc *proc = NULL;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case SGT_TTO: proc = &LoadTTOMain; break;
|
|
|
|
case SGT_TTD: proc = &LoadTTDMain; break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
_savegame_type = type;
|
|
|
|
|
|
|
|
if (proc == NULL || !proc(&ls)) {
|
2009-01-18 23:26:38 +00:00
|
|
|
SetSaveLoadError(STR_GAME_SAVELOAD_ERROR_DATA_INTEGRITY_CHECK_FAILED);
|
|
|
|
fclose(ls.file);
|
|
|
|
return false;
|
|
|
|
}
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2007-03-06 20:59:52 +00:00
|
|
|
_pause_game = 2;
|
2005-02-07 19:23:38 +00:00
|
|
|
|
2004-08-09 17:04:08 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-01-18 22:44:53 +00:00
|
|
|
void GetOldSaveGameName(const char *path, const char *file, char *title, const char *last)
|
2004-08-09 17:04:08 +00:00
|
|
|
{
|
2006-08-05 00:47:32 +00:00
|
|
|
char filename[MAX_PATH];
|
|
|
|
|
|
|
|
snprintf(filename, lengthof(filename), "%s" PATHSEP "%s", path, file);
|
2009-01-18 22:44:53 +00:00
|
|
|
FILE *f = fopen(filename, "rb");
|
2004-08-09 17:04:08 +00:00
|
|
|
|
2009-01-18 23:33:57 +00:00
|
|
|
if (f == NULL) {
|
2009-01-19 00:53:31 +00:00
|
|
|
*title = '\0';
|
2009-01-18 23:33:57 +00:00
|
|
|
return;
|
2009-01-19 00:53:31 +00:00
|
|
|
}
|
2005-02-06 19:22:54 +00:00
|
|
|
|
2009-01-23 02:35:17 +00:00
|
|
|
DetermineOldSavegameType(f, title, last);
|
2005-02-06 18:28:35 +00:00
|
|
|
|
2004-08-09 17:04:08 +00:00
|
|
|
fclose(f);
|
|
|
|
}
|