/* 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 3 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, see . * */ #include "lastexpress/lastexpress.h" #include "lastexpress/data/cvcrfile.h" #include "lastexpress/menu/clock.h" #include "lastexpress/game/vcr.h" #include "common/config-manager.h" #include "common/savefile.h" #include "engines/metaengine.h" namespace LastExpress { VCR::VCR(LastExpressEngine *engine) { _engine = engine; } void VCR::virginSaveFile() { CVCRFile *saveFile = new CVCRFile(_engine); SVCRFileHeader fileHeader; assert(_engine->_savegameFilename); fileHeader.magicNumber = 0x12001200; fileHeader.numVCRGames = 0; fileHeader.nextWritePos = sizeof(SVCRFileHeader); fileHeader.realWritePos = sizeof(SVCRFileHeader); fileHeader.lastIsTemporary = 0; fileHeader.brightness = _engine->getGraphicsManager()->getGammaLevel(); fileHeader.saveVersion = 9; fileHeader.volume = _engine->_soundMan->getMasterVolume(); saveFile->open(_engine->_savegameFilename, CVCRMODE_WB); saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0); saveFile->close(); delete saveFile; } void VCR::writeSavePoint(int type, int entity, int event) { SVCRSavePointHeader savePointHeader; SVCRFileHeader fileHeader; CVCRFile *saveFile = new CVCRFile(_engine); if (_engine->getLogicManager()->_activeNode <= 30) { delete saveFile; return; } if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename && !_engine->_gracePeriodTimer) { makePermanent(); } saveFile->open(_engine->_savegameFilename, CVCRMODE_RWB); saveFile->read(&fileHeader, 32, 1, 0, 1); if (!_engine->getSaveManager()->checkFileHeader(&fileHeader)) { saveFile->close(); delete saveFile; return; } saveFile->seek(fileHeader.nextWritePos, 0); if (fileHeader.numVCRGames > 0) { saveFile->seek(fileHeader.realWritePos, 0); saveFile->read(&savePointHeader, 32, 1, 0, 1); if ((!_engine->getSaveManager()->checkSavePointHeader(&savePointHeader)) || (savePointHeader.time > _engine->getLogicManager()->_gameTime) || (type == 5 && savePointHeader.time == _engine->getLogicManager()->_gameTime)) { saveFile->close(); delete saveFile; return; } saveFile->seek(fileHeader.nextWritePos, 0); if ((type == 1 || type == 2) && savePointHeader.type == 5 && (_engine->getLogicManager()->_gameTime - savePointHeader.time) < 2700) { saveFile->seek(fileHeader.realWritePos, 0); fileHeader.numVCRGames--; } } if (type != 3 && type != 4) fileHeader.realWritePos = saveFile->tell(); _engine->getSaveManager()->writeSavePoint(saveFile, type, entity, event); if (!fileHeader.lastIsTemporary) ++fileHeader.numVCRGames; if (type == 3 || type == 4) { fileHeader.lastIsTemporary = 1; } else { fileHeader.lastIsTemporary = 0; fileHeader.nextWritePos = saveFile->tell(); _engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime; } _engine->getSaveManager()->checkFileHeader(&fileHeader); saveFile->seek(0, 0); saveFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, 0); saveFile->close(); delete saveFile; } void VCR::selectFromName(const char *filename) { // Search through valid savegame names... for (int saveNameIndex = 0; saveNameIndex < 6; saveNameIndex++) { Common::String curSaveName = _engine->getTargetName() + "-" + _engine->_savegameNames[saveNameIndex]; if (Common::String(filename).equalsIgnoreCase(curSaveName)) { // Found a match! setCurrentGameColor(saveNameIndex); return; } } } void VCR::shuffleGames() { int currentSlot; // Process all save game slots for (currentSlot = 0; currentSlot < ARRAYSIZE(_engine->_savegameNames); currentSlot++) { const char *currentFile = _engine->_savegameNames[currentSlot]; Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(currentFile)); bool slotFilled = false; // Check if current slot has a valid save if (saveFile) { if (saveFile->size() <= 32) { delete saveFile; // Remove invalid/corrupted save files if (_engine->getSaveManager()->removeSavegame(currentFile) != 0) { error("Error deleting file \"%s\"", currentFile); } // Also remove the timestamp Common::String tsName = currentFile; tsName.chop(4); tsName = _engine->getTargetName() + "-" + tsName + ".timestamp"; if (g_system->getSavefileManager()->exists(tsName)) g_system->getSavefileManager()->removeSavefile(tsName); } else { slotFilled = true; delete saveFile; } } // If slot is empty, try to find a valid save to move here if (!slotFilled && currentSlot < ARRAYSIZE(_engine->_savegameNames) - 1) { for (const char **candidateFile = &_engine->_savegameNames[currentSlot + 1]; candidateFile < _engine->_savegameTempNames; candidateFile++) { Common::InSaveFile *candidateSave = _engine->getSaveFileManager()->openForLoading(_engine->getTargetName() + "-" + Common::String(*candidateFile)); if (candidateSave) { if (candidateSave->size() > 32) { delete candidateSave; // Move this valid save to the empty slot if (_engine->getSaveManager()->renameSavegame(*candidateFile, currentFile) != 0) { error("Error renaming file \"%s\" to \"%s\"", *candidateFile, currentFile); } slotFilled = true; break; } else { delete candidateSave; // Remove invalid candidate files if (_engine->getSaveManager()->removeSavegame(*candidateFile) != 0) { error("Error deleting file \"%s\"", *candidateFile); } // Also remove the timestamp Common::String tsName = *candidateFile; tsName.chop(4); tsName = _engine->getTargetName() + "-" + tsName + ".timestamp"; if (g_system->getSavefileManager()->exists(tsName)) g_system->getSavefileManager()->removeSavefile(tsName); } } } } } // Set the current game color based on the most recent save... Common::String currentSaveName = _engine->getTargetName() + "-" + Common::String(_engine->_savegameNames[currentSlot % 6]); if (_engine->_currentGameFileColorId == -1 || !_engine->getSaveFileManager()->exists(currentSaveName)) { setCurrentGameColor(0); // Default color int32 newestSaveSecs = 0; uint32 newestSaveTime = 0; uint32 newestSaveDate = 0; // Find the most recently modified save file for (int i = 0; i < 6; i++) { Common::String tsFilename = _engine->_savegameNames[i]; tsFilename.chop(4); tsFilename = _engine->getTargetName() + "-" + tsFilename + ".timestamp"; Common::InSaveFile *saveFile = _engine->getSaveFileManager()->openForLoading(tsFilename); if (saveFile) { int32 eshSecs = saveFile->readSint32LE(); ExtendedSavegameHeader esh; if (_engine->getMetaEngine()->readSavegameHeader(saveFile, &esh)) { if (esh.date > newestSaveDate || (esh.date == newestSaveDate && esh.time > newestSaveTime) || (esh.date == newestSaveDate && esh.time == newestSaveTime && eshSecs > newestSaveSecs)) { newestSaveSecs = eshSecs; newestSaveTime = esh.time; newestSaveDate = esh.date; setCurrentGameColor(i); } // SwapBytesVCR(i); } } delete saveFile; } } } void VCR::setCurrentGameColor(int index) { _engine->_currentGameFileColorId = index; _engine->_savegameFilename = _engine->_savegameNames[index]; } void VCR::init(bool doSaveGameFlag, int saveType, int32 time) { int cdNum; SVCRFileHeader header; char path[80]; int32 chosenTime = 0; bool flag = true; bool writeSavePoint = false; if (_engine->_savegame && _engine->_savegame->fileIsOpen()) _engine->_savegame->close(); if (_engine->_gracePeriodTimer) { if (_engine->isDemo()) { time = 0; _engine->_gracePeriodTimer = 0; doSaveGameFlag = false; if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) { error("Error deleting file \"%s\"", _engine->_savegameFilename); } _engine->_currentSavePoint = _engine->_gracePeriodIndex; flag = false; _engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId]; } else { chosenTime = 0; if (_engine->getLogicManager()->_globals[kGlobalChapter] <= 1) { cdNum = 1; } else { cdNum = (_engine->getLogicManager()->_globals[kGlobalChapter] > 3) + 2; } if (_engine->getArchiveManager()->isCDAvailable(cdNum, path, sizeof(path))) { writeSavePoint = 0; _engine->_gracePeriodTimer = 0; if (_engine->getSaveManager()->removeSavegame(_engine->_savegameFilename)) { error("Error deleting file \"%s\"", _engine->_savegameFilename); } flag = false; _engine->_currentSavePoint = _engine->_gracePeriodIndex; _engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId]; } else { writeSavePoint = false; } } } else { if (_engine->_savegameFilename == _engine->_savegameTempNames[_engine->_currentGameFileColorId]) _engine->getVCR()->makePermanent(); if (!_engine->isDemo()) { writeSavePoint = doSaveGameFlag; chosenTime = time; } } if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename)) _engine->getVCR()->virginSaveFile(); if (_engine->isDemo()) { if (doSaveGameFlag) _engine->getVCR()->writeSavePoint(3, kCharacterCath, 0); } else { if (writeSavePoint) _engine->getVCR()->writeSavePoint(3, kCharacterCath, 0); } if (!_engine->_gracePeriodTimer && _engine->getSaveManager()->fileExists(_engine->_savegameTempNames[_engine->_currentGameFileColorId]) && _engine->getSaveManager()->removeSavegame(_engine->_savegameTempNames[_engine->_currentGameFileColorId]) ) { error("Error deleting file \"%s\"", _engine->_savegameTempNames[_engine->_currentGameFileColorId]); } _engine->getSaveManager()->validateSaveFile(true); if (!_engine->_savegame) _engine->_savegame = new CVCRFile(_engine); _engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB); _engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true); if (!_engine->getSaveManager()->checkFileHeader(&header)) { _engine->_savegame->close(); error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } _engine->_lastSavePointIdInFile = header.numVCRGames; _engine->_gameTimeOfLastSavePointInFile = _engine->_savePointHeaders[header.numVCRGames].time; if (flag) _engine->_currentSavePoint = _engine->_lastSavePointIdInFile; if (!_engine->_gracePeriodTimer) _engine->_gracePeriodIndex = 0; if (!_engine->getLogicManager()->_globals[kGlobalChapter]) _engine->getLogicManager()->_globals[kGlobalChapter] = 1; _engine->getLogicManager()->_gameTime = _engine->_savePointHeaders[_engine->_currentSavePoint].time; _engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber; if (_engine->_gameTimeOfLastSavePointInFile >= 1061100) { _engine->getClock()->startClock(_engine->getLogicManager()->_gameTime); if (_engine->isDemo()) { // Demo: use modified time parameter _engine->getVCR()->autoRewind(saveType, time); } else { _engine->getVCR()->autoRewind(saveType, chosenTime); } } } void VCR::autoRewind(int saveType, int32 time) { int selectedIdx = 0; if (time) { switch (saveType) { case 0: if (_engine->_currentSavePoint <= time) { selectedIdx = 1; } else { selectedIdx = _engine->_currentSavePoint - time; } break; case 1: if (time < 1061100) break; if (!_engine->_currentSavePoint) return; selectedIdx = _engine->_currentSavePoint; do { if (_engine->_savePointHeaders[selectedIdx].time <= time) break; selectedIdx--; } while (selectedIdx); break; case 2: if (!_engine->_currentSavePoint) return; selectedIdx = _engine->_currentSavePoint; do { if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time) break; selectedIdx--; } while (selectedIdx); break; case 3: selectedIdx = _engine->_currentSavePoint; if (_engine->_currentSavePoint > 1) { do { if (_engine->_savePointHeaders[selectedIdx].latestGameEvent == time) break; selectedIdx--; } while (selectedIdx != 1); } selectedIdx--; break; default: break; } if (selectedIdx) { _currentSavePointInVCR = selectedIdx; _engine->getClock()->setClock(_engine->_savePointHeaders[selectedIdx].time); } } } void VCR::free() { _engine->getClock()->endClock(); if (_engine->_savePointHeaders) { _engine->getMemoryManager()->freeMem(_engine->_savePointHeaders); _engine->_savePointHeaders = nullptr; } _engine->_savegame->close(); } bool VCR::isVirgin(int savegameIndex) { SVCRFileHeader header; CVCRFile *file = new CVCRFile(_engine); if (_engine->getSaveManager()->fileExists(_engine->_savegameNames[savegameIndex]) && file->open(_engine->_savegameNames[savegameIndex], CVCRMODE_RB)) { if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) && _engine->getSaveManager()->checkFileHeader(&header) && header.numVCRGames > 0) { file->close(); delete file; return false; } } file->close(); delete file; return true; } bool VCR::currentEndsGame() { SVCRSavePointHeader *header = &_engine->_savePointHeaders[_engine->_currentSavePoint]; int32 latestGameEvent = header->latestGameEvent; return _engine->_lastSavePointIdInFile == _engine->_currentSavePoint && header->type == 2 && ( latestGameEvent == kEventAnnaKilled || latestGameEvent == kEventKronosHostageAnnaNoFirebird || latestGameEvent == kEventKahinaPunchBaggageCarEntrance || latestGameEvent == kEventKahinaPunchBlue || latestGameEvent == kEventKahinaPunchYellow || latestGameEvent == kEventKahinaPunchSalon || latestGameEvent == kEventKahinaPunchKitchen || latestGameEvent == kEventKahinaPunchBaggageCar || latestGameEvent == kEventKahinaPunchCar || latestGameEvent == kEventKahinaPunchSuite4 || latestGameEvent == kEventKahinaPunchRestaurant || latestGameEvent == kEventKahinaPunch || latestGameEvent == kEventKronosGiveFirebird || latestGameEvent == kEventAugustFindCorpse || latestGameEvent == kEventMertensBloodJacket || latestGameEvent == kEventMertensCorpseFloor || latestGameEvent == kEventMertensCorpseBed || latestGameEvent == kEventCoudertBloodJacket || latestGameEvent == kEventGendarmesArrestation || latestGameEvent == kEventAbbotDrinkGiveDetonator || latestGameEvent == kEventMilosCorpseFloor || latestGameEvent == kEventLocomotiveAnnaStopsTrain || latestGameEvent == kEventTrainStopped || latestGameEvent == kEventCathVesnaRestaurantKilled || latestGameEvent == kEventCathVesnaTrainTopKilled || latestGameEvent == kEventLocomotiveConductorsDiscovered || latestGameEvent == kEventViennaAugustUnloadGuns || latestGameEvent == kEventViennaKronosFirebird || latestGameEvent == kEventVergesAnnaDead || latestGameEvent == kEventTrainExplosionBridge || latestGameEvent == kEventKronosBringNothing ); } bool VCR::makePermanent() { if (_engine->getSaveManager()->removeSavegame(_engine->_savegameNames[_engine->_currentGameFileColorId])) { error("Error deleting file \"%s\"", _engine->_savegameNames[_engine->_currentGameFileColorId]); return false; } else if (_engine->getSaveManager()->renameSavegame(_engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId])) { error("Error renaming file \"%s\" to \"%s\"", _engine->_savegameFilename, _engine->_savegameNames[_engine->_currentGameFileColorId]); return false; } else { _engine->_savegameFilename = _engine->_savegameNames[_engine->_currentGameFileColorId]; return true; } } int VCR::switchGames() { int index = 0; if (!isVirgin(_engine->_currentGameFileColorId)) { index = (_engine->_currentGameFileColorId + 1) % 6; } _engine->_currentGameFileColorId = index; _engine->_savegameFilename = _engine->_savegameNames[index]; if (!_engine->getSaveManager()->fileExists(_engine->_savegameFilename)) virginSaveFile(); _engine->getLogicManager()->_gameTime = 0; _engine->getClock()->turnOnClock(false); if (_engine->_savePointHeaders) { _engine->getMemoryManager()->freeMem(_engine->_savePointHeaders); _engine->_savePointHeaders = nullptr; } _engine->_savegame->close(); storeSettings(); init(false, 0, 0); return _engine->_currentGameFileColorId; } void VCR::storeSettings() { SVCRFileHeader header; CVCRFile *file = new CVCRFile(_engine); file->open(_engine->_savegameFilename, CVCRMODE_RWB); if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) { if (!_engine->getSaveManager()->checkFileHeader(&header)) { file->close(); delete file; return; } header.brightness = _engine->getGraphicsManager()->getGammaLevel(); header.volume = _engine->getSoundManager()->getMasterVolume(); file->seek(0, SEEK_SET); file->write(&header, sizeof(SVCRFileHeader), 1, false); file->close(); } else { file->close(); virginSaveFile(); } delete file; } void VCR::loadSettings() { SVCRFileHeader header; CVCRFile *file = new CVCRFile(_engine); if (_engine->getSaveManager()->fileExists(_engine->_savegameFilename)) { file->open(_engine->_savegameFilename, CVCRMODE_RB); if (file->read(&header, sizeof(SVCRFileHeader), 1, false, false) == 1) { if (_engine->getSaveManager()->checkFileHeader(&header)) { file->seek(0, SEEK_SET); _engine->getGraphicsManager()->setGammaLevel(header.brightness); _engine->getSoundManager()->setMasterVolume(header.volume); } file->close(); } else { file->close(); virginSaveFile(); } } delete file; } void VCR::rewind() { if (_engine->_currentSavePoint) { _currentSavePointInVCR = 0; _engine->getClock()->setClock(_engine->_savePointHeaders->time); } } void VCR::forward() { if (_engine->_lastSavePointIdInFile > _engine->_currentSavePoint) { _currentSavePointInVCR = _engine->_lastSavePointIdInFile; _engine->getClock()->setClock(_engine->_savePointHeaders[_engine->_lastSavePointIdInFile].time); } } void VCR::stop() { _currentSavePointInVCR = _engine->_currentSavePoint; _engine->getClock()->stopClock(_engine->_savePointHeaders[_engine->_currentSavePoint].time); } void VCR::seekToTime(int32 time) { int bestIdx = 0; int32 minDiff = ABS(_engine->_savePointHeaders[0].time - time); for (int i = 0; i <= _engine->_lastSavePointIdInFile; i++) { int32 curDiff = ABS(_engine->_savePointHeaders[i].time - time); if (curDiff < minDiff) { minDiff = curDiff; bestIdx = i; } } _currentSavePointInVCR = bestIdx; _engine->getClock()->setClock(_engine->_savePointHeaders[bestIdx].time); } void VCR::updateCurGame(int32 fromTime, int32 toTime, bool searchEntry) { int32 minTimeDiff = 0x7FFFFFFF; int newMenuIdx = 0; if (toTime != fromTime) { newMenuIdx = _engine->_currentSavePoint; if (toTime >= fromTime) { if (searchEntry) { for (int idx = _engine->_currentSavePoint; idx >= 0; --idx) { int32 gameTime = _engine->_savePointHeaders[idx].time; if (gameTime <= fromTime && minTimeDiff >= fromTime - gameTime) { minTimeDiff = fromTime - gameTime; newMenuIdx = idx; } } } else { newMenuIdx = _engine->_currentSavePoint - 1; } } else if (searchEntry) { for (int idx = _engine->_currentSavePoint; idx <= _engine->_lastSavePointIdInFile; ++idx) { int32 gameTime = _engine->_savePointHeaders[idx].time; if (gameTime >= fromTime && minTimeDiff > gameTime - fromTime) { minTimeDiff = gameTime - fromTime; newMenuIdx = idx; } } } else { newMenuIdx = _engine->_currentSavePoint + 1; } _engine->_currentSavePoint = newMenuIdx; _engine->getMenu()->updateEgg(); } if (_engine->_currentSavePoint == _currentSavePointInVCR && _engine->_savePointHeaders[newMenuIdx].partNumber != _engine->getLogicManager()->_globals[kGlobalChapter]) { _engine->getLogicManager()->_globals[kGlobalChapter] = _engine->_savePointHeaders[_engine->_currentSavePoint].partNumber; } } void VCR::go() { free(); if (_engine->_savegameTempNames[_engine->_currentGameFileColorId] == _engine->_savegameFilename) { if (_engine->_lastSavePointIdInFile == _engine->_currentSavePoint) { _engine->getSaveManager()->continueGame(); return; } _engine->getSaveManager()->startRewoundGame(); return; } if (_engine->_lastSavePointIdInFile != _engine->_currentSavePoint) { _engine->getSaveManager()->startRewoundGame(); return; } _engine->_gracePeriodTimer = 0; if (_engine->_currentSavePoint) { _engine->getSaveManager()->continueGame(); return; } _engine->startNewGame(); } } // End of namespace LastExpress