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