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 
24 #include "gui/EventRecorder.h"
25 
26 #ifdef ENABLE_EVENTRECORDER
27 
28 namespace Common {
29 DECLARE_SINGLETON(GUI::EventRecorder);
30 }
31 
32 #include "common/debug-channels.h"
33 #include "backends/timer/sdl/sdl-timer.h"
34 #include "backends/mixer/mixer.h"
35 #include "common/config-manager.h"
36 #include "common/md5.h"
37 #include "gui/gui-manager.h"
38 #include "gui/widget.h"
39 #include "gui/onscreendialog.h"
40 #include "common/random.h"
41 #include "common/savefile.h"
42 #include "common/textconsole.h"
43 #include "graphics/thumbnail.h"
44 #include "graphics/surface.h"
45 #include "graphics/scaler.h"
46 
47 namespace GUI {
48 
49 
50 const int kMaxRecordsNames = 0x64;
51 const int kDefaultScreenshotPeriod = 60000;
52 
readTime(Common::ReadStream * inFile)53 uint32 readTime(Common::ReadStream *inFile) {
54 	uint32 d = inFile->readByte();
55 	if (d == 0xff) {
56 		d = inFile->readUint32LE();
57 	}
58 
59 	return d;
60 }
61 
writeTime(Common::WriteStream * outFile,uint32 d)62 void writeTime(Common::WriteStream *outFile, uint32 d) {
63 		//Simple RLE compression
64 	if (d >= 0xff) {
65 		outFile->writeByte(0xff);
66 		outFile->writeUint32LE(d);
67 	} else {
68 		outFile->writeByte(d);
69 	}
70 }
71 
EventRecorder()72 EventRecorder::EventRecorder() {
73 	_timerManager = nullptr;
74 	_recordMode = kPassthrough;
75 	_fakeMixerManager = nullptr;
76 	_initialized = false;
77 	_needRedraw = false;
78 	_processingMillis = false;
79 	_fastPlayback = false;
80 	_lastTimeDate.tm_sec = 0;
81 	_lastTimeDate.tm_min = 0;
82 	_lastTimeDate.tm_hour = 0;
83 	_lastTimeDate.tm_mday = 0;
84 	_lastTimeDate.tm_mon = 0;
85 	_lastTimeDate.tm_year = 0;
86 	_lastTimeDate.tm_wday = 0;
87 
88 	_fakeTimer = 0;
89 	_savedState = false;
90 	_needcontinueGame = false;
91 	_temporarySlot = 0;
92 	_realSaveManager = nullptr;
93 	_realMixerManager = nullptr;
94 	_controlPanel = nullptr;
95 	_lastMillis = 0;
96 	_lastScreenshotTime = 0;
97 	_screenshotPeriod = 0;
98 	_playbackFile = nullptr;
99 }
100 
~EventRecorder()101 EventRecorder::~EventRecorder() {
102 	delete _timerManager;
103 }
104 
deinit()105 void EventRecorder::deinit() {
106 	if (!_initialized) {
107 		return;
108 	}
109 	setFileHeader();
110 	_needRedraw = false;
111 	_initialized = false;
112 	_recordMode = kPassthrough;
113 	delete _fakeMixerManager;
114 	_fakeMixerManager = nullptr;
115 	_controlPanel->close();
116 	delete _controlPanel;
117 	debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
118 	Common::EventDispatcher *eventDispater = g_system->getEventManager()->getEventDispatcher();
119 	eventDispater->unregisterSource(this);
120 	eventDispater->ignoreSources(false);
121 	_recordMode = kPassthrough;
122 	_playbackFile->close();
123 	delete _playbackFile;
124 	switchMixer();
125 	switchTimerManagers();
126 	DebugMan.disableDebugChannel("EventRec");
127 }
128 
processTimeAndDate(TimeDate & td,bool skipRecord)129 void EventRecorder::processTimeAndDate(TimeDate &td, bool skipRecord) {
130 	if (!_initialized) {
131 		return;
132 	}
133 	if (skipRecord) {
134 		td = _lastTimeDate;
135 		return;
136 	}
137 	Common::RecorderEvent timeDateEvent;
138 	switch (_recordMode) {
139 	case kRecorderRecord:
140 		timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
141 		timeDateEvent.timeDate = td;
142 		_lastTimeDate = td;
143 		_playbackFile->writeEvent(timeDateEvent);
144 		break;
145 	case kRecorderPlayback:
146 		if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimeDate) {
147 			// just re-use any previous date time value
148 			td = _lastTimeDate;
149 			return;
150 		}
151 		_lastTimeDate = _nextEvent.timeDate;
152 		td = _lastTimeDate;
153 		debug(3, "timedate event");
154 
155 		_nextEvent = _playbackFile->getNextEvent();
156 		break;
157 	case kRecorderPlaybackPause:
158 		td = _lastTimeDate;
159 		break;
160 	default:
161 		break;
162 	}
163 }
164 
processMillis(uint32 & millis,bool skipRecord)165 void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
166 	if (!_initialized) {
167 		return;
168 	}
169 	if (skipRecord || _processingMillis) {
170 		millis = _fakeTimer;
171 		return;
172 	}
173 	// to prevent calling this recursively
174 	if (_recordMode == kRecorderPlaybackPause) {
175 		millis = _fakeTimer;
176 	}
177 	uint32 millisDelay;
178 	Common::RecorderEvent timerEvent;
179 	switch (_recordMode) {
180 	case kRecorderRecord:
181 		updateSubsystems();
182 		millisDelay = millis - _lastMillis;
183 		_lastMillis = millis;
184 		_fakeTimer += millisDelay;
185 		_controlPanel->setReplayedTime(_fakeTimer);
186 		timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
187 		timerEvent.time = _fakeTimer;
188 		_playbackFile->writeEvent(timerEvent);
189 		takeScreenshot();
190 		_timerManager->handler();
191 		break;
192 	case kRecorderPlayback:
193 		if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimer) {
194 			// just re-use any previous millis value
195 			// this might happen if you have EventSource instances registered, that
196 			// are querying the millis by themselves, too. If the EventRecorder::poll
197 			// is registered and thus dispatched after those EventSource instances, it
198 			// might look like it ran out-of-sync.
199 			millis = _fakeTimer;
200 			return;
201 		}
202 		_processingMillis = true;
203 		_fakeTimer = _nextEvent.time;
204 		millis = _fakeTimer;
205 		debug(3, "millis event: %u", millis);
206 
207 		updateSubsystems();
208 		_nextEvent = _playbackFile->getNextEvent();
209 		_timerManager->handler();
210 		_controlPanel->setReplayedTime(_fakeTimer);
211 		_processingMillis = false;
212 		break;
213 	case kRecorderPlaybackPause:
214 		millis = _fakeTimer;
215 		break;
216 	default:
217 		break;
218 	}
219 }
220 
processDelayMillis()221 bool EventRecorder::processDelayMillis() {
222 	return _fastPlayback;
223 }
224 
checkForKeyCode(const Common::Event & event)225 void EventRecorder::checkForKeyCode(const Common::Event &event) {
226 	if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.flags & Common::KBD_CTRL) && (event.kbd.keycode == Common::KEYCODE_p) && (!event.kbdRepeat)) {
227 		togglePause();
228 	}
229 }
230 
pollEvent(Common::Event & ev)231 bool EventRecorder::pollEvent(Common::Event &ev) {
232 	if ((_recordMode != kRecorderPlayback) || !_initialized)
233 		return false;
234 
235 	if (_nextEvent.recordedtype == Common::kRecorderEventTypeTimer
236 	 || _nextEvent.recordedtype == Common::kRecorderEventTypeTimeDate
237 	 || _nextEvent.type == Common::EVENT_INVALID) {
238 		return false;
239 	}
240 
241 	ev = _nextEvent;
242 	_nextEvent = _playbackFile->getNextEvent();
243 	switch (ev.type) {
244 	case Common::EVENT_MOUSEMOVE:
245 	case Common::EVENT_LBUTTONDOWN:
246 	case Common::EVENT_LBUTTONUP:
247 	case Common::EVENT_RBUTTONDOWN:
248 	case Common::EVENT_RBUTTONUP:
249 	case Common::EVENT_WHEELUP:
250 	case Common::EVENT_WHEELDOWN:
251 		g_system->warpMouse(ev.mouse.x, ev.mouse.y);
252 		break;
253 	default:
254 		break;
255 	}
256 	return true;
257 }
258 
switchFastMode()259 void EventRecorder::switchFastMode() {
260 	if (_recordMode == kRecorderPlaybackPause) {
261 		_fastPlayback = !_fastPlayback;
262 	}
263 }
264 
togglePause()265 void EventRecorder::togglePause() {
266 	RecordMode oldState;
267 	switch (_recordMode) {
268 	case kRecorderPlayback:
269 	case kRecorderRecord:
270 		oldState = _recordMode;
271 		_recordMode = kRecorderPlaybackPause;
272 		_controlPanel->runModal();
273 		_recordMode = oldState;
274 		_initialized = true;
275 		break;
276 	case kRecorderPlaybackPause:
277 		_controlPanel->close();
278 		break;
279 	default:
280 		break;
281 	}
282 }
283 
RegisterEventSource()284 void EventRecorder::RegisterEventSource() {
285 	g_system->getEventManager()->getEventDispatcher()->registerObserver(this, Common::EventManager::kEventRecorderPriority, false);
286 }
287 
getRandomSeed(const Common::String & name)288 uint32 EventRecorder::getRandomSeed(const Common::String &name) {
289 	if (_recordMode == kRecorderPlayback) {
290 		return _playbackFile->getHeader().randomSourceRecords[name];
291 	}
292 	uint32 result = g_system->getMillis();
293 	if (_recordMode == kRecorderRecord) {
294 		_playbackFile->getHeader().randomSourceRecords[name] = result;
295 	}
296 	return result;
297 }
298 
generateRecordFileName(const Common::String & target)299 Common::String EventRecorder::generateRecordFileName(const Common::String &target) {
300 	Common::String pattern(target + ".r??");
301 	Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
302 	for (int i = 0; i < kMaxRecordsNames; ++i) {
303 		Common::String recordName = Common::String::format("%s.r%02d", target.c_str(), i);
304 		if (find(files.begin(), files.end(), recordName) != files.end()) {
305 			continue;
306 		}
307 		return recordName;
308 	}
309 	return "";
310 }
311 
312 
init(const Common::String & recordFileName,RecordMode mode)313 void EventRecorder::init(const Common::String &recordFileName, RecordMode mode) {
314 	_fakeMixerManager = new NullMixerManager();
315 	_fakeMixerManager->init();
316 	_fakeMixerManager->suspendAudio();
317 	_fakeTimer = 0;
318 	_lastMillis = g_system->getMillis();
319 	_playbackFile = new Common::PlaybackFile();
320 	_lastScreenshotTime = 0;
321 	_recordMode = mode;
322 	_needcontinueGame = false;
323 	if (ConfMan.hasKey("disable_display")) {
324 		DebugMan.enableDebugChannel("EventRec");
325 		gDebugLevel = 1;
326 	}
327 	if (_recordMode == kRecorderPlayback) {
328 		debugC(1, kDebugLevelEventRec, "playback:action=\"Load file\" filename=%s", recordFileName.c_str());
329 		Common::EventDispatcher *eventDispater = g_system->getEventManager()->getEventDispatcher();
330 		eventDispater->clearEvents();
331 		eventDispater->ignoreSources(true);
332 		eventDispater->registerSource(this, false);
333 	}
334 	_screenshotPeriod = ConfMan.getInt("screenshot_period");
335 	if (_screenshotPeriod == 0) {
336 		_screenshotPeriod = kDefaultScreenshotPeriod;
337 	}
338 	if (!openRecordFile(recordFileName)) {
339 		deinit();
340 		error("playback:action=error reason=\"Record file loading error\"");
341 		return;
342 	}
343 	if (_recordMode != kPassthrough) {
344 		_controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
345 		_controlPanel->reflowLayout();
346 	}
347 	if (_recordMode == kRecorderPlayback) {
348 		applyPlaybackSettings();
349 		_nextEvent = _playbackFile->getNextEvent();
350 	}
351 	if (_recordMode == kRecorderRecord) {
352 		getConfig();
353 	}
354 
355 	switchMixer();
356 	switchTimerManagers();
357 	_needRedraw = true;
358 	_initialized = true;
359 }
360 
361 
362 /**
363  * Opens or creates file depend of recording mode.
364  *
365  * @param id of recording or playing back game
366  * @return true in case of success, false in case of error
367  */
openRecordFile(const Common::String & fileName)368 bool EventRecorder::openRecordFile(const Common::String &fileName) {
369 	bool result;
370 	switch (_recordMode) {
371 	case kRecorderRecord:
372 		return _playbackFile->openWrite(fileName);
373 	case kRecorderPlayback:
374 		_recordMode = kPassthrough;
375 		result = _playbackFile->openRead(fileName);
376 		_recordMode = kRecorderPlayback;
377 		return result;
378 	default:
379 		return false;
380 	}
381 	return true;
382 }
383 
checkGameHash(const ADGameDescription * gameDesc)384 bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) {
385 	if (_playbackFile->getHeader().hashRecords.size() == 0) {
386 		warning("Engine doesn't contain description table");
387 		return false;
388 	}
389 	for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
390 		if (fileDesc->md5 == nullptr)
391 			continue;
392 
393 		if (_playbackFile->getHeader().hashRecords.find(fileDesc->fileName) == _playbackFile->getHeader().hashRecords.end()) {
394 			warning("MD5 hash for file %s not found in record file", fileDesc->fileName);
395 			debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=\"\" result=different", fileDesc->fileName, fileDesc->md5);
396 			return false;
397 		}
398 		if (_playbackFile->getHeader().hashRecords[fileDesc->fileName] != fileDesc->md5) {
399 			warning("Incorrect version of game file %s. Stored MD5 is %s. MD5 of loaded game is %s", fileDesc->fileName, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str(), fileDesc->md5);
400 			debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=different", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
401 			return false;
402 		}
403 		debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=equal", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
404 	}
405 	return true;
406 }
407 
registerMixerManager(MixerManager * mixerManager)408 void EventRecorder::registerMixerManager(MixerManager *mixerManager) {
409 	_realMixerManager = mixerManager;
410 }
411 
switchMixer()412 void EventRecorder::switchMixer() {
413 	if (_recordMode == kPassthrough) {
414 		_realMixerManager->resumeAudio();
415 	} else {
416 		_realMixerManager->suspendAudio();
417 		_fakeMixerManager->resumeAudio();
418 	}
419 }
420 
getMixerManager()421 MixerManager *EventRecorder::getMixerManager() {
422 	if (_recordMode == kPassthrough) {
423 		return _realMixerManager;
424 	} else {
425 		return _fakeMixerManager;
426 	}
427 }
428 
getConfigFromDomain(const Common::ConfigManager::Domain * domain)429 void EventRecorder::getConfigFromDomain(const Common::ConfigManager::Domain *domain) {
430 	for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
431 		_playbackFile->getHeader().settingsRecords[entry->_key] = entry->_value;
432 	}
433 }
434 
getConfig()435 void EventRecorder::getConfig() {
436 	getConfigFromDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
437 	getConfigFromDomain(ConfMan.getActiveDomain());
438 	_playbackFile->getHeader().settingsRecords["save_slot"] = ConfMan.get("save_slot");
439 }
440 
441 
applyPlaybackSettings()442 void EventRecorder::applyPlaybackSettings() {
443 	for (Common::StringMap::const_iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) {
444 		Common::String currentValue = ConfMan.get(i->_key);
445 		if (currentValue != i->_value) {
446 			ConfMan.set(i->_key, i->_value, ConfMan.kTransientDomain);
447 			debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=different", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
448 		} else {
449 			debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=equal", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
450 		}
451 	}
452 	removeDifferentEntriesInDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
453 	removeDifferentEntriesInDomain(ConfMan.getActiveDomain());
454 }
455 
removeDifferentEntriesInDomain(Common::ConfigManager::Domain * domain)456 void EventRecorder::removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain) {
457 	for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
458 		if (_playbackFile->getHeader().settingsRecords.find(entry->_key) == _playbackFile->getHeader().settingsRecords.end()) {
459 			debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" checksettings:key=%s storedvalue=%s currentvalue="" result=different", entry->_key.c_str(), entry->_value.c_str());
460 			domain->erase(entry->_key);
461 		}
462 	}
463 }
464 
getTimerManager()465 DefaultTimerManager *EventRecorder::getTimerManager() {
466 	return _timerManager;
467 }
468 
registerTimerManager(DefaultTimerManager * timerManager)469 void EventRecorder::registerTimerManager(DefaultTimerManager *timerManager) {
470 	_timerManager = timerManager;
471 }
472 
switchTimerManagers()473 void EventRecorder::switchTimerManagers() {
474 	delete _timerManager;
475 	if (_recordMode == kPassthrough) {
476 		_timerManager = new SdlTimerManager();
477 	} else {
478 		_timerManager = new DefaultTimerManager();
479 	}
480 }
481 
updateSubsystems()482 void EventRecorder::updateSubsystems() {
483 	if (_recordMode == kPassthrough) {
484 		return;
485 	}
486 	RecordMode oldRecordMode = _recordMode;
487 	_recordMode = kPassthrough;
488 	_fakeMixerManager->update();
489 	_recordMode = oldRecordMode;
490 }
491 
notifyEvent(const Common::Event & ev)492 bool EventRecorder::notifyEvent(const Common::Event &ev) {
493 	if ((!_initialized) && (_recordMode != kRecorderPlaybackPause)) {
494 		return false;
495 	}
496 
497 	checkForKeyCode(ev);
498 	Common::Event evt = ev;
499 	evt.mouse.x = evt.mouse.x * (g_system->getOverlayWidth() / g_system->getWidth());
500 	evt.mouse.y = evt.mouse.y * (g_system->getOverlayHeight() / g_system->getHeight());
501 	switch (_recordMode) {
502 	case kRecorderPlayback:
503 		return false;
504 	case kRecorderRecord:
505 		g_gui.processEvent(evt, _controlPanel);
506 		if (((evt.type == Common::EVENT_LBUTTONDOWN) || (evt.type == Common::EVENT_LBUTTONUP) || (evt.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
507 			return true;
508 		} else {
509 			Common::RecorderEvent e(ev);
510 			e.recordedtype = Common::kRecorderEventTypeNormal;
511 			e.time = _fakeTimer;
512 			_playbackFile->writeEvent(e);
513 			return false;
514 		}
515 	case kRecorderPlaybackPause: {
516 		Common::Event dialogEvent;
517 		if (_controlPanel->isEditDlgVisible()) {
518 			dialogEvent = ev;
519 		} else {
520 			dialogEvent = evt;
521 		}
522 		g_gui.processEvent(dialogEvent, _controlPanel->getActiveDlg());
523 		if (((dialogEvent.type == Common::EVENT_LBUTTONDOWN) || (dialogEvent.type == Common::EVENT_LBUTTONUP) || (dialogEvent.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
524 			return true;
525 		}
526 		return false;
527 	}
528 	default:
529 		return false;
530 	}
531 }
532 
setGameMd5(const ADGameDescription * gameDesc)533 void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) {
534 	for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
535 		if (fileDesc->md5 != nullptr) {
536 			_playbackFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
537 		}
538 	}
539 }
540 
processGameDescription(const ADGameDescription * desc)541 void EventRecorder::processGameDescription(const ADGameDescription *desc) {
542 	if (_recordMode == kRecorderRecord) {
543 		setGameMd5(desc);
544 	}
545 	if ((_recordMode == kRecorderPlayback) && !checkGameHash(desc)) {
546 		deinit();
547 		error("playback:action=error reason=\"\"");
548 	}
549 }
550 
deleteRecord(const Common::String & fileName)551 void EventRecorder::deleteRecord(const Common::String& fileName) {
552 	g_system->getSavefileManager()->removeSavefile(fileName);
553 }
554 
takeScreenshot()555 void EventRecorder::takeScreenshot() {
556 	if ((_fakeTimer - _lastScreenshotTime) > _screenshotPeriod) {
557 		Graphics::Surface screen;
558 		uint8 md5[16];
559 		if (grabScreenAndComputeMD5(screen, md5)) {
560 			_lastScreenshotTime = _fakeTimer;
561 			_playbackFile->saveScreenShot(screen, md5);
562 			screen.free();
563 		}
564 	}
565 }
566 
grabScreenAndComputeMD5(Graphics::Surface & screen,uint8 md5[16])567 bool EventRecorder::grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]) {
568 	if (!createScreenShot(screen)) {
569 		warning("Can't save screenshot");
570 		return false;
571 	}
572 	Common::MemoryReadStream bitmapStream((const byte*)screen.getPixels(), screen.w * screen.h * screen.format.bytesPerPixel);
573 	computeStreamMD5(bitmapStream, md5);
574 	return true;
575 }
576 
processSaveStream(const Common::String & fileName)577 Common::SeekableReadStream *EventRecorder::processSaveStream(const Common::String &fileName) {
578 	Common::InSaveFile *saveFile;
579 	switch (_recordMode) {
580 	case kRecorderPlayback:
581 		debugC(1, kDebugLevelEventRec, "playback:action=\"Process save file\" filename=%s len=%d", fileName.c_str(), _playbackFile->getHeader().saveFiles[fileName].size);
582 		return new Common::MemoryReadStream(_playbackFile->getHeader().saveFiles[fileName].buffer, _playbackFile->getHeader().saveFiles[fileName].size);
583 	case kRecorderRecord:
584 		saveFile = _realSaveManager->openForLoading(fileName);
585 		if (saveFile != nullptr) {
586 			_playbackFile->addSaveFile(fileName, saveFile);
587 			saveFile->seek(0);
588 		}
589 		return saveFile;
590 	default:
591 		return nullptr;
592 	}
593 }
594 
getSaveManager(Common::SaveFileManager * realSaveManager)595 Common::SaveFileManager *EventRecorder::getSaveManager(Common::SaveFileManager *realSaveManager) {
596 	_realSaveManager = realSaveManager;
597 	if (_recordMode != kPassthrough) {
598 		return &_fakeSaveManager;
599 	} else {
600 		return realSaveManager;
601 	}
602 }
603 
preDrawOverlayGui()604 void EventRecorder::preDrawOverlayGui() {
605 	if ((_initialized) || (_needRedraw)) {
606 		RecordMode oldMode = _recordMode;
607 		_recordMode = kPassthrough;
608 		g_system->showOverlay();
609 		g_gui.checkScreenChange();
610 		g_gui.theme()->clearAll();
611 		g_gui.theme()->drawToBackbuffer();
612 		_controlPanel->drawDialog(kDrawLayerBackground);
613 		g_gui.theme()->drawToScreen();
614 		g_gui.theme()->copyBackBufferToScreen();
615 		_controlPanel->drawDialog(kDrawLayerForeground);
616 		g_gui.theme()->updateScreen();
617 		_recordMode = oldMode;
618 	}
619 }
620 
postDrawOverlayGui()621 void EventRecorder::postDrawOverlayGui() {
622 	if ((_initialized) || (_needRedraw)) {
623 		RecordMode oldMode = _recordMode;
624 		_recordMode = kPassthrough;
625 	    g_system->hideOverlay();
626 		_recordMode = oldMode;
627 	}
628 }
629 
listSaveFiles(const Common::String & pattern)630 Common::StringArray EventRecorder::listSaveFiles(const Common::String &pattern) {
631 	if (_recordMode == kRecorderPlayback) {
632 		Common::StringArray result;
633 		for (Common::HashMap<Common::String, Common::PlaybackFile::SaveFileBuffer>::iterator  i = _playbackFile->getHeader().saveFiles.begin(); i != _playbackFile->getHeader().saveFiles.end(); ++i) {
634 			if (i->_key.matchString(pattern, false, "/")) {
635 				result.push_back(i->_key);
636 			}
637 		}
638 		return result;
639 	} else {
640 		return _realSaveManager->listSavefiles(pattern);
641 	}
642 }
643 
setFileHeader()644 void EventRecorder::setFileHeader() {
645 	if (_recordMode != kRecorderRecord) {
646 		return;
647 	}
648 	TimeDate t;
649 	QualifiedGameDescriptor desc = EngineMan.findTarget(ConfMan.getActiveDomainName());
650 	g_system->getTimeAndDate(t);
651 	if (_author.empty()) {
652 		setAuthor("Unknown Author");
653 	}
654 	if (_name.empty()) {
655 		g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year) + desc.description);
656 	}
657 	_playbackFile->getHeader().author = _author;
658 	_playbackFile->getHeader().notes = _desc;
659 	_playbackFile->getHeader().name = _name;
660 }
661 
getSurface(int width,int height)662 SDL_Surface *EventRecorder::getSurface(int width, int height) {
663 	// Create a RGB565 surface of the requested dimensions.
664 	return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 16, 0xF800, 0x07E0, 0x001F, 0x0000);
665 }
666 
switchMode()667 bool EventRecorder::switchMode() {
668 	const Plugin *plugin = EngineMan.findPlugin(ConfMan.get("engineid"));
669 	bool metaInfoSupport = plugin->get<MetaEngine>().hasFeature(MetaEngine::kSavesSupportMetaInfo);
670 	bool featuresSupport = metaInfoSupport &&
671 						  g_engine->canSaveGameStateCurrently() &&
672 						  plugin->get<MetaEngine>().hasFeature(MetaEngine::kSupportsListSaves) &&
673 						  plugin->get<MetaEngine>().hasFeature(MetaEngine::kSupportsDeleteSave);
674 	if (!featuresSupport) {
675 		return false;
676 	}
677 
678 	const Common::String target = ConfMan.getActiveDomainName();
679 	SaveStateList saveList = plugin->get<MetaEngine>().listSaves(target.c_str());
680 
681 	int emptySlot = 1;
682 	for (SaveStateList::const_iterator x = saveList.begin(); x != saveList.end(); ++x) {
683 		int saveSlot = x->getSaveSlot();
684 		if (saveSlot == 0) {
685 			continue;
686 		}
687 		if (emptySlot != saveSlot) {
688 			break;
689 		}
690 		emptySlot++;
691 	}
692 	Common::String saveName;
693 	if (emptySlot >= 0) {
694 		saveName = Common::String::format("Save %d", emptySlot + 1);
695 		Common::Error status = g_engine->saveGameState(emptySlot, saveName);
696 		if (status.getCode() == Common::kNoError) {
697 			Common::Event eventReturnToLauncher;
698 			eventReturnToLauncher.type = Common::EVENT_RETURN_TO_LAUNCHER;
699 			g_system->getEventManager()->pushEvent(eventReturnToLauncher);
700 		}
701 	}
702 	ConfMan.set("record_mode", "", Common::ConfigManager::kTransientDomain);
703 	ConfMan.setInt("save_slot", emptySlot, Common::ConfigManager::kTransientDomain);
704 	_needcontinueGame = true;
705 	return true;
706 }
707 
checkForContinueGame()708 bool EventRecorder::checkForContinueGame() {
709 	bool result = _needcontinueGame;
710 	_needcontinueGame = false;
711 	return result;
712 }
713 
deleteTemporarySave()714 void EventRecorder::deleteTemporarySave() {
715 	if (_temporarySlot == -1) return;
716 	const Plugin *plugin = EngineMan.findPlugin(ConfMan.get("engineid"));
717 	const Common::String target = ConfMan.getActiveDomainName();
718 	 plugin->get<MetaEngine>().removeSaveState(target.c_str(), _temporarySlot);
719 	_temporarySlot = -1;
720 }
721 
722 } // End of namespace GUI
723 
724 #endif // ENABLE_EVENTRECORDER
725