diff options
Diffstat (limited to 'common')
| -rw-r--r-- | common/EventDispatcher.cpp | 14 | ||||
| -rw-r--r-- | common/EventRecorder.cpp | 428 | ||||
| -rw-r--r-- | common/EventRecorder.h | 110 | ||||
| -rw-r--r-- | common/debug.h | 4 | ||||
| -rw-r--r-- | common/events.h | 12 | ||||
| -rw-r--r-- | common/macresman.cpp | 98 | ||||
| -rw-r--r-- | common/macresman.h | 25 | ||||
| -rw-r--r-- | common/memstream.h | 37 | ||||
| -rw-r--r-- | common/module.mk | 6 | ||||
| -rw-r--r-- | common/random.cpp | 13 | ||||
| -rw-r--r-- | common/recorderfile.cpp | 714 | ||||
| -rw-r--r-- | common/recorderfile.h | 180 | ||||
| -rw-r--r-- | common/str.cpp | 19 | ||||
| -rw-r--r-- | common/str.h | 5 | ||||
| -rw-r--r-- | common/system.cpp | 17 | ||||
| -rw-r--r-- | common/system.h | 18 | ||||
| -rw-r--r-- | common/winexe_ne.cpp | 2 | 
17 files changed, 1079 insertions, 623 deletions
diff --git a/common/EventDispatcher.cpp b/common/EventDispatcher.cpp index 012a2dfce5..e60c1aa7ff 100644 --- a/common/EventDispatcher.cpp +++ b/common/EventDispatcher.cpp @@ -24,7 +24,7 @@  namespace Common { -EventDispatcher::EventDispatcher() : _mapper(0) { +EventDispatcher::EventDispatcher() : _autoFreeMapper(false), _mapper(0) {  }  EventDispatcher::~EventDispatcher() { @@ -38,7 +38,9 @@ EventDispatcher::~EventDispatcher() {  			delete i->observer;  	} -	delete _mapper; +	if (_autoFreeMapper) { +		delete _mapper; +	}  	_mapper = 0;  } @@ -68,11 +70,15 @@ void EventDispatcher::dispatch() {  	}  } -void EventDispatcher::registerMapper(EventMapper *mapper) { -	delete _mapper; +void EventDispatcher::registerMapper(EventMapper *mapper, bool autoFree) { +	if (_autoFreeMapper) { +		delete _mapper; +	}  	_mapper = mapper; +	_autoFreeMapper = autoFree;  } +  void EventDispatcher::registerSource(EventSource *source, bool autoFree) {  	SourceEntry newEntry; diff --git a/common/EventRecorder.cpp b/common/EventRecorder.cpp deleted file mode 100644 index 5e24f128c3..0000000000 --- a/common/EventRecorder.cpp +++ /dev/null @@ -1,428 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/EventRecorder.h" - -#include "common/bufferedstream.h" -#include "common/config-manager.h" -#include "common/random.h" -#include "common/savefile.h" -#include "common/textconsole.h" - -namespace Common { - -DECLARE_SINGLETON(EventRecorder); - -#define RECORD_SIGNATURE 0x54455354 -#define RECORD_VERSION 1 - -uint32 readTime(ReadStream *inFile) { -	uint32 d = inFile->readByte(); -	if (d == 0xff) { -		d = inFile->readUint32LE(); -	} - -	return d; -} - -void writeTime(WriteStream *outFile, uint32 d) { -		//Simple RLE compression -	if (d >= 0xff) { -		outFile->writeByte(0xff); -		outFile->writeUint32LE(d); -	} else { -		outFile->writeByte(d); -	} -} - -void readRecord(SeekableReadStream *inFile, uint32 &diff, Event &event, uint32 &millis) { -	millis = readTime(inFile); - -	diff = inFile->readUint32LE(); - -	event.type = (EventType)inFile->readUint32LE(); - -	switch (event.type) { -	case EVENT_KEYDOWN: -	case EVENT_KEYUP: -		event.kbd.keycode = (KeyCode)inFile->readSint32LE(); -		event.kbd.ascii = inFile->readUint16LE(); -		event.kbd.flags = inFile->readByte(); -		break; -	case EVENT_MOUSEMOVE: -	case EVENT_LBUTTONDOWN: -	case EVENT_LBUTTONUP: -	case EVENT_RBUTTONDOWN: -	case EVENT_RBUTTONUP: -	case EVENT_WHEELUP: -	case EVENT_WHEELDOWN: -	case EVENT_MBUTTONDOWN: -	case EVENT_MBUTTONUP: -		event.mouse.x = inFile->readSint16LE(); -		event.mouse.y = inFile->readSint16LE(); -		break; -	default: -		break; -	} -} - -void writeRecord(WriteStream *outFile, uint32 diff, const Event &event, uint32 millis) { -	writeTime(outFile, millis); - -	outFile->writeUint32LE(diff); - -	outFile->writeUint32LE((uint32)event.type); - -	switch (event.type) { -	case EVENT_KEYDOWN: -	case EVENT_KEYUP: -		outFile->writeSint32LE(event.kbd.keycode); -		outFile->writeUint16LE(event.kbd.ascii); -		outFile->writeByte(event.kbd.flags); -		break; -	case EVENT_MOUSEMOVE: -	case EVENT_LBUTTONDOWN: -	case EVENT_LBUTTONUP: -	case EVENT_RBUTTONDOWN: -	case EVENT_RBUTTONUP: -	case EVENT_WHEELUP: -	case EVENT_WHEELDOWN: -	case EVENT_MBUTTONDOWN: -	case EVENT_MBUTTONUP: -		outFile->writeSint16LE(event.mouse.x); -		outFile->writeSint16LE(event.mouse.y); -		break; -	default: -		break; -	} -} - -EventRecorder::EventRecorder() { -	_recordFile = NULL; -	_recordTimeFile = NULL; -	_playbackFile = NULL; -	_playbackTimeFile = NULL; -	_timeMutex = g_system->createMutex(); -	_recorderMutex = g_system->createMutex(); - -	_eventCount = 0; -	_lastEventCount = 0; -	_lastMillis = 0; -	_lastEventMillis = 0; - -	_recordMode = kPassthrough; -} - -EventRecorder::~EventRecorder() { -	deinit(); - -	g_system->deleteMutex(_timeMutex); -	g_system->deleteMutex(_recorderMutex); -} - -void EventRecorder::init() { -	String recordModeString = ConfMan.get("record_mode"); -	if (recordModeString.compareToIgnoreCase("record") == 0) { -		_recordMode = kRecorderRecord; - -		debug(3, "EventRecorder: record"); -	} else { -		if (recordModeString.compareToIgnoreCase("playback") == 0) { -			_recordMode = kRecorderPlayback; -			debug(3, "EventRecorder: playback"); -		} else { -			_recordMode = kPassthrough; -			debug(3, "EventRecorder: passthrough"); -		} -	} - -	_recordFileName = ConfMan.get("record_file_name"); -	if (_recordFileName.empty()) { -		_recordFileName = "record.bin"; -	} -	_recordTempFileName = ConfMan.get("record_temp_file_name"); -	if (_recordTempFileName.empty()) { -		_recordTempFileName = "record.tmp"; -	} -	_recordTimeFileName = ConfMan.get("record_time_file_name"); -	if (_recordTimeFileName.empty()) { -		_recordTimeFileName = "record.time"; -	} - -	// recorder stuff -	if (_recordMode == kRecorderRecord) { -		_recordCount = 0; -		_recordTimeCount = 0; -		_recordFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTempFileName), 128 * 1024); -		_recordTimeFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTimeFileName), 128 * 1024); -		_recordSubtitles = ConfMan.getBool("subtitles"); -	} - -	uint32 sign; -	uint32 randomSourceCount; -	if (_recordMode == kRecorderPlayback) { -		_playbackCount = 0; -		_playbackTimeCount = 0; -		_playbackFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordFileName), 128 * 1024, DisposeAfterUse::YES); -		_playbackTimeFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordTimeFileName), 128 * 1024, DisposeAfterUse::YES); - -		if (!_playbackFile) { -			warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str()); -			_recordMode = kPassthrough; -		} - -		if (!_playbackTimeFile) { -			warning("Cannot open playback time file %s. Playback was switched off", _recordTimeFileName.c_str()); -			_recordMode = kPassthrough; -		} -	} - -	if (_recordMode == kRecorderPlayback) { -		sign = _playbackFile->readUint32LE(); -		if (sign != RECORD_SIGNATURE) { -			error("Unknown record file signature"); -		} - -		_playbackFile->readUint32LE(); // version - -		// conf vars -		ConfMan.setBool("subtitles", _playbackFile->readByte() != 0); - -		_recordCount = _playbackFile->readUint32LE(); -		_recordTimeCount = _playbackFile->readUint32LE(); - -		randomSourceCount = _playbackFile->readUint32LE(); -		for (uint i = 0; i < randomSourceCount; ++i) { -			RandomSourceRecord rec; -			rec.name = ""; -			uint32 sLen = _playbackFile->readUint32LE(); -			for (uint j = 0; j < sLen; ++j) { -				char c = _playbackFile->readSByte(); -				rec.name += c; -			} -			rec.seed = _playbackFile->readUint32LE(); -			_randomSourceRecords.push_back(rec); -		} - -		_hasPlaybackEvent = false; -	} - -	g_system->getEventManager()->getEventDispatcher()->registerSource(this, false); -	g_system->getEventManager()->getEventDispatcher()->registerObserver(this, EventManager::kEventRecorderPriority, false, true); -} - -void EventRecorder::deinit() { -	debug(3, "EventRecorder: deinit"); - -	g_system->getEventManager()->getEventDispatcher()->unregisterSource(this); -	g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this); - -	g_system->lockMutex(_timeMutex); -	g_system->lockMutex(_recorderMutex); -	_recordMode = kPassthrough; -	g_system->unlockMutex(_timeMutex); -	g_system->unlockMutex(_recorderMutex); - -	delete _playbackFile; -	delete _playbackTimeFile; - -	if (_recordFile != NULL) { -		_recordFile->finalize(); -		delete _recordFile; -		_recordTimeFile->finalize(); -		delete _recordTimeFile; - -		_playbackFile = g_system->getSavefileManager()->openForLoading(_recordTempFileName); - -		assert(_playbackFile); - -		_recordFile = g_system->getSavefileManager()->openForSaving(_recordFileName); -		_recordFile->writeUint32LE(RECORD_SIGNATURE); -		_recordFile->writeUint32LE(RECORD_VERSION); - -		// conf vars -		_recordFile->writeByte(_recordSubtitles ? 1 : 0); - -		_recordFile->writeUint32LE(_recordCount); -		_recordFile->writeUint32LE(_recordTimeCount); - -		_recordFile->writeUint32LE(_randomSourceRecords.size()); -		for (uint i = 0; i < _randomSourceRecords.size(); ++i) { -			_recordFile->writeUint32LE(_randomSourceRecords[i].name.size()); -			_recordFile->writeString(_randomSourceRecords[i].name); -			_recordFile->writeUint32LE(_randomSourceRecords[i].seed); -		} - -		for (uint i = 0; i < _recordCount; ++i) { -			uint32 tempDiff; -			Event tempEvent; -			uint32 millis; -			readRecord(_playbackFile, tempDiff, tempEvent, millis); -			writeRecord(_recordFile, tempDiff, tempEvent, millis); -		} - -		_recordFile->finalize(); -		delete _recordFile; -		delete _playbackFile; - -		//TODO: remove recordTempFileName'ed file -	} -} - -void EventRecorder::registerRandomSource(RandomSource &rnd, const String &name) { -	if (_recordMode == kRecorderRecord) { -		RandomSourceRecord rec; -		rec.name = name; -		rec.seed = rnd.getSeed(); -		_randomSourceRecords.push_back(rec); -	} - -	if (_recordMode == kRecorderPlayback) { -		for (uint i = 0; i < _randomSourceRecords.size(); ++i) { -			if (_randomSourceRecords[i].name == name) { -				rnd.setSeed(_randomSourceRecords[i].seed); -				_randomSourceRecords.remove_at(i); -				break; -			} -		} -	} -} - -void EventRecorder::processMillis(uint32 &millis) { -	uint32 d; -	if (_recordMode == kPassthrough) { -		return; -	} - -	g_system->lockMutex(_timeMutex); -	if (_recordMode == kRecorderRecord) { -		d = millis - _lastMillis; -		writeTime(_recordTimeFile, d); - -		_recordTimeCount++; -	} - -	if (_recordMode == kRecorderPlayback) { -		if (_recordTimeCount > _playbackTimeCount) { -			d = readTime(_playbackTimeFile); - -			while ((_lastMillis + d > millis) && (_lastMillis + d - millis > 50)) { -				_recordMode = kPassthrough; -				g_system->delayMillis(50); -				millis = g_system->getMillis(); -				_recordMode = kRecorderPlayback; -			} - -			millis = _lastMillis + d; -			_playbackTimeCount++; -		} -	} - -	_lastMillis = millis; -	g_system->unlockMutex(_timeMutex); -} - -bool EventRecorder::processDelayMillis(uint &msecs) { -	if (_recordMode == kRecorderPlayback) { -		_recordMode = kPassthrough; - -		uint32 millis = g_system->getMillis(); - -		_recordMode = kRecorderPlayback; - -		if (_lastMillis > millis) { -			// Skip delay if we're getting late -			return true; -		} -	} - -	return false; -} - -bool EventRecorder::notifyEvent(const Event &ev) { -	if (_recordMode != kRecorderRecord) -		return false; - -	StackLock lock(_recorderMutex); -	++_eventCount; - -	writeRecord(_recordFile, _eventCount - _lastEventCount, ev, _lastMillis - _lastEventMillis); - -	_recordCount++; -	_lastEventCount = _eventCount; -	_lastEventMillis = _lastMillis; - -	return false; -} - -bool EventRecorder::notifyPoll() { -	if (_recordMode != kRecorderRecord) -		return false; - -	++_eventCount; - -	return false; -} - -bool EventRecorder::pollEvent(Event &ev) { -	uint32 millis; - -	if (_recordMode != kRecorderPlayback) -		return false; - -	StackLock lock(_recorderMutex); -	++_eventCount; - -	if (!_hasPlaybackEvent) { -		if (_recordCount > _playbackCount) { -			readRecord(_playbackFile, const_cast<uint32&>(_playbackDiff), _playbackEvent, millis); -			_playbackCount++; -			_hasPlaybackEvent = true; -		} -	} - -	if (_hasPlaybackEvent) { -		if (_playbackDiff <= (_eventCount - _lastEventCount)) { -			switch (_playbackEvent.type) { -			case EVENT_MOUSEMOVE: -			case EVENT_LBUTTONDOWN: -			case EVENT_LBUTTONUP: -			case EVENT_RBUTTONDOWN: -			case EVENT_RBUTTONUP: -			case EVENT_WHEELUP: -			case EVENT_WHEELDOWN: -				g_system->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y); -				break; -			default: -				break; -			} -			ev = _playbackEvent; -			_hasPlaybackEvent = false; -			_lastEventCount = _eventCount; -			return true; -		} -	} - -	return false; -} - -} // End of namespace Common diff --git a/common/EventRecorder.h b/common/EventRecorder.h deleted file mode 100644 index 43a08b08cd..0000000000 --- a/common/EventRecorder.h +++ /dev/null @@ -1,110 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef COMMON_EVENTRECORDER_H -#define COMMON_EVENTRECORDER_H - -#include "common/scummsys.h" -#include "common/events.h" -#include "common/singleton.h" -#include "common/mutex.h" -#include "common/array.h" - -#define g_eventRec (Common::EventRecorder::instance()) - -namespace Common { - -class RandomSource; -class SeekableReadStream; -class WriteStream; - -/** - * Our generic event recorder. - * - * TODO: Add more documentation. - */ -class EventRecorder : private EventSource, private EventObserver, public Singleton<EventRecorder> { -	friend class Singleton<SingletonBaseType>; -	EventRecorder(); -	~EventRecorder(); -public: -	void init(); -	void deinit(); - -	/** Register random source so it can be serialized in game test purposes */ -	void registerRandomSource(RandomSource &rnd, const String &name); - -	/** TODO: Add documentation, this is only used by the backend */ -	void processMillis(uint32 &millis); - -	/** TODO: Add documentation, this is only used by the backend */ -	bool processDelayMillis(uint &msecs); - -private: -	bool notifyEvent(const Event &ev); -	bool notifyPoll(); -	bool pollEvent(Event &ev); -	bool allowMapping() const { return false; } - -	class RandomSourceRecord { -	public: -		String name; -		uint32 seed; -	}; -	Array<RandomSourceRecord> _randomSourceRecords; - -	bool _recordSubtitles; -	volatile uint32 _recordCount; -	volatile uint32 _lastRecordEvent; -	volatile uint32 _recordTimeCount; -	volatile uint32 _lastEventMillis; -	WriteStream *_recordFile; -	WriteStream *_recordTimeFile; -	MutexRef _timeMutex; -	MutexRef _recorderMutex; -	volatile uint32 _lastMillis; - -	volatile uint32 _playbackCount; -	volatile uint32 _playbackDiff; -	volatile bool _hasPlaybackEvent; -	volatile uint32 _playbackTimeCount; -	Event _playbackEvent; -	SeekableReadStream *_playbackFile; -	SeekableReadStream *_playbackTimeFile; - -	volatile uint32 _eventCount; -	volatile uint32 _lastEventCount; - -	enum RecordMode { -		kPassthrough = 0, -		kRecorderRecord = 1, -		kRecorderPlayback = 2 -	}; -	volatile RecordMode _recordMode; -	String _recordFileName; -	String _recordTempFileName; -	String _recordTimeFileName; -}; - -} // End of namespace Common - -#endif diff --git a/common/debug.h b/common/debug.h index dc94839082..859f3c41b3 100644 --- a/common/debug.h +++ b/common/debug.h @@ -117,5 +117,9 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);   */  extern int gDebugLevel; +//Global constant for EventRecorder debug channel +enum GlobalDebugLevels { +	kDebugLevelEventRec = 1 << 30 +};  #endif diff --git a/common/events.h b/common/events.h index 7366c51d36..9029a4096a 100644 --- a/common/events.h +++ b/common/events.h @@ -288,11 +288,14 @@ public:  	 * to the EventDispatcher, thus it will be deleted  	 * with "delete", when EventDispatcher is destroyed.  	 * -	 * Note there is only one mapper per EventDispatcher -	 * possible, thus when this method is called twice, -	 * the former mapper will be destroied. +	 * @param autoFree	Destroy previous mapper [default] +	 *         		Normally we allow only one event mapper to exists, +	 *			However Event Recorder must intervent into normal +	 *			event flow without altering its semantics. Thus during +	 *			Event Recorder playback and recording we allow +	 *			two mappers.  	 */ -	void registerMapper(EventMapper *mapper); +	void registerMapper(EventMapper *mapper, bool autoFree = true);  	/**  	 * Queries the setup event mapper. @@ -326,6 +329,7 @@ public:  	 */  	void unregisterObserver(EventObserver *obs);  private: +	bool _autoFreeMapper;  	EventMapper *_mapper;  	struct Entry { diff --git a/common/macresman.cpp b/common/macresman.cpp index 00562f746a..ba44caafd9 100644 --- a/common/macresman.cpp +++ b/common/macresman.cpp @@ -102,18 +102,18 @@ String MacResManager::computeResForkMD5AsString(uint32 length) const {  	return computeStreamMD5AsString(resForkStream, MIN<uint32>(length, _resForkSize));  } -bool MacResManager::open(String filename) { +bool MacResManager::open(const String &fileName) {  	close();  #ifdef MACOSX  	// Check the actual fork on a Mac computer -	String fullPath = ConfMan.get("path") + "/" + filename + "/..namedfork/rsrc"; +	String fullPath = ConfMan.get("path") + "/" + fileName + "/..namedfork/rsrc";  	FSNode resFsNode = FSNode(fullPath);  	if (resFsNode.exists()) {  		SeekableReadStream *macResForkRawStream = resFsNode.createReadStream();  		if (macResForkRawStream && loadFromRawFork(*macResForkRawStream)) { -			_baseFileName = filename; +			_baseFileName = fileName;  			return true;  		} @@ -123,38 +123,39 @@ bool MacResManager::open(String filename) {  	File *file = new File(); -	// First, let's try to see if the Mac converted name exists -	if (file->open(constructAppleDoubleName(filename)) && loadFromAppleDouble(*file)) { -		_baseFileName = filename; +	// Prefer standalone files first, starting with raw forks +	if (file->open(fileName + ".rsrc") && loadFromRawFork(*file)) { +		_baseFileName = fileName;  		return true;  	}  	file->close(); -	// Check .bin too -	if (file->open(filename + ".bin") && loadFromMacBinary(*file)) { -		_baseFileName = filename; +	// Then try for AppleDouble using Apple's naming +	if (file->open(constructAppleDoubleName(fileName)) && loadFromAppleDouble(*file)) { +		_baseFileName = fileName;  		return true;  	}  	file->close(); -	// Maybe we have a dumped fork? -	if (file->open(filename + ".rsrc") && loadFromRawFork(*file)) { -		_baseFileName = filename; +	// Check .bin for MacBinary next +	if (file->open(fileName + ".bin") && loadFromMacBinary(*file)) { +		_baseFileName = fileName;  		return true;  	}  	file->close(); -	// Fine, what about just the data fork? -	if (file->open(filename)) { -		_baseFileName = filename; +	// As a last resort, see if just the data fork exists +	if (file->open(fileName)) { +		_baseFileName = fileName; +		// FIXME: Is this really needed?  		if (isMacBinary(*file)) { -			file->seek(0, SEEK_SET); +			file->seek(0);  			if (loadFromMacBinary(*file))  				return true;  		} -		file->seek(0, SEEK_SET); +		file->seek(0);  		_stream = file;  		return true;  	} @@ -165,18 +166,18 @@ bool MacResManager::open(String filename) {  	return false;  } -bool MacResManager::open(FSNode path, String filename) { +bool MacResManager::open(const FSNode &path, const String &fileName) {  	close();  #ifdef MACOSX  	// Check the actual fork on a Mac computer -	String fullPath = path.getPath() + "/" + filename + "/..namedfork/rsrc"; +	String fullPath = path.getPath() + "/" + fileName + "/..namedfork/rsrc";  	FSNode resFsNode = FSNode(fullPath);  	if (resFsNode.exists()) {  		SeekableReadStream *macResForkRawStream = resFsNode.createReadStream();  		if (macResForkRawStream && loadFromRawFork(*macResForkRawStream)) { -			_baseFileName = filename; +			_baseFileName = fileName;  			return true;  		} @@ -184,52 +185,53 @@ bool MacResManager::open(FSNode path, String filename) {  	}  #endif -	// First, let's try to see if the Mac converted name exists -	FSNode fsNode = path.getChild(constructAppleDoubleName(filename)); +	// Prefer standalone files first, starting with raw forks +	FSNode fsNode = path.getChild(fileName + ".rsrc");  	if (fsNode.exists() && !fsNode.isDirectory()) {  		SeekableReadStream *stream = fsNode.createReadStream(); -		if (loadFromAppleDouble(*stream)) { -			_baseFileName = filename; +		if (loadFromRawFork(*stream)) { +			_baseFileName = fileName;  			return true;  		}  		delete stream;  	} -	// Check .bin too -	fsNode = path.getChild(filename + ".bin"); +	// Then try for AppleDouble using Apple's naming +	fsNode = path.getChild(constructAppleDoubleName(fileName));  	if (fsNode.exists() && !fsNode.isDirectory()) {  		SeekableReadStream *stream = fsNode.createReadStream(); -		if (loadFromMacBinary(*stream)) { -			_baseFileName = filename; +		if (loadFromAppleDouble(*stream)) { +			_baseFileName = fileName;  			return true;  		}  		delete stream;  	} -	// Maybe we have a dumped fork? -	fsNode = path.getChild(filename + ".rsrc"); +	// Check .bin for MacBinary next +	fsNode = path.getChild(fileName + ".bin");  	if (fsNode.exists() && !fsNode.isDirectory()) {  		SeekableReadStream *stream = fsNode.createReadStream(); -		if (loadFromRawFork(*stream)) { -			_baseFileName = filename; +		if (loadFromMacBinary(*stream)) { +			_baseFileName = fileName;  			return true;  		}  		delete stream;  	} -	// Fine, what about just the data fork? -	fsNode = path.getChild(filename); +	// As a last resort, see if just the data fork exists +	fsNode = path.getChild(fileName);  	if (fsNode.exists() && !fsNode.isDirectory()) {  		SeekableReadStream *stream = fsNode.createReadStream(); -		_baseFileName = filename; +		_baseFileName = fileName; +		// FIXME: Is this really needed?  		if (isMacBinary(*stream)) { -			stream->seek(0, SEEK_SET); +			stream->seek(0);  			if (loadFromMacBinary(*stream))  				return true;  		} -		stream->seek(0, SEEK_SET); +		stream->seek(0);  		_stream = stream;  		return true;  	} @@ -238,22 +240,22 @@ bool MacResManager::open(FSNode path, String filename) {  	return false;  } -bool MacResManager::exists(const String &filename) { +bool MacResManager::exists(const String &fileName) {  	// Try the file name by itself -	if (Common::File::exists(filename)) +	if (File::exists(fileName))  		return true;  	// Try the .rsrc extension -	if (Common::File::exists(filename + ".rsrc")) +	if (File::exists(fileName + ".rsrc"))  		return true;  	// Check if we have a MacBinary file -	Common::File tempFile; -	if (tempFile.open(filename + ".bin") && isMacBinary(tempFile)) +	File tempFile; +	if (tempFile.open(fileName + ".bin") && isMacBinary(tempFile))  		return true;  	// Check if we have an AppleDouble file -	if (tempFile.open(constructAppleDoubleName(filename)) && tempFile.readUint32BE() == 0x00051607) +	if (tempFile.open(constructAppleDoubleName(fileName)) && tempFile.readUint32BE() == 0x00051607)  		return true;  	return false; @@ -480,10 +482,10 @@ SeekableReadStream *MacResManager::getResource(uint32 typeID, uint16 resID) {  	return _stream->readStream(len);  } -SeekableReadStream *MacResManager::getResource(const String &filename) { +SeekableReadStream *MacResManager::getResource(const String &fileName) {  	for (uint32 i = 0; i < _resMap.numTypes; i++) {  		for (uint32 j = 0; j < _resTypes[i].items; j++) { -			if (_resLists[i][j].nameOffset != -1 && filename.equalsIgnoreCase(_resLists[i][j].name)) { +			if (_resLists[i][j].nameOffset != -1 && fileName.equalsIgnoreCase(_resLists[i][j].name)) {  				_stream->seek(_dataOffset + _resLists[i][j].dataOffset);  				uint32 len = _stream->readUint32BE(); @@ -499,13 +501,13 @@ SeekableReadStream *MacResManager::getResource(const String &filename) {  	return 0;  } -SeekableReadStream *MacResManager::getResource(uint32 typeID, const String &filename) { +SeekableReadStream *MacResManager::getResource(uint32 typeID, const String &fileName) {  	for (uint32 i = 0; i < _resMap.numTypes; i++) {  		if (_resTypes[i].id != typeID)  			continue;  		for (uint32 j = 0; j < _resTypes[i].items; j++) { -			if (_resLists[i][j].nameOffset != -1 && filename.equalsIgnoreCase(_resLists[i][j].name)) { +			if (_resLists[i][j].nameOffset != -1 && fileName.equalsIgnoreCase(_resLists[i][j].name)) {  				_stream->seek(_dataOffset + _resLists[i][j].dataOffset);  				uint32 len = _stream->readUint32BE(); @@ -574,7 +576,7 @@ void MacResManager::readMap() {  	}  } -Common::String MacResManager::constructAppleDoubleName(Common::String name) { +String MacResManager::constructAppleDoubleName(String name) {  	// Insert "._" before the last portion of a path name  	for (int i = name.size() - 1; i >= 0; i--) {  		if (i == 0) { diff --git a/common/macresman.h b/common/macresman.h index ed74da9cc6..cca6592f21 100644 --- a/common/macresman.h +++ b/common/macresman.h @@ -54,27 +54,32 @@ public:  	/**  	 * Open a Mac data/resource fork pair. +	 * +	 * This uses SearchMan to find the data/resource forks. This should only be used +	 * from inside an engine. +	 *  	 * @param filename The base file name of the file  	 * @note This will check for the raw resource fork, MacBinary, and AppleDouble formats.  	 * @return True on success  	 */ -	bool open(String filename); +	bool open(const String &fileName);  	/**  	 * Open a Mac data/resource fork pair. +	 *  	 * @param path The path that holds the forks  	 * @param filename The base file name of the file  	 * @note This will check for the raw resource fork, MacBinary, and AppleDouble formats.  	 * @return True on success  	 */ -	bool open(FSNode path, String filename); +	bool open(const FSNode &path, const String &fileName);  	/**  	 * See if a Mac data/resource fork pair exists.  	 * @param filename The base file name of the file  	 * @return True if either a data fork or resource fork with this name exists  	 */ -	static bool exists(const String &filename); +	static bool exists(const String &fileName);  	/**  	 * Close the Mac data/resource fork pair. @@ -94,12 +99,6 @@ public:  	bool hasResFork() const;  	/** -	 * Check if the given stream is in the MacBinary format. -	 * @param stream The stream we're checking -	 */ -	static bool isMacBinary(SeekableReadStream &stream); - -	/**  	 * Read resource from the MacBinary file  	 * @param typeID FourCC of the type  	 * @param resID Resource ID to fetch @@ -176,7 +175,13 @@ private:  	bool loadFromMacBinary(SeekableReadStream &stream);  	bool loadFromAppleDouble(SeekableReadStream &stream); -	static Common::String constructAppleDoubleName(Common::String name); +	static String constructAppleDoubleName(String name); + +	/** +	 * Check if the given stream is in the MacBinary format. +	 * @param stream The stream we're checking +	 */ +	static bool isMacBinary(SeekableReadStream &stream);  	enum {  		kResForkNone = 0, diff --git a/common/memstream.h b/common/memstream.h index 260fb64d84..7fa6500753 100644 --- a/common/memstream.h +++ b/common/memstream.h @@ -89,8 +89,9 @@ public:   */  class MemoryWriteStream : public WriteStream {  private: -	byte *_ptr;  	const uint32 _bufSize; +protected: +	byte *_ptr;  	uint32 _pos;  	bool _err;  public: @@ -117,6 +118,40 @@ public:  };  /** + * MemoryWriteStream subclass with ability to set stream position indicator. + */ +class SeekableMemoryWriteStream : public MemoryWriteStream { +private: +	byte *_ptrOrig; +public: +	SeekableMemoryWriteStream(byte *buf, uint32 len) : MemoryWriteStream(buf, len), _ptrOrig(buf) {} +	uint32 seek(uint32 offset, int whence = SEEK_SET) { +		switch (whence) { +		case SEEK_END: +			// SEEK_END works just like SEEK_SET, only 'reversed', +			// i.e. from the end. +			offset = size() + offset; +			// Fall through +		case SEEK_SET: +			_ptr = _ptrOrig + offset; +			_pos = offset; +			break; +		case SEEK_CUR: +			_ptr += offset; +			_pos += offset; +			break; +		} +		// Post-Condition +		if (_pos > size()) { +			_pos = size(); +			_ptr = _ptrOrig + _pos; +		} +		return _pos; +	} +}; + + +/**   * A sort of hybrid between MemoryWriteStream and Array classes. A stream   * that grows as it's written to.   */ diff --git a/common/module.mk b/common/module.mk index d96b11ee40..9f9126c8ef 100644 --- a/common/module.mk +++ b/common/module.mk @@ -10,7 +10,6 @@ MODULE_OBJS := \  	error.o \  	EventDispatcher.o \  	EventMapper.o \ -	EventRecorder.o \  	file.o \  	fs.o \  	gui_options.o \ @@ -51,5 +50,10 @@ MODULE_OBJS += \  	rdft.o \  	sinetables.o +ifdef ENABLE_EVENTRECORDER +MODULE_OBJS += \ +	recorderfile.o +endif +  # Include common rules  include $(srcdir)/rules.mk diff --git a/common/random.cpp b/common/random.cpp index fd75534c44..de1269b485 100644 --- a/common/random.cpp +++ b/common/random.cpp @@ -21,7 +21,7 @@  #include "common/random.h"  #include "common/system.h" -#include "common/EventRecorder.h" +#include "gui/EventRecorder.h"  namespace Common { @@ -30,13 +30,12 @@ RandomSource::RandomSource(const String &name) {  	// Use system time as RNG seed. Normally not a good idea, if you are using  	// a RNG for security purposes, but good enough for our purposes.  	assert(g_system); -	uint32 seed = g_system->getMillis(); -	setSeed(seed); -	// Register this random source with the event recorder. This may end -	// up querying or resetting the current seed, so we must call it -	// *after* the initial seed has been set. -	g_eventRec.registerRandomSource(*this, name); +#ifdef ENABLE_EVENTRECORDER +	setSeed(g_eventRec.getRandomSeed(name)); +#else +	setSeed(g_system->getMillis()); +#endif  }  void RandomSource::setSeed(uint32 seed) { diff --git a/common/recorderfile.cpp b/common/recorderfile.cpp new file mode 100644 index 0000000000..d08bc599f1 --- /dev/null +++ b/common/recorderfile.cpp @@ -0,0 +1,714 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "gui/EventRecorder.h" +#include "common/md5.h" +#include "common/recorderfile.h" +#include "common/savefile.h" +#include "common/bufferedstream.h" +#include "graphics/thumbnail.h" +#include "graphics/surface.h" +#include "graphics/scaler.h" + +#define RECORD_VERSION 1 + +namespace Common { + +PlaybackFile::PlaybackFile() : _tmpRecordFile(_tmpBuffer, kRecordBuffSize), _tmpPlaybackFile(_tmpBuffer, kRecordBuffSize) { +	_readStream = NULL; +	_writeStream = NULL; +	_screenshotsFile = NULL; +	_mode = kClosed; + +	_recordFile = 0; +	_headerDumped = false; +	_recordCount = 0; +	_eventsSize = 0; +	memset(_tmpBuffer, 1, kRecordBuffSize); +} + +PlaybackFile::~PlaybackFile() { +	close(); +} + +bool PlaybackFile::openWrite(const String &fileName) { +	close(); +	_header.fileName = fileName; +	_writeStream = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(fileName), 128 * 1024); +	_headerDumped = false; +	_recordCount = 0; +	if (_writeStream == NULL) { +		return false; +	} +	_mode = kWrite; +	return true; +} + +bool PlaybackFile::openRead(const String &fileName) { +	close(); +	_header.fileName = fileName; +	_eventsSize = 0; +	_tmpPlaybackFile.seek(0); +	_readStream = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(fileName), 128 * 1024, DisposeAfterUse::YES); +	if (_readStream == NULL) { +		debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"file %s not found\"", fileName.c_str()); +		return false; +	} +	if (!parseHeader()) { +		debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"header parsing failed\""); +		return false; +	} +	_screenshotsFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving("screenshots.bin"), 128 * 1024); +	debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=success"); +	_mode = kRead; +	return true; +} + +void PlaybackFile::close() { +	delete _readStream; +	_readStream = NULL; +	if (_writeStream != NULL) { +		dumpRecordsToFile(); +		_writeStream->finalize(); +		delete _writeStream; +		_writeStream = NULL; +		updateHeader(); +	} +	if (_screenshotsFile != NULL) { +		_screenshotsFile->finalize(); +		delete _screenshotsFile; +		_screenshotsFile = NULL; +	} +	for (HashMap<String, SaveFileBuffer>::iterator  i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) { +		free(i->_value.buffer); +	} +	_header.saveFiles.clear(); +	_mode = kClosed; +} + +bool PlaybackFile::parseHeader() { +	PlaybackFileHeader result; +	ChunkHeader nextChunk; +	_playbackParseState = kFileStateCheckFormat; +	if (!readChunkHeader(nextChunk)) { +		_playbackParseState = kFileStateError; +		return false; +	} +	while ((_playbackParseState != kFileStateDone) && (_playbackParseState != kFileStateError)) { +		if (processChunk(nextChunk)) { +			if (!readChunkHeader(nextChunk)) { +				warning("Error in header parsing"); +				_playbackParseState = kFileStateError; +			} +		} +	} +	return _playbackParseState == kFileStateDone; +} + +bool PlaybackFile::checkPlaybackFileVersion() { +	uint32 version; +	version = _readStream->readUint32LE(); +	if (version != RECORD_VERSION) { +		warning("Incorrect playback file version. Expected version %d, but got %d.", RECORD_VERSION, version); +		return false; +	} +	return true; +} + + +String PlaybackFile::readString(int len) { +	String result; +	char buf[50]; +	int readSize = 49; +	while (len > 0)	{ +		if (len <= 49) { +			readSize = len; +		} +		_readStream->read(buf, readSize); +		buf[readSize] = 0; +		result += buf; +		len -= readSize; +	} +	return result; +} + +bool PlaybackFile::readChunkHeader(PlaybackFile::ChunkHeader &nextChunk) { +	nextChunk.id = (FileTag)_readStream->readUint32LE(); +	nextChunk.len = _readStream->readUint32LE(); +	return !_readStream->err() && !_readStream->eos(); +} + +bool PlaybackFile::processChunk(ChunkHeader &nextChunk) { +	switch (_playbackParseState) { +	case kFileStateCheckFormat: +		if (nextChunk.id == kFormatIdTag) { +			_playbackParseState = kFileStateCheckVersion; +		} else { +			warning("Unknown playback file signature"); +			_playbackParseState = kFileStateError; +		} +		break; +	case kFileStateCheckVersion: +		if ((nextChunk.id == kVersionTag) && checkPlaybackFileVersion()) { +			_playbackParseState = kFileStateSelectSection; +		} else { +			_playbackParseState = kFileStateError; +		} +		break; +	case kFileStateSelectSection: +		switch (nextChunk.id) { +		case kHeaderSectionTag: +			_playbackParseState = kFileStateProcessHeader; +			break; +		case kHashSectionTag: +			_playbackParseState = kFileStateProcessHash; +			break; +		case kRandomSectionTag: +			_playbackParseState = kFileStateProcessRandom; +			break; +		case kEventTag: +		case kScreenShotTag: +			_readStream->seek(-8, SEEK_CUR); +			_playbackParseState = kFileStateDone; +			return false; +		case kSaveTag: +			_playbackParseState = kFileStateProcessSave; +			break; +		case kSettingsSectionTag: +			_playbackParseState = kFileStateProcessSettings; +			warning("Loading record header"); +			break; +		default: +			_readStream->skip(nextChunk.len); +			break; +		} +		break; +	case kFileStateProcessSave: +		if (nextChunk.id == kSaveRecordTag) { +			readSaveRecord(); +		} else { +			_playbackParseState = kFileStateSelectSection; +			return false; +		} +		break; +	case kFileStateProcessHeader: +		switch (nextChunk.id) { +		case kAuthorTag: +			_header.author = readString(nextChunk.len); +			break; +		case kCommentsTag: +			_header.notes = readString(nextChunk.len); +			break; +		case kNameTag: +			_header.name = readString(nextChunk.len); +			break; +		default: +			_playbackParseState = kFileStateSelectSection; +			return false; +		} +		break; +	case kFileStateProcessHash: +		if (nextChunk.id == kHashRecordTag) { +			readHashMap(nextChunk); +		} else { +			_playbackParseState = kFileStateSelectSection; +			return false; +		} +		break; +	case kFileStateProcessRandom: +		if (nextChunk.id == kRandomRecordTag) { +			processRndSeedRecord(nextChunk); +		} else { +			_playbackParseState = kFileStateSelectSection; +			return false; +		} +		break; +	case kFileStateProcessSettings: +		if (nextChunk.id == kSettingsRecordTag) { +			if (!processSettingsRecord()) { +				_playbackParseState = kFileStateError; +				return false; +			} +		} else { +			_playbackParseState = kFileStateSelectSection; +			return false; +		} +		break; +	default: +			return false; +	} +	return true; +} + +void PlaybackFile::returnToChunkHeader() { +	_readStream->seek(-8, SEEK_CUR); +} + +void PlaybackFile::readHashMap(ChunkHeader chunk) { +	String hashName = readString(chunk.len - 32); +	String hashMd5 = readString(32); +	_header.hashRecords[hashName] = hashMd5; +} + +void PlaybackFile::processRndSeedRecord(ChunkHeader chunk) { +	String randomSourceName = readString(chunk.len - 4); +	uint32 randomSourceSeed = _readStream->readUint32LE(); +	_header.randomSourceRecords[randomSourceName] = randomSourceSeed; +} + +bool PlaybackFile::processSettingsRecord() { +	ChunkHeader keyChunk; +	if (!readChunkHeader(keyChunk) || (keyChunk.id != kSettingsRecordKeyTag)) { +		warning("Invalid format of settings section"); +		return false; +	} +	String key = readString(keyChunk.len); +	ChunkHeader valueChunk; +	if (!readChunkHeader(valueChunk) || (valueChunk.id != kSettingsRecordValueTag)) { +		warning("Invalid format of settings section"); +		return false; +	} +	String value = readString(valueChunk.len); +	_header.settingsRecords[key] = value; +	return true; +} + + +bool PlaybackFile::readSaveRecord() { +	ChunkHeader fileNameChunk; +	if (!readChunkHeader(fileNameChunk) || (fileNameChunk.id != kSaveRecordNameTag)) { +		warning("Invalid format of save section"); +		return false; +	} +	String fileName = readString(fileNameChunk.len); +	ChunkHeader saveBufferChunk; +	if (!readChunkHeader(saveBufferChunk) || (saveBufferChunk.id != kSaveRecordBufferTag)) { +		warning("Invalid format of save section"); +		return false; +	} +	SaveFileBuffer buf; +	buf.size = saveBufferChunk.len; +	buf.buffer = (byte *)malloc(saveBufferChunk.len); +	_readStream->read(buf.buffer, buf.size); +	_header.saveFiles[fileName] = buf; +	debugC(1, kDebugLevelEventRec, "playback:action=\"Load save file\" filename=%s len=%d", fileName.c_str(), buf.size); +	return true; +} + + + +RecorderEvent PlaybackFile::getNextEvent() { +	assert(_mode == kRead); +	if (isEventsBufferEmpty()) { +		PlaybackFile::ChunkHeader header; +		header.id = kFormatIdTag; +		while (header.id != kEventTag) { +			if (!readChunkHeader(header) || _readStream->eos()) { +				break; +			} +			switch (header.id) { +			case kEventTag: +				readEventsToBuffer(header.len); +				break; +			case kScreenShotTag: +				_readStream->seek(-4, SEEK_CUR); +				header.len = _readStream->readUint32BE(); +				_readStream->skip(header.len-8); +				break; +			case kMD5Tag: +				checkRecordedMD5(); +				break; +			default: +				_readStream->skip(header.len); +				break; +			} +		} +	} +	RecorderEvent result; +	readEvent(result); +	return result; +} + +bool PlaybackFile::isEventsBufferEmpty() { +	return (uint32)_tmpPlaybackFile.pos() == _eventsSize; +} + +void PlaybackFile::readEvent(RecorderEvent& event) { +	event.recordedtype = (RecorderEventType)_tmpPlaybackFile.readByte(); +	switch (event.recordedtype) { +	case kRecorderEventTypeTimer: +		event.time = _tmpPlaybackFile.readUint32LE(); +		break; +	case kRecorderEventTypeNormal: +		event.type = (EventType)_tmpPlaybackFile.readUint32LE(); +		switch (event.type) { +		case EVENT_KEYDOWN: +		case EVENT_KEYUP: +			event.time = _tmpPlaybackFile.readUint32LE(); +			event.kbd.keycode = (KeyCode)_tmpPlaybackFile.readSint32LE(); +			event.kbd.ascii = _tmpPlaybackFile.readUint16LE(); +			event.kbd.flags = _tmpPlaybackFile.readByte(); +			break; +		case EVENT_MOUSEMOVE: +		case EVENT_LBUTTONDOWN: +		case EVENT_LBUTTONUP: +		case EVENT_RBUTTONDOWN: +		case EVENT_RBUTTONUP: +		case EVENT_WHEELUP: +		case EVENT_WHEELDOWN: +		case EVENT_MBUTTONDOWN: +		case EVENT_MBUTTONUP: +			event.time = _tmpPlaybackFile.readUint32LE(); +			event.mouse.x = _tmpPlaybackFile.readSint16LE(); +			event.mouse.y = _tmpPlaybackFile.readSint16LE(); +			break; +		default: +			event.time = _tmpPlaybackFile.readUint32LE(); +			break; +		} +		break; +	} +	event.synthetic = true; +} + +void PlaybackFile::readEventsToBuffer(uint32 size) { +	_readStream->read(_tmpBuffer, size); +	_tmpPlaybackFile.seek(0); +	_eventsSize = size; +} + +void PlaybackFile::saveScreenShot(Graphics::Surface &screen, byte md5[16]) { +	dumpRecordsToFile(); +	_writeStream->writeUint32LE(kMD5Tag); +	_writeStream->writeUint32LE(16); +	_writeStream->write(md5, 16); +	Graphics::saveThumbnail(*_writeStream, screen); +} + +void PlaybackFile::dumpRecordsToFile() { +	if (!_headerDumped) { +		dumpHeaderToFile(); +		_headerDumped = true; +	} +	if (_recordCount == 0) { +		return; +	} +	_writeStream->writeUint32LE(kEventTag); +	_writeStream->writeUint32LE(_tmpRecordFile.pos()); +	_writeStream->write(_tmpBuffer, _tmpRecordFile.pos()); +	_tmpRecordFile.seek(0); +	_recordCount = 0; +} + +void PlaybackFile::dumpHeaderToFile() { +	_writeStream->writeUint32LE(kFormatIdTag); +	// Specify size for first tag as NULL since we cannot calculate +	// size of the file at time of the header dumping +	_writeStream->writeUint32LE(0); +	_writeStream->writeUint32LE(kVersionTag); +	_writeStream->writeUint32LE(4); +	_writeStream->writeUint32LE(RECORD_VERSION); +	writeHeaderSection(); +	writeGameHash(); +	writeRandomRecords(); +	writeGameSettings(); +	writeSaveFilesSection(); +} + +void PlaybackFile::writeHeaderSection() { +	uint32 headerSize = 0; +	if (!_header.author.empty()) { +		headerSize = _header.author.size() + 8; +	} +	if (!_header.notes.empty()) { +		headerSize += _header.notes.size() + 8; +	} +	if (!_header.name.empty()) { +		headerSize += _header.name.size() + 8; +	} +	if (headerSize == 0) { +		return; +	} +	_writeStream->writeUint32LE(kHeaderSectionTag); +	_writeStream->writeUint32LE(headerSize); +	if (!_header.author.empty()) { +		_writeStream->writeUint32LE(kAuthorTag); +		_writeStream->writeUint32LE(_header.author.size()); +		_writeStream->writeString(_header.author); +	} +	if (!_header.notes.empty()) { +		_writeStream->writeUint32LE(kCommentsTag); +		_writeStream->writeUint32LE(_header.notes.size()); +		_writeStream->writeString(_header.notes); +	} +	if (!_header.name.empty()) { +		_writeStream->writeUint32LE(kNameTag); +		_writeStream->writeUint32LE(_header.name.size()); +		_writeStream->writeString(_header.name); +	} +} + +void PlaybackFile::writeGameHash() { +	uint32 hashSectionSize = 0; +	for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) { +		hashSectionSize = hashSectionSize + i->_key.size() + i->_value.size() + 8; +	} +	if (_header.hashRecords.size() == 0) { +		return; +	} +	_writeStream->writeUint32LE(kHashSectionTag); +	_writeStream->writeUint32LE(hashSectionSize); +	for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) { +		_writeStream->writeUint32LE(kHashRecordTag); +		_writeStream->writeUint32LE(i->_key.size() + i->_value.size()); +		_writeStream->writeString(i->_key); +		_writeStream->writeString(i->_value); +	} +} + +void PlaybackFile::writeRandomRecords() { +	uint32 randomSectionSize = 0; +	for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) { +		randomSectionSize = randomSectionSize + i->_key.size() + 12; +	} +	if (_header.randomSourceRecords.size() == 0) { +		return; +	} +	_writeStream->writeUint32LE(kRandomSectionTag); +	_writeStream->writeUint32LE(randomSectionSize); +	for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) { +		_writeStream->writeUint32LE(kRandomRecordTag); +		_writeStream->writeUint32LE(i->_key.size() + 4); +		_writeStream->writeString(i->_key); +		_writeStream->writeUint32LE(i->_value); +	} +} + +void PlaybackFile::writeEvent(const RecorderEvent &event) { +	assert(_mode == kWrite); +	_recordCount++; +	_tmpRecordFile.writeByte(event.recordedtype); +	switch (event.recordedtype) { +	case kRecorderEventTypeTimer: +		_tmpRecordFile.writeUint32LE(event.time); +		break; +	case kRecorderEventTypeNormal: +		_tmpRecordFile.writeUint32LE((uint32)event.type); +		switch(event.type) { +		case EVENT_KEYDOWN: +		case EVENT_KEYUP: +			_tmpRecordFile.writeUint32LE(event.time); +			_tmpRecordFile.writeSint32LE(event.kbd.keycode); +			_tmpRecordFile.writeUint16LE(event.kbd.ascii); +			_tmpRecordFile.writeByte(event.kbd.flags); +			break; +		case EVENT_MOUSEMOVE: +		case EVENT_LBUTTONDOWN: +		case EVENT_LBUTTONUP: +		case EVENT_RBUTTONDOWN: +		case EVENT_RBUTTONUP: +		case EVENT_WHEELUP: +		case EVENT_WHEELDOWN: +		case EVENT_MBUTTONDOWN: +		case EVENT_MBUTTONUP: +			_tmpRecordFile.writeUint32LE(event.time); +			_tmpRecordFile.writeSint16LE(event.mouse.x); +			_tmpRecordFile.writeSint16LE(event.mouse.y); +			break; +		default: +			_tmpRecordFile.writeUint32LE(event.time); +			break; +		} +		break; +	} +	if (_recordCount == kMaxBufferedRecords) { +		dumpRecordsToFile(); +	} +} + +void PlaybackFile::writeGameSettings() { +	_writeStream->writeUint32LE(kSettingsSectionTag); +	uint32 settingsSectionSize = 0; +	for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) { +		settingsSectionSize += i->_key.size() + i->_value.size() + 24; +	} +	_writeStream->writeUint32LE(settingsSectionSize); +	for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) { +		_writeStream->writeUint32LE(kSettingsRecordTag); +		_writeStream->writeUint32LE(i->_key.size() + i->_value.size() + 16); +		_writeStream->writeUint32LE(kSettingsRecordKeyTag); +		_writeStream->writeUint32LE(i->_key.size()); +		_writeStream->writeString(i->_key); +		_writeStream->writeUint32LE(kSettingsRecordValueTag); +		_writeStream->writeUint32LE(i->_value.size()); +		_writeStream->writeString(i->_value); +	} +} + +int PlaybackFile::getScreensCount() { +	if (_mode != kRead) { +		return 0; +	} +	_readStream->seek(0); +	int result = 0; +	while (skipToNextScreenshot()) { +		uint32 size = _readStream->readUint32BE(); +		_readStream->skip(size-8); +		++result; +	} +	return result; +} + +bool PlaybackFile::skipToNextScreenshot() { +	while (true) { +		FileTag id = (FileTag)_readStream->readUint32LE(); +		if (_readStream->eos()) { +			break; +		} +		if (id == kScreenShotTag) { +			return true; +		} +		else { +			uint32 size = _readStream->readUint32LE(); +			_readStream->skip(size); +		} +	} +	return false; +} + +Graphics::Surface *PlaybackFile::getScreenShot(int number) { +	if (_mode != kRead) { +		return NULL; +	} +	_readStream->seek(0); +	int screenCount = 1; +	while (skipToNextScreenshot()) { +		if (screenCount == number) { +			screenCount++; +			_readStream->seek(-4, SEEK_CUR); +			return Graphics::loadThumbnail(*_readStream); +		} else { +			uint32 size = _readStream->readUint32BE(); +			_readStream->skip(size-8); +			screenCount++; +		} +	} +	return NULL; +} + +void PlaybackFile::updateHeader() { +	if (_mode == kWrite) { +		_readStream = g_system->getSavefileManager()->openForLoading(_header.fileName); +	} +	_readStream->seek(0); +	skipHeader(); +	String tmpFilename = "_" + _header.fileName; +	_writeStream = g_system->getSavefileManager()->openForSaving(tmpFilename); +	dumpHeaderToFile(); +	uint32 readedSize = 0; +	do { +		readedSize = _readStream->read(_tmpBuffer, kRecordBuffSize); +		_writeStream->write(_tmpBuffer, readedSize); +	} while (readedSize != 0); +	delete _writeStream; +	_writeStream = NULL; +	delete _readStream; +	_readStream = NULL; +	g_system->getSavefileManager()->removeSavefile(_header.fileName); +	g_system->getSavefileManager()->renameSavefile(tmpFilename, _header.fileName); +	if (_mode == kRead) { +		openRead(_header.fileName); +	} +} + +void PlaybackFile::skipHeader() { +	while (true) { +		uint32 id = _readStream->readUint32LE(); +		if (_readStream->eos()) { +			break; +		} +		if ((id == kScreenShotTag) || (id == kEventTag) || (id == kMD5Tag)) { +			_readStream->seek(-4, SEEK_CUR); +			return; +		} +		else { +			uint32 size = _readStream->readUint32LE(); +			_readStream->skip(size); +		} +	} +} + +void PlaybackFile::addSaveFile(const String &fileName, InSaveFile *saveStream) { +	uint oldPos = saveStream->pos(); +	saveStream->seek(0); +	_header.saveFiles[fileName].buffer = (byte *)malloc(saveStream->size()); +	_header.saveFiles[fileName].size = saveStream->size(); +	saveStream->read(_header.saveFiles[fileName].buffer, saveStream->size()); +	saveStream->seek(oldPos); +} + +void PlaybackFile::writeSaveFilesSection() { +	uint size = 0; +	for (HashMap<String, SaveFileBuffer>::iterator  i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) { +		size += i->_value.size + i->_key.size() + 24; +	} +	if (size == 0) { +		return; +	} +	_writeStream->writeSint32LE(kSaveTag); +	_writeStream->writeSint32LE(size); +	for (HashMap<String, SaveFileBuffer>::iterator  i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) { +		_writeStream->writeSint32LE(kSaveRecordTag); +		_writeStream->writeSint32LE(i->_key.size() + i->_value.size + 16); +		_writeStream->writeSint32LE(kSaveRecordNameTag); +		_writeStream->writeSint32LE(i->_key.size()); +		_writeStream->writeString(i->_key); +		_writeStream->writeSint32LE(kSaveRecordBufferTag); +		_writeStream->writeSint32LE(i->_value.size); +		_writeStream->write(i->_value.buffer, i->_value.size); +	} +} + + +void PlaybackFile::checkRecordedMD5() { +	uint8 currentMD5[16]; +	uint8 savedMD5[16]; +	Graphics::Surface screen; +	_readStream->read(savedMD5, 16); +	if (!g_eventRec.grabScreenAndComputeMD5(screen, currentMD5)) { +		return; +	} +	uint32 seconds = g_system->getMillis(true) / 1000; +	String screenTime = String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60); +	if (memcmp(savedMD5, currentMD5, 16) != 0) { +		debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = fail", screenTime.c_str()); +		warning("Recorded and current screenshots are different"); +	} else { +		debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = success", screenTime.c_str()); +	} +	Graphics::saveThumbnail(*_screenshotsFile, screen); +	screen.free(); +} + + +} diff --git a/common/recorderfile.h b/common/recorderfile.h new file mode 100644 index 0000000000..1c95e5a915 --- /dev/null +++ b/common/recorderfile.h @@ -0,0 +1,180 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef COMMON_RECORDERFILE_H +#define COMMON_RECORDERFILE_H + +#include "common/scummsys.h" +#include "common/events.h" +#include "common/mutex.h" +#include "common/memstream.h" +#include "common/config-manager.h" +#include "common/savefile.h" + +//capacity of records buffer +#define kMaxBufferedRecords 10000 +#define kRecordBuffSize sizeof(RecorderEvent) * kMaxBufferedRecords + +namespace Common { + +enum RecorderEventType { +	kRecorderEventTypeNormal = 0, +	kRecorderEventTypeTimer = 1 +}; + +struct RecorderEvent : Event { +	RecorderEventType recordedtype; +	uint32 time; +}; + + + +class PlaybackFile { +	typedef HashMap<String, uint32, IgnoreCase_Hash, IgnoreCase_EqualTo> RandomSeedsDictionary; +	enum fileMode { +		kRead = 0, +		kWrite = 1, +		kClosed = 2 +	}; +	enum PlaybackFileState { +		kFileStateCheckFormat, +		kFileStateCheckVersion, +		kFileStateProcessHash, +		kFileStateProcessHeader, +		kFileStateProcessRandom, +		kFileStateSelectSection, +		kFileStateProcessSettings, +		kFileStateProcessSave, +		kFileStateDone, +		kFileStateError +	}; +	enum FileTag { +		kFormatIdTag = MKTAG('P','B','C','K'), +		kVersionTag = MKTAG('V','E','R','S'), +		kHeaderSectionTag = MKTAG('H','E','A','D'), +		kHashSectionTag = MKTAG('H','A','S','H'), +		kRandomSectionTag = MKTAG('R','A','N','D'), +		kEventTag = MKTAG('E','V','N','T'), +		kScreenShotTag = MKTAG('B','M','H','T'), +		kSettingsSectionTag = MKTAG('S','E','T','T'), +		kAuthorTag = MKTAG('H','A','U','T'), +		kCommentsTag = MKTAG('H','C','M','T'), +		kNameTag = MKTAG('H','N','A','M'), +		kHashRecordTag = MKTAG('H','R','C','D'), +		kRandomRecordTag = MKTAG('R','R','C','D'), +		kSettingsRecordTag = MKTAG('S','R','E','C'), +		kSettingsRecordKeyTag = MKTAG('S','K','E','Y'), +		kSettingsRecordValueTag = MKTAG('S','V','A','L'), +		kSaveTag = MKTAG('S','A','V','E'), +		kSaveRecordTag = MKTAG('R','S','A','V'), +		kSaveRecordNameTag = MKTAG('S','N','A','M'), +		kSaveRecordBufferTag = MKTAG('S','B','U','F'), +		kMD5Tag = MKTAG('M','D','5',' ') +	}; +	struct ChunkHeader { +		FileTag id; +		uint32 len; +	}; +public: +	struct SaveFileBuffer { +		byte *buffer; +		uint32 size; +	}; +	struct PlaybackFileHeader { +		String fileName; +		String author; +		String name; +		String notes; +		String description; +		StringMap hashRecords; +		StringMap settingsRecords; +		HashMap<String, SaveFileBuffer> saveFiles; +		RandomSeedsDictionary randomSourceRecords; +	}; +	PlaybackFile(); +	~PlaybackFile(); + +	bool openWrite(const String &fileName); +	bool openRead(const String &fileName); +	void close(); + +	RecorderEvent getNextEvent(); +	void writeEvent(const RecorderEvent &event); + +	void saveScreenShot(Graphics::Surface &screen, byte md5[16]); +	Graphics::Surface *getScreenShot(int number); +	int getScreensCount(); + +	bool isEventsBufferEmpty(); +	PlaybackFileHeader &getHeader() {return _header;} +	void updateHeader(); +	void addSaveFile(const String &fileName, InSaveFile *saveStream); +private: +	WriteStream *_recordFile; +	WriteStream *_writeStream; +	WriteStream *_screenshotsFile; +	MemoryReadStream _tmpPlaybackFile; +	SeekableReadStream *_readStream; +	SeekableMemoryWriteStream _tmpRecordFile; + +	fileMode _mode; +	bool _headerDumped; +	int _recordCount; +	uint32 _eventsSize; +	byte _tmpBuffer[kRecordBuffSize]; +	PlaybackFileHeader _header; +	PlaybackFileState _playbackParseState; + +	void skipHeader(); +	bool parseHeader(); +	bool processChunk(ChunkHeader &nextChunk); +	void returnToChunkHeader(); + +	bool readSaveRecord(); +	void checkRecordedMD5(); +	bool readChunkHeader(ChunkHeader &nextChunk); +	void processRndSeedRecord(ChunkHeader chunk); +	bool processSettingsRecord(); + +	bool checkPlaybackFileVersion(); + +	void dumpHeaderToFile(); +	void writeSaveFilesSection(); +	void writeGameSettings(); +	void writeHeaderSection(); +	void writeGameHash(); +	void writeRandomRecords(); + +	void dumpRecordsToFile(); + +	String readString(int len); +	void readHashMap(ChunkHeader chunk); + +	bool skipToNextScreenshot(); +	void readEvent(RecorderEvent& event); +	void readEventsToBuffer(uint32 size); +	bool grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]); +}; + +} // End of namespace Common + +#endif diff --git a/common/str.cpp b/common/str.cpp index 5d647ee4f0..4a10792373 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -361,6 +361,25 @@ void String::deleteChar(uint32 p) {  	_size--;  } +void String::erase(uint32 p, uint32 len) { +	assert(p < _size); + +	makeUnique(); +	// If len == npos or p + len is over the end, remove all the way to the end +	if (len == npos || p + len >= _size) { +		// Delete char at p as well. So _size = (p - 1) + 1 +		_size = p; +		// Null terminate +		_str[_size] = 0; +		return; +	} + +	for ( ; p + len <= _size; p++) { +		_str[p] = _str[p + len]; +	} +	_size -= len; +} +  void String::clear() {  	decRefCount(_extern._refCount); diff --git a/common/str.h b/common/str.h index 5039130707..6b4475e1c4 100644 --- a/common/str.h +++ b/common/str.h @@ -43,6 +43,8 @@ namespace Common {   * behavior in some operations.   */  class String { +public: +	static const uint32 npos = 0xFFFFFFFF;  protected:  	/**  	 * The size of the internal storage. Increasing this means less heap @@ -191,6 +193,9 @@ public:  	/** Remove the character at position p from the string. */  	void deleteChar(uint32 p); +	/** Remove all characters from position p to the p + len. If len = String::npos, removes all characters to the end */ +	void erase(uint32 p, uint32 len = npos); +  	/** Set character c at position p, replacing the previous character there. */  	void setChar(char c, uint32 p); diff --git a/common/system.cpp b/common/system.cpp index 59210544ab..d86b5b2b81 100644 --- a/common/system.cpp +++ b/common/system.cpp @@ -30,6 +30,9 @@  #include "common/taskbar.h"  #include "common/updates.h"  #include "common/textconsole.h" +#ifdef ENABLE_EVENTRECORDER +#include "gui/EventRecorder.h" +#endif  #include "backends/audiocd/default/default-audiocd.h"  #include "backends/fs/fs-factory.h" @@ -84,7 +87,7 @@ void OSystem::initBackend() {  		error("Backend failed to instantiate audio CD manager");  	if (!_eventManager)  		error("Backend failed to instantiate event manager"); -	if (!_timerManager) +	if (!getTimerManager())  		error("Backend failed to instantiate timer manager");  	// TODO: We currently don't check _savefileManager, because at least @@ -152,3 +155,15 @@ Common::String OSystem::getDefaultConfigFileName() {  Common::String OSystem::getSystemLanguage() const {  	return "en_US";  } + +Common::TimerManager *OSystem::getTimerManager() { +	return _timerManager; +} + +Common::SaveFileManager *OSystem::getSavefileManager() { +#ifdef ENABLE_EVENTRECORDER +	return g_eventRec.getSaveManager(_savefileManager); +#else +	return _savefileManager; +#endif +} diff --git a/common/system.h b/common/system.h index 99b947d7f3..81c4bdf34e 100644 --- a/common/system.h +++ b/common/system.h @@ -890,8 +890,14 @@ public:  	/** @name Events and Time */  	//@{ -	/** Get the number of milliseconds since the program was started. */ -	virtual uint32 getMillis() = 0; +	/** Get the number of milliseconds since the program was started. + +	    @param skipRecord   Skip recording of this value by event recorder. +	    			This could be needed particularly when we are in +				an on-screen GUI loop where player can pause +				the recording. +	*/ +	virtual uint32 getMillis(bool skipRecord = false) = 0;  	/** Delay/sleep for the specified amount of milliseconds. */  	virtual void delayMillis(uint msecs) = 0; @@ -907,9 +913,7 @@ public:  	 * Return the timer manager singleton. For more information, refer  	 * to the TimerManager documentation.  	 */ -	inline Common::TimerManager *getTimerManager() { -		return _timerManager; -	} +	virtual Common::TimerManager *getTimerManager();  	/**  	 * Return the event manager singleton. For more information, refer @@ -1086,9 +1090,7 @@ public:  	 * and other modifiable persistent game data. For more information,  	 * refer to the SaveFileManager documentation.  	 */ -	inline Common::SaveFileManager *getSavefileManager() { -		return _savefileManager; -	} +	Common::SaveFileManager *getSavefileManager();  #if defined(USE_TASKBAR)  	/** diff --git a/common/winexe_ne.cpp b/common/winexe_ne.cpp index 6bb40e0980..c3698d5fce 100644 --- a/common/winexe_ne.cpp +++ b/common/winexe_ne.cpp @@ -231,7 +231,7 @@ bool NEResources::readResourceTable(uint32 offset) {  			if (id & 0x8000)  				res.id = id & 0x7FFF;  			else -				res.id = getResourceString(*_exe, offset + id);				 +				res.id = getResourceString(*_exe, offset + id);  			if (typeID & 0x8000 && ((typeID & 0x7FFF) < ARRAYSIZE(s_resTypeNames)) && s_resTypeNames[typeID & 0x7FFF][0] != 0)  				debug(2, "Found resource %s %s", s_resTypeNames[typeID & 0x7FFF], res.id.toString().c_str());  | 
