diff options
Diffstat (limited to 'engines/dm/loadsave.cpp')
-rw-r--r-- | engines/dm/loadsave.cpp | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/engines/dm/loadsave.cpp b/engines/dm/loadsave.cpp new file mode 100644 index 0000000000..8bc3770226 --- /dev/null +++ b/engines/dm/loadsave.cpp @@ -0,0 +1,445 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +/* +* Based on the Reverse Engineering work of Christophe Fontanel, +* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/) +*/ +#include "common/system.h" +#include "common/savefile.h" +#include "common/translation.h" +#include "graphics/thumbnail.h" +#include "gui/saveload.h" + +#include "dm/dm.h" +#include "dm/dungeonman.h" +#include "dm/timeline.h" +#include "dm/group.h" +#include "dm/champion.h" +#include "dm/menus.h" +#include "dm/eventman.h" +#include "dm/projexpl.h" +#include "dm/dialog.h" + +namespace DM { + +LoadgameResult DMEngine::loadgame(int16 slot) { + if (slot == -1 && _gameMode == kDMModeLoadSavedGame) + return kDMLoadgameFailure; + + bool fadePalette = true; + Common::String fileName; + Common::SaveFileManager *saveFileManager = nullptr; + Common::InSaveFile *file = nullptr; + + struct { + SaveTarget _saveTarget; + int32 _saveVersion; + OriginalSaveFormat _saveFormat; + OriginalSavePlatform _savePlatform; + uint16 _dungeonId; + } dmSaveHeader; + + if (_gameMode != kDMModeLoadSavedGame) { + //L1366_B_FadePalette = !F0428_DIALOG_RequireGameDiskInDrive_NoDialogDrawn(C0_DO_NOT_FORCE_DIALOG_DM_CSB, true); + _restartGameAllowed = false; + _championMan->_partyChampionCount = 0; + _championMan->_leaderHandObject = Thing::_none; + } else { + fileName = getSavefileName(slot); + saveFileManager = _system->getSavefileManager(); + file = saveFileManager->openForLoading(fileName); + + SaveGameHeader header; + readSaveGameHeader(file, &header); + + warning("MISSING CODE: missing check for matching format and platform in save in f435_loadgame"); + + dmSaveHeader._saveTarget = (SaveTarget)file->readSint32BE(); + dmSaveHeader._saveVersion = file->readSint32BE(); + dmSaveHeader._saveFormat = (OriginalSaveFormat)file->readSint32BE(); + dmSaveHeader._savePlatform = (OriginalSavePlatform)file->readSint32BE(); + + // Skip _gameId, which was useless + file->readSint32BE(); + dmSaveHeader._dungeonId = file->readUint16BE(); + + _gameTime = file->readSint32BE(); + // G0349_ul_LastRandomNumber = L1371_s_GlobalData.LastRandomNumber; + _championMan->_partyChampionCount = file->readUint16BE(); + _dungeonMan->_partyMapX = file->readSint16BE(); + _dungeonMan->_partyMapY = file->readSint16BE(); + _dungeonMan->_partyDir = (Direction)file->readUint16BE(); + _dungeonMan->_partyMapIndex = file->readByte(); + _championMan->_leaderIndex = (ChampionIndex)file->readSint16BE(); + _championMan->_magicCasterChampionIndex = (ChampionIndex)file->readSint16BE(); + _timeline->_eventCount = file->readUint16BE(); + _timeline->_firstUnusedEventIndex = file->readUint16BE(); + _timeline->_eventMaxCount = file->readUint16BE(); + _groupMan->_currActiveGroupCount = file->readUint16BE(); + _projexpl->_lastCreatureAttackTime = file->readSint32BE(); + _projexpl->_lastPartyMovementTime = file->readSint32BE(); + _disabledMovementTicks = file->readSint16BE(); + _projectileDisableMovementTicks = file->readSint16BE(); + _lastProjectileDisabledMovementDirection = file->readSint16BE(); + _championMan->_leaderHandObject = Thing(file->readUint16BE()); + _groupMan->_maxActiveGroupCount = file->readUint16BE(); + if (!_restartGameRequest) { + _timeline->initTimeline(); + _groupMan->initActiveGroups(); + } + + _groupMan->loadActiveGroupPart(file); + _championMan->loadPartyPart2(file); + _timeline->loadEventsPart(file); + _timeline->loadTimelinePart(file); + + // read sentinel + uint32 sentinel = file->readUint32BE(); + assert(sentinel == 0x6f85e3d3); + + _dungeonId = dmSaveHeader._dungeonId; + } + + _dungeonMan->loadDungeonFile(file); + delete file; + + if (_gameMode != kDMModeLoadSavedGame) { + _timeline->initTimeline(); + _groupMan->initActiveGroups(); + + if (fadePalette) { + _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer); + delay(1); + _displayMan->fillScreen(kDMColorBlack); + _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen); + } + } else { + _restartGameAllowed = true; + + switch (getGameLanguage()) { // localized + case Common::DE_DEU: + _dialog->dialogDraw(nullptr, "SPIEL WIRD GELADEN . . .", nullptr, nullptr, nullptr, nullptr, true, true, true); + break; + case Common::FR_FRA: + _dialog->dialogDraw(nullptr, "CHARGEMENT DU JEU . . .", nullptr, nullptr, nullptr, nullptr, true, true, true); + break; + default: + _dialog->dialogDraw(nullptr, "LOADING GAME . . .", nullptr, nullptr, nullptr, nullptr, true, true, true); + break; + } + } + _championMan->_partyDead = false; + + return kDMLoadgameSuccess; +} + + +void DMEngine::saveGame() { + _menuMan->drawDisabledMenu(); + _eventMan->showMouse(); + + switch (getGameLanguage()) { // localized + default: + case Common::EN_ANY: + _dialog->dialogDraw(nullptr, nullptr, "SAVE AND PLAY", "SAVE AND QUIT", "CANCEL", "LOAD", false, false, false); + break; + case Common::DE_DEU: + _dialog->dialogDraw(nullptr, nullptr, "SICHERN/SPIEL", "SICHERN/ENDEN", "WIDERRUFEN", "LOAD", false, false, false); + break; + case Common::FR_FRA: + _dialog->dialogDraw(nullptr, nullptr, "GARDER/JOUER", "GARDER/SORTIR", "ANNULLER", "LOAD", false, false, false); + break; + } + + enum SaveAndPlayChoice { + kSaveAndPlay = 1, + kSaveAndQuit = 2, + kCancel = 3, + kLoad = 4 + }; + + SaveAndPlayChoice saveAndPlayChoice = (SaveAndPlayChoice)_dialog->getChoice(4, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone); + + if (saveAndPlayChoice == kLoad) { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + int loadSlot = dialog->runModalWithCurrentTarget(); + delete dialog; + if (loadSlot >= 0) { + _loadSaveSlotAtRuntime = loadSlot; + return; + } + + saveAndPlayChoice = kCancel; + } + + if (saveAndPlayChoice == kSaveAndQuit || saveAndPlayChoice == kSaveAndPlay) { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + int16 saveSlot = dialog->runModalWithCurrentTarget(); + Common::String saveDescription = dialog->getResultString(); + if (saveDescription.empty()) + saveDescription = "Nice save ^^"; + delete dialog; + + if (saveSlot >= 0) { + switch (getGameLanguage()) { // localized + default: + case Common::EN_ANY: + _dialog->dialogDraw(nullptr, "SAVING GAME . . .", nullptr, nullptr, nullptr, nullptr, false, false, false); + break; + case Common::DE_DEU: + _dialog->dialogDraw(nullptr, "SPIEL WIRD GESICHERT . . .", nullptr, nullptr, nullptr, nullptr, false, false, false); + break; + case Common::FR_FRA: + _dialog->dialogDraw(nullptr, "UN MOMENT A SAUVEGARDER DU JEU...", nullptr, nullptr, nullptr, nullptr, false, false, false); + break; + } + + uint16 champHandObjWeight = 0; + if (!_championMan->_leaderEmptyHanded) { + champHandObjWeight = _dungeonMan->getObjectWeight(_championMan->_leaderHandObject); + _championMan->_champions[_championMan->_leaderIndex]._load -= champHandObjWeight; + } + + if (!writeCompleteSaveFile(saveSlot, saveDescription, saveAndPlayChoice)) { + _dialog->dialogDraw(nullptr, "Unable to open file for saving", "OK", nullptr, nullptr, nullptr, false, false, false); + _dialog->getChoice(1, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone); + } + + if (!_championMan->_leaderEmptyHanded) { + _championMan->_champions[_championMan->_leaderIndex]._load += champHandObjWeight; + } + } else + saveAndPlayChoice = kCancel; + } + + + if (saveAndPlayChoice == kSaveAndQuit) { + _eventMan->hideMouse(); + endGame(false); + } + + _restartGameAllowed = true; + _menuMan->drawEnabledMenus(); + _eventMan->hideMouse(); +} + +Common::String DMEngine::getSavefileName(uint16 slot) { + return Common::String::format("%s.%03u", _targetName.c_str(), slot); +} + +#define SAVEGAME_ID MKTAG('D', 'M', '2', '1') +#define SAVEGAME_VERSION 1 + +void DMEngine::writeSaveGameHeader(Common::OutSaveFile *out, const Common::String& saveName) { + out->writeUint32BE(SAVEGAME_ID); + + // Write version + out->writeByte(SAVEGAME_VERSION); + + // Write savegame name + out->writeString(saveName); + out->writeByte(0); + + // Save the game thumbnail + if (_saveThumbnail) + out->write(_saveThumbnail->getData(), _saveThumbnail->size()); + else + Graphics::saveThumbnail(*out); + + // Creation date/time + TimeDate curTime; + _system->getTimeAndDate(curTime); + + uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF); + uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF); + uint32 playTime = getTotalPlayTime() / 1000; + + out->writeUint32BE(saveDate); + out->writeUint16BE(saveTime); + out->writeUint32BE(playTime); +} + +bool DMEngine::writeCompleteSaveFile(int16 saveSlot, Common::String& saveDescription, int16 saveAndPlayChoice) { + Common::String savefileName = getSavefileName(saveSlot); + Common::SaveFileManager *saveFileManager = _system->getSavefileManager(); + Common::OutSaveFile *file = saveFileManager->openForSaving(savefileName); + + if (!file) + return false; + + writeSaveGameHeader(file, saveDescription); + + file->writeSint32BE(_gameVersion->_saveTargetToWrite); + file->writeSint32BE(1); // save version + file->writeSint32BE(_gameVersion->_origSaveFormatToWrite); + file->writeSint32BE(_gameVersion->_origPlatformToWrite); + + // Was _gameID, useless. + file->writeSint32BE(0); + file->writeUint16BE(_dungeonId); + + // write C0_SAVE_PART_GLOBAL_DATA part + file->writeSint32BE(_gameTime); + //L1348_s_GlobalData.LastRandomNumber = G0349_ul_LastRandomNumber; + file->writeUint16BE(_championMan->_partyChampionCount); + file->writeSint16BE(_dungeonMan->_partyMapX); + file->writeSint16BE(_dungeonMan->_partyMapY); + file->writeUint16BE(_dungeonMan->_partyDir); + file->writeByte(_dungeonMan->_partyMapIndex); + file->writeSint16BE(_championMan->_leaderIndex); + file->writeSint16BE(_championMan->_magicCasterChampionIndex); + file->writeUint16BE(_timeline->_eventCount); + file->writeUint16BE(_timeline->_firstUnusedEventIndex); + file->writeUint16BE(_timeline->_eventMaxCount); + file->writeUint16BE(_groupMan->_currActiveGroupCount); + file->writeSint32BE(_projexpl->_lastCreatureAttackTime); + file->writeSint32BE(_projexpl->_lastPartyMovementTime); + file->writeSint16BE(_disabledMovementTicks); + file->writeSint16BE(_projectileDisableMovementTicks); + file->writeSint16BE(_lastProjectileDisabledMovementDirection); + file->writeUint16BE(_championMan->_leaderHandObject.toUint16()); + file->writeUint16BE(_groupMan->_maxActiveGroupCount); + + // write C1_SAVE_PART_ACTIVE_GROUP part + _groupMan->saveActiveGroupPart(file); + // write C2_SAVE_PART_PARTY part + _championMan->savePartyPart2(file); + // write C3_SAVE_PART_EVENTS part + _timeline->saveEventsPart(file); + // write C4_SAVE_PART_TIMELINE part + _timeline->saveTimelinePart(file); + + // write sentinel + file->writeUint32BE(0x6f85e3d3); + + // save _g278_dungeonFileHeader + DungeonFileHeader &header = _dungeonMan->_dungeonFileHeader; + file->writeUint16BE(header._ornamentRandomSeed); + file->writeUint16BE(header._rawMapDataSize); + file->writeByte(header._mapCount); + file->writeByte(0); // to match the structure of dungeon.dat, will be discarded + file->writeUint16BE(header._textDataWordCount); + file->writeUint16BE(header._partyStartLocation); + file->writeUint16BE(header._squareFirstThingCount); + for (uint16 i = 0; i < 16; ++i) + file->writeUint16BE(header._thingCounts[i]); + + // save _g277_dungeonMaps + for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._mapCount; ++i) { + Map &map = _dungeonMan->_dungeonMaps[i]; + + file->writeUint16BE(map._rawDunDataOffset); + file->writeUint32BE(0); // to match the structure of dungeon.dat, will be discarded + file->writeByte(map._offsetMapX); + file->writeByte(map._offsetMapY); + + uint16 tmp; + tmp = ((map._height & 0x1F) << 11) | ((map._width & 0x1F) << 6) | (map._level & 0x3F); + file->writeUint16BE(tmp); + + tmp = ((map._randFloorOrnCount & 0xF) << 12) | ((map._floorOrnCount & 0xF) << 8) + | ((map._randWallOrnCount & 0xF) << 4) | (map._wallOrnCount & 0xF); + file->writeUint16BE(tmp); + + tmp = ((map._difficulty & 0xF) << 12) | ((map._creatureTypeCount & 0xF) << 4) | (map._doorOrnCount & 0xF); + file->writeUint16BE(tmp); + + tmp = ((map._doorSet1 & 0xF) << 12) | ((map._doorSet0 & 0xF) << 8) + | ((map._wallSet & 0xF) << 4) | (map._floorSet & 0xF); + file->writeUint16BE(tmp); + } + + // save _g280_dungeonColumnsCumulativeSquareThingCount + for (uint16 i = 0; i < _dungeonMan->_dungeonColumCount; ++i) + file->writeUint16BE(_dungeonMan->_dungeonColumnsCumulativeSquareThingCount[i]); + + // save _g283_squareFirstThings + for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._squareFirstThingCount; ++i) + file->writeUint16BE(_dungeonMan->_squareFirstThings[i].toUint16()); + + // save _g260_dungeonTextData + for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._textDataWordCount; ++i) + file->writeUint16BE(_dungeonMan->_dungeonTextData[i]); + + // save _g284_thingData + for (uint16 thingIndex = 0; thingIndex < 16; ++thingIndex) + for (uint16 i = 0; i < _dungeonMan->_thingDataWordCount[thingIndex] * _dungeonMan->_dungeonFileHeader._thingCounts[thingIndex]; ++i) + file->writeUint16BE(_dungeonMan->_thingData[thingIndex][i]); + + // save _g276_dungeonRawMapData + for (uint32 i = 0; i < _dungeonMan->_dungeonFileHeader._rawMapDataSize; ++i) + file->writeByte(_dungeonMan->_dungeonRawMapData[i]); + + file->flush(); + file->finalize(); + delete file; + + return true; +} + +bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader *header) { + uint32 id = in->readUint32BE(); + + // Check if it's a valid ScummVM savegame + if (id != SAVEGAME_ID) + return false; + + // Read in the version + header->_version = in->readByte(); + + // Check that the save version isn't newer than this binary + if (header->_version > SAVEGAME_VERSION) + return false; + + // Read in the save name + Common::String saveName; + char ch; + while ((ch = (char)in->readByte()) != '\0') + saveName += ch; + header->_descr.setDescription(saveName); + + // Get the thumbnail + header->_descr.setThumbnail(Graphics::loadThumbnail(*in)); + + uint32 saveDate = in->readUint32BE(); + uint16 saveTime = in->readUint16BE(); + uint32 playTime = in->readUint32BE(); + + int day = (saveDate >> 24) & 0xFF; + int month = (saveDate >> 16) & 0xFF; + int year = saveDate & 0xFFFF; + header->_descr.setSaveDate(year, month, day); + + int hour = (saveTime >> 8) & 0xFF; + int minutes = saveTime & 0xFF; + header->_descr.setSaveTime(hour, minutes); + + header->_descr.setPlayTime(playTime * 1000); + if (g_engine) + g_engine->setTotalPlayTime(playTime * 1000); + + return true; +} + +} + |