https://github.com/scummvm/scummvm
Tip revision: a06f50421c1e672bda191d2b4775c4c31feb1194 authored by Lothar Serra Mari on 20 January 2023, 19:26:11 UTC
RELEASE: This is 2.7.0pre
RELEASE: This is 2.7.0pre
Tip revision: a06f504
saveload.cpp
/* 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 "common/savefile.h"
#include "common/system.h"
#include "graphics/thumbnail.h"
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/events.h"
#include "saga/interface.h"
#include "saga/isomap.h"
#include "saga/music.h"
#include "saga/render.h"
#include "saga/scene.h"
#include "saga/script.h"
#define CURRENT_SAGA_VER 8
namespace Saga {
static SaveFileData emptySlot = {
"", 0
};
char* SagaEngine::calcSaveFileName(uint slotNumber) {
static char name[MAX_FILE_NAME];
Common::sprintf_s(name, "%s.s%02u", _targetName.c_str(), slotNumber);
return name;
}
SaveFileData *SagaEngine::getSaveFile(uint idx) {
if (idx >= MAX_SAVES) {
error("getSaveFileName wrong idx");
}
if (isSaveListFull()) {
return &_saveFiles[_saveFilesCount - idx - 1];
} else {
if (!emptySlot.name[0])
Common::strlcpy(emptySlot.name, getTextString(kTextNewSave), SAVE_TITLE_SIZE);
return (idx == 0) ? &emptySlot : &_saveFiles[_saveFilesCount - idx];
}
}
bool SagaEngine::locateSaveFile(char *saveName, uint &titleNumber) {
uint i;
for (i = 0; i < _saveFilesCount; i++) {
if (strcmp(saveName, _saveFiles[i].name) == 0) {
if (isSaveListFull()) {
titleNumber = _saveFilesCount - i - 1;
} else {
titleNumber = _saveFilesCount - i;
}
return true;
}
}
return false;
}
uint SagaEngine::getNewSaveSlotNumber() const {
uint i, j;
bool found;
for (i = 0; i < MAX_SAVES; i++) {
found = false;
for (j = 0; j < _saveFilesCount; j++) {
if (_saveFiles[j].slotNumber == i) {
found = true;
break;
}
}
if (!found) {
return i;
}
}
error("getNewSaveSlotNumber save list is full");
}
static int compareSaveFileData(const void *a, const void *b) {
const SaveFileData *s1 = (const SaveFileData *)a;
const SaveFileData *s2 = (const SaveFileData *)b;
if (s1->slotNumber < s2->slotNumber) {
return -1;
} else if (s1->slotNumber > s2->slotNumber) {
return 1;
} else {
return 0;
}
}
void SagaEngine::fillSaveList() {
int i;
Common::InSaveFile *in;
Common::StringArray filenames;
char slot[3];
int slotNumber;
char *name;
name = calcSaveFileName(MAX_SAVES);
name[strlen(name) - 2] = '*';
name[strlen(name) - 1] = 0;
filenames = _saveFileMan->listSavefiles(name);
for (i = 0; i < MAX_SAVES; i++) {
_saveFiles[i].name[0] = 0;
_saveFiles[i].slotNumber = (uint)-1;
}
_saveFilesCount = 0;
for (Common::StringArray::iterator file = filenames.begin(); file != filenames.end(); ++file){
//Obtain the last 2 digits of the filename, since they correspond to the save slot
slot[0] = file->c_str()[file->size()-2];
slot[1] = file->c_str()[file->size()-1];
slot[2] = 0;
slotNumber = atoi(slot);
if (slotNumber >= 0 && slotNumber < MAX_SAVES) {
name = calcSaveFileName(slotNumber);
if ((in = _saveFileMan->openForLoading(name)) != nullptr) {
_saveHeader.type = in->readUint32BE();
_saveHeader.size = in->readUint32LE();
_saveHeader.version = in->readUint32LE();
in->read(_saveHeader.name, sizeof(_saveHeader.name));
if (_saveHeader.type != MKTAG('S','A','G','A')) {
warning("SagaEngine::load wrong save %s format", name);
i++;
continue;
}
Common::CodePage cp = Common::kDos850;
if (getGameId() == GID_ITE) {
if (getLanguage() == Common::JA_JPN)
cp = Common::kWindows932;
}
Common::strlcpy(_saveFiles[_saveFilesCount].name, Common::U32String(_saveHeader.name).encode(cp).c_str(), sizeof(_saveFiles[_saveFilesCount].name));
_saveFiles[_saveFilesCount].slotNumber = slotNumber;
delete in;
_saveFilesCount++;
}
}
}
qsort(_saveFiles, _saveFilesCount, sizeof(_saveFiles[0]), compareSaveFileData);
}
void SagaEngine::save(const char *fileName, const char *saveName) {
Common::OutSaveFile *out;
char title[TITLESIZE];
if (!(out = _saveFileMan->openForSaving(fileName))) {
return;
}
_saveHeader.type = MKTAG('S','A','G','A');
_saveHeader.size = 0;
_saveHeader.version = CURRENT_SAGA_VER;
// Note that IHNM has a smaller save title size than ITE
// We allocate the ITE save title size here, to preserve
// savegame backwards compatibility
Common::strlcpy(_saveHeader.name, saveName, SAVE_TITLE_SIZE);
out->writeUint32BE(_saveHeader.type);
out->writeUint32LE(_saveHeader.size);
out->writeUint32LE(_saveHeader.version);
out->write(_saveHeader.name, sizeof(_saveHeader.name));
// Original game title
memset(title, 0, TITLESIZE);
Common::strlcpy(title, _gameTitle.c_str(), TITLESIZE);
out->write(title, TITLESIZE);
// Thumbnail
// First draw scene without save dialog
int oldMode = _interface->getMode();
_render->clearFlag(RF_RENDERPAUSE); // Don't show paused game message in saved thumbnail
_interface->setMode(kPanelMain);
_render->drawScene();
Graphics::saveThumbnail(*out);
_interface->setMode(oldMode);
// 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 = g_engine->getTotalPlayTime() / 1000;
out->writeUint32BE(saveDate);
out->writeUint16BE(saveTime);
out->writeUint32BE(playTime);
// Surrounding scene
out->writeSint32LE(_scene->getOutsetSceneNumber());
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
out->writeSint32LE(_scene->currentChapterNumber());
out->writeSint32LE(0); // obsolete, was used for the protagonist
out->writeSint32LE(_scene->getCurrentMusicTrack());
out->writeSint32LE(_scene->getCurrentMusicRepeat());
}
#endif
// Inset scene
out->writeSint32LE(_scene->currentSceneNumber());
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
out->writeUint32LE(_globalFlags);
for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
out->writeSint16LE(_ethicsPoints[i]);
}
#endif
_interface->saveState(out);
_actor->saveState(out);
out->writeSint16LE(_script->_commonBuffer.size());
out->write(_script->_commonBuffer.getBuffer(), _script->_commonBuffer.size());
// ISO map x, y coordinates for ITE
if (getGameId() == GID_ITE) {
out->writeSint16LE(_isoMap->getMapPosition().x);
out->writeSint16LE(_isoMap->getMapPosition().y);
}
out->finalize();
if (out->err())
warning("Can't write file '%s'. (Disk full?)", fileName);
delete out;
_interface->resetSaveReminder();
}
void SagaEngine::load(const char *fileName) {
Common::InSaveFile *in;
int commonBufferSize;
int sceneNumber, insetSceneNumber;
int mapx, mapy;
char title[TITLESIZE];
if (!(in = _saveFileMan->openForLoading(fileName))) {
return;
}
_saveHeader.type = in->readUint32BE();
_saveHeader.size = in->readUint32LE();
_saveHeader.version = in->readUint32LE();
in->read(_saveHeader.name, sizeof(_saveHeader.name));
// Some older saves were not written in an endian safe fashion.
// We try to detect this here by checking for extremly high version values.
// If found, we retry with the data swapped.
if (_saveHeader.version > 0xFFFFFF) {
warning("This savegame is not endian safe, retrying with the data swapped");
_saveHeader.version = SWAP_BYTES_32(_saveHeader.version);
}
debug(2, "Save version: 0x%X", _saveHeader.version);
if (_saveHeader.version < 4)
warning("This savegame is not endian-safe. There may be problems");
if (_saveHeader.type != MKTAG('S','A','G','A')) {
error("SagaEngine::load wrong save game format");
}
if (_saveHeader.version > 4) {
in->read(title, TITLESIZE);
debug(0, "Save is for: %s", title);
}
if (_saveHeader.version >= 6) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*in);
in->readUint32BE(); // save date
in->readUint16BE(); // save time
if (_saveHeader.version >= 8) {
uint32 playTime = in->readUint32BE();
g_engine->setTotalPlayTime(playTime * 1000);
}
}
// Clear pending events here, and don't process queued music events
_events->clearList(false);
// Surrounding scene
sceneNumber = in->readSint32LE();
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
int currentChapter = _scene->currentChapterNumber();
_scene->setChapterNumber(in->readSint32LE());
in->skip(4); // obsolete, was used for setting the protagonist
if (_scene->currentChapterNumber() != currentChapter)
_scene->changeScene(-2, 0, kTransitionFade, _scene->currentChapterNumber());
_scene->setCurrentMusicTrack(in->readSint32LE());
_scene->setCurrentMusicRepeat(in->readSint32LE());
_music->stop();
if (_scene->currentChapterNumber() == 8)
_interface->setMode(kPanelChapterSelection);
if (!isIHNMDemo()) {
_music->play(_music->_songTable[_scene->getCurrentMusicTrack()], _scene->getCurrentMusicRepeat() ? MUSIC_LOOP : MUSIC_NORMAL);
} else {
_music->play(3, MUSIC_LOOP);
}
}
#endif
// Inset scene
insetSceneNumber = in->readSint32LE();
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
_globalFlags = in->readUint32LE();
for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
_ethicsPoints[i] = in->readSint16LE();
}
#endif
_interface->loadState(in);
_actor->loadState(in);
commonBufferSize = in->readSint16LE();
_script->_commonBuffer.resize(commonBufferSize);
in->read(_script->_commonBuffer.getBuffer(), commonBufferSize);
if (getGameId() == GID_ITE) {
mapx = in->readSint16LE();
mapy = in->readSint16LE();
_isoMap->setMapPosition(mapx, mapy);
}
// Note: the mapx, mapy ISO map positions were incorrectly saved
// for IHNM too, which has no ISO map scenes, up to save version 6.
// Since they're at the end of the savegame, we just ignore them
delete in;
// Mute volume to prevent outScene music play
int volume = _music->getVolume();
_music->setVolume(0);
_scene->clearSceneQueue();
_scene->changeScene(sceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
_events->handleEvents(0); //dissolve backgrounds
if (insetSceneNumber != sceneNumber) {
_render->setFlag(RF_DISABLE_ACTORS);
_scene->draw();
_render->drawScene();
_render->clearFlag(RF_DISABLE_ACTORS);
_scene->changeScene(insetSceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
}
_music->setVolume(volume);
_interface->draw();
// Abort any scene entry protagonist animations and auto-cue speeches.
// Fixes bug #10009.
_actor->abortAllSpeeches();
_actor->_protagonist->_location = _actor->_protagonist->_finalTarget;
_actor->actorEndWalk(ID_PROTAG, true);
}
} // End of namespace Saga