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 #include "common/config-manager.h"
24 #include "common/debug.h"
25 #include "common/debug-channels.h"
26 #include "common/endian.h"
27 #include "common/error.h"
28 #include "common/events.h"
29 #include "common/file.h"
30 #include "common/fs.h"
31 #include "common/memstream.h"
32 #include "common/savefile.h"
33 #include "common/str.h"
34 #include "common/system.h"
35 #include "common/translation.h"
36 #include "engines/util.h"
37 #include "graphics/cursorman.h"
38 #include "graphics/surface.h"
39 #include "graphics/screen.h"
40 #include "graphics/palette.h"
41 #include "graphics/thumbnail.h"
42 #include "gui/saveload.h"
43 
44 #include "supernova/resman.h"
45 #include "supernova/screen.h"
46 #include "supernova/sound.h"
47 #include "supernova/supernova.h"
48 #include "supernova/supernova1/state.h"
49 #include "supernova/supernova2/state.h"
50 #include "supernova/game-manager.h"
51 
52 namespace Supernova {
53 
operator |(ObjectType a,ObjectType b)54 ObjectType operator|(ObjectType a, ObjectType b) {
55 	return static_cast<ObjectType>(+a | +b);
56 }
57 
operator &(ObjectType a,ObjectType b)58 ObjectType operator&(ObjectType a, ObjectType b) {
59 	return static_cast<ObjectType>(+a & +b);
60 }
61 
operator ^(ObjectType a,ObjectType b)62 ObjectType operator^(ObjectType a, ObjectType b) {
63 	return static_cast<ObjectType>(+a ^ +b);
64 }
65 
operator |=(ObjectType & a,ObjectType b)66 ObjectType &operator|=(ObjectType &a, ObjectType b) {
67 	return a = a | b;
68 }
69 
operator &=(ObjectType & a,ObjectType b)70 ObjectType &operator&=(ObjectType &a, ObjectType b) {
71 	return a = a & b;
72 }
73 
operator ^=(ObjectType & a,ObjectType b)74 ObjectType &operator^=(ObjectType &a, ObjectType b) {
75 	return a = a ^ b;
76 }
77 
SupernovaEngine(OSystem * syst)78 SupernovaEngine::SupernovaEngine(OSystem *syst)
79 	: Engine(syst)
80 	, _gm(nullptr)
81 	, _sound(nullptr)
82 	, _resMan(nullptr)
83 	, _screen(nullptr)
84 	, _allowLoadGame(true)
85 	, _allowSaveGame(true)
86 	, _sleepAutoSave(nullptr)
87 	, _sleepAuoSaveVersion(-1)
88 	, _delay(33)
89 	, _textSpeed(kTextSpeed[2])
90 	, _improved(false) {
91 	if (ConfMan.hasKey("textspeed"))
92 		_textSpeed = ConfMan.getInt("textspeed");
93 
94 	if (ConfMan.get("gameid") == "msn1")
95 		_MSPart = 1;
96 	else if (ConfMan.get("gameid") == "msn2")
97 		_MSPart = 2;
98 	else
99 		_MSPart = 0;
100 
101 	_improved = ConfMan.getBool("improved");
102 }
103 
~SupernovaEngine()104 SupernovaEngine::~SupernovaEngine() {
105 	delete _sleepAutoSave;
106 	delete _gm;
107 	delete _sound;
108 	delete _resMan;
109 	delete _screen;
110 }
111 
run()112 Common::Error SupernovaEngine::run() {
113 	init();
114 
115 	while (!shouldQuit()) {
116 		uint32 start = _system->getMillis();
117 		_gm->updateEvents();
118 		_gm->executeRoom();
119 		_system->updateScreen();
120 		int end = _delay - (_system->getMillis() - start);
121 		if (end > 0)
122 			_system->delayMillis(end);
123 	}
124 
125 	_mixer->stopAll();
126 
127 	return Common::kNoError;
128 }
129 
init()130 void SupernovaEngine::init() {
131 	Graphics::ModeList modes;
132 	modes.push_back(Graphics::Mode(320, 200));
133 	modes.push_back(Graphics::Mode(640, 480));
134 	initGraphicsModes(modes);
135 	initGraphics(320, 200);
136 
137 	Common::Error status = loadGameStrings();
138 	if (status.getCode() != Common::kNoError)
139 		error("Failed reading game strings");
140 
141 	_resMan = new ResourceManager(this);
142 	_sound = new Sound(_mixer, _resMan);
143 	_screen = new Screen(this, _resMan);
144 	if (_MSPart == 1)
145 		_gm = new GameManager1(this, _sound);
146 	else if (_MSPart == 2)
147 		_gm = new GameManager2(this, _sound);
148 	setDebugger(new Console(this, _gm));
149 
150 	setTotalPlayTime(0);
151 
152 	int saveSlot = ConfMan.getInt("save_slot");
153 	if (saveSlot >= 0) {
154 		if (loadGameState(saveSlot).getCode() != Common::kNoError)
155 			error("Failed to load save game from slot %i", saveSlot);
156 	}
157 }
158 
hasFeature(EngineFeature f) const159 bool SupernovaEngine::hasFeature(EngineFeature f) const {
160 	switch (f) {
161 	case kSupportsReturnToLauncher:
162 		return true;
163 	case kSupportsLoadingDuringRuntime:
164 		return true;
165 	case kSupportsSavingDuringRuntime:
166 		return true;
167 	default:
168 		return false;
169 	}
170 }
171 
pauseEngineIntern(bool pause)172 void SupernovaEngine::pauseEngineIntern(bool pause) {
173 	_mixer->pauseAll(pause);
174 	_gm->pauseTimer(pause);
175 }
176 
loadGameStrings()177 Common::Error SupernovaEngine::loadGameStrings() {
178 	Common::String string_id("TEXT");
179 
180 	Common::SeekableReadStream *stream = getBlockFromDatFile(string_id);
181 
182 	if (stream == nullptr) {
183 		Common::Language l = Common::parseLanguage(ConfMan.get("language"));
184 		GUIErrorMessageFormat(_("Unable to locate the text for %s language in engine data file."), Common::getLanguageDescription(l));
185 		return Common::kReadingFailed;
186 	}
187 
188 	int size = stream->size();
189 	while (size > 0) {
190 		Common::String s;
191 		char ch;
192 		while ((ch = (char)stream->readByte()) != '\0')
193 			s += ch;
194 		_gameStrings.push_back(s);
195 		size -= s.size() + 1;
196 	}
197 
198 	return Common::kNoError;
199 }
200 
getGameString(int idx) const201 const Common::String &SupernovaEngine::getGameString(int idx) const {
202 	if (idx < 0 || idx >= (int)_gameStrings.size())
203 		return _nullString;
204 	return _gameStrings[idx];
205 }
206 
setGameString(int idx,const Common::String & string)207 void SupernovaEngine::setGameString(int idx, const Common::String &string) {
208 	if (idx < 0)
209 		return;
210 	while ((int)_gameStrings.size() <= idx)
211 		_gameStrings.push_back(Common::String());
212 	_gameStrings[idx] = string;
213 }
214 
playSound(AudioId sample)215 void SupernovaEngine::playSound(AudioId sample) {
216 	if (!shouldQuit())
217 		_sound->play(sample);
218 }
219 
playSound(MusicId index)220 void SupernovaEngine::playSound(MusicId index) {
221 	if (!shouldQuit())
222 		_sound->play(index);
223 }
224 
renderImage(int section)225 void SupernovaEngine::renderImage(int section) {
226 	_gm->_currentRoom->setSectionVisible(section, true);
227 
228 	_screen->renderImage(section);
229 }
230 
renderImage(ImageId id,bool removeImage)231 void SupernovaEngine::renderImage(ImageId id, bool removeImage) {
232 	_gm->_currentRoom->setSectionVisible(_screen->getImageInfo(id)->section, !removeImage);
233 	_screen->renderImage(id, removeImage);
234 }
235 
setCurrentImage(int filenumber)236 bool SupernovaEngine::setCurrentImage(int filenumber) {
237 	return _screen->setCurrentImage(filenumber);
238 }
239 
saveScreen(int x,int y,int width,int height)240 void SupernovaEngine::saveScreen(int x, int y, int width, int height) {
241 	_screen->saveScreen(x, y, width, height);
242 }
243 
saveScreen(const GuiElement & guiElement)244 void SupernovaEngine::saveScreen(const GuiElement &guiElement) {
245 	_screen->saveScreen(guiElement);
246 }
247 
restoreScreen()248 void SupernovaEngine::restoreScreen() {
249 	_screen->restoreScreen();
250 }
251 
renderRoom(Room & room)252 void SupernovaEngine::renderRoom(Room &room) {
253 	_screen->renderRoom(room);
254 }
255 
renderMessage(const char * text,MessagePosition position)256 void SupernovaEngine::renderMessage(const char *text, MessagePosition position) {
257 	_gm->_messageDuration = (Common::strnlen(text, 512) + 20) * _textSpeed / 10;
258 	_screen->renderMessage(text, position);
259 }
260 
renderMessage(const Common::String & text,MessagePosition position)261 void SupernovaEngine::renderMessage(const Common::String &text, MessagePosition position) {
262 	_gm->_messageDuration = (text.size() + 20) * _textSpeed / 10;
263 	_screen->renderMessage(text, position);
264 }
265 
renderMessage(int stringId,MessagePosition position,Common::String var1,Common::String var2)266 void SupernovaEngine::renderMessage(int stringId, MessagePosition position, Common::String var1, Common::String var2) {
267 	_gm->_messageDuration = (getGameString(stringId).size() + 20) * _textSpeed / 10;
268 	_screen->renderMessage(stringId, position, var1, var2);
269 }
270 
renderMessage(int stringId,int x,int y)271 void SupernovaEngine::renderMessage(int stringId, int x, int y) {
272 	_gm->_messageDuration = (getGameString(stringId).size() + 20) * _textSpeed / 10;
273 	_screen->renderMessage(getGameString(stringId).c_str(), kMessageNormal, x, y);
274 }
275 
removeMessage()276 void SupernovaEngine::removeMessage() {
277 	_screen->removeMessage();
278 }
279 
renderText(const uint16 character)280 void SupernovaEngine::renderText(const uint16 character) {
281 	_screen->renderText(character);
282 }
283 
renderText(const char * text)284 void SupernovaEngine::renderText(const char *text) {
285 	_screen->renderText(text);
286 }
287 
renderText(const Common::String & text)288 void SupernovaEngine::renderText(const Common::String &text) {
289 	_screen->renderText(text);
290 }
291 
renderText(int stringId)292 void SupernovaEngine::renderText(int stringId) {
293 	_screen->renderText(stringId);
294 }
295 
renderText(const GuiElement & guiElement)296 void SupernovaEngine::renderText(const GuiElement &guiElement) {
297 	_screen->renderText(guiElement);
298 }
299 
renderText(const uint16 character,int x,int y,byte color)300 void SupernovaEngine::renderText(const uint16 character, int x, int y, byte color) {
301 	_screen->renderText(character, x, y, color);
302 }
303 
renderText(const char * text,int x,int y,byte color)304 void SupernovaEngine::renderText(const char *text, int x, int y, byte color) {
305 	_screen->renderText(text, x, y, color);
306 }
307 
renderText(const Common::String & text,int x,int y,byte color)308 void SupernovaEngine::renderText(const Common::String &text, int x, int y, byte color) {
309 	_screen->renderText(text, x, y, color);
310 }
311 
renderText(int stringId,int x,int y,byte color)312 void SupernovaEngine::renderText(int stringId, int x, int y, byte color) {
313 	_screen->renderText(stringId, x, y, color);
314 }
315 
renderBox(int x,int y,int width,int height,byte color)316 void SupernovaEngine::renderBox(int x, int y, int width, int height, byte color) {
317 	_screen->renderBox(x, y, width, height, color);
318 }
319 
renderBox(const GuiElement & guiElement)320 void SupernovaEngine::renderBox(const GuiElement &guiElement) {
321 	_screen->renderBox(guiElement);
322 }
323 
paletteBrightness()324 void SupernovaEngine::paletteBrightness() {
325 	_screen->paletteBrightness();
326 }
327 
paletteFadeOut(int minBrightness)328 void SupernovaEngine::paletteFadeOut(int minBrightness) {
329 	if (!shouldQuit())
330 		_screen->paletteFadeOut(minBrightness);
331 }
332 
paletteFadeIn()333 void SupernovaEngine::paletteFadeIn() {
334 	if (!shouldQuit()) {
335 		_gm->roomBrightness();
336 		_screen->paletteFadeIn(_gm->_roomBrightness);
337 	}
338 }
339 
setColor63(byte value)340 void SupernovaEngine::setColor63(byte value) {
341 	_screen->setColor63(value);
342 }
343 
setTextSpeed()344 void SupernovaEngine::setTextSpeed() {
345 	const Common::String &textSpeedString = getGameString(kStringTextSpeed);
346 	int stringWidth = Screen::textWidth(textSpeedString);
347 	int textX = (kScreenWidth - stringWidth) / 2;
348 	int textY = 100;
349 	stringWidth += 4;
350 	int boxX = stringWidth > 110 ? (kScreenWidth - stringWidth) / 2 : 105;
351 	int boxY = 97;
352 	int boxWidth = stringWidth > 110 ? stringWidth : 110;
353 	int boxHeight = 27;
354 
355 	// Disable improved mode temporarilly so that Key 1-5 are received below
356 	// instead of being mapped to action selection.
357 	bool hasImprovedMode = _improved;
358 	_improved = false;
359 
360 	_gm->animationOff();
361 	_gm->saveTime();
362 	saveScreen(boxX, boxY, boxWidth, boxHeight);
363 
364 	renderBox(boxX, boxY, boxWidth, boxHeight, kColorBlue);
365 	renderText(textSpeedString, textX, textY, kColorWhite99); // Text speed
366 
367 	// Find the closest index in kTextSpeed for the current _textSpeed.
368 	// Important note: values in kTextSpeed decrease with the index.
369 	int speedIndex = 0;
370 	while (speedIndex < 4 && _textSpeed < (kTextSpeed[speedIndex] + kTextSpeed[speedIndex+1]) / 2)
371 		++speedIndex;
372 
373 	char nbString[2];
374 	nbString[1] = 0;
375 	for (int i = 0; i < 5; ++i) {
376 		byte color = i == speedIndex ? kColorWhite63 : kColorWhite35;
377 		renderBox(110 + 21 * i, 111, 16, 10, color);
378 
379 		nbString[0] = '1' + i;
380 		renderText(nbString, 115 + 21 * i, 112, kColorWhite99);
381 	}
382 	do {
383 		_gm->getInput();
384 		int key = _gm->_keyPressed ? _gm->_key.keycode : Common::KEYCODE_INVALID;
385 		if (!_gm->_keyPressed && _gm->_mouseClicked && _gm->_mouseY >= 111 && _gm->_mouseY < 121 && (_gm->_mouseX + 16) % 21 < 16)
386 			key = Common::KEYCODE_0 - 5 + (_gm->_mouseX + 16) / 21;
387 		if (key == Common::KEYCODE_ESCAPE)
388 			break;
389 		else if (key >= Common::KEYCODE_1 && key <= Common::KEYCODE_5) {
390 			speedIndex = key - Common::KEYCODE_1;
391 			_textSpeed = kTextSpeed[speedIndex];
392 			ConfMan.setInt("textspeed", _textSpeed);
393 			break;
394 		}
395 	} while (!shouldQuit());
396 	_gm->resetInputState();
397 
398 	restoreScreen();
399 	_gm->loadTime();
400 	_gm->animationOn();
401 
402 	_improved = hasImprovedMode;
403 }
404 
showHelpScreen1()405 void SupernovaEngine::showHelpScreen1() {
406 	if (_screen->isMessageShown())
407 		_screen->removeMessage();
408 	_gm->animationOff();
409 	_gm->saveTime();
410 
411 	paletteFadeOut();
412 	renderImage(kImageHelpScreen);
413 	renderBox(100, 100, 192, 78, kColorWhite35);
414 	renderText(kStringHelpOverview1, 105, 105, kColorWhite99);
415 	renderText(kStringHelpOverview2, 105, 115, kColorWhite99);
416 	renderText(kStringHelpOverview3, 105, 125, kColorWhite99);
417 	renderText(kStringHelpOverview4, 105, 135, kColorWhite99);
418 	renderText(kStringHelpOverview5, 105, 145, kColorWhite99);
419 	renderText(kStringHelpOverview6, 105, 155, kColorWhite99);
420 	renderText(kStringHelpOverview7, 105, 165, kColorWhite99);
421 	paletteFadeIn();
422 	_gm->getInput(true);
423 
424 	paletteFadeOut();
425 
426 	_gm->loadTime();
427 	_gm->animationOn();
428 }
429 
showHelpScreen2()430 void SupernovaEngine::showHelpScreen2() {
431 	if (_screen->isMessageShown())
432 		_screen->removeMessage();
433 	_gm->animationOff();
434 	_gm->saveTime();
435 
436 	paletteFadeOut();
437 	setCurrentImage(27);
438 	renderImage(0);
439 	paletteFadeIn();
440 	_gm->getInput(true);
441 
442 	paletteFadeOut();
443 
444 	_gm->loadTime();
445 	_gm->animationOn();
446 }
447 
getBlockFromDatFile(Common::String name)448 Common::SeekableReadStream *SupernovaEngine::getBlockFromDatFile(Common::String name) {
449 	Common::String cur_lang = ConfMan.get("language");
450 
451 	// Validate the data file header
452 	Common::File f;
453 	char id[5], lang[5];
454 	id[4] = lang[4] = '\0';
455 	if (!f.open(SUPERNOVA_DAT)) {
456 		GUIErrorMessageFormat(_("Unable to locate the '%s' engine data file."), SUPERNOVA_DAT);
457 		return nullptr;
458 	}
459 	f.read(id, 3);
460 	if (strncmp(id, "MSN", 3) != 0) {
461 		GUIErrorMessageFormat(_("The '%s' engine data file is corrupt."), SUPERNOVA_DAT);
462 		return nullptr;
463 	}
464 
465 	int version = f.readByte();
466 	if (version != SUPERNOVA_DAT_VERSION) {
467 		GUIErrorMessageFormat(
468 			_("Incorrect version of the '%s' engine data file found. Expected %d but got %d."),
469 			SUPERNOVA_DAT, SUPERNOVA_DAT_VERSION, version);
470 		return nullptr;
471 	}
472 
473 	uint32 gameBlockSize = 0;
474 	while (!f.eos()) {
475 		int part = f.readByte();
476 		gameBlockSize = f.readUint32LE();
477 		if (f.eos()){
478 			GUIErrorMessageFormat(_("Unable to find block for part %d"), _MSPart);
479 			return nullptr;
480 		}
481 		if (part == _MSPart) {
482 			break;
483 		} else
484 			f.skip(gameBlockSize);
485 	}
486 
487 	uint32 readSize = 0;
488 
489 	while (readSize < gameBlockSize) {
490 		f.read(id, 4);
491 		f.read(lang, 4);
492 		uint32 size = f.readUint32LE();
493 		if (f.eos())
494 			break;
495 		if (name == id && cur_lang == lang) {
496 			return f.readStream(size);
497 		} else {
498 			f.skip(size);
499 			// size + 4 bytes for id + 4 bytes for lang + 4 bytes for size
500 			readSize += size + 12;
501 		}
502 	}
503 
504 	return nullptr;
505 }
506 
showTextReader(const char * extension)507 Common::Error SupernovaEngine::showTextReader(const char *extension) {
508 	Common::SeekableReadStream *stream;
509 	Common::String blockName;
510 	blockName = Common::String::format("%s%d", extension, _MSPart);
511 	blockName.toUppercase();
512 	if ((stream = getBlockFromDatFile(blockName)) == nullptr) {
513 		Common::File file;
514 		Common::String filename;
515 		if (_MSPart == 1)
516 			filename = Common::String::format("msn.%s", extension);
517 		if (_MSPart == 2)
518 			filename = Common::String::format("ms2.%s", extension);
519 
520 		if (!file.open(filename)) {
521 			GUIErrorMessageFormat(_("Unable to find '%s' in game folder or the engine data file."), filename.c_str());
522 			return Common::kReadingFailed;
523 		}
524 		stream = file.readStream(file.size());
525 	}
526 	int linesInFile = 0;
527 	while (!stream->eos()) {
528 		stream->readLine();
529 		++linesInFile;
530 	}
531 	--linesInFile;
532 	stream->seek(0);
533 	stream->clearErr();
534 
535 	if (_screen->isMessageShown())
536 		_screen->removeMessage();
537 	_gm->animationOff();
538 	_gm->saveTime();
539 	paletteFadeOut();
540 	g_system->fillScreen(kColorWhite35);
541 	for (int y = 6; y < (200 - kFontHeight); y += (kFontHeight + 2)) {
542 		Common::String line = stream->readLine();
543 		if (stream->eos())
544 			break;
545 		_screen->renderText(line, 6, y, kColorWhite99);
546 	}
547 	paletteFadeIn();
548 
549 	const int linesPerPage = 19;
550 	int lineNumber = 0;
551 	bool exitReader = false;
552 	do {
553 		stream->seek(0);
554 		stream->clearErr();
555 		for (int i = 0; i < lineNumber; ++i)
556 			stream->readLine();
557 		g_system->fillScreen(kColorWhite35);
558 		for (int y = 6; y < (_screen->getScreenHeight() - kFontHeight); y += (kFontHeight + 2)) {
559 			Common::String line = stream->readLine();
560 			if (stream->eos())
561 				break;
562 			_screen->renderText(line, 6, y, kColorWhite99);
563 		}
564 		_gm->getInput(true);
565 		switch (_gm->_key.keycode) {
566 		case Common::KEYCODE_ESCAPE:
567 			exitReader = true;
568 			break;
569 		case Common::KEYCODE_UP:
570 			lineNumber = lineNumber > 0 ? lineNumber - 1 : 0;
571 			break;
572 		case Common::KEYCODE_DOWN:
573 			lineNumber = lineNumber < linesInFile - (linesPerPage + 1) ? lineNumber + 1
574 			                                                           : linesInFile - linesPerPage;
575 			break;
576 		case Common::KEYCODE_PAGEUP:
577 			lineNumber = lineNumber > linesPerPage ? lineNumber - linesPerPage : 0;
578 			break;
579 		case Common::KEYCODE_PAGEDOWN:
580 			lineNumber = lineNumber < linesInFile - (linesPerPage * 2) ? lineNumber + linesPerPage
581 			                                                           : linesInFile - linesPerPage;
582 			break;
583 		default:
584 			break;
585 		}
586 	} while (!exitReader && !shouldQuit());
587 
588 	paletteFadeOut();
589 	_gm->loadTime();
590 	_gm->animationOn();
591 
592 	return Common::kNoError;
593 }
594 
quitGameDialog()595 bool SupernovaEngine::quitGameDialog() {
596 	bool quit = false;
597 
598 	GuiElement guiQuitBox;
599 	guiQuitBox.setColor(kColorRed, kColorWhite99, kColorRed, kColorWhite99);
600 	guiQuitBox.setSize(112, 97, 112 + 96, 97 + 27);
601 	guiQuitBox.setText(getGameString(kStringLeaveGame).c_str());
602 	guiQuitBox.setTextPosition(guiQuitBox.left + 3, guiQuitBox.top + 3);
603 	GuiElement guiQuitYes;
604 	guiQuitYes.setColor(kColorWhite35, kColorWhite99, kColorWhite35, kColorWhite99);
605 	guiQuitYes.setSize(115, 111, 158, 121);
606 	guiQuitYes.setText(getGameString(kStringYes).c_str());
607 	guiQuitYes.setTextPosition(132, 112);
608 	GuiElement guiQuitNo;
609 	guiQuitNo.setColor(kColorWhite35, kColorWhite99, kColorWhite35, kColorWhite99);
610 	guiQuitNo.setSize(162, 111, 205, 121);
611 	guiQuitNo.setText(getGameString(kStringNo).c_str());
612 	guiQuitNo.setTextPosition(173, 112);
613 
614 	_gm->animationOff();
615 	saveScreen(guiQuitBox);
616 
617 	renderBox(guiQuitBox);
618 	renderText(guiQuitBox);
619 	renderBox(guiQuitYes);
620 	renderText(guiQuitYes);
621 	renderBox(guiQuitNo);
622 	renderText(guiQuitNo);
623 
624 	do {
625 		_gm->getInput();
626 		if (_gm->_keyPressed) {
627 			if (_gm->_key.keycode == Common::KEYCODE_j) {
628 				quit = true;
629 				break;
630 			} else if (_gm->_key.keycode == Common::KEYCODE_n) {
631 				quit = false;
632 				break;
633 			}
634 		}
635 		if (_gm->_mouseClicked) {
636 			if (guiQuitYes.contains(_gm->_mouseX, _gm->_mouseY)) {
637 				quit = true;
638 				break;
639 			} else if (guiQuitNo.contains(_gm->_mouseX, _gm->_mouseY)) {
640 				quit = false;
641 				break;
642 			}
643 		}
644 	} while (true);
645 
646 	_gm->resetInputState();
647 	restoreScreen();
648 	_gm->animationOn();
649 
650 	return quit;
651 }
652 
653 
canLoadGameStateCurrently()654 bool SupernovaEngine::canLoadGameStateCurrently() {
655 	return _allowLoadGame;
656 }
657 
loadGameState(int slot)658 Common::Error SupernovaEngine::loadGameState(int slot) {
659 	return (loadGame(slot) ? Common::kNoError : Common::kReadingFailed);
660 }
661 
canSaveGameStateCurrently()662 bool SupernovaEngine::canSaveGameStateCurrently() {
663 	// Do not allow saving when either _allowSaveGame, _animationEnabled or _guiEnabled is false
664 	return _allowSaveGame && _gm->canSaveGameStateCurrently();
665 }
666 
saveGameState(int slot,const Common::String & desc,bool isAutosave)667 Common::Error SupernovaEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
668 	return (saveGame(slot, desc) ? Common::kNoError : Common::kWritingFailed);
669 }
670 
serialize(Common::WriteStream * out)671 bool SupernovaEngine::serialize(Common::WriteStream *out) {
672 	if (!_gm->serialize(out))
673 		return false;
674 	out->writeByte(_screen->getGuiBrightness());
675 	out->writeByte(_screen->getViewportBrightness());
676 	return true;
677 }
678 
deserialize(Common::ReadStream * in,int version)679 bool SupernovaEngine::deserialize(Common::ReadStream *in, int version) {
680 	if (!_gm->deserialize(in, version))
681 		return false;
682 	if (version >= 5) {
683 		_screen->setGuiBrightness(in->readByte());
684 		_screen->setViewportBrightness(in->readByte());
685 	} else {
686 		_screen->setGuiBrightness(255);
687 		_screen->setViewportBrightness(255);
688 	}
689 	return true;
690 }
691 
getSaveStateName(int slot) const692 Common::String SupernovaEngine::getSaveStateName(int slot) const {
693 	if (_MSPart == 1)
694 		return Common::String::format("msn_save.%03d", slot);
695 	else if (_MSPart == 2)
696 		return Common::String::format("ms2_save.%03d", slot);
697 
698 	return "";
699 }
700 
loadGame(int slot)701 bool SupernovaEngine::loadGame(int slot) {
702 	if (slot < 0)
703 		return false;
704 
705 	// Stop any sound currently playing.
706 	_sound->stop();
707 
708 	// Make sure no message is displayed as this would otherwise delay the
709 	// switch to the new location until a mouse click.
710 	removeMessage();
711 
712 	if (slot == kSleepAutosaveSlot) {
713 		if (_sleepAutoSave != nullptr && deserialize(_sleepAutoSave, _sleepAuoSaveVersion)) {
714 			// We no longer need the sleep autosave
715 			delete _sleepAutoSave;
716 			_sleepAutoSave = nullptr;
717 			return true;
718 		}
719 		// Old version used to save it literally in the kSleepAutosaveSlot, so
720 		// continue to try to load it from there.
721 	}
722 
723 	Common::String filename = getSaveStateName(slot);
724 	Common::InSaveFile *savefile = _saveFileMan->openForLoading(filename);
725 	if (!savefile)
726 		return false;
727 
728 	uint saveHeader = savefile->readUint32LE();
729 	if ((_MSPart == 1 && saveHeader != SAVEGAME_HEADER) ||
730 		(_MSPart == 2 && saveHeader != SAVEGAME_HEADER2)) {
731 		warning("No header found in '%s'", filename.c_str());
732 		delete savefile;
733 		return false; //Common::kUnknownError
734 	}
735 
736 	byte saveVersion = savefile->readByte();
737 	// Save version 1 was used during development and is no longer supported
738 	if (saveVersion > SAVEGAME_VERSION || saveVersion < 10) {
739 		warning("Save game version %i not supported", saveVersion);
740 		delete savefile;
741 		return false; //Common::kUnknownError;
742 	}
743 
744 	int descriptionSize = savefile->readSint16LE();
745 	savefile->skip(descriptionSize);
746 	savefile->skip(6);
747 	setTotalPlayTime(savefile->readUint32LE() * 1000);
748 	Graphics::skipThumbnail(*savefile);
749 	if (!deserialize(savefile, saveVersion)) {
750 		delete savefile;
751 		return false;
752 	};
753 
754 	// With version 9 onward the sleep auto-save is save at the end of a normal save.
755 	delete _sleepAutoSave;
756 	_sleepAutoSave = nullptr;
757 	if (saveVersion >= 9) {
758 		_sleepAuoSaveVersion = saveVersion;
759 		byte hasAutoSave = savefile->readByte();
760 		if (hasAutoSave) {
761 			_sleepAutoSave = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
762 			uint nb;
763 			char buf[4096];
764 			while ((nb = savefile->read(buf, 4096)) > 0)
765 				_sleepAutoSave->write(buf, nb);
766 		}
767 	}
768 
769 	delete savefile;
770 
771 	return true;
772 }
773 
saveGame(int slot,const Common::String & description)774 bool SupernovaEngine::saveGame(int slot, const Common::String &description) {
775 	if (slot < 0)
776 		return false;
777 
778 	if (slot == kSleepAutosaveSlot) {
779 		delete _sleepAutoSave;
780 		_sleepAutoSave = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
781 		_sleepAuoSaveVersion = SAVEGAME_VERSION;
782 		serialize(_sleepAutoSave);
783 		return true;
784 	}
785 
786 	Common::String filename = getSaveStateName(slot);
787 	Common::OutSaveFile *savefile = _saveFileMan->openForSaving(filename);
788 	if (!savefile)
789 		return false;
790 
791 	if (_MSPart == 1)
792 		savefile->writeUint32LE(SAVEGAME_HEADER);
793 	else if (_MSPart == 2)
794 		savefile->writeUint32LE(SAVEGAME_HEADER2);
795 	savefile->writeByte(SAVEGAME_VERSION);
796 
797 	TimeDate currentDate;
798 	_system->getTimeAndDate(currentDate);
799 	uint32 saveDate = (currentDate.tm_mday & 0xFF) << 24 | ((currentDate.tm_mon + 1) & 0xFF) << 16 | ((currentDate.tm_year + 1900) & 0xFFFF);
800 	uint16 saveTime = (currentDate.tm_hour & 0xFF) << 8 | ((currentDate.tm_min) & 0xFF);
801 
802 	savefile->writeSint16LE(description.size() + 1);
803 	savefile->write(description.c_str(), description.size() + 1);
804 	savefile->writeUint32LE(saveDate);
805 	savefile->writeUint16LE(saveTime);
806 	savefile->writeUint32LE(getTotalPlayTime() / 1000);
807 	Graphics::saveThumbnail(*savefile);
808 	serialize(savefile);
809 
810 	if (_sleepAutoSave == nullptr)
811 		savefile->writeByte(0);
812 	else {
813 		savefile->writeByte(1);
814 		savefile->write(_sleepAutoSave->getData(), _sleepAutoSave->size());
815 	}
816 
817 	savefile->finalize();
818 	delete savefile;
819 
820 	return true;
821 }
822 
errorTempSave(bool saving)823 void SupernovaEngine::errorTempSave(bool saving) {
824 	GUIErrorMessage(saving
825 		? _("Failed to save temporary game state. Make sure your save game directory is set in ScummVM and that you can write to it.")
826 		: _("Failed to load temporary game state."));
827 	error("Unrecoverable error");
828 }
829 
stopSound()830 void SupernovaEngine::stopSound() {
831 	_sound->stop();
832 }
833 
834 
835 }
836