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