Files
2026-02-02 04:50:13 +01:00

710 lines
21 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<int32>(_engine->_savePointHeaders[0].time - time);
for (int i = 0; i <= _engine->_lastSavePointIdInFile; i++) {
int32 curDiff = ABS<int32>(_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