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