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