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