https://github.com/scummvm/scummvm
Raw File
Tip revision: cd7e6cd31cd96c88689ba4769897c062e42e1353 authored by Thierry Crozat on 30 November 2017, 00:10:21 UTC
RELEASE: This is 2.1.0git
Tip revision: cd7e6cd
savegame.cpp
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "lastexpress/game/savegame.h"

#include "lastexpress/game/entities.h"
#include "lastexpress/game/inventory.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/game/object.h"
#include "lastexpress/game/savepoint.h"
#include "lastexpress/game/state.h"

#include "lastexpress/menu/menu.h"

#include "lastexpress/sound/queue.h"

#include "lastexpress/debug.h"
#include "lastexpress/lastexpress.h"

#include "common/file.h"

namespace LastExpress {

#define DISABLE_COMPRESSION 1

// Names of savegames
static const struct {
	const char *saveFile;
} gameInfo[6] = {
	{"lastexpress-blue.egg"},
	{"lastexpress-red.egg"},
	{"lastexpress-green.egg"},
	{"lastexpress-purple.egg"},
	{"lastexpress-teal.egg"},
	{"lastexpress-gold.egg"}
};

//////////////////////////////////////////////////////////////////////////
// SavegameStream
//////////////////////////////////////////////////////////////////////////

uint32 SavegameStream::write(const void *dataPtr, uint32 dataSize) {
#if !DISABLE_COMPRESSION
	if (_enableCompression)
		return writeCompressed(dataPtr, dataSize);
#endif

	return Common::MemoryWriteStreamDynamic::write(dataPtr, dataSize);
}

uint32 SavegameStream::read(void *dataPtr, uint32 dataSize) {
#if !DISABLE_COMPRESSION
	if (_enableCompression)
		return readCompressed(dataPtr, dataSize);
#endif

	return readUncompressed(dataPtr, dataSize);
}

uint32 SavegameStream::readUncompressed(void *dataPtr, uint32 dataSize) {
	if ((int32)dataSize > size() - pos()) {
		dataSize = (uint32)(size() - pos());
		_eos = true;
	}
	memcpy(dataPtr, getData() + pos(), dataSize);

	seek(dataSize, SEEK_CUR);

	return dataSize;
}

void SavegameStream::writeBuffer(uint8 value, bool onlyValue) {
	if (_bufferOffset == -1)
		_bufferOffset = 0;

	if (_bufferOffset == 256) {
		_bufferOffset = 0;
		Common::MemoryWriteStreamDynamic::write(_buffer, 256);
	}

	if (onlyValue || value < 0xFB)
		_buffer[_bufferOffset] = value;
	else
		_buffer[_bufferOffset] = 0xFE;

	_offset++;
	_bufferOffset++;

	if (!onlyValue && value >= 0xFB)
	{
		if (_bufferOffset == 256) {
			_bufferOffset = 0;
			Common::MemoryWriteStreamDynamic::write(_buffer, 256);
		}

		_buffer[_bufferOffset] = value;

		_bufferOffset++;
		_offset++;
	}
}

uint8 SavegameStream::readBuffer() {
	if (_bufferOffset == -1 || _bufferOffset >= 256) {
		readUncompressed(_buffer, 256);
		_bufferOffset = 0;
	}

	byte val = _buffer[_bufferOffset];
	_bufferOffset++;

	return val;
}

uint32 SavegameStream::process() {
	_enableCompression = !_enableCompression;

#if DISABLE_COMPRESSION
	return 0;
#else
	switch (_status) {
	default:
		break;

	case kStatusReading:
		_status = kStatusReady;
		if (_bufferOffset != -1 && _bufferOffset != 256) {
			seek(_bufferOffset - 256, SEEK_CUR);
			_bufferOffset = -1;
		}
		break;

	case kStatusWriting:
		switch (_valueCount) {
		default:
			break;

		case 1:
			writeBuffer(_previousValue, false);
			break;

		case 2:
			if (_previousValue) {
				writeBuffer(0xFF);
				writeBuffer(_repeatCount);
				writeBuffer(_previousValue);
				break;
			}

			if (_repeatCount == 3) {
				writeBuffer(0xFB);
				break;
			}

			if (_repeatCount == 255) {
				writeBuffer(0xFC);
				break;
			}

			writeBuffer(0xFD);
			writeBuffer(_repeatCount);
			break;
		}

		if (_bufferOffset != -1 && _bufferOffset != 0) {
			Common::MemoryWriteStreamDynamic::write(_buffer, _bufferOffset);
			_bufferOffset = -1;
		}
		break;
	}

	_status = kStatusReady;
	_valueCount = 0;
	uint32 offset = _offset;
	_offset = 0;

	return offset;
#endif
}

uint32 SavegameStream::writeCompressed(const void *dataPtr, uint32 dataSize) {
	if (_status == kStatusReading)
		error("[SavegameStream::writeCompressed] Error: Compression buffer is in read mode.");

	_status = kStatusWriting;
	const byte *data = (const byte *)dataPtr;

	while (dataSize) {
		switch (_valueCount) {
		default:
			error("[SavegameStream::writeCompressed] Invalid value count (%d)", _valueCount);

		case 0:
			_previousValue = *data++;
			_valueCount = 1;
			break;

		case 1:
			if (*data != _previousValue) {
				writeBuffer(_previousValue, false);
				_previousValue = *data;
			} else {
				_valueCount = 2;
				_repeatCount = 2;
			}

			++data;
			break;

		case 2:
			if (*data != _previousValue || _repeatCount >= 255) {
				if (_previousValue) {
					writeBuffer(0xFF, true);
					writeBuffer((uint8)_repeatCount, true);
					writeBuffer(_previousValue, true);

					_previousValue = *data++;
					_valueCount = 1;
					break;
				}

				if (_repeatCount == 3) {
					writeBuffer(0xFB, true);

					_previousValue = *data++;
					_valueCount = 1;
					break;
				}

				if (_repeatCount == -1) {
					writeBuffer(0xFC, true);

					_previousValue = *data++;
					_valueCount = 1;
					break;
				}

				writeBuffer(0xFD, true);
				writeBuffer((uint8)_repeatCount, true);

				_previousValue = *data++;
				_valueCount = 1;
			}

			++data;
			++_repeatCount;
			break;
		}

		--dataSize;
	}

	return _offset;
}

uint32 SavegameStream::readCompressed(void *dataPtr, uint32 dataSize) {
	if (_status == kStatusWriting)
		error("[SavegameStream::writeCompressed] Error: Compression buffer is in write mode.");

	_status = kStatusReady;
	byte *data = (byte *)dataPtr;

	while (dataSize) {
		switch (_valueCount) {
		default:
			error("[SavegameStream::readCompressed] Invalid value count (%d)", _valueCount);

		case 0:
		case 1: {
			// Read control code
			byte control = readBuffer();

			switch (control) {
			default:
				// Data value
				*data++ = control;
				break;

			case 0xFB:
				_repeatCount = 2;
				_previousValue = 0;
				*data++ = 0;
				_valueCount = 2;
				break;

			case 0xFC:
				_repeatCount = 254;
				_previousValue = 0;
				*data++ = 0;
				_valueCount = 2;
				break;

			case 0xFD:
				_repeatCount = readBuffer() - 1;
				_previousValue = 0;
				*data++ = 0;
				_valueCount = 2;
				break;

			case 0xFE:
				*data++ = readBuffer();
				break;

			case 0xFF:
				_repeatCount = readBuffer() - 1;
				_previousValue = readBuffer();
				*data++ = _previousValue;
				_valueCount = 2;
				break;
			}
			}
			break;

		case 2:
			*data++ = _previousValue;
			_repeatCount--;
			if (!_repeatCount)
				_valueCount = 1;
			break;
		}

		--dataSize;
	}

	return _offset;
}

//////////////////////////////////////////////////////////////////////////
// Constructors
//////////////////////////////////////////////////////////////////////////

SaveLoad::SaveLoad(LastExpressEngine *engine) : _engine(engine), _savegame(NULL), _gameTicksLastSavegame(0), _entity(kEntityPlayer) {
}

SaveLoad::~SaveLoad() {
	clear(true);
	_savegame = NULL;

	// Zero passed pointers
	_engine = NULL;
}

void SaveLoad::initStream() {
	delete _savegame;
	_savegame = new SavegameStream();
}

void SaveLoad::flushStream(GameId id) {
	Common::OutSaveFile *save = openForSaving(id);
	if (!save)
		error("[SaveLoad::flushStream] Cannot open savegame (%s)", getFilename(id).c_str());

	if (!_savegame)
		error("[SaveLoad::flushStream] Savegame stream is invalid");

	save->write(_savegame->getData(), (uint32)_savegame->size());
	save->finalize();

	delete save;
}

//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////
void SaveLoad::create(GameId id) {
	initStream();

	Common::Serializer ser(NULL, _savegame);
	SavegameMainHeader header;
	header.saveLoadWithSerializer(ser);

	flushStream(id);
}

uint32 SaveLoad::init(GameId id, bool resetHeaders) {
	initStream();

	// Load game data
	loadStream(id);

	// Get the main header
	Common::Serializer ser(_savegame, NULL);
	SavegameMainHeader mainHeader;
	mainHeader.saveLoadWithSerializer(ser);
	if (!mainHeader.isValid())
		error("[SaveLoad::init] Savegame seems to be corrupted (invalid header)");

	// Reset cached entry headers if needed
	if (resetHeaders) {
		clear();

		SavegameEntryHeader *entryHeader = new SavegameEntryHeader();
		entryHeader->time = kTimeCityParis;
		entryHeader->chapter = kChapter1;

		_gameHeaders.push_back(entryHeader);
	}

	// Read the list of entry headers
	if (_savegame->size() > 32) {
		while (_savegame->pos() < _savegame->size() && !_savegame->eos() && !_savegame->err()) {

			// Update sound queue while we go through the savegame
			getSoundQueue()->updateQueue();

			SavegameEntryHeader *entry = new SavegameEntryHeader();
			entry->saveLoadWithSerializer(ser);

			if (!entry->isValid())
				break;

			_gameHeaders.push_back(entry);

			_savegame->seek(entry->offset, SEEK_CUR);
		}
	}

	// return the index to the current save game entry (we store count + 1 entries, so we're good)
	return mainHeader.count;
}

void SaveLoad::loadStream(GameId id) {
	Common::InSaveFile *save = openForLoading(id);
	if (save->size() < 32)
		error("[SaveLoad::loadStream] Savegame seems to be corrupted (not enough data: %i bytes)", save->size());

	if (!_savegame)
		error("[SaveLoad::loadStream] Savegame stream is invalid");

	// Load all savegame data
	uint8 *buf = new uint8[8192];
	while (!save->eos() && !save->err()) {
		_engine->pollEvents();

		uint32 count = save->read(buf, 8192);
		if (count) {
			uint32 w = _savegame->write(buf, count);
			assert (w == count);
		}
	}

	if (save->err())
		error("SaveLoad::init - Error reading savegame");

	delete[] buf;
	delete save;

	// Move back to the beginning of the stream
	_savegame->seek(0);
}

void SaveLoad::clear(bool clearStream) {
	for (uint i = 0; i < _gameHeaders.size(); i++)
		SAFE_DELETE(_gameHeaders[i]);

	_gameHeaders.clear();

	if (clearStream)
		SAFE_DELETE(_savegame);
}

//////////////////////////////////////////////////////////////////////////
// Save & Load
//////////////////////////////////////////////////////////////////////////

// Load last saved game
void SaveLoad::loadLastGame() {
	if (!_savegame)
		error("[SaveLoad::loadLastGame] No savegame stream present");

	// Rewind current savegame
	_savegame->seek(0);

	// Validate main header
	SavegameMainHeader header;
	if (!loadMainHeader(_savegame, &header)) {
		debugC(2, kLastExpressDebugSavegame, "Cannot load main header: %s", getFilename(getMenu()->getGameId()).c_str());
		return;
	}

	// Load the last entry
	_savegame->seek(header.offsetEntry);

	SavegameType type = kSavegameTypeIndex;
	EntityIndex entity = kEntityPlayer;
	uint32 val = 0;
	readEntry(&type, &entity, &val, header.keepIndex == 1);

	// Setup last loading time
	_gameTicksLastSavegame = getState()->timeTicks;

	if (header.keepIndex) {
		getSoundQueue()->clearQueue();

		readEntry(&type, &entity, &val, false);
	}

	getEntities()->reset();
	getEntities()->setup(false, entity);
}

// Load a specific game entry
void SaveLoad::loadGame(uint32 index) {
	if (!_savegame)
		error("[SaveLoad::loadLastGame] No savegame stream present");

	// Rewind current savegame
	_savegame->seek(0);

	// Write main header (with selected index)
	SavegameMainHeader header;
	header.count = index;
	header.brightness = getState()->brightness;
	header.volume = getState()->volume;

	Common::Serializer ser(NULL, _savegame);
	header.saveLoadWithSerializer(ser);

	// TODO
	// Go to the entry
	// Load the entry
	// Get offset (main and entry)
	// Write main header again with correct entry offset
	// Setup game and start

	error("[SaveLoad::loadGame] Not implemented! (only loading the last entry is working for now)");
}

// Save game
void SaveLoad::saveGame(SavegameType type, EntityIndex entity, uint32 value) {
	if (getState()->scene <= kSceneIntro)
		return;

	// Validate main header
	SavegameMainHeader header;
	if (!loadMainHeader(_savegame, &header)) {
		debugC(2, kLastExpressDebugSavegame, "Cannot load main header: %s", getFilename(getMenu()->getGameId()).c_str());
		return;
	}

	if (!_savegame)
		error("[SaveLoad::saveGame] Savegame stream is invalid");

	// Validate the current entry if it exists
	if (header.count > 0) {
		_savegame->seek(header.offsetEntry);

		// Load entry header
		SavegameEntryHeader entry;
		Common::Serializer ser(_savegame, NULL);
		entry.saveLoadWithSerializer(ser);

		if (!entry.isValid()) {
			warning("[SaveLoad::saveGame] Invalid entry. This savegame might be corrupted");
			_savegame->seek(header.offset);
		} else if (getState()->time < entry.time || (type == kSavegameTypeTickInterval && getState()->time == entry.time)) {
			// Not ready to save a game, skipping!
			return;
		} else if ((type == kSavegameTypeTime || type == kSavegameTypeEvent)
			&& (entry.type == kSavegameTypeTickInterval && (getState()->time - entry.time) < 450)) {
			_savegame->seek(header.offsetEntry);
			--header.count;
		} else {
			_savegame->seek(header.offset);
		}
	} else {
		// Seek to the next savegame entry
		_savegame->seek(header.offset);
	}

	if (type != kSavegameTypeEvent2 && type != kSavegameTypeAuto)
		header.offsetEntry = (uint32)_savegame->pos();

	// Write the savegame entry
	writeEntry(type, entity, value);

	if (!header.keepIndex)
		++header.count;

	if (type == kSavegameTypeEvent2 || type == kSavegameTypeAuto) {
		header.keepIndex = 1;
	} else {
		header.keepIndex = 0;
		header.offset = (uint32)_savegame->pos();

		// Save ticks
		_gameTicksLastSavegame = getState()->timeTicks;
	}

	// Validate the main header
	if (!header.isValid())
		error("[SaveLoad::saveGame] Main game header is invalid");

	// Write the main header
	_savegame->seek(0);
	Common::Serializer ser(NULL, _savegame);
	header.saveLoadWithSerializer(ser);

	flushStream(getMenu()->getGameId());
}

void SaveLoad::saveVolumeBrightness() {
	warning("[SaveLoad::saveVolumeBrightness] Not implemented");
}

//////////////////////////////////////////////////////////////////////////
// Headers
//////////////////////////////////////////////////////////////////////////
bool SaveLoad::loadMainHeader(Common::InSaveFile *stream, SavegameMainHeader *header) {
	if (!stream)
		return false;

	// Check there is enough data (32 bytes)
	if (stream->size() < 32) {
		debugC(2, kLastExpressDebugSavegame, "Savegame seems to be corrupted (not enough data: %i bytes)", stream->size());
		return false;
	}

	// Rewind stream
	stream->seek(0);

	Common::Serializer ser(stream, NULL);
	header->saveLoadWithSerializer(ser);

	// Validate the header
	if (!header->isValid()) {
		debugC(2, kLastExpressDebugSavegame, "Cannot validate main header");
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
// Entries
//////////////////////////////////////////////////////////////////////////
uint32 SaveLoad::writeValue(Common::Serializer &ser, const char *name, Common::Functor1<Common::Serializer &, void> *function, uint size) {
	if (!_savegame)
		error("[SaveLoad::writeValue] Stream not initialized properly");

	debugC(kLastExpressDebugSavegame, "Savegame: Writing %s: %u bytes", name, size);

	uint32 prevPosition = (uint32)_savegame->pos();

	// Serialize data into our buffer
	(*function)(ser);

	uint32 count = (uint32)_savegame->pos() - prevPosition;

#if DISABLE_COMPRESSION
	if (count != size)
		error("[SaveLoad::writeValue] %s - Number of bytes written (%d) differ from expected count (%d)", name, count, size);
#endif

	return count;
}

uint32 SaveLoad::readValue(Common::Serializer &ser, const char *name, Common::Functor1<Common::Serializer &, void> *function, uint size) {
	if (!_savegame)
		error("[SaveLoad::readValue] Stream not initialized properly");

	debugC(kLastExpressDebugSavegame, "Savegame: Reading %s: %u bytes", name, size);

	uint32 prevPosition = (uint32)_savegame->pos();

	(*function)(ser);

	uint32 count = (uint32)_savegame->pos() - prevPosition;

#if DISABLE_COMPRESSION
	if (size != 0 && count != size)
		error("[SaveLoad::readValue] %s - Number of bytes read (%d) differ from expected count (%d)", name, count, size);
#endif

	return count;
}

void SaveLoad::syncEntity(Common::Serializer &ser) {
	ser.syncAsUint32LE(_entity);
}

void SaveLoad::writeEntry(SavegameType type, EntityIndex entity, uint32 value) {
	if (!_savegame)
		error("[SaveLoad::writeEntry] Savegame stream is invalid");

	SavegameEntryHeader header;

	header.type = type;
	header.time = (uint32)getState()->time;
	header.chapter = getProgress().chapter;
	header.value = value;

	// Save position
	uint32 originalPosition = (uint32)_savegame->pos();

	// Write header
	Common::Serializer ser(NULL, _savegame);
	header.saveLoadWithSerializer(ser);

	// Write game data
	_entity = entity;

	_savegame->process();
	writeValue(ser, "entity index", WRAP_SYNC_FUNCTION(this, SaveLoad, syncEntity), 4);
	writeValue(ser, "state", WRAP_SYNC_FUNCTION(getState(), State::GameState, saveLoadWithSerializer), 4 + 4 + 4 + 4 + 1 + 4 + 4);
	writeValue(ser, "selected item", WRAP_SYNC_FUNCTION(getInventory(), Inventory, saveSelectedItem), 4);
	writeValue(ser, "positions", WRAP_SYNC_FUNCTION(getEntities(), Entities, savePositions), 4 * 1000);
	writeValue(ser, "compartments", WRAP_SYNC_FUNCTION(getEntities(), Entities, saveCompartments), 4 * 16 * 2);
	writeValue(ser, "progress", WRAP_SYNC_FUNCTION(&getProgress(), State::GameProgress, saveLoadWithSerializer), 4 * 128);
	writeValue(ser, "events", WRAP_SYNC_FUNCTION(getState(), State::GameState, syncEvents), 512);
	writeValue(ser, "inventory", WRAP_SYNC_FUNCTION(getInventory(), Inventory, saveLoadWithSerializer), 7 * 32);
	writeValue(ser, "objects", WRAP_SYNC_FUNCTION(getObjects(), Objects, saveLoadWithSerializer), 5 * 128);
	writeValue(ser, "entities", WRAP_SYNC_FUNCTION(getEntities(), Entities, saveLoadWithSerializer), 1262 * 40);
	writeValue(ser, "sound", WRAP_SYNC_FUNCTION(getSoundQueue(), SoundQueue, saveLoadWithSerializer), 3 * 4 + getSoundQueue()->count() * 64);
	writeValue(ser, "savepoints", WRAP_SYNC_FUNCTION(getSavePoints(), SavePoints, saveLoadWithSerializer), 128 * 16 + 4 + getSavePoints()->count() * 16);
	_savegame->process();

	header.offset = (uint32)_savegame->pos() - (originalPosition + 32);

	// Add padding if necessary
	while (header.offset & 0xF) {
		_savegame->writeByte(0);
		header.offset++;
	}

	// Save end position
	uint32 endPosition = (uint32)_savegame->pos();

	// Validate entry header
	if (!header.isValid())
		error("[SaveLoad::writeEntry] Entry header is invalid");

	// Save the header with the updated info
	_savegame->seek(originalPosition);
	header.saveLoadWithSerializer(ser);

	// Move back to the end of the entry
	_savegame->seek(endPosition);
}

void SaveLoad::readEntry(SavegameType *type, EntityIndex *entity, uint32 *val, bool keepIndex) {
	if (!type || !entity || !val)
		error("[SaveLoad::readEntry] Invalid parameters passed");

	if (!_savegame)
		error("[SaveLoad::readEntry] No savegame stream present");

	// Load entry header
	SavegameEntryHeader entry;
	Common::Serializer ser(_savegame, NULL);
	entry.saveLoadWithSerializer(ser);

	if (!entry.isValid())
		error("[SaveLoad::readEntry] Entry header is invalid");

	// Init type, entity & value
	*type = entry.type;
	*val = entry.value;

	// Save position
	uint32 originalPosition = (uint32)_savegame->pos();

	// Load game data
	_savegame->process();
	readValue(ser, "entity index", WRAP_SYNC_FUNCTION(this, SaveLoad, syncEntity), 4);
	readValue(ser, "state", WRAP_SYNC_FUNCTION(getState(), State::GameState, saveLoadWithSerializer), 4 + 4 + 4 + 4 + 1 + 4 + 4);
	readValue(ser, "selected item", WRAP_SYNC_FUNCTION(getInventory(), Inventory, saveSelectedItem), 4);
	readValue(ser, "positions", WRAP_SYNC_FUNCTION(getEntities(), Entities, savePositions), 4 * 1000);
	readValue(ser, "compartments", WRAP_SYNC_FUNCTION(getEntities(), Entities, saveCompartments), 4 * 16 * 2);
	readValue(ser, "progress", WRAP_SYNC_FUNCTION(&getProgress(), State::GameProgress, saveLoadWithSerializer), 4 * 128);
	readValue(ser, "events", WRAP_SYNC_FUNCTION(getState(), State::GameState, syncEvents), 512);
	readValue(ser, "inventory", WRAP_SYNC_FUNCTION(getInventory(), Inventory, saveLoadWithSerializer), 7 * 32);
	readValue(ser, "objects", WRAP_SYNC_FUNCTION(getObjects(), Objects, saveLoadWithSerializer), 5 * 128);
	readValue(ser, "entities", WRAP_SYNC_FUNCTION(getEntities(), Entities, saveLoadWithSerializer), 1262 * 40);
	readValue(ser, "sound", WRAP_SYNC_FUNCTION(getSoundQueue(), SoundQueue, saveLoadWithSerializer));
	readValue(ser, "savepoints", WRAP_SYNC_FUNCTION(getSavePoints(), SavePoints, saveLoadWithSerializer));
	_savegame->process();

	// Update chapter
	*entity = _entity;
	getProgress().chapter = entry.chapter;

	// Skip padding
	uint32 offset = (uint32)_savegame->pos() - originalPosition;
	if (offset & 0xF) {
		_savegame->seek((~offset & 0xF) + 1, SEEK_SET);
	}
}

SaveLoad::SavegameEntryHeader *SaveLoad::getEntry(uint32 index) {
	if (index >= _gameHeaders.size())
		error("[SaveLoad::getEntry] Invalid index (was:%d, max:%d)", index, _gameHeaders.size() - 1);

	return _gameHeaders[index];
}

//////////////////////////////////////////////////////////////////////////
// Checks
//////////////////////////////////////////////////////////////////////////

// Check if a specific savegame exists
bool SaveLoad::isSavegamePresent(GameId id) {
	if (g_system->getSavefileManager()->listSavefiles(getFilename(id)).size() == 0)
		return false;

	return true;
}

// Check if the game has been started in the specific savegame
bool SaveLoad::isSavegameValid(GameId id) {
	if (!isSavegamePresent(id)) {
		debugC(2, kLastExpressDebugSavegame, "Savegame does not exist: %s", getFilename(id).c_str());
		return false;
	}

	SavegameMainHeader header;

	Common::InSaveFile *save = openForLoading(id);
	bool isHeaderValid = loadMainHeader(save, &header);
	delete save;

	return isHeaderValid;
}

bool SaveLoad::isGameFinished(uint32 menuIndex, uint32 savegameIndex) {
	SavegameEntryHeader *data = getEntry(menuIndex);

	if (savegameIndex != menuIndex)
		return false;

	if (data->type != kSavegameTypeEvent)
		return false;

	return (data->value == kEventAnnaKilled
	     || data->value == kEventKronosHostageAnnaNoFirebird
	     || data->value == kEventKahinaPunchBaggageCarEntrance
	     || data->value == kEventKahinaPunchBlue
	     || data->value == kEventKahinaPunchYellow
	     || data->value == kEventKahinaPunchSalon
	     || data->value == kEventKahinaPunchKitchen
	     || data->value == kEventKahinaPunchBaggageCar
	     || data->value == kEventKahinaPunchCar
	     || data->value == kEventKahinaPunchSuite4
	     || data->value == kEventKahinaPunchRestaurant
	     || data->value == kEventKahinaPunch
	     || data->value == kEventKronosGiveFirebird
	     || data->value == kEventAugustFindCorpse
	     || data->value == kEventMertensBloodJacket
	     || data->value == kEventMertensCorpseFloor
	     || data->value == kEventMertensCorpseBed
	     || data->value == kEventCoudertBloodJacket
	     || data->value == kEventGendarmesArrestation
	     || data->value == kEventAbbotDrinkGiveDetonator
	     || data->value == kEventMilosCorpseFloor
	     || data->value == kEventLocomotiveAnnaStopsTrain
	     || data->value == kEventTrainStopped
	     || data->value == kEventCathVesnaRestaurantKilled
	     || data->value == kEventCathVesnaTrainTopKilled
	     || data->value == kEventLocomotiveConductorsDiscovered
	     || data->value == kEventViennaAugustUnloadGuns
	     || data->value == kEventViennaKronosFirebird
	     || data->value == kEventVergesAnnaDead
	     || data->value == kEventTrainExplosionBridge
	     || data->value == kEventKronosBringNothing);
}


//////////////////////////////////////////////////////////////////////////
// Private methods
//////////////////////////////////////////////////////////////////////////

// Get the file name from the savegame ID
Common::String SaveLoad::getFilename(GameId id) {
	if (id >= 6)
		error("[SaveLoad::getFilename] Attempting to use an invalid game id. Valid values: 0 - 5, was %d", id);

	return gameInfo[id].saveFile;
}

Common::InSaveFile *SaveLoad::openForLoading(GameId id) {
	Common::InSaveFile *load = g_system->getSavefileManager()->openForLoading(getFilename(id));

	if (!load)
		debugC(2, kLastExpressDebugSavegame, "Cannot open savegame for loading: %s", getFilename(id).c_str());

	return load;
}

Common::OutSaveFile *SaveLoad::openForSaving(GameId id) {
	Common::OutSaveFile *save = g_system->getSavefileManager()->openForSaving(getFilename(id), false); // TODO Enable compression again

	if (!save)
		debugC(2, kLastExpressDebugSavegame, "Cannot open savegame for writing: %s", getFilename(id).c_str());

	return save;
}

} // End of namespace LastExpress
back to top