/* 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/game/savegame.h" #include "lastexpress/data/cvcrfile.h" #include "lastexpress/game/logic.h" #include "lastexpress/menu/menu.h" #include "lastexpress/debug.h" #include "lastexpress/lastexpress.h" #include "common/savefile.h" namespace LastExpress { SaveManager::SaveManager(LastExpressEngine *engine) { _engine = engine; } void SaveManager::writeSavePoint(CVCRFile *file, int saveType, int character, int value) { int32 originalFilePos; int32 paddingSize; int32 posAfterWriting; SVCRSavePointHeader savePointHeader; byte emptyHeader[15]; savePointHeader.type = saveType; savePointHeader.magicNumber = 0xE660E660; savePointHeader.size = 0; savePointHeader.time = _engine->getLogicManager()->_gameTime; savePointHeader.partNumber = _engine->getLogicManager()->_globals[kGlobalChapter]; savePointHeader.latestGameEvent = value; savePointHeader.emptyField1 = 0; savePointHeader.emptyField2 = 0; originalFilePos = file->tell(); file->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, 0); file->flush(); file->writeRLE(&character, 4, 1); file->writeRLE(&_engine->getLogicManager()->_gameTime, 4, 1); file->writeRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1); file->writeRLE(&_engine->getLogicManager()->_realTime, 4, 1); file->writeRLE(&_engine->getLogicManager()->_activeNode, 4, 1); file->writeRLE(&_engine->getLogicManager()->_closeUp, 1, 1); file->writeRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1); file->writeRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1); file->writeRLE(&_engine->getLogicManager()->_activeItem, 4, 1); file->writeRLE(_engine->getLogicManager()->_blockedViews, 4, 1000); file->writeRLE(_engine->getLogicManager()->_blockedX, 4, 16); file->writeRLE(_engine->getLogicManager()->_softBlockedX, 4, 16); file->writeRLE(_engine->getLogicManager()->_globals, 4, 128); file->writeRLE(_engine->getLogicManager()->_doneNIS, 1, 512); // Handle complex types (which were fine in the original, but not within a crossplatform context)... byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each byte *charactersBuffer = (byte *)malloc(40 * 1262); // 128 objects, 5 bytes each assert(inventoryBuffer && doorsBuffer && charactersBuffer); // Copy Item data... for (int i = 0; i < 32; i++) { int offset = i * 7; inventoryBuffer[offset] = _engine->getLogicManager()->_items[i].mnum; WRITE_LE_UINT16(&inventoryBuffer[offset + 1], _engine->getLogicManager()->_items[i].closeUp); inventoryBuffer[offset + 3] = _engine->getLogicManager()->_items[i].useable; inventoryBuffer[offset + 4] = _engine->getLogicManager()->_items[i].haveIt; inventoryBuffer[offset + 5] = _engine->getLogicManager()->_items[i].inPocket; inventoryBuffer[offset + 6] = _engine->getLogicManager()->_items[i].floating; } file->writeRLE(inventoryBuffer, 7, 32); // Copy Door data... for (int i = 0; i < 128; i++) { int offset = i * 5; doorsBuffer[offset] = _engine->getLogicManager()->_doors[i].who; doorsBuffer[offset + 1] = _engine->getLogicManager()->_doors[i].status; doorsBuffer[offset + 2] = _engine->getLogicManager()->_doors[i].windowCursor; doorsBuffer[offset + 3] = _engine->getLogicManager()->_doors[i].handleCursor; doorsBuffer[offset + 4] = _engine->getLogicManager()->_doors[i].model; } file->writeRLE(doorsBuffer, 5, 128); // Copy Character data... for (int i = 0; i < 40; i++) { int offset = 0; Character characterStruct = getCharacter(i); // First copy CallParams (9 sets of 32 integers)... for (int j = 0; j < 9; j++) { for (int k = 0; k < 32; k++) { WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], characterStruct.callParams[j].parameters[k]); offset += 4; } } // Copy callbacks array (16 bytes)... for (int j = 0; j < 16; j++) { charactersBuffer[i * 1262 + offset++] = characterStruct.callbacks[j]; } // Copy currentCall (1 byte)... charactersBuffer[i * 1262 + offset++] = characterStruct.currentCall; // Copy characterPosition (3 uint16)... WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.position); offset += 2; WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.location); offset += 2; WRITE_LE_UINT16(&charactersBuffer[i * 1262 + offset], characterStruct.characterPosition.car); offset += 2; // Copy the remaining basic fields... charactersBuffer[i * 1262 + offset++] = characterStruct.walkCounter; charactersBuffer[i * 1262 + offset++] = characterStruct.attachedConductor; charactersBuffer[i * 1262 + offset++] = characterStruct.inventoryItem; charactersBuffer[i * 1262 + offset++] = characterStruct.direction; WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.waitedTicksUntilCycleRestart); offset += 2; WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq1); offset += 2; WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.currentFrameSeq2); offset += 2; WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.elapsedFrames); offset += 2; WRITE_LE_INT16(&charactersBuffer[i * 1262 + offset], characterStruct.walkStepSize); offset += 2; charactersBuffer[i * 1262 + offset++] = characterStruct.clothes; charactersBuffer[i * 1262 + offset++] = characterStruct.position2; charactersBuffer[i * 1262 + offset++] = characterStruct.car2; charactersBuffer[i * 1262 + offset++] = characterStruct.doProcessEntity; charactersBuffer[i * 1262 + offset++] = characterStruct.needsPosFudge; charactersBuffer[i * 1262 + offset++] = characterStruct.needsSecondaryPosFudge; charactersBuffer[i * 1262 + offset++] = characterStruct.directionSwitch; // Copy string fields memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName, 13); offset += 13; memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceName2, 13); offset += 13; memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNamePrefix, 7); offset += 7; memcpy(&charactersBuffer[i * 1262 + offset], characterStruct.sequenceNameCopy, 13); offset += 13; // Set pointers to zero (each 4 bytes) WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame1 WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // frame2 WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence1 WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence2 WRITE_LE_UINT32(&charactersBuffer[i * 1262 + offset], 0); offset += 4; // sequence3 // At this point, offset should equal 1262! assert(offset == 1262); } file->writeRLE(charactersBuffer, 1262, 40); free(inventoryBuffer); free(doorsBuffer); free(charactersBuffer); _engine->getSoundManager()->saveSoundInfo(file); _engine->getMessageManager()->saveMessages(file); savePointHeader.size = file->flush(); if ((savePointHeader.size & 0xF) != 0) { memset(emptyHeader, 0, sizeof(emptyHeader)); paddingSize = ((~(savePointHeader.size & 0xFF) & 0xF) + 1); file->write(&emptyHeader, 1, paddingSize, 0); savePointHeader.size += paddingSize; } posAfterWriting = file->tell(); file->seek(originalFilePos, 0); checkSavePointHeader(&savePointHeader); file->write(&savePointHeader, 32, 1, false); file->seek(posAfterWriting, 0); } void SaveManager::readSavePoint(CVCRFile *file, int *saveType, uint8 *character, int *saveEvent, bool skipSoundLoading) { int latestGameEvent; int32 originalPos; int32 posDiff; SVCRSavePointHeader savePointHeader; if (saveType && character && saveEvent) { *saveType = 1; *character = kCharacterCath; *saveEvent = 0; file->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true); if (checkSavePointHeader(&savePointHeader)) { latestGameEvent = savePointHeader.latestGameEvent; *saveType = savePointHeader.type; *saveEvent = latestGameEvent; file->flush(); originalPos = file->tell(); // The original treats the "character" arg as uint8, but then asks // for a four bytes integer, causing a stack corruption around it. // This is our workaround... int32 intCharacter; file->readRLE(&intCharacter, 4, 1); *character = (uint8)(intCharacter & 0xFF); if (*character >= 40) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_gameTime, 4, 1); if (_engine->getLogicManager()->_gameTime < 1061100) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } if (_engine->getLogicManager()->_gameTime > 4941000) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_timeSpeed, 4, 1); if (_engine->getLogicManager()->_gameTime > 4941000) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_realTime, 4, 1); file->readRLE(&_engine->getLogicManager()->_activeNode, 4, 1); if (_engine->getLogicManager()->_activeNode >= 2500) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_closeUp, 1, 1); if (_engine->getLogicManager()->_closeUp > 1) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_nodeReturn, 4, 1); if (_engine->getLogicManager()->_nodeReturn >= 2500) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_nodeReturn2, 4, 1); if (_engine->getLogicManager()->_nodeReturn2 >= 2500) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(&_engine->getLogicManager()->_activeItem, 4, 1); if (_engine->getLogicManager()->_activeItem >= 32) { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); } file->readRLE(_engine->getLogicManager()->_blockedViews, 4, 1000); file->readRLE(_engine->getLogicManager()->_blockedX, 4, 16); file->readRLE(_engine->getLogicManager()->_softBlockedX, 4, 16); file->readRLE(_engine->getLogicManager()->_globals, 4, 128); file->readRLE(_engine->getLogicManager()->_doneNIS, 1, 512); // Handle complex types (which were fine in the original, but not within a crossplatform context)... byte *inventoryBuffer = (byte *)malloc(32 * 7); // 32 items, 7 bytes each byte *doorsBuffer = (byte *)malloc(128 * 5); // 128 objects, 5 bytes each byte *charactersBuffer = (byte *)malloc(40 * 1262); // 40 characters, 1262 bytes each assert(inventoryBuffer && doorsBuffer && charactersBuffer); // Read data from file file->readRLE(inventoryBuffer, 7, 32); file->readRLE(doorsBuffer, 5, 128); file->readRLE(charactersBuffer, 1262, 40); // Copy Item data from buffer to structures for (int i = 0; i < 32; i++) { int offset = i * 7; _engine->getLogicManager()->_items[i].mnum = inventoryBuffer[offset]; _engine->getLogicManager()->_items[i].closeUp = READ_LE_UINT16(&inventoryBuffer[offset + 1]); _engine->getLogicManager()->_items[i].useable = inventoryBuffer[offset + 3]; _engine->getLogicManager()->_items[i].haveIt = inventoryBuffer[offset + 4]; _engine->getLogicManager()->_items[i].inPocket = inventoryBuffer[offset + 5]; _engine->getLogicManager()->_items[i].floating = inventoryBuffer[offset + 6]; } // Copy Door data from buffer to structures for (int i = 0; i < 128; i++) { int offset = i * 5; _engine->getLogicManager()->_doors[i].who = doorsBuffer[offset]; _engine->getLogicManager()->_doors[i].status = doorsBuffer[offset + 1]; _engine->getLogicManager()->_doors[i].windowCursor = doorsBuffer[offset + 2]; _engine->getLogicManager()->_doors[i].handleCursor = doorsBuffer[offset + 3]; _engine->getLogicManager()->_doors[i].model = doorsBuffer[offset + 4]; } // Copy Character data from buffer to structures for (int i = 0; i < 40; i++) { int offset = 0; Character *characterStruct = &getCharacter(i); // Copy CallParams (9 sets of 32 integers) for (int j = 0; j < 9; j++) { for (int k = 0; k < 32; k++) { characterStruct->callParams[j].parameters[k] = READ_LE_UINT32(&charactersBuffer[i * 1262 + offset]); offset += 4; } } // Copy callbacks array (16 bytes) for (int j = 0; j < 16; j++) { characterStruct->callbacks[j] = charactersBuffer[i * 1262 + offset]; offset++; } // Copy currentCall (1 byte) characterStruct->currentCall = charactersBuffer[i * 1262 + offset]; offset++; // Copy characterPosition (3 uint16) characterStruct->characterPosition.position = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->characterPosition.location = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->characterPosition.car = READ_LE_UINT16(&charactersBuffer[i * 1262 + offset]); offset += 2; // Copy the remaining basic fields characterStruct->walkCounter = charactersBuffer[i * 1262 + offset++]; characterStruct->attachedConductor = charactersBuffer[i * 1262 + offset++]; characterStruct->inventoryItem = charactersBuffer[i * 1262 + offset++]; characterStruct->direction = charactersBuffer[i * 1262 + offset++]; characterStruct->waitedTicksUntilCycleRestart = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->currentFrameSeq1 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->currentFrameSeq2 = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->elapsedFrames = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->walkStepSize = READ_LE_INT16(&charactersBuffer[i * 1262 + offset]); offset += 2; characterStruct->clothes = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->position2 = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->car2 = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->doProcessEntity = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->needsPosFudge = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->needsSecondaryPosFudge = charactersBuffer[i * 1262 + offset]; offset++; characterStruct->directionSwitch = charactersBuffer[i * 1262 + offset]; offset++; // Copy string fields memcpy(characterStruct->sequenceName, &charactersBuffer[i * 1262 + offset], 13); offset += 13; memcpy(characterStruct->sequenceName2, &charactersBuffer[i * 1262 + offset], 13); offset += 13; memcpy(characterStruct->sequenceNamePrefix, &charactersBuffer[i * 1262 + offset], 7); offset += 7; memcpy(characterStruct->sequenceNameCopy, &charactersBuffer[i * 1262 + offset], 13); offset += 13; // Skip pointer data... offset += 4; // frame1 offset += 4; // frame2 offset += 4; // sequence1 offset += 4; // sequence2 offset += 4; // sequence3 // At this point, offset should equal 1262! assert(offset == 1262); } free(inventoryBuffer); free(doorsBuffer); free(charactersBuffer); _engine->getSoundManager()->loadSoundInfo(file, skipSoundLoading); _engine->getMessageManager()->loadMessages(file); _engine->getLogicManager()->_globals[kGlobalChapter] = savePointHeader.partNumber; file->flush(); posDiff = (file->tell() - originalPos) & 0xFF; if ((posDiff & 0xF) != 0) file->seek(((~posDiff & 0xF) + 1), 1); for (int i = 0; i < 40; i++) { getCharacter(i).frame1 = nullptr; getCharacter(i).frame2 = nullptr; getCharacter(i).sequence1 = nullptr; getCharacter(i).sequence2 = nullptr; getCharacter(i).sequence3 = nullptr; } } } } void SaveManager::validateSaveFile(bool flag) { SVCRSavePointHeader savePointHeader; SVCRFileHeader fileHeader; CVCRFile *tempFile = new CVCRFile(_engine); CVCRFile *saveFile = new CVCRFile(_engine); bool hasValidationError = false; if (flag) { if (_engine->_savePointHeaders) _engine->getMemoryManager()->freeMem(_engine->_savePointHeaders); _engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem( sizeof(SVCRSavePointHeader), _engine->_savegameFilename, kCharacterMaster ); if (!_engine->_savePointHeaders) { error("Out of memory"); } _engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700; _engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1; } saveFile->open(_engine->_savegameFilename, CVCRMODE_RB); saveFile->seek(0, SEEK_END); int fileSize = saveFile->tell(); saveFile->seek(0, SEEK_SET); if (fileSize >= (int32)sizeof(SVCRFileHeader)) { saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true); if (checkFileHeader(&fileHeader)) { if (flag) { if (_engine->_savePointHeaders) _engine->getMemoryManager()->freeMem(_engine->_savePointHeaders); _engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem( sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1), _engine->_savegameFilename, kCharacterMaster ); if (!_engine->_savePointHeaders) { error("Out of memory"); } _engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700; _engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1; } for (int i = 0; fileSize >= (int32)sizeof(SVCRFileHeader) && i < fileHeader.numVCRGames; ++i) { _engine->getSoundManager()->soundThread(); saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true); if (flag) { memcpy(&_engine->_savePointHeaders[i + 1], &savePointHeader, sizeof(savePointHeader)); } fileSize -= sizeof(SVCRSavePointHeader); if (fileSize >= 0) { if (checkSavePointHeader(&savePointHeader)) { fileSize -= savePointHeader.size; if (fileSize >= 0) { saveFile->seek(savePointHeader.size, SEEK_CUR); } else { hasValidationError = true; } } else { fileSize = 0; hasValidationError = true; } } else { hasValidationError = true; } } saveFile->close(); } else { hasValidationError = true; saveFile->close(); } } else { hasValidationError = true; saveFile->close(); } if (hasValidationError) { saveFile->open(_engine->_savegameFilename, CVCRMODE_RB); saveFile->seek(0, SEEK_END); fileSize = saveFile->tell(); saveFile->seek(0, SEEK_SET); if (fileSize < (int32)sizeof(SVCRFileHeader)) { if (fileSize) { error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename); } saveFile->close(); _engine->getVCR()->virginSaveFile(); delete tempFile; delete saveFile; return; } saveFile->read(&fileHeader, sizeof(SVCRFileHeader), 1, false, true); if (!checkFileHeader(&fileHeader)) { error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename); saveFile->close(); _engine->getVCR()->virginSaveFile(); delete tempFile; delete saveFile; return; } if (flag) { if (_engine->_savePointHeaders) _engine->getMemoryManager()->freeMem(_engine->_savePointHeaders); _engine->_savePointHeaders = (SVCRSavePointHeader *)_engine->getMemoryManager()->allocMem( sizeof(SVCRSavePointHeader) * (fileHeader.numVCRGames + 1), _engine->_savegameFilename, kCharacterMaster ); if (!_engine->_savePointHeaders) { error("Out of memory"); } _engine->_savePointHeaders->time = _engine->isDemo() ? 2241000 : 1037700; _engine->_savePointHeaders->partNumber = _engine->isDemo() ? 3 : 1; } int offset = sizeof(SVCRFileHeader); tempFile->open("temp.egg", CVCRMODE_WB); tempFile->seek(sizeof(SVCRFileHeader), 0); tempFile->close(); fileHeader.realWritePos = sizeof(SVCRFileHeader); fileHeader.nextWritePos = sizeof(SVCRFileHeader); fileHeader.lastIsTemporary = 0; int numSavePoints = 0; for (int j = 0; true; j++) { if (fileSize < (int32)sizeof(SVCRFileHeader) || j >= fileHeader.numVCRGames) { saveFile->close(); fileHeader.numVCRGames = numSavePoints; tempFile->open("temp.egg", CVCRMODE_RWB); tempFile->seek(0, SEEK_SET); tempFile->write(&fileHeader, sizeof(SVCRFileHeader), 1, false); tempFile->seek(offset, SEEK_SET); tempFile->close(); if (removeSavegame(_engine->_savegameFilename)) { error("Error deleting file \"%s\"", _engine->_savegameFilename); } else if (renameSavegame("temp.egg", _engine->_savegameFilename)) { error("Error renaming file \"%s\" to \"%s\"", "temp.egg", _engine->_savegameFilename); } delete tempFile; delete saveFile; return; } _engine->getSoundManager()->soundThread(); saveFile->read(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false, true); if (flag) { memcpy(&_engine->_savePointHeaders[j + 1], &savePointHeader, sizeof(savePointHeader)); } fileSize -= sizeof(SVCRSavePointHeader); if (fileSize < 0) break; if (checkSavePointHeader(&savePointHeader)) { fileSize -= savePointHeader.size; if (fileSize < 0) break; numSavePoints++; byte *tempMem = (byte *)malloc(savePointHeader.size); saveFile->read(tempMem, savePointHeader.size, 1, false, true); tempFile->open("temp.egg", CVCRMODE_RWB); tempFile->seek(offset, SEEK_SET); if (savePointHeader.type != 3 && savePointHeader.type != 4) fileHeader.realWritePos = offset; tempFile->write(&savePointHeader, sizeof(SVCRSavePointHeader), 1, false); tempFile->write(tempMem, savePointHeader.size, 1, false); tempFile->close(); free(tempMem); offset += savePointHeader.size + sizeof(SVCRSavePointHeader); if (savePointHeader.type == 3 || savePointHeader.type == 4) { fileHeader.lastIsTemporary = 1; } else { fileHeader.lastIsTemporary = 0; fileHeader.nextWritePos = offset; } } else { fileSize = 0; error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename); } } error("Attempting to salvage corrupt save game file \"%s\"", _engine->_savegameFilename); } delete tempFile; delete saveFile; } bool SaveManager::checkFileHeader(SVCRFileHeader *fileHeader) { if (fileHeader->magicNumber == 0x12001200 && fileHeader->numVCRGames >= 0 && fileHeader->nextWritePos >= (int32)sizeof(SVCRFileHeader) && fileHeader->realWritePos >= (int32)sizeof(SVCRFileHeader) && fileHeader->lastIsTemporary < 2 && fileHeader->brightness <= 6 && fileHeader->volume < 8) { if (fileHeader->saveVersion == 9) { return true; } else { error("Save game file \"%s\" is incompatible with this version of the game", _engine->_savegameFilename); return false; } } else { error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); return false; } } bool SaveManager::checkSavePointHeader(SVCRSavePointHeader *savePointHeader) { if ((uint32)savePointHeader->magicNumber == 0xE660E660) { if (savePointHeader->type > 0 && savePointHeader->type <= 5) { if (savePointHeader->time >= 1061100 && savePointHeader->time <= 4941000) { if (savePointHeader->size > 0 && (savePointHeader->size & 0xF) == 0 && savePointHeader->partNumber > 0) return true; } } } error("Save game file \"%s\" is corrupt", _engine->_savegameFilename); return false; } void SaveManager::continueGame() { uint8 character; int saveEvent; int saveType; SVCRFileHeader header; _engine->_savegame->open(_engine->_savegameFilename, CVCRMODE_RB); _engine->_savegame->read(&header, sizeof(SVCRFileHeader), 1, false, true); if (checkFileHeader(&header)) { _engine->_savegame->seek(header.realWritePos, SEEK_SET); readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, header.lastIsTemporary); _engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime; if (header.lastIsTemporary) { _engine->getSoundManager()->destroyAllSound(); readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, 0); } _engine->_savegame->close(); _engine->getOtisManager()->wipeAllGSysInfo(); _engine->getLogicManager()->CONS_All(false, character); } else { _engine->_savegame->close(); } } void SaveManager::startRewoundGame() { SVCRFileHeader header; SVCRSavePointHeader savePointHeader; int saveEvent; int saveType; uint8 character = 0; CVCRFile *saveFile = new CVCRFile(_engine); byte *buf = (byte *)malloc(0x2000); _engine->_fightSkipCounter = 0; _engine->_savegame->open(_engine->_savegameNames[_engine->_currentGameFileColorId], CVCRMODE_RB); _engine->_savegameFilename = _engine->_savegameTempNames[_engine->_currentGameFileColorId]; saveFile->open(_engine->_savegameFilename, CVCRMODE_WB); header.nextWritePos = sizeof(SVCRFileHeader); header.numVCRGames = _engine->_currentSavePoint; header.realWritePos = sizeof(SVCRFileHeader); header.magicNumber = 0x12001200; header.lastIsTemporary = 0; header.brightness = _engine->getGraphicsManager()->getGammaLevel(); header.volume = _engine->getSoundManager()->getMasterVolume(); header.saveVersion = 9; saveFile->write(&header, sizeof(header), 1, false); _engine->_savegame->seek(sizeof(header), SEEK_SET); if (_engine->_currentSavePoint > 1) { int count = _engine->_currentSavePoint - 1; do { _engine->_savegame->read(&savePointHeader, sizeof(savePointHeader), 1, false, true); saveFile->write(&savePointHeader, sizeof(savePointHeader), 1, false); for (; savePointHeader.size > 0x2000; savePointHeader.size -= 0x2000) { _engine->_savegame->read(buf, 0x2000, 1, false, true); saveFile->write(buf, 0x2000, 1, false); } _engine->_savegame->read(buf, savePointHeader.size, 1, false, true); saveFile->write(buf, savePointHeader.size, 1, false); --count; } while (count); } free(buf); buf = nullptr; if (_engine->_currentSavePoint) { readSavePoint(_engine->_savegame, &saveType, &character, &saveEvent, false); _engine->_savegame->close(); _engine->getLogicManager()->_lastSavegameSessionTicks = _engine->getLogicManager()->_realTime; header.realWritePos = saveFile->tell(); writeSavePoint(saveFile, saveType, character, saveEvent); header.nextWritePos = saveFile->tell(); checkFileHeader(&header); saveFile->seek(0, SEEK_SET); saveFile->write(&header, sizeof(header), 1, false); } _engine->_savegame->close(); saveFile->close(); delete saveFile; _engine->_gracePeriodIndex = _engine->_currentSavePoint; _engine->_gracePeriodTimer = _engine->getLogicManager()->_globals[kGlobalJacket] < 2 ? 225 : 450; if (_engine->_currentSavePoint) { _engine->getOtisManager()->wipeAllGSysInfo(); _engine->getLogicManager()->CONS_All(false, character); } else { _engine->startNewGame(); } } bool SaveManager::fileExists(const char *filename) { return g_system->getSavefileManager()->exists(_engine->getTargetName() + "-" + Common::String(filename)); } bool SaveManager::removeSavegame(const char *filename) { return !g_system->getSavefileManager()->removeSavefile(_engine->getTargetName() + "-" + Common::String(filename)); } bool SaveManager::renameSavegame(const char *oldName, const char *newName) { return !g_system->getSavefileManager()->renameSavefile( _engine->getTargetName() + "-" + Common::String(oldName), _engine->getTargetName() + "-" + Common::String(newName), false); } } // End of namespace LastExpress