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 #if defined(WIN32) && !defined(__SYMBIAN32__)
24 #define WIN32_LEAN_AND_MEAN
25 #include <windows.h>
26 #include "backends/platform/sdl/win32/win32_wrapper.h"
27 #endif
28
29 #include "engines/engine.h"
30 #include "engines/dialogs.h"
31 #include "engines/util.h"
32 #include "engines/metaengine.h"
33
34 #include "common/config-manager.h"
35 #include "common/events.h"
36 #include "common/file.h"
37 #include "common/system.h"
38 #include "common/str.h"
39 #include "common/ustr.h"
40 #include "common/error.h"
41 #include "common/list.h"
42 #include "common/memstream.h"
43 #include "common/savefile.h"
44 #include "common/scummsys.h"
45 #include "common/taskbar.h"
46 #include "common/textconsole.h"
47 #include "common/translation.h"
48 #include "common/singleton.h"
49
50 #include "backends/audiocd/audiocd.h"
51 #include "backends/keymapper/action.h"
52 #include "backends/keymapper/keymapper.h"
53 #include "base/version.h"
54
55 #include "gui/gui-manager.h"
56 #include "gui/debugger.h"
57 #include "gui/dialog.h"
58 #include "gui/message.h"
59 #include "gui/saveload.h"
60
61 #include "audio/mixer.h"
62
63 #include "graphics/cursorman.h"
64 #include "graphics/fontman.h"
65 #include "graphics/pixelformat.h"
66 #include "image/bmp.h"
67
68 #include "common/text-to-speech.h"
69
70 // FIXME: HACK for error()
71 Engine *g_engine = 0;
72
73 // Output formatter for debug() and error() which invokes
74 // the errorString method of the active engine, if any.
defaultOutputFormatter(char * dst,const char * src,size_t dstSize)75 static void defaultOutputFormatter(char *dst, const char *src, size_t dstSize) {
76 if (g_engine) {
77 g_engine->errorString(src, dst, dstSize);
78 } else {
79 Common::strlcpy(dst, src, dstSize);
80 }
81 }
82
defaultErrorHandler(const char * msg)83 static bool defaultErrorHandler(const char *msg) {
84 bool handled = false;
85
86 // Unless this error -originated- within the debugger itself, we
87 // now invoke the debugger, if available / supported.
88 if (g_engine) {
89 GUI::Debugger *debugger = g_engine->getOrCreateDebugger();
90
91 #if defined(USE_TASKBAR)
92 g_system->getTaskbarManager()->notifyError();
93 #endif
94
95 if (debugger && !debugger->isActive()) {
96 debugger->attach(msg);
97 debugger->onFrame();
98 handled = true;
99 }
100
101
102 #if defined(USE_TASKBAR)
103 g_system->getTaskbarManager()->clearError();
104 #endif
105
106 }
107
108 return handled;
109 }
110
111 // Chained games manager
112
ChainedGamesManager()113 ChainedGamesManager::ChainedGamesManager() {
114 clear();
115 }
116
clear()117 void ChainedGamesManager::clear() {
118 _chainedGames.clear();
119 }
120
push(const Common::String target,const int slot)121 void ChainedGamesManager::push(const Common::String target, const int slot) {
122 Game game;
123 game.target = target;
124 game.slot = slot;
125 _chainedGames.push(game);
126 }
127
pop(Common::String & target,int & slot)128 bool ChainedGamesManager::pop(Common::String &target, int &slot) {
129 if (_chainedGames.empty()) {
130 return false;
131 }
132 Game game = _chainedGames.pop();
133 target = game.target;
134 slot = game.slot;
135 return true;
136 }
137
138 namespace Common {
139 DECLARE_SINGLETON(ChainedGamesManager);
140 }
141
Engine(OSystem * syst)142 Engine::Engine(OSystem *syst)
143 : _system(syst),
144 _mixer(_system->getMixer()),
145 _timer(_system->getTimerManager()),
146 _eventMan(_system->getEventManager()),
147 _saveFileMan(_system->getSavefileManager()),
148 _targetName(ConfMan.getActiveDomainName()),
149 _pauseLevel(0),
150 _pauseStartTime(0),
151 _saveSlotToLoad(-1),
152 _autoSaving(false),
153 _engineStartTime(_system->getMillis()),
154 _mainMenuDialog(NULL),
155 _debugger(NULL),
156 _autosaveInterval(ConfMan.getInt("autosave_period")),
157 _lastAutosaveTime(_system->getMillis()) {
158
159 g_engine = this;
160 Common::setErrorOutputFormatter(defaultOutputFormatter);
161 Common::setErrorHandler(defaultErrorHandler);
162
163 // FIXME: Get rid of the following again. It is only here
164 // temporarily. We really should never run with a non-working Mixer,
165 // so ought to handle this at a much earlier stage. If we *really*
166 // want to support systems without a working mixer, then we need
167 // more work. E.g. we could modify the Mixer to immediately drop any
168 // streams passed to it. This way, at least we don't crash because
169 // heaps of (sound) memory get allocated but never freed. Of course,
170 // there still would be problems with many games...
171 if (!_mixer->isReady())
172 warning("Sound initialization failed. This may cause severe problems in some games");
173
174 // Setup a dummy cursor and palette, so that all engines can use
175 // CursorMan.replace without having any headaches about memory leaks.
176 //
177 // If an engine only used CursorMan.replaceCursor and no cursor has
178 // been setup before, then replaceCursor just uses pushCursor. This
179 // means that that the engine's cursor is never again removed from
180 // CursorMan. Hence we setup a fake cursor here and remove it again
181 // in the destructor.
182 CursorMan.pushCursor(NULL, 0, 0, 0, 0, 0);
183 // Note: Using this dummy palette will actually disable cursor
184 // palettes till the user enables it again.
185 CursorMan.pushCursorPalette(NULL, 0, 0);
186 }
187
~Engine()188 Engine::~Engine() {
189 _mixer->stopAll();
190
191 delete _debugger;
192 delete _mainMenuDialog;
193 g_engine = NULL;
194
195 // Remove our cursors again to prevent memory leaks
196 CursorMan.popCursor();
197 CursorMan.popCursorPalette();
198 }
199
initializePath(const Common::FSNode & gamePath)200 void Engine::initializePath(const Common::FSNode &gamePath) {
201 SearchMan.addDirectory(gamePath.getPath(), gamePath, 0, 4);
202 }
203
initCommonGFX()204 void initCommonGFX() {
205 const Common::ConfigManager::Domain *gameDomain = ConfMan.getActiveDomain();
206
207 // Any global or command line settings already have been applied at the time
208 // we get here, so we only do something if the game domain overrides those
209 // values
210 if (gameDomain) {
211 if (gameDomain->contains("aspect_ratio"))
212 g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio"));
213
214 if (gameDomain->contains("fullscreen"))
215 g_system->setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen"));
216
217 if (gameDomain->contains("filtering"))
218 g_system->setFeatureState(OSystem::kFeatureFilteringMode, ConfMan.getBool("filtering"));
219
220 if (gameDomain->contains("stretch_mode"))
221 g_system->setStretchMode(ConfMan.get("stretch_mode").c_str());
222
223 if (gameDomain->contains("scaler") || gameDomain->contains("scale_factor"))
224 g_system->setScaler(ConfMan.get("scaler").c_str(), ConfMan.getInt("scale_factor"));
225
226 if (gameDomain->contains("shader"))
227 g_system->setShader(ConfMan.get("shader").c_str());
228 }
229 }
230
231 // Please leave the splash screen in working order for your releases, even if they're commercial.
232 // This is a proper and good way to show your appreciation for our hard work over these years.
233 bool splash = false;
234
235 #include "logo_data.h"
236
splashScreen()237 void splashScreen() {
238 Common::MemoryReadStream stream(logo_data, ARRAYSIZE(logo_data));
239
240 Image::BitmapDecoder bitmap;
241
242 if (!bitmap.loadStream(stream)) {
243 warning("Error loading logo file");
244 return;
245 }
246
247 g_system->showOverlay();
248 float scaleFactor = g_system->getHiDPIScreenFactor();
249 int16 overlayWidth = g_system->getOverlayWidth();
250 int16 overlayHeight = g_system->getOverlayHeight();
251 int16 scaledW = (int16)(overlayWidth / scaleFactor);
252 int16 scaledH = (int16)(overlayHeight / scaleFactor);
253
254 // Fill with orange
255 Graphics::Surface screen;
256 screen.create(scaledW, scaledH, g_system->getOverlayFormat());
257 screen.fillRect(Common::Rect(screen.w, screen.h), screen.format.ARGBToColor(0xff, 0xcc, 0x66, 0x00));
258
259 // Print version information
260 const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
261 int w = font->getStringWidth(gScummVMVersionDate);
262 int x = screen.w - w - 5;
263 int y = screen.h - font->getFontHeight() - 5;
264 font->drawString(&screen, gScummVMVersionDate, x, y, w, screen.format.ARGBToColor(0xff, 0, 0, 0));
265
266 // Scale if needed and copy to overlay
267 if (screen.w != overlayWidth) {
268 Graphics::Surface *scaledScreen = screen.scale(overlayWidth, overlayHeight, false);
269 g_system->copyRectToOverlay(scaledScreen->getPixels(), scaledScreen->pitch, 0, 0, scaledScreen->w, scaledScreen->h);
270 scaledScreen->free();
271 delete scaledScreen;
272 } else
273 g_system->copyRectToOverlay(screen.getPixels(), screen.pitch, 0, 0, screen.w, screen.h);
274 screen.free();
275
276 // Draw logo
277 Graphics::Surface *logo = bitmap.getSurface()->convertTo(g_system->getOverlayFormat(), bitmap.getPalette());
278 if (scaleFactor != 1.0f) {
279 Graphics::Surface *tmp = logo->scale(int16(logo->w * scaleFactor), int16(logo->h * scaleFactor), true);
280 logo->free();
281 delete logo;
282 logo = tmp;
283 }
284
285 int lx = MAX((overlayWidth - logo->w) / 2, 0);
286 int ly = MAX((overlayHeight - logo->h) / 2, 0);
287 int lw = MIN<uint16>(logo->w, overlayWidth - lx);
288 int lh = MIN<uint16>(logo->h, overlayHeight - ly);
289
290 g_system->copyRectToOverlay(logo->getPixels(), logo->pitch, lx, ly, lw, lh);
291 logo->free();
292 delete logo;
293
294 g_system->updateScreen();
295
296 // Delay 0.6 secs
297 uint time0 = g_system->getMillis();
298 Common::Event event;
299
300 // We must poll an event in order to have the window shown at least on Mac
301 g_system->getEventManager()->pollEvent(event);
302
303 while (time0 + 600 > g_system->getMillis()) {
304 g_system->delayMillis(10);
305 }
306 g_system->hideOverlay();
307
308 splash = true;
309 }
310
initGraphicsModes(const Graphics::ModeList & modes)311 void initGraphicsModes(const Graphics::ModeList &modes) {
312 g_system->initSizeHint(modes);
313 }
314
initGraphics(int width,int height,const Graphics::PixelFormat * format)315 void initGraphics(int width, int height, const Graphics::PixelFormat *format) {
316
317 g_system->beginGFXTransaction();
318
319 initCommonGFX();
320 #ifdef USE_RGB_COLOR
321 if (format)
322 g_system->initSize(width, height, format);
323 else {
324 Graphics::PixelFormat bestFormat = g_system->getSupportedFormats().front();
325 g_system->initSize(width, height, &bestFormat);
326 }
327 #else
328 g_system->initSize(width, height);
329 #endif
330
331 OSystem::TransactionError gfxError = g_system->endGFXTransaction();
332
333 if (!splash && !GUI::GuiManager::instance()._launched)
334 splashScreen();
335
336 if (gfxError == OSystem::kTransactionSuccess)
337 return;
338
339 // Error out on size switch failure
340 if (gfxError & OSystem::kTransactionSizeChangeFailed) {
341 Common::U32String message;
342 message = Common::U32String::format(_("Could not switch to resolution '%dx%d'."), width, height);
343
344 GUIErrorMessage(message);
345 error("Could not switch to resolution '%dx%d'.", width, height);
346 }
347
348 // Just show warnings then these occur:
349 #ifdef USE_RGB_COLOR
350 if (gfxError & OSystem::kTransactionFormatNotSupported) {
351 Common::U32String message = _("Could not initialize color format.");
352
353 GUI::MessageDialog dialog(message);
354 dialog.runModal();
355 }
356 #endif
357
358 if (gfxError & OSystem::kTransactionModeSwitchFailed) {
359 Common::U32String message;
360 message = Common::U32String::format(_("Could not switch to video mode '%s'."), ConfMan.get("gfx_mode").c_str());
361
362 GUI::MessageDialog dialog(message);
363 dialog.runModal();
364 }
365
366 if (gfxError & OSystem::kTransactionStretchModeSwitchFailed) {
367 Common::U32String message;
368 message = Common::U32String::format(_("Could not switch to stretch mode '%s'."), ConfMan.get("stretch_mode").c_str());
369
370 GUI::MessageDialog dialog(message);
371 dialog.runModal();
372 }
373
374 if (gfxError & OSystem::kTransactionAspectRatioFailed) {
375 GUI::MessageDialog dialog(_("Could not apply aspect ratio setting."));
376 dialog.runModal();
377 }
378
379 if (gfxError & OSystem::kTransactionFullscreenFailed) {
380 GUI::MessageDialog dialog(_("Could not apply fullscreen setting."));
381 dialog.runModal();
382 }
383
384 if (gfxError & OSystem::kTransactionFilteringFailed) {
385 GUI::MessageDialog dialog(_("Could not apply filtering setting."));
386 dialog.runModal();
387 }
388 }
389
390 /**
391 * Determines the first matching format between two lists.
392 *
393 * @param backend The higher priority list, meant to be a list of formats supported by the backend
394 * @param frontend The lower priority list, meant to be a list of formats supported by the engine
395 * @return The first item on the backend list that also occurs on the frontend list
396 * or PixelFormat::createFormatCLUT8() if no matching formats were found.
397 */
findCompatibleFormat(const Common::List<Graphics::PixelFormat> & backend,const Common::List<Graphics::PixelFormat> & frontend)398 inline Graphics::PixelFormat findCompatibleFormat(const Common::List<Graphics::PixelFormat> &backend, const Common::List<Graphics::PixelFormat> &frontend) {
399 #ifdef USE_RGB_COLOR
400 for (Common::List<Graphics::PixelFormat>::const_iterator i = backend.begin(); i != backend.end(); ++i) {
401 for (Common::List<Graphics::PixelFormat>::const_iterator j = frontend.begin(); j != frontend.end(); ++j) {
402 if (*i == *j)
403 return *i;
404 }
405 }
406 #endif
407 return Graphics::PixelFormat::createFormatCLUT8();
408 }
409
410
initGraphics(int width,int height,const Common::List<Graphics::PixelFormat> & formatList)411 void initGraphics(int width, int height, const Common::List<Graphics::PixelFormat> &formatList) {
412 Graphics::PixelFormat format = findCompatibleFormat(g_system->getSupportedFormats(), formatList);
413 initGraphics(width, height, &format);
414 }
415
initGraphics(int width,int height)416 void initGraphics(int width, int height) {
417 Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
418 initGraphics(width, height, &format);
419 }
420
initGraphics3d(int width,int height)421 void initGraphics3d(int width, int height) {
422 g_system->beginGFXTransaction();
423 g_system->setGraphicsMode(0, OSystem::kGfxModeRender3d);
424 g_system->initSize(width, height);
425 g_system->setFeatureState(OSystem::kFeatureFullscreenMode, ConfMan.getBool("fullscreen")); // TODO: Replace this with initCommonGFX()
426 g_system->setFeatureState(OSystem::kFeatureAspectRatioCorrection, ConfMan.getBool("aspect_ratio")); // TODO: Replace this with initCommonGFX()
427 g_system->endGFXTransaction();
428 }
429
GUIErrorMessageWithURL(const Common::U32String & msg,const char * url)430 void GUIErrorMessageWithURL(const Common::U32String &msg, const char *url) {
431 GUIErrorMessage(msg, url);
432 }
433
GUIErrorMessageWithURL(const Common::String & msg,const char * url)434 void GUIErrorMessageWithURL(const Common::String &msg, const char *url) {
435 GUIErrorMessage(Common::U32String(msg), url);
436 }
437
GUIErrorMessage(const Common::String & msg,const char * url)438 void GUIErrorMessage(const Common::String &msg, const char *url) {
439 GUIErrorMessage(Common::U32String(msg), url);
440 }
441
GUIErrorMessage(const Common::U32String & msg,const char * url)442 void GUIErrorMessage(const Common::U32String &msg, const char *url) {
443 g_system->setWindowCaption(_("Error"));
444 g_system->beginGFXTransaction();
445 initCommonGFX();
446 g_system->initSize(320, 200);
447 if (g_system->endGFXTransaction() == OSystem::kTransactionSuccess) {
448 if (url) {
449 GUI::MessageDialogWithURL dialog(msg, url);
450 dialog.runModal();
451 } else {
452 GUI::MessageDialog dialog(msg);
453 dialog.runModal();
454 }
455 } else {
456 error("%s", msg.encode().c_str());
457 }
458 }
459
GUIErrorMessageFormat(const char * fmt,...)460 void GUIErrorMessageFormat(const char *fmt, ...) {
461 Common::String msg;
462
463 va_list va;
464 va_start(va, fmt);
465 msg = Common::String::vformat(fmt, va);
466 va_end(va);
467
468 GUIErrorMessage(msg);
469 }
470
GUIErrorMessageFormat(Common::U32String fmt,...)471 void GUIErrorMessageFormat(Common::U32String fmt, ...) {
472 Common::U32String msg("");
473
474 va_list va;
475 va_start(va, fmt);
476 Common::U32String::vformat(fmt.begin(), fmt.end(), msg, va);
477 va_end(va);
478
479 GUIErrorMessage(msg);
480 }
481
482 /**
483 * Checks if supported (extracted) audio files are found.
484 *
485 * @return true if audio files of the expected naming scheme are found, as long as ScummVM
486 * is also built with support to the respective audio format (eg. ogg, flac, mad/mp3)
487 */
existExtractedCDAudioFiles()488 bool Engine::existExtractedCDAudioFiles() {
489 return g_system->getAudioCDManager()->existExtractedCDAudioFiles();
490 }
491
492 /**
493 * Displays a warning on Windows version of ScummVM, if game data
494 * are read from the same CD drive which should also play game CD audio.
495 * @return true, if this case is applicable and the warning is displayed
496 */
isDataAndCDAudioReadFromSameCD()497 bool Engine::isDataAndCDAudioReadFromSameCD() {
498 #if defined(WIN32) && !defined(__SYMBIAN32__)
499 // It is a known bug under Windows that games that play CD audio cause
500 // ScummVM to crash if the data files are read from the same CD. Check
501 // if this appears to be the case and issue a warning.
502
503 // If we can find a compressed audio track, then it should be ok even
504 // if it's running from CD.
505 char driveLetter;
506 const Common::FSNode gameDataDir(ConfMan.get("path"));
507 if (!gameDataDir.getPath().empty()) {
508 driveLetter = gameDataDir.getPath()[0];
509 } else {
510 // That's it! I give up!
511 Common::FSNode currentDir(".");
512 if (!currentDir.getPath().empty()) {
513 driveLetter = currentDir.getPath()[0];
514 } else {
515 return false;
516 }
517 }
518
519 if (Win32::isDriveCD(driveLetter)) {
520 GUI::MessageDialog dialog(
521 _("You appear to be playing this game directly\n"
522 "from the CD. This is known to cause problems,\n"
523 "and it is therefore recommended that you copy\n"
524 "the data files to your hard disk instead.\n"
525 "See the documentation (CD audio) for details."), _("OK"));
526 dialog.runModal();
527 return true;
528 }
529 #endif // defined(WIN32) && !defined(__SYMBIAN32__)
530 return false;
531 }
532
533 /**
534 * Displays a warning, for the case when the game has CD audio but
535 * no extracted (ripped) audio files were found.
536 *
537 * This method only shows the warning. It does not check for the
538 * existence of the ripped audio files.
539 */
warnMissingExtractedCDAudio()540 void Engine::warnMissingExtractedCDAudio() {
541 // Display a modal informative dialogue for the case when:
542 // - The game has audio tracks,
543 // - and the tracks have not been ripped.
544 GUI::MessageDialog dialog(
545 _("This game has audio tracks on its CD. These\n"
546 "tracks need to be ripped from the CD using\n"
547 "an appropriate CD audio extracting tool in\n"
548 "order to listen to the game's music.\n"
549 "See the documentation (CD audio) for details."), _("OK"));
550 dialog.runModal();
551 }
552
handleAutoSave()553 void Engine::handleAutoSave() {
554 const int diff = _system->getMillis() - _lastAutosaveTime;
555
556 if (_autosaveInterval != 0 && diff > (_autosaveInterval * 1000)) {
557 // Save the autosave
558 saveAutosaveIfEnabled();
559 }
560 }
561
warnBeforeOverwritingAutosave()562 bool Engine::warnBeforeOverwritingAutosave() {
563 SaveStateDescriptor desc = getMetaEngine()->querySaveMetaInfos(
564 _targetName.c_str(), getAutosaveSlot());
565 if (!desc.isValid() || desc.hasAutosaveName())
566 return true;
567 Common::U32StringArray altButtons;
568 altButtons.push_back(_("Overwrite"));
569 altButtons.push_back(_("Cancel autosave"));
570 const Common::U32String message = Common::U32String::format(
571 _("WARNING: The autosave slot has a saved game named %S. "
572 "You can either move the existing save to a new slot, "
573 "Overwrite the existing save, "
574 "or cancel autosave (will not prompt again until restart)"), desc.getDescription().c_str());
575 GUI::MessageDialog warn(message, _("Move"), altButtons);
576 switch (runDialog(warn)) {
577 case GUI::kMessageOK:
578 if (!getMetaEngine()->copySaveFileToFreeSlot(_targetName.c_str(), getAutosaveSlot())) {
579 GUI::MessageDialog error(_("ERROR: Could not copy the savegame to a new slot"));
580 error.runModal();
581 return false;
582 }
583 return true;
584 case GUI::kMessageAlt: // Overwrite
585 return true;
586 case GUI::kMessageAlt + 1: // Cancel autosave
587 _autosaveInterval = 0;
588 return false;
589 default: // Hitting Escape returns -1. On this case, don't save but do prompt again later.
590 return false;
591 }
592 }
593
saveAutosaveIfEnabled()594 void Engine::saveAutosaveIfEnabled() {
595 // Prevents recursive calls if saving the game causes the engine to poll events
596 // (as is the case with the AGS engine for example, or when showing a prompt).
597 if (_autoSaving || _autosaveInterval == 0)
598 return;
599 const int autoSaveSlot = getAutosaveSlot();
600 if (autoSaveSlot < 0)
601 return;
602 _autoSaving = true;
603
604 bool saveFlag = canSaveAutosaveCurrently();
605 const Common::String autoSaveName = Common::convertFromU32String(_("Autosave"));
606
607 // First check for an existing savegame in the slot, and if present, if it's an autosave
608 if (saveFlag)
609 saveFlag = warnBeforeOverwritingAutosave();
610
611 if (saveFlag && saveGameState(autoSaveSlot, autoSaveName, true).getCode() != Common::kNoError) {
612 // Couldn't autosave at the designated time
613 g_system->displayMessageOnOSD(_("Error occurred making autosave"));
614 saveFlag = false;
615 }
616
617 if (saveFlag) {
618 _lastAutosaveTime = _system->getMillis();
619 } else {
620 // Set the next autosave interval to be in 5 minutes, rather than whatever
621 // full autosave interval the user has selected
622 _lastAutosaveTime += (5 * 60 * 1000) - _autosaveInterval;
623 }
624 _autoSaving = false;
625 }
626
errorString(const char * buf1,char * buf2,int size)627 void Engine::errorString(const char *buf1, char *buf2, int size) {
628 Common::strlcpy(buf2, buf1, size);
629 }
630
pauseEngine()631 PauseToken Engine::pauseEngine() {
632 assert(_pauseLevel >= 0);
633
634 _pauseLevel++;
635
636 if (_pauseLevel == 1) {
637 _pauseStartTime = _system->getMillis();
638 pauseEngineIntern(true);
639 }
640
641 return PauseToken(this);
642 }
643
resumeEngine()644 void Engine::resumeEngine() {
645 assert(_pauseLevel > 0);
646
647 _pauseLevel--;
648
649 if (_pauseLevel == 0) {
650 pauseEngineIntern(false);
651 _engineStartTime += _system->getMillis() - _pauseStartTime;
652 _pauseStartTime = 0;
653 }
654 }
655
pauseEngineIntern(bool pause)656 void Engine::pauseEngineIntern(bool pause) {
657 // By default, just (un)pause all digital sounds
658 _mixer->pauseAll(pause);
659 }
660
openMainMenuDialog()661 void Engine::openMainMenuDialog() {
662 if (!_mainMenuDialog)
663 _mainMenuDialog = new MainMenuDialog(this);
664 Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
665 if (ttsMan != nullptr) {
666 ttsMan->pushState();
667 g_gui.initTextToSpeech();
668 }
669
670 setGameToLoadSlot(-1);
671
672 bool hasVKeyb = g_system->getFeatureState(OSystem::kFeatureVirtualKeyboard);
673 if (hasVKeyb)
674 g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
675
676 runDialog(*_mainMenuDialog);
677
678 if (hasVKeyb)
679 g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
680
681 // Load savegame after main menu execution
682 // (not from inside the menu loop to avoid
683 // mouse cursor glitches and similar bugs,
684 // e.g. #4420).
685 if (_saveSlotToLoad >= 0) {
686 Common::Error status = loadGameState(_saveSlotToLoad);
687 if (status.getCode() != Common::kNoError) {
688 Common::U32String failMessage = Common::U32String::format(_("Failed to load saved game (%s)! "
689 "Please consult the README for basic information, and for "
690 "instructions on how to obtain further assistance."), status.getDesc().c_str());
691 GUI::MessageDialog dialog(failMessage);
692 dialog.runModal();
693 }
694 }
695
696 if (ttsMan != nullptr)
697 ttsMan->popState();
698
699 g_system->applyBackendSettings();
700 applyGameSettings();
701 syncSoundSettings();
702 }
703
warnUserAboutUnsupportedGame(Common::String msg)704 bool Engine::warnUserAboutUnsupportedGame(Common::String msg) {
705 if (ConfMan.getBool("enable_unsupported_game_warning")) {
706 GUI::MessageDialog alert(!msg.empty() ? _("WARNING: ") + Common::U32String(msg) + _(" Shall we still run the game?") :
707 _("WARNING: The game you are about to start is"
708 " not yet fully supported by ScummVM. As such, it is likely to be"
709 " unstable, and any saved game you make might not work in future"
710 " versions of ScummVM."), _("Start anyway"), _("Cancel"));
711 return alert.runModal() == GUI::kMessageOK;
712 }
713 return true;
714 }
715
errorUnsupportedGame(Common::String extraMsg)716 void Engine::errorUnsupportedGame(Common::String extraMsg) {
717 Common::String message = extraMsg.empty() ? _("This game is not supported.") : _("This game is not supported for the following reason:\n\n");
718 message += _(extraMsg);
719 message += "\n\n";
720 GUI::MessageDialog(message).runModal();
721 }
722
getTotalPlayTime() const723 uint32 Engine::getTotalPlayTime() const {
724 if (!_pauseLevel)
725 return _system->getMillis() - _engineStartTime;
726 else
727 return _pauseStartTime - _engineStartTime;
728 }
729
setTotalPlayTime(uint32 time)730 void Engine::setTotalPlayTime(uint32 time) {
731 const uint32 currentTime = _system->getMillis();
732
733 // We need to reset the pause start time here in case the engine is already
734 // paused to avoid any incorrect play time counting.
735 if (_pauseLevel > 0)
736 _pauseStartTime = currentTime;
737
738 _engineStartTime = currentTime - time;
739 }
740
runDialog(GUI::Dialog & dialog)741 int Engine::runDialog(GUI::Dialog &dialog) {
742 PauseToken pt = pauseEngine();
743 int result = dialog.runModal();
744
745 return result;
746 }
747
setGameToLoadSlot(int slot)748 void Engine::setGameToLoadSlot(int slot) {
749 _saveSlotToLoad = slot;
750 }
751
syncSoundSettings()752 void Engine::syncSoundSettings() {
753 // Sync the engine with the config manager
754 int soundVolumeMusic = ConfMan.getInt("music_volume");
755 int soundVolumeSFX = ConfMan.getInt("sfx_volume");
756 int soundVolumeSpeech = ConfMan.getInt("speech_volume");
757
758 bool mute = false;
759 if (ConfMan.hasKey("mute"))
760 mute = ConfMan.getBool("mute");
761
762 // We need to handle the speech mute separately here. This is because the
763 // engine code should be able to rely on all speech sounds muted when the
764 // user specified subtitles only mode, which results in "speech_mute" to
765 // be set to "true". The global mute setting has precedence over the
766 // speech mute setting though.
767 bool speechMute = mute;
768 if (!speechMute)
769 speechMute = ConfMan.getBool("speech_mute");
770
771 _mixer->muteSoundType(Audio::Mixer::kPlainSoundType, mute);
772 _mixer->muteSoundType(Audio::Mixer::kMusicSoundType, mute);
773 _mixer->muteSoundType(Audio::Mixer::kSFXSoundType, mute);
774 _mixer->muteSoundType(Audio::Mixer::kSpeechSoundType, speechMute);
775
776 _mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, Audio::Mixer::kMaxMixerVolume);
777 _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
778 _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolumeSFX);
779 _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, soundVolumeSpeech);
780 }
781
flipMute()782 void Engine::flipMute() {
783 // Mute will be set to true by default here. This has two reasons:
784 // - if the game already has an "mute" config entry, it will be overwritten anyway.
785 // - if it does not have a "mute" config entry, the sound is unmuted currently and should be muted now.
786 bool mute = true;
787
788 if (ConfMan.hasKey("mute")) {
789 mute = !ConfMan.getBool("mute");
790 }
791
792 ConfMan.setBool("mute", mute);
793
794 syncSoundSettings();
795 }
796
loadGameState(int slot)797 Common::Error Engine::loadGameState(int slot) {
798 // In case autosaves are on, do a save first before loading the new save
799 saveAutosaveIfEnabled();
800
801 Common::InSaveFile *saveFile = _saveFileMan->openForLoading(getSaveStateName(slot));
802
803 if (!saveFile)
804 return Common::kReadingFailed;
805
806 Common::Error result = loadGameStream(saveFile);
807 if (result.getCode() == Common::kNoError) {
808 ExtendedSavegameHeader header;
809 if (MetaEngine::readSavegameHeader(saveFile, &header))
810 setTotalPlayTime(header.playtime);
811 }
812
813 delete saveFile;
814 return result;
815 }
816
loadGameStream(Common::SeekableReadStream * stream)817 Common::Error Engine::loadGameStream(Common::SeekableReadStream *stream) {
818 // Default to returning an error when not implemented
819 return Common::kReadingFailed;
820 }
821
canLoadGameStateCurrently()822 bool Engine::canLoadGameStateCurrently() {
823 // Do not allow loading by default
824 return false;
825 }
826
saveGameState(int slot,const Common::String & desc,bool isAutosave)827 Common::Error Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
828 Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(getSaveStateName(slot));
829
830 if (!saveFile)
831 return Common::kWritingFailed;
832
833 Common::Error result = saveGameStream(saveFile, isAutosave);
834 if (result.getCode() == Common::kNoError) {
835 getMetaEngine()->appendExtendedSave(saveFile, getTotalPlayTime() / 1000, desc, isAutosave);
836
837 saveFile->finalize();
838 }
839
840 delete saveFile;
841 return result;
842 }
843
saveGameStream(Common::WriteStream * stream,bool isAutosave)844 Common::Error Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
845 // Default to returning an error when not implemented
846 return Common::kWritingFailed;
847 }
848
canSaveGameStateCurrently()849 bool Engine::canSaveGameStateCurrently() {
850 // Do not allow saving by default
851 return false;
852 }
853
loadGameDialog()854 bool Engine::loadGameDialog() {
855 if (!canLoadGameStateCurrently()) {
856 g_system->displayMessageOnOSD(_("Loading game is currently unavailable"));
857 return false;
858 }
859
860 GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false);
861
862 int slotNum;
863 {
864 PauseToken pt = pauseEngine();
865 slotNum = dialog->runModalWithCurrentTarget();
866 }
867
868 delete dialog;
869
870 if (slotNum < 0)
871 return false;
872
873 Common::Error loadError = loadGameState(slotNum);
874 if (loadError.getCode() != Common::kNoError) {
875 GUI::MessageDialog errorDialog(loadError.getDesc());
876 errorDialog.runModal();
877 return false;
878 }
879
880 return true;
881 }
882
saveGameDialog()883 bool Engine::saveGameDialog() {
884 if (!canSaveGameStateCurrently()) {
885 g_system->displayMessageOnOSD(_("Saving game is currently unavailable"));
886 return false;
887 }
888
889 GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
890 int slotNum;
891 {
892 PauseToken pt = pauseEngine();
893 slotNum = dialog->runModalWithCurrentTarget();
894 }
895
896 Common::String desc = dialog->getResultString();
897 if (desc.empty())
898 desc = dialog->createDefaultSaveDescription(slotNum);
899
900 delete dialog;
901
902 if (slotNum < 0)
903 return false;
904
905 Common::Error saveError = saveGameState(slotNum, desc);
906 if (saveError.getCode() != Common::kNoError) {
907 GUI::MessageDialog errorDialog(saveError.getDesc());
908 errorDialog.runModal();
909 return false;
910 }
911
912 return true;
913 }
914
quitGame()915 void Engine::quitGame() {
916 Common::Event event;
917
918 event.type = Common::EVENT_QUIT;
919 g_system->getEventManager()->pushEvent(event);
920 }
921
shouldQuit()922 bool Engine::shouldQuit() {
923 Common::EventManager *eventMan = g_system->getEventManager();
924 return (eventMan->shouldQuit() || eventMan->shouldReturnToLauncher());
925 }
926
getOrCreateDebugger()927 GUI::Debugger *Engine::getOrCreateDebugger() {
928 if (!_debugger)
929 // Create a bare-bones debugger. This is useful for engines without their own
930 // debugger when an error occurs
931 _debugger = new GUI::Debugger();
932
933 return _debugger;
934 }
935
936 /*
937 EnginePlugin *Engine::getMetaEnginePlugin() const {
938 return EngineMan.findPlugin(ConfMan.get("engineid"));
939 }
940
941 */
942
getMetaEngineDetection()943 MetaEngineDetection &Engine::getMetaEngineDetection() {
944 const Plugin *plugin = EngineMan.findPlugin(ConfMan.get("engineid"));
945 assert(plugin);
946 return plugin->get<MetaEngineDetection>();
947 }
948
PauseToken()949 PauseToken::PauseToken() : _engine(nullptr) {}
950
PauseToken(Engine * engine)951 PauseToken::PauseToken(Engine *engine) : _engine(engine) {}
952
operator =(const PauseToken & t2)953 void PauseToken::operator=(const PauseToken &t2) {
954 if (_engine) {
955 error("Tried to assign to an already busy PauseToken");
956 }
957 _engine = t2._engine;
958 if (_engine) {
959 _engine->_pauseLevel++;
960 }
961 }
962
PauseToken(const PauseToken & t2)963 PauseToken::PauseToken(const PauseToken &t2) : _engine(t2._engine) {
964 if (_engine) {
965 _engine->_pauseLevel++;
966 }
967 }
968
clear()969 void PauseToken::clear() {
970 if (!_engine) {
971 error("Tried to clear an already cleared PauseToken");
972 }
973 _engine->resumeEngine();
974 _engine = nullptr;
975 }
976
~PauseToken()977 PauseToken::~PauseToken() {
978 if (_engine) {
979 _engine->resumeEngine();
980 }
981 }
982
983 #if __cplusplus >= 201103L
PauseToken(PauseToken && t2)984 PauseToken::PauseToken(PauseToken &&t2) : _engine(t2._engine) {
985 t2._engine = nullptr;
986 }
987
operator =(PauseToken && t2)988 void PauseToken::operator=(PauseToken &&t2) {
989 if (_engine) {
990 error("Tried to assign to an already busy PauseToken");
991 }
992 _engine = t2._engine;
993 t2._engine = nullptr;
994 }
995 #endif
996