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  * Copyright 2020 Google
22  *
23  */
24 
25 #include "common/debug-channels.h"
26 #include "common/error.h"
27 #include "common/events.h"
28 #include "common/ini-file.h"
29 #include "common/stream.h"
30 #include "common/system.h"
31 #include "common/file.h"
32 #include "common/keyboard.h"
33 #include "common/macresman.h"
34 #include "common/util.h"
35 #include "common/zlib.h"
36 #include "common/config-manager.h"
37 
38 #include "engines/advancedDetector.h"
39 #include "engines/util.h"
40 
41 #include "graphics/surface.h"
42 
43 #include "video/smk_decoder.h"
44 
45 #include "hadesch/pod_file.h"
46 #include "hadesch/hadesch.h"
47 #include "hadesch/video.h"
48 #include "hadesch/pod_image.h"
49 
50 #include "graphics/palette.h"
51 #include "common/memstream.h"
52 #include "common/winexe_pe.h"
53 #include "common/substream.h"
54 #include "common/md5.h"
55 #include "graphics/wincursor.h"
56 #include "graphics/cursorman.h"
57 #include "graphics/maccursor.h"
58 
59 namespace Hadesch {
60 
61 HadeschEngine *g_vm;
62 
63 static const uint32 cursorids[] = {
64 	127, 128, 129, // normal and active
65 	125, 134, 135, 136, 137, 138, 139, 140, 141, 142, // waiting
66 	130, // up arrow
67 	131, // left arrow
68 	132, // down arrow
69 	133, // right arrow
70 	// 143:cross, 146:cross
71 	146
72 };
73 
HadeschEngine(OSystem * system,const ADGameDescription * desc)74 HadeschEngine::HadeschEngine(OSystem *system, const ADGameDescription *desc)
75 	: Engine(system), _desc(desc), _rnd("hadesch"), _cheatsEnabled(false) {
76 
77 	g_vm = this;
78 	_sceneStartTime = _system->getMillis();
79 	_currentTime = 0;
80 	_isQuitting = false;
81 	_isRestoring = false;
82 
83 	_subtitleID = 0;
84 
85 	debug("HadeschEngine::ctor");
86 }
87 
~HadeschEngine()88 HadeschEngine::~HadeschEngine() {
89 	debug("HadeschEngine::dtor");
90 	for (unsigned i = 0; i < _winCursors.size(); i++) {
91 		delete _winCursors[i];
92 		_winCursors[i] = nullptr;
93 	}
94 	for (unsigned i = 0; i < _macCursors.size(); i++) {
95 		delete _macCursors[i];
96 		_macCursors[i] = nullptr;
97 	}
98 
99 #ifdef USE_TRANSLATION
100 	delete _transMan;
101 #endif
102 }
103 
setVideoRoom(Common::SharedPtr<VideoRoom> room,Common::SharedPtr<Handler> handler,RoomId roomId)104 void HadeschEngine::setVideoRoom(Common::SharedPtr<VideoRoom> room,
105 				 Common::SharedPtr<Handler> handler,
106 				 RoomId roomId) {
107 	assert(!_isInOptions || _isRestoring);
108 	_sceneVideoRoom = room;
109 	_sceneHandler = handler;
110 	_currentTime = 0;
111 	_sceneStartTime = _system->getMillis();
112 	if (!_isRestoring) {
113 		_persistent._previousRoomId = _persistent._currentRoomId;
114 		_persistent._currentRoomId = roomId;
115 	}
116 	_sceneTimers.clear();
117 }
118 
getMousePos()119 Common::Point HadeschEngine::getMousePos() {
120 	return _mousePos;
121 }
122 
handleEvent(EventHandlerWrapper event)123 void HadeschEngine::handleEvent(EventHandlerWrapper event) {
124 	event();
125 }
126 
newGame()127 void HadeschEngine::newGame() {
128 	_persistent = Persistent();
129 	_persistent._quest = kCreteQuest;
130 	moveToRoom(kWallOfFameRoom);
131 }
132 
133 #if defined(USE_ZLIB)
134 
135 struct WiseFile {
136 	uint start;
137 	uint end;
138 	uint uncompressedLength;
139 };
140 
141 // Some variants use wise installer. Wise installer use raw zlib compressed files
142 // Rather than parsing out the wise structures, we just store offsets for
143 // the files we care about
144 static const struct {
145 	const char *md5;
146 	uint setupLength;
147 	WiseFile _wdPod;
148 	WiseFile _hadeschExe;
149 } setups[] = {
150 	{
151 		// Russian, Windows
152 		"9ddf1b0b271426b9d023dbf3edbb1caa",
153 		7491209,
154 		{0xB8DA2, 0x7246CB, 8691909},
155 		{0x4109C, 0xB4628, 1007616}
156 	}
157 };
158 
readWiseFile(Common::File & setupFile,const struct WiseFile & wiseFile)159 Common::MemoryReadStream *readWiseFile(Common::File &setupFile, const struct WiseFile &wiseFile) {
160 	// -4 to skip CRC
161 	byte *compressedBuffer = new byte[wiseFile.end - wiseFile.start - 4];
162 	byte *uncompressedBuffer = new byte[wiseFile.uncompressedLength];
163 	setupFile.seek(wiseFile.start);
164 	setupFile.read(compressedBuffer, wiseFile.end - wiseFile.start - 4);
165 	if (!Common::inflateZlibHeaderless(uncompressedBuffer, wiseFile.uncompressedLength,
166 					   compressedBuffer, wiseFile.end - wiseFile.start - 4)) {
167 		debug("wise inflate failed");
168 		delete[] compressedBuffer;
169 		delete[] uncompressedBuffer;
170 		return nullptr;
171 	}
172 
173 	delete[] compressedBuffer;
174 	return new Common::MemoryReadStream(uncompressedBuffer, wiseFile.uncompressedLength);
175 }
176 #endif
177 
loadWindowsCursors(Common::PEResources & exe)178 Common::ErrorCode HadeschEngine::loadWindowsCursors(Common::PEResources &exe) {
179 	for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
180 		Graphics::WinCursorGroup *group = Graphics::WinCursorGroup::createCursorGroup(&exe, cursorids[i]);
181 
182 		if (!group) {
183 			debug("Cannot find cursor group %d", cursorids[i]);
184 			return Common::kUnsupportedGameidError;
185 		}
186 
187 		_cursors.push_back(group->cursors[0].cursor);
188 		_winCursors.push_back(group);
189 	}
190 
191 	return Common::kNoError;
192 }
193 
loadCursors()194 Common::ErrorCode HadeschEngine::loadCursors() {
195 	debug("HadeschEngine: loading cursors");
196 
197 	{
198 		Common::PEResources exe = Common::PEResources();
199 		if (exe.loadFromEXE("HADESCH.EXE")) {
200 			return loadWindowsCursors(exe);
201 		}
202 	}
203 
204 	const char *const macPaths[] = {
205 		"Hades_-_Copy_To_Hard_Drive/Hades_Challenge/Hades_Challenge_PPC",
206 		"Hades - Copy To Hard Drive/Hades Challenge/Hades Challenge PPC"
207 	};
208 
209 	for (uint j = 0; j < ARRAYSIZE(macPaths); ++j) {
210 	  	Common::MacResManager resMan = Common::MacResManager();
211 		if (!resMan.open(macPaths[j])) {
212 			continue;
213 		}
214 
215 		for (unsigned i = 0; i < ARRAYSIZE(cursorids); i++) {
216 			Common::SeekableReadStream *stream = resMan.getResource(MKTAG('c','r','s','r'), cursorids[i]);
217 			if (!stream) {
218 				debug("Couldn't load cursor %d", cursorids[i]);
219 				return Common::kUnsupportedGameidError;
220 			}
221 			Graphics::MacCursor *macCursor = new Graphics::MacCursor();
222 			macCursor->readFromStream(*stream);
223 			_cursors.push_back(macCursor);
224 			delete stream;
225 			_macCursors.push_back(macCursor);
226 		}
227 		return Common::kNoError;
228 	}
229 
230 #if defined(USE_ZLIB)
231 	Common::File setupFile;
232 	if (setupFile.open("Setup.exe")) {
233 		uint len = setupFile.size();
234 		Common::String md5 = Common::computeStreamMD5AsString(setupFile, len);
235 		for (uint chosenSetup = 0; chosenSetup < ARRAYSIZE(setups); chosenSetup++) {
236 			if (setups[chosenSetup].setupLength == len && setups[chosenSetup].md5 == md5) {
237 				Common::MemoryReadStream *wdPod = readWiseFile(setupFile, setups[0]._wdPod);
238 				if (!wdPod) {
239 					debug("wd.pod inflate failed");
240 					return Common::kUnsupportedGameidError;
241 				}
242 				_wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
243 				_wdPodFile->openStore(Common::SharedPtr<Common::SeekableReadStream>(wdPod));
244 
245 				Common::MemoryReadStream *hadeschExe = readWiseFile(setupFile, setups[0]._hadeschExe);
246 				if (!hadeschExe) {
247 					debug("hadesch.exe inflate failed");
248 					return Common::kUnsupportedGameidError;
249 				}
250 
251 				Common::PEResources exe = Common::PEResources();
252 				if (exe.loadFromEXE(hadeschExe)) {
253 					return loadWindowsCursors(exe);
254 				}
255 			}
256 		}
257 	}
258 #endif
259 
260 	debug("Cannot open hadesch.exe");
261 	return Common::kUnsupportedGameidError;
262 }
263 
264 static const char *roomCheats[] = {
265 	"",
266 	"in",
267 	"mo",
268 	"ht",
269 	"se",
270 	"at",
271 	"mi",
272 	"me",
273 	"ar",
274 	"tr",
275 	"ca",
276 	"pr",
277 	"th",
278 	"cp",
279 	"mp",
280 	"dw",
281 	"mn",
282 	"vt",
283 	"nr",
284 	"htr",
285 	"ff",
286 	"mm",
287 	"hc",
288 	"cr",
289 	"op"
290 };
291 
292 static const char *itemCheats[] = {
293 	"",
294 	"",
295 	"straw",
296 	"stone",
297 	"bricks",
298 	"message",
299 	"key",
300 	"decree",
301 	"wood",
302 	"statue1",
303 	"statue2",
304 	"statue3",
305 	"statue4",
306 	"statue",
307 	"coin",
308 	"potion",
309 	"shield",
310 	"sword",
311 	"bag",
312 	"helmet",
313 	"sandals",
314 	"torch"
315 };
316 
handleGenericCheat(const Common::String & cheat)317 bool HadeschEngine::handleGenericCheat(const Common::String &cheat) {
318 	if (cheat == "cheatsoff") {
319 		_cheatsEnabled = false;
320 		return true;
321 	}
322 
323 	for (int i = kIntroRoom; i < kOptionsRoom; i++)
324 		if (cheat == roomCheats[i]) {
325 			moveToRoom((RoomId) i);
326 			getVideoRoom()->disableMouse();
327 			return true;
328 		}
329 
330 	if (cheat == "commandson") {
331 		getVideoRoom()->enableMouse();
332 		return true;
333 	}
334 
335 	if (cheat == "commandsoff") {
336 		getVideoRoom()->disableMouse();
337 		return true;
338 	}
339 
340 	if (cheat == "cretequest") {
341 		_persistent._quest = kCreteQuest;
342 		_persistent._roomVisited[kWallOfFameRoom] = true;
343 		return true;
344 	}
345 
346 	if (cheat == "troyquest") {
347 		_persistent._quest = kTroyQuest;
348 		_persistent._roomVisited[kWallOfFameRoom] = true;
349 		_persistent._roomVisited[kMinotaurPuzzle] = true;
350 
351 		_persistent._powerLevel[0] = MAX(_persistent._powerLevel[0], 1);
352 		return true;
353 	}
354 
355 	if (cheat == "medusaquest") {
356 		_persistent._quest = kMedusaQuest;
357 		_persistent._roomVisited[kWallOfFameRoom] = true;
358 		_persistent._roomVisited[kMinotaurPuzzle] = true;
359 		_persistent._roomVisited[kTrojanHorsePuzzle] = true;
360 
361 		for (int i = 0; i < 2; i++)
362 			_persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
363 		return true;
364 	}
365 
366 	if (cheat == "rescuephilquest") {
367 		_persistent._quest = kRescuePhilQuest;
368 		_persistent._roomVisited[kWallOfFameRoom] = true;
369 		_persistent._roomVisited[kMedusaPuzzle] = true;
370 		_persistent._roomVisited[kTrojanHorsePuzzle] = true;
371 		_persistent._roomVisited[kMinotaurPuzzle] = true;
372 
373 		for (int i = 0; i < 3; i++)
374 			_persistent._powerLevel[i] = MAX(_persistent._powerLevel[i], 1);
375 
376 		return true;
377 	}
378 
379 	// TODO: "noquest", "op", "click*", "memory", "showhotspots", "hidehotspots", "showcursor",
380 	// giveall, takeall, givekeyanddecree, takekeyanddecree
381 
382 	for (int i = kStraw; i <= kTorch; i++)
383 		if (cheat == Common::String("give") + itemCheats[i]) {
384 			_heroBelt->placeToInventory((InventoryItem)i);
385 			return true;
386 		}
387 
388 	for (int i = kStraw; i <= kTorch; i++)
389 		if (cheat == Common::String("take") + itemCheats[i]) {
390 			_heroBelt->removeFromInventory((InventoryItem)i);
391 			return true;
392 		}
393 
394 	if (cheat == "hero") {
395 		_persistent._gender = kMale;
396 		return true;
397 	}
398 
399 	if (cheat == "heroine") {
400 		_persistent._gender = kFemale;
401 		return true;
402 	}
403 
404 	if (cheat.matchString("powerstrength#")) {
405 		_persistent._powerLevel[kPowerStrength] = cheat.substr(13).asUint64();
406 		return true;
407 	}
408 
409 	if (cheat.matchString("powerstealth#")) {
410 		_persistent._powerLevel[kPowerStealth] = cheat.substr(12).asUint64();
411 		return true;
412 	}
413 
414 	if (cheat.matchString("powerwisdom#")) {
415 		_persistent._powerLevel[kPowerWisdom] = cheat.substr(11).asUint64();
416 		return true;
417 	}
418 
419 	if (cheat.matchString("powerall#")) {
420 		int val = cheat.substr(8).asUint64();
421 		_persistent._powerLevel[kPowerWisdom] = val;
422 		_persistent._powerLevel[kPowerStealth] = val;
423 		_persistent._powerLevel[kPowerStrength] = val;
424 		return true;
425 	}
426 
427 	return false;
428 }
429 
resetOptionsRoom()430 void HadeschEngine::resetOptionsRoom() {
431 	_optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("", "", "OPAssets.txt"));
432 }
433 
enterOptions()434 void HadeschEngine::enterOptions() {
435 	_isInOptions = true;
436 	_optionsEnterTime = _system->getMillis();
437 	_sceneVideoRoom->pause();
438 	resetOptionsRoom();
439 	_optionsHandler = makeOptionsHandler();
440 	_optionsHandler->prepareRoom();
441 }
442 
enterOptionsCredits()443 void HadeschEngine::enterOptionsCredits() {
444 	if (_isInOptions) {
445 		_sceneStartTime += _system->getMillis() - _optionsEnterTime;
446 	}
447 	_isInOptions = true;
448 	_optionsEnterTime = _system->getMillis();
449 	_optionsRoom = Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", ""));
450 	_optionsHandler = makeCreditsHandler(true);
451 	_optionsHandler->prepareRoom();
452 }
453 
exitOptions()454 void HadeschEngine::exitOptions() {
455 	_isInOptions = false;
456 	_sceneStartTime += _system->getMillis() - _optionsEnterTime;
457 	_optionsHandler.reset();
458 	_optionsRoom.reset();
459 	_sceneVideoRoom->unpause();
460 }
461 
translate(const Common::String & str)462 Common::U32String HadeschEngine::translate(const Common::String &str) {
463 #ifdef USE_TRANSLATION
464 	return _transMan->getTranslation(str);
465 #else
466 	return str.decode();
467 #endif
468 }
469 
run()470 Common::Error HadeschEngine::run() {
471 	debug("HadeschEngine::run");
472 
473 #ifdef USE_TRANSLATION
474 	_transMan = new Common::TranslationManager("hadesch_translations.dat");
475 	_transMan->setLanguage(TransMan.getCurrentLanguage());
476 #endif
477 
478 	const Common::FSNode gameDataDir(ConfMan.get("path"));
479 	SearchMan.addSubDirectoryMatching(gameDataDir, "WIN9x");
480 
481 	Common::ErrorCode err = loadCursors();
482 	if (err != Common::kNoError)
483 		return err;
484 
485 	if (!_wdPodFile) {
486 		const char *const wdpodpaths[] = {
487 			"WIN9x/WORLD/WD.POD", "WD.POD",
488 			"Hades_-_Copy_To_Hard_Drive/Hades_Challenge/World/wd.pod",
489 			"Hades - Copy To Hard Drive/Hades Challenge/World/wd.pod"};
490 		debug("HadeschEngine: loading wd.pod");
491 		for (uint i = 0; i < ARRAYSIZE(wdpodpaths); ++i) {
492 			Common::SharedPtr<Common::File> file(new Common::File());
493 			if (file->open(wdpodpaths[i])) {
494 				_wdPodFile = Common::SharedPtr<PodFile>(new PodFile("WD.POD"));
495 				_wdPodFile->openStore(file);
496 				break;
497 			}
498 		}
499 	}
500 
501 	if (!_wdPodFile) {
502 		debug("Cannot find WD.POD");
503 		return Common::kUnsupportedGameidError;
504 	}
505 
506 	_cdScenesPath = "";
507 
508 	// It's tempting to use SearchMan for this but it
509 	// doesn't work because we need to access subdirs based
510 	// on cdScenePath
511 	const char *const scenepaths[] = {"CDAssets/", "Scenes/"};
512 	for (uint i = 0; i < ARRAYSIZE(scenepaths); ++i) {
513 		Common::ScopedPtr<Common::File> file(new Common::File());
514 		if (file->open(Common::String(scenepaths[i]) + "OLYMPUS/OL.POD")) {
515 			_cdScenesPath = scenepaths[i];
516 			break;
517 		}
518 	}
519 
520 	if (_cdScenesPath == "") {
521 		debug("Cannot find OL.POD");
522 		return Common::kUnsupportedGameidError;
523 	}
524 
525 	debug("HadeschEngine: intro");
526 	initGraphics(kVideoWidth, kVideoHeight);
527 
528 	_heroBelt = Common::SharedPtr<HeroBelt>(new HeroBelt());
529 	_gfxContext = Common::SharedPtr<GfxContext8Bit>(new GfxContext8Bit(2 * kVideoWidth + 10, kVideoHeight + 50));
530 	_isInOptions = false;
531 
532 	ConfMan.registerDefault("subtitles", "false");
533 	ConfMan.registerDefault("sfx_volume", 192);
534 	ConfMan.registerDefault("music_volume", 192);
535 	ConfMan.registerDefault("speech_volume", 192);
536 	ConfMan.registerDefault("mute", "false");
537 	ConfMan.registerDefault("speech_mute", "false");
538 	ConfMan.registerDefault("talkspeed", 60);
539 	_mixer->setVolumeForSoundType(_mixer->kMusicSoundType, ConfMan.getInt("music_volume"));
540 	_mixer->setVolumeForSoundType(_mixer->kSFXSoundType, ConfMan.getInt("sfx_volume"));
541 	_mixer->setVolumeForSoundType(_mixer->kSpeechSoundType, ConfMan.getInt("speech_volume"));
542 
543 	if (!ConfMan.getBool("subtitles"))
544 		_subtitleDelayPerChar = -1;
545 	else
546 		_subtitleDelayPerChar = 4500 / ConfMan.getInt("talkspeed");
547 
548 	debug("HadeschEngine: moving to main loop");
549 	_nextRoom.clear();
550 	int loadSlot = ConfMan.getInt("save_slot");
551 	if (loadSlot >= 0) {
552 		loadGameState(loadSlot);
553 	} else {
554 		_nextRoom.push_back(kIntroRoom);
555 	}
556 
557 	while (true) {
558 		if (_isRestoring) {
559 			moveToRoomReal(_persistent._currentRoomId);
560 			_isRestoring = false;
561 			CursorMan.showMouse(true);
562 			CursorMan.replaceCursor(_cursors[3]);
563 		} else if (!_nextRoom.empty()) {
564 			moveToRoomReal(_nextRoom.remove_at(0));
565 			CursorMan.showMouse(true);
566 			CursorMan.replaceCursor(_cursors[3]);
567 		}
568 
569 		if (_isQuitting) {
570 			return Common::kNoError;
571 		}
572 		Common::Event event;
573 		bool stopVideo = false;
574 		while (_eventMan->pollEvent(event)) {
575 			switch (event.type) {
576 			case Common::EVENT_QUIT:
577 			case Common::EVENT_RETURN_TO_LAUNCHER:
578 				return Common::kNoError;
579 			case Common::EVENT_LBUTTONDOWN: {
580 				if(getVideoRoom()->isMouseEnabled() && getVideoRoom()->isHeroBeltEnabled()
581 				   && _heroBelt->isPositionOverHeroBelt(event.mouse)) {
582 					debug("handling belt click");
583 					_heroBelt->handleClick(event.mouse);
584 					break;
585 				}
586 				const Common::String &q = getVideoRoom()->mapClick(event.mouse);
587 				debug("handling click on <%s>", q.c_str());
588 				if (getVideoRoom()->isHeroBeltEnabled() && _heroBelt->isHoldingItem())
589 					getCurrentHandler()->handleClickWithItem(q, _heroBelt->getHoldingItem());
590 				else {
591 					getCurrentHandler()->handleAbsoluteClick(event.mouse);
592 					getCurrentHandler()->handleClick(q);
593 				}
594 			}
595 				break;
596 			case Common::EVENT_LBUTTONUP:
597 				getCurrentHandler()->handleUnclick(getVideoRoom()->mapClick(event.mouse), event.mouse);
598 				break;
599 			case Common::EVENT_KEYDOWN:
600 				// TODO: make equivalents for mobile devices. Keyboard is
601 				// used for 4 things:
602 				//
603 				// * Skipping cutscenes (press space)
604 				// * Entering name.
605 				//      Original requires a non-empty name. We allow an
606 				//      empty name.
607 				// * Optional save name
608 				// * Cheats
609 				if (event.kbd.keycode == Common::KEYCODE_SPACE)
610 					stopVideo = true;
611 				if ((event.kbd.ascii >= 'a' && event.kbd.ascii <= 'z')
612 				    || (event.kbd.ascii >= '0' && event.kbd.ascii <= '9')) {
613 					_cheat += event.kbd.ascii;
614 				}
615 				if (event.kbd.keycode == Common::KEYCODE_RETURN) {
616 					Common::String cheat = _cheat;
617 					_cheat = "";
618 					if (cheat == "qazxcdewsrfvbnhytg") {
619 						debug("Cheats enabled");
620 						_cheatsEnabled = true;
621 						break;
622 					}
623 					if (_cheatsEnabled) {
624 						if (handleGenericCheat(cheat))
625 							break;
626 						if (getCurrentHandler()->handleCheat(cheat))
627 							break;
628 					}
629 				}
630 				getCurrentHandler()->handleKeypress(event.kbd.ascii);
631 				break;
632 			default:
633 				break;
634 			}
635 		}
636 
637 		if (_isInOptions) {
638 			_currentTime = _system->getMillis() - _optionsEnterTime;
639 		} else {
640 			_currentTime = _system->getMillis() - _sceneStartTime;
641 			for (Common::List<Timer>::iterator it = _sceneTimers.begin();
642 			     it != _sceneTimers.end();) {
643 				if ((it->period_count != 0 && it->next_time < _currentTime)
644 				    || (it->skippable && stopVideo)) {
645 					it->next_time = _currentTime + it->period;
646 					if (it->period_count != -1) {
647 						it->period_count--;
648 					}
649 					handleEvent(it->event);
650 				}
651 
652 				if (it->period_count == 0) {
653 					it = _sceneTimers.erase(it);
654 				} else
655 					it++;
656 			}
657 		}
658 
659 		Common::String oldhotzone = getVideoRoom()->getHotZone();
660 		_mousePos = _eventMan->getMousePos();
661 		getVideoRoom()->computeHotZone(_currentTime, _mousePos);
662 
663 		Common::String newhotzone = getVideoRoom()->getHotZone();
664 
665 		if (oldhotzone != newhotzone) {
666 			getCurrentHandler()->handleMouseOut(oldhotzone);
667 			getCurrentHandler()->handleMouseOver(newhotzone);
668 		}
669 
670 		getCurrentHandler()->frameCallback();
671 
672 		getVideoRoom()->nextFrame(_gfxContext, _currentTime, stopVideo);
673 
674 		if (getVideoRoom()->getDragged()) {
675 			CursorMan.showMouse(true);
676 			CursorMan.replaceCursor(getVideoRoom()->getDragged());
677 		} else if (getVideoRoom()->isHeroBeltEnabled()
678 		    && _heroBelt->isHoldingItem()) {
679 			const Graphics::Cursor *cursor = _heroBelt->getHoldingItemCursor(
680 				getVideoRoom()->getCursorAnimationFrame(_currentTime));
681 			CursorMan.showMouse(true);
682 			CursorMan.replaceCursor(cursor);
683 		} else {
684 			int cursor = getVideoRoom()->getCursor();
685 			if (cursor >= 0) {
686 				CursorMan.showMouse(true);
687 				CursorMan.replaceCursor(_cursors[cursor]);
688 			} else {
689 				CursorMan.showMouse(true);
690 				CursorMan.replaceCursor(_cursors[3]);
691 			}
692 		}
693 
694 		_system->updateScreen();
695 		_system->delayMillis(15);
696 	}
697 
698 	return Common::kNoError;
699 }
700 
getRnd()701 Common::RandomSource &HadeschEngine::getRnd() {
702 	return _rnd;
703 }
704 
hasFeature(EngineFeature f) const705 bool HadeschEngine::hasFeature(EngineFeature f) const {
706 	return
707 		f == kSupportsReturnToLauncher ||
708 		f == kSupportsLoadingDuringRuntime ||
709 		f == kSupportsSavingDuringRuntime ||
710 		f == kSupportsChangingOptionsDuringRuntime;
711 }
712 
loadGameStream(Common::SeekableReadStream * stream)713 Common::Error HadeschEngine::loadGameStream(Common::SeekableReadStream *stream) {
714 	Common::Serializer s(stream, nullptr);
715 	if (!_persistent.syncGameStream(s))
716 		return Common::kUnknownError;
717 
718 	_isRestoring = true;
719 
720 	return Common::kNoError;
721 }
722 
saveGameStream(Common::WriteStream * stream,bool isAutosave)723 Common::Error HadeschEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
724 	Common::Serializer s(nullptr, stream);
725 	if (isAutosave)
726 		_persistent._slotDescription = "Autosave";
727 	if(_persistent._currentRoomId == 0)
728 		return Common::kUnknownError;
729 	bool res = _persistent.syncGameStream(s);
730 	_persistent._slotDescription = "";
731 	return res ? Common::kNoError
732 		: Common::kUnknownError;
733 }
734 
getCDScenesPath() const735 const Common::String &HadeschEngine::getCDScenesPath() const {
736 	return _cdScenesPath;
737 }
738 
addTimer(EventHandlerWrapper eventId,int32 start_time,int period,int repeat,bool skippable)739 void HadeschEngine::addTimer(EventHandlerWrapper eventId, int32 start_time, int period, int repeat, bool skippable) {
740 	struct Timer timer;
741 	assert(!_isInOptions);
742 	timer.next_time = start_time + period;
743 	timer.period_count = repeat;
744 	timer.period = period;
745 	timer.event = eventId;
746 	timer.skippable = skippable;
747 	_sceneTimers.push_back(timer);
748 }
749 
addTimer(EventHandlerWrapper eventId,int period,int repeat)750 void HadeschEngine::addTimer(EventHandlerWrapper eventId, int period, int repeat) {
751 	addTimer(eventId, _currentTime, period, repeat, false);
752 }
753 
addSkippableTimer(EventHandlerWrapper eventId,int period,int repeat)754 void HadeschEngine::addSkippableTimer(EventHandlerWrapper eventId, int period, int repeat) {
755 	addTimer(eventId, _currentTime, period, repeat, true);
756 }
757 
cancelTimer(int eventId)758 void HadeschEngine::cancelTimer(int eventId) {
759 	assert(!_isInOptions);
760 	for (Common::List<Timer>::iterator it = _sceneTimers.begin();
761 	     it != _sceneTimers.end();) {
762 		if (it->event == eventId) {
763 			it = _sceneTimers.erase(it);
764 		} else
765 			it++;
766 	}
767 }
768 
getCurrentHandler()769 Common::SharedPtr<Handler> HadeschEngine::getCurrentHandler() {
770 	return _isInOptions ? _optionsHandler : _sceneHandler;
771 }
772 
getVideoRoom()773 Common::SharedPtr<VideoRoom> HadeschEngine::getVideoRoom() {
774 	return _isInOptions ? _optionsRoom : _sceneVideoRoom;
775 }
776 
moveToRoomReal(RoomId id)777 void HadeschEngine::moveToRoomReal(RoomId id) {
778 	if (_sceneVideoRoom)
779 		_sceneVideoRoom->finish();
780 
781 	_heroBelt->reset();
782 
783 	switch (id) {
784 	case kWallOfFameRoom:
785 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("WALLFAME", "WF", "HTAssets.txt")),
786 			     makeWallOfFameHandler(), id);
787 		break;
788 	case kArgoRoom:
789 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ARGO", "AR", "ARAssets.txt")),
790 			     makeArgoHandler(), id);
791 		break;
792 	case kCreteRoom:
793 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CRETE", "CR", "")),
794 			     makeCreteHandler(), id);
795 			     break;
796 	case kOlympusRoom:
797 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("OLYMPUS", "OL", "MOAssets.txt")),
798 			     makeOlympusHandler(), id);
799 		break;
800 	case kMinosPalaceRoom:
801 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOS", "MI", "MIAssets.txt")),
802 			     makeMinosHandler(), id);
803 		break;
804 	case kDaedalusRoom:
805 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("DAEDALUS", "DA", "DWAssets.txt")),
806 			     makeDaedalusHandler(), id);
807 		break;
808 	case kSeriphosRoom:
809 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("SERIPHOS", "SE", "")),
810 			     makeSeriphosHandler(), id);
811 		break;
812 	case kMedIsleRoom:
813 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDISLE", "MI", "")),
814 			     makeMedIsleHandler(), id);
815 		break;
816 	case kMedusaPuzzle:
817 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MEDUSA", "ME", "")),
818 			     makeMedusaHandler(), id);
819 		break;
820 	case kTroyRoom:
821 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROY", "TR", "")),
822 			     makeTroyHandler(), id);
823 		break;
824 	case kTrojanHorsePuzzle:
825 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("TROJAN", "TH", "")),
826 			     makeTrojanHandler(), id);
827 		break;
828 	case kMinotaurPuzzle:
829 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MINOTAUR", "MM", "")),
830 			     makeMinotaurHandler(), id);
831 			     break;
832 	case kQuiz:
833 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("HADESCH", "HC", "HcAssets.txt")),
834 			     makeQuizHandler(), id);
835 		break;
836 	case kCatacombsRoom:
837 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CATACOMB", "CA", "CaAssets.txt")),
838 			     makeCatacombsHandler(), id);
839 		break;
840 	case kPriamRoom:
841 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("PRIAM", "PR", "PrAssets.txt")),
842 			     makePriamHandler(), id);
843 		break;
844 	case kAthenaRoom:
845 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("ATHENA", "AT", "")),
846 			     makeAthenaHandler(), id);
847 		break;
848 	case kVolcanoRoom:
849 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("VOLCANO", "VO", "VTAssets.txt")),
850 			     makeVolcanoHandler(), id);
851 		break;
852 	case kRiverStyxRoom:
853 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("NEARR", "NR", "NRAssets.txt")),
854 			     makeRiverStyxHandler(), id);
855 		break;
856 	case kHadesThroneRoom:
857 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("THRONE", "TH", "HTRAsset.txt")),
858 			     makeHadesThroneHandler(), id);
859 		break;
860 	case kCreditsRoom:
861 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("CREDITS", "CR", "")),
862 			     makeCreditsHandler(false), id);
863 		break;
864 	case kIntroRoom:
865 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("INTRO", "IN", "")),
866 			     makeIntroHandler(), id);
867 		break;
868 	case kFerrymanPuzzle:
869 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("FERRY", "FF", "")),
870 			     makeFerryHandler(), id);
871 		break;
872 	case kMonsterPuzzle:
873 		setVideoRoom(Common::SharedPtr<VideoRoom>(new VideoRoom("MONSTER", "MM", "")),
874 			     makeMonsterHandler(), id);
875 		break;
876 	default:
877 		debug("unknown room %d", id);
878 		assert(0);
879 		return;
880 	}
881 
882 	_sceneHandler->prepareRoom();
883 
884 	_persistent._roomVisited[id] = true;
885 }
886 
genSubtitleID()887 int HadeschEngine::genSubtitleID() {
888 	return _subtitleID++;
889 }
890 
firstAvailableSlot()891 int HadeschEngine::firstAvailableSlot() {
892 	for (unsigned slot = 3; ; slot++) {
893 		SaveStateDescriptor desc = getMetaEngine()->querySaveMetaInfos(_targetName.c_str(), slot);
894 		if (desc.getSaveSlot() == -1 && !desc.getWriteProtectedFlag())
895 			return slot;
896 	}
897 }
898 
quit()899 void HadeschEngine::quit() {
900 	_isQuitting = true;
901 }
902 
hasAnySaves()903 bool HadeschEngine::hasAnySaves() {
904 	Common::SaveFileManager *saveFileMan = getSaveFileManager();
905 	Common::StringArray filenames;
906 	Common::String pattern(getMetaEngine()->getSavegameFilePattern(_targetName.c_str()));
907 
908 	filenames = saveFileMan->listSavefiles(pattern);
909 
910 	return !filenames.empty();
911 }
912 
getHadeschSavesList()913 Common::Array<HadeschSaveDescriptor> HadeschEngine::getHadeschSavesList() {
914 	Common::SaveFileManager *saveFileMan = getSaveFileManager();
915 	Common::StringArray filenames;
916 	Common::String pattern(getMetaEngine()->getSavegameFilePattern(_targetName.c_str()));
917 
918 	filenames = saveFileMan->listSavefiles(pattern);
919 
920 	Common::Array<HadeschSaveDescriptor> saveList;
921 	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
922 		// Obtain the last 2 digits of the filename, since they correspond to the save slot
923 		int slotNum = atoi(file->c_str() + file->size() - 3);
924 
925 		if (slotNum >= 0) {
926 			Common::ScopedPtr<Common::InSaveFile> in(saveFileMan->openForLoading(*file));
927 			if (!in) {
928 				continue;
929 			}
930 
931 			Common::Serializer s(in.get(), nullptr);
932 
933 			saveList.push_back(HadeschSaveDescriptor(s, slotNum));
934 		}
935 	}
936 
937 	// Sort saves based on slot number.
938 	// TODO: change it to chronological save id
939 	Common::sort(saveList.begin(), saveList.end(), HadeschSaveDescriptorSlotComparator());
940 	return saveList;
941 }
942 
deleteSave(int slot)943 void HadeschEngine::deleteSave(int slot) {
944 	getMetaEngine()->removeSaveState(_targetName.c_str(), slot);
945 }
946 
operator ()() const947 void EventHandlerWrapper::operator()() const {
948   	if (_handler && _eventId == -1)
949 		debug("handling anon event");
950 	else if (_eventId != 20001 && _eventId != 14006 && _eventId != 15266)
951 		debug("handling event %d", _eventId);
952 	if (_handler)
953 		_handler->operator()();
954 	if (_eventId > 0)
955 		g_vm->getCurrentHandler()->handleEvent(_eventId);
956 }
957 
operator ==(int b) const958 bool EventHandlerWrapper::operator==(int b) const {
959 	return _eventId == b;
960 }
961 
getSubtitleDelayPerChar() const962 uint32 HadeschEngine::getSubtitleDelayPerChar() const {
963 	return _subtitleDelayPerChar;
964 }
965 
966 } // End of namespace Hadesch
967