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