/* 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. * */ #include "common/textconsole.h" #include "common/savefile.h" #include "touche/graphics.h" #include "touche/touche.h" namespace Touche { enum { kGameStateDescriptionLen = 32, kCurrentGameStateVersion = 6 }; static void saveOrLoad(Common::WriteStream &stream, uint16 &i) { stream.writeUint16LE(i); } static void saveOrLoad(Common::ReadStream &stream, uint16 &i) { i = stream.readUint16LE(); } static void saveOrLoad(Common::WriteStream &stream, int16 &i) { stream.writeSint16LE(i); } static void saveOrLoad(Common::ReadStream &stream, int16 &i) { i = stream.readSint16LE(); } static void saveOrLoadPtr(Common::WriteStream &stream, int16 *&p, int16 *base) { int32 offset = (int32)(p - base); stream.writeSint32LE(offset); } static void saveOrLoadPtr(Common::ReadStream &stream, int16 *&p, int16 *base) { int32 offset = stream.readSint32LE(); p = base + offset; } template static void saveOrLoad(S &s, Common::Rect &r) { saveOrLoad(s, r.left); saveOrLoad(s, r.top); saveOrLoad(s, r.right); saveOrLoad(s, r.bottom); } template static void saveOrLoad(S &s, SequenceEntry &seq) { saveOrLoad(s, seq.sprNum); saveOrLoad(s, seq.seqNum); } template static void saveOrLoad(S &s, KeyChar &key) { saveOrLoad(s, key.num); saveOrLoad(s, key.flags); saveOrLoad(s, key.currentAnimCounter); saveOrLoad(s, key.strNum); saveOrLoad(s, key.walkDataNum); saveOrLoad(s, key.spriteNum); saveOrLoad(s, key.prevBoundingRect); saveOrLoad(s, key.boundingRect); saveOrLoad(s, key.xPos); saveOrLoad(s, key.yPos); saveOrLoad(s, key.zPos); saveOrLoad(s, key.xPosPrev); saveOrLoad(s, key.yPosPrev); saveOrLoad(s, key.zPosPrev); saveOrLoad(s, key.prevWalkDataNum); saveOrLoad(s, key.textColor); for (uint i = 0; i < 4; ++i) { saveOrLoad(s, key.inventoryItems[i]); } saveOrLoad(s, key.money); saveOrLoad(s, key.pointsDataNum); saveOrLoad(s, key.currentWalkBox); saveOrLoad(s, key.prevPointsDataNum); saveOrLoad(s, key.currentAnim); saveOrLoad(s, key.facingDirection); saveOrLoad(s, key.currentAnimSpeed); for (uint i = 0; i < 16; ++i) { saveOrLoad(s, key.framesList[i]); } saveOrLoad(s, key.framesListCount); saveOrLoad(s, key.currentFrame); saveOrLoad(s, key.anim1Start); saveOrLoad(s, key.anim1Count); saveOrLoad(s, key.anim2Start); saveOrLoad(s, key.anim2Count); saveOrLoad(s, key.anim3Start); saveOrLoad(s, key.anim3Count); saveOrLoad(s, key.followingKeyCharNum); saveOrLoad(s, key.followingKeyCharPos); saveOrLoad(s, key.sequenceDataIndex); saveOrLoad(s, key.sequenceDataOffset); saveOrLoad(s, key.walkPointsListIndex); for (uint i = 0; i < 40; ++i) { saveOrLoad(s, key.walkPointsList[i]); } saveOrLoad(s, key.scriptDataStartOffset); saveOrLoad(s, key.scriptDataOffset); saveOrLoadPtr(s, key.scriptStackPtr, &key.scriptStackTable[39]); saveOrLoad(s, key.delay); saveOrLoad(s, key.waitingKeyChar); for (uint i = 0; i < 3; ++i) { saveOrLoad(s, key.waitingKeyCharPosTable[i]); } for (uint i = 0; i < 40; ++i) { saveOrLoad(s, key.scriptStackTable[i]); } } template static void saveOrLoad(S &s, TalkEntry &entry) { saveOrLoad(s, entry.otherKeyChar); saveOrLoad(s, entry.talkingKeyChar); saveOrLoad(s, entry.num); } template static void saveOrLoad(S &s, ProgramHitBoxData &data) { saveOrLoad(s, data.item); saveOrLoad(s, data.talk); saveOrLoad(s, data.state); saveOrLoad(s, data.str); saveOrLoad(s, data.defaultStr); for (uint i = 0; i < 8; ++i) { saveOrLoad(s, data.actions[i]); } for (uint i = 0; i < 2; ++i) { saveOrLoad(s, data.hitBoxes[i]); } } template static void saveOrLoad(S &s, Area &area) { saveOrLoad(s, area.r); saveOrLoad(s, area.srcX); saveOrLoad(s, area.srcY); } template static void saveOrLoad(S &s, ProgramBackgroundData &data) { saveOrLoad(s, data.area); saveOrLoad(s, data.type); saveOrLoad(s, data.offset); saveOrLoad(s, data.scaleMul); saveOrLoad(s, data.scaleDiv); } template static void saveOrLoad(S &s, ProgramAreaData &data) { saveOrLoad(s, data.area); saveOrLoad(s, data.id); saveOrLoad(s, data.state); saveOrLoad(s, data.animCount); saveOrLoad(s, data.animNext); } template static void saveOrLoad(S &s, ProgramWalkData &data) { saveOrLoad(s, data.point1); saveOrLoad(s, data.point2); saveOrLoad(s, data.clippingRect); saveOrLoad(s, data.area1); saveOrLoad(s, data.area2); } template static void saveOrLoad(S &s, ProgramPointData &data) { saveOrLoad(s, data.x); saveOrLoad(s, data.y); saveOrLoad(s, data.z); saveOrLoad(s, data.order); } template static void saveOrLoadCommonArray(Common::WriteStream &stream, A &array) { uint count = array.size(); assert(count < 0xFFFF); stream.writeUint16LE(count); for (uint i = 0; i < count; ++i) { saveOrLoad(stream, array[i]); } } template static void saveOrLoadCommonArray(Common::ReadStream &stream, A &array) { uint count = stream.readUint16LE(); if (count == array.size()) { for (uint i = 0; i < count; ++i) { saveOrLoad(stream, array[i]); } } } template static void saveOrLoadStaticArray(S &s, A &array, uint count) { for (uint i = 0; i < count; ++i) { saveOrLoad(s, array[i]); } } static const uint32 saveLoadEndMarker = 0x55AA55AA; void ToucheEngine::saveGameStateData(Common::WriteStream *stream) { setKeyCharMoney(); stream->writeUint16LE(_currentEpisodeNum); stream->writeUint16LE(_currentMusicNum); stream->writeUint16LE(_currentRoomNum); stream->writeUint16LE(_flagsTable[614]); stream->writeUint16LE(_flagsTable[615]); stream->writeUint16LE(_disabledInputCounter); saveOrLoadCommonArray(*stream, _programHitBoxTable); saveOrLoadCommonArray(*stream, _programBackgroundTable); saveOrLoadCommonArray(*stream, _programAreaTable); saveOrLoadCommonArray(*stream, _programWalkTable); saveOrLoadCommonArray(*stream, _programPointsTable); stream->write(_updatedRoomAreasTable, 200); saveOrLoadStaticArray(*stream, _sequenceEntryTable, NUM_SEQUENCES); saveOrLoadStaticArray(*stream, _flagsTable, 1024); saveOrLoadStaticArray(*stream, _inventoryList1, 100); saveOrLoadStaticArray(*stream, _inventoryList2, 100); saveOrLoadStaticArray(*stream, _inventoryList3, 6); saveOrLoadStaticArray(*stream, _keyCharsTable, NUM_KEYCHARS); saveOrLoadStaticArray(*stream, _inventoryItemsInfoTable, NUM_INVENTORY_ITEMS); saveOrLoadStaticArray(*stream, _talkTable, NUM_TALK_ENTRIES); stream->writeUint16LE(_talkListEnd); stream->writeUint16LE(_talkListCurrent); stream->writeUint32LE(saveLoadEndMarker); } void ToucheEngine::loadGameStateData(Common::ReadStream *stream) { setKeyCharMoney(); clearDirtyRects(); _flagsTable[115] = 0; clearRoomArea(); _currentEpisodeNum = stream->readUint16LE(); _newMusicNum = stream->readUint16LE(); _currentRoomNum = stream->readUint16LE(); res_loadRoom(_currentRoomNum); int16 roomOffsX = _flagsTable[614] = stream->readUint16LE(); int16 roomOffsY = _flagsTable[615] = stream->readUint16LE(); _disabledInputCounter = stream->readUint16LE(); res_loadProgram(_currentEpisodeNum); setupEpisode(-1); saveOrLoadCommonArray(*stream, _programHitBoxTable); saveOrLoadCommonArray(*stream, _programBackgroundTable); saveOrLoadCommonArray(*stream, _programAreaTable); saveOrLoadCommonArray(*stream, _programWalkTable); saveOrLoadCommonArray(*stream, _programPointsTable); stream->read(_updatedRoomAreasTable, 200); for (uint i = 1; i < _updatedRoomAreasTable[0]; ++i) { updateRoomAreas(_updatedRoomAreasTable[i], -1); } saveOrLoadStaticArray(*stream, _sequenceEntryTable, NUM_SEQUENCES); saveOrLoadStaticArray(*stream, _flagsTable, 1024); saveOrLoadStaticArray(*stream, _inventoryList1, 100); saveOrLoadStaticArray(*stream, _inventoryList2, 100); saveOrLoadStaticArray(*stream, _inventoryList3, 6); saveOrLoadStaticArray(*stream, _keyCharsTable, NUM_KEYCHARS); saveOrLoadStaticArray(*stream, _inventoryItemsInfoTable, NUM_INVENTORY_ITEMS); saveOrLoadStaticArray(*stream, _talkTable, NUM_TALK_ENTRIES); _talkListEnd = stream->readUint16LE(); _talkListCurrent = stream->readUint16LE(); if (stream->readUint32LE() != saveLoadEndMarker) { warning("Corrupted gamestate data"); // if that ever happens, exit the game quitGame(); } _flagsTable[614] = roomOffsX; _flagsTable[615] = roomOffsY; for (uint i = 0; i < NUM_SEQUENCES; ++i) { if (_sequenceEntryTable[i].seqNum != -1) { res_loadSequence(_sequenceEntryTable[i].seqNum, i); } if (_sequenceEntryTable[i].sprNum != -1) { res_loadSprite(_sequenceEntryTable[i].sprNum, i); } } _currentKeyCharNum = _flagsTable[104]; _inventoryStateTable[0].displayOffset = 0; _inventoryStateTable[1].displayOffset = 0; _inventoryStateTable[2].displayOffset = 0; drawInventory(_currentKeyCharNum, 1); Graphics::copyRect(_offscreenBuffer, kScreenWidth, 0, 0, _backdropBuffer, _currentBitmapWidth, _flagsTable[614], _flagsTable[615], kScreenWidth, kRoomHeight); updateRoomRegions(); _fullRedrawCounter = 1; _roomNeedRedraw = false; if (_flagsTable[617] != 0) { res_loadSpeech(_flagsTable[617]); } debug(0, "Loaded state, current episode %d", _currentEpisodeNum); } Common::Error ToucheEngine::saveGameState(int num, const Common::String &description) { bool saveOk = false; Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num); Common::OutSaveFile *f = _saveFileMan->openForSaving(gameStateFileName); if (f) { f->writeUint16LE(kCurrentGameStateVersion); f->writeUint16LE(0); char headerDescription[kGameStateDescriptionLen]; memset(headerDescription, 0, kGameStateDescriptionLen); strncpy(headerDescription, description.c_str(), kGameStateDescriptionLen - 1); f->write(headerDescription, kGameStateDescriptionLen); saveGameStateData(f); f->finalize(); if (!f->err()) { saveOk = true; } else { warning("Can't write file '%s'", gameStateFileName.c_str()); } delete f; } return saveOk ? Common::kNoError : Common::kUnknownError; } Common::Error ToucheEngine::loadGameState(int num) { bool loadOk = false; Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num); Common::InSaveFile *f = _saveFileMan->openForLoading(gameStateFileName); if (f) { uint16 version = f->readUint16LE(); if (version < kCurrentGameStateVersion) { warning("Unsupported gamestate version %d (index %d)", version, num); } else { f->skip(2 + kGameStateDescriptionLen); loadGameStateData(f); if (f->err() || f->eos()) { warning("Can't read file '%s'", gameStateFileName.c_str()); } else { loadOk = true; } } delete f; } return loadOk ? Common::kNoError : Common::kUnknownError; } void readGameStateDescription(Common::ReadStream *f, char *description, int len) { uint16 version = f->readUint16LE(); if (version >= kCurrentGameStateVersion) { f->readUint16LE(); f->read(description, MIN(len, kGameStateDescriptionLen)); description[len] = 0; } else { description[0] = 0; } } Common::String generateGameStateFileName(const char *target, int slot, bool prefixOnly) { Common::String name(target); if (prefixOnly) { name += ".*"; } else { name += Common::String::format(".%d", slot); } return name; } int getGameStateFileSlot(const char *filename) { int i = -1; const char *slot = strrchr(filename, '.'); if (slot) { i = atoi(slot + 1); } return i; } } // namespace Touche