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 #include "common/system.h"
24 #include "gui/EventRecorder.h"
25 #include "common/md5.h"
26 #include "common/recorderfile.h"
27 #include "common/savefile.h"
28 #include "common/bufferedstream.h"
29 #include "graphics/thumbnail.h"
30 #include "graphics/surface.h"
31 #include "graphics/scaler.h"
32
33 #define RECORD_VERSION 1
34
35 namespace Common {
36
PlaybackFile()37 PlaybackFile::PlaybackFile() : _tmpRecordFile(_tmpBuffer, kRecordBuffSize), _tmpPlaybackFile(_tmpBuffer, kRecordBuffSize) {
38 _readStream = NULL;
39 _writeStream = NULL;
40 _screenshotsFile = NULL;
41 _mode = kClosed;
42
43 _recordFile = 0;
44 _headerDumped = false;
45 _recordCount = 0;
46 _eventsSize = 0;
47 memset(_tmpBuffer, 1, kRecordBuffSize);
48
49 _playbackParseState = kFileStateCheckFormat;
50 }
51
~PlaybackFile()52 PlaybackFile::~PlaybackFile() {
53 close();
54 }
55
openWrite(const String & fileName)56 bool PlaybackFile::openWrite(const String &fileName) {
57 close();
58 _header.fileName = fileName;
59 _writeStream = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(fileName), 128 * 1024);
60 _headerDumped = false;
61 _recordCount = 0;
62 if (_writeStream == NULL) {
63 return false;
64 }
65 _mode = kWrite;
66 return true;
67 }
68
openRead(const String & fileName)69 bool PlaybackFile::openRead(const String &fileName) {
70 close();
71 _header.fileName = fileName;
72 _eventsSize = 0;
73 _tmpPlaybackFile.seek(0);
74 _readStream = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(fileName), 128 * 1024, DisposeAfterUse::YES);
75 if (_readStream == NULL) {
76 debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"file %s not found\"", fileName.c_str());
77 return false;
78 }
79 if (!parseHeader()) {
80 debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"header parsing failed\"");
81 return false;
82 }
83 _screenshotsFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving("screenshots.bin"), 128 * 1024);
84 debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=success");
85 _mode = kRead;
86 return true;
87 }
88
close()89 void PlaybackFile::close() {
90 delete _readStream;
91 _readStream = NULL;
92 if (_writeStream != NULL) {
93 dumpRecordsToFile();
94 _writeStream->finalize();
95 delete _writeStream;
96 _writeStream = NULL;
97 updateHeader();
98 }
99 if (_screenshotsFile != NULL) {
100 _screenshotsFile->finalize();
101 delete _screenshotsFile;
102 _screenshotsFile = NULL;
103 }
104 for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
105 free(i->_value.buffer);
106 }
107 _header.saveFiles.clear();
108 _mode = kClosed;
109 }
110
parseHeader()111 bool PlaybackFile::parseHeader() {
112 PlaybackFileHeader result;
113 ChunkHeader nextChunk;
114 _playbackParseState = kFileStateCheckFormat;
115 if (!readChunkHeader(nextChunk)) {
116 _playbackParseState = kFileStateError;
117 return false;
118 }
119 while ((_playbackParseState != kFileStateDone) && (_playbackParseState != kFileStateError)) {
120 if (processChunk(nextChunk)) {
121 if (!readChunkHeader(nextChunk)) {
122 warning("Error in header parsing");
123 _playbackParseState = kFileStateError;
124 }
125 }
126 }
127 return _playbackParseState == kFileStateDone;
128 }
129
checkPlaybackFileVersion()130 bool PlaybackFile::checkPlaybackFileVersion() {
131 uint32 version;
132 version = _readStream->readUint32LE();
133 if (version != RECORD_VERSION) {
134 warning("Incorrect playback file version. Expected version %d, but got %d.", RECORD_VERSION, version);
135 return false;
136 }
137 return true;
138 }
139
140
readString(int len)141 String PlaybackFile::readString(int len) {
142 String result;
143 char buf[50];
144 int readSize = 49;
145 while (len > 0) {
146 if (len <= 49) {
147 readSize = len;
148 }
149 _readStream->read(buf, readSize);
150 buf[readSize] = 0;
151 result += buf;
152 len -= readSize;
153 }
154 return result;
155 }
156
readChunkHeader(PlaybackFile::ChunkHeader & nextChunk)157 bool PlaybackFile::readChunkHeader(PlaybackFile::ChunkHeader &nextChunk) {
158 nextChunk.id = (FileTag)_readStream->readUint32LE();
159 nextChunk.len = _readStream->readUint32LE();
160 return !_readStream->err() && !_readStream->eos();
161 }
162
processChunk(ChunkHeader & nextChunk)163 bool PlaybackFile::processChunk(ChunkHeader &nextChunk) {
164 switch (_playbackParseState) {
165 case kFileStateCheckFormat:
166 if (nextChunk.id == kFormatIdTag) {
167 _playbackParseState = kFileStateCheckVersion;
168 } else {
169 warning("Unknown playback file signature");
170 _playbackParseState = kFileStateError;
171 }
172 break;
173 case kFileStateCheckVersion:
174 if ((nextChunk.id == kVersionTag) && checkPlaybackFileVersion()) {
175 _playbackParseState = kFileStateSelectSection;
176 } else {
177 _playbackParseState = kFileStateError;
178 }
179 break;
180 case kFileStateSelectSection:
181 switch (nextChunk.id) {
182 case kHeaderSectionTag:
183 _playbackParseState = kFileStateProcessHeader;
184 break;
185 case kHashSectionTag:
186 _playbackParseState = kFileStateProcessHash;
187 break;
188 case kRandomSectionTag:
189 _playbackParseState = kFileStateProcessRandom;
190 break;
191 case kEventTag:
192 case kScreenShotTag:
193 _readStream->seek(-8, SEEK_CUR);
194 _playbackParseState = kFileStateDone;
195 return false;
196 case kSaveTag:
197 _playbackParseState = kFileStateProcessSave;
198 break;
199 case kSettingsSectionTag:
200 _playbackParseState = kFileStateProcessSettings;
201 warning("Loading record header");
202 break;
203 default:
204 _readStream->skip(nextChunk.len);
205 break;
206 }
207 break;
208 case kFileStateProcessSave:
209 if (nextChunk.id == kSaveRecordTag) {
210 readSaveRecord();
211 } else {
212 _playbackParseState = kFileStateSelectSection;
213 return false;
214 }
215 break;
216 case kFileStateProcessHeader:
217 switch (nextChunk.id) {
218 case kAuthorTag:
219 _header.author = readString(nextChunk.len);
220 break;
221 case kCommentsTag:
222 _header.notes = readString(nextChunk.len);
223 break;
224 case kNameTag:
225 _header.name = readString(nextChunk.len);
226 break;
227 default:
228 _playbackParseState = kFileStateSelectSection;
229 return false;
230 }
231 break;
232 case kFileStateProcessHash:
233 if (nextChunk.id == kHashRecordTag) {
234 readHashMap(nextChunk);
235 } else {
236 _playbackParseState = kFileStateSelectSection;
237 return false;
238 }
239 break;
240 case kFileStateProcessRandom:
241 if (nextChunk.id == kRandomRecordTag) {
242 processRndSeedRecord(nextChunk);
243 } else {
244 _playbackParseState = kFileStateSelectSection;
245 return false;
246 }
247 break;
248 case kFileStateProcessSettings:
249 if (nextChunk.id == kSettingsRecordTag) {
250 if (!processSettingsRecord()) {
251 _playbackParseState = kFileStateError;
252 return false;
253 }
254 } else {
255 _playbackParseState = kFileStateSelectSection;
256 return false;
257 }
258 break;
259 default:
260 return false;
261 }
262 return true;
263 }
264
returnToChunkHeader()265 void PlaybackFile::returnToChunkHeader() {
266 _readStream->seek(-8, SEEK_CUR);
267 }
268
readHashMap(ChunkHeader chunk)269 void PlaybackFile::readHashMap(ChunkHeader chunk) {
270 String hashName = readString(chunk.len - 32);
271 String hashMd5 = readString(32);
272 _header.hashRecords[hashName] = hashMd5;
273 }
274
processRndSeedRecord(ChunkHeader chunk)275 void PlaybackFile::processRndSeedRecord(ChunkHeader chunk) {
276 String randomSourceName = readString(chunk.len - 4);
277 uint32 randomSourceSeed = _readStream->readUint32LE();
278 _header.randomSourceRecords[randomSourceName] = randomSourceSeed;
279 }
280
processSettingsRecord()281 bool PlaybackFile::processSettingsRecord() {
282 ChunkHeader keyChunk;
283 if (!readChunkHeader(keyChunk) || (keyChunk.id != kSettingsRecordKeyTag)) {
284 warning("Invalid format of settings section");
285 return false;
286 }
287 String key = readString(keyChunk.len);
288 ChunkHeader valueChunk;
289 if (!readChunkHeader(valueChunk) || (valueChunk.id != kSettingsRecordValueTag)) {
290 warning("Invalid format of settings section");
291 return false;
292 }
293 String value = readString(valueChunk.len);
294 _header.settingsRecords[key] = value;
295 return true;
296 }
297
298
readSaveRecord()299 bool PlaybackFile::readSaveRecord() {
300 ChunkHeader fileNameChunk;
301 if (!readChunkHeader(fileNameChunk) || (fileNameChunk.id != kSaveRecordNameTag)) {
302 warning("Invalid format of save section");
303 return false;
304 }
305 String fileName = readString(fileNameChunk.len);
306 ChunkHeader saveBufferChunk;
307 if (!readChunkHeader(saveBufferChunk) || (saveBufferChunk.id != kSaveRecordBufferTag)) {
308 warning("Invalid format of save section");
309 return false;
310 }
311 SaveFileBuffer buf;
312 buf.size = saveBufferChunk.len;
313 buf.buffer = (byte *)malloc(saveBufferChunk.len);
314 _readStream->read(buf.buffer, buf.size);
315 _header.saveFiles[fileName] = buf;
316 debugC(1, kDebugLevelEventRec, "playback:action=\"Load save file\" filename=%s len=%d", fileName.c_str(), buf.size);
317 return true;
318 }
319
320
321
getNextEvent()322 RecorderEvent PlaybackFile::getNextEvent() {
323 assert(_mode == kRead);
324 if (isEventsBufferEmpty()) {
325 PlaybackFile::ChunkHeader header;
326 header.id = kFormatIdTag;
327 while (header.id != kEventTag) {
328 if (!readChunkHeader(header) || _readStream->eos()) {
329 break;
330 }
331 switch (header.id) {
332 case kEventTag:
333 readEventsToBuffer(header.len);
334 break;
335 case kScreenShotTag:
336 _readStream->seek(-4, SEEK_CUR);
337 header.len = _readStream->readUint32BE();
338 _readStream->skip(header.len - 8);
339 break;
340 case kMD5Tag:
341 checkRecordedMD5();
342 break;
343 default:
344 _readStream->skip(header.len);
345 break;
346 }
347 }
348 }
349 RecorderEvent result;
350 readEvent(result);
351 return result;
352 }
353
isEventsBufferEmpty()354 bool PlaybackFile::isEventsBufferEmpty() {
355 return (uint32)_tmpPlaybackFile.pos() == _eventsSize;
356 }
357
readEvent(RecorderEvent & event)358 void PlaybackFile::readEvent(RecorderEvent& event) {
359 event.recordedtype = (RecorderEventType)_tmpPlaybackFile.readByte();
360 switch (event.recordedtype) {
361 case kRecorderEventTypeTimer:
362 event.time = _tmpPlaybackFile.readUint32LE();
363 break;
364 case kRecorderEventTypeNormal:
365 event.type = (EventType)_tmpPlaybackFile.readUint32LE();
366 switch (event.type) {
367 case EVENT_KEYDOWN:
368 case EVENT_KEYUP:
369 event.time = _tmpPlaybackFile.readUint32LE();
370 event.kbd.keycode = (KeyCode)_tmpPlaybackFile.readSint32LE();
371 event.kbd.ascii = _tmpPlaybackFile.readUint16LE();
372 event.kbd.flags = _tmpPlaybackFile.readByte();
373 break;
374 case EVENT_MOUSEMOVE:
375 case EVENT_LBUTTONDOWN:
376 case EVENT_LBUTTONUP:
377 case EVENT_RBUTTONDOWN:
378 case EVENT_RBUTTONUP:
379 case EVENT_WHEELUP:
380 case EVENT_WHEELDOWN:
381 case EVENT_MBUTTONDOWN:
382 case EVENT_MBUTTONUP:
383 event.time = _tmpPlaybackFile.readUint32LE();
384 event.mouse.x = _tmpPlaybackFile.readSint16LE();
385 event.mouse.y = _tmpPlaybackFile.readSint16LE();
386 break;
387 default:
388 event.time = _tmpPlaybackFile.readUint32LE();
389 break;
390 }
391 break;
392 }
393 event.kbdRepeat = true;
394 }
395
readEventsToBuffer(uint32 size)396 void PlaybackFile::readEventsToBuffer(uint32 size) {
397 _readStream->read(_tmpBuffer, size);
398 _tmpPlaybackFile.seek(0);
399 _eventsSize = size;
400 }
401
saveScreenShot(Graphics::Surface & screen,byte md5[16])402 void PlaybackFile::saveScreenShot(Graphics::Surface &screen, byte md5[16]) {
403 dumpRecordsToFile();
404 _writeStream->writeUint32LE(kMD5Tag);
405 _writeStream->writeUint32LE(16);
406 _writeStream->write(md5, 16);
407 Graphics::saveThumbnail(*_writeStream, screen);
408 }
409
dumpRecordsToFile()410 void PlaybackFile::dumpRecordsToFile() {
411 if (!_headerDumped) {
412 dumpHeaderToFile();
413 _headerDumped = true;
414 }
415 if (_recordCount == 0) {
416 return;
417 }
418 _writeStream->writeUint32LE(kEventTag);
419 _writeStream->writeUint32LE(_tmpRecordFile.pos());
420 _writeStream->write(_tmpBuffer, _tmpRecordFile.pos());
421 _tmpRecordFile.seek(0);
422 _recordCount = 0;
423 }
424
dumpHeaderToFile()425 void PlaybackFile::dumpHeaderToFile() {
426 _writeStream->writeUint32LE(kFormatIdTag);
427 // Specify size for first tag as NULL since we cannot calculate
428 // size of the file at time of the header dumping
429 _writeStream->writeUint32LE(0);
430 _writeStream->writeUint32LE(kVersionTag);
431 _writeStream->writeUint32LE(4);
432 _writeStream->writeUint32LE(RECORD_VERSION);
433 writeHeaderSection();
434 writeGameHash();
435 writeRandomRecords();
436 writeGameSettings();
437 writeSaveFilesSection();
438 }
439
writeHeaderSection()440 void PlaybackFile::writeHeaderSection() {
441 uint32 headerSize = 0;
442 if (!_header.author.empty()) {
443 headerSize = _header.author.size() + 8;
444 }
445 if (!_header.notes.empty()) {
446 headerSize += _header.notes.size() + 8;
447 }
448 if (!_header.name.empty()) {
449 headerSize += _header.name.size() + 8;
450 }
451 if (headerSize == 0) {
452 return;
453 }
454 _writeStream->writeUint32LE(kHeaderSectionTag);
455 _writeStream->writeUint32LE(headerSize);
456 if (!_header.author.empty()) {
457 _writeStream->writeUint32LE(kAuthorTag);
458 _writeStream->writeUint32LE(_header.author.size());
459 _writeStream->writeString(_header.author);
460 }
461 if (!_header.notes.empty()) {
462 _writeStream->writeUint32LE(kCommentsTag);
463 _writeStream->writeUint32LE(_header.notes.size());
464 _writeStream->writeString(_header.notes);
465 }
466 if (!_header.name.empty()) {
467 _writeStream->writeUint32LE(kNameTag);
468 _writeStream->writeUint32LE(_header.name.size());
469 _writeStream->writeString(_header.name);
470 }
471 }
472
writeGameHash()473 void PlaybackFile::writeGameHash() {
474 uint32 hashSectionSize = 0;
475 for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
476 hashSectionSize = hashSectionSize + i->_key.size() + i->_value.size() + 8;
477 }
478 if (_header.hashRecords.size() == 0) {
479 return;
480 }
481 _writeStream->writeUint32LE(kHashSectionTag);
482 _writeStream->writeUint32LE(hashSectionSize);
483 for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
484 _writeStream->writeUint32LE(kHashRecordTag);
485 _writeStream->writeUint32LE(i->_key.size() + i->_value.size());
486 _writeStream->writeString(i->_key);
487 _writeStream->writeString(i->_value);
488 }
489 }
490
writeRandomRecords()491 void PlaybackFile::writeRandomRecords() {
492 uint32 randomSectionSize = 0;
493 for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
494 randomSectionSize = randomSectionSize + i->_key.size() + 12;
495 }
496 if (_header.randomSourceRecords.size() == 0) {
497 return;
498 }
499 _writeStream->writeUint32LE(kRandomSectionTag);
500 _writeStream->writeUint32LE(randomSectionSize);
501 for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
502 _writeStream->writeUint32LE(kRandomRecordTag);
503 _writeStream->writeUint32LE(i->_key.size() + 4);
504 _writeStream->writeString(i->_key);
505 _writeStream->writeUint32LE(i->_value);
506 }
507 }
508
writeEvent(const RecorderEvent & event)509 void PlaybackFile::writeEvent(const RecorderEvent &event) {
510 assert(_mode == kWrite);
511 _recordCount++;
512 _tmpRecordFile.writeByte(event.recordedtype);
513 switch (event.recordedtype) {
514 case kRecorderEventTypeTimer:
515 _tmpRecordFile.writeUint32LE(event.time);
516 break;
517 case kRecorderEventTypeNormal:
518 _tmpRecordFile.writeUint32LE((uint32)event.type);
519 switch(event.type) {
520 case EVENT_KEYDOWN:
521 case EVENT_KEYUP:
522 _tmpRecordFile.writeUint32LE(event.time);
523 _tmpRecordFile.writeSint32LE(event.kbd.keycode);
524 _tmpRecordFile.writeUint16LE(event.kbd.ascii);
525 _tmpRecordFile.writeByte(event.kbd.flags);
526 break;
527 case EVENT_MOUSEMOVE:
528 case EVENT_LBUTTONDOWN:
529 case EVENT_LBUTTONUP:
530 case EVENT_RBUTTONDOWN:
531 case EVENT_RBUTTONUP:
532 case EVENT_WHEELUP:
533 case EVENT_WHEELDOWN:
534 case EVENT_MBUTTONDOWN:
535 case EVENT_MBUTTONUP:
536 _tmpRecordFile.writeUint32LE(event.time);
537 _tmpRecordFile.writeSint16LE(event.mouse.x);
538 _tmpRecordFile.writeSint16LE(event.mouse.y);
539 break;
540 default:
541 _tmpRecordFile.writeUint32LE(event.time);
542 break;
543 }
544 break;
545 }
546 if (_recordCount == kMaxBufferedRecords) {
547 dumpRecordsToFile();
548 }
549 }
550
writeGameSettings()551 void PlaybackFile::writeGameSettings() {
552 _writeStream->writeUint32LE(kSettingsSectionTag);
553 uint32 settingsSectionSize = 0;
554 for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
555 settingsSectionSize += i->_key.size() + i->_value.size() + 24;
556 }
557 _writeStream->writeUint32LE(settingsSectionSize);
558 for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
559 _writeStream->writeUint32LE(kSettingsRecordTag);
560 _writeStream->writeUint32LE(i->_key.size() + i->_value.size() + 16);
561 _writeStream->writeUint32LE(kSettingsRecordKeyTag);
562 _writeStream->writeUint32LE(i->_key.size());
563 _writeStream->writeString(i->_key);
564 _writeStream->writeUint32LE(kSettingsRecordValueTag);
565 _writeStream->writeUint32LE(i->_value.size());
566 _writeStream->writeString(i->_value);
567 }
568 }
569
getScreensCount()570 int PlaybackFile::getScreensCount() {
571 if (_mode != kRead) {
572 return 0;
573 }
574 _readStream->seek(0);
575 int result = 0;
576 while (skipToNextScreenshot()) {
577 uint32 size = _readStream->readUint32BE();
578 _readStream->skip(size - 8);
579 ++result;
580 }
581 return result;
582 }
583
skipToNextScreenshot()584 bool PlaybackFile::skipToNextScreenshot() {
585 while (true) {
586 FileTag id = (FileTag)_readStream->readUint32LE();
587 if (_readStream->eos()) {
588 break;
589 }
590 if (id == kScreenShotTag) {
591 return true;
592 }
593 else {
594 uint32 size = _readStream->readUint32LE();
595 _readStream->skip(size);
596 }
597 }
598 return false;
599 }
600
getScreenShot(int number)601 Graphics::Surface *PlaybackFile::getScreenShot(int number) {
602 if (_mode != kRead) {
603 return NULL;
604 }
605 _readStream->seek(0);
606 int screenCount = 1;
607 while (skipToNextScreenshot()) {
608 if (screenCount == number) {
609 screenCount++;
610 _readStream->seek(-4, SEEK_CUR);
611 Graphics::Surface *thumbnail;
612 return Graphics::loadThumbnail(*_readStream, thumbnail) ? thumbnail : NULL;
613 } else {
614 uint32 size = _readStream->readUint32BE();
615 _readStream->skip(size - 8);
616 screenCount++;
617 }
618 }
619 return NULL;
620 }
621
updateHeader()622 void PlaybackFile::updateHeader() {
623 if (_mode == kWrite) {
624 _readStream = g_system->getSavefileManager()->openForLoading(_header.fileName);
625 }
626 _readStream->seek(0);
627 skipHeader();
628 String tmpFilename = "_" + _header.fileName;
629 _writeStream = g_system->getSavefileManager()->openForSaving(tmpFilename);
630 dumpHeaderToFile();
631 uint32 readedSize = 0;
632 do {
633 readedSize = _readStream->read(_tmpBuffer, kRecordBuffSize);
634 _writeStream->write(_tmpBuffer, readedSize);
635 } while (readedSize != 0);
636 delete _writeStream;
637 _writeStream = NULL;
638 delete _readStream;
639 _readStream = NULL;
640 g_system->getSavefileManager()->removeSavefile(_header.fileName);
641 g_system->getSavefileManager()->renameSavefile(tmpFilename, _header.fileName);
642 if (_mode == kRead) {
643 openRead(_header.fileName);
644 }
645 }
646
skipHeader()647 void PlaybackFile::skipHeader() {
648 while (true) {
649 uint32 id = _readStream->readUint32LE();
650 if (_readStream->eos()) {
651 break;
652 }
653 if ((id == kScreenShotTag) || (id == kEventTag) || (id == kMD5Tag)) {
654 _readStream->seek(-4, SEEK_CUR);
655 return;
656 }
657 else {
658 uint32 size = _readStream->readUint32LE();
659 _readStream->skip(size);
660 }
661 }
662 }
663
addSaveFile(const String & fileName,InSaveFile * saveStream)664 void PlaybackFile::addSaveFile(const String &fileName, InSaveFile *saveStream) {
665 uint oldPos = saveStream->pos();
666 saveStream->seek(0);
667 _header.saveFiles[fileName].buffer = (byte *)malloc(saveStream->size());
668 _header.saveFiles[fileName].size = saveStream->size();
669 saveStream->read(_header.saveFiles[fileName].buffer, saveStream->size());
670 saveStream->seek(oldPos);
671 }
672
writeSaveFilesSection()673 void PlaybackFile::writeSaveFilesSection() {
674 uint size = 0;
675 for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
676 size += i->_value.size + i->_key.size() + 24;
677 }
678 if (size == 0) {
679 return;
680 }
681 _writeStream->writeSint32LE(kSaveTag);
682 _writeStream->writeSint32LE(size);
683 for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
684 _writeStream->writeSint32LE(kSaveRecordTag);
685 _writeStream->writeSint32LE(i->_key.size() + i->_value.size + 16);
686 _writeStream->writeSint32LE(kSaveRecordNameTag);
687 _writeStream->writeSint32LE(i->_key.size());
688 _writeStream->writeString(i->_key);
689 _writeStream->writeSint32LE(kSaveRecordBufferTag);
690 _writeStream->writeSint32LE(i->_value.size);
691 _writeStream->write(i->_value.buffer, i->_value.size);
692 }
693 }
694
695
checkRecordedMD5()696 void PlaybackFile::checkRecordedMD5() {
697 uint8 currentMD5[16];
698 uint8 savedMD5[16];
699 Graphics::Surface screen;
700 _readStream->read(savedMD5, 16);
701 if (!g_eventRec.grabScreenAndComputeMD5(screen, currentMD5)) {
702 return;
703 }
704 uint32 seconds = g_system->getMillis(true) / 1000;
705 String screenTime = String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60);
706 if (memcmp(savedMD5, currentMD5, 16) != 0) {
707 debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = fail", screenTime.c_str());
708 warning("Recorded and current screenshots are different");
709 } else {
710 debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = success", screenTime.c_str());
711 }
712 Graphics::saveThumbnail(*_screenshotsFile, screen);
713 screen.free();
714 }
715
716
717 }
718