LASTEXPRESS: Savegame support update

- Implement Menu::startGame() properly
 - Add stubs functions for game restart
 - Made savegame headers serializable and moved validity checks inside struct definition
 - Implement create/init savegame functions
 - Add SavegameStream to be able to read/write to the same memory stream
 - Add stubs for setup, writeEntry & loadEntry functions

svn-id: r53841
This commit is contained in:
Julien Templier 2010-10-26 00:41:42 +00:00
parent 8217efc74a
commit ae2c4b7cd2
7 changed files with 472 additions and 296 deletions

View File

@ -402,7 +402,23 @@ void Logic::eventTick(const Common::Event &) {
// Game over, Chapters & credits
//////////////////////////////////////////////////////////////////////////
// Handle game over
/**
* Resets the game state.
*/
void Logic::resetState() {
getState()->scene = kSceneDefault;
warning("Logic::resetState: not implemented! You need to restart the engine until this is implemented.");
}
/**
* Handle game over
*
* @param type The savegame type.
* @param value The value (event, time, index, ...)
* @param sceneIndex Index of the scene to show.
* @param showScene true to show a scene, false to return to menu directly
*/
void Logic::gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const {
getSound()->processEntries();

View File

@ -54,6 +54,7 @@ public:
void eventMouse(const Common::Event &ev);
void eventTick(const Common::Event &ev);
void resetState();
void gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const;
void playFinalSequence() const;
void updateCursor(bool redraw = true) const;

View File

@ -360,7 +360,7 @@ Menu::Menu(LastExpressEngine *engine) : _engine(engine),
_gameId(kGameBlue), _hasShownStartScreen(false), _hasShownIntro(false),
_isShowingCredits(false), _isGameStarted(false), _isShowingMenu(false),
_creditsSequenceIndex(0), _checkHotspotsTicks(15), _mouseFlags(Common::EVENT_INVALID), _lastHotspot(NULL),
_currentIndex(0), _currentTime(0), _lowerTime(0), _index(0), _index2(0), _time(0), _delta(0), _handleTimeDelta(false) {
_currentIndex(0), _currentTime(0), _lowerTime(0), _index(0), _savegameIndex(0), _time(0), _delta(0), _handleTimeDelta(false) {
_clock = new Clock(_engine);
_trainLine = new TrainLine(_engine);
@ -661,8 +661,8 @@ bool Menu::handleEvent(StartMenuAction action, Common::EventType type) {
if (_isGameStarted) {
showFrame(kOverlayEggButtons, kButtonContinue, true);
if (_index2 == _index) {
showFrame(kOverlayTooltip, isGameFinished() ? kTooltipViewGameEnding : kTooltipContinueGame, true);
if (_savegameIndex == _index) {
showFrame(kOverlayTooltip, getSaveLoad()->isGameFinished(_index, _savegameIndex) ? kTooltipViewGameEnding : kTooltipContinueGame, true);
} else {
showFrame(kOverlayTooltip, kTooltipContinueRewoundGame, true);
}
@ -828,7 +828,7 @@ bool Menu::handleEvent(StartMenuAction action, Common::EventType type) {
//////////////////////////////////////////////////////////////////////////
case kMenuForwardGame:
if (_index2 <= _index || _currentTime > _time) {
if (_savegameIndex <= _index || _currentTime > _time) {
hideOverlays();
break;
}
@ -1075,7 +1075,7 @@ void Menu::init(bool doSavegame, SavegameType type, uint32 value) {
// Create a new savegame if needed
if (!SaveLoad::isSavegamePresent(_gameId))
SaveLoad::writeMainHeader(_gameId);
getSaveLoad()->create(_gameId);
if (doSavegame)
getSaveLoad()->saveGame(kSavegameTypeEvent2, kEntityPlayer, kEventNone);
@ -1084,18 +1084,12 @@ void Menu::init(bool doSavegame, SavegameType type, uint32 value) {
// TODO: remove existing savegame temp file
}
// Init savegame and get the header data
getSaveLoad()->initSavegame(_gameId, true);
SaveLoad::SavegameMainHeader header;
if (!SaveLoad::loadMainHeader(_gameId, &header))
error("Menu::init: Corrupted savegame - Recovery path not implemented!");
// Init Menu values
_index2 = header.index;
_lowerTime = getSaveLoad()->getEntry(_index2)->time;
// Init savegame & menu values
_savegameIndex = getSaveLoad()->init(_gameId, true);
_lowerTime = getSaveLoad()->getTime(_savegameIndex);
if (useSameIndex)
_index = _index2;
_index = _savegameIndex;
//if (!getGlobalTimer())
// _index3 = 0;
@ -1103,8 +1097,8 @@ void Menu::init(bool doSavegame, SavegameType type, uint32 value) {
if (!getProgress().chapter)
getProgress().chapter = kChapter1;
getState()->time = getSaveLoad()->getEntry(_index)->time;
getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter;
getState()->time = getSaveLoad()->getTime(_index);
getProgress().chapter = getSaveLoad()->getChapter(_index);
if (_lowerTime >= kTimeStartGame) {
_currentTime = getState()->time;
@ -1117,11 +1111,26 @@ void Menu::init(bool doSavegame, SavegameType type, uint32 value) {
}
void Menu::startGame() {
// TODO: we need to reset the current scene
getState()->scene = kSceneDefault;
// TODO: Clear train sequences & savegame headers
getEntities()->setup(true, kEntityPlayer);
warning("Menu::startGame: not implemented!");
if (0 /* test for temp filename */ ) {
if (_savegameIndex == _index)
getSaveLoad()->loadGame(_gameId);
else
getSaveLoad()->loadGame2(_gameId);
} else {
if (_savegameIndex == _index) {
setGlobalTimer(0);
if (_index) {
getSaveLoad()->loadGame(_gameId);
} else {
getLogic()->resetState();
getEntities()->setup(true, kEntityPlayer);
}
} else {
getSaveLoad()->loadGame2(_gameId);
}
}
}
// Switch to the next savegame
@ -1132,7 +1141,7 @@ void Menu::switchGame() {
// Initialize savegame if needed
if (!SaveLoad::isSavegamePresent(_gameId))
SaveLoad::writeMainHeader(_gameId);
getSaveLoad()->create(_gameId);
getState()->time = 0;
@ -1141,53 +1150,11 @@ void Menu::switchGame() {
_trainLine->clear();
// Clear loaded savegame data
getSaveLoad()->clearEntries();
getSaveLoad()->clear();
init(false, kSavegameTypeIndex, 0);
}
bool Menu::isGameFinished() const {
SaveLoad::SavegameEntryHeader *data = getSaveLoad()->getEntry(_index);
if (_index2 != _index)
return false;
if (data->type != SaveLoad::kHeaderType2)
return false;
return (data->event == kEventAnnaKilled
|| data->event == kEventKronosHostageAnnaNoFirebird
|| data->event == kEventKahinaPunchBaggageCarEntrance
|| data->event == kEventKahinaPunchBlue
|| data->event == kEventKahinaPunchYellow
|| data->event == kEventKahinaPunchSalon
|| data->event == kEventKahinaPunchKitchen
|| data->event == kEventKahinaPunchBaggageCar
|| data->event == kEventKahinaPunchCar
|| data->event == kEventKahinaPunchSuite4
|| data->event == kEventKahinaPunchRestaurant
|| data->event == kEventKahinaPunch
|| data->event == kEventKronosGiveFirebird
|| data->event == kEventAugustFindCorpse
|| data->event == kEventMertensBloodJacket
|| data->event == kEventMertensCorpseFloor
|| data->event == kEventMertensCorpseBed
|| data->event == kEventCoudertBloodJacket
|| data->event == kEventGendarmesArrestation
|| data->event == kEventAbbotDrinkGiveDetonator
|| data->event == kEventMilosCorpseFloor
|| data->event == kEventLocomotiveAnnaStopsTrain
|| data->event == kEventTrainStopped
|| data->event == kEventCathVesnaRestaurantKilled
|| data->event == kEventCathVesnaTrainTopKilled
|| data->event == kEventLocomotiveConductorsDiscovered
|| data->event == kEventViennaAugustUnloadGuns
|| data->event == kEventViennaKronosFirebird
|| data->event == kEventVergesAnnaDead
|| data->event == kEventTrainExplosionBridge
|| data->event == kEventKronosBringNothing);
}
//////////////////////////////////////////////////////////////////////////
// Overlays & elements
//////////////////////////////////////////////////////////////////////////
@ -1307,7 +1274,7 @@ void Menu::initTime(SavegameType type, uint32 value) {
// Iterate through existing entries
do {
if (getSaveLoad()->getEntry(entryIndex)->time <= value)
if (getSaveLoad()->getTime(entryIndex) <= value)
break;
entryIndex--;
@ -1320,7 +1287,7 @@ void Menu::initTime(SavegameType type, uint32 value) {
break;
do {
if (getSaveLoad()->getEntry(entryIndex)->event == (EventIndex)value)
if (getSaveLoad()->getValue(entryIndex) == value)
break;
entryIndex--;
@ -1332,7 +1299,7 @@ void Menu::initTime(SavegameType type, uint32 value) {
if (_index > 1) {
uint32 index = _index;
do {
if (getSaveLoad()->getEntry(index)->event == (EventIndex)value)
if (getSaveLoad()->getValue(index) == value)
break;
index--;
@ -1347,7 +1314,7 @@ void Menu::initTime(SavegameType type, uint32 value) {
if (entryIndex) {
_currentIndex = entryIndex;
updateTime(getSaveLoad()->getEntry(entryIndex)->time);
updateTime(getSaveLoad()->getTime(entryIndex));
}
}
@ -1381,7 +1348,7 @@ void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) {
if ((int32)_index >= 0) {
do {
// Calculate new delta
int32 newDelta = time1 - getSaveLoad()->getEntry(currentIndex)->time;
int32 newDelta = time1 - getSaveLoad()->getTime(currentIndex);
if (newDelta >= 0 && timeDelta >= newDelta) {
timeDelta = newDelta;
@ -1398,10 +1365,10 @@ void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) {
if (searchEntry) {
uint32 currentIndex = _index;
if (_index2 >= _index) {
if (_savegameIndex >= _index) {
do {
// Calculate new delta
int32 newDelta = getSaveLoad()->getEntry(currentIndex)->time - time1;
int32 newDelta = getSaveLoad()->getTime(currentIndex) - time1;
if (newDelta >= 0 && timeDelta > newDelta) {
timeDelta = newDelta;
@ -1409,7 +1376,7 @@ void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) {
}
++currentIndex;
} while (currentIndex >= _index2);
} while (currentIndex >= _savegameIndex);
}
} else {
index = _index + 1;
@ -1421,45 +1388,45 @@ void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) {
}
if (_index == _currentIndex) {
if (getProgress().chapter != getSaveLoad()->getEntry(index)->chapter)
getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter;
if (getProgress().chapter != getSaveLoad()->getChapter(index))
getProgress().chapter = getSaveLoad()->getChapter(_index);
}
}
void Menu::goToTime(uint32 time) {
uint32 entryIndex = 0;
uint32 deltaTime = (uint32)ABS((int32)(getSaveLoad()->getEntry(0)->time - time));
uint32 deltaTime = (uint32)ABS((int32)(getSaveLoad()->getTime(0) - time));
uint32 index = 0;
do {
uint32 deltaTime2 = (uint32)ABS((int32)(getSaveLoad()->getEntry(index)->time - time));
uint32 deltaTime2 = (uint32)ABS((int32)(getSaveLoad()->getTime(index) - time));
if (deltaTime2 < deltaTime) {
deltaTime = deltaTime2;
entryIndex = index;
}
++index;
} while (_index2 >= index);
} while (_savegameIndex >= index);
_currentIndex = entryIndex;
updateTime(getSaveLoad()->getEntry(entryIndex)->time);
updateTime(getSaveLoad()->getTime(entryIndex));
}
void Menu::setTime() {
_currentIndex = _index;
_currentTime = getSaveLoad()->getEntry(_currentIndex)->time;
_currentTime = getSaveLoad()->getTime(_currentIndex);
if (_time == _currentTime)
adjustTime();
}
void Menu::forwardTime() {
if (_index2 <= _index)
if (_savegameIndex <= _index)
return;
_currentIndex = _index2;
updateTime(getSaveLoad()->getEntry(_currentIndex)->time);
_currentIndex = _savegameIndex;
updateTime(getSaveLoad()->getTime(_currentIndex));
}
void Menu::rewindTime() {
@ -1467,7 +1434,7 @@ void Menu::rewindTime() {
return;
_currentIndex = 0;
updateTime(getSaveLoad()->getEntry(_currentIndex)->time);
updateTime(getSaveLoad()->getTime(_currentIndex));
}
void Menu::adjustTime() {

View File

@ -145,7 +145,6 @@ private:
// Game-related
void startGame();
void switchGame();
bool isGameFinished() const;
//////////////////////////////////////////////////////////////////////////
// Overlays & elements
@ -183,7 +182,7 @@ private:
uint32 _lowerTime; // lower time value
uint32 _index;
uint32 _index2;
uint32 _savegameIndex;
uint32 _time;
uint32 _delta;
bool _handleTimeDelta;

View File

@ -23,8 +23,10 @@
*
*/
#include "lastexpress/game/logic.h"
#include "lastexpress/game/savegame.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/game/menu.h"
#include "lastexpress/game/state.h"
#include "lastexpress/debug.h"
@ -36,10 +38,6 @@
namespace LastExpress {
// Savegame signatures
#define SAVEGAME_SIGNATURE 0x12001200
#define SAVEGAME_HEADER 0xE660E660
// Names of savegames
static const struct {
const char *saveFile;
@ -56,15 +54,91 @@ static const struct {
// Constructors
//////////////////////////////////////////////////////////////////////////
SaveLoad::SaveLoad(LastExpressEngine *engine) : _engine(engine) {
_gameTicksLastSavegame = 0;
SaveLoad::SaveLoad(LastExpressEngine *engine) : _engine(engine), _savegame(NULL), _gameTicksLastSavegame(0) {
}
SaveLoad::~SaveLoad() {
clear();
SAFE_DELETE(_savegame);
//Zero passed pointers
_engine = NULL;
}
clearEntries();
void SaveLoad::initStream() {
SAFE_DELETE(_savegame);
_savegame = new SavegameStream();
}
void SaveLoad::flushStream(GameId id) {
Common::OutSaveFile *save = openForSaving(id);
if (!save)
error("SaveLoad::initSave: Cannot init savegame (%s)!", getFilename(id).c_str());
save->write(_savegame->getData(), _savegame->size());
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();
// Open savegame
Common::InSaveFile *save = openForLoading(id);
if (save->size() < 32)
error("SaveLoad::init - Savegame seems to be corrupted (not enough data: %i bytes)", save->size());
// Load all savegame data
while (!save->eos() && !save->err())
_savegame->writeByte(save->readByte());
_savegame->seek(0);
delete save;
// Load the main header
Common::Serializer ser(_savegame, NULL);
SavegameMainHeader header;
header.saveLoadWithSerializer(ser);
if (!header.isValid())
error("SaveLoad::init - Savegame seems to be corrupted (invalid header)");
// Reset cached entry headers if needed
if (resetHeaders) {
clear();
SavegameEntryHeader *header = new SavegameEntryHeader();
header->time = kTimeCityParis;
header->chapter = kChapter1;
_gameHeaders.push_back(header);
}
warning("SaveLoad::initSavegame: not implemented!");
// return the index to the current save game entry (we store count + 1 entries, so we're good)
return 0; //header.count;
}
void SaveLoad::clear() {
for (uint i = 0; i < _gameHeaders.size(); i++)
SAFE_DELETE(_gameHeaders[i]);
_gameHeaders.clear();
}
//////////////////////////////////////////////////////////////////////////
@ -80,21 +154,80 @@ bool SaveLoad::loadGame(GameId id) {
//Common::InSaveFile *save = SaveLoad::openForLoading(id);
// Validate header
error("SaveLoad::loadgame: not implemented!");
return false;
}
bool SaveLoad::loadGame2(GameId id) {
error("SaveLoad::loadgame2: not implemented!");
}
// Save game
void SaveLoad::saveGame(SavegameType type, EntityIndex entity, uint32 value) {
if (getState()->scene <= kSceneIntro)
return;
// Save ticks
_gameTicksLastSavegame = getState()->timeTicks;
// Validate main header
SavegameMainHeader header;
if (!loadMainHeader(_savegame, &header)) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::saveGame - Cannot load main header: %s", getFilename(getMenu()->getGameId()).c_str());
return;
}
warning("SaveLoad::savegame: not implemented!");
// 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() || getState()->time < entry.time || (type == kSavegameTypeTickInterval && getState()->time == entry.time))
return;
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 = _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 = _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() {
@ -107,7 +240,7 @@ void SaveLoad::saveVolumeBrightness() {
// Check if a specific savegame exists
bool SaveLoad::isSavegamePresent(GameId id) {
if (g_system->getSavefileManager()->listSavefiles(getSavegameName(id)).size() == 0)
if (g_system->getSavefileManager()->listSavefiles(getFilename(id)).size() == 0)
return false;
return true;
@ -116,114 +249,57 @@ bool SaveLoad::isSavegamePresent(GameId id) {
// Check if the game has been started in the specific savegame
bool SaveLoad::isSavegameValid(GameId id) {
if (!isSavegamePresent(id)) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::isSavegameValid - Savegame does not exist: %s", getSavegameName(id).c_str());
debugC(2, kLastExpressDebugSavegame, "SaveLoad::isSavegameValid - Savegame does not exist: %s", getFilename(id).c_str());
return false;
}
SavegameMainHeader header;
if (!loadMainHeader(id, &header))
return false;
return validateMainHeader(header);
Common::InSaveFile *save = openForLoading(id);
return loadMainHeader(save, &header);
}
//////////////////////////////////////////////////////////////////////////
// Headers
//////////////////////////////////////////////////////////////////////////
bool SaveLoad::loadMainHeader(GameId id, SavegameMainHeader *header) {
// Read first 32 bytes of savegame
Common::InSaveFile *save = openForLoading(id);
if (!save) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot open savegame for reading: %s", getSavegameName(id).c_str());
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, "SaveLoad::loadMainHeader - Savegame seems to be corrupted (not enough data: %i bytes)!", stream->size());
return false;
}
// Check there is enough data
if (save->size() < 32) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Savegame seems to be corrupted (not enough data: %i bytes): %s", save->size(), getSavegameName(id).c_str());
delete save;
return false;
}
// Rewind stream
stream->seek(0);
header->signature = save->readUint32LE();
header->index = save->readUint32LE();
header->time = save->readUint32LE();
header->field_C = save->readUint32LE();
header->field_10 = save->readUint32LE();
header->brightness = save->readSint32LE();
header->volume = save->readSint32LE();
header->field_1C = save->readUint32LE();
Common::Serializer ser(stream, NULL);
header->saveLoadWithSerializer(ser);
delete save;
// Valide the header
if (!validateMainHeader(*header)) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot validate main header for savegame %s.", getSavegameName(id).c_str());
// Validate the header
if (!header->isValid()) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot validate main header!");
return false;
}
return true;
}
void SaveLoad::loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader *header) {
header->signature = save->readUint32LE();
header->type = (HeaderType)save->readUint32LE();
header->time = save->readUint32LE();
header->field_C = save->readUint32LE();
header->chapter = (ChapterIndex)save->readUint32LE();
header->event = (EventIndex)save->readUint32LE();
header->field_18 = save->readUint32LE();
header->field_1C = save->readUint32LE();
void SaveLoad::loadEntryHeader(SavegameEntryHeader *header) {
error("SaveLoad::loadEntryHeader: not implemented!");
}
bool SaveLoad::validateMainHeader(const SavegameMainHeader &header) {
if (header.signature != SAVEGAME_SIGNATURE)
return false;
/* Check not needed as it can never be < 0
if (header.chapter < 0)
return false;*/
if (header.time < 32)
return false;
if (header.field_C < 32)
return false;
if (header.field_10 != 1 && header.field_10)
return false;
if (header.brightness < 0 || header.brightness > 6)
return false;
if (header.volume < 0 || header.volume > 7)
return false;
if (header.field_1C != 9)
return false;
return true;
//////////////////////////////////////////////////////////////////////////
// Entries
//////////////////////////////////////////////////////////////////////////
void SaveLoad::writeEntry(SavegameType type, EntityIndex entity, uint32 value) {
warning("SaveLoad::writeEntry: not implemented!");
}
bool SaveLoad::validateEntryHeader(const SavegameEntryHeader &header) {
if (header.signature != SAVEGAME_HEADER)
return false;
if (header.type < kHeaderType1 || header.type > kHeaderType5)
return false;
if (header.time < kTimeStartGame || header.time > kTimeCityConstantinople)
return false;
if (header.field_C <= 0 || header.field_C >= 15)
return false;
/* No check for < 0, as it cannot happen normaly */
if (header.chapter == 0)
return false;
return true;
void SaveLoad::readEntry(SavegameType type, EntityIndex entity, uint32 value) {
warning("SaveLoad::readEntry: not implemented!");
}
SaveLoad::SavegameEntryHeader *SaveLoad::getEntry(uint32 index) {
@ -233,78 +309,80 @@ SaveLoad::SavegameEntryHeader *SaveLoad::getEntry(uint32 index) {
return _gameHeaders[index];
}
void SaveLoad::clearEntries() {
for (uint i = 0; i < _gameHeaders.size(); i++)
SAFE_DELETE(_gameHeaders[i]);
_gameHeaders.clear();
}
//////////////////////////////////////////////////////////////////////////
// Init
// Checks
//////////////////////////////////////////////////////////////////////////
void SaveLoad::writeMainHeader(GameId id) {
Common::OutSaveFile *save = openForSaving(id);
if (!save) {
debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str());
return;
}
bool SaveLoad::isGameFinished(uint32 menuIndex, uint32 savegameIndex) {
SavegameEntryHeader *data = getEntry(menuIndex);
// Write default values to savegame
save->writeUint32LE(SAVEGAME_SIGNATURE);
save->writeUint32LE(0);
save->writeUint32LE(32);
save->writeUint32LE(32);
save->writeUint32LE(0);
save->writeUint32LE(3);
save->writeUint32LE(7);
save->writeUint32LE(9);
if (savegameIndex != menuIndex)
return false;
delete save;
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);
}
void SaveLoad::initSavegame(GameId id, bool resetHeaders) {
//Common::OutSaveFile *save = openForSaving(id);
//if (!save) {
// debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str());
// return;
//}
if (resetHeaders) {
clearEntries();
SavegameEntryHeader *header = new SavegameEntryHeader();
header->time = kTimeCityParis;
header->chapter = kChapter1;
_gameHeaders.push_back(header);
}
// Open the savegame and read all game headers
warning("SaveLoad::initSavegame: not implemented!");
//delete save;
}
//////////////////////////////////////////////////////////////////////////
// Private methods
//////////////////////////////////////////////////////////////////////////
// Get the file name from the savegame ID
Common::String SaveLoad::getSavegameName(GameId id) {
Common::String SaveLoad::getFilename(GameId id) {
if (id >= 6)
error("SaveLoad::getSavegameName - attempting to use an invalid game id. Valid values: 0 - 5, was %d", id);
error("SaveLoad::getName - attempting to use an invalid game id. Valid values: 0 - 5, was %d", id);
return gameInfo[id].saveFile;
}
Common::InSaveFile *SaveLoad::openForLoading(GameId id) {
return g_system->getSavefileManager()->openForLoading(getSavegameName(id));
Common::InSaveFile *load = g_system->getSavefileManager()->openForLoading(getFilename(id));
if (!load)
debugC(2, kLastExpressDebugSavegame, "SaveLoad::openForLoading - Cannot open savegame for loading: %s", getFilename(id).c_str());
return load;
}
Common::OutSaveFile *SaveLoad::openForSaving(GameId id) {
return g_system->getSavefileManager()->openForSaving(getSavegameName(id));
Common::OutSaveFile *save = g_system->getSavefileManager()->openForSaving(getFilename(id));
if (!save)
debugC(2, kLastExpressDebugSavegame, "SaveLoad::openForSaving - Cannot open savegame for writing: %s", getFilename(id).c_str());
return save;
}
} // End of namespace LastExpress

View File

@ -77,95 +77,208 @@
#include "lastexpress/shared.h"
#include "common/savefile.h"
#include "common/serializer.h"
namespace LastExpress {
// Savegame signatures
#define SAVEGAME_SIGNATURE 0x12001200
#define SAVEGAME_HEADER 0xE660E660
class LastExpressEngine;
class SaveLoad {
public:
enum HeaderType {
kHeaderTypeNone = 0,
kHeaderType1 = 1,
kHeaderType2 = 2,
kHeaderType3 = 3,
kHeaderType4 = 4,
kHeaderType5 = 5
};
struct SavegameMainHeader {
uint32 signature;
uint32 index;
uint32 time;
uint32 field_C;
uint32 field_10;
int32 brightness;
int32 volume;
uint32 field_1C;
};
struct SavegameEntryHeader {
uint32 signature;
HeaderType type;
uint32 time;
int field_C;
ChapterIndex chapter;
EventIndex event;
int field_18;
int field_1C;
SavegameEntryHeader() {
signature = 0;
type = kHeaderTypeNone;
time = 0;
field_C = 0;
chapter = kChapterAll;
event = kEventNone;
field_18 = 0;
field_1C = 0;
}
};
SaveLoad(LastExpressEngine *engine);
~SaveLoad();
// Init
void create(GameId id);
void clear();
uint32 init(GameId id, bool resetHeaders);
// Save & Load
bool loadGame(GameId id);
bool loadGame2(GameId id);
void saveGame(SavegameType type, EntityIndex entity, uint32 value);
void saveVolumeBrightness();
// Init
void initSavegame(GameId id, bool resetHeaders);
static void writeMainHeader(GameId id);
// Getting information
static bool isSavegamePresent(GameId id);
static bool isSavegameValid(GameId id);
// Opening save files
static Common::InSaveFile *openForLoading(GameId id);
static Common::OutSaveFile *openForSaving(GameId id);
bool isGameFinished(uint32 menuIndex, uint32 savegameIndex);
// Headers
static bool loadMainHeader(GameId id, SavegameMainHeader *header);
SavegameEntryHeader *getEntry(uint32 index);
void clearEntries();
uint32 getLastSavegameTicks() const { return _gameTicksLastSavegame; }
// Accessors
TimeValue getTime(uint32 index) { return getEntry(index)->time; }
ChapterIndex getChapter(uint32 index) { return getEntry(index)->chapter; }
uint32 getValue(uint32 index) { return getEntry(index)->value; }
uint32 getLastSavegameTicks() const { return _gameTicksLastSavegame; }
private:
class SavegameStream : public Common::MemoryWriteStreamDynamic, public Common::SeekableReadStream {
public:
SavegameStream() : MemoryWriteStreamDynamic(DisposeAfterUse::YES),
_eos(false) {}
int32 pos() const { return MemoryWriteStreamDynamic::pos(); }
int32 size() const { return MemoryWriteStreamDynamic::size(); }
bool seek(int32 offset, int whence = SEEK_SET) { return MemoryWriteStreamDynamic::seek(offset, whence); }
bool eos() const { return _eos; }
uint32 read(void *dataPtr, uint32 dataSize) {
if ((int32)dataSize > size() - pos()) {
dataSize = size() - pos();
_eos = true;
}
memcpy(dataPtr, getData() + pos(), dataSize);
seek(dataSize, SEEK_CUR);
return dataSize;
}
private:
bool _eos;
};
LastExpressEngine *_engine;
uint32 _gameTicksLastSavegame;
struct SavegameMainHeader : Common::Serializable {
uint32 signature;
uint32 count;
uint32 offset;
uint32 offsetEntry;
uint32 keepIndex;
int32 brightness;
int32 volume;
uint32 field_1C;
SavegameMainHeader() {
signature = SAVEGAME_SIGNATURE;
count = 0;
offset = 32;
offsetEntry = 32;
keepIndex = 0;
brightness = 3;
volume = 7;
field_1C = 9;
}
void saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsUint32LE(signature);
s.syncAsUint32LE(count);
s.syncAsUint32LE(offset);
s.syncAsUint32LE(offsetEntry);
s.syncAsUint32LE(keepIndex);
s.syncAsUint32LE(brightness);
s.syncAsUint32LE(volume);
s.syncAsUint32LE(field_1C);
}
bool isValid() {
if (signature != SAVEGAME_SIGNATURE)
return false;
/* Check not needed as it can never be < 0
if (header.chapter < 0)
return false;*/
if (offset < 32)
return false;
if (offsetEntry < 32)
return false;
if (keepIndex != 1 && keepIndex != 0)
return false;
if (brightness < 0 || brightness > 6)
return false;
if (volume < 0 || volume > 7)
return false;
if (field_1C != 9)
return false;
return true;
}
};
struct SavegameEntryHeader : Common::Serializable {
uint32 signature;
SavegameType type;
TimeValue time;
int field_C;
ChapterIndex chapter;
uint32 value;
int field_18;
int field_1C;
SavegameEntryHeader() {
signature = 0;
type = kSavegameTypeIndex;
time = kTimeNone;
field_C = 0;
chapter = kChapterAll;
value = 0;
field_18 = 0;
field_1C = 0;
}
void saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsUint32LE(signature);
s.syncAsUint32LE(type);
s.syncAsUint32LE(time);
s.syncAsUint32LE(field_C);
s.syncAsUint32LE(chapter);
s.syncAsUint32LE(value);
s.syncAsUint32LE(field_18);
s.syncAsUint32LE(field_1C);
}
bool isValid() {
if (signature != SAVEGAME_HEADER)
return false;
if (type < kSavegameTypeTime || type > kSavegameTypeTickInterval)
return false;
if (time < kTimeStartGame || time > kTimeCityConstantinople)
return false;
if (field_C <= 0 || field_C >= 15)
return false;
/* No check for < 0, as it cannot happen normaly */
if (chapter == 0)
return false;
return true;
}
};
SavegameStream *_savegame;
Common::Array<SavegameEntryHeader *> _gameHeaders;
uint32 _gameTicksLastSavegame;
static Common::String getSavegameName(GameId id);
// Headers
static bool loadMainHeader(Common::InSaveFile *stream, SavegameMainHeader *header);
void loadEntryHeader(SavegameEntryHeader *header);
static void loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader *header);
// Entries
void writeEntry(SavegameType type, EntityIndex entity, uint32 value);
void readEntry(SavegameType type, EntityIndex entity, uint32 value);
SavegameEntryHeader *getEntry(uint32 index);
static bool validateMainHeader(const SavegameMainHeader &header);
static bool validateEntryHeader(const SavegameEntryHeader &header);
// Opening save files
static Common::String getFilename(GameId id);
static Common::InSaveFile *openForLoading(GameId id);
static Common::OutSaveFile *openForSaving(GameId id);
// Savegame stream
void initStream();
void flushStream(GameId id);
};
} // End of namespace LastExpress

View File

@ -342,6 +342,8 @@ enum SceneIndex {
kSceneNone = 0,
kSceneMenu = 1,
kSceneIntro = 30,
// Inventory
kSceneMatchbox = 31,
kSceneTelegram = 32,