1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 /*
24 * Based on the Reverse Engineering work of Christophe Fontanel,
25 * maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
26 */
27 #include "common/system.h"
28 #include "common/savefile.h"
29 #include "common/translation.h"
30 #include "graphics/thumbnail.h"
31 #include "gui/saveload.h"
32
33 #include "dm/dm.h"
34 #include "dm/dungeonman.h"
35 #include "dm/timeline.h"
36 #include "dm/group.h"
37 #include "dm/champion.h"
38 #include "dm/menus.h"
39 #include "dm/eventman.h"
40 #include "dm/projexpl.h"
41 #include "dm/dialog.h"
42
43 namespace DM {
44
loadgame(int16 slot)45 LoadgameResult DMEngine::loadgame(int16 slot) {
46 if (slot == -1 && _gameMode == kDMModeLoadSavedGame)
47 return kDMLoadgameFailure;
48
49 bool fadePalette = true;
50 Common::String fileName;
51 Common::SaveFileManager *saveFileManager = nullptr;
52 Common::InSaveFile *file = nullptr;
53
54 struct {
55 SaveTarget _saveTarget;
56 int32 _saveVersion;
57 OriginalSaveFormat _saveFormat;
58 OriginalSavePlatform _savePlatform;
59 uint16 _dungeonId;
60 } dmSaveHeader;
61
62 if (_gameMode != kDMModeLoadSavedGame) {
63 //L1366_B_FadePalette = !F0428_DIALOG_RequireGameDiskInDrive_NoDialogDrawn(C0_DO_NOT_FORCE_DIALOG_DM_CSB, true);
64 _restartGameAllowed = false;
65 _championMan->_partyChampionCount = 0;
66 _championMan->_leaderHandObject = _thingNone;
67 } else {
68 fileName = getSavefileName(slot);
69 saveFileManager = _system->getSavefileManager();
70 file = saveFileManager->openForLoading(fileName);
71
72 SaveGameHeader header;
73 if (!readSaveGameHeader(file, &header)) {
74 delete file;
75 return kDMLoadgameFailure;
76 }
77
78 warning("MISSING CODE: missing check for matching format and platform in save in f435_loadgame");
79
80 dmSaveHeader._saveTarget = (SaveTarget)file->readSint32BE();
81 dmSaveHeader._saveVersion = file->readSint32BE();
82 dmSaveHeader._saveFormat = (OriginalSaveFormat)file->readSint32BE();
83 dmSaveHeader._savePlatform = (OriginalSavePlatform)file->readSint32BE();
84
85 // Skip _gameId, which was useless
86 file->readSint32BE();
87 dmSaveHeader._dungeonId = file->readUint16BE();
88
89 _gameTime = file->readSint32BE();
90 // G0349_ul_LastRandomNumber = L1371_s_GlobalData.LastRandomNumber;
91 _championMan->_partyChampionCount = file->readUint16BE();
92 _dungeonMan->_partyMapX = file->readSint16BE();
93 _dungeonMan->_partyMapY = file->readSint16BE();
94 _dungeonMan->_partyDir = (Direction)file->readUint16BE();
95 _dungeonMan->_partyMapIndex = file->readByte();
96 _championMan->_leaderIndex = (ChampionIndex)file->readSint16BE();
97 _championMan->_magicCasterChampionIndex = (ChampionIndex)file->readSint16BE();
98 _timeline->_eventCount = file->readUint16BE();
99 _timeline->_firstUnusedEventIndex = file->readUint16BE();
100 _timeline->_eventMaxCount = file->readUint16BE();
101 _groupMan->_currActiveGroupCount = file->readUint16BE();
102 _projexpl->_lastCreatureAttackTime = file->readSint32BE();
103 _projexpl->_lastPartyMovementTime = file->readSint32BE();
104 _disabledMovementTicks = file->readSint16BE();
105 _projectileDisableMovementTicks = file->readSint16BE();
106 _lastProjectileDisabledMovementDirection = file->readSint16BE();
107 _championMan->_leaderHandObject = Thing(file->readUint16BE());
108 _groupMan->_maxActiveGroupCount = file->readUint16BE();
109 if (!_restartGameRequest) {
110 _timeline->initTimeline();
111 _groupMan->initActiveGroups();
112 }
113
114 _groupMan->loadActiveGroupPart(file);
115 _championMan->loadPartyPart2(file);
116 _timeline->loadEventsPart(file);
117 _timeline->loadTimelinePart(file);
118
119 // read sentinel
120 uint32 sentinel = file->readUint32BE();
121 assert(sentinel == 0x6f85e3d3);
122
123 _dungeonId = dmSaveHeader._dungeonId;
124 }
125
126 _dungeonMan->loadDungeonFile(file);
127 delete file;
128
129 if (_gameMode != kDMModeLoadSavedGame) {
130 _timeline->initTimeline();
131 _groupMan->initActiveGroups();
132
133 if (fadePalette) {
134 _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
135 delay(1);
136 _displayMan->fillScreen(kDMColorBlack);
137 _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
138 }
139 } else {
140 _restartGameAllowed = true;
141
142 switch (getGameLanguage()) { // localized
143 case Common::DE_DEU:
144 _dialog->dialogDraw(nullptr, "SPIEL WIRD GELADEN . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
145 break;
146 case Common::FR_FRA:
147 _dialog->dialogDraw(nullptr, "CHARGEMENT DU JEU . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
148 break;
149 default:
150 _dialog->dialogDraw(nullptr, "LOADING GAME . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
151 break;
152 }
153 }
154 _championMan->_partyDead = false;
155
156 return kDMLoadgameSuccess;
157 }
158
159
saveGame()160 void DMEngine::saveGame() {
161 _menuMan->drawDisabledMenu();
162 _eventMan->showMouse();
163
164 switch (getGameLanguage()) { // localized
165 default:
166 case Common::EN_ANY:
167 _dialog->dialogDraw(nullptr, nullptr, "SAVE AND PLAY", "SAVE AND QUIT", "CANCEL", "LOAD", false, false, false);
168 break;
169 case Common::DE_DEU:
170 _dialog->dialogDraw(nullptr, nullptr, "SICHERN/SPIEL", "SICHERN/ENDEN", "WIDERRUFEN", "LOAD", false, false, false);
171 break;
172 case Common::FR_FRA:
173 _dialog->dialogDraw(nullptr, nullptr, "GARDER/JOUER", "GARDER/SORTIR", "ANNULLER", "LOAD", false, false, false);
174 break;
175 }
176
177 enum SaveAndPlayChoice {
178 kSaveAndPlay = 1,
179 kSaveAndQuit = 2,
180 kCancel = 3,
181 kLoad = 4
182 };
183
184 SaveAndPlayChoice saveAndPlayChoice = (SaveAndPlayChoice)_dialog->getChoice(4, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone);
185
186 if (saveAndPlayChoice == kLoad) {
187 GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
188 int loadSlot = dialog->runModalWithCurrentTarget();
189 delete dialog;
190 if (loadSlot >= 0) {
191 _loadSaveSlotAtRuntime = loadSlot;
192 return;
193 }
194
195 saveAndPlayChoice = kCancel;
196 }
197
198 if (saveAndPlayChoice == kSaveAndQuit || saveAndPlayChoice == kSaveAndPlay) {
199 GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
200 int16 saveSlot = dialog->runModalWithCurrentTarget();
201 Common::String saveDescription = dialog->getResultString();
202 if (saveDescription.empty())
203 saveDescription = "Nice save ^^";
204 delete dialog;
205
206 if (saveSlot >= 0) {
207 switch (getGameLanguage()) { // localized
208 default:
209 case Common::EN_ANY:
210 _dialog->dialogDraw(nullptr, "SAVING GAME . . .", nullptr, nullptr, nullptr, nullptr, false, false, false);
211 break;
212 case Common::DE_DEU:
213 _dialog->dialogDraw(nullptr, "SPIEL WIRD GESICHERT . . .", nullptr, nullptr, nullptr, nullptr, false, false, false);
214 break;
215 case Common::FR_FRA:
216 _dialog->dialogDraw(nullptr, "UN MOMENT A SAUVEGARDER DU JEU...", nullptr, nullptr, nullptr, nullptr, false, false, false);
217 break;
218 }
219
220 uint16 champHandObjWeight = 0;
221 if (!_championMan->_leaderEmptyHanded) {
222 champHandObjWeight = _dungeonMan->getObjectWeight(_championMan->_leaderHandObject);
223 _championMan->_champions[_championMan->_leaderIndex]._load -= champHandObjWeight;
224 }
225
226 if (!writeCompleteSaveFile(saveSlot, saveDescription, saveAndPlayChoice)) {
227 _dialog->dialogDraw(nullptr, "Unable to open file for saving", "OK", nullptr, nullptr, nullptr, false, false, false);
228 _dialog->getChoice(1, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone);
229 }
230
231 if (!_championMan->_leaderEmptyHanded) {
232 _championMan->_champions[_championMan->_leaderIndex]._load += champHandObjWeight;
233 }
234 } else
235 saveAndPlayChoice = kCancel;
236 }
237
238
239 if (saveAndPlayChoice == kSaveAndQuit) {
240 _eventMan->hideMouse();
241 endGame(false);
242 }
243
244 _restartGameAllowed = true;
245 _menuMan->drawEnabledMenus();
246 _eventMan->hideMouse();
247 }
248
getSavefileName(uint16 slot)249 Common::String DMEngine::getSavefileName(uint16 slot) {
250 return Common::String::format("%s.%03u", _targetName.c_str(), slot);
251 }
252
253 #define SAVEGAME_ID MKTAG('D', 'M', '2', '1')
254 #define SAVEGAME_VERSION 1
255
writeSaveGameHeader(Common::OutSaveFile * out,const Common::String & saveName)256 void DMEngine::writeSaveGameHeader(Common::OutSaveFile *out, const Common::String& saveName) {
257 out->writeUint32BE(SAVEGAME_ID);
258
259 // Write version
260 out->writeByte(SAVEGAME_VERSION);
261
262 // Write savegame name
263 out->writeString(saveName);
264 out->writeByte(0);
265
266 // Save the game thumbnail
267 if (_saveThumbnail)
268 out->write(_saveThumbnail->getData(), _saveThumbnail->size());
269 else
270 Graphics::saveThumbnail(*out);
271
272 // Creation date/time
273 TimeDate curTime;
274 _system->getTimeAndDate(curTime);
275
276 uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
277 uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
278 uint32 playTime = getTotalPlayTime() / 1000;
279
280 out->writeUint32BE(saveDate);
281 out->writeUint16BE(saveTime);
282 out->writeUint32BE(playTime);
283 }
284
writeCompleteSaveFile(int16 saveSlot,Common::String & saveDescription,int16 saveAndPlayChoice)285 bool DMEngine::writeCompleteSaveFile(int16 saveSlot, Common::String& saveDescription, int16 saveAndPlayChoice) {
286 Common::String savefileName = getSavefileName(saveSlot);
287 Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
288 Common::OutSaveFile *file = saveFileManager->openForSaving(savefileName);
289
290 if (!file)
291 return false;
292
293 writeSaveGameHeader(file, saveDescription);
294
295 file->writeSint32BE(_gameVersion->_saveTargetToWrite);
296 file->writeSint32BE(1); // save version
297 file->writeSint32BE(_gameVersion->_origSaveFormatToWrite);
298 file->writeSint32BE(_gameVersion->_origPlatformToWrite);
299
300 // Was _gameID, useless.
301 file->writeSint32BE(0);
302 file->writeUint16BE(_dungeonId);
303
304 // write C0_SAVE_PART_GLOBAL_DATA part
305 file->writeSint32BE(_gameTime);
306 //L1348_s_GlobalData.LastRandomNumber = G0349_ul_LastRandomNumber;
307 file->writeUint16BE(_championMan->_partyChampionCount);
308 file->writeSint16BE(_dungeonMan->_partyMapX);
309 file->writeSint16BE(_dungeonMan->_partyMapY);
310 file->writeUint16BE(_dungeonMan->_partyDir);
311 file->writeByte(_dungeonMan->_partyMapIndex);
312 file->writeSint16BE(_championMan->_leaderIndex);
313 file->writeSint16BE(_championMan->_magicCasterChampionIndex);
314 file->writeUint16BE(_timeline->_eventCount);
315 file->writeUint16BE(_timeline->_firstUnusedEventIndex);
316 file->writeUint16BE(_timeline->_eventMaxCount);
317 file->writeUint16BE(_groupMan->_currActiveGroupCount);
318 file->writeSint32BE(_projexpl->_lastCreatureAttackTime);
319 file->writeSint32BE(_projexpl->_lastPartyMovementTime);
320 file->writeSint16BE(_disabledMovementTicks);
321 file->writeSint16BE(_projectileDisableMovementTicks);
322 file->writeSint16BE(_lastProjectileDisabledMovementDirection);
323 file->writeUint16BE(_championMan->_leaderHandObject.toUint16());
324 file->writeUint16BE(_groupMan->_maxActiveGroupCount);
325
326 // write C1_SAVE_PART_ACTIVE_GROUP part
327 _groupMan->saveActiveGroupPart(file);
328 // write C2_SAVE_PART_PARTY part
329 _championMan->savePartyPart2(file);
330 // write C3_SAVE_PART_EVENTS part
331 _timeline->saveEventsPart(file);
332 // write C4_SAVE_PART_TIMELINE part
333 _timeline->saveTimelinePart(file);
334
335 // write sentinel
336 file->writeUint32BE(0x6f85e3d3);
337
338 // save _g278_dungeonFileHeader
339 DungeonFileHeader &header = _dungeonMan->_dungeonFileHeader;
340 file->writeUint16BE(header._ornamentRandomSeed);
341 file->writeUint16BE(header._rawMapDataSize);
342 file->writeByte(header._mapCount);
343 file->writeByte(0); // to match the structure of dungeon.dat, will be discarded
344 file->writeUint16BE(header._textDataWordCount);
345 file->writeUint16BE(header._partyStartLocation);
346 file->writeUint16BE(header._squareFirstThingCount);
347 for (uint16 i = 0; i < 16; ++i)
348 file->writeUint16BE(header._thingCounts[i]);
349
350 // save _g277_dungeonMaps
351 for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._mapCount; ++i) {
352 Map &map = _dungeonMan->_dungeonMaps[i];
353
354 file->writeUint16BE(map._rawDunDataOffset);
355 file->writeUint32BE(0); // to match the structure of dungeon.dat, will be discarded
356 file->writeByte(map._offsetMapX);
357 file->writeByte(map._offsetMapY);
358
359 uint16 tmp;
360 tmp = ((map._height & 0x1F) << 11) | ((map._width & 0x1F) << 6) | (map._level & 0x3F);
361 file->writeUint16BE(tmp);
362
363 tmp = ((map._randFloorOrnCount & 0xF) << 12) | ((map._floorOrnCount & 0xF) << 8)
364 | ((map._randWallOrnCount & 0xF) << 4) | (map._wallOrnCount & 0xF);
365 file->writeUint16BE(tmp);
366
367 tmp = ((map._difficulty & 0xF) << 12) | ((map._creatureTypeCount & 0xF) << 4) | (map._doorOrnCount & 0xF);
368 file->writeUint16BE(tmp);
369
370 tmp = ((map._doorSet1 & 0xF) << 12) | ((map._doorSet0 & 0xF) << 8)
371 | ((map._wallSet & 0xF) << 4) | (map._floorSet & 0xF);
372 file->writeUint16BE(tmp);
373 }
374
375 // save _g280_dungeonColumnsCumulativeSquareThingCount
376 for (uint16 i = 0; i < _dungeonMan->_dungeonColumCount; ++i)
377 file->writeUint16BE(_dungeonMan->_dungeonColumnsCumulativeSquareThingCount[i]);
378
379 // save _g283_squareFirstThings
380 for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._squareFirstThingCount; ++i)
381 file->writeUint16BE(_dungeonMan->_squareFirstThings[i].toUint16());
382
383 // save _g260_dungeonTextData
384 for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._textDataWordCount; ++i)
385 file->writeUint16BE(_dungeonMan->_dungeonTextData[i]);
386
387 // save _g284_thingData
388 for (uint16 thingIndex = 0; thingIndex < 16; ++thingIndex)
389 for (uint16 i = 0; i < _dungeonMan->_thingDataWordCount[thingIndex] * _dungeonMan->_dungeonFileHeader._thingCounts[thingIndex]; ++i)
390 file->writeUint16BE(_dungeonMan->_thingData[thingIndex][i]);
391
392 // save _g276_dungeonRawMapData
393 for (uint32 i = 0; i < _dungeonMan->_dungeonFileHeader._rawMapDataSize; ++i)
394 file->writeByte(_dungeonMan->_dungeonRawMapData[i]);
395
396 file->flush();
397 file->finalize();
398 delete file;
399
400 return true;
401 }
402
readSaveGameHeader(Common::InSaveFile * in,SaveGameHeader * header,bool skipThumbnail)403 WARN_UNUSED_RESULT bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader *header, bool skipThumbnail) {
404 uint32 id = in->readUint32BE();
405
406 // Check if it's a valid ScummVM savegame
407 if (id != SAVEGAME_ID)
408 return false;
409
410 // Read in the version
411 header->_version = in->readByte();
412
413 // Check that the save version isn't newer than this binary
414 if (header->_version > SAVEGAME_VERSION)
415 return false;
416
417 // Read in the save name
418 Common::String saveName;
419 char ch;
420 while ((ch = (char)in->readByte()) != '\0')
421 saveName += ch;
422 header->_descr.setDescription(saveName);
423
424 // Get the thumbnail
425 Graphics::Surface *thumbnail;
426 if (!Graphics::loadThumbnail(*in, thumbnail, skipThumbnail)) {
427 return false;
428 }
429 header->_descr.setThumbnail(thumbnail);
430
431 uint32 saveDate = in->readUint32BE();
432 uint16 saveTime = in->readUint16BE();
433 uint32 playTime = in->readUint32BE();
434
435 int day = (saveDate >> 24) & 0xFF;
436 int month = (saveDate >> 16) & 0xFF;
437 int year = saveDate & 0xFFFF;
438 header->_descr.setSaveDate(year, month, day);
439
440 int hour = (saveTime >> 8) & 0xFF;
441 int minutes = saveTime & 0xFF;
442 header->_descr.setSaveTime(hour, minutes);
443
444 header->_descr.setPlayTime(playTime * 1000);
445 if (g_engine)
446 g_engine->setTotalPlayTime(playTime * 1000);
447
448 return true;
449 }
450
451 }
452
453