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